Files
alrahma_sunday_school_api/apps/api/dist/modules/reservations/reservation.document.service.js
T
2026-06-11 03:22:12 -04:00

279 lines
13 KiB
JavaScript

"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