archetecture security fix
This commit is contained in:
@@ -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
|
||||
+1
@@ -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
|
||||
+1
@@ -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"}
|
||||
@@ -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"}
|
||||
@@ -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
Reference in New Issue
Block a user