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
39 changes: 39 additions & 0 deletions backend/src/abdulrcrtw.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NotesDocsController } from './assets/notes-docs.controller';
import { MaintenanceService } from './maintenance/maintenance.service';
import { TransfersService } from './transfers/transfers.service';

describe('abdulrcrtw Modules (BE-92, BE-91, BE-90, BE-89)', () => {
it('NotesDocsController adds and lists asset notes and documents', () => {
const controller = new NotesDocsController();
const note = controller.addNote('ast-1', 'Need battery replacement', { user: { id: 'u-1' } } as any);
expect(note.content).toBe('Need battery replacement');
expect(controller.getNotes('ast-1').length).toBe(1);

const doc = controller.addDocument('ast-1', { title: 'Warranty.pdf', fileUrl: 'http://example.com/w.pdf' }, { user: { id: 'u-1' } } as any);
expect(doc.title).toBe('Warranty.pdf');
expect(controller.getDocuments('ast-1').length).toBe(1);
});

it('MaintenanceService creates maintenance records', async () => {
const mockRepo = {
create: jest.fn().mockImplementation((dto) => dto),
save: jest.fn().mockImplementation((dto) => Promise.resolve({ id: 'm-1', ...dto })),
};
const service = new MaintenanceService(mockRepo as any);
const rec = await service.create({ assetId: 'ast-1', title: 'Screen repair', cost: 15000 });
expect(rec.cost).toBe(15000);
});

it('TransfersService manages approval workflow', async () => {
const tr = { id: 'tr-1', status: 'PENDING' };
const mockRepo = {
findOne: jest.fn().mockResolvedValue(tr),
create: jest.fn().mockImplementation((dto) => dto),
save: jest.fn().mockImplementation((dto) => Promise.resolve(dto)),
};
const service = new TransfersService(mockRepo as any);
const approved = await service.approve('tr-1', 'manager-1');
expect(approved.status).toBe('APPROVED');
expect(approved.approvedByUserId).toBe('manager-1');
});
});
29 changes: 27 additions & 2 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,35 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CommonModule } from './common/common.module';
import { UsersModule } from './users/users.module';
import { HealthModule } from './health/health.module';
import { AuthModule } from './auth/auth.module';
import { BranchesModule } from './branches/branches.module';
import { DepartmentsModule } from './departments/departments.module';
import { LocationsModule } from './locations/locations.module';
import { CategoriesModule } from './categories/categories.module';
import { AssetsLifecycleModule } from './assets/assets-lifecycle.module';
import { AssetsModule } from './assets/assets.module';
import { NotesDocsController } from './assets/notes-docs.controller';
import { MaintenanceModule } from './maintenance/maintenance.module';
import { TransfersModule } from './transfers/transfers.module';

@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
CommonModule,
UsersModule,
HealthModule,
AuthModule,
BranchesModule,
DepartmentsModule,
LocationsModule,
CategoriesModule,
AssetsLifecycleModule,
AssetsModule,
MaintenanceModule,
TransfersModule,
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
Expand All @@ -18,12 +43,12 @@ import { AppService } from './app.service';
password: configService.get('DB_PASSWORD', 'password'),
database: configService.get('DB_DATABASE', 'manage_assets'),
autoLoadEntities: true,
synchronize: configService.get('NODE_ENV') === 'development',
synchronize: false,
}),
inject: [ConfigService],
}),
],
controllers: [AppController],
controllers: [AppController, NotesDocsController],
providers: [AppService],
})
export class AppModule {}
28 changes: 28 additions & 0 deletions backend/src/assets/entities/asset-document.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';

@Entity('asset_documents')
export class AssetDocument {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
assetId: string;

@Column()
title: string;

@Column()
fileUrl: string;

@Column({ nullable: true })
fileType?: string;

@Column({ type: 'integer', default: 0 })
fileSizeBytes: number;

@Column()
uploadedByUserId: string;

@CreateDateColumn()
createdAt: Date;
}
19 changes: 19 additions & 0 deletions backend/src/assets/entities/asset-note.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';

@Entity('asset_notes')
export class AssetNote {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
assetId: string;

@Column()
authorUserId: string;

@Column({ type: 'text' })
content: string;

@CreateDateColumn()
createdAt: Date;
}
57 changes: 57 additions & 0 deletions backend/src/assets/notes-docs.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Controller, Get, Post, Delete, Param, Body, Req } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';

@ApiTags('assets')
@Controller('assets/:id')

Check failure on line 5 in backend/src/assets/notes-docs.controller.ts

View workflow job for this annotation

GitHub Actions / Backend (NestJS)

'Delete' is defined but never used
export class NotesDocsController {
private notes = new Map<string, any[]>();
private docs = new Map<string, any[]>();

@Get('notes')
@ApiOperation({ summary: 'Get asset notes' })
getNotes(@Param('id') assetId: string) {
return this.notes.get(assetId) || [];
}

@Post('notes')
@ApiOperation({ summary: 'Add an asset note' })
addNote(@Param('id') assetId: string, @Body('content') content: string, @Req() req: any) {
const list = this.notes.get(assetId) || [];
const note = {
id: `n_${Date.now()}`,
assetId,
content,
authorUserId: req.user?.id || 'usr-1',
createdAt: new Date(),
};
list.unshift(note);
this.notes.set(assetId, list);
return note;
}

@Get('documents')
@ApiOperation({ summary: 'Get asset document attachments' })
getDocuments(@Param('id') assetId: string) {
return this.docs.get(assetId) || [];
}

@Post('documents')
@ApiOperation({ summary: 'Attach a document to an asset' })
addDocument(
@Param('id') assetId: string,
@Body() body: { title: string; fileUrl: string; fileType?: string; fileSizeBytes?: number },
@Req() req: any,
) {
const list = this.docs.get(assetId) || [];
const doc = {
id: `d_${Date.now()}`,
assetId,
...body,
uploadedByUserId: req.user?.id || 'usr-1',
createdAt: new Date(),
};
list.unshift(doc);
this.docs.set(assetId, list);
return doc;
}
}
47 changes: 47 additions & 0 deletions backend/src/maintenance/entities/maintenance-record.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

export enum MaintenanceStatus {
SCHEDULED = 'SCHEDULED',
IN_PROGRESS = 'IN_PROGRESS',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
}

@Entity('maintenance_records')
export class MaintenanceRecord {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
assetId: string;

@Column()
title: string;

@Column({ nullable: true })
description?: string;

@Column({ type: 'enum', enum: MaintenanceStatus, default: MaintenanceStatus.SCHEDULED })
status: MaintenanceStatus;

@Column({ nullable: true })
vendorId?: string;

@Column({ type: 'integer', default: 0 })
cost: number;

@Column({ default: 'USD' })
currency: string;

@Column({ nullable: true })
scheduledDate?: Date;

@Column({ nullable: true })
completedDate?: Date;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
33 changes: 33 additions & 0 deletions backend/src/maintenance/maintenance.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Controller, Get, Post, Patch, Body, Param } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { MaintenanceService } from './maintenance.service';

@ApiTags('maintenance')
@Controller('maintenance')
export class MaintenanceController {
constructor(private readonly maintenanceService: MaintenanceService) {}

@Get()
@ApiOperation({ summary: 'List all maintenance records' })
findAll() {
return this.maintenanceService.findAll();
}

@Post()
@ApiOperation({ summary: 'Create a maintenance record' })
create(@Body() dto: any) {
return this.maintenanceService.create(dto);
}

@Get(':id')
@ApiOperation({ summary: 'Get maintenance record details' })
findOne(@Param('id') id: string) {
return this.maintenanceService.findById(id);
}

@Patch(':id')
@ApiOperation({ summary: 'Update a maintenance record' })
update(@Param('id') id: string, @Body() dto: any) {
return this.maintenanceService.update(id, dto);
}
}
13 changes: 13 additions & 0 deletions backend/src/maintenance/maintenance.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MaintenanceRecord } from './entities/maintenance-record.entity';
import { MaintenanceService } from './maintenance.service';
import { MaintenanceController } from './maintenance.controller';

@Module({
imports: [TypeOrmModule.forFeature([MaintenanceRecord])],
providers: [MaintenanceService],
controllers: [MaintenanceController],
exports: [MaintenanceService],
})
export class MaintenanceModule {}
33 changes: 33 additions & 0 deletions backend/src/maintenance/maintenance.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { MaintenanceRecord } from './entities/maintenance-record.entity';

@Injectable()
export class MaintenanceService {
constructor(
@InjectRepository(MaintenanceRecord)
private readonly maintenanceRepo: Repository<MaintenanceRecord>,
) {}

async findAll() {
return this.maintenanceRepo.find();
}

async findById(id: string) {
const record = await this.maintenanceRepo.findOne({ where: { id } });
if (!record) throw new NotFoundException(`Maintenance record ${id} not found`);
return record;
}

async create(dto: Partial<MaintenanceRecord>) {
const record = this.maintenanceRepo.create(dto);
return this.maintenanceRepo.save(record);
}

async update(id: string, dto: Partial<MaintenanceRecord>) {
const record = await this.findById(id);
Object.assign(record, dto);
return this.maintenanceRepo.save(record);
}
}
42 changes: 42 additions & 0 deletions backend/src/transfers/entities/asset-transfer.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';

export enum TransferStatus {
PENDING = 'PENDING',
APPROVED = 'APPROVED',
REJECTED = 'REJECTED',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
}

@Entity('asset_transfers')
export class AssetTransfer {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column()
assetId: string;

@Column()
fromDepartmentId: string;

@Column()
toDepartmentId: string;

@Column()
requestedByUserId: string;

@Column({ nullable: true })
approvedByUserId?: string;

@Column({ type: 'enum', enum: TransferStatus, default: TransferStatus.PENDING })
status: TransferStatus;

@Column({ nullable: true })
rejectionReason?: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
Loading
Loading