import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../../../infrastructure/database/database.service';
/**
* Read-only hotel F&B evidence composed from existing PMS, fulfillment and
* settlement records. PMS and folio truth remain external; this read model
* deliberately projects status/count evidence only and never invents guest
* or room facts.
*/
@Injectable()
export class HotelFnbOverviewService {
constructor(private readonly database: DatabaseService) {}
async overview(input: { organizationId: string; locationId: string }) {
const [pmsConnections, roomserviceOrders, roomserviceQueue, folioCharges, conflicts] =
await Promise.all([
this.database.query<any>(
`select id,provider,display_name,status,last_health_ok,last_health_check_at,
last_successful_sync_at,last_error_code
from integration_connections
where organization_id=$1 and category='pms'
and (location_id is null or location_id=$2)
order by display_name`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select o.status,count(*)::int as order_count,
coalesce(sum(oi.quantity),0)::text as item_quantity
from orders o
left join order_items oi on oi.order_id=o.id and oi.status='active'
where o.organization_id=$1 and o.location_id=$2
and o.fulfillment_mode='roomservice'
and o.status in ('draft','submitted')
group by o.status
order by o.status`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select o.id as order_id,o.status,o.order_channel,
o.fulfillment_target_at,o.place_id,o.operational_unit_id,
count(oi.id)::int as item_count,
coalesce(sum(oi.quantity),0)::text as item_quantity
from orders o
left join order_items oi on oi.order_id=o.id and oi.status='active'
where o.organization_id=$1 and o.location_id=$2
and o.fulfillment_mode='roomservice'
and o.status in ('draft','submitted')
group by o.id,o.status,o.order_channel,o.fulfillment_target_at,
o.place_id,o.operational_unit_id,o.created_at
order by o.fulfillment_target_at nulls last,o.created_at
limit 100`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select status,count(*)::int as charge_count
from pms_posted_charges
where organization_id=$1 and location_id=$2
group by status
order by status`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select c.id,c.conflict_type,c.severity,c.summary,c.created_at
from external_integration_conflicts c
join integration_connections i on i.id=c.integration_connection_id
where c.organization_id=$1 and c.location_id=$2
and i.category='pms' and c.status='open'
order by case c.severity when 'critical' then 0 when 'high' then 1 else 2 end,
c.created_at`,
[input.organizationId, input.locationId],
),
]);
return { pmsConnections, roomserviceOrders, roomserviceQueue, folioCharges, conflicts };
}
}
