"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.formatDocumentNumber = formatDocumentNumber; exports.ensureReservationDocumentNumbers = ensureReservationDocumentNumbers; exports.buildReservationInvoiceLineItems = buildReservationInvoiceLineItems; exports.getContract = getContract; exports.getBilling = getBilling; const prisma_1 = require("../../lib/prisma"); const repo = __importStar(require("./reservation.repo")); function formatDocumentNumber(prefix, sequence) { return `${prefix}-${String(sequence).padStart(6, '0')}`; } async function ensureReservationDocumentNumbers(companyId, reservationId) { return prisma_1.prisma.$transaction(async (tx) => { const reservation = await tx.reservation.findFirstOrThrow({ where: { id: reservationId, companyId }, select: { id: true, contractNumber: true, invoiceNumber: true }, }); if (reservation.contractNumber && reservation.invoiceNumber) return reservation; const settings = await tx.contractSettings.upsert({ where: { companyId }, update: {}, create: { companyId }, }); const data = {}; const settingsUpdate = {}; if (!reservation.contractNumber) { const nextSeq = settings.lastContractSeq + 1; data.contractNumber = formatDocumentNumber(settings.contractPrefix, nextSeq); settingsUpdate.lastContractSeq = nextSeq; } if (!reservation.invoiceNumber) { const nextSeq = settings.lastInvoiceSeq + 1; data.invoiceNumber = formatDocumentNumber(settings.invoicePrefix, nextSeq); settingsUpdate.lastInvoiceSeq = nextSeq; } if (Object.keys(settingsUpdate).length > 0) { await tx.contractSettings.update({ where: { companyId }, data: settingsUpdate }); } return tx.reservation.update({ where: { id: reservationId }, data, select: { id: true, contractNumber: true, invoiceNumber: true }, }); }); } function buildReservationInvoiceLineItems(reservation) { const baseAmount = reservation.dailyRate * reservation.totalDays; return [ { description: `${reservation.vehicle.year} ${reservation.vehicle.make} ${reservation.vehicle.model}`, qty: reservation.totalDays, unitPrice: reservation.dailyRate, total: baseAmount, category: 'RENTAL', }, ...reservation.insurances.map((ins) => ({ description: ins.policyName, qty: ins.chargeType === 'PER_DAY' ? reservation.totalDays : 1, unitPrice: ins.chargeType === 'PER_DAY' ? ins.chargeValue : ins.totalCharge, total: ins.totalCharge, category: 'INSURANCE', })), ...reservation.additionalDrivers .filter((d) => d.totalCharge > 0) .map((d) => ({ description: `Additional driver - ${d.firstName} ${d.lastName}`, qty: 1, unitPrice: d.totalCharge, total: d.totalCharge, category: 'ADDITIONAL_DRIVER', })), ...((reservation.pricingRulesApplied ?? []).map((rule, i) => ({ description: rule.name?.trim() || `Pricing adjustment ${i + 1}`, qty: 1, unitPrice: Number(rule.amount ?? 0), total: Number(rule.amount ?? 0), category: 'PRICING_RULE', }))), ...(reservation.discountAmount > 0 ? [{ description: 'Discount', qty: 1, unitPrice: -reservation.discountAmount, total: -reservation.discountAmount, category: 'DISCOUNT', }] : []), ...(reservation.depositAmount > 0 ? [{ description: 'Security deposit', qty: 1, unitPrice: reservation.depositAmount, total: reservation.depositAmount, category: 'DEPOSIT', }] : []), ]; } async function getContract(id, companyId) { await ensureReservationDocumentNumbers(companyId, id); const reservation = await repo.findForContract(id, companyId); const contractSettings = reservation.company.contractSettings ?? await prisma_1.prisma.contractSettings.upsert({ where: { companyId }, update: {}, create: { companyId }, }); const extras = reservation.extras && typeof reservation.extras === 'object' && !Array.isArray(reservation.extras) ? reservation.extras : {}; const invoiceLineItems = buildReservationInvoiceLineItems({ dailyRate: reservation.dailyRate, totalDays: reservation.totalDays, discountAmount: reservation.discountAmount, depositAmount: reservation.depositAmount, pricingRulesApplied: Array.isArray(reservation.pricingRulesApplied) ? reservation.pricingRulesApplied : [], insurances: reservation.insurances, additionalDrivers: reservation.additionalDrivers, vehicle: reservation.vehicle, }); const subtotal = invoiceLineItems.reduce((s, i) => s + i.total, 0); const taxes = contractSettings.showTax && contractSettings.taxRate ? [{ label: contractSettings.taxLabel?.trim() || 'Tax', rate: contractSettings.taxRate, amount: Math.round(subtotal * contractSettings.taxRate / 100) }] : []; const taxTotal = taxes.reduce((s, t) => s + t.amount, 0); const grandTotal = subtotal + taxTotal; const amountPaid = reservation.rentalPayments .filter((p) => p.status === 'SUCCEEDED') .reduce((s, p) => s + p.amount, 0); const checkInInspection = reservation.inspections.find((i) => i.type === 'CHECKIN') ?? null; const checkOutInspection = reservation.inspections.find((i) => i.type === 'CHECKOUT') ?? null; return { reservationId: reservation.id, contractLocale: reservation.company.brand?.defaultLocale ?? 'en', contractNumber: reservation.contractNumber, invoiceNumber: reservation.invoiceNumber, generatedAt: new Date().toISOString(), status: reservation.status, paymentStatus: reservation.paymentStatus, paymentMode: typeof extras.paymentMode === 'string' ? extras.paymentMode : null, notes: reservation.notes, company: { name: reservation.company.brand?.displayName ?? reservation.company.name, legalName: contractSettings.legalName || reservation.company.name, email: reservation.company.brand?.publicEmail ?? reservation.company.email, phone: reservation.company.brand?.publicPhone ?? reservation.company.phone, address: reservation.company.brand?.publicAddress ?? reservation.company.address ?? null, city: reservation.company.brand?.publicCity ?? null, country: reservation.company.brand?.publicCountry ?? null, registrationNumber: contractSettings.registrationNumber, taxId: contractSettings.taxId, logoUrl: reservation.company.brand?.logoUrl ?? null, }, driver: { firstName: reservation.customer.firstName, lastName: reservation.customer.lastName, email: reservation.customer.email, phone: reservation.customer.phone, dateOfBirth: reservation.customer.dateOfBirth, driverLicense: reservation.customer.driverLicense, licenseCountry: reservation.customer.licenseCountry, licenseCategory: reservation.customer.licenseCategory, licenseIssuedAt: reservation.customer.licenseIssuedAt, licenseExpiry: reservation.customer.licenseExpiry, nationality: reservation.customer.nationality, }, additionalDrivers: reservation.additionalDrivers.map((d) => ({ id: d.id, firstName: d.firstName, lastName: d.lastName, email: d.email, phone: d.phone, driverLicense: d.driverLicense, licenseIssuedAt: d.licenseIssuedAt, licenseExpiry: d.licenseExpiry, licenseCountry: d.nationality, dateOfBirth: d.dateOfBirth, totalCharge: d.totalCharge, })), vehicle: { make: reservation.vehicle.make, model: reservation.vehicle.model, year: reservation.vehicle.year, color: reservation.vehicle.color, licensePlate: reservation.vehicle.licensePlate, vin: reservation.vehicle.vin, category: reservation.vehicle.category, }, rentalPeriod: { startDate: reservation.startDate, endDate: reservation.endDate, totalDays: reservation.totalDays, pickupLocation: reservation.pickupLocation, returnLocation: reservation.returnLocation, }, insurance: { total: reservation.insuranceTotal, policies: reservation.insurances.map((ins) => ({ id: ins.id, name: ins.policyName, chargeType: ins.chargeType, unitPrice: ins.chargeValue, totalCharge: ins.totalCharge, })), }, inspections: { checkIn: checkInInspection, checkOut: checkOutInspection }, terms: { terms: contractSettings.terms, fuelPolicy: contractSettings.fuelPolicy, fuelPolicyType: contractSettings.fuelPolicyType, depositPolicy: contractSettings.depositPolicy, lateFeePolicy: contractSettings.lateFeePolicy, damagePolicy: contractSettings.damagePolicy, footerNote: contractSettings.contractFooterNote, signatureRequired: contractSettings.signatureRequired, }, invoice: { currency: reservation.company.accountingSettings?.currency ?? reservation.company.brand?.defaultCurrency ?? 'MAD', lineItems: invoiceLineItems, subtotal, taxes, taxTotal, total: grandTotal, amountPaid, balanceDue: grandTotal - amountPaid, payments: reservation.rentalPayments.map((p) => ({ id: p.id, amount: p.amount, currency: p.currency, type: p.type, status: p.status, provider: p.paymentProvider, paymentMethod: p.paymentMethod, paidAt: p.paidAt, createdAt: p.createdAt, })), }, }; } async function getBilling(id, companyId) { const reservation = await repo.findForBilling(id, companyId); const baseAmount = reservation.dailyRate * reservation.totalDays; const lineItems = [ { description: `${reservation.vehicle.year} ${reservation.vehicle.make} ${reservation.vehicle.model} — ${reservation.totalDays} day(s)`, qty: reservation.totalDays, unitPrice: reservation.dailyRate, total: baseAmount, category: 'RENTAL', }, ...reservation.insurances.map((ins) => ({ description: ins.policyName, qty: ins.chargeType === 'PER_DAY' ? reservation.totalDays : 1, unitPrice: ins.chargeType === 'PER_DAY' ? ins.chargeValue : ins.totalCharge, total: ins.totalCharge, category: 'INSURANCE', })), ...reservation.additionalDrivers .filter((d) => d.totalCharge > 0) .map((d) => ({ description: `Additional Driver — ${d.firstName} ${d.lastName}`, qty: 1, unitPrice: d.totalCharge, total: d.totalCharge, category: 'ADDITIONAL_DRIVER', })), ...(reservation.depositAmount > 0 ? [{ description: 'Security Deposit (refundable)', qty: 1, unitPrice: reservation.depositAmount, total: reservation.depositAmount, category: 'DEPOSIT', }] : []), ]; const grandTotal = lineItems.reduce((s, i) => s + i.total, 0) - reservation.discountAmount; return { lineItems, discountAmount: reservation.discountAmount, pricingRulesApplied: reservation.pricingRulesApplied, pricingRulesTotal: reservation.pricingRulesTotal, grandTotal, }; } //# sourceMappingURL=reservation.document.service.js.map