All files / src/domains/inventory/receipts/services receipt-integration.service.ts

0% Statements 0/34
0% Branches 0/96
0% Functions 0/4
0% Lines 0/32

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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144                                                                                                                                                                                                                                                                                               
import { Injectable } from '@nestjs/common';
import { LedgerService } from '../../stock-ledger/services/ledger.service';
import { BatchSerialService } from '../../batches/services/batch-serial.service';
import { ConfigurationService } from '../../configuration/services/configuration.service';
 
@Injectable()
export class ReceiptIntegrationService {
  constructor(
    private readonly ledgerService: LedgerService,
    private readonly batchSerialService: BatchSerialService,
    private readonly configService: ConfigurationService
  ) {}
 
  async handleGoodsReceiptApproved(payload: any): Promise<void> {
    const config = await this.configService.getConfiguration(payload.tenantId);
 
    for (const line of payload.lines) {
      const itemCode = line.itemReference;
      const totalReceived = line.receivedQuantity;
      const accepted = line.acceptedQuantity;
      const damaged = line.damagedQuantity;
      const quarantined = line.quarantinedQuantity;
 
      // Handle batches creation if batch details are provided
      if (line.batchDetails && config.batchTrackingEnabled) {
        await this.batchSerialService.createBatch(payload.tenantId, {
          batchNumber: line.batchDetails,
          itemCode,
          manufacturingDate: line.manufacturingDate,
          expiryDate: line.expiryDate,
        });
      }
 
      // Handle serial numbers creation if serial details are provided
      if (line.serialDetails && config.serialTrackingEnabled) {
        await this.batchSerialService.createSerial(payload.tenantId, {
          serialNumber: line.serialDetails,
          itemCode,
          batchNumber: line.batchDetails || null,
          warehouseId: line.warehouseReference || config.defaultWarehouseId,
        });
      }
 
      // 1. Post Accepted Stock
      if (accepted > 0) {
        await this.ledgerService.postLedgerEntry(payload.tenantId, {
          itemCode,
          warehouseId: line.warehouseReference || config.defaultWarehouseId,
          locationId: line.locationReference || null,
          transactionType: 'Purchase Receipt',
          quantityChange: accepted,
          unitCost: line.unitCostSnapshot || 0,
          totalCost: accepted * (line.unitCostSnapshot || 0),
          batchNumber: line.batchDetails || null,
          serialNumber: line.serialDetails || null,
          sourceEntityId: payload.goodsReceiptId,
          sourceEntityType: 'GoodsReceipt',
          correlationId: `${payload.goodsReceiptId}-accepted-${itemCode}-${line.serialDetails || 'global'}`,
        }, 'system');
      }
 
      // 2. Post Quarantine Stock
      if (quarantined > 0) {
        await this.ledgerService.postLedgerEntry(payload.tenantId, {
          itemCode,
          warehouseId: config.defaultQuarantineWarehouseId || 'WH-QUARANTINE',
          locationId: null,
          transactionType: 'Purchase Receipt',
          quantityChange: quarantined,
          unitCost: line.unitCostSnapshot || 0,
          totalCost: quarantined * (line.unitCostSnapshot || 0),
          batchNumber: line.batchDetails || null,
          serialNumber: line.serialDetails || null,
          sourceEntityId: payload.goodsReceiptId,
          sourceEntityType: 'GoodsReceipt',
          correlationId: `${payload.goodsReceiptId}-quarantined-${itemCode}-${line.serialDetails || 'global'}`,
        }, 'system');
      }
 
      // 3. Post Damaged Stock
      if (damaged > 0) {
        await this.ledgerService.postLedgerEntry(payload.tenantId, {
          itemCode,
          warehouseId: config.defaultDamageWarehouseId || 'WH-DAMAGE',
          locationId: null,
          transactionType: 'Purchase Receipt',
          quantityChange: damaged,
          unitCost: line.unitCostSnapshot || 0,
          totalCost: damaged * (line.unitCostSnapshot || 0),
          batchNumber: line.batchDetails || null,
          serialNumber: line.serialDetails || null,
          sourceEntityId: payload.goodsReceiptId,
          sourceEntityType: 'GoodsReceipt',
          correlationId: `${payload.goodsReceiptId}-damaged-${itemCode}-${line.serialDetails || 'global'}`,
        }, 'system');
      }
    }
  }
 
  async handleGoodsReceiptReversed(payload: any): Promise<void> {
    for (const line of payload.lines) {
      // Reversal posts a compensating ledger entry with opposite quantity
      const grId = payload.goodsReceiptId;
      await this.ledgerService.postLedgerEntry(payload.tenantId, {
        itemCode: line.itemReference || 'ITEM-DEFAULT',
        warehouseId: line.warehouseReference || 'WH-MAIN',
        locationId: line.locationReference || null,
        transactionType: 'Purchase Receipt Reversal',
        quantityChange: line.receivedQuantity, // this will be negative quantity
        unitCost: line.unitCostSnapshot || 0,
        totalCost: line.receivedQuantity * (line.unitCostSnapshot || 0),
        batchNumber: line.batchDetails || null,
        serialNumber: line.serialDetails || null,
        sourceEntityId: grId,
        sourceEntityType: 'GoodsReceipt',
        correlationId: `rev-${grId}-${line.goodsReceiptLineId}`,
      }, 'system');
    }
  }
 
  async handlePurchaseReturnApproved(payload: any): Promise<void> {
    for (const line of payload.lines) {
      const returnId = payload.returnId;
      // Post negative change to represent stock leaving
      const qtyChange = -line.quantityReturned;
 
      await this.ledgerService.postLedgerEntry(payload.tenantId, {
        itemCode: line.itemReference || 'ITEM-DEFAULT',
        warehouseId: line.warehouseReference || 'WH-MAIN',
        locationId: line.locationReference || null,
        transactionType: 'Purchase Return',
        quantityChange: qtyChange,
        unitCost: line.unitCostSnapshot || 0,
        totalCost: qtyChange * (line.unitCostSnapshot || 0),
        batchNumber: line.batchDetails || null,
        serialNumber: line.serialDetails || null,
        sourceEntityId: returnId,
        sourceEntityType: 'VendorReturn',
        correlationId: `ret-${returnId}-${line.poLineIndex}`,
      }, 'system');
    }
  }
}