archetecture security fix

This commit is contained in:
root
2026-06-11 03:22:12 -04:00
parent 6def9993da
commit 9483750161
3126 changed files with 177194 additions and 37211 deletions
+14
View File
@@ -0,0 +1,14 @@
export declare function presentVehicle(vehicle: any): any;
export declare function presentVehicleList(vehicles: any[], meta: {
total: number;
page: number;
pageSize: number;
totalPages: number;
}): {
total: number;
page: number;
pageSize: number;
totalPages: number;
data: any[];
};
//# sourceMappingURL=vehicle.presenter.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"vehicle.presenter.d.ts","sourceRoot":"","sources":["../../../src/modules/vehicles/vehicle.presenter.ts"],"names":[],"mappings":"AAIA,wBAAgB,cAAc,CAAC,OAAO,EAAE,GAAG,OAO1C;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE;WAA5D,MAAM;UAAQ,MAAM;cAAY,MAAM;gBAAc,MAAM;;EAE5H"}
+19
View File
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.presentVehicle = presentVehicle;
exports.presentVehicleList = presentVehicleList;
function normalizeLocations(value) {
return Array.isArray(value) ? value.filter((entry) => typeof entry === 'string') : [];
}
function presentVehicle(vehicle) {
return {
...vehicle,
pickupLocations: normalizeLocations(vehicle?.pickupLocations),
allowDifferentDropoff: Boolean(vehicle?.allowDifferentDropoff),
dropoffLocations: normalizeLocations(vehicle?.dropoffLocations),
};
}
function presentVehicleList(vehicles, meta) {
return { data: vehicles, ...meta };
}
//# sourceMappingURL=vehicle.presenter.js.map
@@ -0,0 +1 @@
{"version":3,"file":"vehicle.presenter.js","sourceRoot":"","sources":["../../../src/modules/vehicles/vehicle.presenter.ts"],"names":[],"mappings":";;AAIA,wCAOC;AAED,gDAEC;AAfD,SAAS,kBAAkB,CAAC,KAAc;IACxC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACxG,CAAC;AAED,SAAgB,cAAc,CAAC,OAAY;IACzC,OAAO;QACL,GAAG,OAAO;QACV,eAAe,EAAE,kBAAkB,CAAC,OAAO,EAAE,eAAe,CAAC;QAC7D,qBAAqB,EAAE,OAAO,CAAC,OAAO,EAAE,qBAAqB,CAAC;QAC9D,gBAAgB,EAAE,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,CAAC;KAChE,CAAA;AACH,CAAC;AAED,SAAgB,kBAAkB,CAAC,QAAe,EAAE,IAA2E;IAC7H,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAA;AACpC,CAAC"}
+23
View File
@@ -0,0 +1,23 @@
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 findFirst(id: string, companyId: string): Promise<any>;
export declare function create(data: any): Promise<any>;
export declare function updateById(id: string, data: any): Promise<any>;
export declare function softDelete(id: string, companyId: string): Promise<any>;
export declare function setPublished(id: string, companyId: string, isPublished: boolean): Promise<any>;
export declare function findReservationConflicts(vehicleId: string, companyId: string, start: Date, end: Date): Promise<any>;
export declare function findCalendarBlockConflicts(vehicleId: string, start: Date, end: Date): Promise<any>;
export declare function findCalendarEvents(vehicleId: string, companyId: string, rangeStart: Date, rangeEnd: Date): Promise<any>;
export declare function createCalendarBlock(data: any): Promise<any>;
export declare function deleteCalendarBlock(id: string, vehicleId: string): Promise<any>;
export declare function findMaintenanceLogs(vehicleId: string): Promise<any>;
export declare function createMaintenanceLog(data: any): Promise<any>;
export declare function findPricingConfiguration(vehicleId: string): Promise<any>;
export declare function createPricingConfiguration(data: any): Promise<any>;
export declare function updatePricingConfiguration(id: string, data: any): Promise<any>;
export declare function findPricingRule(id: string, configurationId: string): Promise<any>;
export declare function createPricingRule(data: any): Promise<any>;
export declare function updatePricingRule(id: string, data: any): Promise<any>;
export declare function deletePricingRule(id: string, configurationId: string): Promise<any>;
export declare function createPriceHistory(data: any): Promise<any>;
//# sourceMappingURL=vehicle.repo.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"vehicle.repo.d.ts","sourceRoot":"","sources":["../../../src/modules/vehicles/vehicle.repo.ts"],"names":[],"mappings":"AAEA,wBAAsB,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,gBAKpE;AAED,wBAAsB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAE3D;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAE5D;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,GAAG,gBAErC;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,gBAErD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAE7D;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,gBAErF;AAED,wBAAsB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,gBAW1G;AAED,wBAAsB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,gBAKzF;AAED,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,gBAqB9G;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,GAAG,gBAElD;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAEtE;AAED,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,gBAE1D;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,GAAG,gBAEnD;AAED,wBAAsB,wBAAwB,CAAC,SAAS,EAAE,MAAM,gBAQ/D;AAED,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,GAAG,gBAQzD;AAED,wBAAsB,0BAA0B,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,gBASrE;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,gBAExE;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,GAAG,gBAEhD;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,gBAE5D;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,gBAE1E;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,GAAG,gBAEjD"}
+145
View File
@@ -0,0 +1,145 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findMany = findMany;
exports.findById = findById;
exports.findFirst = findFirst;
exports.create = create;
exports.updateById = updateById;
exports.softDelete = softDelete;
exports.setPublished = setPublished;
exports.findReservationConflicts = findReservationConflicts;
exports.findCalendarBlockConflicts = findCalendarBlockConflicts;
exports.findCalendarEvents = findCalendarEvents;
exports.createCalendarBlock = createCalendarBlock;
exports.deleteCalendarBlock = deleteCalendarBlock;
exports.findMaintenanceLogs = findMaintenanceLogs;
exports.createMaintenanceLog = createMaintenanceLog;
exports.findPricingConfiguration = findPricingConfiguration;
exports.createPricingConfiguration = createPricingConfiguration;
exports.updatePricingConfiguration = updatePricingConfiguration;
exports.findPricingRule = findPricingRule;
exports.createPricingRule = createPricingRule;
exports.updatePricingRule = updatePricingRule;
exports.deletePricingRule = deletePricingRule;
exports.createPriceHistory = createPriceHistory;
const prisma_1 = require("../../lib/prisma");
async function findMany(where, skip, take) {
return Promise.all([
prisma_1.prisma.vehicle.findMany({ where, skip, take, orderBy: { createdAt: 'desc' } }),
prisma_1.prisma.vehicle.count({ where }),
]);
}
async function findById(id, companyId) {
return prisma_1.prisma.vehicle.findFirst({ where: { id, companyId } });
}
async function findFirst(id, companyId) {
return prisma_1.prisma.vehicle.findFirst({ where: { id, companyId } });
}
async function create(data) {
return prisma_1.prisma.vehicle.create({ data });
}
async function updateById(id, data) {
return prisma_1.prisma.vehicle.update({ where: { id }, data });
}
async function softDelete(id, companyId) {
return prisma_1.prisma.vehicle.updateMany({ where: { id, companyId }, data: { status: 'OUT_OF_SERVICE', isPublished: false } });
}
async function setPublished(id, companyId, isPublished) {
return prisma_1.prisma.vehicle.updateMany({ where: { id, companyId }, data: { isPublished } });
}
async function findReservationConflicts(vehicleId, companyId, start, end) {
return prisma_1.prisma.reservation.findMany({
where: {
vehicleId,
companyId,
status: { in: ['CONFIRMED', 'ACTIVE'] },
startDate: { lt: end },
endDate: { gt: start },
},
select: { id: true, startDate: true, endDate: true, status: true },
});
}
async function findCalendarBlockConflicts(vehicleId, start, end) {
return prisma_1.prisma.vehicleCalendarBlock.findMany({
where: { vehicleId, startDate: { lt: end }, endDate: { gt: start } },
select: { id: true, startDate: true, endDate: true, type: true, reason: true },
});
}
async function findCalendarEvents(vehicleId, companyId, rangeStart, rangeEnd) {
return Promise.all([
prisma_1.prisma.reservation.findMany({
where: {
vehicleId,
companyId,
status: { in: ['DRAFT', 'CONFIRMED', 'ACTIVE'] },
startDate: { lt: rangeEnd },
endDate: { gt: rangeStart },
},
select: {
id: true, startDate: true, endDate: true, status: true,
customer: { select: { firstName: true, lastName: true } },
},
orderBy: { startDate: 'asc' },
}),
prisma_1.prisma.vehicleCalendarBlock.findMany({
where: { vehicleId, startDate: { lt: rangeEnd }, endDate: { gt: rangeStart } },
orderBy: { startDate: 'asc' },
}),
]);
}
async function createCalendarBlock(data) {
return prisma_1.prisma.vehicleCalendarBlock.create({ data });
}
async function deleteCalendarBlock(id, vehicleId) {
return prisma_1.prisma.vehicleCalendarBlock.deleteMany({ where: { id, vehicleId } });
}
async function findMaintenanceLogs(vehicleId) {
return prisma_1.prisma.maintenanceLog.findMany({ where: { vehicleId }, orderBy: { performedAt: 'desc' } });
}
async function createMaintenanceLog(data) {
return prisma_1.prisma.maintenanceLog.create({ data });
}
async function findPricingConfiguration(vehicleId) {
return prisma_1.prisma.vehiclePricingConfiguration.findUnique({
where: { vehicleId },
include: {
rules: { orderBy: [{ sortOrder: 'asc' }, { startDate: 'asc' }, { createdAt: 'asc' }] },
history: { orderBy: { createdAt: 'desc' }, take: 20 },
},
});
}
async function createPricingConfiguration(data) {
return prisma_1.prisma.vehiclePricingConfiguration.create({
data,
include: {
rules: { orderBy: [{ sortOrder: 'asc' }, { startDate: 'asc' }, { createdAt: 'asc' }] },
history: { orderBy: { createdAt: 'desc' }, take: 20 },
},
});
}
async function updatePricingConfiguration(id, data) {
return prisma_1.prisma.vehiclePricingConfiguration.update({
where: { id },
data,
include: {
rules: { orderBy: [{ sortOrder: 'asc' }, { startDate: 'asc' }, { createdAt: 'asc' }] },
history: { orderBy: { createdAt: 'desc' }, take: 20 },
},
});
}
async function findPricingRule(id, configurationId) {
return prisma_1.prisma.vehiclePricingRule.findFirst({ where: { id, configurationId } });
}
async function createPricingRule(data) {
return prisma_1.prisma.vehiclePricingRule.create({ data });
}
async function updatePricingRule(id, data) {
return prisma_1.prisma.vehiclePricingRule.update({ where: { id }, data });
}
async function deletePricingRule(id, configurationId) {
return prisma_1.prisma.vehiclePricingRule.deleteMany({ where: { id, configurationId } });
}
async function createPriceHistory(data) {
return prisma_1.prisma.vehiclePriceHistory.create({ data });
}
//# sourceMappingURL=vehicle.repo.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
declare const router: import("express-serve-static-core").Router;
export default router;
//# sourceMappingURL=vehicle.routes.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"vehicle.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/vehicles/vehicle.routes.ts"],"names":[],"mappings":"AAgBA,QAAA,MAAM,MAAM,4CAAW,CAAA;AA0KvB,eAAe,MAAM,CAAA"}
+255
View File
@@ -0,0 +1,255 @@
"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 requireCompanyAuth_1 = require("../../middleware/requireCompanyAuth");
const requireTenant_1 = require("../../middleware/requireTenant");
const requireSubscription_1 = require("../../middleware/requireSubscription");
const requireRole_1 = require("../../middleware/requireRole");
const validate_1 = require("../../http/validate");
const respond_1 = require("../../http/respond");
const upload_1 = require("../../http/upload");
const service = __importStar(require("./vehicle.service"));
const vehicle_schemas_1 = require("./vehicle.schemas");
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)(vehicle_schemas_1.listQuerySchema, req);
const result = await service.listVehicles(req.companyId, query);
res.json(result);
}
catch (err) {
next(err);
}
});
router.post('/', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const body = (0, validate_1.parseBody)(vehicle_schemas_1.vehicleSchema, req);
const vehicle = await service.createVehicle(body, req.companyId);
(0, respond_1.created)(res, vehicle);
}
catch (err) {
next(err);
}
});
router.get('/:id', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const vehicle = await service.getVehicle(id, req.companyId);
(0, respond_1.ok)(res, vehicle);
}
catch (err) {
next(err);
}
});
router.patch('/:id', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const body = (0, validate_1.parseBody)(vehicle_schemas_1.vehicleSchema.partial(), req);
const vehicle = await service.updateVehicle(id, req.companyId, body);
(0, respond_1.ok)(res, vehicle);
}
catch (err) {
next(err);
}
});
router.delete('/:id', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
await service.deleteVehicle(id, req.companyId);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
router.post('/:id/photos', (0, requireRole_1.requireRole)('MANAGER'), upload_1.imageUpload.array('photos', 10), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const files = req.files;
(0, upload_1.assertImageFiles)(files);
const updated = await service.uploadPhotos(id, req.companyId, files);
(0, respond_1.ok)(res, updated);
}
catch (err) {
next(err);
}
});
router.delete('/:id/photos/:idx', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id, idx } = (0, validate_1.parseParams)(vehicle_schemas_1.photoIdxSchema, req);
const updated = await service.deletePhoto(id, req.companyId, parseInt(idx, 10));
(0, respond_1.ok)(res, updated);
}
catch (err) {
next(err);
}
});
router.patch('/:id/status', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const { status } = (0, validate_1.parseBody)(vehicle_schemas_1.statusSchema, req);
(0, respond_1.ok)(res, await service.setStatus(id, req.companyId, status));
}
catch (err) {
next(err);
}
});
router.patch('/:id/publish', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const { isPublished } = (0, validate_1.parseBody)(vehicle_schemas_1.publishSchema, req);
await service.setPublished(id, req.companyId, isPublished);
(0, respond_1.ok)(res, { success: true, isPublished });
}
catch (err) {
next(err);
}
});
router.get('/:id/availability', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const { startDate, endDate } = (0, validate_1.parseQuery)(vehicle_schemas_1.availabilityQuerySchema, req);
const result = await service.checkAvailability(id, req.companyId, startDate, endDate);
(0, respond_1.ok)(res, result);
}
catch (err) {
next(err);
}
});
router.get('/:id/calendar', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const { year, month } = (0, validate_1.parseQuery)(vehicle_schemas_1.calendarQuerySchema, req);
const events = await service.getCalendar(id, req.companyId, year, month);
(0, respond_1.ok)(res, events);
}
catch (err) {
next(err);
}
});
router.post('/:id/calendar/blocks', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const body = (0, validate_1.parseBody)(vehicle_schemas_1.calendarBlockSchema, req);
const block = await service.createCalendarBlock(id, req.companyId, body);
(0, respond_1.created)(res, block);
}
catch (err) {
next(err);
}
});
router.delete('/:id/calendar/blocks/:blockId', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id, blockId } = (0, validate_1.parseParams)(vehicle_schemas_1.blockIdParamSchema, req);
await service.deleteCalendarBlock(id, req.companyId, blockId);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
router.get('/:id/maintenance', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const logs = await service.getMaintenanceLogs(id, req.companyId);
(0, respond_1.ok)(res, logs);
}
catch (err) {
next(err);
}
});
router.post('/:id/maintenance', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const body = (0, validate_1.parseBody)(vehicle_schemas_1.maintenanceLogSchema, req);
const log = await service.createMaintenanceLog(id, req.companyId, body);
(0, respond_1.created)(res, log);
}
catch (err) {
next(err);
}
});
router.get('/:id/pricing', async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
(0, respond_1.ok)(res, await service.getVehiclePricing(id, req.companyId));
}
catch (err) {
next(err);
}
});
router.put('/:id/pricing', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const body = (0, validate_1.parseBody)(vehicle_schemas_1.pricingConfigSchema, req);
(0, respond_1.ok)(res, await service.updateVehiclePricing(id, req.companyId, req.employee.id, body));
}
catch (err) {
next(err);
}
});
router.post('/:id/pricing/rules', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(vehicle_schemas_1.idParamSchema, req);
const body = (0, validate_1.parseBody)(vehicle_schemas_1.pricingRuleSchema, req);
(0, respond_1.created)(res, await service.createVehiclePricingRule(id, req.companyId, req.employee.id, body));
}
catch (err) {
next(err);
}
});
router.patch('/:id/pricing/rules/:ruleId', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id, ruleId } = (0, validate_1.parseParams)(vehicle_schemas_1.pricingRuleParamSchema, req);
const body = (0, validate_1.parseBody)(vehicle_schemas_1.pricingRuleSchema.partial(), req);
(0, respond_1.ok)(res, await service.updateVehiclePricingRule(id, req.companyId, ruleId, req.employee.id, body));
}
catch (err) {
next(err);
}
});
router.delete('/:id/pricing/rules/:ruleId', (0, requireRole_1.requireRole)('MANAGER'), async (req, res, next) => {
try {
const { id, ruleId } = (0, validate_1.parseParams)(vehicle_schemas_1.pricingRuleParamSchema, req);
(0, respond_1.ok)(res, await service.deleteVehiclePricingRule(id, req.companyId, ruleId, req.employee.id));
}
catch (err) {
next(err);
}
});
exports.default = router;
//# sourceMappingURL=vehicle.routes.js.map
File diff suppressed because one or more lines are too long
+283
View File
@@ -0,0 +1,283 @@
import { z } from 'zod';
export declare const vehicleSchema: z.ZodObject<{
make: z.ZodString;
model: z.ZodString;
year: z.ZodNumber;
color: z.ZodDefault<z.ZodString>;
licensePlate: z.ZodString;
vin: z.ZodOptional<z.ZodString>;
category: z.ZodEnum<["ECONOMY", "COMPACT", "MIDSIZE", "FULLSIZE", "SUV", "LUXURY", "VAN", "TRUCK"]>;
seats: z.ZodDefault<z.ZodNumber>;
transmission: z.ZodDefault<z.ZodEnum<["AUTOMATIC", "MANUAL"]>>;
fuelType: z.ZodDefault<z.ZodEnum<["GASOLINE", "DIESEL", "ELECTRIC", "HYBRID"]>>;
features: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
dailyRate: z.ZodOptional<z.ZodNumber>;
mileage: z.ZodOptional<z.ZodNumber>;
notes: z.ZodOptional<z.ZodString>;
isPublished: z.ZodDefault<z.ZodBoolean>;
status: z.ZodOptional<z.ZodEnum<["AVAILABLE", "RESERVED", "READY", "RENTED", "RETURNED", "NEEDS_CLEANING", "MAINTENANCE", "DAMAGE_REVIEW", "OUT_OF_SERVICE"]>>;
pickupLocations: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
allowDifferentDropoff: z.ZodDefault<z.ZodBoolean>;
dropoffLocations: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
}, "strip", z.ZodTypeAny, {
make: string;
model: string;
year: number;
color: string;
licensePlate: string;
category: "ECONOMY" | "COMPACT" | "MIDSIZE" | "FULLSIZE" | "SUV" | "LUXURY" | "VAN" | "TRUCK";
seats: number;
transmission: "AUTOMATIC" | "MANUAL";
fuelType: "GASOLINE" | "DIESEL" | "ELECTRIC" | "HYBRID";
features: string[];
isPublished: boolean;
pickupLocations: string[];
allowDifferentDropoff: boolean;
dropoffLocations: string[];
vin?: string | undefined;
status?: "AVAILABLE" | "RESERVED" | "READY" | "RENTED" | "RETURNED" | "NEEDS_CLEANING" | "MAINTENANCE" | "DAMAGE_REVIEW" | "OUT_OF_SERVICE" | undefined;
dailyRate?: number | undefined;
mileage?: number | undefined;
notes?: string | undefined;
}, {
make: string;
model: string;
year: number;
licensePlate: string;
category: "ECONOMY" | "COMPACT" | "MIDSIZE" | "FULLSIZE" | "SUV" | "LUXURY" | "VAN" | "TRUCK";
color?: string | undefined;
vin?: string | undefined;
status?: "AVAILABLE" | "RESERVED" | "READY" | "RENTED" | "RETURNED" | "NEEDS_CLEANING" | "MAINTENANCE" | "DAMAGE_REVIEW" | "OUT_OF_SERVICE" | undefined;
seats?: number | undefined;
transmission?: "AUTOMATIC" | "MANUAL" | undefined;
fuelType?: "GASOLINE" | "DIESEL" | "ELECTRIC" | "HYBRID" | undefined;
features?: string[] | undefined;
dailyRate?: number | undefined;
mileage?: number | undefined;
notes?: string | undefined;
isPublished?: boolean | undefined;
pickupLocations?: string[] | undefined;
allowDifferentDropoff?: boolean | undefined;
dropoffLocations?: string[] | undefined;
}>;
export declare const listQuerySchema: z.ZodObject<{
status: z.ZodOptional<z.ZodString>;
category: z.ZodOptional<z.ZodString>;
published: z.ZodOptional<z.ZodString>;
page: z.ZodDefault<z.ZodNumber>;
pageSize: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
page: number;
pageSize: number;
category?: string | undefined;
status?: string | undefined;
published?: string | undefined;
}, {
category?: string | undefined;
status?: string | undefined;
published?: string | undefined;
page?: number | undefined;
pageSize?: number | undefined;
}>;
export declare const availabilityQuerySchema: z.ZodObject<{
startDate: z.ZodString;
endDate: z.ZodString;
}, "strip", z.ZodTypeAny, {
startDate: string;
endDate: string;
}, {
startDate: string;
endDate: string;
}>;
export declare const calendarQuerySchema: z.ZodObject<{
year: z.ZodNumber;
month: z.ZodNumber;
}, "strip", z.ZodTypeAny, {
year: number;
month: number;
}, {
year: number;
month: number;
}>;
export declare const calendarBlockSchema: z.ZodObject<{
startDate: z.ZodUnion<[z.ZodString, z.ZodString]>;
endDate: z.ZodUnion<[z.ZodString, z.ZodString]>;
reason: z.ZodOptional<z.ZodString>;
type: z.ZodDefault<z.ZodEnum<["MANUAL", "MAINTENANCE"]>>;
}, "strip", z.ZodTypeAny, {
type: "MANUAL" | "MAINTENANCE";
startDate: string;
endDate: string;
reason?: string | undefined;
}, {
startDate: string;
endDate: string;
type?: "MANUAL" | "MAINTENANCE" | undefined;
reason?: string | undefined;
}>;
export declare const maintenanceLogSchema: z.ZodObject<{
type: z.ZodString;
description: z.ZodOptional<z.ZodString>;
cost: z.ZodOptional<z.ZodNumber>;
mileage: z.ZodOptional<z.ZodNumber>;
performedAt: z.ZodString;
nextDueAt: z.ZodOptional<z.ZodString>;
nextDueMileage: z.ZodOptional<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
type: string;
performedAt: string;
mileage?: number | undefined;
description?: string | undefined;
cost?: number | undefined;
nextDueAt?: string | undefined;
nextDueMileage?: number | undefined;
}, {
type: string;
performedAt: string;
mileage?: number | undefined;
description?: string | undefined;
cost?: number | undefined;
nextDueAt?: string | undefined;
nextDueMileage?: number | undefined;
}>;
export declare const pricingConfigSchema: z.ZodObject<{
pricingMode: z.ZodEnum<["MANUAL", "AUTOMATIC"]>;
baseDailyRate: z.ZodNumber;
weeklyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
weekendRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
holidayRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
monthlyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
longTermDailyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
minimumDailyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
maximumDailyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
maxDailyPriceMovementPct: z.ZodDefault<z.ZodNumber>;
automaticPricingEnabled: z.ZodDefault<z.ZodBoolean>;
approvalRequired: z.ZodDefault<z.ZodBoolean>;
note: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
pricingMode: "AUTOMATIC" | "MANUAL";
baseDailyRate: number;
maxDailyPriceMovementPct: number;
automaticPricingEnabled: boolean;
approvalRequired: boolean;
weeklyRate?: number | null | undefined;
weekendRate?: number | null | undefined;
holidayRate?: number | null | undefined;
monthlyRate?: number | null | undefined;
longTermDailyRate?: number | null | undefined;
minimumDailyRate?: number | null | undefined;
maximumDailyRate?: number | null | undefined;
note?: string | undefined;
}, {
pricingMode: "AUTOMATIC" | "MANUAL";
baseDailyRate: number;
weeklyRate?: number | null | undefined;
weekendRate?: number | null | undefined;
holidayRate?: number | null | undefined;
monthlyRate?: number | null | undefined;
longTermDailyRate?: number | null | undefined;
minimumDailyRate?: number | null | undefined;
maximumDailyRate?: number | null | undefined;
maxDailyPriceMovementPct?: number | undefined;
automaticPricingEnabled?: boolean | undefined;
approvalRequired?: boolean | undefined;
note?: string | undefined;
}>;
export declare const pricingRuleSchema: z.ZodObject<{
name: z.ZodString;
ruleType: z.ZodDefault<z.ZodEnum<["DATE_RANGE", "SEASONAL", "HOLIDAY", "WEEKEND", "SPECIAL_EVENT", "MANUAL_OVERRIDE"]>>;
startDate: z.ZodUnion<[z.ZodString, z.ZodString]>;
endDate: z.ZodUnion<[z.ZodString, z.ZodString]>;
dailyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
weeklyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
weekendRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
monthlyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
minDailyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
maxDailyRate: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
automaticAdjustmentPct: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
priceAdjustment: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
isActive: z.ZodDefault<z.ZodBoolean>;
sortOrder: z.ZodDefault<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
startDate: string;
endDate: string;
name: string;
ruleType: "DATE_RANGE" | "SEASONAL" | "HOLIDAY" | "WEEKEND" | "SPECIAL_EVENT" | "MANUAL_OVERRIDE";
isActive: boolean;
sortOrder: number;
dailyRate?: number | null | undefined;
weeklyRate?: number | null | undefined;
weekendRate?: number | null | undefined;
monthlyRate?: number | null | undefined;
minDailyRate?: number | null | undefined;
maxDailyRate?: number | null | undefined;
automaticAdjustmentPct?: number | null | undefined;
priceAdjustment?: number | null | undefined;
}, {
startDate: string;
endDate: string;
name: string;
dailyRate?: number | null | undefined;
weeklyRate?: number | null | undefined;
weekendRate?: number | null | undefined;
monthlyRate?: number | null | undefined;
ruleType?: "DATE_RANGE" | "SEASONAL" | "HOLIDAY" | "WEEKEND" | "SPECIAL_EVENT" | "MANUAL_OVERRIDE" | undefined;
minDailyRate?: number | null | undefined;
maxDailyRate?: number | null | undefined;
automaticAdjustmentPct?: number | null | undefined;
priceAdjustment?: number | null | undefined;
isActive?: boolean | undefined;
sortOrder?: number | undefined;
}>;
export declare const publishSchema: z.ZodObject<{
isPublished: z.ZodBoolean;
}, "strip", z.ZodTypeAny, {
isPublished: boolean;
}, {
isPublished: boolean;
}>;
export declare const statusSchema: z.ZodObject<{
status: z.ZodEnum<["AVAILABLE", "RESERVED", "READY", "RENTED", "RETURNED", "NEEDS_CLEANING", "MAINTENANCE", "DAMAGE_REVIEW", "OUT_OF_SERVICE"]>;
}, "strip", z.ZodTypeAny, {
status: "AVAILABLE" | "RESERVED" | "READY" | "RENTED" | "RETURNED" | "NEEDS_CLEANING" | "MAINTENANCE" | "DAMAGE_REVIEW" | "OUT_OF_SERVICE";
}, {
status: "AVAILABLE" | "RESERVED" | "READY" | "RENTED" | "RETURNED" | "NEEDS_CLEANING" | "MAINTENANCE" | "DAMAGE_REVIEW" | "OUT_OF_SERVICE";
}>;
export declare const idParamSchema: z.ZodObject<{
id: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
}, {
id: string;
}>;
export declare const photoIdxSchema: z.ZodObject<{
id: z.ZodString;
idx: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
idx: string;
}, {
id: string;
idx: string;
}>;
export declare const blockIdParamSchema: z.ZodObject<{
id: z.ZodString;
blockId: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
blockId: string;
}, {
id: string;
blockId: string;
}>;
export declare const pricingRuleParamSchema: z.ZodObject<{
id: z.ZodString;
ruleId: z.ZodString;
}, "strip", z.ZodTypeAny, {
id: string;
ruleId: string;
}, {
id: string;
ruleId: string;
}>;
//# sourceMappingURL=vehicle.schemas.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"vehicle.schemas.d.ts","sourceRoot":"","sources":["../../../src/modules/vehicles/vehicle.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoBxB,CAAA;AAEF,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;EAM1B,CAAA;AAEF,eAAO,MAAM,uBAAuB;;;;;;;;;EAGlC,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;EAG9B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;EAK9B,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;EAQ/B,CAAA;AAIF,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAc9B,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAe5B,CAAA;AAEF,eAAO,MAAM,aAAa;;;;;;EAAyC,CAAA;AACnE,eAAO,MAAM,YAAY;;;;;;EAA8J,CAAA;AAEvL,eAAO,MAAM,aAAa;;;;;;EAAkC,CAAA;AAC5D,eAAO,MAAM,cAAc;;;;;;;;;EAAkD,CAAA;AAC7E,eAAO,MAAM,kBAAkB;;;;;;;;;EAAoD,CAAA;AACnF,eAAO,MAAM,sBAAsB;;;;;;;;;EAAmD,CAAA"}
+94
View File
@@ -0,0 +1,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pricingRuleParamSchema = exports.blockIdParamSchema = exports.photoIdxSchema = exports.idParamSchema = exports.statusSchema = exports.publishSchema = exports.pricingRuleSchema = exports.pricingConfigSchema = exports.maintenanceLogSchema = exports.calendarBlockSchema = exports.calendarQuerySchema = exports.availabilityQuerySchema = exports.listQuerySchema = exports.vehicleSchema = void 0;
const zod_1 = require("zod");
exports.vehicleSchema = zod_1.z.object({
make: zod_1.z.string().min(1),
model: zod_1.z.string().min(1),
year: zod_1.z.number().int().min(1990).max(new Date().getFullYear() + 1),
color: zod_1.z.string().default(''),
licensePlate: zod_1.z.string().min(1),
vin: zod_1.z.string().optional(),
category: zod_1.z.enum(['ECONOMY', 'COMPACT', 'MIDSIZE', 'FULLSIZE', 'SUV', 'LUXURY', 'VAN', 'TRUCK']),
seats: zod_1.z.number().int().min(1).max(20).default(5),
transmission: zod_1.z.enum(['AUTOMATIC', 'MANUAL']).default('AUTOMATIC'),
fuelType: zod_1.z.enum(['GASOLINE', 'DIESEL', 'ELECTRIC', 'HYBRID']).default('GASOLINE'),
features: zod_1.z.array(zod_1.z.string()).default([]),
dailyRate: zod_1.z.number().int().min(0).optional(),
mileage: zod_1.z.number().int().optional(),
notes: zod_1.z.string().optional(),
isPublished: zod_1.z.boolean().default(true),
status: zod_1.z.enum(['AVAILABLE', 'RESERVED', 'READY', 'RENTED', 'RETURNED', 'NEEDS_CLEANING', 'MAINTENANCE', 'DAMAGE_REVIEW', 'OUT_OF_SERVICE']).optional(),
pickupLocations: zod_1.z.array(zod_1.z.string().trim().min(1)).default([]),
allowDifferentDropoff: zod_1.z.boolean().default(false),
dropoffLocations: zod_1.z.array(zod_1.z.string().trim().min(1)).default([]),
});
exports.listQuerySchema = zod_1.z.object({
status: zod_1.z.string().optional(),
category: zod_1.z.string().optional(),
published: 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.availabilityQuerySchema = zod_1.z.object({
startDate: zod_1.z.string(),
endDate: zod_1.z.string(),
});
exports.calendarQuerySchema = zod_1.z.object({
year: zod_1.z.coerce.number().int(),
month: zod_1.z.coerce.number().int().min(1).max(12),
});
exports.calendarBlockSchema = zod_1.z.object({
startDate: zod_1.z.string().datetime({ offset: true }).or(zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}$/)),
endDate: zod_1.z.string().datetime({ offset: true }).or(zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}$/)),
reason: zod_1.z.string().optional(),
type: zod_1.z.enum(['MANUAL', 'MAINTENANCE']).default('MANUAL'),
});
exports.maintenanceLogSchema = zod_1.z.object({
type: zod_1.z.string().min(1),
description: zod_1.z.string().optional(),
cost: zod_1.z.number().int().optional(),
mileage: zod_1.z.number().int().optional(),
performedAt: zod_1.z.string().datetime(),
nextDueAt: zod_1.z.string().datetime().optional(),
nextDueMileage: zod_1.z.number().int().optional(),
});
const nullableRateSchema = zod_1.z.number().int().min(0).nullable().optional();
exports.pricingConfigSchema = zod_1.z.object({
pricingMode: zod_1.z.enum(['MANUAL', 'AUTOMATIC']),
baseDailyRate: zod_1.z.number().int().min(0),
weeklyRate: nullableRateSchema,
weekendRate: nullableRateSchema,
holidayRate: nullableRateSchema,
monthlyRate: nullableRateSchema,
longTermDailyRate: nullableRateSchema,
minimumDailyRate: nullableRateSchema,
maximumDailyRate: nullableRateSchema,
maxDailyPriceMovementPct: zod_1.z.number().int().min(0).max(100).default(10),
automaticPricingEnabled: zod_1.z.boolean().default(false),
approvalRequired: zod_1.z.boolean().default(false),
note: zod_1.z.string().trim().min(1).max(500).optional(),
});
exports.pricingRuleSchema = zod_1.z.object({
name: zod_1.z.string().trim().min(1).max(120),
ruleType: zod_1.z.enum(['DATE_RANGE', 'SEASONAL', 'HOLIDAY', 'WEEKEND', 'SPECIAL_EVENT', 'MANUAL_OVERRIDE']).default('DATE_RANGE'),
startDate: zod_1.z.string().datetime({ offset: true }).or(zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}$/)),
endDate: zod_1.z.string().datetime({ offset: true }).or(zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}$/)),
dailyRate: nullableRateSchema,
weeklyRate: nullableRateSchema,
weekendRate: nullableRateSchema,
monthlyRate: nullableRateSchema,
minDailyRate: nullableRateSchema,
maxDailyRate: nullableRateSchema,
automaticAdjustmentPct: zod_1.z.number().int().min(-100).max(200).nullable().optional(),
priceAdjustment: zod_1.z.number().int().nullable().optional(),
isActive: zod_1.z.boolean().default(true),
sortOrder: zod_1.z.number().int().min(0).max(1000).default(0),
});
exports.publishSchema = zod_1.z.object({ isPublished: zod_1.z.boolean() });
exports.statusSchema = zod_1.z.object({ status: zod_1.z.enum(['AVAILABLE', 'RESERVED', 'READY', 'RENTED', 'RETURNED', 'NEEDS_CLEANING', 'MAINTENANCE', 'DAMAGE_REVIEW', 'OUT_OF_SERVICE']) });
exports.idParamSchema = zod_1.z.object({ id: zod_1.z.string() });
exports.photoIdxSchema = zod_1.z.object({ id: zod_1.z.string(), idx: zod_1.z.string() });
exports.blockIdParamSchema = zod_1.z.object({ id: zod_1.z.string(), blockId: zod_1.z.string() });
exports.pricingRuleParamSchema = zod_1.z.object({ id: zod_1.z.string(), ruleId: zod_1.z.string() });
//# sourceMappingURL=vehicle.schemas.js.map
File diff suppressed because one or more lines are too long
+217
View File
@@ -0,0 +1,217 @@
export declare function listVehicles(companyId: string, query: {
status?: string;
category?: string;
published?: string;
page?: number;
pageSize?: number;
}): Promise<{
total: number;
page: number;
pageSize: number;
totalPages: number;
data: any[];
}>;
export declare function getVehicle(id: string, companyId: string): Promise<any>;
export declare function createVehicle(data: any, companyId: string): Promise<any>;
export declare function updateVehicle(id: string, companyId: string, data: any): Promise<any>;
export declare function setStatus(id: string, companyId: string, status: string): Promise<any>;
export declare function deleteVehicle(id: string, companyId: string): Promise<any>;
export declare function uploadPhotos(id: string, companyId: string, files: Express.Multer.File[]): Promise<any>;
export declare function deletePhoto(id: string, companyId: string, idx: number): Promise<any>;
export declare function setPublished(id: string, companyId: string, isPublished: boolean): Promise<void>;
export declare function checkAvailability(id: string, companyId: string, startDate: string, endDate: string): Promise<{
available: boolean;
conflicts: any;
blocks: any;
}>;
export declare function getCalendar(id: string, companyId: string, year: number, month: number): Promise<any[]>;
export declare function createCalendarBlock(id: string, companyId: string, data: {
startDate: string;
endDate: string;
reason?: string;
type?: 'MANUAL' | 'MAINTENANCE';
}): Promise<any>;
export declare function deleteCalendarBlock(vehicleId: string, companyId: string, blockId: string): Promise<void>;
export declare function getMaintenanceLogs(id: string, companyId: string): Promise<any>;
export declare function createMaintenanceLog(id: string, companyId: string, data: any): Promise<any>;
export declare function getVehiclePricing(id: string, companyId: string): Promise<{
configuration: {
id: any;
vehicleId: any;
pricingMode: any;
baseDailyRate: any;
weeklyRate: any;
weekendRate: any;
holidayRate: any;
monthlyRate: any;
longTermDailyRate: any;
minimumDailyRate: any;
maximumDailyRate: any;
maxDailyPriceMovementPct: any;
automaticPricingEnabled: any;
approvalRequired: any;
createdAt: any;
updatedAt: any;
};
rules: any;
history: any;
preview: {
today: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
} | undefined;
next14Days: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
}[];
};
}>;
export declare function updateVehiclePricing(id: string, companyId: string, employeeId: string, data: any): Promise<{
configuration: {
id: any;
vehicleId: any;
pricingMode: any;
baseDailyRate: any;
weeklyRate: any;
weekendRate: any;
holidayRate: any;
monthlyRate: any;
longTermDailyRate: any;
minimumDailyRate: any;
maximumDailyRate: any;
maxDailyPriceMovementPct: any;
automaticPricingEnabled: any;
approvalRequired: any;
createdAt: any;
updatedAt: any;
};
rules: any;
history: any;
preview: {
today: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
} | undefined;
next14Days: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
}[];
};
}>;
export declare function createVehiclePricingRule(id: string, companyId: string, employeeId: string, data: any): Promise<{
configuration: {
id: any;
vehicleId: any;
pricingMode: any;
baseDailyRate: any;
weeklyRate: any;
weekendRate: any;
holidayRate: any;
monthlyRate: any;
longTermDailyRate: any;
minimumDailyRate: any;
maximumDailyRate: any;
maxDailyPriceMovementPct: any;
automaticPricingEnabled: any;
approvalRequired: any;
createdAt: any;
updatedAt: any;
};
rules: any;
history: any;
preview: {
today: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
} | undefined;
next14Days: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
}[];
};
}>;
export declare function updateVehiclePricingRule(id: string, companyId: string, ruleId: string, employeeId: string, data: any): Promise<{
configuration: {
id: any;
vehicleId: any;
pricingMode: any;
baseDailyRate: any;
weeklyRate: any;
weekendRate: any;
holidayRate: any;
monthlyRate: any;
longTermDailyRate: any;
minimumDailyRate: any;
maximumDailyRate: any;
maxDailyPriceMovementPct: any;
automaticPricingEnabled: any;
approvalRequired: any;
createdAt: any;
updatedAt: any;
};
rules: any;
history: any;
preview: {
today: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
} | undefined;
next14Days: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
}[];
};
}>;
export declare function deleteVehiclePricingRule(id: string, companyId: string, ruleId: string, employeeId: string): Promise<{
configuration: {
id: any;
vehicleId: any;
pricingMode: any;
baseDailyRate: any;
weeklyRate: any;
weekendRate: any;
holidayRate: any;
monthlyRate: any;
longTermDailyRate: any;
minimumDailyRate: any;
maximumDailyRate: any;
maxDailyPriceMovementPct: any;
automaticPricingEnabled: any;
approvalRequired: any;
createdAt: any;
updatedAt: any;
};
rules: any;
history: any;
preview: {
today: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
} | undefined;
next14Days: {
date: string;
dailyRate: any;
pricingType: string;
matchedRuleName: any;
}[];
};
}>;
//# sourceMappingURL=vehicle.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"vehicle.service.d.ts","sourceRoot":"","sources":["../../../src/modules/vehicles/vehicle.service.ts"],"names":[],"mappings":"AA2VA,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;;GAUxJ;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAI7D;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,gBAM/D;AAID,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,gBAY3E;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,gBAK5E;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAEhE;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,gBAK7F;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,gBAK3E;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,iBAErF;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;GAQxG;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,kBAwB3F;AAED,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAA;CAAE,gBAOtK;AAED,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAI9F;AAED,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,gBAIrE;AAED,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,gBASlF;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGpE;AAED,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDtG;AAED,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8B1G;AAED,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkC1H;AAED,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2B/G"}
+633
View File
@@ -0,0 +1,633 @@
"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.listVehicles = listVehicles;
exports.getVehicle = getVehicle;
exports.createVehicle = createVehicle;
exports.updateVehicle = updateVehicle;
exports.setStatus = setStatus;
exports.deleteVehicle = deleteVehicle;
exports.uploadPhotos = uploadPhotos;
exports.deletePhoto = deletePhoto;
exports.setPublished = setPublished;
exports.checkAvailability = checkAvailability;
exports.getCalendar = getCalendar;
exports.createCalendarBlock = createCalendarBlock;
exports.deleteCalendarBlock = deleteCalendarBlock;
exports.getMaintenanceLogs = getMaintenanceLogs;
exports.createMaintenanceLog = createMaintenanceLog;
exports.getVehiclePricing = getVehiclePricing;
exports.updateVehiclePricing = updateVehiclePricing;
exports.createVehiclePricingRule = createVehiclePricingRule;
exports.updateVehiclePricingRule = updateVehiclePricingRule;
exports.deleteVehiclePricingRule = deleteVehiclePricingRule;
const storage_1 = require("../../lib/storage");
const errors_1 = require("../../http/errors");
const vehicle_presenter_1 = require("./vehicle.presenter");
const repo = __importStar(require("./vehicle.repo"));
const DAY_MS = 24 * 60 * 60 * 1000;
const RULE_PRIORITY = {
MANUAL_OVERRIDE: 6,
HOLIDAY: 5,
SPECIAL_EVENT: 4,
SEASONAL: 3,
WEEKEND: 2,
DATE_RANGE: 1,
};
function isPricingStorageMissing(error) {
if (!error || typeof error !== 'object')
return false;
const candidate = error;
return (candidate.code === 'P2021' ||
candidate.message?.includes('vehicle_pricing_configurations') === true ||
candidate.message?.includes('vehicle_pricing_rules') === true ||
candidate.message?.includes('vehicle_price_history') === true);
}
function normalizeLocations(locations) {
if (!Array.isArray(locations))
return [];
const seen = new Set();
const normalized = [];
for (const entry of locations) {
if (typeof entry !== 'string')
continue;
const value = entry.trim();
const key = value.toLocaleLowerCase();
if (!value || seen.has(key))
continue;
seen.add(key);
normalized.push(value);
}
return normalized;
}
function applyLocationSettings(data) {
const patch = { ...data };
if (patch.pickupLocations !== undefined) {
patch.pickupLocations = normalizeLocations(patch.pickupLocations);
}
if (patch.dropoffLocations !== undefined) {
patch.dropoffLocations = normalizeLocations(patch.dropoffLocations);
}
if (patch.allowDifferentDropoff === false) {
patch.dropoffLocations = [];
}
if (patch.allowDifferentDropoff === true && patch.dropoffLocations !== undefined && patch.dropoffLocations.length === 0) {
throw new errors_1.ValidationError('Drop-off locations are required when different drop-off is enabled');
}
return patch;
}
function toStartOfDay(value) {
const date = new Date(value);
date.setHours(0, 0, 0, 0);
return date;
}
function toEndOfDay(value) {
const date = new Date(value);
date.setHours(23, 59, 59, 999);
return date;
}
function isWeekend(date) {
const day = date.getDay();
return day === 0 || day === 6;
}
function clampRate(value, min, max) {
if (min != null && value < min)
return min;
if (max != null && value > max)
return max;
return value;
}
function validateRateBounds(minimum, maximum, label = 'Daily') {
if (minimum != null && maximum != null && minimum > maximum) {
throw new errors_1.ValidationError(`${label} minimum rate must be less than or equal to the maximum rate`);
}
}
function normalizePricingConfigurationInput(data) {
const normalized = {
pricingMode: data.pricingMode,
baseDailyRate: data.baseDailyRate,
weeklyRate: data.weeklyRate ?? null,
weekendRate: data.weekendRate ?? null,
holidayRate: data.holidayRate ?? null,
monthlyRate: data.monthlyRate ?? null,
longTermDailyRate: data.longTermDailyRate ?? null,
minimumDailyRate: data.minimumDailyRate ?? null,
maximumDailyRate: data.maximumDailyRate ?? null,
maxDailyPriceMovementPct: data.maxDailyPriceMovementPct,
automaticPricingEnabled: data.automaticPricingEnabled,
approvalRequired: data.approvalRequired,
};
validateRateBounds(normalized.minimumDailyRate, normalized.maximumDailyRate);
validateRateBounds(normalized.baseDailyRate, normalized.maximumDailyRate, 'Base');
return normalized;
}
function normalizePricingRuleInput(data) {
const normalized = {
name: data.name,
ruleType: data.ruleType,
startDate: toStartOfDay(data.startDate),
endDate: toEndOfDay(data.endDate),
dailyRate: data.dailyRate ?? null,
weeklyRate: data.weeklyRate ?? null,
weekendRate: data.weekendRate ?? null,
monthlyRate: data.monthlyRate ?? null,
minDailyRate: data.minDailyRate ?? null,
maxDailyRate: data.maxDailyRate ?? null,
automaticAdjustmentPct: data.automaticAdjustmentPct ?? null,
priceAdjustment: data.priceAdjustment ?? null,
isActive: data.isActive,
sortOrder: data.sortOrder,
};
if (normalized.endDate < normalized.startDate) {
throw new errors_1.ValidationError('Rule end date must be on or after the start date');
}
validateRateBounds(normalized.minDailyRate, normalized.maxDailyRate);
const hasPricingEffect = [
normalized.dailyRate,
normalized.weeklyRate,
normalized.weekendRate,
normalized.monthlyRate,
normalized.minDailyRate,
normalized.maxDailyRate,
normalized.automaticAdjustmentPct,
normalized.priceAdjustment,
].some((value) => value != null);
if (!hasPricingEffect) {
throw new errors_1.ValidationError('Pricing rules must define at least one rate, bound, or adjustment');
}
return normalized;
}
async function ensurePricingConfigurationForVehicle(vehicleId, companyId) {
const vehicle = await repo.findById(vehicleId, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
let configuration = null;
let persistenceAvailable = true;
try {
configuration = await repo.findPricingConfiguration(vehicleId);
}
catch (error) {
if (!isPricingStorageMissing(error))
throw error;
persistenceAvailable = false;
}
if (!persistenceAvailable) {
return {
vehicle,
persistenceAvailable,
configuration: {
id: `transient-${vehicle.id}`,
vehicleId: vehicle.id,
pricingMode: 'MANUAL',
baseDailyRate: vehicle.dailyRate,
weeklyRate: vehicle.dailyRate * 7,
weekendRate: null,
holidayRate: null,
monthlyRate: null,
longTermDailyRate: null,
minimumDailyRate: null,
maximumDailyRate: null,
maxDailyPriceMovementPct: 10,
automaticPricingEnabled: false,
approvalRequired: false,
rules: [],
history: [],
createdAt: vehicle.createdAt,
updatedAt: vehicle.updatedAt,
},
};
}
if (!configuration) {
configuration = await repo.createPricingConfiguration({
vehicleId,
pricingMode: 'MANUAL',
baseDailyRate: vehicle.dailyRate,
weeklyRate: vehicle.dailyRate * 7,
automaticPricingEnabled: false,
approvalRequired: false,
maxDailyPriceMovementPct: 10,
});
}
return { vehicle, configuration, persistenceAvailable };
}
async function syncPricingBaseRateFromVehicle(vehicleId, previousDailyRate, nextDailyRate) {
const configuration = await repo.findPricingConfiguration(vehicleId);
if (!configuration)
return;
const currentBaseRate = configuration.baseDailyRate ?? previousDailyRate;
if (currentBaseRate === nextDailyRate)
return;
await repo.updatePricingConfiguration(configuration.id, { baseDailyRate: nextDailyRate });
await repo.createPriceHistory({
configurationId: configuration.id,
vehicleId,
source: 'CONFIG_UPDATE',
previousDailyRate: currentBaseRate,
nextDailyRate,
previousWeeklyRate: configuration.weeklyRate ?? null,
nextWeeklyRate: configuration.weeklyRate ?? null,
note: 'Base daily rate synced from vehicle details',
});
}
function buildPricePreview(vehicle, configuration) {
const baseDailyRate = configuration.baseDailyRate ?? vehicle.dailyRate;
const activeRules = (configuration.rules ?? []).filter((rule) => rule.isActive);
const items = Array.from({ length: 14 }, (_, index) => {
const date = new Date(Date.now() + (index * DAY_MS));
date.setHours(12, 0, 0, 0);
let dailyRate = baseDailyRate;
let pricingType = configuration.pricingMode === 'AUTOMATIC' && configuration.automaticPricingEnabled
? 'AUTOMATIC_BASE'
: 'MANUAL_BASE';
if (isWeekend(date) && configuration.weekendRate != null) {
dailyRate = configuration.weekendRate;
pricingType = 'WEEKEND_RATE';
}
const matchingRule = activeRules
.filter((rule) => date >= new Date(rule.startDate) && date <= new Date(rule.endDate))
.sort((a, b) => {
const priorityDiff = (RULE_PRIORITY[b.ruleType] ?? 0) - (RULE_PRIORITY[a.ruleType] ?? 0);
if (priorityDiff !== 0)
return priorityDiff;
if (a.sortOrder !== b.sortOrder)
return b.sortOrder - a.sortOrder;
return new Date(b.startDate).getTime() - new Date(a.startDate).getTime();
})[0];
if (matchingRule) {
if (matchingRule.dailyRate != null) {
dailyRate = matchingRule.dailyRate;
}
else {
if (matchingRule.weekendRate != null && isWeekend(date)) {
dailyRate = matchingRule.weekendRate;
}
if (matchingRule.priceAdjustment != null) {
dailyRate += matchingRule.priceAdjustment;
}
if (matchingRule.automaticAdjustmentPct != null) {
dailyRate = Math.round(dailyRate * (100 + matchingRule.automaticAdjustmentPct) / 100);
}
}
pricingType = configuration.pricingMode === 'AUTOMATIC' ? 'AUTOMATIC_RULE' : 'RULE_RATE';
dailyRate = clampRate(dailyRate, matchingRule.minDailyRate ?? configuration.minimumDailyRate, matchingRule.maxDailyRate ?? configuration.maximumDailyRate);
}
else {
dailyRate = clampRate(dailyRate, configuration.minimumDailyRate, configuration.maximumDailyRate);
}
return {
date: date.toISOString().slice(0, 10),
dailyRate,
pricingType,
matchedRuleName: matchingRule?.name ?? null,
};
});
return {
today: items[0],
next14Days: items,
};
}
function presentVehiclePricing(vehicle, configuration) {
return {
configuration: {
id: configuration.id,
vehicleId: configuration.vehicleId,
pricingMode: configuration.pricingMode,
baseDailyRate: configuration.baseDailyRate ?? vehicle.dailyRate,
weeklyRate: configuration.weeklyRate ?? null,
weekendRate: configuration.weekendRate ?? null,
holidayRate: configuration.holidayRate ?? null,
monthlyRate: configuration.monthlyRate ?? null,
longTermDailyRate: configuration.longTermDailyRate ?? null,
minimumDailyRate: configuration.minimumDailyRate ?? null,
maximumDailyRate: configuration.maximumDailyRate ?? null,
maxDailyPriceMovementPct: configuration.maxDailyPriceMovementPct,
automaticPricingEnabled: configuration.automaticPricingEnabled,
approvalRequired: configuration.approvalRequired,
createdAt: configuration.createdAt,
updatedAt: configuration.updatedAt,
},
rules: (configuration.rules ?? []).map((rule) => ({
id: rule.id,
name: rule.name,
ruleType: rule.ruleType,
startDate: rule.startDate,
endDate: rule.endDate,
dailyRate: rule.dailyRate,
weeklyRate: rule.weeklyRate,
weekendRate: rule.weekendRate,
monthlyRate: rule.monthlyRate,
minDailyRate: rule.minDailyRate,
maxDailyRate: rule.maxDailyRate,
automaticAdjustmentPct: rule.automaticAdjustmentPct,
priceAdjustment: rule.priceAdjustment,
isActive: rule.isActive,
sortOrder: rule.sortOrder,
createdAt: rule.createdAt,
updatedAt: rule.updatedAt,
})),
history: (configuration.history ?? []).map((entry) => ({
id: entry.id,
source: entry.source,
changedByEmployeeId: entry.changedByEmployeeId,
previousDailyRate: entry.previousDailyRate,
nextDailyRate: entry.nextDailyRate,
previousWeeklyRate: entry.previousWeeklyRate,
nextWeeklyRate: entry.nextWeeklyRate,
note: entry.note,
effectiveFrom: entry.effectiveFrom,
createdAt: entry.createdAt,
})),
preview: buildPricePreview(vehicle, configuration),
};
}
async function listVehicles(companyId, query) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const { status, category, published } = query;
const where = { companyId };
if (status)
where.status = status;
if (category)
where.category = category;
if (published !== undefined)
where.isPublished = published === 'true';
const [vehicles, total] = await repo.findMany(where, (page - 1) * pageSize, pageSize);
return (0, vehicle_presenter_1.presentVehicleList)(vehicles.map(vehicle_presenter_1.presentVehicle), { total, page, pageSize, totalPages: Math.ceil(total / pageSize) });
}
async function getVehicle(id, companyId) {
const vehicle = await repo.findById(id, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
return (0, vehicle_presenter_1.presentVehicle)(vehicle);
}
async function createVehicle(data, companyId) {
return (0, vehicle_presenter_1.presentVehicle)(await repo.create({
dailyRate: 0,
...applyLocationSettings(data),
companyId,
}));
}
const PUBLISHED_STATUSES = new Set(['AVAILABLE', 'RESERVED', 'READY', 'RENTED']);
async function updateVehicle(id, companyId, data) {
const patch = applyLocationSettings(data);
if (patch.status) {
patch.isPublished = PUBLISHED_STATUSES.has(patch.status);
}
const existing = await repo.findFirst(id, companyId);
if (!existing)
throw new errors_1.NotFoundError('Vehicle not found');
const updated = await repo.updateById(id, patch);
if (patch.dailyRate !== undefined && typeof patch.dailyRate === 'number') {
await syncPricingBaseRateFromVehicle(id, existing.dailyRate, patch.dailyRate);
}
return (0, vehicle_presenter_1.presentVehicle)(updated);
}
async function setStatus(id, companyId, status) {
const existing = await repo.findFirst(id, companyId);
if (!existing)
throw new errors_1.NotFoundError('Vehicle not found');
const isPublished = PUBLISHED_STATUSES.has(status);
return (0, vehicle_presenter_1.presentVehicle)(await repo.updateById(id, { status, isPublished }));
}
async function deleteVehicle(id, companyId) {
return repo.softDelete(id, companyId);
}
async function uploadPhotos(id, companyId, files) {
const vehicle = await repo.findById(id, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
const urls = await Promise.all(files.map((f) => (0, storage_1.uploadImage)(f.buffer, `companies/${companyId}/vehicles`)));
return (0, vehicle_presenter_1.presentVehicle)(await repo.updateById(id, { photos: [...vehicle.photos, ...urls] }));
}
async function deletePhoto(id, companyId, idx) {
const vehicle = await repo.findById(id, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
const photos = vehicle.photos.filter((_, i) => i !== idx);
return (0, vehicle_presenter_1.presentVehicle)(await repo.updateById(id, { photos }));
}
async function setPublished(id, companyId, isPublished) {
await repo.setPublished(id, companyId, isPublished);
}
async function checkAvailability(id, companyId, startDate, endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
const [conflicts, blocks] = await Promise.all([
repo.findReservationConflicts(id, companyId, start, end),
repo.findCalendarBlockConflicts(id, start, end),
]);
return { available: conflicts.length === 0 && blocks.length === 0, conflicts, blocks };
}
async function getCalendar(id, companyId, year, month) {
const vehicle = await repo.findById(id, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
const rangeStart = new Date(year, month - 1, 1);
const rangeEnd = new Date(year, month, 1);
const [reservations, blocks] = await repo.findCalendarEvents(id, companyId, rangeStart, rangeEnd);
return [
...reservations.map((r) => ({
id: r.id,
type: 'RESERVATION',
startDate: r.startDate,
endDate: r.endDate,
status: r.status,
label: r.customer ? `${r.customer.firstName} ${r.customer.lastName}` : 'Reserved',
})),
...blocks.map((b) => ({
id: b.id,
type: b.type === 'MAINTENANCE' ? 'MAINTENANCE' : 'BLOCK',
startDate: b.startDate,
endDate: b.endDate,
status: null,
label: b.reason ?? (b.type === 'MAINTENANCE' ? 'Maintenance' : 'Blocked'),
})),
];
}
async function createCalendarBlock(id, companyId, data) {
const start = new Date(data.startDate);
const end = new Date(data.endDate);
if (end <= start)
throw new errors_1.ValidationError('endDate must be after startDate');
const vehicle = await repo.findById(id, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
return repo.createCalendarBlock({ vehicleId: id, startDate: start, endDate: end, reason: data.reason, type: data.type ?? 'MANUAL' });
}
async function deleteCalendarBlock(vehicleId, companyId, blockId) {
const vehicle = await repo.findById(vehicleId, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
await repo.deleteCalendarBlock(blockId, vehicleId);
}
async function getMaintenanceLogs(id, companyId) {
const vehicle = await repo.findById(id, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
return repo.findMaintenanceLogs(id);
}
async function createMaintenanceLog(id, companyId, data) {
const vehicle = await repo.findById(id, companyId);
if (!vehicle)
throw new errors_1.NotFoundError('Vehicle not found');
return repo.createMaintenanceLog({
...data,
vehicleId: id,
performedAt: new Date(data.performedAt),
nextDueAt: data.nextDueAt ? new Date(data.nextDueAt) : undefined,
});
}
async function getVehiclePricing(id, companyId) {
const { vehicle, configuration } = await ensurePricingConfigurationForVehicle(id, companyId);
return presentVehiclePricing(vehicle, configuration);
}
async function updateVehiclePricing(id, companyId, employeeId, data) {
const { vehicle, configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId);
if (!persistenceAvailable) {
throw new errors_1.AppError('Vehicle pricing tables are not available yet. Run the latest database migration, then retry.', 503, 'pricing_migration_required');
}
const nextConfig = normalizePricingConfigurationInput(data);
const currentBaseDailyRate = configuration.baseDailyRate ?? vehicle.dailyRate;
const currentWeeklyRate = configuration.weeklyRate ?? null;
const changed = [
'pricingMode',
'baseDailyRate',
'weeklyRate',
'weekendRate',
'holidayRate',
'monthlyRate',
'longTermDailyRate',
'minimumDailyRate',
'maximumDailyRate',
'maxDailyPriceMovementPct',
'automaticPricingEnabled',
'approvalRequired',
].some((key) => (configuration[key] ?? null) !== (nextConfig[key] ?? null));
await repo.updatePricingConfiguration(configuration.id, nextConfig);
if (vehicle.dailyRate !== nextConfig.baseDailyRate) {
await repo.updateById(id, { dailyRate: nextConfig.baseDailyRate });
}
if (changed) {
await repo.createPriceHistory({
configurationId: configuration.id,
vehicleId: id,
source: 'CONFIG_UPDATE',
changedByEmployeeId: employeeId,
previousDailyRate: currentBaseDailyRate,
nextDailyRate: nextConfig.baseDailyRate,
previousWeeklyRate: currentWeeklyRate,
nextWeeklyRate: nextConfig.weeklyRate ?? null,
note: data.note,
});
}
return getVehiclePricing(id, companyId);
}
async function createVehiclePricingRule(id, companyId, employeeId, data) {
const { configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId);
if (!persistenceAvailable) {
throw new errors_1.AppError('Vehicle pricing tables are not available yet. Run the latest database migration, then retry.', 503, 'pricing_migration_required');
}
const payload = normalizePricingRuleInput(data);
const rule = await repo.createPricingRule({
configurationId: configuration.id,
...payload,
});
await repo.createPriceHistory({
configurationId: configuration.id,
vehicleId: id,
source: payload.ruleType === 'MANUAL_OVERRIDE' ? 'MANUAL_OVERRIDE' : 'RULE_CREATED',
changedByEmployeeId: employeeId,
previousDailyRate: configuration.baseDailyRate ?? null,
nextDailyRate: payload.dailyRate,
previousWeeklyRate: configuration.weeklyRate ?? null,
nextWeeklyRate: payload.weeklyRate,
note: `Created pricing rule: ${rule.name}`,
effectiveFrom: rule.startDate,
});
return getVehiclePricing(id, companyId);
}
async function updateVehiclePricingRule(id, companyId, ruleId, employeeId, data) {
const { configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId);
if (!persistenceAvailable) {
throw new errors_1.AppError('Vehicle pricing tables are not available yet. Run the latest database migration, then retry.', 503, 'pricing_migration_required');
}
const existingRule = await repo.findPricingRule(ruleId, configuration.id);
if (!existingRule)
throw new errors_1.NotFoundError('Pricing rule not found');
const mergedRule = normalizePricingRuleInput({
...existingRule,
...data,
startDate: data.startDate ?? existingRule.startDate,
endDate: data.endDate ?? existingRule.endDate,
});
const updatedRule = await repo.updatePricingRule(ruleId, mergedRule);
await repo.createPriceHistory({
configurationId: configuration.id,
vehicleId: id,
source: mergedRule.ruleType === 'MANUAL_OVERRIDE' ? 'MANUAL_OVERRIDE' : 'RULE_UPDATED',
changedByEmployeeId: employeeId,
previousDailyRate: existingRule.dailyRate,
nextDailyRate: updatedRule.dailyRate,
previousWeeklyRate: existingRule.weeklyRate,
nextWeeklyRate: updatedRule.weeklyRate,
note: `Updated pricing rule: ${updatedRule.name}`,
effectiveFrom: updatedRule.startDate,
});
return getVehiclePricing(id, companyId);
}
async function deleteVehiclePricingRule(id, companyId, ruleId, employeeId) {
const { configuration, persistenceAvailable } = await ensurePricingConfigurationForVehicle(id, companyId);
if (!persistenceAvailable) {
throw new errors_1.AppError('Vehicle pricing tables are not available yet. Run the latest database migration, then retry.', 503, 'pricing_migration_required');
}
const existingRule = await repo.findPricingRule(ruleId, configuration.id);
if (!existingRule)
throw new errors_1.NotFoundError('Pricing rule not found');
await repo.deletePricingRule(ruleId, configuration.id);
await repo.createPriceHistory({
configurationId: configuration.id,
vehicleId: id,
source: existingRule.ruleType === 'MANUAL_OVERRIDE' ? 'MANUAL_OVERRIDE' : 'RULE_DELETED',
changedByEmployeeId: employeeId,
previousDailyRate: existingRule.dailyRate,
nextDailyRate: configuration.baseDailyRate ?? null,
previousWeeklyRate: existingRule.weeklyRate,
nextWeeklyRate: configuration.weeklyRate ?? null,
note: `Deleted pricing rule: ${existingRule.name}`,
effectiveFrom: existingRule.startDate,
});
return getVehiclePricing(id, companyId);
}
//# sourceMappingURL=vehicle.service.js.map
File diff suppressed because one or more lines are too long