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

223 lines
12 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.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