Skip to content

Commit 7e223e5

Browse files
Merge pull request #560 from NteinPrecious/feat/add-standalone-asset-duplication-endpoint-for-bulk-similar-asset-entry
feat: add standalone asset duplication endpoint for bulk similar asse…
2 parents 4071410 + 3d6d6e9 commit 7e223e5

3 files changed

Lines changed: 77 additions & 0 deletions

File tree

backend/src/assets/assets.controller.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { CreateNoteDto } from './dto/create-note.dto';
2525
import { CreateMaintenanceDto } from './dto/create-maintenance.dto';
2626
import { UpdateMaintenanceDto } from './dto/update-maintenance.dto';
2727
import { CreateDocumentDto } from './dto/create-document.dto';
28+
import { DuplicateAssetDto } from './dto/duplicate-asset.dto';
2829
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
2930
import { RolesGuard } from '../auth/guards/roles.guard';
3031
import { CurrentUser } from '../auth/decorators/current-user.decorator';
@@ -77,6 +78,13 @@ export class AssetsController {
7778
return this.service.transfer(id, dto, user);
7879
}
7980

81+
@Post(':id/duplicate')
82+
@ApiOperation({ summary: 'Duplicate an asset (MANAGER/ADMIN only)' })
83+
@Roles(UserRole.MANAGER, UserRole.ADMIN)
84+
duplicate(@Param('id') id: string, @Body() dto: DuplicateAssetDto, @CurrentUser() user: User) {
85+
return this.service.duplicate(id, dto, user);
86+
}
87+
8088
@Delete(':id')
8189
@HttpCode(HttpStatus.NO_CONTENT)
8290
@ApiOperation({ summary: 'Delete an asset' })

backend/src/assets/assets.service.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { CreateNoteDto } from './dto/create-note.dto';
1616
import { CreateMaintenanceDto } from './dto/create-maintenance.dto';
1717
import { UpdateMaintenanceDto } from './dto/update-maintenance.dto';
1818
import { CreateDocumentDto } from './dto/create-document.dto';
19+
import { DuplicateAssetDto } from './dto/duplicate-asset.dto';
1920
import { AssetStatus, AssetHistoryAction, StellarStatus } from './enums';
2021
import { DepartmentsService } from '../departments/departments.service';
2122
import { CategoriesService } from '../categories/categories.service';
@@ -247,6 +248,53 @@ export class AssetsService {
247248
return this.findOne(id);
248249
}
249250

251+
async duplicate(id: string, dto: DuplicateAssetDto, currentUser: User): Promise<Asset[]> {
252+
const source = await this.findOne(id);
253+
const quantity = dto.quantity ?? 1;
254+
const results: Asset[] = [];
255+
256+
for (let i = 0; i < quantity; i++) {
257+
const newAssetId = await this.generateAssetId();
258+
const copy = this.assetsRepo.create({
259+
assetId: newAssetId,
260+
name: dto.name ?? source.name,
261+
description: source.description,
262+
category: source.category,
263+
department: source.department,
264+
assignedTo: null,
265+
serialNumber: quantity === 1 && dto.serialNumber ? dto.serialNumber : null,
266+
purchaseDate: source.purchaseDate,
267+
purchasePrice: source.purchasePrice,
268+
currentValue: source.currentValue,
269+
warrantyExpiration: source.warrantyExpiration,
270+
status: AssetStatus.ACTIVE,
271+
condition: source.condition,
272+
location: source.location,
273+
manufacturer: source.manufacturer,
274+
model: source.model,
275+
tags: source.tags,
276+
notes: source.notes,
277+
customFields: source.customFields,
278+
imageUrls: source.imageUrls,
279+
createdBy: currentUser,
280+
updatedBy: currentUser,
281+
});
282+
283+
const saved = await this.assetsRepo.save(copy);
284+
await this.logHistory(
285+
saved,
286+
AssetHistoryAction.CREATED,
287+
`Duplicated from ${source.assetId}`,
288+
null,
289+
null,
290+
currentUser,
291+
);
292+
results.push(await this.findOne(saved.id));
293+
}
294+
295+
return results;
296+
}
297+
250298
async remove(id: string): Promise<void> {
251299
const asset = await this.findOne(id);
252300
await this.assetsRepo.remove(asset);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ApiPropertyOptional } from '@nestjs/swagger';
2+
import { IsOptional, IsString, IsInt, Min, Max } from 'class-validator';
3+
4+
export class DuplicateAssetDto {
5+
@ApiPropertyOptional({ description: 'Override name for duplicated asset(s)' })
6+
@IsString()
7+
@IsOptional()
8+
name?: string;
9+
10+
@ApiPropertyOptional({ description: 'Serial number for the duplicated asset (only used when quantity=1)' })
11+
@IsString()
12+
@IsOptional()
13+
serialNumber?: string;
14+
15+
@ApiPropertyOptional({ description: 'Number of copies to create (default 1, max 50)', default: 1 })
16+
@IsInt()
17+
@Min(1)
18+
@Max(50)
19+
@IsOptional()
20+
quantity?: number = 1;
21+
}

0 commit comments

Comments
 (0)