import { DomainConflictError } from '../../../common/errors/domain-conflict.error';
type CashInput = Record<string, unknown>;
function assertObject(input: unknown, code: string): asserts input is CashInput {
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new DomainConflictError(code);
}
function assertText(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 assertOptionalText(value: unknown, max: number, code: string): void {
if (value !== undefined && value !== null) assertText(value, max, code);
}
function assertScope(input: CashInput, code: string): void {
assertText(input.organizationId, 200, code);
assertText(input.locationId, 200, code);
}
function assertDate(value: unknown, code: string): void {
assertText(value, 10, code);
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) throw new DomainConflictError(code);
const parsed = new Date(`${value}T00:00:00.000Z`);
if (Number.isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) {
throw new DomainConflictError(code);
}
}
function assertCurrency(value: unknown, code: string): void {
if (typeof value !== 'string' || !/^[A-Z]{3}$/.test(value)) throw new DomainConflictError(code);
}
function assertMinor(value: unknown, code: string, allowZero = false): void {
if (!Number.isSafeInteger(value) || Number(value) < (allowZero ? 0 : 1)) {
throw new DomainConflictError(code);
}
}
export function assertCashOpenInput(input: unknown): asserts input is CashInput {
const code = 'cash.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.tillId, 200, code);
assertDate(input.businessDate, code);
assertMinor(input.openingFloatMinor, code, true);
assertCurrency(input.currencyCode, code);
assertOptionalText(input.userId, 200, code);
}
export function assertCashMovementInput(input: unknown): asserts input is CashInput {
const code = 'cash.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.cashShiftId, 200, code);
if (!['pay_in', 'pay_out', 'safe_drop', 'correction'].includes(String(input.movementType))) {
throw new DomainConflictError(code);
}
assertMinor(input.amountMinor, code);
assertCurrency(input.currencyCode, code);
assertText(input.reason, 500, code);
assertOptionalText(input.userId, 200, code);
}
export function assertCashSaleInput(input: unknown): asserts input is CashInput {
const code = 'cash.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.cashShiftId, 200, code);
assertText(input.paymentId, 200, code);
assertMinor(input.amountMinor, code);
assertCurrency(input.currencyCode, code);
assertOptionalText(input.userId, 200, code);
}
export function assertCashCountInput(input: unknown): asserts input is CashInput {
const code = 'cash.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.cashShiftId, 200, code);
assertMinor(input.totalMinor, code, true);
assertCurrency(input.currencyCode, code);
if (!input.denominationCounts || typeof input.denominationCounts !== 'object' || Array.isArray(input.denominationCounts)) {
throw new DomainConflictError(code);
}
const entries = Object.entries(input.denominationCounts as Record<string, unknown>);
if (entries.length > 100) throw new DomainConflictError(code);
for (const [denomination, count] of entries) {
assertText(denomination, 80, code);
if (!Number.isSafeInteger(count) || Number(count) < 0) throw new DomainConflictError(code);
}
assertOptionalText(input.userId, 200, code);
}
export function assertCashCloseInput(input: unknown): asserts input is CashInput {
const code = 'cash.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.cashShiftId, 200, code);
assertOptionalText(input.notes, 2000, code);
assertOptionalText(input.userId, 200, code);
}
