import { DomainConflictError } from '../../../common/errors/domain-conflict.error';
type FiscalInput = Record<string, unknown>;
function assertObject(input: unknown, code: string): asserts input is FiscalInput {
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: FiscalInput, code: string, locationRequired = true): void {
assertText(input.organizationId, 200, code);
if (locationRequired) assertText(input.locationId, 200, code);
else if (input.locationId !== undefined && input.locationId !== null) assertText(input.locationId, 200, code);
}
function assertDateTime(value: unknown, code: string): void {
assertText(value, 80, code);
if (Number.isNaN(new Date(value).getTime())) throw new DomainConflictError(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 assertRecord(value: unknown, code: string, maxKeys = 100): void {
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new DomainConflictError(code);
const keys = Object.keys(value as object);
if (keys.length > maxKeys) throw new DomainConflictError(code);
for (const key of keys) assertText(key, 200, code);
try {
const serialized = JSON.stringify(value);
if (serialized.length > 200_000) throw new DomainConflictError(code);
} catch (error) {
if (error instanceof DomainConflictError) throw error;
throw new DomainConflictError(code);
}
}
export function assertFiscalCreateTransactionInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.fiscalDeviceId, 200, code);
assertOptionalText(input.checkId, 200, code);
assertOptionalText(input.paymentId, 200, code);
if (!['sale', 'refund', 'cash_movement', 'training', 'other'].includes(String(input.transactionType))) {
throw new DomainConflictError(code);
}
assertText(input.clientTransactionRef, 250, code);
assertRecord(input.processData, code);
}
export function assertFiscalStartedInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.input_invalid';
assertObject(input, code);
assertText(input.organizationId, 200, code);
assertText(input.fiscalTransactionId, 200, code);
assertText(input.providerReference, 500, code);
if (input.tseTransactionNumber !== undefined &&
(!Number.isSafeInteger(input.tseTransactionNumber) || Number(input.tseTransactionNumber) < 0)) {
throw new DomainConflictError(code);
}
if (input.tseStartAt !== undefined && input.tseStartAt !== null) assertDateTime(input.tseStartAt, code);
}
export function assertFiscalCompletedInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.input_invalid';
assertObject(input, code);
assertText(input.organizationId, 200, code);
assertText(input.fiscalTransactionId, 200, code);
for (const key of ['tseTransactionNumber', 'signatureCounter']) {
if (!Number.isSafeInteger(input[key]) || Number(input[key]) < 0) throw new DomainConflictError(code);
}
assertDateTime(input.tseStartAt, code);
assertDateTime(input.tseEndAt, code);
if (new Date(String(input.tseEndAt)).getTime() < new Date(String(input.tseStartAt)).getTime()) {
throw new DomainConflictError(code);
}
assertText(input.verificationValue, 4000, code);
assertOptionalText(input.providerReference, 500, code);
}
export function assertFiscalUncertainInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.input_invalid';
assertObject(input, code);
assertText(input.organizationId, 200, code);
assertText(input.fiscalTransactionId, 200, code);
assertOptionalText(input.providerStatus, 200, code);
}
export function assertFiscalReceiptInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.input_invalid';
assertObject(input, code);
assertText(input.organizationId, 200, code);
assertText(input.fiscalTransactionId, 200, code);
assertText(input.receiptNumber, 200, code);
assertText(input.issuerName, 300, code);
assertText(input.issuerAddress, 1000, code);
if (input.locale !== undefined && input.locale !== null && !['de-DE', 'en-GB'].includes(String(input.locale))) {
throw new DomainConflictError(code);
}
assertOptionalText(input.qrPayload, 10_000, code);
assertRecord(input.receiptPayload, code);
}
export function assertFiscalRealStartInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.checkId, 200, code);
assertOptionalText(input.paymentId, 200, code);
if (!['sale', 'refund', 'cancellation', 'cash_movement'].includes(String(input.transactionType))) {
throw new DomainConflictError(code);
}
assertText(input.mappingId, 200, code);
assertText(input.clientTransactionRef, 250, code);
}
export function assertFiscalRealReferenceInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.fiscalTransactionId, 200, code);
}
export function assertFiscalPeriodInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.export_input_invalid';
assertObject(input, code);
assertScope(input, code);
assertDate(input.periodStart, code);
assertDate(input.periodEnd, code);
if (String(input.periodEnd) < String(input.periodStart)) throw new DomainConflictError('fiscal.export_period_invalid');
}
export function assertFiscalExportCreateInput(input: unknown): asserts input is FiscalInput {
assertFiscalPeriodInput(input);
const code = 'fiscal.export_input_invalid';
if (!['dsfinvk', 'tse', 'tse_export', 'cash_journal'].includes(String(input.exportType))) {
throw new DomainConflictError(code);
}
assertOptionalText(input.connectionId, 200, code);
assertOptionalText(input.userId, 200, code);
}
export function assertFiscalExportListInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.export_input_invalid';
assertObject(input, code);
assertScope(input, code);
}
export function assertFiscalRecoveryListInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.recovery_input_invalid';
assertObject(input, code);
assertScope(input, code);
}
export function assertFiscalRecoveryResolveInput(input: unknown): asserts input is FiscalInput {
const code = 'fiscal.recovery_input_invalid';
assertObject(input, code);
assertScope(input, code);
assertText(input.caseId, 200, code);
if (!['refresh_provider', 'retry_finish', 'provider_support', 'ignore'].includes(String(input.action))) {
throw new DomainConflictError(code);
}
assertOptionalText(input.userId, 200, code);
assertOptionalText(input.note, 2000, code);
}
