import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../../../infrastructure/database/database.service';
@Injectable()
export class ActiveMenuService {
constructor(private readonly database: DatabaseService) {}
async list(input: { organizationId:string; locationId:string; locale:string; channel?:string; placeId?:string; operationalUnitId?:string; stationId?:string; at?:string }) {
if (input.stationId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(input.stationId)) {
throw new Error('menu.station_id_invalid');
}
const at = input.at ? new Date(input.at) : new Date();
if (Number.isNaN(at.getTime())) throw new Error('menu.at_invalid');
return this.database.query(
`select p.id, coalesce(pt.name,p.name) as name,
coalesce(ct.name,c.name) as category_name,
pp.amount_minor, pp.currency_code,
coalesce(rule.available,ma.available,true) as available,
coalesce(rule.reason_code,ma.reason_code) as reason_code,
coalesce(jsonb_agg(distinct jsonb_build_object('code',pap.allergen_code,'relation',pap.relation,'name',at.display_name)) filter (where pap.allergen_code is not null),'[]'::jsonb) as allergens
from products p
left join product_translations pt on pt.product_id=p.id and pt.locale=$3
left join menu_categories c on c.id=p.category_id
left join menu_category_translations ct on ct.category_id=c.id and ct.locale=$3
left join lateral (
select amount_minor,currency_code from product_prices pp2
where pp2.product_id=p.id and pp2.location_id=$2 and pp2.channel=$4
and pp2.valid_from<=now() and (pp2.valid_until is null or pp2.valid_until>now())
order by pp2.valid_from desc limit 1
) pp on true
left join menu_availability ma on ma.product_id=p.id and ma.location_id=$2
left join lateral (
select mar.available,mar.reason_code from menu_availability_rules mar
where mar.product_id=p.id and mar.organization_id=$1 and mar.location_id=$2
and (mar.channel is null or mar.channel=$4)
and (mar.place_id is null or mar.place_id=$5)
and (mar.operational_unit_id is null or mar.operational_unit_id=$6)
and (mar.station_id is null or mar.station_id=$7)
and mar.starts_at<=($8::timestamptz)
and (mar.ends_at is null or mar.ends_at>($8::timestamptz))
order by mar.priority desc,(mar.place_id is not null) desc,(mar.operational_unit_id is not null) desc,(mar.station_id is not null) desc,mar.starts_at desc
limit 1
) rule on true
left join product_allergen_projection pap on pap.product_id=p.id
left join allergen_translations at on at.allergen_code=pap.allergen_code and at.locale=$3
where p.organization_id=$1 and p.status='active'
group by p.id,pt.name,c.name,ct.name,pp.amount_minor,pp.currency_code,rule.available,rule.reason_code,ma.available,ma.reason_code
order by lower(coalesce(ct.name,c.name)),lower(coalesce(pt.name,p.name))`,
[input.organizationId,input.locationId,input.locale,input.channel??'onsite',input.placeId??null,input.operationalUnitId??null,input.stationId??null,at.toISOString()],
);
}
}
