import { Injectable } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { DatabaseService } from '../../../infrastructure/database/database.service';
import {
assertAccountingBoolean,
assertAccountingCurrency,
assertAccountingEnum,
assertAccountingRecord,
assertAccountingScope,
assertAccountingSafeInteger,
assertOptionalAccountingText,
} from './accounting-input-validation';
@Injectable()
export class AccountingConfigService {
constructor(private readonly database:DatabaseService) {}
async upsert(input:unknown) {
assertAccountingScope(input, 'accounting.config_input_invalid', { locationRequired: false });
const currencyCode = input.currencyCode ?? 'EUR';
const chartOfAccounts = input.chartOfAccounts ?? 'SKR03';
const eInvoiceProfile = input.eInvoiceProfile ?? 'zugferd';
assertAccountingCurrency(currencyCode, 'accounting.config_input_invalid');
assertAccountingEnum(chartOfAccounts, ['SKR03', 'SKR04', 'custom'], 'accounting.config_input_invalid');
assertAccountingEnum(eInvoiceProfile, ['zugferd', 'xrechnung', 'both'], 'accounting.config_input_invalid');
for (const key of [
'defaultRevenueAccount', 'defaultCashAccount', 'defaultCardClearingAccount', 'defaultTaxAccount',
]) assertOptionalAccountingText(input[key], 120, 'accounting.config_input_invalid');
assertOptionalAccountingText(input.invoiceNumberPrefix, 40, 'accounting.config_input_invalid');
assertOptionalAccountingText(input.creditNoteNumberPrefix, 40, 'accounting.config_input_invalid');
if (input.eInvoiceEnabled !== undefined && input.eInvoiceEnabled !== null) {
assertAccountingBoolean(input.eInvoiceEnabled, 'accounting.config_input_invalid');
}
const id=randomUUID();
await this.database.query(
`insert into accounting_configurations(
id,organization_id,location_id,currency_code,chart_of_accounts,
default_revenue_account,default_cash_account,default_card_clearing_account,
default_tax_account,invoice_number_prefix,credit_note_number_prefix,
e_invoice_profile,e_invoice_enabled
) values($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
on conflict(organization_id,location_id)
do update set
currency_code=excluded.currency_code,
chart_of_accounts=excluded.chart_of_accounts,
default_revenue_account=excluded.default_revenue_account,
default_cash_account=excluded.default_cash_account,
default_card_clearing_account=excluded.default_card_clearing_account,
default_tax_account=excluded.default_tax_account,
invoice_number_prefix=excluded.invoice_number_prefix,
credit_note_number_prefix=excluded.credit_note_number_prefix,
e_invoice_profile=excluded.e_invoice_profile,
e_invoice_enabled=excluded.e_invoice_enabled,
updated_at=now(),version=accounting_configurations.version+1`,
[
id,input.organizationId,input.locationId??null,currencyCode,
chartOfAccounts,input.defaultRevenueAccount??null,
input.defaultCashAccount??null,input.defaultCardClearingAccount??null,
input.defaultTaxAccount??null,input.invoiceNumberPrefix??'RE',
input.creditNoteNumberPrefix??'GS',eInvoiceProfile,
input.eInvoiceEnabled??false,
],
);
return {updated:true};
}
async addRule(input:unknown) {
assertAccountingScope(input, 'accounting.rule_input_invalid', { locationRequired: false });
assertAccountingEnum(input.ruleType, ['sale', 'payment', 'refund', 'cash_movement', 'purchase', 'fee', 'rounding'], 'accounting.rule_input_invalid');
const match = input.match ?? {};
assertAccountingRecord(match, 'accounting.rule_input_invalid');
for (const key of ['debitAccount', 'creditAccount', 'taxAccount', 'taxKey']) {
assertOptionalAccountingText(input[key], 120, 'accounting.rule_input_invalid');
}
if (input.priority !== undefined && input.priority !== null) {
assertAccountingSafeInteger(input.priority, 'accounting.rule_input_invalid', 0, 1_000_000);
}
if (input.isActive !== undefined && input.isActive !== null) {
assertAccountingBoolean(input.isActive, 'accounting.rule_input_invalid');
}
const id=randomUUID();
await this.database.query(
`insert into accounting_rules(
id,organization_id,location_id,rule_type,match_json,debit_account,
credit_account,tax_account,tax_key,priority,is_active
) values($1,$2,$3,$4,$5::jsonb,$6,$7,$8,$9,$10,$11)`,
[
id,input.organizationId,input.locationId??null,input.ruleType,
JSON.stringify(match),input.debitAccount??null,input.creditAccount??null,
input.taxAccount??null,input.taxKey??null,input.priority??100,input.isActive??true,
],
);
return {id};
}
}
