diff --git a/backend/src/assets/assets.controller.ts b/backend/src/assets/assets.controller.ts new file mode 100644 index 0000000..5d0adc9 --- /dev/null +++ b/backend/src/assets/assets.controller.ts @@ -0,0 +1,50 @@ +import { + Controller, + Get, + Post, + Body, + Param, + Delete, + Patch, +} from '@nestjs/common'; +import { AssetsService } from './assets.service'; +import { CreateAssetDto } from './dto/create-asset.dto'; +import { UpdateAssetDto } from './dto/update-asset.dto'; +import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; + +@ApiTags('Assets') +@Controller('api/v1/assets') +export class AssetsController { + constructor(private readonly assetsService: AssetsService) {} + + @Post() + @ApiOperation({ summary: 'Register a new asset' }) + @ApiResponse({ status: 201, description: 'Asset created successfully' }) + create(@Body() dto: CreateAssetDto) { + return this.assetsService.create(dto); + } + + @Get() + @ApiOperation({ summary: 'Get all registered assets' }) + findAll() { + return this.assetsService.findAll(); + } + + @Get(':id') + @ApiOperation({ summary: 'Get an asset by ID' }) + findOne(@Param('id') id: string) { + return this.assetsService.findOne(+id); + } + + @Patch(':id') + @ApiOperation({ summary: 'Update an asset' }) + update(@Param('id') id: string, @Body() dto: UpdateAssetDto) { + return this.assetsService.update(+id, dto); + } + + @Delete(':id') + @ApiOperation({ summary: 'Delete an asset' }) + remove(@Param('id') id: string) { + return this.assetsService.remove(+id); + } +} diff --git a/backend/src/assets/assets.service.ts b/backend/src/assets/assets.service.ts new file mode 100644 index 0000000..7d37a77 --- /dev/null +++ b/backend/src/assets/assets.service.ts @@ -0,0 +1,85 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Asset } from './entities/asset.entity'; +import { CreateAssetDto } from './dto/create-asset.dto'; +import { UpdateAssetDto } from './dto/update-asset.dto'; +import { Supplier } from '../suppliers/entities/supplier.entity'; +import { Department } from '../departments/entities/department.entity'; +import { Category } from '../categories/entities/category.entity'; + +@Injectable() +export class AssetsService { + constructor( + @InjectRepository(Asset) + private assetsRepo: Repository, + @InjectRepository(Supplier) + private supplierRepo: Repository, + @InjectRepository(Department) + private departmentRepo: Repository, + @InjectRepository(Category) + private categoryRepo: Repository, + ) {} + + async create(dto: CreateAssetDto): Promise { + const supplier = await this.supplierRepo.findOneBy({ id: dto.supplierId }); + if (!supplier) throw new NotFoundException('Supplier not found'); + + const category = await this.categoryRepo.findOneBy({ id: dto.categoryId }); + if (!category) throw new NotFoundException('Category not found'); + + let department: Department = null; + if (dto.assignedDepartmentId) { + department = await this.departmentRepo.findOneBy({ id: dto.assignedDepartmentId }); + if (!department) throw new NotFoundException('Department not found'); + } + + const asset = this.assetsRepo.create({ + ...dto, + purchaseDate: new Date(dto.purchaseDate), + warrantyEnd: dto.warrantyEnd ? new Date(dto.warrantyEnd) : null, + supplier, + category, + assignedDepartment: department, + }); + + return this.assetsRepo.save(asset); + } + + async findAll(): Promise { + return this.assetsRepo.find({ + relations: ['supplier', 'category', 'assignedDepartment'], + }); + } + + async findOne(id: number): Promise { + const asset = await this.assetsRepo.findOne({ + where: { id }, + relations: ['supplier', 'category', 'assignedDepartment'], + }); + if (!asset) throw new NotFoundException(`Asset with ID ${id} not found`); + return asset; + } + + async update(id: number, dto: UpdateAssetDto): Promise { + const asset = await this.findOne(id); + + if (dto.supplierId) { + asset.supplier = await this.supplierRepo.findOneBy({ id: dto.supplierId }); + } + if (dto.categoryId) { + asset.category = await this.categoryRepo.findOneBy({ id: dto.categoryId }); + } + if (dto.assignedDepartmentId) { + asset.assignedDepartment = await this.departmentRepo.findOneBy({ id: dto.assignedDepartmentId }); + } + + Object.assign(asset, dto); + return this.assetsRepo.save(asset); + } + + async remove(id: number): Promise { + const asset = await this.findOne(id); + await this.assetsRepo.remove(asset); + } +} diff --git a/backend/src/assets/dto/create-asset.dto.ts b/backend/src/assets/dto/create-asset.dto.ts new file mode 100644 index 0000000..69775b1 --- /dev/null +++ b/backend/src/assets/dto/create-asset.dto.ts @@ -0,0 +1,31 @@ +import { IsString, IsDateString, IsOptional, IsNumber } from 'class-validator'; + +export class CreateAssetDto { + @IsString() + serialNumber: string; + + @IsDateString() + purchaseDate: string; + + @IsOptional() + @IsDateString() + warrantyEnd?: string; + + @IsNumber() + supplierId: number; + + @IsOptional() + @IsNumber() + assignedDepartmentId?: number; + + @IsNumber() + categoryId: number; + + @IsOptional() + @IsNumber() + purchaseCost?: number; + + @IsOptional() + @IsString() + description?: string; +} diff --git a/backend/src/assets/dto/update-asset.dto.ts b/backend/src/assets/dto/update-asset.dto.ts new file mode 100644 index 0000000..891e144 --- /dev/null +++ b/backend/src/assets/dto/update-asset.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateAssetDto } from './create-asset.dto'; + +export class UpdateAssetDto extends PartialType(CreateAssetDto) {} diff --git a/backend/src/assets/entity/asset.entity.ts b/backend/src/assets/entity/asset.entity.ts new file mode 100644 index 0000000..0e1df50 --- /dev/null +++ b/backend/src/assets/entity/asset.entity.ts @@ -0,0 +1,54 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { Supplier } from '../../suppliers/entities/supplier.entity'; +import { Department } from '../../departments/entities/department.entity'; +import { Category } from '../../categories/entities/category.entity'; + +@Entity('assets') +export class Asset { + @PrimaryGeneratedColumn() + id: number; + + @Column({ unique: true }) + serialNumber: string; + + @Column({ type: 'date' }) + purchaseDate: Date; + + @Column({ type: 'date', nullable: true }) + warrantyEnd?: Date; + + @ManyToOne(() => Supplier, { nullable: false }) + @JoinColumn({ name: 'supplier_id' }) + supplier: Supplier; + + @ManyToOne(() => Department, { nullable: true }) + @JoinColumn({ name: 'assigned_department_id' }) + assignedDepartment?: Department; + + @ManyToOne(() => Category, { nullable: false }) + @JoinColumn({ name: 'category_id' }) + category: Category; + + @Column({ type: 'decimal', precision: 12, scale: 2, nullable: true }) + purchaseCost?: number; + + @Column({ type: 'text', nullable: true }) + description?: string; + + @Column({ default: true }) + isActive: boolean; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +}