1- import { Injectable , Logger , NotFoundException } from '@nestjs/common' ;
2- import { PaginatedResponse } from '../common/dto/paginated-response.dto' ;
1+ import { Injectable , Logger , NotFoundException , ForbiddenException } from '@nestjs/common' ;
32import { InjectRepository } from '@nestjs/typeorm' ;
43import { Repository } from 'typeorm' ;
54import { ConfigService } from '@nestjs/config' ;
@@ -19,6 +18,7 @@ import { UpdateMaintenanceDto } from './dto/update-maintenance.dto';
1918import { CreateDocumentDto } from './dto/create-document.dto' ;
2019import { DuplicateAssetDto } from './dto/duplicate-asset.dto' ;
2120import { AssetStatus , AssetHistoryAction , StellarStatus } from './enums' ;
21+ import { UserRole } from '../users/user.entity' ;
2222import { DepartmentsService } from '../departments/departments.service' ;
2323import { CategoriesService } from '../categories/categories.service' ;
2424import { UsersService } from '../users/users.service' ;
@@ -51,8 +51,12 @@ export class AssetsService {
5151 private readonly storageService : StorageService ,
5252 ) { }
5353
54- async findAll ( filters : AssetFiltersDto ) : Promise < PaginatedResponse < Asset > > {
55- const { search, status, condition, categoryId, departmentId, page = 1 , limit = 20 } = filters ;
54+ async findAll ( filters : AssetFiltersDto , currentUser ?: User ) : Promise < { data : Asset [ ] ; total : number ; page : number ; limit : number } > {
55+ const { search, status, condition, categoryId, departmentId, page = 1 , limit = 20 , includeDeleted = false } = filters ;
56+
57+ if ( includeDeleted && currentUser ?. role !== UserRole . ADMIN ) {
58+ throw new ForbiddenException ( 'Only admins can view deleted assets' ) ;
59+ }
5660
5761 const qb = this . assetsRepo
5862 . createQueryBuilder ( 'asset' )
@@ -62,6 +66,10 @@ export class AssetsService {
6266 . leftJoinAndSelect ( 'asset.createdBy' , 'createdBy' )
6367 . leftJoinAndSelect ( 'asset.updatedBy' , 'updatedBy' ) ;
6468
69+ if ( includeDeleted ) {
70+ qb . withDeleted ( ) ;
71+ }
72+
6573 if ( search ) {
6674 // Multi-column ILIKE search — covers name, assetId, serialNumber, location, notes,
6775 // category.name, department.name (case-insensitive, partial match).
@@ -70,13 +78,9 @@ export class AssetsService {
7078 // CREATE INDEX CONCURRENTLY idx_assets_name_gin ON assets USING gin(to_tsvector('english', name));
7179 // CREATE INDEX CONCURRENTLY idx_assets_serial_gin ON assets USING gin(to_tsvector('english', coalesce("serialNumber", '')));
7280 qb . andWhere (
73- `(asset.name ILIKE :search
74- OR asset.assetId ILIKE :search
75- OR asset.serialNumber ILIKE :search
76- OR asset.location ILIKE :search
77- OR asset.notes ILIKE :search
78- OR category.name ILIKE :search
79- OR department.name ILIKE :search)` ,
81+ `(asset.name ILIKE :search OR asset.assetId ILIKE :search OR asset.serialNumber ILIKE :search
82+ OR asset.location ILIKE :search OR asset.notes ILIKE :search
83+ OR category.name ILIKE :search OR department.name ILIKE :search)` ,
8084 { search : `%${ search } %` } ,
8185 ) ;
8286 }
@@ -249,56 +253,24 @@ export class AssetsService {
249253 return this . findOne ( id ) ;
250254 }
251255
252- async duplicate ( id : string , dto : DuplicateAssetDto , currentUser : User ) : Promise < Asset [ ] > {
253- const source = await this . findOne ( id ) ;
254- const quantity = dto . quantity ?? 1 ;
255- const results : Asset [ ] = [ ] ;
256-
257- for ( let i = 0 ; i < quantity ; i ++ ) {
258- const newAssetId = await this . generateAssetId ( ) ;
259- const copy = this . assetsRepo . create ( {
260- assetId : newAssetId ,
261- name : dto . name ?? source . name ,
262- description : source . description ,
263- category : source . category ,
264- department : source . department ,
265- assignedTo : null ,
266- serialNumber : quantity === 1 && dto . serialNumber ? dto . serialNumber : null ,
267- purchaseDate : source . purchaseDate ,
268- purchasePrice : source . purchasePrice ,
269- currentValue : source . currentValue ,
270- warrantyExpiration : source . warrantyExpiration ,
271- status : AssetStatus . ACTIVE ,
272- condition : source . condition ,
273- location : source . location ,
274- manufacturer : source . manufacturer ,
275- model : source . model ,
276- tags : source . tags ,
277- notes : source . notes ,
278- customFields : source . customFields ,
279- imageUrls : source . imageUrls ,
280- createdBy : currentUser ,
281- updatedBy : currentUser ,
282- } ) ;
283-
284- const saved = await this . assetsRepo . save ( copy ) ;
285- await this . logHistory (
286- saved ,
287- AssetHistoryAction . CREATED ,
288- `Duplicated from ${ source . assetId } ` ,
289- null ,
290- null ,
291- currentUser ,
292- ) ;
293- results . push ( await this . findOne ( saved . id ) ) ;
294- }
295-
296- return results ;
256+ async remove ( id : string , currentUser : User ) : Promise < void > {
257+ await this . findOne ( id ) ;
258+ await this . assetsRepo . softDelete ( id ) ;
259+ await this . logHistory (
260+ { id } as Asset ,
261+ AssetHistoryAction . DELETED ,
262+ 'Asset soft-deleted' ,
263+ null ,
264+ null ,
265+ currentUser ,
266+ ) ;
297267 }
298268
299- async remove ( id : string ) : Promise < void > {
269+ async restore ( id : string , currentUser : User ) : Promise < Asset > {
270+ await this . assetsRepo . restore ( id ) ;
300271 const asset = await this . findOne ( id ) ;
301- await this . assetsRepo . remove ( asset ) ;
272+ await this . logHistory ( asset , AssetHistoryAction . RESTORED , 'Asset restored' , null , null , currentUser ) ;
273+ return asset ;
302274 }
303275
304276 async getHistory ( assetId : string ) : Promise < AssetHistory [ ] > {
0 commit comments