import { DomainConflictError } from '../../../common/errors/domain-conflict.error';
type MenuInput = Record<string, any>;
function object(input: unknown, code: string): asserts input is MenuInput {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new DomainConflictError(code);
}
}
function text(value: unknown, max: number, code: string): asserts value is string {
if (typeof value !== 'string' || value.trim().length === 0 || value.length > max) {
throw new DomainConflictError(code);
}
}
function optionalText(value: unknown, max: number, code: string): void {
if (value !== undefined && value !== null) text(value, max, code);
}
function scope(input: unknown, code: string): asserts input is MenuInput {
object(input, code);
text(input.organizationId, 200, code);
}
function id(value: unknown, code: string): void {
text(value, 200, code);
}
function safeInteger(value: unknown, code: string, min?: number, max?: number): void {
if (!Number.isSafeInteger(value) || (min !== undefined && Number(value) < min) || (max !== undefined && Number(value) > max)) {
throw new DomainConflictError(code);
}
}
function optionalSafeInteger(value: unknown, code: string, min?: number, max?: number): void {
if (value !== undefined && value !== null) safeInteger(value, code, min, max);
}
function list(value: unknown, max: number, code: string): asserts value is unknown[] {
if (!Array.isArray(value) || value.length > max) throw new DomainConflictError(code);
}
function optionalId(value: unknown, code: string): void {
if (value !== undefined && value !== null) id(value, code);
}
function optionalDate(value: unknown, code: string): Date | undefined {
if (value === undefined || value === null) return undefined;
text(value, 100, code);
const date = new Date(value);
if (Number.isNaN(date.getTime())) throw new DomainConflictError('menu.availability_window_invalid');
return date;
}
export function assertCreateIngredientInput(input: unknown): asserts input is MenuInput {
const code = 'menu.input_invalid';
scope(input, code);
text(input.name, 200, code);
text(input.baseUnitCode, 32, code);
optionalText(input.sku, 100, code);
}
export function assertIngredientAllergensInput(input: unknown): asserts input is MenuInput {
const code = 'menu.input_invalid';
scope(input, code);
id(input.ingredientId, code);
list(input.allergens, 64, code);
const seen = new Set<string>();
for (const allergen of input.allergens) {
object(allergen, code);
text(allergen.code, 64, code);
if (allergen.relation !== 'contains' && allergen.relation !== 'may_contain') {
throw new DomainConflictError(code);
}
if (seen.has(allergen.code)) throw new DomainConflictError(code);
seen.add(allergen.code);
}
}
export function assertCreateProductInput(input: unknown): asserts input is MenuInput {
const code = 'menu.input_invalid';
scope(input, code);
text(input.name, 200, code);
optionalText(input.categoryId, 200, code);
optionalText(input.recipeId, 200, code);
optionalText(input.sku, 100, code);
if (input.productType !== undefined && !['dish', 'drink', 'retail', 'service', 'other'].includes(input.productType)) {
throw new DomainConflictError(code);
}
}
export function assertSetPriceInput(input: unknown): asserts input is MenuInput {
const code = 'menu.input_invalid';
scope(input, code);
id(input.productId, code);
text(input.locationId, 200, code);
if (typeof input.currencyCode !== 'string' || !/^[A-Z]{3}$/.test(input.currencyCode)) {
throw new DomainConflictError(code);
}
safeInteger(input.amountMinor, code, 0);
optionalText(input.channel, 64, code);
}
export function assertSetAvailabilityInput(input: unknown): asserts input is MenuInput {
const code = 'menu.input_invalid';
scope(input, code);
id(input.productId, code);
text(input.locationId, 200, code);
if (typeof input.available !== 'boolean') throw new DomainConflictError(code);
optionalText(input.reasonCode, 100, code);
optionalText(input.userId, 200, code);
}
export function assertAvailabilityRuleInput(input: unknown): asserts input is MenuInput {
const code = 'menu.input_invalid';
scope(input, code);
id(input.productId, code);
id(input.locationId, code);
optionalId(input.placeId, code);
optionalId(input.operationalUnitId, code);
optionalId(input.stationId, code);
optionalText(input.channel, 64, code);
optionalDate(input.startsAt, code);
const endsAt = optionalDate(input.endsAt, code);
const startsAt = input.startsAt === undefined || input.startsAt === null ? new Date() : new Date(input.startsAt);
if (endsAt && endsAt <= startsAt) throw new DomainConflictError('menu.availability_window_invalid');
if (typeof input.available !== 'boolean') throw new DomainConflictError(code);
optionalText(input.reasonCode, 100, code);
optionalSafeInteger(input.priority, code, 0, 1000);
optionalText(input.userId, 200, code);
}
export function assertModifierGroupInput(input: unknown): asserts input is MenuInput {
const code = 'modifier.input_invalid';
scope(input, code);
text(input.name, 200, code);
safeInteger(input.minSelect, code, 0, 1000);
optionalSafeInteger(input.maxSelect, code, 0, 1000);
if (input.maxSelect !== undefined && input.maxSelect !== null && input.maxSelect < input.minSelect) {
throw new DomainConflictError(code);
}
}
export function assertModifierInput(input: unknown): asserts input is MenuInput {
const code = 'modifier.input_invalid';
scope(input, code);
text(input.name, 200, code);
optionalText(input.recipeId, 200, code);
}
export function assertAttachModifierInput(input: unknown): asserts input is MenuInput {
const code = 'modifier.input_invalid';
scope(input, code);
id(input.modifierGroupId, code);
id(input.modifierId, code);
safeInteger(input.priceDeltaMinor, code);
safeInteger(input.sortOrder, code, 0, 10000);
}
export function assertAttachModifierGroupInput(input: unknown): asserts input is MenuInput {
const code = 'modifier.input_invalid';
scope(input, code);
id(input.productId, code);
id(input.modifierGroupId, code);
safeInteger(input.sortOrder, code, 0, 10000);
}
