archetecture security fix

This commit is contained in:
root
2026-06-11 03:22:12 -04:00
parent 6def9993da
commit 9483750161
3126 changed files with 177194 additions and 37211 deletions
@@ -0,0 +1,3 @@
export { applyAdditionalDriversToReservation, calculateAdditionalDriverCharge } from '../../services/additionalDriverService';
export declare function approveAdditionalDriver(reservationId: string, driverId: string, companyId: string, approved: boolean, note: string | undefined, approvedBy: string): Promise<any>;
//# sourceMappingURL=reservation.additional-driver.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.additional-driver.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.additional-driver.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mCAAmC,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAA;AAI7H,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,EACjB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,UAAU,EAAE,MAAM,gBAQnB"}
@@ -0,0 +1,50 @@
"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.calculateAdditionalDriverCharge = exports.applyAdditionalDriversToReservation = void 0;
exports.approveAdditionalDriver = approveAdditionalDriver;
var additionalDriverService_1 = require("../../services/additionalDriverService");
Object.defineProperty(exports, "applyAdditionalDriversToReservation", { enumerable: true, get: function () { return additionalDriverService_1.applyAdditionalDriversToReservation; } });
Object.defineProperty(exports, "calculateAdditionalDriverCharge", { enumerable: true, get: function () { return additionalDriverService_1.calculateAdditionalDriverCharge; } });
const repo = __importStar(require("./reservation.repo"));
async function approveAdditionalDriver(reservationId, driverId, companyId, approved, note, approvedBy) {
const driver = await repo.findAdditionalDriver(driverId, reservationId, companyId);
return repo.updateAdditionalDriver(driver.id, {
approvedAt: approved ? new Date() : null,
approvedBy: approved ? approvedBy : null,
approvalNote: note ?? driver.approvalNote,
});
}
//# sourceMappingURL=reservation.additional-driver.service.js.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.additional-driver.service.js","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.additional-driver.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,0DAcC;AAlBD,kFAA6H;AAApH,8IAAA,mCAAmC,OAAA;AAAE,0IAAA,+BAA+B,OAAA;AAE7E,yDAA0C;AAEnC,KAAK,UAAU,uBAAuB,CAC3C,aAAqB,EACrB,QAAgB,EAChB,SAAiB,EACjB,QAAiB,EACjB,IAAwB,EACxB,UAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,CAAC,CAAA;IAClF,OAAO,IAAI,CAAC,sBAAsB,CAAC,MAAM,CAAC,EAAE,EAAE;QAC5C,UAAU,EAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;QAC1C,UAAU,EAAI,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI;QAC1C,YAAY,EAAE,IAAI,IAAI,MAAM,CAAC,YAAY;KAC1C,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,137 @@
export declare function formatDocumentNumber(prefix: string, sequence: number): string;
export declare function ensureReservationDocumentNumbers(companyId: string, reservationId: string): Promise<any>;
export declare function buildReservationInvoiceLineItems(reservation: {
dailyRate: number;
totalDays: number;
discountAmount: number;
depositAmount: number;
pricingRulesApplied: Array<{
name?: string;
amount?: number;
type?: string;
}> | null;
insurances: Array<{
id: string;
policyName: string;
chargeType: string;
chargeValue: number;
totalCharge: number;
}>;
additionalDrivers: Array<{
id: string;
firstName: string;
lastName: string;
totalCharge: number;
}>;
vehicle: {
year: number;
make: string;
model: string;
};
}): {
description: string;
qty: number;
unitPrice: number;
total: number;
category: string;
}[];
export declare function getContract(id: string, companyId: string): Promise<{
reservationId: any;
contractLocale: any;
contractNumber: any;
invoiceNumber: any;
generatedAt: string;
status: any;
paymentStatus: any;
paymentMode: string | null;
notes: any;
company: {
name: any;
legalName: any;
email: any;
phone: any;
address: any;
city: any;
country: any;
registrationNumber: any;
taxId: any;
logoUrl: any;
};
driver: {
firstName: any;
lastName: any;
email: any;
phone: any;
dateOfBirth: any;
driverLicense: any;
licenseCountry: any;
licenseCategory: any;
licenseIssuedAt: any;
licenseExpiry: any;
nationality: any;
};
additionalDrivers: any;
vehicle: {
make: any;
model: any;
year: any;
color: any;
licensePlate: any;
vin: any;
category: any;
};
rentalPeriod: {
startDate: any;
endDate: any;
totalDays: any;
pickupLocation: any;
returnLocation: any;
};
insurance: {
total: any;
policies: any;
};
inspections: {
checkIn: any;
checkOut: any;
};
terms: {
terms: any;
fuelPolicy: any;
fuelPolicyType: any;
depositPolicy: any;
lateFeePolicy: any;
damagePolicy: any;
footerNote: any;
signatureRequired: any;
};
invoice: {
currency: any;
lineItems: {
description: string;
qty: number;
unitPrice: number;
total: number;
category: string;
}[];
subtotal: number;
taxes: {
label: any;
rate: any;
amount: number;
}[];
taxTotal: number;
total: number;
amountPaid: any;
balanceDue: number;
payments: any;
};
}>;
export declare function getBilling(id: string, companyId: string): Promise<{
lineItems: any[];
discountAmount: any;
pricingRulesApplied: any;
pricingRulesTotal: any;
grandTotal: number;
}>;
//# sourceMappingURL=reservation.document.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.document.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.document.service.ts"],"names":[],"mappings":"AAGA,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE7E;AAED,wBAAsB,gCAAgC,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,gBAsC9F;AAED,wBAAgB,gCAAgC,CAAC,WAAW,EAAE;IAC5D,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAA;IACpF,UAAU,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACnH,iBAAiB,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAClG,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACvD;;;;;;IAgDA;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwH9D;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;GAkC7D"}
@@ -0,0 +1,279 @@
"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
File diff suppressed because one or more lines are too long
@@ -0,0 +1,19 @@
export declare function getInspections(reservationId: string, companyId: string): Promise<any>;
export declare function upsertInspection(reservationId: string, companyId: string, type: 'CHECKIN' | 'CHECKOUT', body: {
mileage?: number;
fuelLevel: string;
fuelCharge?: number;
generalCondition?: string;
employeeNotes?: string;
customerAgreed?: boolean;
damagePoints?: Array<{
viewType: string;
x: number;
y: number;
damageType: string;
severity?: string;
description?: string;
isPreExisting?: boolean;
}>;
}, inspectedBy: string): Promise<any>;
//# sourceMappingURL=reservation.inspection.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.inspection.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.inspection.service.ts"],"names":[],"mappings":"AAKA,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAE5E;AAED,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,SAAS,GAAG,UAAU,EAC5B,IAAI,EAAE;IACJ,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,YAAY,CAAC,EAAE,KAAK,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;QAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KACjE,CAAC,CAAA;CACH,EACD,WAAW,EAAE,MAAM,gBA6EpB"}
@@ -0,0 +1,117 @@
"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.getInspections = getInspections;
exports.upsertInspection = upsertInspection;
const prisma_1 = require("../../lib/prisma");
const errors_1 = require("../../http/errors");
const reservation_presenter_1 = require("./reservation.presenter");
const repo = __importStar(require("./reservation.repo"));
async function getInspections(reservationId, companyId) {
return repo.findInspections(reservationId, companyId);
}
async function upsertInspection(reservationId, companyId, type, body, inspectedBy) {
const reservation = await repo.findForInspection(reservationId, companyId);
const workflow = (0, reservation_presenter_1.buildReservationWorkflow)(reservation);
if (workflow.closed)
throw new errors_1.AppError('This reservation has already been closed', 400, 'reservation_closed');
if (type === 'CHECKIN' && !workflow.checkInInspectionEditable) {
throw new errors_1.AppError('Check-in inspection is not editable at this stage', 400, 'invalid_status');
}
if (type === 'CHECKOUT' && !workflow.checkOutInspectionEditable) {
throw new errors_1.AppError('Check-out inspection is only editable after the vehicle is returned', 400, 'invalid_status');
}
const inspection = await prisma_1.prisma.damageInspection.upsert({
where: { reservationId_type: { reservationId, type } },
update: {
mileage: body.mileage ?? null,
fuelLevel: body.fuelLevel,
fuelCharge: body.fuelCharge ?? null,
generalCondition: body.generalCondition ?? null,
employeeNotes: body.employeeNotes ?? null,
customerAgreed: body.customerAgreed ?? false,
inspectedBy,
inspectedAt: new Date(),
damagePoints: { deleteMany: {}, create: body.damagePoints },
},
create: {
reservationId, companyId, type,
mileage: body.mileage ?? null,
fuelLevel: body.fuelLevel,
fuelCharge: body.fuelCharge ?? null,
generalCondition: body.generalCondition ?? null,
employeeNotes: body.employeeNotes ?? null,
customerAgreed: body.customerAgreed ?? false,
inspectedBy,
damagePoints: { create: body.damagePoints },
},
include: { damagePoints: true },
});
const damageData = (body.damagePoints ?? []).map((p) => ({
viewType: p.viewType, x: p.x, y: p.y, damageType: p.damageType,
severity: p.severity ?? 'MINOR', note: p.description ?? '', isPreExisting: p.isPreExisting ?? false,
}));
await prisma_1.prisma.damageReport.upsert({
where: { reservationId_type: { reservationId, type } },
update: {
damages: damageData,
fuelLevel: body.fuelLevel,
mileage: body.mileage ?? null,
inspectedAt: new Date(),
inspectedBy,
customerSignedAt: (body.customerAgreed ?? false) ? new Date() : null,
customerName: `${reservation.customer.firstName} ${reservation.customer.lastName}`,
},
create: {
reservationId, companyId, type,
damages: damageData,
photos: [],
fuelLevel: body.fuelLevel,
mileage: body.mileage ?? null,
inspectedAt: new Date(),
inspectedBy,
customerSignedAt: (body.customerAgreed ?? false) ? new Date() : null,
customerName: `${reservation.customer.firstName} ${reservation.customer.lastName}`,
},
});
await prisma_1.prisma.reservation.update({
where: { id: reservationId },
data: type === 'CHECKIN'
? { checkInMileage: body.mileage ?? null, checkInFuelLevel: body.fuelLevel }
: { checkOutMileage: body.mileage ?? null, checkOutFuelLevel: body.fuelLevel },
});
return inspection;
}
//# sourceMappingURL=reservation.inspection.service.js.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.inspection.service.js","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.inspection.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAKA,wCAEC;AAED,4CA6FC;AAtGD,6CAAyC;AACzC,8CAA4C;AAC5C,mEAAkE;AAClE,yDAA0C;AAEnC,KAAK,UAAU,cAAc,CAAC,aAAqB,EAAE,SAAiB;IAC3E,OAAO,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;AACvD,CAAC;AAEM,KAAK,UAAU,gBAAgB,CACpC,aAAqB,EACrB,SAAiB,EACjB,IAA4B,EAC5B,IAWC,EACD,WAAmB;IAEnB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,aAAa,EAAE,SAAS,CAAC,CAAA;IAC1E,MAAM,QAAQ,GAAM,IAAA,gDAAwB,EAAC,WAAW,CAAC,CAAA;IAEzD,IAAI,QAAQ,CAAC,MAAM;QAAE,MAAM,IAAI,iBAAQ,CAAC,0CAA0C,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAA;IAC9G,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,yBAAyB,EAAE,CAAC;QAC9D,MAAM,IAAI,iBAAQ,CAAC,mDAAmD,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAA;IAChG,CAAC;IACD,IAAI,IAAI,KAAK,UAAU,IAAI,CAAC,QAAQ,CAAC,0BAA0B,EAAE,CAAC;QAChE,MAAM,IAAI,iBAAQ,CAAC,qEAAqE,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAA;IAClH,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,eAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC;QACtD,KAAK,EAAE,EAAE,kBAAkB,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE;QACtD,MAAM,EAAE;YACN,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAgB;YAChC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;YACnC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI;YAC/C,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;YACzC,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;YAC5C,WAAW;YACX,WAAW,EAAE,IAAI,IAAI,EAAE;YACvB,YAAY,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,YAAmB,EAAE;SACnE;QACD,MAAM,EAAE;YACN,aAAa,EAAE,SAAS,EAAE,IAAI;YAC9B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;YAC7B,SAAS,EAAE,IAAI,CAAC,SAAgB;YAChC,UAAU,EAAE,IAAI,CAAC,UAAU,IAAI,IAAI;YACnC,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,IAAI,IAAI;YAC/C,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;YACzC,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;YAC5C,WAAW;YACX,YAAY,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,YAAmB,EAAE;SACnD;QACD,OAAO,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;KAChC,CAAC,CAAA;IAEF,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvD,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU;QAC9D,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,IAAI,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC,aAAa,IAAI,KAAK;KACpG,CAAC,CAAC,CAAA;IAEH,MAAM,eAAM,CAAC,YAAY,CAAC,MAAM,CAAC;QAC/B,KAAK,EAAE,EAAE,kBAAkB,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE;QACtD,MAAM,EAAE;YACN,OAAO,EAAE,UAAU;YACnB,SAAS,EAAE,IAAI,CAAC,SAAgB;YAChC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;YAC7B,WAAW,EAAE,IAAI,IAAI,EAAE;YACvB,WAAW;YACX,gBAAgB,EAAE,CAAC,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;YACpE,YAAY,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,SAAS,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,EAAE;SACnF;QACD,MAAM,EAAE;YACN,aAAa,EAAE,SAAS,EAAE,IAAI;YAC9B,OAAO,EAAE,UAAU;YACnB,MAAM,EAAE,EAAE;YACV,SAAS,EAAE,IAAI,CAAC,SAAgB;YAChC,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI;YAC7B,WAAW,EAAE,IAAI,IAAI,EAAE;YACvB,WAAW;YACX,gBAAgB,EAAE,CAAC,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;YACpE,YAAY,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,SAAS,IAAI,WAAW,CAAC,QAAQ,CAAC,QAAQ,EAAE;SACnF;KACF,CAAC,CAAA;IAEF,MAAM,eAAM,CAAC,WAAW,CAAC,MAAM,CAAC;QAC9B,KAAK,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE;QAC5B,IAAI,EAAE,IAAI,KAAK,SAAS;YACtB,CAAC,CAAC,EAAE,cAAc,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,SAAS,EAAE;YAC5E,CAAC,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,OAAO,IAAI,IAAI,EAAE,iBAAiB,EAAE,IAAI,CAAC,SAAS,EAAE;KACjF,CAAC,CAAA;IAEF,OAAO,UAAU,CAAA;AACnB,CAAC"}
@@ -0,0 +1,2 @@
export { applyInsurancesToReservation, calculateInsuranceCharge } from '../../services/insuranceService';
//# sourceMappingURL=reservation.insurance.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.insurance.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.insurance.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAA"}
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateInsuranceCharge = exports.applyInsurancesToReservation = void 0;
var insuranceService_1 = require("../../services/insuranceService");
Object.defineProperty(exports, "applyInsurancesToReservation", { enumerable: true, get: function () { return insuranceService_1.applyInsurancesToReservation; } });
Object.defineProperty(exports, "calculateInsuranceCharge", { enumerable: true, get: function () { return insuranceService_1.calculateInsuranceCharge; } });
//# sourceMappingURL=reservation.insurance.service.js.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.insurance.service.js","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.insurance.service.ts"],"names":[],"mappings":";;;AAAA,oEAAwG;AAA/F,gIAAA,4BAA4B,OAAA;AAAE,4HAAA,wBAAwB,OAAA"}
@@ -0,0 +1,7 @@
export declare function confirmReservation(id: string, companyId: string): Promise<any>;
export declare function checkinReservation(id: string, companyId: string, mileage?: number): Promise<any>;
export declare function checkoutReservation(id: string, companyId: string, mileage?: number): Promise<any>;
export declare function closeReservation(id: string, companyId: string, closedBy: string): Promise<any>;
export declare function extendReservation(id: string, companyId: string, newEndDate: Date, reason: string): Promise<any>;
export declare function cancelReservation(id: string, companyId: string, reason?: string): Promise<any>;
//# sourceMappingURL=reservation.lifecycle.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.lifecycle.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.lifecycle.service.ts"],"names":[],"mappings":"AA8CA,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAsDrE;AAED,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,gBAYvF;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,gBAcxF;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,gBA8CrF;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,gBAkCtG;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,gBAUrF"}
@@ -0,0 +1,253 @@
"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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.confirmReservation = confirmReservation;
exports.checkinReservation = checkinReservation;
exports.checkoutReservation = checkoutReservation;
exports.closeReservation = closeReservation;
exports.extendReservation = extendReservation;
exports.cancelReservation = cancelReservation;
const crypto_1 = __importDefault(require("crypto"));
const prisma_1 = require("../../lib/prisma");
const errors_1 = require("../../http/errors");
const licenseValidationService_1 = require("../../services/licenseValidationService");
const notificationService_1 = require("../../services/notificationService");
const emailTranslations_1 = require("../../lib/emailTranslations");
const notificationLocalizationService_1 = require("../../services/notificationLocalizationService");
const reservation_presenter_1 = require("./reservation.presenter");
const repo = __importStar(require("./reservation.repo"));
function buildPickupAddress(value) {
if (!value || typeof value !== 'object' || Array.isArray(value))
return '';
const address = value;
return [
typeof address.streetAddress === 'string' ? address.streetAddress.trim() : '',
typeof address.city === 'string' ? address.city.trim() : '',
typeof address.country === 'string' ? address.country.trim() : '',
].filter(Boolean).join(', ');
}
async function assertLicenseCompliance(reservationId, companyId) {
const reservation = await repo.findForLicenseCheck(reservationId, companyId);
const customerLicense = (0, licenseValidationService_1.validateLicense)(reservation.customer.licenseExpiry);
const customerDenied = ['DENIED', 'EXPIRED'].includes(reservation.customer.licenseValidationStatus);
if (customerDenied || customerLicense.status === 'EXPIRED') {
throw new errors_1.AppError('Primary driver license is not valid for this reservation', 400, 'invalid_primary_license');
}
if (customerLicense.requiresApproval && reservation.customer.licenseValidationStatus !== 'APPROVED') {
throw new errors_1.AppError('Primary driver license requires manager approval before this reservation can proceed', 400, 'primary_license_requires_approval');
}
const blockedDriver = reservation.additionalDrivers.find((d) => {
if (d.licenseExpired)
return true;
return d.requiresApproval && !d.approvedAt;
});
if (blockedDriver) {
throw new errors_1.AppError(`Additional driver ${blockedDriver.firstName} ${blockedDriver.lastName} requires approval before this reservation can proceed`, 400, 'additional_driver_requires_approval');
}
}
async function confirmReservation(id, companyId) {
const reservation = await repo.findByIdSimple(id, companyId);
if (reservation.status !== 'DRAFT')
throw new errors_1.AppError('Only DRAFT reservations can be confirmed', 400, 'invalid_status');
await assertLicenseCompliance(reservation.id, companyId);
const updated = await repo.updateById(id, { status: 'CONFIRMED' });
await repo.updateVehicleStatus(reservation.vehicleId, 'RESERVED');
const [customer, company, vehicle] = await Promise.all([
repo.findCustomerById(reservation.customerId),
repo.findCompanyWithBrand(companyId),
repo.findVehicle(reservation.vehicleId, companyId),
]);
const fallbackLocale = (0, notificationLocalizationService_1.coerceNotificationLocale)(company?.brand?.defaultLocale);
const pickupLocation = reservation.pickupLocation?.trim() || buildPickupAddress(company?.address);
await (0, notificationService_1.sendNotification)({
type: 'BOOKING_CONFIRMED',
companyId,
renterId: reservation.renterId ?? undefined,
email: customer.email,
channels: ['EMAIL', 'IN_APP'],
locale: reservation.renterId ? undefined : fallbackLocale,
templateKey: 'booking.confirmed',
templateVariables: {
firstName: customer.firstName,
companyName: company?.brand?.displayName ?? company?.name ?? 'RentalDriveGo',
startDate: {
type: 'date',
value: reservation.startDate,
options: {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
},
endDate: {
type: 'date',
value: reservation.endDate,
options: {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
},
},
pickupLocation,
vehicleName: `${vehicle.year} ${vehicle.make} ${vehicle.model}`,
},
}).catch(() => null);
return updated;
}
async function checkinReservation(id, companyId, mileage) {
const reservation = await repo.findByIdSimple(id, companyId);
if (reservation.status !== 'CONFIRMED')
throw new errors_1.AppError('Only CONFIRMED reservations can be checked in', 400, 'invalid_status');
await assertLicenseCompliance(reservation.id, companyId);
const updated = await repo.updateById(id, {
status: 'ACTIVE',
checkedInAt: new Date(),
checkInMileage: mileage ?? null,
});
await repo.updateVehicleStatus(reservation.vehicleId, 'RENTED');
return updated;
}
async function checkoutReservation(id, companyId, mileage) {
const reservation = await repo.findByIdForCheckout(id, companyId);
if (reservation.status !== 'ACTIVE')
throw new errors_1.AppError('Only ACTIVE reservations can be checked out', 400, 'invalid_status');
const reviewToken = reservation.reviewToken ?? crypto_1.default.randomBytes(32).toString('hex');
const updated = await repo.updateById(id, {
status: 'COMPLETED',
checkedOutAt: new Date(),
checkOutMileage: mileage ?? null,
reviewToken,
});
await repo.updateVehicleStatus(reservation.vehicleId, 'RETURNED', mileage);
return updated;
}
async function closeReservation(id, companyId, closedBy) {
const reservation = await repo.findForClose(id, companyId);
const workflow = (0, reservation_presenter_1.buildReservationWorkflow)(reservation);
if (reservation.status !== 'COMPLETED')
throw new errors_1.AppError('Only completed reservations can be closed', 400, 'invalid_status');
if (workflow.closed)
throw new errors_1.AppError('This reservation is already closed', 400, 'already_closed');
const extras = (0, reservation_presenter_1.parseReservationExtras)(reservation.extras);
extras.reservationClosedAt = new Date().toISOString();
extras.reservationClosedBy = closedBy;
const updated = await prisma_1.prisma.reservation.update({
where: { id: reservation.id },
data: { extras: extras },
include: { vehicle: true, customer: true, insurances: true, additionalDrivers: true, rentalPayments: true, damageReports: true },
});
// Send review request email if eligible
if (reservation.reviewToken && !reservation.reviewPaused) {
try {
const customer = updated.customer;
if (!customer.reviewOptOut && customer.email) {
const company = await repo.findCompanyWithBrand(companyId);
const lang = (company?.brand?.defaultLocale ?? 'fr');
const companyName = company?.brand?.displayName ?? company?.name ?? 'the rental company';
const vehicleLabel = `${updated.vehicle.year} ${updated.vehicle.make} ${updated.vehicle.model}`;
const reviewUrl = `${process.env.MARKETPLACE_URL ?? 'http://localhost:3000'}/review?token=${reservation.reviewToken}`;
(0, notificationService_1.sendTransactionalEmail)({
to: customer.email,
subject: emailTranslations_1.reviewRequestEmail.subject(vehicleLabel, lang),
html: emailTranslations_1.reviewRequestEmail.html({ firstName: customer.firstName, companyName, vehicleLabel, reviewUrl }, lang),
text: emailTranslations_1.reviewRequestEmail.text({ firstName: customer.firstName, companyName, vehicleLabel, reviewUrl }, lang),
}).catch(() => null);
await prisma_1.prisma.reservation.update({
where: { id: reservation.id },
data: { reviewRequestSentAt: new Date() },
});
}
}
catch {
// Non-blocking: do not fail close if email fails
}
}
return (0, reservation_presenter_1.serializeReservationForDashboard)(updated);
}
async function extendReservation(id, companyId, newEndDate, reason) {
const reservation = await repo.findByIdSimple(id, companyId);
if (!['CONFIRMED', 'ACTIVE'].includes(reservation.status)) {
throw new errors_1.AppError('Only CONFIRMED or ACTIVE reservations can be extended', 400, 'invalid_status');
}
if (newEndDate <= reservation.endDate) {
throw new errors_1.AppError('New end date must be after the current end date', 400, 'invalid_end_date');
}
const conflict = await repo.findConflict(reservation.vehicleId, reservation.endDate, newEndDate, id);
if (conflict) {
throw new errors_1.AppError('Vehicle is unavailable for the requested extension period', 409, 'vehicle_unavailable');
}
const additionalDays = Math.ceil((newEndDate.getTime() - reservation.endDate.getTime()) / (1000 * 60 * 60 * 24));
const additionalAmount = additionalDays * reservation.dailyRate;
const extras = (0, reservation_presenter_1.parseReservationExtras)(reservation.extras);
if (!Array.isArray(extras.extensions))
extras.extensions = [];
extras.extensions.push({
previousEndDate: reservation.endDate.toISOString(),
newEndDate: newEndDate.toISOString(),
additionalDays,
additionalAmount,
reason,
approvedAt: new Date().toISOString(),
});
const updated = await prisma_1.prisma.reservation.update({
where: { id },
data: {
endDate: newEndDate,
totalDays: reservation.totalDays + additionalDays,
totalAmount: reservation.totalAmount + additionalAmount,
extras: extras,
},
});
return updated;
}
async function cancelReservation(id, companyId, reason) {
const reservation = await repo.findByIdSimple(id, companyId);
if (['COMPLETED', 'CANCELLED'].includes(reservation.status)) {
throw new errors_1.AppError('Reservation cannot be cancelled', 400, 'invalid_status');
}
const updated = await repo.updateById(id, { status: 'CANCELLED', cancelReason: reason ?? null, cancelledBy: 'COMPANY' });
if (['CONFIRMED', 'ACTIVE', 'DRAFT'].includes(reservation.status)) {
await repo.updateVehicleStatus(reservation.vehicleId, 'AVAILABLE');
}
return updated;
}
//# sourceMappingURL=reservation.lifecycle.service.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
export declare function listPhotos(reservationId: string, companyId: string): Promise<any>;
export declare function uploadPhoto(reservationId: string, companyId: string, type: 'PICKUP' | 'DROPOFF', buffer: Buffer): Promise<any>;
export declare function deletePhoto(photoId: string, reservationId: string, companyId: string): Promise<void>;
//# sourceMappingURL=reservation.photo.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.photo.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.photo.service.ts"],"names":[],"mappings":"AAIA,wBAAsB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAOxE;AAED,wBAAsB,WAAW,CAC/B,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,QAAQ,GAAG,SAAS,EAC1B,MAAM,EAAE,MAAM,gBAMf;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAO1F"}
@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listPhotos = listPhotos;
exports.uploadPhoto = uploadPhoto;
exports.deletePhoto = deletePhoto;
const prisma_1 = require("../../lib/prisma");
const storage_1 = require("../../lib/storage");
const errors_1 = require("../../http/errors");
async function listPhotos(reservationId, companyId) {
const reservation = await prisma_1.prisma.reservation.findFirst({ where: { id: reservationId, companyId }, select: { id: true } });
if (!reservation)
throw new errors_1.NotFoundError('Reservation not found');
return prisma_1.prisma.reservationPhoto.findMany({
where: { reservationId },
orderBy: { createdAt: 'asc' },
});
}
async function uploadPhoto(reservationId, companyId, type, buffer) {
const reservation = await prisma_1.prisma.reservation.findFirst({ where: { id: reservationId, companyId }, select: { id: true } });
if (!reservation)
throw new errors_1.NotFoundError('Reservation not found');
const url = await (0, storage_1.uploadImage)(buffer, `companies/${companyId}/reservations/${reservationId}/photos`);
return prisma_1.prisma.reservationPhoto.create({ data: { reservationId, type, url } });
}
async function deletePhoto(photoId, reservationId, companyId) {
const photo = await prisma_1.prisma.reservationPhoto.findFirst({
where: { id: photoId, reservationId, reservation: { companyId } },
});
if (!photo)
throw new errors_1.NotFoundError('Photo not found');
await (0, storage_1.deleteImage)(photo.url);
await prisma_1.prisma.reservationPhoto.delete({ where: { id: photoId } });
}
//# sourceMappingURL=reservation.photo.service.js.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.photo.service.js","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.photo.service.ts"],"names":[],"mappings":";;AAIA,gCAOC;AAED,kCAUC;AAED,kCAOC;AAhCD,6CAAyC;AACzC,+CAA4D;AAC5D,8CAAiD;AAE1C,KAAK,UAAU,UAAU,CAAC,aAAqB,EAAE,SAAiB;IACvE,MAAM,WAAW,GAAG,MAAM,eAAM,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IACzH,IAAI,CAAC,WAAW;QAAE,MAAM,IAAI,sBAAa,CAAC,uBAAuB,CAAC,CAAA;IAClE,OAAO,eAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC;QACtC,KAAK,EAAE,EAAE,aAAa,EAAE;QACxB,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;KAC9B,CAAC,CAAA;AACJ,CAAC;AAEM,KAAK,UAAU,WAAW,CAC/B,aAAqB,EACrB,SAAiB,EACjB,IAA0B,EAC1B,MAAc;IAEd,MAAM,WAAW,GAAG,MAAM,eAAM,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;IACzH,IAAI,CAAC,WAAW;QAAE,MAAM,IAAI,sBAAa,CAAC,uBAAuB,CAAC,CAAA;IAClE,MAAM,GAAG,GAAG,MAAM,IAAA,qBAAW,EAAC,MAAM,EAAE,aAAa,SAAS,iBAAiB,aAAa,SAAS,CAAC,CAAA;IACpG,OAAO,eAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAA;AAC/E,CAAC;AAEM,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,aAAqB,EAAE,SAAiB;IACzF,MAAM,KAAK,GAAG,MAAM,eAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC;QACpD,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,EAAE;KAClE,CAAC,CAAA;IACF,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,sBAAa,CAAC,iBAAiB,CAAC,CAAA;IACtD,MAAM,IAAA,qBAAW,EAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5B,MAAM,eAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;AAClE,CAAC"}
@@ -0,0 +1,31 @@
export declare function parseReservationExtras(extras: unknown): Record<string, unknown>;
export declare function normalizeOptionalString(value: string | null | undefined): string | null;
export declare function buildReservationWorkflow(reservation: {
status: string;
contractNumber: string | null;
invoiceNumber: string | null;
extras: unknown;
}): {
contractGenerated: boolean;
closed: boolean;
closedAt: string | null;
closedBy: string | null;
coreEditable: boolean;
returnEditable: boolean;
checkInInspectionEditable: boolean;
checkOutInspectionEditable: boolean;
};
export declare function serializeReservationForDashboard<T extends {
extras: unknown;
status: string;
contractNumber: string | null;
invoiceNumber: string | null;
customer?: {
id: string;
licenseImageUrl?: string | null;
} | null;
}>(reservation: T): T & {
paymentMode: string | null;
workflow: ReturnType<typeof buildReservationWorkflow>;
};
//# sourceMappingURL=reservation.presenter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.presenter.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.presenter.ts"],"names":[],"mappings":"AAEA,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAG/E;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGvF;AAED,wBAAgB,wBAAwB,CAAC,WAAW,EAAE;IACpD,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,MAAM,EAAE,OAAO,CAAA;CAChB;;;;;;;;;EAgBA;AAED,wBAAgB,gCAAgC,CAAC,CAAC,SAAS;IACzD,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,EAAE;QACT,EAAE,EAAE,MAAM,CAAA;QACV,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAChC,GAAG,IAAI,CAAA;CACT,EAAE,WAAW,EAAE,CAAC,GAAG,CAAC,GAAG;IAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAA;CAAE,CAkB5G"}
@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseReservationExtras = parseReservationExtras;
exports.normalizeOptionalString = normalizeOptionalString;
exports.buildReservationWorkflow = buildReservationWorkflow;
exports.serializeReservationForDashboard = serializeReservationForDashboard;
const storage_1 = require("../../lib/storage");
function parseReservationExtras(extras) {
if (!extras || typeof extras !== 'object' || Array.isArray(extras))
return {};
return { ...extras };
}
function normalizeOptionalString(value) {
const trimmed = typeof value === 'string' ? value.trim() : value;
return trimmed || null;
}
function buildReservationWorkflow(reservation) {
const extras = parseReservationExtras(reservation.extras);
const closedAt = typeof extras.reservationClosedAt === 'string' ? extras.reservationClosedAt : null;
const contractGenerated = Boolean(reservation.contractNumber || reservation.invoiceNumber);
const closed = Boolean(closedAt);
return {
contractGenerated,
closed,
closedAt,
closedBy: typeof extras.reservationClosedBy === 'string' ? extras.reservationClosedBy : null,
coreEditable: !closed && !contractGenerated && ['DRAFT', 'CONFIRMED'].includes(reservation.status),
returnEditable: !closed && reservation.status === 'COMPLETED',
checkInInspectionEditable: !closed && ['CONFIRMED', 'ACTIVE'].includes(reservation.status),
checkOutInspectionEditable: !closed && reservation.status === 'COMPLETED',
};
}
function serializeReservationForDashboard(reservation) {
const extras = parseReservationExtras(reservation.extras);
const customer = reservation.customer
? {
...reservation.customer,
licenseImageUrl: reservation.customer.licenseImageUrl
? (0, storage_1.getProtectedCustomerLicenseImageUrl)(reservation.customer.id)
: null,
}
: reservation.customer;
return {
...reservation,
...(customer !== undefined ? { customer } : {}),
paymentMode: typeof extras.paymentMode === 'string' ? extras.paymentMode : null,
workflow: buildReservationWorkflow(reservation),
};
}
//# sourceMappingURL=reservation.presenter.js.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.presenter.js","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.presenter.ts"],"names":[],"mappings":";;AAEA,wDAGC;AAED,0DAGC;AAED,4DAqBC;AAED,4EA2BC;AA9DD,+CAAuE;AAEvE,SAAgB,sBAAsB,CAAC,MAAe;IACpD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAA;IAC7E,OAAO,EAAE,GAAI,MAAkC,EAAE,CAAA;AACnD,CAAC;AAED,SAAgB,uBAAuB,CAAC,KAAgC;IACtE,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAA;IAChE,OAAO,OAAO,IAAI,IAAI,CAAA;AACxB,CAAC;AAED,SAAgB,wBAAwB,CAAC,WAKxC;IACC,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IACzD,MAAM,QAAQ,GAAG,OAAO,MAAM,CAAC,mBAAmB,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAA;IACnG,MAAM,iBAAiB,GAAG,OAAO,CAAC,WAAW,CAAC,cAAc,IAAI,WAAW,CAAC,aAAa,CAAC,CAAA;IAC1F,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAEhC,OAAO;QACL,iBAAiB;QACjB,MAAM;QACN,QAAQ;QACR,QAAQ,EAAE,OAAO,MAAM,CAAC,mBAAmB,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI;QAC5F,YAAY,EAAE,CAAC,MAAM,IAAI,CAAC,iBAAiB,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC;QAClG,cAAc,EAAE,CAAC,MAAM,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW;QAC7D,yBAAyB,EAAE,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,CAAC;QAC1F,0BAA0B,EAAE,CAAC,MAAM,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW;KAC1E,CAAA;AACH,CAAC;AAED,SAAgB,gCAAgC,CAS7C,WAAc;IACf,MAAM,MAAM,GAAG,sBAAsB,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IACzD,MAAM,QAAQ,GACZ,WAAW,CAAC,QAAQ;QAClB,CAAC,CAAC;YACE,GAAG,WAAW,CAAC,QAAQ;YACvB,eAAe,EAAE,WAAW,CAAC,QAAQ,CAAC,eAAe;gBACnD,CAAC,CAAC,IAAA,6CAAmC,EAAC,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9D,CAAC,CAAC,IAAI;SACT;QACH,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAA;IAE1B,OAAO;QACL,GAAG,WAAW;QACd,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/C,WAAW,EAAE,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;QAC/E,QAAQ,EAAE,wBAAwB,CAAC,WAAW,CAAC;KAChD,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
export { applyPricingRules } from '../../services/pricingRuleService';
export declare function calculateUpdatedInsuranceCharge(chargeType: 'PER_DAY' | 'PER_RENTAL' | 'PERCENTAGE_OF_RENTAL', chargeValue: number, totalDays: number, baseRentalAmount: number): number;
export declare function calculateUpdatedAdditionalDriverCharge(chargeType: 'PER_DAY' | 'FLAT' | 'FREE', chargeValue: number, totalDays: number): number;
//# sourceMappingURL=reservation.pricing.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.pricing.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.pricing.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAA;AAErE,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,SAAS,GAAG,YAAY,GAAG,sBAAsB,EAC7D,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,MAAM,CAOR;AAED,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,EACvC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,MAAM,CAMR"}
@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyPricingRules = void 0;
exports.calculateUpdatedInsuranceCharge = calculateUpdatedInsuranceCharge;
exports.calculateUpdatedAdditionalDriverCharge = calculateUpdatedAdditionalDriverCharge;
var pricingRuleService_1 = require("../../services/pricingRuleService");
Object.defineProperty(exports, "applyPricingRules", { enumerable: true, get: function () { return pricingRuleService_1.applyPricingRules; } });
function calculateUpdatedInsuranceCharge(chargeType, chargeValue, totalDays, baseRentalAmount) {
switch (chargeType) {
case 'PER_DAY': return chargeValue * totalDays;
case 'PER_RENTAL': return chargeValue;
case 'PERCENTAGE_OF_RENTAL': return Math.round(baseRentalAmount * chargeValue / 100);
default: return 0;
}
}
function calculateUpdatedAdditionalDriverCharge(chargeType, chargeValue, totalDays) {
switch (chargeType) {
case 'PER_DAY': return chargeValue * totalDays;
case 'FLAT': return chargeValue;
default: return 0;
}
}
//# sourceMappingURL=reservation.pricing.service.js.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.pricing.service.js","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.pricing.service.ts"],"names":[],"mappings":";;;AAEA,0EAYC;AAED,wFAUC;AA1BD,wEAAqE;AAA5D,uHAAA,iBAAiB,OAAA;AAE1B,SAAgB,+BAA+B,CAC7C,UAA6D,EAC7D,WAAmB,EACnB,SAAiB,EACjB,gBAAwB;IAExB,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,SAAS,CAAC,CAAc,OAAO,WAAW,GAAG,SAAS,CAAA;QAC3D,KAAK,YAAY,CAAC,CAAW,OAAO,WAAW,CAAA;QAC/C,KAAK,sBAAsB,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,gBAAgB,GAAG,WAAW,GAAG,GAAG,CAAC,CAAA;QACpF,OAAO,CAAC,CAAqB,OAAO,CAAC,CAAA;IACvC,CAAC;AACH,CAAC;AAED,SAAgB,sCAAsC,CACpD,UAAuC,EACvC,WAAmB,EACnB,SAAiB;IAEjB,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,SAAS,CAAC,CAAC,OAAO,WAAW,GAAG,SAAS,CAAA;QAC9C,KAAK,MAAM,CAAC,CAAI,OAAO,WAAW,CAAA;QAClC,OAAO,CAAC,CAAQ,OAAO,CAAC,CAAA;IAC1B,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
export declare function findMany(where: any, skip: number, take: number): Promise<any>;
export declare function findById(id: string, companyId: string): Promise<any>;
export declare function findByIdSimple(id: string, companyId: string): Promise<any>;
export declare function findByIdWithRelations(id: string, companyId: string): Promise<any>;
export declare function findByIdForCheckout(id: string, companyId: string): Promise<any>;
export declare function findForLicenseCheck(id: string, companyId: string): Promise<any>;
export declare function findForContract(id: string, companyId: string): Promise<any>;
export declare function findForBilling(id: string, companyId: string): Promise<any>;
export declare function findForClose(id: string, companyId: string): Promise<any>;
export declare function findForInspection(id: string, companyId: string): Promise<any>;
export declare function findConflict(vehicleId: string, start: Date, end: Date, excludeId?: string): Promise<any>;
export declare function create(data: any): Promise<any>;
export declare function updateById(id: string, data: any): Promise<any>;
export declare function findActiveOffer(companyId: string, promoCode: string): Promise<any>;
export declare function incrementOfferRedemption(offerId: string): Promise<any>;
export declare function findVehicle(vehicleId: string, companyId: string): Promise<any>;
export declare function findCustomer(customerId: string, companyId: string): Promise<any>;
export declare function findCustomerById(customerId: string): Promise<any>;
export declare function updateVehicleStatus(vehicleId: string, status: string, mileage?: number): Promise<any>;
export declare function findCompanyWithBrand(companyId: string): Promise<any>;
export declare function findBrandLocale(companyId: string): Promise<any>;
export declare function findInspections(reservationId: string, companyId: string): Promise<any>;
export declare function findAdditionalDriver(driverId: string, reservationId: string, companyId: string): Promise<any>;
export declare function updateAdditionalDriver(id: string, data: any): Promise<any>;
//# sourceMappingURL=reservation.repo.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.repo.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.repo.ts"],"names":[],"mappings":"AAWA,wBAAsB,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,gBAWpE;AAED,wBAAsB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAE3D;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAEjE;AAED,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAKxE;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAKtE;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAKtE;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAalE;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAKjE;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAE/D;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAKpE;AAED,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,gBAU/F;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,GAAG,gBAErC;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,gBAErD;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAIzE;AAED,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,gBAE7D;AAED,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAErE;AAED,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAEvE;AAED,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,gBAExD;AAED,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,gBAK5F;AAED,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,gBAK3D;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,gBAEtD;AAED,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAM7E;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAEpG;AAED,wBAAsB,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,gBAEjE"}
+163
View File
@@ -0,0 +1,163 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findMany = findMany;
exports.findById = findById;
exports.findByIdSimple = findByIdSimple;
exports.findByIdWithRelations = findByIdWithRelations;
exports.findByIdForCheckout = findByIdForCheckout;
exports.findForLicenseCheck = findForLicenseCheck;
exports.findForContract = findForContract;
exports.findForBilling = findForBilling;
exports.findForClose = findForClose;
exports.findForInspection = findForInspection;
exports.findConflict = findConflict;
exports.create = create;
exports.updateById = updateById;
exports.findActiveOffer = findActiveOffer;
exports.incrementOfferRedemption = incrementOfferRedemption;
exports.findVehicle = findVehicle;
exports.findCustomer = findCustomer;
exports.findCustomerById = findCustomerById;
exports.updateVehicleStatus = updateVehicleStatus;
exports.findCompanyWithBrand = findCompanyWithBrand;
exports.findBrandLocale = findBrandLocale;
exports.findInspections = findInspections;
exports.findAdditionalDriver = findAdditionalDriver;
exports.updateAdditionalDriver = updateAdditionalDriver;
const prisma_1 = require("../../lib/prisma");
const FULL_INCLUDE = {
vehicle: true,
customer: true,
insurances: true,
additionalDrivers: true,
rentalPayments: true,
damageReports: true,
};
async function findMany(where, skip, take) {
return Promise.all([
prisma_1.prisma.reservation.findMany({
where,
include: { vehicle: true, customer: true },
skip,
take,
orderBy: { createdAt: 'desc' },
}),
prisma_1.prisma.reservation.count({ where }),
]);
}
async function findById(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({ where: { id, companyId }, include: FULL_INCLUDE });
}
async function findByIdSimple(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({ where: { id, companyId } });
}
async function findByIdWithRelations(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({
where: { id, companyId },
include: { insurances: true, additionalDrivers: true },
});
}
async function findByIdForCheckout(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({
where: { id, companyId },
include: { vehicle: true },
});
}
async function findForLicenseCheck(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({
where: { id, companyId },
include: { customer: true, additionalDrivers: true },
});
}
async function findForContract(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({
where: { id, companyId },
include: {
vehicle: true,
customer: true,
insurances: true,
additionalDrivers: true,
rentalPayments: { orderBy: { createdAt: 'asc' } },
inspections: { include: { damagePoints: true }, orderBy: { inspectedAt: 'asc' } },
company: { include: { brand: true, contractSettings: true, accountingSettings: true } },
},
});
}
async function findForBilling(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({
where: { id, companyId },
include: { vehicle: true, insurances: true, additionalDrivers: true },
});
}
async function findForClose(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({ where: { id, companyId }, include: { customer: true } });
}
async function findForInspection(id, companyId) {
return prisma_1.prisma.reservation.findFirstOrThrow({
where: { id, companyId },
include: { vehicle: true, customer: true },
});
}
async function findConflict(vehicleId, start, end, excludeId) {
return prisma_1.prisma.reservation.findFirst({
where: {
vehicleId,
status: { in: ['CONFIRMED', 'ACTIVE'] },
startDate: { lt: end },
endDate: { gt: start },
...(excludeId ? { id: { not: excludeId } } : {}),
},
});
}
async function create(data) {
return prisma_1.prisma.reservation.create({ data, include: { vehicle: true, customer: true } });
}
async function updateById(id, data) {
return prisma_1.prisma.reservation.update({ where: { id }, data });
}
async function findActiveOffer(companyId, promoCode) {
return prisma_1.prisma.offer.findFirst({
where: { companyId, promoCode, isActive: true, validFrom: { lte: new Date() }, validUntil: { gte: new Date() } },
});
}
async function incrementOfferRedemption(offerId) {
return prisma_1.prisma.offer.update({ where: { id: offerId }, data: { redemptionCount: { increment: 1 } } });
}
async function findVehicle(vehicleId, companyId) {
return prisma_1.prisma.vehicle.findFirstOrThrow({ where: { id: vehicleId, companyId } });
}
async function findCustomer(customerId, companyId) {
return prisma_1.prisma.customer.findFirstOrThrow({ where: { id: customerId, companyId } });
}
async function findCustomerById(customerId) {
return prisma_1.prisma.customer.findUniqueOrThrow({ where: { id: customerId } });
}
async function updateVehicleStatus(vehicleId, status, mileage) {
return prisma_1.prisma.vehicle.update({
where: { id: vehicleId },
data: { status: status, ...(mileage !== undefined ? { mileage } : {}) },
});
}
async function findCompanyWithBrand(companyId) {
return prisma_1.prisma.company.findUnique({
where: { id: companyId },
include: { brand: { select: { displayName: true, defaultLocale: true } } },
});
}
async function findBrandLocale(companyId) {
return prisma_1.prisma.brandSettings.findUnique({ where: { companyId }, select: { defaultLocale: true } });
}
async function findInspections(reservationId, companyId) {
return prisma_1.prisma.damageInspection.findMany({
where: { reservationId, companyId },
include: { damagePoints: true },
orderBy: { inspectedAt: 'asc' },
});
}
async function findAdditionalDriver(driverId, reservationId, companyId) {
return prisma_1.prisma.additionalDriver.findFirstOrThrow({ where: { id: driverId, reservationId, companyId } });
}
async function updateAdditionalDriver(id, data) {
return prisma_1.prisma.additionalDriver.update({ where: { id }, data });
}
//# sourceMappingURL=reservation.repo.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
declare const router: import("express-serve-static-core").Router;
export default router;
//# sourceMappingURL=reservation.routes.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.routes.ts"],"names":[],"mappings":"AAwBA,QAAA,MAAM,MAAM,4CAAW,CAAA;AA8JvB,eAAe,MAAM,CAAA"}
+247
View File
@@ -0,0 +1,247 @@
"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 });
const express_1 = require("express");
const zod_1 = require("zod");
const requireCompanyAuth_1 = require("../../middleware/requireCompanyAuth");
const requireTenant_1 = require("../../middleware/requireTenant");
const requireSubscription_1 = require("../../middleware/requireSubscription");
const validate_1 = require("../../http/validate");
const respond_1 = require("../../http/respond");
const upload_1 = require("../../http/upload");
const service = __importStar(require("./reservation.service"));
const lifecycle = __importStar(require("./reservation.lifecycle.service"));
const inspectionService = __importStar(require("./reservation.inspection.service"));
const additionalDriverService = __importStar(require("./reservation.additional-driver.service"));
const photoService = __importStar(require("./reservation.photo.service"));
const reservation_document_service_1 = require("./reservation.document.service");
const reservation_schemas_1 = require("./reservation.schemas");
const photoTypeSchema = zod_1.z.enum(['PICKUP', 'DROPOFF']);
const photoParamSchema = zod_1.z.object({ id: zod_1.z.string(), photoId: zod_1.z.string() });
const router = (0, express_1.Router)();
router.use(requireCompanyAuth_1.requireCompanyAuth, requireTenant_1.requireTenant, requireSubscription_1.requireSubscription);
router.get('/', async (req, res, next) => {
try {
const query = (0, validate_1.parseQuery)(reservation_schemas_1.listQuerySchema, req);
const result = await service.listReservations(req.companyId, query);
res.json(result);
}
catch (err) {
next(err);
}
});
router.post('/', async (req, res, next) => {
try {
const body = (0, validate_1.parseBody)(reservation_schemas_1.createSchema, req);
const reservation = await service.createReservation(req.companyId, body);
(0, respond_1.created)(res, reservation);
}
catch (err) {
next(err);
}
});
router.get('/:id', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const reservation = await service.getReservation(id, req.companyId);
(0, respond_1.ok)(res, reservation);
}
catch (err) {
next(err);
}
});
router.patch('/:id', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const body = (0, validate_1.parseBody)(reservation_schemas_1.updateSchema, req);
const reservation = await service.updateReservation(id, req.companyId, body);
(0, respond_1.ok)(res, reservation);
}
catch (err) {
next(err);
}
});
router.get('/:id/contract', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const contract = await (0, reservation_document_service_1.getContract)(id, req.companyId);
(0, respond_1.ok)(res, contract);
}
catch (err) {
next(err);
}
});
router.get('/:id/billing', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const billing = await (0, reservation_document_service_1.getBilling)(id, req.companyId);
(0, respond_1.ok)(res, billing);
}
catch (err) {
next(err);
}
});
router.post('/:id/confirm', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const updated = await lifecycle.confirmReservation(id, req.companyId);
(0, respond_1.ok)(res, updated);
}
catch (err) {
next(err);
}
});
router.post('/:id/checkin', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const { mileage } = (0, validate_1.parseBody)(reservation_schemas_1.checkinSchema, req);
const updated = await lifecycle.checkinReservation(id, req.companyId, mileage);
(0, respond_1.ok)(res, updated);
}
catch (err) {
next(err);
}
});
router.post('/:id/checkout', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const { mileage } = (0, validate_1.parseBody)(reservation_schemas_1.checkoutSchema, req);
const updated = await lifecycle.checkoutReservation(id, req.companyId, mileage);
(0, respond_1.ok)(res, updated);
}
catch (err) {
next(err);
}
});
router.post('/:id/close', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const closedBy = `${req.employee.firstName} ${req.employee.lastName}`;
const reservation = await lifecycle.closeReservation(id, req.companyId, closedBy);
(0, respond_1.ok)(res, reservation);
}
catch (err) {
next(err);
}
});
router.post('/:id/extend', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const { newEndDate, reason } = (0, validate_1.parseBody)(reservation_schemas_1.extendSchema, req);
(0, respond_1.ok)(res, await lifecycle.extendReservation(id, req.companyId, new Date(newEndDate), reason));
}
catch (err) {
next(err);
}
});
router.post('/:id/cancel', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const { reason } = (0, validate_1.parseBody)(reservation_schemas_1.cancelSchema, req);
const updated = await lifecycle.cancelReservation(id, req.companyId, reason);
(0, respond_1.ok)(res, updated);
}
catch (err) {
next(err);
}
});
router.get('/:id/inspections', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
const inspections = await inspectionService.getInspections(id, req.companyId);
(0, respond_1.ok)(res, inspections);
}
catch (err) {
next(err);
}
});
router.put('/:id/inspections/:type', async (req, res, next) => {
try {
const { id, type: rawType } = (0, validate_1.parseParams)(reservation_schemas_1.inspectionParamSchema, req);
const type = reservation_schemas_1.inspectionTypeParam.parse(rawType.toUpperCase());
const body = (0, validate_1.parseBody)(reservation_schemas_1.inspectionSchema, req);
const inspectedBy = `${req.employee.firstName} ${req.employee.lastName}`;
const result = await inspectionService.upsertInspection(id, req.companyId, type, body, inspectedBy);
(0, respond_1.ok)(res, result);
}
catch (err) {
next(err);
}
});
router.patch('/:id/additional-drivers/:driverId/approval', async (req, res, next) => {
try {
const { id, driverId } = (0, validate_1.parseParams)(reservation_schemas_1.driverParamSchema, req);
const { approved, note } = (0, validate_1.parseBody)(reservation_schemas_1.approvalSchema, req);
const approvedBy = `${req.employee.firstName} ${req.employee.lastName}`;
const updated = await additionalDriverService.approveAdditionalDriver(id, driverId, req.companyId, approved, note, approvedBy);
(0, respond_1.ok)(res, updated);
}
catch (err) {
next(err);
}
});
router.get('/:id/photos', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
(0, respond_1.ok)(res, await photoService.listPhotos(id, req.companyId));
}
catch (err) {
next(err);
}
});
router.post('/:id/photos', upload_1.imageUpload.single('photo'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(reservation_schemas_1.idParamSchema, req);
(0, upload_1.assertImageFile)(req.file, 'photo');
const type = photoTypeSchema.parse(req.body.type);
const photo = await photoService.uploadPhoto(id, req.companyId, type, req.file.buffer);
(0, respond_1.created)(res, photo);
}
catch (err) {
next(err);
}
});
router.delete('/:id/photos/:photoId', async (req, res, next) => {
try {
const { id, photoId } = (0, validate_1.parseParams)(photoParamSchema, req);
await photoService.deletePhoto(photoId, id, req.companyId);
(0, respond_1.ok)(res, { deleted: true });
}
catch (err) {
next(err);
}
});
exports.default = router;
//# sourceMappingURL=reservation.routes.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,316 @@
import { z } from 'zod';
export declare const additionalDriverSchema: z.ZodObject<{
firstName: z.ZodString;
lastName: z.ZodString;
email: z.ZodOptional<z.ZodString>;
phone: z.ZodOptional<z.ZodString>;
driverLicense: z.ZodString;
licenseExpiry: z.ZodOptional<z.ZodString>;
licenseIssuedAt: z.ZodOptional<z.ZodString>;
dateOfBirth: z.ZodOptional<z.ZodString>;
nationality: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
firstName: string;
lastName: string;
driverLicense: string;
email?: string | undefined;
phone?: string | undefined;
licenseExpiry?: string | undefined;
licenseIssuedAt?: string | undefined;
dateOfBirth?: string | undefined;
nationality?: string | undefined;
}, {
firstName: string;
lastName: string;
driverLicense: string;
email?: string | undefined;
phone?: string | undefined;
licenseExpiry?: string | undefined;
licenseIssuedAt?: string | undefined;
dateOfBirth?: string | undefined;
nationality?: string | undefined;
}>;
export declare const inspectionSchema: z.ZodObject<{
mileage: z.ZodOptional<z.ZodNumber>;
fuelLevel: z.ZodEnum<["FULL", "SEVEN_EIGHTHS", "THREE_QUARTERS", "FIVE_EIGHTHS", "HALF", "THREE_EIGHTHS", "QUARTER", "ONE_EIGHTH", "EMPTY"]>;
fuelCharge: z.ZodOptional<z.ZodNumber>;
generalCondition: z.ZodOptional<z.ZodString>;
employeeNotes: z.ZodOptional<z.ZodString>;
customerAgreed: z.ZodDefault<z.ZodBoolean>;
damagePoints: z.ZodDefault<z.ZodArray<z.ZodObject<{
viewType: z.ZodEnum<["TOP", "FRONT", "REAR", "LEFT", "RIGHT"]>;
x: z.ZodNumber;
y: z.ZodNumber;
damageType: z.ZodEnum<["SCRATCH", "DENT", "CRACK", "CHIP", "MISSING", "STAIN", "OTHER"]>;
severity: z.ZodDefault<z.ZodEnum<["MINOR", "MODERATE", "MAJOR"]>>;
description: z.ZodOptional<z.ZodString>;
isPreExisting: z.ZodDefault<z.ZodBoolean>;
}, "strip", z.ZodTypeAny, {
viewType: "TOP" | "FRONT" | "REAR" | "LEFT" | "RIGHT";
x: number;
y: number;
damageType: "SCRATCH" | "DENT" | "CRACK" | "CHIP" | "MISSING" | "STAIN" | "OTHER";
severity: "MINOR" | "MODERATE" | "MAJOR";
isPreExisting: boolean;
description?: string | undefined;
}, {
viewType: "TOP" | "FRONT" | "REAR" | "LEFT" | "RIGHT";
x: number;
y: number;
damageType: "SCRATCH" | "DENT" | "CRACK" | "CHIP" | "MISSING" | "STAIN" | "OTHER";
description?: string | undefined;
severity?: "MINOR" | "MODERATE" | "MAJOR" | undefined;
isPreExisting?: boolean | undefined;
}>, "many">>;
}, "strip", z.ZodTypeAny, {
fuelLevel: "FULL" | "SEVEN_EIGHTHS" | "THREE_QUARTERS" | "FIVE_EIGHTHS" | "HALF" | "THREE_EIGHTHS" | "QUARTER" | "ONE_EIGHTH" | "EMPTY";
customerAgreed: boolean;
damagePoints: {
viewType: "TOP" | "FRONT" | "REAR" | "LEFT" | "RIGHT";
x: number;
y: number;
damageType: "SCRATCH" | "DENT" | "CRACK" | "CHIP" | "MISSING" | "STAIN" | "OTHER";
severity: "MINOR" | "MODERATE" | "MAJOR";
isPreExisting: boolean;
description?: string | undefined;
}[];
mileage?: number | undefined;
fuelCharge?: number | undefined;
generalCondition?: string | undefined;
employeeNotes?: string | undefined;
}, {
fuelLevel: "FULL" | "SEVEN_EIGHTHS" | "THREE_QUARTERS" | "FIVE_EIGHTHS" | "HALF" | "THREE_EIGHTHS" | "QUARTER" | "ONE_EIGHTH" | "EMPTY";
mileage?: number | undefined;
fuelCharge?: number | undefined;
generalCondition?: string | undefined;
employeeNotes?: string | undefined;
customerAgreed?: boolean | undefined;
damagePoints?: {
viewType: "TOP" | "FRONT" | "REAR" | "LEFT" | "RIGHT";
x: number;
y: number;
damageType: "SCRATCH" | "DENT" | "CRACK" | "CHIP" | "MISSING" | "STAIN" | "OTHER";
description?: string | undefined;
severity?: "MINOR" | "MODERATE" | "MAJOR" | undefined;
isPreExisting?: boolean | undefined;
}[] | undefined;
}>;
export declare const createSchema: z.ZodObject<{
vehicleId: z.ZodString;
customerId: z.ZodString;
startDate: z.ZodString;
endDate: z.ZodString;
pickupLocation: z.ZodOptional<z.ZodString>;
returnLocation: z.ZodOptional<z.ZodString>;
offerId: z.ZodOptional<z.ZodString>;
promoCodeUsed: z.ZodOptional<z.ZodString>;
depositAmount: z.ZodDefault<z.ZodNumber>;
paymentMode: z.ZodOptional<z.ZodString>;
notes: z.ZodOptional<z.ZodString>;
selectedInsurancePolicyIds: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
additionalDrivers: z.ZodDefault<z.ZodArray<z.ZodObject<{
firstName: z.ZodString;
lastName: z.ZodString;
email: z.ZodOptional<z.ZodString>;
phone: z.ZodOptional<z.ZodString>;
driverLicense: z.ZodString;
licenseExpiry: z.ZodOptional<z.ZodString>;
licenseIssuedAt: z.ZodOptional<z.ZodString>;
dateOfBirth: z.ZodOptional<z.ZodString>;
nationality: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
firstName: string;
lastName: string;
driverLicense: string;
email?: string | undefined;
phone?: string | undefined;
licenseExpiry?: string | undefined;
licenseIssuedAt?: string | undefined;
dateOfBirth?: string | undefined;
nationality?: string | undefined;
}, {
firstName: string;
lastName: string;
driverLicense: string;
email?: string | undefined;
phone?: string | undefined;
licenseExpiry?: string | undefined;
licenseIssuedAt?: string | undefined;
dateOfBirth?: string | undefined;
nationality?: string | undefined;
}>, "many">>;
}, "strip", z.ZodTypeAny, {
startDate: string;
endDate: string;
vehicleId: string;
customerId: string;
depositAmount: number;
selectedInsurancePolicyIds: string[];
additionalDrivers: {
firstName: string;
lastName: string;
driverLicense: string;
email?: string | undefined;
phone?: string | undefined;
licenseExpiry?: string | undefined;
licenseIssuedAt?: string | undefined;
dateOfBirth?: string | undefined;
nationality?: string | undefined;
}[];
notes?: string | undefined;
pickupLocation?: string | undefined;
returnLocation?: string | undefined;
offerId?: string | undefined;
promoCodeUsed?: string | undefined;
paymentMode?: string | undefined;
}, {
startDate: string;
endDate: string;
vehicleId: string;
customerId: string;
notes?: string | undefined;
pickupLocation?: string | undefined;
returnLocation?: string | undefined;
offerId?: string | undefined;
promoCodeUsed?: string | undefined;
depositAmount?: number | undefined;
paymentMode?: string | undefined;
selectedInsurancePolicyIds?: string[] | undefined;
additionalDrivers?: {
firstName: string;
lastName: string;
driverLicense: string;
email?: string | undefined;
phone?: string | undefined;
licenseExpiry?: string | undefined;
licenseIssuedAt?: string | undefined;
dateOfBirth?: string | undefined;
nationality?: string | undefined;
}[] | undefined;
}>;
export declare const updateSchema: z.ZodObject<{
startDate: z.ZodOptional<z.ZodString>;
endDate: z.ZodOptional<z.ZodString>;
pickupLocation: z.ZodNullable<z.ZodOptional<z.ZodString>>;
returnLocation: z.ZodNullable<z.ZodOptional<z.ZodString>>;
depositAmount: z.ZodOptional<z.ZodNumber>;
notes: z.ZodNullable<z.ZodOptional<z.ZodString>>;
paymentMode: z.ZodNullable<z.ZodOptional<z.ZodString>>;
damageChargeAmount: z.ZodOptional<z.ZodNumber>;
damageChargeNote: z.ZodNullable<z.ZodOptional<z.ZodString>>;
}, "strip", z.ZodTypeAny, {
notes?: string | null | undefined;
startDate?: string | undefined;
endDate?: string | undefined;
pickupLocation?: string | null | undefined;
returnLocation?: string | null | undefined;
depositAmount?: number | undefined;
paymentMode?: string | null | undefined;
damageChargeAmount?: number | undefined;
damageChargeNote?: string | null | undefined;
}, {
notes?: string | null | undefined;
startDate?: string | undefined;
endDate?: string | undefined;
pickupLocation?: string | null | undefined;
returnLocation?: string | null | undefined;
depositAmount?: number | undefined;
paymentMode?: string | null | undefined;
damageChargeAmount?: number | undefined;
damageChargeNote?: string | null | undefined;
}>;
export declare const listQuerySchema: z.ZodObject<{
status: z.ZodOptional<z.ZodString>;
vehicleId: z.ZodOptional<z.ZodString>;
source: z.ZodOptional<z.ZodString>;
startDate: z.ZodOptional<z.ZodString>;
endDate: z.ZodOptional<z.ZodString>;
page: z.ZodDefault<z.ZodNumber>;
pageSize: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
page: number;
pageSize: number;
status?: string | undefined;
startDate?: string | undefined;
endDate?: string | undefined;
vehicleId?: string | undefined;
source?: string | undefined;
}, {
status?: string | undefined;
page?: number | undefined;
pageSize?: number | undefined;
startDate?: string | undefined;
endDate?: string | undefined;
vehicleId?: string | undefined;
source?: string | undefined;
}>;
export declare const checkinSchema: z.ZodObject<{
mileage: z.ZodOptional<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
mileage?: number | undefined;
}, {
mileage?: number | undefined;
}>;
export declare const checkoutSchema: z.ZodObject<{
mileage: z.ZodOptional<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
mileage?: number | undefined;
}, {
mileage?: number | undefined;
}>;
export declare const extendSchema: z.ZodObject<{
newEndDate: z.ZodString;
reason: z.ZodString;
}, "strip", z.ZodTypeAny, {
reason: string;
newEndDate: string;
}, {
reason: string;
newEndDate: string;
}>;
export declare const cancelSchema: z.ZodObject<{
reason: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
reason?: string | undefined;
}, {
reason?: string | undefined;
}>;
export declare const approvalSchema: z.ZodObject<{
approved: z.ZodBoolean;
note: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
approved: boolean;
note?: string | undefined;
}, {
approved: boolean;
note?: string | undefined;
}>;
export declare const inspectionTypeParam: z.ZodEnum<["CHECKIN", "CHECKOUT"]>;
export declare const idParamSchema: z.ZodObject<{
id: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
}, {
id: string;
}>;
export declare const driverParamSchema: z.ZodObject<{
id: z.ZodString;
driverId: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
driverId: string;
}, {
id: string;
driverId: string;
}>;
export declare const inspectionParamSchema: z.ZodObject<{
id: z.ZodString;
type: z.ZodString;
}, "strip", z.ZodTypeAny, {
type: string;
id: string;
}, {
type: string;
id: string;
}>;
//# sourceMappingURL=reservation.schemas.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.schemas.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUjC,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgB3B,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcvB,CAAA;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAUvB,CAAA;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;EAQ1B,CAAA;AAEF,eAAO,MAAM,aAAa;;;;;;EAAyD,CAAA;AACnF,eAAO,MAAM,cAAc;;;;;;EAAwD,CAAA;AACnF,eAAO,MAAM,YAAY;;;;;;;;;EAAkF,CAAA;AAC3G,eAAO,MAAM,YAAY;;;;;;EAAmD,CAAA;AAC5E,eAAO,MAAM,cAAc;;;;;;;;;EAAsE,CAAA;AACjG,eAAO,MAAM,mBAAmB,oCAAkC,CAAA;AAElE,eAAO,MAAM,aAAa;;;;;;EAAuC,CAAA;AACjE,eAAO,MAAM,iBAAiB;;;;;;;;;EAAyD,CAAA;AACvF,eAAO,MAAM,qBAAqB;;;;;;;;;EAAiD,CAAA"}
@@ -0,0 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.inspectionParamSchema = exports.driverParamSchema = exports.idParamSchema = exports.inspectionTypeParam = exports.approvalSchema = exports.cancelSchema = exports.extendSchema = exports.checkoutSchema = exports.checkinSchema = exports.listQuerySchema = exports.updateSchema = exports.createSchema = exports.inspectionSchema = exports.additionalDriverSchema = void 0;
const zod_1 = require("zod");
exports.additionalDriverSchema = zod_1.z.object({
firstName: zod_1.z.string().min(1),
lastName: zod_1.z.string().min(1),
email: zod_1.z.string().email().optional(),
phone: zod_1.z.string().optional(),
driverLicense: zod_1.z.string().min(1),
licenseExpiry: zod_1.z.string().datetime().optional(),
licenseIssuedAt: zod_1.z.string().datetime().optional(),
dateOfBirth: zod_1.z.string().datetime().optional(),
nationality: zod_1.z.string().optional(),
});
exports.inspectionSchema = zod_1.z.object({
mileage: zod_1.z.number().int().optional(),
fuelLevel: zod_1.z.enum(['FULL', 'SEVEN_EIGHTHS', 'THREE_QUARTERS', 'FIVE_EIGHTHS', 'HALF', 'THREE_EIGHTHS', 'QUARTER', 'ONE_EIGHTH', 'EMPTY']),
fuelCharge: zod_1.z.number().int().min(0).optional(),
generalCondition: zod_1.z.string().optional(),
employeeNotes: zod_1.z.string().optional(),
customerAgreed: zod_1.z.boolean().default(false),
damagePoints: zod_1.z.array(zod_1.z.object({
viewType: zod_1.z.enum(['TOP', 'FRONT', 'REAR', 'LEFT', 'RIGHT']),
x: zod_1.z.number(),
y: zod_1.z.number(),
damageType: zod_1.z.enum(['SCRATCH', 'DENT', 'CRACK', 'CHIP', 'MISSING', 'STAIN', 'OTHER']),
severity: zod_1.z.enum(['MINOR', 'MODERATE', 'MAJOR']).default('MINOR'),
description: zod_1.z.string().optional(),
isPreExisting: zod_1.z.boolean().default(false),
})).default([]),
});
exports.createSchema = zod_1.z.object({
vehicleId: zod_1.z.string().cuid(),
customerId: zod_1.z.string().cuid(),
startDate: zod_1.z.string().datetime(),
endDate: zod_1.z.string().datetime(),
pickupLocation: zod_1.z.string().optional(),
returnLocation: zod_1.z.string().optional(),
offerId: zod_1.z.string().cuid().optional(),
promoCodeUsed: zod_1.z.string().optional(),
depositAmount: zod_1.z.number().int().min(0).default(0),
paymentMode: zod_1.z.string().max(50).optional(),
notes: zod_1.z.string().optional(),
selectedInsurancePolicyIds: zod_1.z.array(zod_1.z.string()).default([]),
additionalDrivers: zod_1.z.array(exports.additionalDriverSchema).default([]),
});
exports.updateSchema = zod_1.z.object({
startDate: zod_1.z.string().datetime().optional(),
endDate: zod_1.z.string().datetime().optional(),
pickupLocation: zod_1.z.string().optional().nullable(),
returnLocation: zod_1.z.string().optional().nullable(),
depositAmount: zod_1.z.number().int().min(0).optional(),
notes: zod_1.z.string().optional().nullable(),
paymentMode: zod_1.z.string().max(50).optional().nullable(),
damageChargeAmount: zod_1.z.number().int().min(0).optional(),
damageChargeNote: zod_1.z.string().optional().nullable(),
});
exports.listQuerySchema = zod_1.z.object({
status: zod_1.z.string().optional(),
vehicleId: zod_1.z.string().optional(),
source: zod_1.z.string().optional(),
startDate: zod_1.z.string().optional(),
endDate: zod_1.z.string().optional(),
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(100).default(20),
});
exports.checkinSchema = zod_1.z.object({ mileage: zod_1.z.number().int().optional() });
exports.checkoutSchema = zod_1.z.object({ mileage: zod_1.z.number().int().optional() });
exports.extendSchema = zod_1.z.object({ newEndDate: zod_1.z.string().datetime(), reason: zod_1.z.string().min(1) });
exports.cancelSchema = zod_1.z.object({ reason: zod_1.z.string().optional() });
exports.approvalSchema = zod_1.z.object({ approved: zod_1.z.boolean(), note: zod_1.z.string().optional() });
exports.inspectionTypeParam = zod_1.z.enum(['CHECKIN', 'CHECKOUT']);
exports.idParamSchema = zod_1.z.object({ id: zod_1.z.string() });
exports.driverParamSchema = zod_1.z.object({ id: zod_1.z.string(), driverId: zod_1.z.string() });
exports.inspectionParamSchema = zod_1.z.object({ id: zod_1.z.string(), type: zod_1.z.string() });
//# sourceMappingURL=reservation.schemas.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
export declare function listReservations(companyId: string, query: {
status?: string;
vehicleId?: string;
source?: string;
startDate?: string;
endDate?: string;
page?: number;
pageSize?: number;
}): Promise<{
data: any;
total: any;
page: number;
pageSize: number;
totalPages: number;
}>;
export declare function getReservation(id: string, companyId: string): Promise<any>;
export declare function createReservation(companyId: string, body: {
vehicleId: string;
customerId: string;
startDate: string;
endDate: string;
pickupLocation?: string;
returnLocation?: string;
offerId?: string;
promoCodeUsed?: string;
depositAmount?: number;
paymentMode?: string;
notes?: string;
selectedInsurancePolicyIds?: string[];
additionalDrivers?: any[];
}): Promise<any>;
export declare function updateReservation(id: string, companyId: string, body: {
startDate?: string;
endDate?: string;
pickupLocation?: string | null;
returnLocation?: string | null;
depositAmount?: number;
notes?: string | null;
paymentMode?: string | null;
damageChargeAmount?: number;
damageChargeNote?: string | null;
}): Promise<any>;
//# sourceMappingURL=reservation.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"reservation.service.d.ts","sourceRoot":"","sources":["../../../src/modules/reservations/reservation.service.ts"],"names":[],"mappings":"AASA,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CACvE;;;;;;GAkBA;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAGjE;AAED,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;IAC/D,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IAClE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACpF,0BAA0B,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,iBAAiB,CAAC,EAAE,GAAG,EAAE,CAAA;CACjE,gBAiEA;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACpE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3F,gBAsGA"}
@@ -0,0 +1,223 @@
"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.listReservations = listReservations;
exports.getReservation = getReservation;
exports.createReservation = createReservation;
exports.updateReservation = updateReservation;
const prisma_1 = require("../../lib/prisma");
const errors_1 = require("../../http/errors");
const licenseValidationService_1 = require("../../services/licenseValidationService");
const reservation_pricing_service_1 = require("./reservation.pricing.service");
const reservation_insurance_service_1 = require("./reservation.insurance.service");
const reservation_additional_driver_service_1 = require("./reservation.additional-driver.service");
const reservation_presenter_1 = require("./reservation.presenter");
const repo = __importStar(require("./reservation.repo"));
async function listReservations(companyId, query) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where = { companyId };
if (query.status)
where.status = query.status;
if (query.vehicleId)
where.vehicleId = query.vehicleId;
if (query.source)
where.source = query.source;
if (query.startDate)
where.startDate = { gte: new Date(query.startDate) };
if (query.endDate)
where.endDate = { lte: new Date(query.endDate) };
const [reservations, total] = await repo.findMany(where, (page - 1) * pageSize, pageSize);
return {
data: reservations.map(reservation_presenter_1.serializeReservationForDashboard),
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
};
}
async function getReservation(id, companyId) {
const reservation = await repo.findById(id, companyId);
return (0, reservation_presenter_1.serializeReservationForDashboard)(reservation);
}
async function createReservation(companyId, body) {
const vehicle = await repo.findVehicle(body.vehicleId, companyId);
await repo.findCustomer(body.customerId, companyId);
const start = new Date(body.startDate);
const end = new Date(body.endDate);
const totalDays = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
const conflict = await repo.findConflict(body.vehicleId, start, end);
if (conflict)
throw new errors_1.AppError('Vehicle is not available for the selected dates', 409, 'vehicle_unavailable');
let discountAmount = 0;
let offerId = body.offerId ?? null;
if (body.promoCodeUsed) {
const offer = await repo.findActiveOffer(companyId, body.promoCodeUsed);
if (offer) {
offerId = offer.id;
const base = vehicle.dailyRate * totalDays;
if (offer.type === 'PERCENTAGE')
discountAmount = Math.round(base * offer.discountValue / 100);
else if (offer.type === 'FIXED_AMOUNT')
discountAmount = offer.discountValue;
else if (offer.type === 'FREE_DAY')
discountAmount = vehicle.dailyRate * offer.discountValue;
await repo.incrementOfferRedemption(offer.id);
}
}
const depositAmount = body.depositAmount ?? 0;
const additionalDriversList = body.additionalDrivers ?? [];
const baseAmount = vehicle.dailyRate * totalDays;
const { applied, total: pricingTotal } = await (0, reservation_pricing_service_1.applyPricingRules)(companyId, body.customerId, additionalDriversList, vehicle.dailyRate, totalDays);
const totalAmount = baseAmount - discountAmount + pricingTotal + depositAmount;
const reservation = await repo.create({
companyId,
vehicleId: body.vehicleId,
customerId: body.customerId,
startDate: start,
endDate: end,
pickupLocation: body.pickupLocation ?? null,
returnLocation: body.returnLocation ?? null,
offerId,
promoCodeUsed: body.promoCodeUsed ?? null,
source: 'DASHBOARD',
dailyRate: vehicle.dailyRate,
discountAmount,
totalDays,
totalAmount,
depositAmount,
notes: body.notes ?? null,
extras: body.paymentMode ? { paymentMode: body.paymentMode } : undefined,
pricingRulesApplied: applied,
pricingRulesTotal: pricingTotal,
});
const insuranceIds = body.selectedInsurancePolicyIds ?? [];
const additionalDrivers = body.additionalDrivers ?? [];
if (insuranceIds.length > 0) {
await (0, reservation_insurance_service_1.applyInsurancesToReservation)(reservation.id, companyId, insuranceIds, totalDays, baseAmount);
}
if (additionalDrivers.length > 0) {
await (0, reservation_additional_driver_service_1.applyAdditionalDriversToReservation)(reservation.id, companyId, additionalDrivers, totalDays);
}
await (0, licenseValidationService_1.validateAndFlagLicense)(body.customerId, companyId).catch(() => null);
return reservation;
}
async function updateReservation(id, companyId, body) {
const reservation = await repo.findByIdWithRelations(id, companyId);
if (reservation.status === 'CANCELLED' || reservation.status === 'NO_SHOW') {
throw new errors_1.AppError('This reservation can no longer be edited', 400, 'invalid_status');
}
const workflow = (0, reservation_presenter_1.buildReservationWorkflow)(reservation);
const requested = Object.keys(body).filter((k) => body[k] !== undefined);
const bookingFields = ['startDate', 'endDate', 'pickupLocation', 'returnLocation', 'depositAmount', 'notes', 'paymentMode'];
const returnFields = ['returnLocation', 'damageChargeAmount', 'damageChargeNote'];
if (!workflow.coreEditable && !workflow.returnEditable) {
throw new errors_1.AppError('This reservation is locked for editing', 400, 'reservation_locked');
}
if (workflow.coreEditable) {
const invalid = requested.filter((f) => !bookingFields.includes(f));
if (invalid.length > 0)
throw new errors_1.AppError('Only booking details can be edited at this stage', 400, 'invalid_fields');
const nextStart = body.startDate ? new Date(body.startDate) : reservation.startDate;
const nextEnd = body.endDate ? new Date(body.endDate) : reservation.endDate;
const totalDays = Math.ceil((nextEnd.getTime() - nextStart.getTime()) / (1000 * 60 * 60 * 24));
if (nextEnd <= nextStart || totalDays <= 0)
throw new errors_1.AppError('End date must be after start date', 400, 'invalid_dates');
if (body.startDate || body.endDate) {
const conflict = await repo.findConflict(reservation.vehicleId, nextStart, nextEnd, reservation.id);
if (conflict)
throw new errors_1.AppError('Vehicle is not available for the selected dates', 409, 'vehicle_unavailable');
}
const baseAmount = reservation.dailyRate * totalDays;
const { applied, total: pricingRulesTotal } = await (0, reservation_pricing_service_1.applyPricingRules)(companyId, reservation.customerId, reservation.additionalDrivers.map((d) => ({ dateOfBirth: d.dateOfBirth, licenseIssuedAt: d.licenseIssuedAt })), reservation.dailyRate, totalDays);
const insuranceUpdates = reservation.insurances.map((ins) => ({
id: ins.id,
totalCharge: (0, reservation_pricing_service_1.calculateUpdatedInsuranceCharge)(ins.chargeType, ins.chargeValue, totalDays, baseAmount),
}));
const driverUpdates = reservation.additionalDrivers.map((d) => ({
id: d.id,
totalCharge: (0, reservation_pricing_service_1.calculateUpdatedAdditionalDriverCharge)(d.chargeType, d.chargeValue, totalDays),
}));
const insuranceTotal = insuranceUpdates.reduce((s, i) => s + i.totalCharge, 0);
const additionalDriverTotal = driverUpdates.reduce((s, d) => s + d.totalCharge, 0);
const depositAmount = body.depositAmount ?? reservation.depositAmount;
const totalAmount = baseAmount - reservation.discountAmount + pricingRulesTotal + insuranceTotal + additionalDriverTotal + depositAmount;
const extras = (0, reservation_presenter_1.parseReservationExtras)(reservation.extras);
if (body.paymentMode !== undefined) {
const next = (0, reservation_presenter_1.normalizeOptionalString)(body.paymentMode);
if (next)
extras.paymentMode = next;
else
delete extras.paymentMode;
}
await prisma_1.prisma.$transaction([
prisma_1.prisma.reservation.update({
where: { id: reservation.id },
data: {
startDate: nextStart,
endDate: nextEnd,
totalDays,
pickupLocation: body.pickupLocation !== undefined ? (0, reservation_presenter_1.normalizeOptionalString)(body.pickupLocation) : reservation.pickupLocation,
returnLocation: body.returnLocation !== undefined ? (0, reservation_presenter_1.normalizeOptionalString)(body.returnLocation) : reservation.returnLocation,
depositAmount,
notes: body.notes !== undefined ? (0, reservation_presenter_1.normalizeOptionalString)(body.notes) : reservation.notes,
pricingRulesApplied: applied,
pricingRulesTotal,
insuranceTotal,
additionalDriverTotal,
totalAmount,
extras: (Object.keys(extras).length > 0 ? extras : {}),
},
}),
...insuranceUpdates.map((ins) => prisma_1.prisma.reservationInsurance.update({ where: { id: ins.id }, data: { totalCharge: ins.totalCharge } })),
...driverUpdates.map((d) => prisma_1.prisma.additionalDriver.update({ where: { id: d.id }, data: { totalCharge: d.totalCharge } })),
]);
}
else {
const invalid = requested.filter((f) => !returnFields.includes(f));
if (invalid.length > 0)
throw new errors_1.AppError('Only return details can be edited after vehicle return', 400, 'invalid_fields');
await prisma_1.prisma.reservation.update({
where: { id: reservation.id },
data: {
returnLocation: body.returnLocation !== undefined ? (0, reservation_presenter_1.normalizeOptionalString)(body.returnLocation) : reservation.returnLocation,
damageChargeAmount: body.damageChargeAmount !== undefined ? body.damageChargeAmount : reservation.damageChargeAmount,
damageChargeNote: body.damageChargeNote !== undefined ? (0, reservation_presenter_1.normalizeOptionalString)(body.damageChargeNote) : reservation.damageChargeNote,
},
});
}
return repo.findById(id, companyId).then(reservation_presenter_1.serializeReservationForDashboard);
}
//# sourceMappingURL=reservation.service.js.map
File diff suppressed because one or more lines are too long