Skip to content

Commit 85ab57d

Browse files
Merge pull request #1023 from tolulopedd26/feat-900-901-902-903
[BE-27/BE-28/BE-29/BE-30] Notes, QR/barcode, bulk, check-in/out
2 parents 471e939 + 0d9d5cd commit 85ab57d

17 files changed

Lines changed: 546 additions & 0 deletions

backend/src/app.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { AppController } from './app.controller';
1111
import { AppService } from './app.service';
1212
import { UsersModule } from './users/users.module';
1313
import { AuthModule } from './auth/auth.module';
14+
import { AssetsOpsModule } from './assets/assets-ops.module';
15+
import { CheckinModule } from './checkin/checkin.module';
1416
import { AssetsModule } from './assets/assets.module';
1517
import { QueueModule } from './queue/queue.module';
1618
import { StorageModule } from './storage/storage.module';
@@ -62,6 +64,8 @@ import { CacheService } from './cache/cache.service';
6264
},
6365
}),
6466

67+
AssetsOpsModule,
68+
CheckinModule,
6569
AssetsModule,
6670
QueueModule,
6771
StorageModule,
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Controller, Post, Get, Delete, Param, Body, Req, UseGuards, Query } from '@nestjs/common';
2+
import { AuthGuard } from '@nestjs/passport';
3+
import { AssetsOpsService } from './assets-ops.service';
4+
import { CreateNoteDto } from './dtos/create-note.dto';
5+
import { BulkStatusDto } from './dtos/bulk-status.dto';
6+
import { BulkDeleteDto } from './dtos/bulk-delete.dto';
7+
import { BulkTransferDto } from './dtos/bulk-transfer.dto';
8+
9+
@Controller('assets')
10+
@UseGuards(AuthGuard('jwt'))
11+
export class AssetsOpsController {
12+
constructor(private readonly assetsOpsService: AssetsOpsService) {}
13+
14+
@Post(':id/notes')
15+
async createNote(@Param('id') id: string, @Body() dto: CreateNoteDto, @Req() req: any) {
16+
return this.assetsOpsService.createNote(id, dto, req.user?.id);
17+
}
18+
19+
@Get(':id/notes')
20+
async getNotes(@Param('id') id: string) {
21+
return this.assetsOpsService.getNotes(id);
22+
}
23+
24+
@Delete(':id/notes/:noteId')
25+
async deleteNote(@Param('id') id: string, @Param('noteId') noteId: string) {
26+
await this.assetsOpsService.deleteNote(id, noteId);
27+
return { message: 'Note deleted' };
28+
}
29+
30+
@Post(':id/qrcode')
31+
async generateQRCode(@Param('id') id: string) {
32+
const qrCode = await this.assetsOpsService.generateQRCode(id);
33+
return { qrCode };
34+
}
35+
36+
@Post(':id/barcode')
37+
async generateBarcode(@Param('id') id: string) {
38+
const barcode = await this.assetsOpsService.generateBarcode(id);
39+
return { barcode };
40+
}
41+
42+
@Post('bulk/status')
43+
async bulkStatusUpdate(@Body() dto: BulkStatusDto, @Req() req: any) {
44+
const count = await this.assetsOpsService.bulkStatusUpdate(dto, req.user?.id);
45+
return { updated: count };
46+
}
47+
48+
@Post('bulk/delete')
49+
async bulkDelete(@Body() dto: BulkDeleteDto) {
50+
const count = await this.assetsOpsService.bulkDelete(dto);
51+
return { deleted: count };
52+
}
53+
54+
@Post('bulk/transfer')
55+
async bulkTransfer(@Body() dto: BulkTransferDto, @Req() req: any) {
56+
const count = await this.assetsOpsService.bulkTransfer(dto, req.user?.id);
57+
return { transferred: count };
58+
}
59+
60+
@Get('bulk/export')
61+
async bulkExport(@Query('ids') ids?: string) {
62+
const idArray = ids ? ids.split(',') : undefined;
63+
return this.assetsOpsService.bulkExport(idArray);
64+
}
65+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
import { Asset } from './entities/asset.entity';
4+
import { AssetNote } from './entities/asset-note.entity';
5+
import { AssetsOpsService } from './assets-ops.service';
6+
import { AssetsOpsController } from './assets-ops.controller';
7+
8+
@Module({
9+
imports: [TypeOrmModule.forFeature([Asset, AssetNote])],
10+
controllers: [AssetsOpsController],
11+
providers: [AssetsOpsService],
12+
exports: [AssetsOpsService],
13+
})
14+
export class AssetsOpsModule {}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { Injectable, NotFoundException } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
import { Repository, In } from 'typeorm';
4+
import { Asset } from './entities/asset.entity';
5+
import { AssetNote } from './entities/asset-note.entity';
6+
import { CreateNoteDto } from './dtos/create-note.dto';
7+
import { BulkStatusDto } from './dtos/bulk-status.dto';
8+
import { BulkDeleteDto } from './dtos/bulk-delete.dto';
9+
import { BulkTransferDto } from './dtos/bulk-transfer.dto';
10+
import * as QRCode from 'qrcode';
11+
import * as bwipjs from 'bwip-js';
12+
13+
@Injectable()
14+
export class AssetsOpsService {
15+
constructor(
16+
@InjectRepository(Asset)
17+
private readonly assetRepository: Repository<Asset>,
18+
@InjectRepository(AssetNote)
19+
private readonly noteRepository: Repository<AssetNote>,
20+
) {}
21+
22+
async createNote(assetId: string, dto: CreateNoteDto, userId?: string): Promise<AssetNote> {
23+
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
24+
if (!asset) throw new NotFoundException('Asset not found');
25+
26+
const note = this.noteRepository.create({
27+
assetId,
28+
content: dto.content,
29+
createdById: userId,
30+
});
31+
return this.noteRepository.save(note);
32+
}
33+
34+
async getNotes(assetId: string): Promise<AssetNote[]> {
35+
return this.noteRepository.find({
36+
where: { assetId },
37+
relations: ['createdBy'],
38+
order: { createdAt: 'DESC' },
39+
});
40+
}
41+
42+
async deleteNote(assetId: string, noteId: string): Promise<void> {
43+
const note = await this.noteRepository.findOne({ where: { id: noteId, assetId } });
44+
if (!note) throw new NotFoundException('Note not found');
45+
await this.noteRepository.remove(note);
46+
}
47+
48+
async generateQRCode(assetId: string): Promise<string> {
49+
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
50+
if (!asset) throw new NotFoundException('Asset not found');
51+
52+
const qrData = JSON.stringify({ id: asset.id, assetId: asset.assetId, name: asset.name });
53+
const qrCode = await QRCode.toDataURL(qrData);
54+
55+
await this.assetRepository.update(assetId, { qrCode });
56+
return qrCode;
57+
}
58+
59+
async generateBarcode(assetId: string): Promise<string> {
60+
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
61+
if (!asset) throw new NotFoundException('Asset not found');
62+
63+
const barcode = await new Promise<string>((resolve, reject) => {
64+
bwipjs.toBuffer({
65+
bcid: 'code128',
66+
text: asset.assetId,
67+
scale: 3,
68+
height: 10,
69+
includetext: true,
70+
textxalign: 'center',
71+
}, (err: Error | null, buffer?: Buffer) => {
72+
if (err) reject(err);
73+
else resolve(`data:image/png;base64,${buffer.toString('base64')}`);
74+
});
75+
});
76+
77+
await this.assetRepository.update(assetId, { barcode });
78+
return barcode;
79+
}
80+
81+
async bulkStatusUpdate(dto: BulkStatusDto, userId?: string): Promise<number> {
82+
const result = await this.assetRepository.update(
83+
{ id: In(dto.ids) },
84+
{ status: dto.status, updatedById: userId },
85+
);
86+
return result.affected || 0;
87+
}
88+
89+
async bulkDelete(dto: BulkDeleteDto): Promise<number> {
90+
const result = await this.assetRepository.softDelete({ id: In(dto.ids) });
91+
return result.affected || 0;
92+
}
93+
94+
async bulkTransfer(dto: BulkTransferDto, userId?: string): Promise<number> {
95+
const updateData: Partial<Asset> = { updatedById: userId };
96+
if (dto.assignedToId) updateData.assignedToId = dto.assignedToId;
97+
if (dto.departmentId) updateData.departmentId = dto.departmentId;
98+
if (dto.location) updateData.location = dto.location;
99+
100+
const result = await this.assetRepository.update(
101+
{ id: In(dto.ids) },
102+
updateData,
103+
);
104+
return result.affected || 0;
105+
}
106+
107+
async bulkExport(ids?: string[]) {
108+
const where = ids ? { id: In(ids) } : {};
109+
return this.assetRepository.find({ where, relations: ['assignedTo', 'createdBy'] });
110+
}
111+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { IsArray } from 'class-validator';
2+
3+
export class BulkDeleteDto {
4+
@IsArray()
5+
ids: string[];
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { IsArray, IsString } from 'class-validator';
2+
3+
export class BulkStatusDto {
4+
@IsArray()
5+
ids: string[];
6+
7+
@IsString()
8+
status: string;
9+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { IsArray, IsOptional, IsString } from 'class-validator';
2+
3+
export class BulkTransferDto {
4+
@IsArray()
5+
ids: string[];
6+
7+
@IsOptional()
8+
@IsString()
9+
assignedToId?: string;
10+
11+
@IsOptional()
12+
@IsString()
13+
departmentId?: string;
14+
15+
@IsOptional()
16+
@IsString()
17+
location?: string;
18+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { IsString } from 'class-validator';
2+
3+
export class CreateNoteDto {
4+
@IsString()
5+
content: string;
6+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
2+
import { Asset } from './asset.entity';
3+
import { User } from '../../users/entities/user.entity';
4+
5+
@Entity('asset_notes')
6+
export class AssetNote {
7+
@PrimaryGeneratedColumn('uuid')
8+
id: string;
9+
10+
@Column()
11+
assetId: string;
12+
13+
@ManyToOne(() => Asset, { onDelete: 'CASCADE' })
14+
@JoinColumn({ name: 'assetId' })
15+
asset: Asset;
16+
17+
@Column({ type: 'text' })
18+
content: string;
19+
20+
@Column({ nullable: true })
21+
createdById: string;
22+
23+
@ManyToOne(() => User, { nullable: true })
24+
@JoinColumn({ name: 'createdById' })
25+
createdBy: User;
26+
27+
@CreateDateColumn()
28+
createdAt: Date;
29+
30+
@UpdateDateColumn()
31+
updatedAt: Date;
32+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
2+
import { User } from '../../users/entities/user.entity';
3+
4+
@Entity('assets')
5+
export class Asset {
6+
@PrimaryGeneratedColumn('uuid')
7+
id: string;
8+
9+
@Column({ unique: true })
10+
assetId: string;
11+
12+
@Column()
13+
name: string;
14+
15+
@Column({ nullable: true, type: 'text' })
16+
description: string;
17+
18+
@Column({ nullable: true })
19+
categoryId: string;
20+
21+
@Column({ nullable: true })
22+
serialNumber: string;
23+
24+
@Column({ default: 'ACTIVE' })
25+
status: string;
26+
27+
@Column({ default: 'NEW' })
28+
condition: string;
29+
30+
@Column({ nullable: true })
31+
departmentId: string;
32+
33+
@Column({ nullable: true })
34+
location: string;
35+
36+
@Column({ nullable: true })
37+
assignedToId: string;
38+
39+
@Column({ nullable: true })
40+
barcode: string;
41+
42+
@Column({ nullable: true })
43+
qrCode: string;
44+
45+
@Column({ nullable: true, type: 'text' })
46+
notes: string;
47+
48+
@Column({ nullable: true })
49+
createdById: string;
50+
51+
@ManyToOne(() => User, { nullable: true })
52+
@JoinColumn({ name: 'createdById' })
53+
createdBy: User;
54+
55+
@Column({ nullable: true })
56+
updatedById: string;
57+
58+
@ManyToOne(() => User, { nullable: true })
59+
@JoinColumn({ name: 'updatedById' })
60+
updatedBy: User;
61+
62+
@CreateDateColumn()
63+
createdAt: Date;
64+
65+
@UpdateDateColumn()
66+
updatedAt: Date;
67+
}

0 commit comments

Comments
 (0)