| const prisma = require('../config/database'); |
| const EWayBillService = require('../services/ewayBill.service'); |
| const { v4: uuidv4 } = require('uuid'); |
|
|
| |
| |
| |
| const generateBillNo = () => { |
| return Math.floor(100000000000 + Math.random() * 900000000000).toString(); |
| }; |
|
|
| |
| |
| |
| const calculateValidity = (distanceKm) => { |
| const days = Math.max(1, Math.ceil(distanceKm / 100)); |
| const validUntil = new Date(); |
| validUntil.setDate(validUntil.getDate() + days); |
| return validUntil; |
| }; |
|
|
| |
| |
| |
| const resolveRecipientGstin = async (delivery, tx) => { |
| if (delivery.shipment && delivery.shipment.recipient && delivery.shipment.recipient.gstin) { |
| return delivery.shipment.recipient.gstin; |
| } |
| |
| const fullDelivery = await tx.delivery.findUnique({ |
| where: { id: delivery.id }, |
| include: { |
| shipment: { |
| include: { |
| recipient: true |
| } |
| } |
| } |
| }); |
| return fullDelivery?.shipment?.recipient?.gstin || '27BBBBB0000B1Z5'; |
| }; |
|
|
| |
| |
| |
| |
| const handleAbsorption = async (req, res) => { |
| const { |
| exporterPhone, |
| importerPhone, |
| exporterTruckPlate, |
| importerTruckPlate, |
| exchangedGoods |
| } = req.body; |
|
|
| const dispatcherId = req.user?.id; |
|
|
| try { |
| |
| const transactionResult = await prisma.$transaction(async (tx) => { |
| |
| const dispatcher = await tx.user.findUnique({ |
| where: { id: dispatcherId || 'mock-dispatcher-id' }, |
| include: { courierCompany: true } |
| }); |
|
|
| |
| const exporterDriver = await tx.user.findUnique({ |
| where: { phone: exporterPhone }, |
| include: { courierCompany: true } |
| }); |
|
|
| const importerDriver = await tx.user.findUnique({ |
| where: { phone: importerPhone }, |
| include: { courierCompany: true } |
| }); |
|
|
| if (!exporterDriver || !importerDriver) { |
| throw new Error('One or both drivers not found by phone number'); |
| } |
|
|
| |
| const truckA = await tx.truck.findUnique({ |
| where: { licensePlate: exporterTruckPlate } |
| }); |
| const truckB = await tx.truck.findUnique({ |
| where: { licensePlate: importerTruckPlate } |
| }); |
|
|
| if (!truckA || !truckB) { |
| throw new Error('One or both trucks not found by license plate'); |
| } |
|
|
| |
| const exporterDeliveries = await tx.delivery.findMany({ |
| where: { |
| driverId: exporterDriver.id, |
| status: { not: 'COMPLETED' } |
| }, |
| include: { |
| shipment: { |
| include: { recipient: true } |
| } |
| } |
| }); |
|
|
| const transferredDeliveries = exporterDeliveries.filter(d => exchangedGoods.includes(d.id)); |
| const remainingDeliveries = exporterDeliveries.filter(d => !exchangedGoods.includes(d.id)); |
|
|
| if (transferredDeliveries.length === 0) { |
| throw new Error('No valid deliveries found to transfer'); |
| } |
|
|
| |
| await tx.delivery.updateMany({ |
| where: { id: { in: exchangedGoods } }, |
| data: { |
| driverId: importerDriver.id, |
| truckId: truckB.id, |
| status: 'ABSORPTION_TRANSFERRED' |
| } |
| }); |
|
|
| |
| const supplierGstin = dispatcher?.courierCompany?.gstin || exporterDriver.courierCompany?.gstin || '27AAAAA0000A1Z5'; |
|
|
| |
| const now = new Date(); |
| const genDateStr = now.toLocaleString(); |
| const docDateStr = now.toLocaleDateString(); |
|
|
| |
| const billANo = generateBillNo(); |
| const totalDistA = remainingDeliveries.reduce((sum, d) => sum + (d.distanceKm || 0), 0); |
| const validUntilA = calculateValidity(totalDistA); |
| const recipientGstinA = remainingDeliveries.length > 0 ? await resolveRecipientGstin(remainingDeliveries[0], tx) : '27BBBBB0000B1Z5'; |
|
|
| const qrDataA = await EWayBillService.generateQRCode({ |
| billNo: billANo, |
| driverName: exporterDriver.name, |
| vehicleNo: truckA.licensePlate |
| }); |
|
|
| const billAData = { |
| billNo: billANo, |
| generatedDate: genDateStr, |
| generatedBy: exporterDriver.name, |
| validFrom: genDateStr, |
| validUntil: validUntilA.toLocaleString(), |
| qrCode: qrDataA, |
| supplierGstin, |
| dispatchPlace: exporterDriver.homeBaseCity || 'Origin Hub', |
| recipientGstin: recipientGstinA, |
| deliveryPlace: remainingDeliveries[0]?.dropLocation || 'Transit Point', |
| docNo: `E-ABS-${uuidv4().substring(0, 8).toUpperCase()}`, |
| docDate: docDateStr, |
| transactionType: 'Absorption - Remaining', |
| vehicleNo: truckA.licensePlate, |
| fromLocation: exporterDriver.homeBaseCity || 'Current Location', |
| goods: remainingDeliveries.map(d => ({ |
| hsnCode: '8708', |
| productName: d.cargoType, |
| quantity: d.packageCount, |
| unit: 'NOS', |
| value: d.baseEarnings.toFixed(2) |
| })) |
| }; |
|
|
| |
| const importerExistingDeliveries = await tx.delivery.findMany({ |
| where: { |
| driverId: importerDriver.id, |
| status: { not: 'COMPLETED' } |
| }, |
| include: { |
| shipment: { |
| include: { recipient: true } |
| } |
| } |
| }); |
|
|
| const combinedDeliveriesB = [...importerExistingDeliveries, ...transferredDeliveries]; |
| const billBNo = generateBillNo(); |
| const totalDistB = combinedDeliveriesB.reduce((sum, d) => sum + (d.distanceKm || 0), 0); |
| const validUntilB = calculateValidity(totalDistB); |
| const recipientGstinB = combinedDeliveriesB.length > 0 ? await resolveRecipientGstin(combinedDeliveriesB[0], tx) : '27CCCCC0000C1Z5'; |
|
|
| const qrDataB = await EWayBillService.generateQRCode({ |
| billNo: billBNo, |
| driverName: importerDriver.name, |
| vehicleNo: truckB.licensePlate |
| }); |
|
|
| const billBData = { |
| billNo: billBNo, |
| generatedDate: genDateStr, |
| generatedBy: importerDriver.name, |
| validFrom: genDateStr, |
| validUntil: validUntilB.toLocaleString(), |
| qrCode: qrDataB, |
| supplierGstin, |
| dispatchPlace: importerDriver.homeBaseCity || 'Transit Hub', |
| recipientGstin: recipientGstinB, |
| deliveryPlace: combinedDeliveriesB[0]?.dropLocation || 'Final Hub', |
| docNo: `I-ABS-${uuidv4().substring(0, 8).toUpperCase()}`, |
| docDate: docDateStr, |
| transactionType: 'Absorption - Combined', |
| vehicleNo: truckB.licensePlate, |
| fromLocation: importerDriver.homeBaseCity || 'Handover Point', |
| goods: combinedDeliveriesB.map(d => ({ |
| hsnCode: '8708', |
| productName: d.cargoType, |
| quantity: d.packageCount, |
| unit: 'NOS', |
| value: d.baseEarnings.toFixed(2) |
| })) |
| }; |
|
|
| |
| await tx.eWayBill.createMany({ |
| data: [ |
| { |
| billNo: billANo, |
| vehicleNo: truckA.licensePlate, |
| from: billAData.fromLocation, |
| to: billAData.deliveryPlace, |
| distance: totalDistA.toString(), |
| driverId: exporterDriver.id, |
| cargoValue: remainingDeliveries.reduce((sum, d) => sum + d.baseEarnings, 0).toString(), |
| validUntil: validUntilA, |
| status: 'ACTIVE' |
| }, |
| { |
| billNo: billBNo, |
| vehicleNo: truckB.licensePlate, |
| from: billBData.fromLocation, |
| to: billBData.deliveryPlace, |
| distance: totalDistB.toString(), |
| driverId: importerDriver.id, |
| cargoValue: combinedDeliveriesB.reduce((sum, d) => sum + d.baseEarnings, 0).toString(), |
| validUntil: validUntilB, |
| status: 'ACTIVE' |
| } |
| ] |
| }); |
|
|
| |
| await tx.absorptionTransfer.updateMany({ |
| where: { |
| exporterDriverId: exporterDriver.id, |
| importerDriverId: importerDriver.id, |
| status: 'PENDING' |
| }, |
| data: { |
| status: 'COMPLETED', |
| completedAt: now |
| } |
| }); |
|
|
| return { billAData, billBData }; |
| }); |
|
|
| |
| console.log('Generating PDFs for:', { |
| billA: transactionResult.billAData.billNo, |
| billB: transactionResult.billBData.billNo |
| }); |
|
|
| const [pdfA, pdfB] = await Promise.all([ |
| EWayBillService.generatePDF(transactionResult.billAData), |
| EWayBillService.generatePDF(transactionResult.billBData) |
| ]); |
|
|
| console.log('PDF Generation complete. Result types:', { |
| pdfA: typeof pdfA, |
| pdfB: typeof pdfB, |
| isBufferA: Buffer.isBuffer(pdfA), |
| isBufferB: Buffer.isBuffer(pdfB) |
| }); |
|
|
| if (!pdfA || !pdfB) { |
| throw new Error(`PDF generation returned empty: pdfA=${!!pdfA}, pdfB=${!!pdfB}`); |
| } |
|
|
| |
| const zipBuffer = await EWayBillService.createZip([ |
| { name: `Truck_A_EWayBill_${transactionResult.billAData.billNo}.pdf`, buffer: pdfA }, |
| { name: `Truck_B_EWayBill_${transactionResult.billBData.billNo}.pdf`, buffer: pdfB } |
| ]); |
|
|
| res.setHeader('Content-Type', 'application/zip'); |
| res.setHeader('Content-Disposition', 'attachment; filename=ewaybills_absorption.zip'); |
| res.status(200).send(zipBuffer); |
|
|
| } catch (error) { |
| console.error('Absorption Handover Error:', error); |
| res.status(500).json({ |
| success: false, |
| message: error.message || 'Internal Server Error' |
| }); |
| } |
| }; |
|
|
| module.exports = { |
| handleAbsorption |
| }; |
|
|