UNPKG

@accounter/server

Version:
97 lines (84 loc) 3.4 kB
import type { charge_type } from '../../charges/types.js'; import type { ChargesMatcherModule } from '../types.js'; type ChargeMatchQueueMode = ChargesMatcherModule.ChargeMatchQueueMode; /** * DB charge types that are expected to hold both documents and transactions, * and therefore require a document ↔ transaction match. Every other type (e.g. * BANK_DEPOSIT, CREDITCARD_BANK, DIVIDEND, FOREIGN_SECURITIES, INTERNAL, VAT, * PAYROLL, CONVERSION, FINANCIAL) never needs an accounting document and is * excluded from the awaiting-match queue. A whitelist keeps new, * non-matchable types out of the queue by default. */ const MATCHABLE_CHARGE_TYPES = new Set<charge_type>(['COMMON', 'BUSINESS_TRIP']); /** * Only COMMON and BUSINESS_TRIP charges (or charges with a not-yet-resolved * `null` type, which resolve to either COMMON or BUSINESS_TRIP) are expected to * hold both documents and transactions, so only they belong in the * awaiting-match queue. */ export function chargeRequiresMatch(charge: { type?: charge_type | null }): boolean { return !charge.type || MATCHABLE_CHARGE_TYPES.has(charge.type); } /** * Max number of charges evaluated when sorting the queue BY_SCORE. Scoring is * calculated on the fly and each pass loads candidates, transactions and * documents, so the evaluation window is deliberately capped to bound request * latency and DB load. */ export const BY_SCORE_EVALUATION_CAP = 100; /** * The subset of enriched charge fields the queue filtering relies on. * Counts are int8 aggregates, surfaced by pgtyped as strings. */ export interface QueueChargeCounts { transactions_count: string | null; invoices_count: string | null; receipts_count: string | null; } function hasTransactions(charge: QueueChargeCounts): boolean { return Number(charge.transactions_count ?? 0) > 0; } function hasReceiptDocuments(charge: QueueChargeCounts): boolean { return Number(charge.receipts_count ?? 0) > 0; } function hasAccountingDocuments(charge: QueueChargeCounts): boolean { return Number(charge.invoices_count ?? 0) > 0 || hasReceiptDocuments(charge); } /** * A charge is transaction-based when it has transactions but no receipt * documents (mirrors the auto-match unmatched-charge semantics) */ export function isTransactionBaseCharge(charge: QueueChargeCounts): boolean { return hasTransactions(charge) && !hasReceiptDocuments(charge); } /** * A charge is document-based when it has accounting documents but no * transactions */ export function isDocumentBaseCharge(charge: QueueChargeCounts): boolean { return !hasTransactions(charge) && hasAccountingDocuments(charge); } /** * A charge belongs in the awaiting-match queue when it is unmatched: * transaction-based XOR document-based. Charges with both sides (matched) or * neither (empty) are excluded. */ export function isUnmatchedBaseCharge(charge: QueueChargeCounts): boolean { return isTransactionBaseCharge(charge) || isDocumentBaseCharge(charge); } /** * Apply the optional queue mode filter on top of the unmatched check */ export function matchesQueueMode( charge: QueueChargeCounts, mode?: ChargeMatchQueueMode | null, ): boolean { switch (mode) { case 'DOC_BASE': return isDocumentBaseCharge(charge); case 'TRANSACTION_BASE': return isTransactionBaseCharge(charge); default: return isUnmatchedBaseCharge(charge); } }