import { Injectable } from '@nestjs/common';
import {
HotelGuestContext,
HotelGuestContextInput,
HotelGuestContextService,
} from '../hotel-guest-context/hotel-guest-context.service';
import {
MultiOutletNetworkService,
OutletNetworkInput,
OutletNetworkProjection,
} from '../multi-outlet-network/multi-outlet-network.service';
import {
PmsContext,
PmsContextInput,
PmsContextProviderService,
} from '../pms-context/pms-context-provider.service';
import {
RoomserviceComposition,
RoomserviceCompositionInput,
RoomserviceCompositionService,
} from '../roomservice-composition/roomservice-composition.service';
import {
SettlementAllocation,
SettlementAllocationService,
SettlementInput,
} from '../entitlement-settlement/settlement-allocation.service';
import { assertHotelFnbCompositionInput } from '../hotel-fnb-input-validation';
export interface HotelFnbCompositionInput {
pms: PmsContextInput;
guest: HotelGuestContextInput;
outlet: OutletNetworkInput;
roomservice?: RoomserviceCompositionInput;
settlement?: SettlementInput;
}
export interface HotelFnbComposition {
pms: PmsContext;
guest: HotelGuestContext;
outlet: OutletNetworkProjection;
roomservice?: RoomserviceComposition;
settlement?: SettlementAllocation[];
ready: boolean;
blockers: string[];
}
/**
* Composes the hotel F&B context from existing provider, privacy, outlet and
* fulfillment primitives. It deliberately owns no PMS, guest, payment or
* folio truth of its own.
*/
@Injectable()
export class HotelFnbCompositionService {
private readonly pmsContext = new PmsContextProviderService();
private readonly guestContext = new HotelGuestContextService();
private readonly outletNetwork = new MultiOutletNetworkService();
private readonly roomservice = new RoomserviceCompositionService();
private readonly settlement = new SettlementAllocationService();
compose(input: HotelFnbCompositionInput): HotelFnbComposition {
assertHotelFnbCompositionInput(input);
const pms = this.pmsContext.normalize(input.pms);
const guest = this.guestContext.project(input.guest);
if (guest.stayId !== pms.stayId) throw new Error('hotel_fnb_context.stay_mismatch');
const outlet = this.outletNetwork.project(input.outlet);
if (outlet.groupId !== pms.hotelId) throw new Error('hotel_fnb_context.hotel_mismatch');
const roomservice = input.roomservice ? this.roomservice.compose(input.roomservice) : undefined;
const settlement = input.settlement ? this.settlement.validate(input.settlement) : undefined;
if (settlement?.some((allocation) => allocation.source === 'room_folio') && !guest.roomChargeAllowed) {
throw new Error('hotel_fnb_context.room_charge_not_allowed');
}
const roomNumbers = [pms.roomNumber, guest.roomNumber, roomservice?.roomNumber].filter(
(value): value is string => Boolean(value),
);
if (new Set(roomNumbers).size > 1) throw new Error('hotel_fnb_context.room_mismatch');
const blockers = [
pms.availability === 'unavailable' ? 'pms_context_unavailable' : undefined,
pms.status === 'departed' ? 'stay_not_active' : undefined,
...(roomservice?.blockers ?? []),
].filter((value): value is string => value !== undefined);
return {pms, guest, outlet, roomservice, settlement, ready: blockers.length === 0, blockers};
}
}
