All files / src/domains/inventory/transfers/services movement.service.ts

0% Statements 0/50
0% Branches 0/42
0% Functions 0/8
0% Lines 0/42

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                                                                                                                                                                                                                                                                       
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { StockTransfer } from '../schemas/movement.schema';
import { LedgerService } from '../../stock-ledger/services/ledger.service';
import { ConfigurationService } from '../../configuration/services/configuration.service';
 
@Injectable()
export class MovementService {
  constructor(
    @InjectModel(StockTransfer.name)
    private readonly transferModel: Model<StockTransfer>,
    private readonly ledgerService: LedgerService,
    private readonly configService: ConfigurationService
  ) {}
 
  async createTransfer(tenantId: string, data: any): Promise<StockTransfer> {
    const count = await this.transferModel.countDocuments({ tenantId }).exec();
    const transferNumber = `TO-${new Date().getFullYear()}-${(count + 1).toString().padStart(5, '0')}`;
 
    return this.transferModel.create({
      ...data,
      tenantId,
      transferNumber,
      status: 'approved',
    });
  }
 
  async dispatchTransfer(tenantId: string, id: string, userId: string): Promise<StockTransfer> {
    const transfer = await this.transferModel.findOne({ _id: id, tenantId, status: 'approved' }).exec();
    if (!transfer) throw new NotFoundException('Approved transfer request not found');
 
    const config = await this.configService.getConfiguration(tenantId);
 
    // Post dispatch ledger entries: deduct from source warehouse, add to transit warehouse
    for (const line of transfer.lines) {
      // Deduct from source warehouse
      await this.ledgerService.postLedgerEntry(tenantId, {
        itemCode: line.itemCode,
        warehouseId: transfer.fromWarehouseId,
        transactionType: 'Warehouse Transfer Dispatch',
        quantityChange: -line.quantityRequested,
        batchNumber: line.batchNumber || null,
        serialNumber: line.serialNumber || null,
        sourceEntityId: transfer._id.toString(),
        sourceEntityType: 'StockTransfer',
        correlationId: `${transfer._id.toString()}-dispatch-from-${line.itemCode}`,
      }, userId);
 
      // Add to transit warehouse (e.g. WH-TRANSIT)
      await this.ledgerService.postLedgerEntry(tenantId, {
        itemCode: line.itemCode,
        warehouseId: config.defaultTransitWarehouseId || 'WH-TRANSIT',
        transactionType: 'Warehouse Transfer Dispatch',
        quantityChange: line.quantityRequested,
        batchNumber: line.batchNumber || null,
        serialNumber: line.serialNumber || null,
        sourceEntityId: transfer._id.toString(),
        sourceEntityType: 'StockTransfer',
        correlationId: `${transfer._id.toString()}-dispatch-transit-${line.itemCode}`,
      }, userId);
 
      line.quantityDispatched = line.quantityRequested;
    }
 
    transfer.status = 'dispatched';
    transfer.dispatchedDate = new Date();
    await transfer.save();
 
    return transfer;
  }
 
  async receiveTransfer(tenantId: string, id: string, receivedLines: any[], userId: string): Promise<StockTransfer> {
    const transfer = await this.transferModel.findOne({ _id: id, tenantId, status: 'dispatched' }).exec();
    if (!transfer) throw new NotFoundException('Dispatched transfer request not found');
 
    const config = await this.configService.getConfiguration(tenantId);
 
    // Post receive ledger entries: deduct from transit warehouse, add to destination warehouse
    for (const lineInput of receivedLines) {
      const line = transfer.lines.find(l => l.itemCode === lineInput.itemCode);
      if (!line) continue;
 
      line.quantityReceived = lineInput.quantityReceived;
 
      // Deduct from transit warehouse
      await this.ledgerService.postLedgerEntry(tenantId, {
        itemCode: line.itemCode,
        warehouseId: config.defaultTransitWarehouseId || 'WH-TRANSIT',
        transactionType: 'Warehouse Transfer Receipt',
        quantityChange: -line.quantityDispatched,
        batchNumber: line.batchNumber || null,
        serialNumber: line.serialNumber || null,
        sourceEntityId: transfer._id.toString(),
        sourceEntityType: 'StockTransfer',
        correlationId: `${transfer._id.toString()}-receive-from-transit-${line.itemCode}`,
      }, userId);
 
      // Add to destination warehouse
      await this.ledgerService.postLedgerEntry(tenantId, {
        itemCode: line.itemCode,
        warehouseId: transfer.toWarehouseId,
        transactionType: 'Warehouse Transfer Receipt',
        quantityChange: line.quantityReceived,
        batchNumber: line.batchNumber || null,
        serialNumber: line.serialNumber || null,
        sourceEntityId: transfer._id.toString(),
        sourceEntityType: 'StockTransfer',
        correlationId: `${transfer._id.toString()}-receive-to-${line.itemCode}`,
      }, userId);
    }
 
    // Determine status (check if discrepancy exists)
    const hasDiscrepancy = transfer.lines.some(l => l.quantityReceived !== l.quantityDispatched);
    transfer.status = hasDiscrepancy ? 'partially_received' : 'received';
    transfer.receivedDate = new Date();
    await transfer.save();
 
    return transfer;
  }
 
  async getTransfers(tenantId: string): Promise<StockTransfer[]> {
    return this.transferModel.find({ tenantId }).exec();
  }
 
  async getTransferById(tenantId: string, id: string): Promise<StockTransfer> {
    const transfer = await this.transferModel.findOne({ tenantId, _id: id }).exec();
    if (!transfer) throw new NotFoundException('Transfer order not found');
    return transfer;
  }
}