import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../../../infrastructure/database/database.service';
import { DomainConflictError } from '../../../common/errors/domain-conflict.error';
import { AccountingProviderRegistry } from '../providers/accounting-provider.registry';
@Injectable()
export class AccountingReadModelService {
constructor(
private readonly database: DatabaseService,
private readonly providers: AccountingProviderRegistry,
) {}
async overview(input: { organizationId: string; locationId: string }) {
const [config, connections, validators, invoices, exports, transfers] =
await Promise.all([
this.database.query<any>(
`select 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,next_invoice_number,
next_credit_note_number,e_invoice_profile,e_invoice_enabled,version,updated_at
from accounting_configurations
where organization_id=$1 and (location_id=$2 or location_id is null)
order by case when location_id=$2 then 0 else 1 end limit 1`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select id,provider,display_name,status,location_id,created_at,updated_at
from integration_connections
where organization_id=$1 and category='accounting'
and (location_id=$2 or location_id is null)
order by display_name,id`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select id,provider,display_name,status,created_at,updated_at
from einvoice_validator_connections
where organization_id=$1
order by display_name,id`,
[input.organizationId],
),
this.database.query<any>(
`select i.id,i.invoice_number,i.document_type,i.status,i.currency_code,
i.gross_total_minor,i.net_total_minor,i.tax_total_minor,
i.issue_date,i.due_date,i.e_invoice_profile,i.e_invoice_status,
i.created_at,i.finalized_at,c.name as customer_name
from invoices i
join invoice_customers c on c.id=i.invoice_customer_id
where i.organization_id=$1 and i.location_id=$2
order by i.created_at desc,i.id desc limit 100`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select id,export_type,period_start,period_end,status,file_asset_id,
provider_export_reference,created_at,started_at,completed_at,last_error
from accounting_export_jobs
where organization_id=$1 and (location_id=$2 or location_id is null)
order by created_at desc,id desc limit 100`,
[input.organizationId, input.locationId],
),
this.database.query<any>(
`select ate.id,ate.source_type,ate.source_id,ate.provider,ate.external_id,
ate.status,ate.response_safe_json,ate.created_at,ate.completed_at,ate.last_error
from accounting_transfer_events ate
where ate.organization_id=$1
and (ate.integration_connection_id is null or exists(
select 1 from integration_connections ic
where ic.id=ate.integration_connection_id
and (ic.location_id=$2 or ic.location_id is null)
))
order by ate.created_at desc,ate.id desc limit 100`,
[input.organizationId, input.locationId],
),
]);
return {
config: config[0] ?? null,
providers: this.providers.list(),
connections,
validators,
invoices,
exports,
transfers,
};
}
async invoice(input: {
organizationId: string;
locationId: string;
invoiceId: string;
}) {
const invoice = await this.database.query<any>(
`select i.*,c.name as customer_name,c.email as customer_email,c.vat_id as customer_vat_id,
c.tax_number as customer_tax_number,c.buyer_reference as customer_buyer_reference,
c.electronic_address,c.address_json,c.locale
from invoices i
join invoice_customers c on c.id=i.invoice_customer_id
where i.id=$1 and i.organization_id=$2 and i.location_id=$3`,
[input.invoiceId, input.organizationId, input.locationId],
);
if (!invoice[0]) throw new DomainConflictError('invoice.not_found');
const [lines, taxes, validations, transfers] = await Promise.all([
this.database.query<any>(
`select id,line_number,description,quantity,unit_code,unit_price_net_minor,
net_amount_minor,tax_rate_basis_points,tax_amount_minor,gross_amount_minor
from invoice_lines where invoice_id=$1 order by line_number`,
[input.invoiceId],
),
this.database.query<any>(
`select tax_rate_basis_points,taxable_net_minor,tax_minor,gross_minor
from invoice_tax_summaries where invoice_id=$1 order by tax_rate_basis_points`,
[input.invoiceId],
),
this.database.query<any>(
`select id,profile,validator,valid,messages_json,validated_at
from e_invoice_validation_results where invoice_id=$1
order by validated_at desc,id desc`,
[input.invoiceId],
),
this.database.query<any>(
`select id,provider,external_id,status,response_safe_json,created_at,completed_at,last_error
from accounting_transfer_events
where organization_id=$1 and source_type='invoice' and source_id=$2
order by created_at desc,id desc`,
[input.organizationId, input.invoiceId],
),
]);
return { invoice: invoice[0], lines, taxes, validations, transfers };
}
}
