Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions backend/src/asset-disposals/asset-disposals.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Controller, Post, Body, Get, Param, UsePipes, ValidationPipe } from '@nestjs/common';
import { AssetDisposalsService } from './asset-disposals.service';
import { CreateAssetDisposalDto } from './dto/create-asset-disposal.dto';

@Controller('asset-disposals')
export class AssetDisposalsController {
constructor(private readonly disposalsService: AssetDisposalsService) {}

@Post()
@UsePipes(new ValidationPipe({ whitelist: true }))
markDisposed(@Body() dto: CreateAssetDisposalDto) {
return this.disposalsService.markDisposed(dto);
}

@Get()
findAll() {
return this.disposalsService.findAll();
}

@Get('asset/:assetId')
findByAsset(@Param('assetId') assetId: string) {
return this.disposalsService.findByAsset(assetId);
}
}
14 changes: 14 additions & 0 deletions backend/src/asset-disposals/asset-disposals.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AssetDisposalsService } from './asset-disposals.service';
import { AssetDisposalsController } from './asset-disposals.controller';
import { AssetDisposal } from './entities/asset-disposal.entity';
import { InventoryItem } from '../inventory/entities/inventory-item.entity';

@Module({
imports: [TypeOrmModule.forFeature([AssetDisposal, InventoryItem])],
controllers: [AssetDisposalsController],
providers: [AssetDisposalsService],
exports: [AssetDisposalsService],
})
export class AssetDisposalsModule {}
54 changes: 54 additions & 0 deletions backend/src/asset-disposals/asset-disposals.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AssetDisposal } from './entities/asset-disposal.entity';
import { CreateAssetDisposalDto } from './dto/create-asset-disposal.dto';
import { InventoryItem, InventoryStatus } from '../inventory/entities/inventory-item.entity';

@Injectable()
export class AssetDisposalsService {
constructor(
@InjectRepository(AssetDisposal)
private readonly disposalRepository: Repository<AssetDisposal>,
@InjectRepository(InventoryItem)
private readonly inventoryRepository: Repository<InventoryItem>,
) {}

async markDisposed(dto: CreateAssetDisposalDto) {
const asset = await this.inventoryRepository.findOne({ where: { id: dto.assetId } });
if (!asset) {
throw new NotFoundException(`Asset with ID ${dto.assetId} not found`);
}

if (asset.status === InventoryStatus.DISPOSED) {
throw new BadRequestException('Asset is already disposed');
}

return await this.inventoryRepository.manager.transaction(async (manager) => {
const itemRepo = manager.getRepository(InventoryItem);
const disposalRepo = manager.getRepository(AssetDisposal);

asset.status = InventoryStatus.DISPOSED;
await itemRepo.save(asset);

const disposal = disposalRepo.create({
assetId: dto.assetId,
disposalDate: new Date(dto.disposalDate),
method: dto.method,
reason: dto.reason,
approvedBy: dto.approvedBy,
});
const saved = await disposalRepo.save(disposal);

return { message: 'Asset marked as disposed', disposal: saved };
});
}

async findAll() {
return this.disposalRepository.find({ order: { disposalDate: 'DESC' } });
}

async findByAsset(assetId: string) {
return this.disposalRepository.find({ where: { assetId }, order: { disposalDate: 'DESC' } });
}
}
21 changes: 21 additions & 0 deletions backend/src/asset-disposals/dto/create-asset-disposal.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IsUUID, IsDateString, IsEnum, IsString, IsNotEmpty, IsOptional } from 'class-validator';
import { DisposalMethod } from '../entities/asset-disposal.entity';

export class CreateAssetDisposalDto {
@IsUUID()
assetId: string;

@IsDateString()
disposalDate: string;

@IsEnum(DisposalMethod)
method: DisposalMethod;

@IsString()
@IsOptional()
reason?: string;

@IsString()
@IsNotEmpty()
approvedBy: string;
}
37 changes: 37 additions & 0 deletions backend/src/asset-disposals/entities/asset-disposal.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';

export enum DisposalMethod {
SALE = 'sale',
DONATION = 'donation',
SCRAP = 'scrap',
OTHER = 'other',
}

@Entity('asset_disposals')
@Index(['assetId', 'disposalDate'])
export class AssetDisposal {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column('uuid')
@Index()
assetId: string;

@Column({ type: 'timestamp' })
disposalDate: Date;

@Column({ type: 'enum', enum: DisposalMethod })
method: DisposalMethod;

@Column({ type: 'text', nullable: true })
reason: string;

@Column()
approvedBy: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
31 changes: 31 additions & 0 deletions backend/src/asset-maintenance/asset-maintenance.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Controller, Post, Body, Get, Param, Patch, UsePipes, ValidationPipe } from '@nestjs/common';
import { AssetMaintenanceService } from './asset-maintenance.service';
import { ScheduleMaintenanceDto } from './dto/schedule-maintenance.dto';
import { CompleteMaintenanceDto } from './dto/complete-maintenance.dto';

@Controller('asset-maintenance')
export class AssetMaintenanceController {
constructor(private readonly maintenanceService: AssetMaintenanceService) {}

@Post('schedule')
@UsePipes(new ValidationPipe({ whitelist: true }))
schedule(@Body() dto: ScheduleMaintenanceDto) {
return this.maintenanceService.schedule(dto);
}

@Patch(':id/complete')
@UsePipes(new ValidationPipe({ whitelist: true }))
complete(@Param('id') id: string, @Body() dto: CompleteMaintenanceDto) {
return this.maintenanceService.complete(id, dto);
}

@Get()
findAll() {
return this.maintenanceService.findAll();
}

@Get('asset/:assetId')
findByAsset(@Param('assetId') assetId: string) {
return this.maintenanceService.findByAsset(assetId);
}
}
14 changes: 14 additions & 0 deletions backend/src/asset-maintenance/asset-maintenance.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AssetMaintenanceService } from './asset-maintenance.service';
import { AssetMaintenanceController } from './asset-maintenance.controller';
import { AssetMaintenance } from './entities/asset-maintenance.entity';
import { InventoryItem } from '../inventory/entities/inventory-item.entity';

@Module({
imports: [TypeOrmModule.forFeature([AssetMaintenance, InventoryItem])],
controllers: [AssetMaintenanceController],
providers: [AssetMaintenanceService],
exports: [AssetMaintenanceService],
})
export class AssetMaintenanceModule {}
63 changes: 63 additions & 0 deletions backend/src/asset-maintenance/asset-maintenance.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { AssetMaintenance } from './entities/asset-maintenance.entity';
import { ScheduleMaintenanceDto } from './dto/schedule-maintenance.dto';
import { CompleteMaintenanceDto } from './dto/complete-maintenance.dto';
import { InventoryItem, InventoryStatus } from '../inventory/entities/inventory-item.entity';

@Injectable()
export class AssetMaintenanceService {
constructor(
@InjectRepository(AssetMaintenance)
private readonly maintenanceRepository: Repository<AssetMaintenance>,
@InjectRepository(InventoryItem)
private readonly inventoryRepository: Repository<InventoryItem>,
) {}

async schedule(dto: ScheduleMaintenanceDto) {
const asset = await this.inventoryRepository.findOne({ where: { id: dto.assetId } });
if (!asset) {
throw new NotFoundException(`Asset with ID ${dto.assetId} not found`);
}

// Prevent scheduling maintenance for disposed assets
if (asset.status === InventoryStatus.DISPOSED) {
throw new BadRequestException('Cannot schedule maintenance for disposed asset');
}

const record = this.maintenanceRepository.create({
assetId: dto.assetId,
scheduledDate: new Date(dto.scheduledDate),
completedDate: null,
maintenanceType: dto.maintenanceType,
notes: dto.notes,
});

const saved = await this.maintenanceRepository.save(record);
return { message: 'Maintenance scheduled', maintenance: saved };
}

async complete(id: string, dto: CompleteMaintenanceDto) {
const record = await this.maintenanceRepository.findOne({ where: { id } });
if (!record) {
throw new NotFoundException(`Maintenance record with ID ${id} not found`);
}

record.completedDate = new Date(dto.completedDate);
if (dto.notes) {
record.notes = dto.notes;
}

const saved = await this.maintenanceRepository.save(record);
return { message: 'Maintenance completed', maintenance: saved };
}

async findAll() {
return this.maintenanceRepository.find({ order: { scheduledDate: 'DESC' } });
}

async findByAsset(assetId: string) {
return this.maintenanceRepository.find({ where: { assetId }, order: { scheduledDate: 'DESC' } });
}
}
10 changes: 10 additions & 0 deletions backend/src/asset-maintenance/dto/complete-maintenance.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { IsDateString, IsString, IsOptional } from 'class-validator';

export class CompleteMaintenanceDto {
@IsDateString()
completedDate: string;

@IsString()
@IsOptional()
notes?: string;
}
17 changes: 17 additions & 0 deletions backend/src/asset-maintenance/dto/schedule-maintenance.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IsUUID, IsDateString, IsEnum, IsString, IsOptional } from 'class-validator';
import { MaintenanceType } from '../entities/asset-maintenance.entity';

export class ScheduleMaintenanceDto {
@IsUUID()
assetId: string;

@IsDateString()
scheduledDate: string;

@IsEnum(MaintenanceType)
maintenanceType: MaintenanceType;

@IsString()
@IsOptional()
notes?: string;
}
39 changes: 39 additions & 0 deletions backend/src/asset-maintenance/entities/asset-maintenance.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';

export enum MaintenanceType {
PREVENTIVE = 'preventive',
CORRECTIVE = 'corrective',
INSPECTION = 'inspection',
REPLACEMENT = 'replacement',
SERVICE = 'service',
OTHER = 'other',
}

@Entity('asset_maintenance')
@Index(['assetId', 'scheduledDate'])
export class AssetMaintenance {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column('uuid')
@Index()
assetId: string;

@Column({ type: 'timestamp' })
scheduledDate: Date;

@Column({ type: 'timestamp', nullable: true })
completedDate: Date | null;

@Column({ type: 'enum', enum: MaintenanceType })
maintenanceType: MaintenanceType;

@Column({ type: 'text', nullable: true })
notes?: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
12 changes: 10 additions & 2 deletions backend/src/inventory/entities/inventory-item.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

export enum InventoryStatus {
ACTIVE = 'active',
DISPOSED = 'disposed',
}

@Entity()
export class InventoryItem {
@PrimaryGeneratedColumn('uuid')
Expand All @@ -14,7 +19,10 @@ export class InventoryItem {
@Column({ type: 'int' })
quantity: number;

// --- ADD THIS NEW FIELD ---
@Column({ type: 'int', default: 10 }) // Default threshold of 10 units
// Default threshold of 10 units
@Column({ type: 'int', default: 10 })
threshold: number;

@Column({ type: 'enum', enum: InventoryStatus, default: InventoryStatus.ACTIVE })
status: InventoryStatus;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { InventoryItem } from '../inventory/entities/inventory-item.entity';
import { InventoryItem } from '../entities/inventory-item.entity';
import { InventoryAlertsService } from './inventory-alerts.service';
import { InventoryAlertsController } from './inventory-alerts.controller';
import { InventoryEventListener } from './listeners/inventory.listener';
Expand Down
Loading