FairRelay / ops /backend-dm /controllers /ewayBillController.js
MouleeswaranM's picture
Upload folder using huggingface_hub
fcf8749 verified
const prisma = require('../config/database');
const EWayBillService = require('../services/ewayBill.service');
const { v4: uuidv4 } = require('uuid');
/**
* Generate a unique 12-digit numeric e-Way Bill number
*/
const generateBillNo = () => {
return Math.floor(100000000000 + Math.random() * 900000000000).toString();
};
/**
* Calculate validity days based on distance (1 day per 100km per government rules for regular cargo)
*/
const calculateValidity = (distanceKm) => {
const days = Math.max(1, Math.ceil(distanceKm / 100)); // Refined rule: 100km per day
const validUntil = new Date();
validUntil.setDate(validUntil.getDate() + days);
return validUntil;
};
/**
* Resolve GSTIN for Recipient by traversing Delivery -> Shipment -> Customer
*/
const resolveRecipientGstin = async (delivery, tx) => {
if (delivery.shipment && delivery.shipment.recipient && delivery.shipment.recipient.gstin) {
return delivery.shipment.recipient.gstin;
}
// Fallback or detailed lookup
const fullDelivery = await tx.delivery.findUnique({
where: { id: delivery.id },
include: {
shipment: {
include: {
recipient: true
}
}
}
});
return fullDelivery?.shipment?.recipient?.gstin || '27BBBBB0000B1Z5';
};
/**
* POST /api/eway-bill/absorption
* Refined Handover Logic with strict identification
*/
const handleAbsorption = async (req, res) => {
const {
exporterPhone,
importerPhone,
exporterTruckPlate,
importerTruckPlate,
exchangedGoods
} = req.body;
const dispatcherId = req.user?.id; // From JWT middleware
try {
// 1. Resolve & Update Data using Prisma Transaction
const transactionResult = await prisma.$transaction(async (tx) => {
// Find Dispatcher and their Company
const dispatcher = await tx.user.findUnique({
where: { id: dispatcherId || 'mock-dispatcher-id' }, // Handle missing for testing
include: { courierCompany: true }
});
// Find Drivers by Phone
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');
}
// Find Trucks by License Plate
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');
}
// Fetch Exporter's Deliveries to separate remaining from transferred
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');
}
// Update Deliveries: Transfer to Truck B / Importer
await tx.delivery.updateMany({
where: { id: { in: exchangedGoods } },
data: {
driverId: importerDriver.id,
truckId: truckB.id,
status: 'ABSORPTION_TRANSFERRED'
}
});
// Resolve GSTINs
const supplierGstin = dispatcher?.courierCompany?.gstin || exporterDriver.courierCompany?.gstin || '27AAAAA0000A1Z5';
// Shared Validity/Date Info
const now = new Date();
const genDateStr = now.toLocaleString();
const docDateStr = now.toLocaleDateString();
// --- Generate Bill A Data (Exporter - Remaining) ---
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)
}))
};
// --- Generate Bill B Data (Importer - Original + Absorbed) ---
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)
}))
};
// Create EWayBill records in DB
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'
}
]
});
// Update existing AbsorptionTransfer record if it exists
await tx.absorptionTransfer.updateMany({
where: {
exporterDriverId: exporterDriver.id,
importerDriverId: importerDriver.id,
status: 'PENDING'
},
data: {
status: 'COMPLETED',
completedAt: now
}
});
return { billAData, billBData };
});
// 2. Render PDFs (Outside transaction for performance)
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}`);
}
// 3. Zip and stream
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
};