fix the booking and contract
Build & Push / Pipeline Tests (push) Failing after 1m31s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 55s
Test / API Unit Tests (push) Successful in 1m8s
Test / Homepage Unit Tests (push) Successful in 47s
Test / Carplace Unit Tests (push) Successful in 44s
Test / Admin Unit Tests (push) Successful in 43s
Test / Dashboard Unit Tests (push) Failing after 46s
Test / API Integration Tests (push) Successful in 1m7s
Build & Push / Pipeline Tests (push) Failing after 1m31s
Build & Push / Build & Push Docker Image (push) Has been skipped
Test / Type Check (all packages) (push) Successful in 55s
Test / API Unit Tests (push) Successful in 1m8s
Test / Homepage Unit Tests (push) Successful in 47s
Test / Carplace Unit Tests (push) Successful in 44s
Test / Admin Unit Tests (push) Successful in 43s
Test / Dashboard Unit Tests (push) Failing after 46s
Test / API Integration Tests (push) Successful in 1m7s
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { prisma } from '../../lib/prisma'
|
||||
import * as repo from './reservation.repo'
|
||||
import { parseReservationExtras, serializeContractFields } from './reservation.presenter'
|
||||
|
||||
export function formatDocumentNumber(prefix: string, sequence: number): string {
|
||||
return `${prefix}-${String(sequence).padStart(6, '0')}`
|
||||
@@ -117,6 +118,7 @@ export async function getContract(id: string, companyId: string) {
|
||||
const extras = reservation.extras && typeof reservation.extras === 'object' && !Array.isArray(reservation.extras)
|
||||
? (reservation.extras as Record<string, unknown>)
|
||||
: {}
|
||||
const contractFields = serializeContractFields(extras.contractFields)
|
||||
|
||||
const invoiceLineItems = buildReservationInvoiceLineItems({
|
||||
dailyRate: reservation.dailyRate,
|
||||
@@ -143,6 +145,7 @@ export async function getContract(id: string, companyId: string) {
|
||||
|
||||
const checkInInspection = reservation.inspections.find((i: any) => i.type === 'CHECKIN') ?? null
|
||||
const checkOutInspection = reservation.inspections.find((i: any) => i.type === 'CHECKOUT') ?? null
|
||||
const customerAddress = parseReservationExtras(reservation.customer.address)
|
||||
|
||||
return {
|
||||
reservationId: reservation.id,
|
||||
@@ -153,6 +156,7 @@ export async function getContract(id: string, companyId: string) {
|
||||
status: reservation.status,
|
||||
paymentStatus: reservation.paymentStatus,
|
||||
paymentMode: typeof extras.paymentMode === 'string' ? extras.paymentMode : null,
|
||||
contractFields,
|
||||
notes: reservation.notes,
|
||||
company: {
|
||||
name: reservation.company.brand?.displayName ?? reservation.company.name,
|
||||
@@ -172,6 +176,9 @@ export async function getContract(id: string, companyId: string) {
|
||||
email: reservation.customer.email,
|
||||
phone: reservation.customer.phone,
|
||||
dateOfBirth: reservation.customer.dateOfBirth,
|
||||
address: typeof customerAddress.fullAddress === 'string' ? customerAddress.fullAddress : null,
|
||||
identityDocumentNumber: typeof customerAddress.identityDocumentNumber === 'string' ? customerAddress.identityDocumentNumber : null,
|
||||
internationalLicenseNumber: typeof customerAddress.internationalLicenseNumber === 'string' ? customerAddress.internationalLicenseNumber : null,
|
||||
driverLicense: reservation.customer.driverLicense,
|
||||
licenseCountry: reservation.customer.licenseCountry,
|
||||
licenseCategory: reservation.customer.licenseCategory,
|
||||
@@ -189,6 +196,8 @@ export async function getContract(id: string, companyId: string) {
|
||||
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,
|
||||
spareWheel: typeof extras.spareWheel === 'boolean' ? extras.spareWheel : false,
|
||||
radioCd: typeof extras.radioCd === 'boolean' ? extras.radioCd : false,
|
||||
},
|
||||
rentalPeriod: {
|
||||
startDate: reservation.startDate, endDate: reservation.endDate, totalDays: reservation.totalDays,
|
||||
@@ -229,30 +238,19 @@ export async function getContract(id: string, companyId: string) {
|
||||
|
||||
export async function getBilling(id: string, companyId: string) {
|
||||
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: any) => ({
|
||||
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: any) => d.totalCharge > 0)
|
||||
.map((d: any) => ({
|
||||
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
|
||||
const lineItems = buildReservationInvoiceLineItems({
|
||||
dailyRate: reservation.dailyRate,
|
||||
totalDays: reservation.totalDays,
|
||||
discountAmount: reservation.discountAmount,
|
||||
depositAmount: reservation.depositAmount,
|
||||
pricingRulesApplied: Array.isArray(reservation.pricingRulesApplied)
|
||||
? (reservation.pricingRulesApplied as any)
|
||||
: [],
|
||||
insurances: reservation.insurances,
|
||||
additionalDrivers: reservation.additionalDrivers,
|
||||
vehicle: reservation.vehicle,
|
||||
})
|
||||
const grandTotal = lineItems.reduce((s, i) => s + i.total, 0)
|
||||
|
||||
return {
|
||||
lineItems,
|
||||
|
||||
@@ -24,9 +24,18 @@ vi.mock('./reservation.repo', () => ({
|
||||
findConflict: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('./reservation.document.service', () => ({
|
||||
ensureReservationDocumentNumbers: vi.fn().mockResolvedValue({
|
||||
id: 'reservation_1',
|
||||
contractNumber: 'CNT-000001',
|
||||
invoiceNumber: 'INV-000001',
|
||||
}),
|
||||
}))
|
||||
|
||||
import { AppError } from '../../http/errors'
|
||||
import { prisma } from '../../lib/prisma'
|
||||
import * as repo from './reservation.repo'
|
||||
import { ensureReservationDocumentNumbers } from './reservation.document.service'
|
||||
import { cancelReservation, checkinReservation, confirmReservation, extendReservation } from './reservation.lifecycle.service'
|
||||
|
||||
const validLicenseReservation = {
|
||||
@@ -57,8 +66,14 @@ describe('reservation.lifecycle.service', () => {
|
||||
vi.mocked(repo.findCompanyWithBrand).mockResolvedValue({ name: 'Atlas Cars', brand: { displayName: 'Atlas', defaultLocale: 'en' } } as never)
|
||||
vi.mocked(repo.findVehicle).mockResolvedValue({ year: 2024, make: 'Toyota', model: 'Yaris' } as never)
|
||||
|
||||
await expect(confirmReservation('reservation_1', 'company_1')).resolves.toEqual({ id: 'reservation_1', status: 'CONFIRMED' })
|
||||
await expect(confirmReservation('reservation_1', 'company_1')).resolves.toEqual({
|
||||
id: 'reservation_1',
|
||||
status: 'CONFIRMED',
|
||||
contractNumber: 'CNT-000001',
|
||||
invoiceNumber: 'INV-000001',
|
||||
})
|
||||
expect(repo.updateById).toHaveBeenCalledWith('reservation_1', { status: 'CONFIRMED' })
|
||||
expect(ensureReservationDocumentNumbers).toHaveBeenCalledWith('company_1', 'reservation_1')
|
||||
expect(repo.updateVehicleStatus).toHaveBeenCalledWith('vehicle_1', 'RESERVED')
|
||||
})
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { sendNotification, sendTransactionalEmail } from '../../services/notific
|
||||
import { reviewRequestEmail, type Lang } from '../../lib/emailTranslations'
|
||||
import { coerceNotificationLocale } from '../../services/notificationLocalizationService'
|
||||
import { buildBookingRequestProgress, buildReservationWorkflow, parseReservationExtras, serializeReservationForDashboard } from './reservation.presenter'
|
||||
import { ensureReservationDocumentNumbers } from './reservation.document.service'
|
||||
import * as repo from './reservation.repo'
|
||||
|
||||
function buildPickupAddress(value: unknown) {
|
||||
@@ -49,6 +50,7 @@ export async function confirmReservation(id: string, companyId: string) {
|
||||
if (reservation.status !== 'DRAFT') throw new AppError('Only DRAFT reservations can be confirmed', 400, 'invalid_status')
|
||||
|
||||
const updated = await repo.updateById(id, { status: 'CONFIRMED' })
|
||||
const documentNumbers = await ensureReservationDocumentNumbers(companyId, id)
|
||||
await repo.updateVehicleStatus(reservation.vehicleId, 'RESERVED')
|
||||
|
||||
const [customer, company, vehicle] = await Promise.all([
|
||||
@@ -134,7 +136,7 @@ export async function confirmReservation(id: string, companyId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
return updated
|
||||
return { ...updated, ...documentNumbers }
|
||||
}
|
||||
|
||||
export async function checkinReservation(id: string, companyId: string, mileage?: number) {
|
||||
|
||||
@@ -10,6 +10,22 @@ export function normalizeOptionalString(value: string | null | undefined): strin
|
||||
return trimmed || null
|
||||
}
|
||||
|
||||
const BLOCKED_CONTRACT_FIELD_KEYS = new Set([
|
||||
'companyName',
|
||||
'companyAddress',
|
||||
'contractCity',
|
||||
'deliveryPlace',
|
||||
'returnPlace',
|
||||
])
|
||||
|
||||
export function serializeContractFields(value: unknown): Record<string, string> {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
||||
return Object.fromEntries(
|
||||
Object.entries(value)
|
||||
.filter(([key, fieldValue]) => !BLOCKED_CONTRACT_FIELD_KEYS.has(key) && typeof fieldValue === 'string'),
|
||||
) as Record<string, string>
|
||||
}
|
||||
|
||||
function readAddressField(address: unknown, key: string): string | null {
|
||||
const extras = parseReservationExtras(address)
|
||||
const value = extras[key]
|
||||
@@ -125,10 +141,14 @@ export function serializeReservationForDashboard<T extends {
|
||||
} | null
|
||||
}>(reservation: T): T & {
|
||||
paymentMode: string | null
|
||||
spareWheel: boolean
|
||||
radioCd: boolean
|
||||
contractFields: Record<string, string>
|
||||
workflow: ReturnType<typeof buildReservationWorkflow>
|
||||
bookingRequest: ReturnType<typeof buildBookingRequestProgress>
|
||||
} {
|
||||
const extras = parseReservationExtras(reservation.extras)
|
||||
const contractFields = serializeContractFields(extras.contractFields)
|
||||
const customer =
|
||||
reservation.customer
|
||||
? {
|
||||
@@ -143,6 +163,9 @@ export function serializeReservationForDashboard<T extends {
|
||||
...reservation,
|
||||
...(customer !== undefined ? { customer } : {}),
|
||||
paymentMode: typeof extras.paymentMode === 'string' ? extras.paymentMode : null,
|
||||
spareWheel: typeof extras.spareWheel === 'boolean' ? extras.spareWheel : false,
|
||||
radioCd: typeof extras.radioCd === 'boolean' ? extras.radioCd : false,
|
||||
contractFields,
|
||||
workflow: buildReservationWorkflow(reservation),
|
||||
bookingRequest: buildBookingRequestProgress({ ...reservation, customer }),
|
||||
}
|
||||
|
||||
@@ -42,11 +42,16 @@ export const createSchema = z.object({
|
||||
promoCodeUsed: z.string().optional(),
|
||||
depositAmount: z.number().int().min(0).default(0),
|
||||
paymentMode: z.string().max(50).optional(),
|
||||
spareWheel: z.boolean().optional(),
|
||||
radioCd: z.boolean().optional(),
|
||||
notes: z.string().optional(),
|
||||
contractFields: z.record(z.string().max(120), z.string().max(500).nullable()).optional(),
|
||||
selectedInsurancePolicyIds: z.array(z.string()).default([]),
|
||||
additionalDrivers: z.array(additionalDriverSchema).default([]),
|
||||
})
|
||||
|
||||
export const contractFieldsSchema = z.record(z.string().max(120), z.string().max(500).nullable()).optional()
|
||||
|
||||
export const updateSchema = z.object({
|
||||
startDate: z.string().datetime().optional(),
|
||||
endDate: z.string().datetime().optional(),
|
||||
@@ -55,6 +60,9 @@ export const updateSchema = z.object({
|
||||
depositAmount: z.number().int().min(0).optional(),
|
||||
notes: z.string().optional().nullable(),
|
||||
paymentMode: z.string().max(50).optional().nullable(),
|
||||
spareWheel: z.boolean().optional().nullable(),
|
||||
radioCd: z.boolean().optional().nullable(),
|
||||
contractFields: contractFieldsSchema,
|
||||
damageChargeAmount: z.number().int().min(0).optional(),
|
||||
damageChargeNote: z.string().optional().nullable(),
|
||||
})
|
||||
|
||||
@@ -4,12 +4,24 @@ import { validateAndFlagLicense } from '../../services/licenseValidationService'
|
||||
import { applyPricingRules, calculateUpdatedInsuranceCharge, calculateUpdatedAdditionalDriverCharge } from './reservation.pricing.service'
|
||||
import { applyInsurancesToReservation } from './reservation.insurance.service'
|
||||
import { applyAdditionalDriversToReservation } from './reservation.additional-driver.service'
|
||||
import { parseReservationExtras, normalizeOptionalString, serializeReservationForDashboard, buildReservationWorkflow } from './reservation.presenter'
|
||||
import { parseReservationExtras, normalizeOptionalString, serializeContractFields, serializeReservationForDashboard, buildReservationWorkflow } from './reservation.presenter'
|
||||
import * as repo from './reservation.repo'
|
||||
|
||||
const RESERVATION_STATUSES = ['DRAFT', 'CONFIRMED', 'ACTIVE', 'COMPLETED', 'CANCELLED', 'NO_SHOW'] as const
|
||||
const BOOKING_SOURCES = ['DASHBOARD', 'PUBLIC_SITE', 'CARPLACE', 'API'] as const
|
||||
|
||||
function normalizeContractFields(value: Record<string, string | null> | undefined) {
|
||||
if (!value) return null
|
||||
const fields: Record<string, string> = {}
|
||||
for (const [key, raw] of Object.entries(value)) {
|
||||
const normalizedKey = key.trim()
|
||||
const normalizedValue = normalizeOptionalString(raw)
|
||||
if (normalizedKey && normalizedValue) fields[normalizedKey] = normalizedValue
|
||||
}
|
||||
const allowedFields = serializeContractFields(fields)
|
||||
return Object.keys(allowedFields).length > 0 ? allowedFields : null
|
||||
}
|
||||
|
||||
export async function listReservations(companyId: string, query: {
|
||||
status?: string; vehicleId?: string; source?: string
|
||||
search?: string
|
||||
@@ -61,6 +73,8 @@ export async 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
|
||||
spareWheel?: boolean; radioCd?: boolean
|
||||
contractFields?: Record<string, string | null>
|
||||
selectedInsurancePolicyIds?: string[]; additionalDrivers?: any[]
|
||||
}) {
|
||||
const vehicle = await repo.findVehicle(body.vehicleId, companyId)
|
||||
@@ -93,6 +107,7 @@ export async function createReservation(companyId: string, body: {
|
||||
const baseAmount = vehicle.dailyRate * totalDays
|
||||
const { applied, total: pricingTotal } = await applyPricingRules(companyId, body.customerId, additionalDriversList, vehicle.dailyRate, totalDays)
|
||||
const totalAmount = baseAmount - discountAmount + pricingTotal + depositAmount
|
||||
const contractFields = normalizeContractFields(body.contractFields)
|
||||
|
||||
const reservation = await repo.create({
|
||||
companyId,
|
||||
@@ -111,7 +126,12 @@ export async function createReservation(companyId: string, body: {
|
||||
totalAmount,
|
||||
depositAmount,
|
||||
notes: body.notes ?? null,
|
||||
extras: body.paymentMode ? { paymentMode: body.paymentMode } : undefined,
|
||||
extras: {
|
||||
...(body.paymentMode ? { paymentMode: body.paymentMode } : {}),
|
||||
...(body.spareWheel !== undefined ? { spareWheel: body.spareWheel } : {}),
|
||||
...(body.radioCd !== undefined ? { radioCd: body.radioCd } : {}),
|
||||
...(contractFields ? { contractFields } : {}),
|
||||
},
|
||||
pricingRulesApplied: applied,
|
||||
pricingRulesTotal: pricingTotal,
|
||||
})
|
||||
@@ -133,6 +153,8 @@ export async 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
|
||||
spareWheel?: boolean | null; radioCd?: boolean | null
|
||||
contractFields?: Record<string, string | null>
|
||||
}) {
|
||||
const reservation = await repo.findByIdWithRelations(id, companyId)
|
||||
|
||||
@@ -142,7 +164,7 @@ export async function updateReservation(id: string, companyId: string, body: {
|
||||
|
||||
const workflow = buildReservationWorkflow(reservation)
|
||||
const requested = Object.keys(body).filter((k) => body[k as keyof typeof body] !== undefined)
|
||||
const bookingFields = ['startDate', 'endDate', 'pickupLocation', 'returnLocation', 'depositAmount', 'notes', 'paymentMode']
|
||||
const bookingFields = ['startDate', 'endDate', 'pickupLocation', 'returnLocation', 'depositAmount', 'notes', 'paymentMode', 'spareWheel', 'radioCd', 'contractFields']
|
||||
const returnFields = ['returnLocation', 'damageChargeAmount', 'damageChargeNote']
|
||||
|
||||
if (!workflow.coreEditable && !workflow.returnEditable) {
|
||||
@@ -193,6 +215,13 @@ export async function updateReservation(id: string, companyId: string, body: {
|
||||
if (next) extras.paymentMode = next
|
||||
else delete extras.paymentMode
|
||||
}
|
||||
if (body.spareWheel !== undefined) extras.spareWheel = Boolean(body.spareWheel)
|
||||
if (body.radioCd !== undefined) extras.radioCd = Boolean(body.radioCd)
|
||||
if (body.contractFields !== undefined) {
|
||||
const contractFields = normalizeContractFields(body.contractFields)
|
||||
if (contractFields) extras.contractFields = contractFields
|
||||
else delete extras.contractFields
|
||||
}
|
||||
|
||||
await prisma.$transaction([
|
||||
prisma.reservation.update({
|
||||
|
||||
@@ -42,6 +42,14 @@ vi.mock('./reservation.additional-driver.service', () => ({
|
||||
approveAdditionalDriver: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('./reservation.document.service', () => ({
|
||||
ensureReservationDocumentNumbers: vi.fn().mockResolvedValue({
|
||||
id: 'reservation-1',
|
||||
contractNumber: 'CNT-000001',
|
||||
invoiceNumber: 'INV-000001',
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../../services/licenseValidationService', () => ({
|
||||
validateAndFlagLicense: vi.fn().mockResolvedValue(undefined),
|
||||
validateLicense: vi.fn(),
|
||||
@@ -63,6 +71,7 @@ vi.mock('../../lib/prisma', () => ({
|
||||
vi.mock('./reservation.presenter', () => ({
|
||||
serializeReservationForDashboard: vi.fn((r) => r),
|
||||
parseReservationExtras: vi.fn(() => ({})),
|
||||
serializeContractFields: vi.fn((fields) => fields),
|
||||
buildReservationWorkflow: vi.fn(),
|
||||
normalizeOptionalString: vi.fn((s) => s),
|
||||
}))
|
||||
@@ -74,6 +83,7 @@ import * as additionalDriverService from './reservation.additional-driver.servic
|
||||
import { validateLicense } from '../../services/licenseValidationService'
|
||||
import { sendNotification } from '../../services/notificationService'
|
||||
import { buildReservationWorkflow } from './reservation.presenter'
|
||||
import { ensureReservationDocumentNumbers } from './reservation.document.service'
|
||||
import { createReservation, listReservations } from './reservation.service'
|
||||
import { confirmReservation, checkinReservation, checkoutReservation, closeReservation } from './reservation.lifecycle.service'
|
||||
import { approveAdditionalDriver } from './reservation.additional-driver.service'
|
||||
@@ -177,6 +187,40 @@ describe('createReservation', () => {
|
||||
expect(result.id).toBe(RES_ID)
|
||||
})
|
||||
|
||||
it('stores contract fields from the booking create payload', async () => {
|
||||
vi.mocked(repo.findVehicle).mockResolvedValue(makeVehicle())
|
||||
vi.mocked(repo.findCustomer).mockResolvedValue({ id: 'customer-1' } as any)
|
||||
vi.mocked(repo.findConflict).mockResolvedValue(null)
|
||||
vi.mocked(repo.findActiveOffer).mockResolvedValue(null)
|
||||
vi.mocked(pricingService.applyPricingRules).mockResolvedValue({ applied: [], total: 0 })
|
||||
vi.mocked(repo.create).mockResolvedValue(makeReservation() as any)
|
||||
|
||||
await createReservation(COMPANY, {
|
||||
vehicleId: 'vehicle-1',
|
||||
customerId: 'customer-1',
|
||||
startDate: '2025-06-01T00:00:00.000Z',
|
||||
endDate: '2025-06-04T00:00:00.000Z',
|
||||
contractFields: {
|
||||
driverFirstName: 'Sara',
|
||||
driverCin: 'AB123',
|
||||
driverPassport: null,
|
||||
vehicleDeparture: '01/06/2025 00:00',
|
||||
},
|
||||
selectedInsurancePolicyIds: [],
|
||||
additionalDrivers: [],
|
||||
})
|
||||
|
||||
expect(repo.create).toHaveBeenCalledWith(expect.objectContaining({
|
||||
extras: expect.objectContaining({
|
||||
contractFields: {
|
||||
driverFirstName: 'Sara',
|
||||
driverCin: 'AB123',
|
||||
vehicleDeparture: '01/06/2025 00:00',
|
||||
},
|
||||
}),
|
||||
}))
|
||||
})
|
||||
|
||||
it('throws conflict error when vehicle is unavailable', async () => {
|
||||
vi.mocked(repo.findVehicle).mockResolvedValue(makeVehicle())
|
||||
vi.mocked(repo.findCustomer).mockResolvedValue({ id: 'customer-1' } as any)
|
||||
@@ -216,6 +260,7 @@ describe('confirmReservation', () => {
|
||||
const result = await confirmReservation(RES_ID, COMPANY)
|
||||
|
||||
expect(repo.updateById).toHaveBeenCalledWith(RES_ID, { status: 'CONFIRMED' })
|
||||
expect(ensureReservationDocumentNumbers).toHaveBeenCalledWith(COMPANY, RES_ID)
|
||||
expect(sendNotification).toHaveBeenCalledWith(expect.objectContaining({
|
||||
templateKey: 'booking.confirmed',
|
||||
templateVariables: expect.objectContaining({
|
||||
@@ -231,6 +276,8 @@ describe('confirmReservation', () => {
|
||||
}),
|
||||
}))
|
||||
expect((result as any).status).toBe('CONFIRMED')
|
||||
expect((result as any).contractNumber).toBe('CNT-000001')
|
||||
expect((result as any).invoiceNumber).toBe('INV-000001')
|
||||
})
|
||||
|
||||
it('rejects non-DRAFT reservation', async () => {
|
||||
|
||||
Reference in New Issue
Block a user