Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; import { Asset, AssetDisposal } from '../schemas'; import { EventBusService } from '../../../platform/events/event-bus.service'; @Injectable() export class AssetDisposalService { constructor( @InjectModel(Asset.name) private readonly assetModel: Model<Asset>, @InjectModel(AssetDisposal.name) private readonly disposalModel: Model<AssetDisposal>, private readonly eventBus: EventBusService ) {} async disposeAsset( tenantId: string, assetId: string, type: string, // 'Sale' | 'Scrap' | 'Write-off' | 'Donation' proceedsMinor: number, userId: string ): Promise<AssetDisposal> { const asset = await this.assetModel.findOne({ _id: assetId, tenantId }).exec(); if (!asset) throw new BadRequestException('Asset not found'); if (asset.status === 'disposed' || asset.status === 'sold' || asset.status === 'scrapped') { throw new BadRequestException('Asset is already disposed'); } const nbv = asset.currentBookValueMinor; const gainLoss = proceedsMinor - nbv; const disposal = new this.disposalModel({ tenantId, assetId: new Types.ObjectId(assetId), disposalType: type, disposalDate: new Date(), proceedsMinor, netBookValueMinor: nbv, gainLossMinor: gainLoss, status: 'pending_posting' }); await disposal.save(); // Trigger Finance Integration Posting Handshake await this.eventBus.publish('asset.disposal-posting-ready.v1', { disposalId: disposal._id.toString(), assetId: asset._id.toString(), assetNumber: asset.assetNumber, tenantId, disposalType: type, proceedsMinor, netBookValueMinor: nbv, gainLossMinor: gainLoss, accumulatedDepreciationMinor: asset.accumulatedDepreciationMinor, capitalizationCostMinor: asset.capitalizationCostMinor, userId }, tenantId); return disposal; } async handleFinanceDisposalPosted(tenantId: string, disposalId: string): Promise<AssetDisposal> { const disposal = await this.disposalModel.findOne({ _id: disposalId, tenantId }).exec(); if (!disposal) throw new Error('Disposal not found'); disposal.status = 'completed'; await disposal.save(); // Mark asset as retired / disposed let finalStatus = 'disposed'; if (disposal.disposalType === 'Sale') finalStatus = 'sold'; if (disposal.disposalType === 'Scrap') finalStatus = 'scrapped'; if (disposal.disposalType === 'Write-off') finalStatus = 'written_off'; await this.assetModel.updateOne( { _id: disposal.assetId }, { status: finalStatus, currentBookValueMinor: 0 // Book value is released } ); return disposal; } } |