"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