UNPKG

@accounter/server

Version:
1,306 lines 54.3 kB
import { __decorate, __metadata } from "tslib";
import DataLoader from 'dataloader';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import { ChargesAuthorizationProvider } from './charges-authorization.provider.js';
const getChargesByIds = sql `
    SELECT *
    FROM accounter_schema.charges
    WHERE id IN $$chargeIds;`;
const getChargesByTransactionIds = sql `
    SELECT t.id AS transaction_id, c.* FROM accounter_schema.transactions t
    LEFT JOIN accounter_schema.charges c
      ON t.charge_id = c.id
    WHERE t.id IN $$transactionIds;`;
const getChargesByMissingRequiredInfo = sql `
    SELECT c.*
    FROM accounter_schema.charges c
    LEFT JOIN accounter_schema.charge_tags t
      ON t.charge_id = c.id
    WHERE c.user_description IS NULL
    OR t.tag_id IS NULL;`;
const updateCharge = sql `
  UPDATE accounter_schema.charges
  SET
  user_description = COALESCE(
    $userDescription,
    user_description
  ),
  type = COALESCE(
    $type,
    type
  ),
  invoice_payment_currency_diff = COALESCE(
    $isInvoicePaymentDifferentCurrency,
    invoice_payment_currency_diff
  ),
  accountant_status = COALESCE(
    $accountantStatus,
    accountant_status
  ),
  tax_category_id = COALESCE(
    $taxCategoryId,
    tax_category_id
  ),
  optional_vat = COALESCE(
    $optionalVAT,
    optional_vat
  ),
  documents_optional_flag = COALESCE(
    $optionalDocuments,
    documents_optional_flag
  ),
  is_property = COALESCE(
    $isProperty,
    is_property
  )
  WHERE
    id = $chargeId
  RETURNING *;
`;
const batchUpdateCharges = sql `
  UPDATE accounter_schema.charges
  SET
  user_description = COALESCE(
    $userDescription,
    user_description
  ),
  type = COALESCE(
    $type,
    type
  ),
  invoice_payment_currency_diff = COALESCE(
    $isInvoicePaymentDifferentCurrency,
    invoice_payment_currency_diff
  ),
  accountant_status = COALESCE(
    $accountantStatus,
    accountant_status
  ),
  tax_category_id = COALESCE(
    $taxCategoryId,
    tax_category_id
  ),
  optional_vat = COALESCE(
    $optionalVAT,
    optional_vat
  ),
  documents_optional_flag = COALESCE(
    $optionalDocuments,
    documents_optional_flag
  ),
  is_property = COALESCE(
    $isProperty,
    is_property
  )
  WHERE
    id in $$chargeIds
  RETURNING *;
`;
const updateAccountantApproval = sql `
  UPDATE accounter_schema.charges
  SET
    accountant_status = $accountantStatus
  WHERE
    id = $chargeId
  RETURNING *;
`;
const generateCharge = sql `
  INSERT INTO accounter_schema.charges (owner_id, type, accountant_status, user_description, tax_category_id, optional_vat, documents_optional_flag, is_property)
  VALUES ($ownerId, $type, $accountantStatus, $userDescription, $taxCategoryId, $optionalVAT, $optionalDocuments, $isProperty)
  RETURNING *;
`;
/**
 * Collapses a search string that is empty once trimmed to `null`.
 *
 * An empty string is not `NULL` in SQL, so it survives the `IS NOT NULL` guards and
 * degrades to `ILIKE '%%'`, which matches every non-null column. On the exclusion side
 * that drops nearly every charge; on the positive side it drops the opposite set --
 * charges whose description is `NULL` -- because they fail the `ILIKE` the others pass.
 * Either way the caller meant "no text predicate", so normalize it away.
 */
export function normalizeSearchText(text) {
    const trimmed = text?.trim().toLowerCase();
    return trimmed || null;
}
/**
 * The amount-matching branches compare against `amount::TEXT`, so a search term with
 * no digit in it cannot match one and only costs a scan. Thousands separators are
 * stripped so both "1,234.56" and "1234.56" match the plain stored value.
 */
export function toNumericSearchText(text) {
    return text && /\d/.test(text) ? text.replaceAll(',', '') : null;
}
/**
 * Note for editors: pgTyped silently truncates the generated parameter list at the
 * first `--` comment inside the closing WHERE clause, dropping every parameter after
 * it. Keep explanatory comments in TypeScript rather than in that clause.
 *
 * The `excluded*` filters are array NON-overlap against the per-charge aggregates
 * (`business_array`, `tags`, `account_array`), so a charge is dropped when *any* of
 * its businesses / tags / accounts appears in the exclusion list. Each is wrapped in
 * COALESCE because those arrays come from LEFT JOINs and `NULL && array` is NULL,
 * which would otherwise drop the charges that have none of the excluded thing.
 * `excludedFreeText` works the other way round, as an `excluded_matches` CTE the
 * charge must not appear in.
 */
const getChargesByFilters = sql `
  WITH search_matches AS (
    -- Strategy: Identify IDs via Trigram indexes before doing any heavy math
    SELECT id FROM accounter_schema.charges 
    WHERE ($freeText::TEXT IS NULL
           OR user_description ILIKE '%' || $freeText || '%'
    )
    UNION
    SELECT charge_id FROM accounter_schema.transactions
    WHERE ($freeText::TEXT IS NOT NULL
           AND (source_description ILIKE '%' || $freeText || '%'
                OR source_reference ILIKE '%' || $freeText || '%')
    )
    UNION
    SELECT charge_id FROM accounter_schema.documents
    WHERE ($freeText::TEXT IS NOT NULL
           AND (description ILIKE '%' || $freeText || '%'
                OR remarks ILIKE '%' || $freeText || '%'
                OR serial_number ILIKE '%' || $freeText || '%')
    )
    UNION
    -- match transaction amounts. $freeTextNumeric has thousands separators stripped
    -- so both "1,234.56" and "1234.56" match the plain value stored in the DB.
    SELECT charge_id FROM accounter_schema.transactions
    WHERE ($freeTextNumeric::TEXT IS NOT NULL
           AND (amount::TEXT ILIKE '%' || $freeTextNumeric || '%'
                OR ABS(amount)::TEXT ILIKE '%' || $freeTextNumeric || '%')
    )
    UNION
    -- match document amounts (total and vat), separators stripped
    SELECT charge_id FROM accounter_schema.documents
    WHERE ($freeTextNumeric::TEXT IS NOT NULL
           AND (total_amount::TEXT ILIKE '%' || $freeTextNumeric || '%'
                OR ABS(total_amount)::TEXT ILIKE '%' || $freeTextNumeric || '%'
                OR vat_amount::TEXT ILIKE '%' || $freeTextNumeric || '%'
                OR ABS(vat_amount)::TEXT ILIKE '%' || $freeTextNumeric || '%')
    )
    UNION
    -- match counterparty business names referenced by the charges transactions
    SELECT t.charge_id FROM accounter_schema.transactions t
    JOIN accounter_schema.financial_entities fe ON fe.id = t.business_id
    WHERE ($freeText::TEXT IS NOT NULL
           AND fe.name ILIKE '%' || $freeText || '%')
    UNION
    -- match counterparty business names referenced by the charges documents (creditor)
    SELECT d.charge_id FROM accounter_schema.documents d
    JOIN accounter_schema.financial_entities fe ON fe.id = d.creditor_id
    WHERE ($freeText::TEXT IS NOT NULL
           AND fe.name ILIKE '%' || $freeText || '%')
    UNION
    -- match counterparty business names referenced by the charges documents (debtor)
    SELECT d.charge_id FROM accounter_schema.documents d
    JOIN accounter_schema.financial_entities fe ON fe.id = d.debtor_id
    WHERE ($freeText::TEXT IS NOT NULL
           AND fe.name ILIKE '%' || $freeText || '%')
  ),
  excluded_matches AS (
    -- Mirror of search_matches for the negative side. Every branch requires the
    -- param to be NOT NULL, so with no exclusion the set is empty and nothing is
    -- dropped -- the opposite of search_matches, whose first branch passes every
    -- charge through when $freeText is NULL.
    -- A charge whose columns are all NULL simply never enters this set, so
    -- "does not mention X" keeps charges with no text at all.
    SELECT id FROM accounter_schema.charges
    WHERE ($excludedFreeText::TEXT IS NOT NULL
           AND user_description ILIKE '%' || $excludedFreeText || '%'
    )
    UNION
    SELECT charge_id FROM accounter_schema.transactions
    WHERE ($excludedFreeText::TEXT IS NOT NULL
           AND (source_description ILIKE '%' || $excludedFreeText || '%'
                OR source_reference ILIKE '%' || $excludedFreeText || '%')
    )
    UNION
    SELECT charge_id FROM accounter_schema.documents
    WHERE ($excludedFreeText::TEXT IS NOT NULL
           AND (description ILIKE '%' || $excludedFreeText || '%'
                OR remarks ILIKE '%' || $excludedFreeText || '%'
                OR serial_number ILIKE '%' || $excludedFreeText || '%')
    )
    UNION
    SELECT charge_id FROM accounter_schema.transactions
    WHERE ($excludedFreeTextNumeric::TEXT IS NOT NULL
           AND (amount::TEXT ILIKE '%' || $excludedFreeTextNumeric || '%'
                OR ABS(amount)::TEXT ILIKE '%' || $excludedFreeTextNumeric || '%')
    )
    UNION
    SELECT charge_id FROM accounter_schema.documents
    WHERE ($excludedFreeTextNumeric::TEXT IS NOT NULL
           AND (total_amount::TEXT ILIKE '%' || $excludedFreeTextNumeric || '%'
                OR ABS(total_amount)::TEXT ILIKE '%' || $excludedFreeTextNumeric || '%'
                OR vat_amount::TEXT ILIKE '%' || $excludedFreeTextNumeric || '%'
                OR ABS(vat_amount)::TEXT ILIKE '%' || $excludedFreeTextNumeric || '%')
    )
    UNION
    SELECT t.charge_id FROM accounter_schema.transactions t
    JOIN accounter_schema.financial_entities fe ON fe.id = t.business_id
    WHERE ($excludedFreeText::TEXT IS NOT NULL
           AND fe.name ILIKE '%' || $excludedFreeText || '%')
    UNION
    SELECT d.charge_id FROM accounter_schema.documents d
    JOIN accounter_schema.financial_entities fe ON fe.id = d.creditor_id
    WHERE ($excludedFreeText::TEXT IS NOT NULL
           AND fe.name ILIKE '%' || $excludedFreeText || '%')
    UNION
    SELECT d.charge_id FROM accounter_schema.documents d
    JOIN accounter_schema.financial_entities fe ON fe.id = d.debtor_id
    WHERE ($excludedFreeText::TEXT IS NOT NULL
           AND fe.name ILIKE '%' || $excludedFreeText || '%')
  ),
  filtered_charges AS MATERIALIZED (
    -- This is our "source of truth" for the rest of the query
    SELECT c.*
    FROM accounter_schema.charges c
    INNER JOIN search_matches sm ON c.id = sm.id 
    WHERE ($isIDs = 0 OR c.id IN $$IDs)
      AND ($isOwnerIds = 0 OR c.owner_id IN $$ownerIds)
      AND (
        ($excludedFreeText::TEXT IS NULL AND $excludedFreeTextNumeric::TEXT IS NULL)
        OR NOT EXISTS (SELECT 1 FROM excluded_matches em WHERE em.id = c.id)
      )
  ),
  years_of_relevance AS (
    SELECT cs.charge_id,
           array_agg(cs.year_of_relevance) AS years_of_relevance
    FROM accounter_schema.charge_spread cs
    JOIN filtered_charges fc ON fc.id = cs.charge_id
    GROUP BY cs.charge_id
  ),
  transactions_by_charge AS (
    SELECT t.charge_id,
           min(t.event_date) AS min_event_date,
           max(t.event_date) AS max_event_date,
           min(COALESCE(t.debit_date_override, t.debit_date)) AS min_debit_date,
           max(COALESCE(t.debit_date_override, t.debit_date)) AS max_debit_date,
           min(COALESCE(t.debit_timestamp, t.debit_date)) AS min_debit_timestamp,
           max(COALESCE(t.debit_timestamp, t.debit_date)) AS max_debit_timestamp,
           COALESCE(
             sum(t.amount) FILTER (WHERE t.is_fee IS NOT TRUE),
             sum(t.amount)
           ) AS fee_excluded_event_amount,
           COALESCE(
             array_agg(DISTINCT t.currency) FILTER (WHERE t.is_fee IS NOT TRUE),
             array_agg(DISTINCT t.currency)
           ) AS fee_excluded_currency_array,
           sum(t.amount) AS event_amount,
           count(*) AS transactions_count,
           count(*) FILTER (
             WHERE t.business_id IS NULL
             OR COALESCE(t.debit_date_override, t.debit_date) IS NULL
           ) > 0 AS invalid_transactions,
           count(*) FILTER (
             WHERE t.business_id IS NULL
           ) > 0 AS missing_counterparty_transactions,
           array_agg(DISTINCT t.currency) AS currency_array,
           array_agg(DISTINCT t.account_id) AS account_array,
           string_agg(COALESCE(t.source_description, '') || ' ' || COALESCE(t.source_reference, ''), ' ') AS search_text
    FROM accounter_schema.transactions t
    JOIN filtered_charges fc ON fc.id = t.charge_id
    GROUP BY t.charge_id
  ),
  documents_by_charge AS (
    SELECT d.charge_id,
           min(d.date) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS min_event_date,
           min(d.date) AS min_any_event_date,
           max(d.date) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS max_event_date,
           max(d.date) AS max_any_event_date,
           sum(
             d.total_amount *
             CASE
               WHEN d.creditor_id = fc.owner_id THEN 1
               ELSE '-1'::integer
             END::double precision
           ) FILTER (
             WHERE businesses.can_settle_with_receipt = true
               AND d.type = ANY (
                 ARRAY[
                   'RECEIPT'::accounter_schema.document_type,
                   'INVOICE_RECEIPT'::accounter_schema.document_type
                 ]
               )
           ) AS receipt_event_amount,
           sum(
             d.total_amount *
             CASE
               WHEN d.creditor_id = fc.owner_id THEN 1
               ELSE '-1'::integer
             END::double precision
           ) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS invoice_event_amount,
           sum(
             d.vat_amount *
             CASE
               WHEN d.creditor_id = fc.owner_id THEN 1
               ELSE '-1'::integer
             END::double precision
           ) FILTER (
             WHERE businesses.can_settle_with_receipt = true
               AND d.type = ANY (
                 ARRAY[
                   'RECEIPT'::accounter_schema.document_type,
                   'INVOICE_RECEIPT'::accounter_schema.document_type
                 ]
               )
           ) AS receipt_vat_amount,
           sum(
             d.vat_amount *
             CASE
               WHEN d.creditor_id = fc.owner_id THEN 1
               ELSE '-1'::integer
             END::double precision
           ) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS invoice_vat_amount,
           count(*) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS invoices_count,
           count(*) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'RECEIPT'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type
               ]
             )
           ) AS receipts_count,
           count(*) AS documents_count,
           count(*) FILTER (
             WHERE (
               d.type = ANY (
                 ARRAY[
                   'INVOICE'::accounter_schema.document_type,
                   'INVOICE_RECEIPT'::accounter_schema.document_type,
                   'RECEIPT'::accounter_schema.document_type,
                   'CREDIT_INVOICE'::accounter_schema.document_type
                 ]
               )
             )
             AND (
               d.debtor_id IS NULL
               OR d.creditor_id IS NULL
               OR d.date IS NULL
               OR d.serial_number IS NULL
               OR d.vat_amount IS NULL
               OR d.total_amount IS NULL
               OR d.charge_id IS NULL
               OR d.currency_code IS NULL
             )
             OR d.type = 'UNPROCESSED'::accounter_schema.document_type
           ) > 0 AS invalid_documents,
           count(*) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
             AND (d.creditor_id IS NULL OR d.debtor_id IS NULL)
           ) > 0 AS missing_counterparty_documents,
           array_agg(d.currency_code) FILTER (
             WHERE (
               businesses.can_settle_with_receipt = true
               AND d.type = ANY (
                 ARRAY[
                   'RECEIPT'::accounter_schema.document_type,
                   'INVOICE_RECEIPT'::accounter_schema.document_type
                 ]
               )
             )
             OR d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS currency_array,
           sum(
             d.total_amount *
             CASE
               WHEN d.debtor_id = fc.owner_id THEN '-1'::integer
               ELSE 1
             END::double precision
           ) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS js_invoice_event_amount,
           sum(
             d.total_amount *
             CASE
               WHEN d.debtor_id = fc.owner_id THEN '-1'::integer
               ELSE 1
             END::double precision
           ) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'RECEIPT'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type
               ]
             )
           ) AS js_receipt_event_amount,
           sum(
             d.total_amount *
             CASE
               WHEN d.debtor_id = fc.owner_id THEN '-1'::integer
               ELSE 1
             END::double precision
           ) FILTER (
             WHERE d.type = 'PROFORMA'::accounter_schema.document_type
           ) AS js_proforma_event_amount,
           sum(
             d.vat_amount *
             CASE
               WHEN d.debtor_id = fc.owner_id THEN '-1'::integer
               ELSE 1
             END::double precision
           ) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS js_invoice_vat_amount,
           sum(
             d.vat_amount *
             CASE
               WHEN d.debtor_id = fc.owner_id THEN '-1'::integer
               ELSE 1
             END::double precision
           ) FILTER (
             WHERE d.type = ANY (
               ARRAY[
                 'RECEIPT'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type
               ]
             )
           ) AS js_receipt_vat_amount,
           array_agg(DISTINCT d.currency_code) FILTER (
             WHERE d.currency_code IS NOT NULL
             AND d.type = ANY (
               ARRAY[
                 'INVOICE'::accounter_schema.document_type,
                 'INVOICE_RECEIPT'::accounter_schema.document_type,
                 'RECEIPT'::accounter_schema.document_type,
                 'CREDIT_INVOICE'::accounter_schema.document_type
               ]
             )
           ) AS js_accountancy_currency_array,
           array_agg(DISTINCT d.currency_code) FILTER (
             WHERE d.currency_code IS NOT NULL
             AND d.type = 'PROFORMA'::accounter_schema.document_type
           ) AS js_proforma_currency_array,
           COALESCE(BOOL_OR(doc_issued.status = 'OPEN'), false) AS open_docs_flag,
           string_agg(COALESCE(d.description, '') || ' ' || COALESCE(d.remarks, '') || ' ' || COALESCE(d.serial_number, ''), ' ') AS search_text
    FROM accounter_schema.documents d
    JOIN filtered_charges fc ON fc.id = d.charge_id
    LEFT JOIN accounter_schema.businesses
      ON (
        d.creditor_id = fc.owner_id
        AND d.debtor_id = businesses.id
      )
      OR (
        d.creditor_id = businesses.id
        AND d.debtor_id = fc.owner_id
      )
    LEFT JOIN accounter_schema.documents_issued doc_issued ON d.id = doc_issued.id
    GROUP BY d.charge_id
  ),
  businesses_by_charge AS (
    SELECT base.charge_id,
           array_remove(base.business_array, fc.owner_id) AS business_array,
           array_remove(base.filtered_business_array, fc.owner_id) AS filtered_business_array
    FROM (
      SELECT b.charge_id,
             array_agg(DISTINCT b.business_id) AS business_array,
             array_remove(
               array_agg(
                 DISTINCT CASE
                   WHEN b.is_fee THEN NULL::uuid
                   ELSE b.business_id
                 END
               ),
               NULL::uuid
             ) AS filtered_business_array
      FROM (
        SELECT t.charge_id, t.business_id, t.is_fee
        FROM accounter_schema.transactions t
        JOIN filtered_charges fc ON fc.id = t.charge_id
        WHERE t.business_id IS NOT NULL

        UNION
        SELECT d.charge_id, d.creditor_id, false AS is_fee
        FROM accounter_schema.documents d
        JOIN filtered_charges fc ON fc.id = d.charge_id
        WHERE d.creditor_id IS NOT NULL

        UNION
        SELECT d.charge_id, d.debtor_id, false AS is_fee
        FROM accounter_schema.documents d
        JOIN filtered_charges fc ON fc.id = d.charge_id
        WHERE d.debtor_id IS NOT NULL

        UNION
        SELECT lr.charge_id, lr.credit_entity1, true AS is_fee
        FROM accounter_schema.ledger_records lr
        JOIN filtered_charges fc ON fc.id = lr.charge_id
        WHERE lr.credit_entity1 IS NOT NULL

        UNION
        SELECT lr.charge_id, lr.credit_entity2, true AS is_fee
        FROM accounter_schema.ledger_records lr
        JOIN filtered_charges fc ON fc.id = lr.charge_id
        WHERE lr.credit_entity2 IS NOT NULL

        UNION
        SELECT lr.charge_id, lr.debit_entity1, true AS is_fee
        FROM accounter_schema.ledger_records lr
        JOIN filtered_charges fc ON fc.id = lr.charge_id
        WHERE lr.debit_entity1 IS NOT NULL

        UNION
        SELECT lr.charge_id, lr.debit_entity2, true AS is_fee
        FROM accounter_schema.ledger_records lr
        JOIN filtered_charges fc ON fc.id = lr.charge_id
        WHERE lr.debit_entity2 IS NOT NULL

        UNION
        SELECT me.charge_id, me.creditor_id, true AS is_fee
        FROM accounter_schema.misc_expenses me
        JOIN filtered_charges fc ON fc.id = me.charge_id
        WHERE me.creditor_id IS NOT NULL

        UNION
        SELECT me.charge_id, me.debtor_id, true AS is_fee
        FROM accounter_schema.misc_expenses me
        JOIN filtered_charges fc ON fc.id = me.charge_id
        WHERE me.debtor_id IS NOT NULL
      ) b
      GROUP BY b.charge_id
    ) base
    JOIN filtered_charges fc ON fc.id = base.charge_id
  ),
  tags_by_charge AS (
    SELECT ct.charge_id,
           array_agg(ct.tag_id) AS tags_array
    FROM accounter_schema.charge_tags ct
    JOIN filtered_charges fc ON fc.id = ct.charge_id
    GROUP BY ct.charge_id
  ),
  ledger_by_charge AS (
    SELECT count(DISTINCT x.id) AS ledger_count,
           array_remove(array_agg(DISTINCT x.financial_entity), NULL::uuid) AS ledger_financial_entities,
           min(x.value_date) AS min_value_date,
           max(x.value_date) AS max_value_date,
           min(x.invoice_date) AS min_invoice_date,
           max(x.invoice_date) AS max_invoice_date,
           x.charge_id
    FROM (
      SELECT lr.charge_id,
             lr.id,
             lr.value_date,
             lr.invoice_date,
             unnest(
               ARRAY[
                 lr.credit_entity1,
                 lr.credit_entity2,
                 lr.debit_entity1,
                 lr.debit_entity2
               ]
             ) AS financial_entity
      FROM accounter_schema.ledger_records lr
      JOIN filtered_charges fc ON fc.id = lr.charge_id
    ) x
    GROUP BY x.charge_id
  ),
  enriched_charges AS (
    SELECT
      c.id,
      c.owner_id,
      c.is_property,
      c.accountant_status,
      c.user_description,
      c.created_at,
      c.updated_at,
      COALESCE(c.tax_category_id, tcm.tax_category_id) AS tax_category_id,
      COALESCE(
        CASE
          WHEN dbc.invoices_count > 0 THEN dbc.js_invoice_event_amount
          WHEN dbc.receipts_count > 0 THEN dbc.js_receipt_event_amount
          ELSE dbc.js_proforma_event_amount
        END::numeric,
        tbc.event_amount
      ) AS event_amount,
      tbc.min_event_date AS transactions_min_event_date,
      tbc.max_event_date AS transactions_max_event_date,
      tbc.min_debit_date AS transactions_min_debit_date,
      tbc.max_debit_date AS transactions_max_debit_date,
      tbc.min_debit_timestamp AS transactions_min_debit_timestamp,
      tbc.max_debit_timestamp AS transactions_max_debit_timestamp,
      tbc.fee_excluded_event_amount AS transactions_fee_excluded_amount,
      tbc.account_array,
      -- Cast enum arrays to TEXT[]: node-postgres has no array parser registered for the
      -- custom accounter_schema.currency OID, so a currency[] arrives in JS as the raw
      -- array literal ('{USD}') rather than an array. TEXT[] is parsed natively.
      tbc.fee_excluded_currency_array::TEXT[] AS transactions_fee_excluded_currencies,
      tbc.event_amount AS transactions_event_amount,
      CASE
        WHEN array_length(tbc.currency_array, 1) = 1 THEN tbc.currency_array[1]
        ELSE NULL::accounter_schema.currency
      END AS transactions_currency,
      tbc.transactions_count,
      tbc.invalid_transactions,
      tbc.missing_counterparty_transactions,
      dbc.missing_counterparty_documents,
      COALESCE(dbc.min_event_date, dbc.min_any_event_date) AS documents_min_date,
      COALESCE(dbc.max_event_date, dbc.max_any_event_date) AS documents_max_date,
      COALESCE(dbc.invoice_event_amount, dbc.receipt_event_amount) AS documents_event_amount,
      COALESCE(dbc.invoice_vat_amount, dbc.receipt_vat_amount) AS documents_vat_amount,
      dbc.js_invoice_event_amount AS documents_invoice_amount,
      dbc.js_receipt_event_amount AS documents_receipt_amount,
      dbc.js_proforma_event_amount AS documents_proforma_amount,
      dbc.js_invoice_vat_amount AS documents_invoice_vat_amount,
      dbc.js_receipt_vat_amount AS documents_receipt_vat_amount,
      -- See the TEXT[] cast note above.
      dbc.js_accountancy_currency_array::TEXT[] AS documents_accountancy_currencies,
      dbc.js_proforma_currency_array::TEXT[] AS documents_proforma_currencies,
      CASE
        WHEN array_length(dbc.currency_array, 1) = 1 THEN dbc.currency_array[1]
        ELSE NULL::accounter_schema.currency
      END AS documents_currency,
      dbc.invoices_count,
      dbc.receipts_count,
      dbc.documents_count,
      dbc.invalid_documents,
      dbc.open_docs_flag,
      bbc.business_array,
      b.id AS business_id,
      COALESCE(b.can_settle_with_receipt, false) AS can_settle_with_receipt,
      tgc.tags_array AS tags,
      btc.business_trip_id,
      lbc.ledger_count,
      lbc.ledger_financial_entities,
      lbc.min_value_date AS ledger_min_value_date,
      lbc.max_value_date AS ledger_max_value_date,
      lbc.min_invoice_date AS ledger_min_invoice_date,
      lbc.max_invoice_date AS ledger_max_invoice_date,
      y.years_of_relevance,
      c.invoice_payment_currency_diff,
      c.type,
      c.optional_vat,
      COALESCE(b.no_invoices_required, false) AS no_invoices_required,
      c.documents_optional_flag
    FROM filtered_charges c
    LEFT JOIN transactions_by_charge tbc ON tbc.charge_id = c.id
    LEFT JOIN documents_by_charge dbc ON dbc.charge_id = c.id
    LEFT JOIN businesses_by_charge bbc ON bbc.charge_id = c.id
    LEFT JOIN accounter_schema.businesses b
      ON b.id = bbc.filtered_business_array[1]
      AND array_length(bbc.filtered_business_array, 1) = 1
    LEFT JOIN accounter_schema.business_tax_category_match tcm
      ON tcm.business_id = b.id
      AND tcm.owner_id = c.owner_id
    LEFT JOIN tags_by_charge tgc ON tgc.charge_id = c.id
    LEFT JOIN accounter_schema.business_trip_charges btc ON btc.charge_id = c.id
    LEFT JOIN ledger_by_charge lbc ON lbc.charge_id = c.id
    LEFT JOIN years_of_relevance y ON y.charge_id = c.id
    LEFT JOIN (
      SELECT DISTINCT charge_id
      FROM accounter_schema.depreciation
    ) d ON d.charge_id = c.id
  )
  SELECT
    ec.*,
    ABS(ec.event_amount) as abs_event_amount
  FROM enriched_charges ec
  WHERE
  ($isBusinessIds = 0 OR ec.business_array && $businessIds)
  AND ($isExcludedBusinessIds = 0 OR NOT COALESCE(ec.business_array && $excludedBusinessIds, FALSE))
  AND ($fromDate ::TEXT IS NULL OR COALESCE(ec.documents_min_date, ec.transactions_min_event_date)::TEXT::DATE >= date_trunc('day', $fromDate ::DATE))
  AND ($fromAnyDate ::TEXT IS NULL OR GREATEST(ec.documents_max_date, ec.transactions_max_event_date, ec.transactions_max_debit_date, ec.ledger_max_invoice_date, ec.ledger_max_value_date)::TEXT::DATE >= date_trunc('day', $fromAnyDate ::DATE))
  AND ($toDate ::TEXT IS NULL OR COALESCE(ec.documents_max_date, ec.transactions_max_event_date)::TEXT::DATE <= date_trunc('day', $toDate ::DATE))
  AND ($toAnyDate ::TEXT IS NULL OR LEAST(ec.documents_min_date, ec.transactions_min_event_date, ec.transactions_min_debit_date, ec.ledger_min_invoice_date, ec.ledger_min_value_date)::TEXT::DATE <= date_trunc('day', $toAnyDate ::DATE))
  AND ($chargeType = 'ALL' OR ($chargeType = 'INCOME' AND ec.transactions_event_amount > 0) OR ($chargeType = 'EXPENSE' AND ec.transactions_event_amount <= 0))
  AND ($type::accounter_schema.charge_type IS NULL OR ec.type = $type)
  AND ($withoutInvoice = FALSE OR COALESCE(ec.invoices_count, 0) = 0)
  AND ($withoutReceipt = FALSE OR (COALESCE(ec.receipts_count, 0) = 0 AND (ec.no_invoices_required IS FALSE)))
  AND ($withoutDocuments = FALSE OR COALESCE(ec.documents_count, 0) = 0)
  AND ($withoutTransactions = FALSE OR COALESCE(ec.transactions_count, 0) = 0)
  AND ($withOpenDocuments = FALSE OR ec.open_docs_flag IS TRUE)
  AND ($withoutLedger = FALSE OR COALESCE(ec.ledger_count, 0) = 0)
  AND ($isAccountantStatuses = 0 OR ec.accountant_status = ANY ($accountantStatuses::accounter_schema.accountant_status[]))
  AND ($isTags = 0 OR ec.tags && $tags)
  AND ($isExcludedTags = 0 OR NOT COALESCE(ec.tags && $excludedTags, FALSE))
  AND ($withoutTags = FALSE OR COALESCE(array_length(ec.tags, 1), 0) = 0)
  AND ($isBusinessTripIds = 0 OR ec.business_trip_id = ANY ($businessTripIds::uuid[]))
  AND ($isAccountIds = 0 OR ec.account_array && $accountIds::uuid[])
  AND ($isExcludedAccountIds = 0 OR NOT COALESCE(ec.account_array && $excludedAccountIds::uuid[], FALSE))
  AND ($withMissingCounterparty = FALSE OR COALESCE(ec.missing_counterparty_transactions, false) = true OR COALESCE(ec.missing_counterparty_documents, false) = true)
  ORDER BY
  CASE WHEN $asc = true AND $sortColumn = 'event_date' THEN (COALESCE(ec.transactions_min_debit_date, ec.transactions_min_event_date, ec.documents_min_date, ec.ledger_min_value_date, ec.ledger_min_invoice_date), COALESCE(ec.documents_min_date, ec.transactions_min_event_date), ec.id) END ASC,
  CASE WHEN $asc = false AND $sortColumn = 'event_date' THEN (COALESCE(ec.transactions_min_debit_date, ec.transactions_min_event_date, ec.documents_min_date, ec.ledger_min_value_date, ec.ledger_min_invoice_date), COALESCE(ec.documents_min_date, ec.transactions_min_event_date), ec.id) END DESC,
  CASE WHEN $asc = true AND $sortColumn = 'event_amount' THEN (ec.event_amount, ec.id) END ASC,
  CASE WHEN $asc = false AND $sortColumn = 'event_amount' THEN (ec.event_amount, ec.id) END DESC,
  CASE WHEN $asc = true AND $sortColumn = 'abs_event_amount' THEN ABS(cast(ec.event_amount as DECIMAL)) END ASC,
  CASE WHEN $asc = false AND $sortColumn = 'abs_event_amount' THEN ABS(cast(ec.event_amount as DECIMAL)) END DESC,
  ec.id;
  `;
const getSimilarCharges = sql `
      WITH owner_charges AS (
        SELECT c.*
        FROM accounter_schema.charges c
        WHERE c.owner_id = $ownerId
      ),
      transactions_by_charge AS (
        SELECT t.charge_id,
               min(t.event_date) AS min_event_date,
               max(t.event_date) AS max_event_date,
               min(COALESCE(t.debit_date_override, t.debit_date)) AS min_debit_date,
               max(COALESCE(t.debit_date_override, t.debit_date)) AS max_debit_date,
               sum(t.amount) AS event_amount,
               count(*) AS transactions_count,
               count(*) FILTER (
                 WHERE t.business_id IS NULL
                 OR COALESCE(t.debit_date_override, t.debit_date) IS NULL
               ) > 0 AS invalid_transactions,
               array_agg(DISTINCT t.currency) AS currency_array
        FROM accounter_schema.transactions t
        JOIN owner_charges oc ON oc.id = t.charge_id
        GROUP BY t.charge_id
      ),
      documents_by_charge AS (
        SELECT d.charge_id,
               min(d.date) FILTER (
                 WHERE d.type = ANY (
                   ARRAY[
                     'INVOICE'::accounter_schema.document_type,
                     'INVOICE_RECEIPT'::accounter_schema.document_type,
                     'RECEIPT'::accounter_schema.document_type,
                     'CREDIT_INVOICE'::accounter_schema.document_type
                   ]
                 )
               ) AS min_event_date,
               min(d.date) AS min_any_event_date,
               max(d.date) FILTER (
                 WHERE d.type = ANY (
                   ARRAY[
                     'INVOICE'::accounter_schema.document_type,
                     'INVOICE_RECEIPT'::accounter_schema.document_type,
                     'RECEIPT'::accounter_schema.document_type,
                     'CREDIT_INVOICE'::accounter_schema.document_type
                   ]
                 )
               ) AS max_event_date,
               max(d.date) AS max_any_event_date,
               sum(
                 d.total_amount *
                 CASE
                   WHEN d.creditor_id = oc.owner_id THEN 1
                   ELSE '-1'::integer
                 END::double precision
               ) FILTER (
                 WHERE businesses.can_settle_with_receipt = true
                 AND (
                   d.type = ANY (
                     ARRAY[
                       'RECEIPT'::accounter_schema.document_type,
                       'INVOICE_RECEIPT'::accounter_schema.document_type
                     ]
                   )
                 )
               ) AS receipt_event_amount,
               sum(
                 d.total_amount *
                 CASE
                   WHEN d.creditor_id = oc.owner_id THEN 1
                   ELSE '-1'::integer
                 END::double precision
               ) FILTER (
                 WHERE d.type = ANY (
                   ARRAY[
                     'INVOICE'::accounter_schema.document_type,
                     'INVOICE_RECEIPT'::accounter_schema.document_type,
                     'CREDIT_INVOICE'::accounter_schema.document_type
                   ]
                 )
               ) AS invoice_event_amount,
               sum(
                 d.vat_amount *
                 CASE
                   WHEN d.creditor_id = oc.owner_id THEN 1
                   ELSE '-1'::integer
                 END::double precision
               ) FILTER (
                 WHERE businesses.can_settle_with_receipt = true
                 AND (
                   d.type = ANY (
                     ARRAY[
                       'RECEIPT'::accounter_schema.document_type,
                       'INVOICE_RECEIPT'::accounter_schema.document_type
                     ]
                   )
                 )
               ) AS receipt_vat_amount,
               sum(
                 d.vat_amount *
                 CASE
                   WHEN d.creditor_id = oc.owner_id THEN 1
                   ELSE '-1'::integer
                 END::double precision
               ) FILTER (
                 WHERE d.type = ANY (
                   ARRAY[
                     'INVOICE'::accounter_schema.document_type,
                     'INVOICE_RECEIPT'::accounter_schema.document_type,
                     'CREDIT_INVOICE'::accounter_schema.document_type
                   ]
                 )
               ) AS invoice_vat_amount,
               count(*) FILTER (
                 WHERE d.type = ANY (
                   ARRAY[
                     'INVOICE'::accounter_schema.document_type,
                     'INVOICE_RECEIPT'::accounter_schema.document_type,
                     'CREDIT_INVOICE'::accounter_schema.document_type
                   ]
                 )
               ) AS invoices_count,
               count(*) FILTER (
                 WHERE d.type = ANY (
                   ARRAY[
                     'RECEIPT'::accounter_schema.document_type,
                     'INVOICE_RECEIPT'::accounter_schema.document_type
                   ]
                 )
               ) AS receipts_count,
               count(*) AS documents_count,
               count(*) FILTER (
                 WHERE (
                   d.type = ANY (
                     ARRAY[
                       'INVOICE'::accounter_schema.document_type,
                       'INVOICE_RECEIPT'::accounter_schema.document_type,
                       'RECEIPT'::accounter_schema.document_type,
                       'CREDIT_INVOICE'::accounter_schema.document_type
                     ]
                   )
                 )
                 AND (
                   d.debtor_id IS NULL
                   OR d.creditor_id IS NULL
                   OR d.date IS NULL
                   OR d.serial_number IS NULL
                   OR d.vat_amount IS NULL
                   OR d.total_amount IS NULL
                   OR d.charge_id IS NULL
                   OR d.currency_code IS NULL
                 )
                 OR d.type = 'UNPROCESSED'::accounter_schema.document_type
               ) > 0 AS invalid_documents,
               array_agg(d.currency_code) FILTER (
                 WHERE (
                   businesses.can_settle_with_receipt = true
                   AND d.type = ANY (
                     ARRAY[
                       'RECEIPT'::accounter_schema.document_type,
                       'INVOICE_RECEIPT'::accounter_schema.document_type
                     ]
                   )
                 )
                 OR d.type = ANY (
                   ARRAY[
                     'INVOICE'::accounter_schema.document_type,
                     'INVOICE_RECEIPT'::accounter_schema.document_type,
                     'CREDIT_INVOICE'::accounter_schema.document_type
                   ]
                 )
               ) AS currency_array,
               COALESCE(BOOL_OR(doc_issued.status = 'OPEN'), false) AS open_docs_flag
        FROM accounter_schema.documents d
        JOIN owner_charges oc ON oc.id = d.charge_id
        LEFT JOIN accounter_schema.businesses
          ON (
            d.creditor_id = oc.owner_id
            AND d.debtor_id = businesses.id
          )
          OR (
            d.creditor_id = businesses.id
            AND d.debtor_id = oc.owner_id
          )
        LEFT JOIN accounter_schema.documents_issued doc_issued ON d.id = doc_issued.id
        GROUP BY d.charge_id
      ),
      businesses_by_charge AS (
        SELECT base.charge_id,
               array_remove(base.business_array, oc.owner_id) AS business_array,
               array_remove(base.filtered_business_array, oc.owner_id) AS filtered_business_array
        FROM (
          SELECT b.charge_id,
                 array_agg(DISTINCT b.business_id) AS business_array,
                 array_remove(
                   array_agg(
                     DISTINCT CASE
                       WHEN b.is_fee THEN NULL::uuid
                       ELSE b.business_id
                     END
                   ),
                   NULL::uuid
                 ) AS filtered_business_array
          FROM (
            SELECT t.charge_id, t.business_id, t.is_fee
            FROM accounter_schema.transactions t
            JOIN owner_charges oc ON oc.id = t.charge_id
            WHERE t.business_id IS NOT NULL

            UNION

            SELECT d.charge_id, d.creditor_id, false AS is_fee
            FROM accounter_schema.documents d
            JOIN owner_charges oc ON oc.id = d.charge_id
            WHERE d.creditor_id IS NOT NULL

            UNION

            SELECT d.charge_id, d.debtor_id, false AS is_fee
            FROM accounter_schema.documents d
            JOIN owner_charges oc ON oc.id = d.charge_id
            WHERE d.debtor_id IS NOT NULL

            UNION

            SELECT lr.charge_id, lr.credit_entity1, true AS is_fee
            FROM accounter_schema.ledger_records lr
            JOIN owner_charges oc ON oc.id = lr.charge_id
            WHERE lr.credit_entity1 IS NOT NULL

            UNION

            SELECT lr.charge_id, lr.credit_entity2, true AS is_fee
            FROM accounter_schema.ledger_records lr
            JOIN owner_charges oc ON oc.id = lr.charge_id
            WHERE lr.credit_entity2 IS NOT NULL

            UNION

            SELECT lr.charge_id, lr.debit_entity1, true AS is_fee
            FROM accounter_schema.ledger_records lr
            JOIN owner_charges oc ON oc.id = lr.charge_id
            WHERE lr.debit_entity1 IS NOT NULL

            UNION

            SELECT lr.charge_id, lr.debit_entity2, true AS is_fee
            FROM accounter_schema.ledger_records lr
            JOIN owner_charges oc ON oc.id = lr.charge_id
            WHERE lr.debit_entity2 IS NOT NULL
          ) b
          GROUP BY b.charge_id
        ) base
        JOIN owner_charges oc ON oc.id = base.charge_id
      ),
      tags_by_charge AS (
        SELECT ct.charge_id,
               array_agg(ct.tag_id) AS tags_array
        FROM accounter_schema.charge_tags ct
        JOIN owner_charges oc ON oc.id = ct.charge_id
        GROUP BY ct.charge_id
      ),
      ledger_by_charge AS (
        SELECT count(DISTINCT x.id) AS ledger_count,
               array_remove(array_agg(DISTINCT x.financial_entity), NULL::uuid) AS ledger_financial_entities,
               min(x.value_date) AS min_value_date,
               max(x.value_date) AS max_value_date,
               min(x.invoice_date) AS min_invoice_date,
               max(x.invoice_date) AS max_invoice_date,
               x.charge_id
        FROM (
          SELECT lr.charge_id,
                 lr.id,
                 lr.value_date,
                 lr.invoice_date,
                 unnest(
                   ARRAY[
                     lr.credit_entity1,
                     lr.credit_entity2,
                     lr.debit_entity1,
                     lr.debit_entity2
                   ]
                 ) AS financial_entity
          FROM accounter_schema.ledger_records lr
          JOIN owner_charges oc ON oc.id = lr.charge_id
        ) x
        GROUP BY x.charge_id
      )
      SELECT
        c.*
      FROM owner_charges c
      LEFT JOIN transactions_by_charge tbc ON tbc.charge_id = c.id
      LEFT JOIN documents_by_charge dbc ON dbc.charge_id = c.id
      LEFT JOIN businesses_by_charge bbc ON bbc.charge_id = c.id
      LEFT JOIN accounter_schema.businesses b
        ON b.id = bbc.filtered_business_array[1]
        AND array_length(bbc.filtered_business_array, 1) = 1
      LEFT JOIN tags_by_charge tgc ON tgc.charge_id = c.id
      LEFT JOIN ledger_by_charge lbc ON lbc.charge_id = c.id
      WHERE ($withMissingTags IS NOT TRUE OR tgc.tags_array IS NULL)
        AND ($withMissingDescription IS NOT TRUE OR c.user_description IS NULL)
        AND (
          $tagsDifferentThan::UUID[] IS NULL
          OR NOT ((tgc.tags_array @> $tagsDifferentThan) AND (tgc.tags_array <@ $tagsDifferentThan))
        )
        AND (
          $descriptionDifferentThan::TEXT IS NULL
          OR c.user_description IS DISTINCT FROM $descriptionDifferentThan
        )
        AND (
          (b.id IS NOT NULL AND b.id = $businessId)
          OR (
            bbc.business_array IS NOT NULL
            AND bbc.business_array @> $businessArray
            AND bbc.business_array <@ $businessArray
          )
        )
      ORDER BY (
        COALESCE(
          COALESCE(dbc.min_event_date, dbc.min_any_event_date),
          tbc.min_debit_date,
          tbc.min_event_date,
          lbc.min_value_date,
          lbc.min_invoice_date
        ),
        COALESCE(tbc.min_event_date, COALESCE(dbc.min_event_date, dbc.min_any_event_date)),
        c.id
      ) DESC;`;
const deleteChargesByIds = sql `
    DELETE FROM accounter_schema.charges
    WHERE id IN $$chargeIds;`;
let ChargesProvider = class ChargesProvider {
    db;
    auth;
    constructor(db, auth) {
        this.db = db;
        this.auth = auth;
    }
    async batchChargesByIds(ids) {
        const charges = await getChargesByIds.run({
            chargeIds: ids,
        }, this.db);
        return ids.map(id => charges.find(charge => charge.id === id));
    }
    getChargeByIdLoader = new DataLoader((keys) => this.batchChargesByIds(keys));
    async batchChargesByTransactionIds(transactionIds) {
        const charges = await getChargesByTransactionIds.run({
            transactionIds,
        }, this.db);
        charges.map(c => this.getChargeByIdLoader.prime(c.id, c));
        return transactionIds.map(id => charges.find(charge => charge.transaction_id === id));
    }
    getChargeByTransactionIdLoader = new DataLoader((transactionIds) => this.batchChargesByTransactionIds(transactionIds), { cache: false });
    async getChargesByMissingRequiredInfo() {
        return getChargesByMissingRequiredInfo.run(undefined, this.db).then(charges => charges.map(c => {
            this.getChargeByIdLoader.prime(c.id, c);
            return c;
        }));
    }
    async updateCharge(params) {
        await this.auth.canWriteCharge();
        if (params.chargeId) {
            this.invalidateCharge(params.chargeId);
        }
        return updateCharge.run(params, this.db).then(([newCharge]) => {
            if (newCharge) {
                this.invalidateCharge(newCharge.id);
                this.getChargeByIdLoader.prime(newCharge.id, newCharge);
            }
            return newCharge;
        });
    }
    async batchUpdateCharges(params) {
        await this.auth.canWriteCharge();
        params.chargeIds.map(chargeId => {
            if (chargeId) {
                this.invalidateCharge(chargeId);
            }
        });
        const charges = await batchUpdateCharges.run(params, this.db);
        charges.map(charge => this.getChargeByIdLoader.prime(charge.id, charge));
        return charges;
    }
    async updateAccountantApproval(params) {
        await this.auth.canWriteCharge();
        const [newCharge] = await updateAccountantApproval.run(params, this.db);
        if (newCharge) {
            this.getChargeByIdLoader.prime(newCharge.id, newCharge);
        }
        return newCharge;
    }
    async generateCharge(params, dbConnection) {
        await this.auth.canWriteCharge();
        const fullParams = {
            isProperty: false,
            userDescription: null,
            optionalVAT: false,
            optionalDocuments: false,
            accountantStatus: 'UNAPPROVED',
            ...params,
        };
        const [newCharge] = await generateCharge.run(fullParams, dbConnection ?? this.db);
        if (newCharge) {
            this.getChargeByIdLoader.prime(newCharge.id, newCharge);
        }
        return newCharge;
    }
    getChargesByFilters(params) {
        const isOwnerIds = !!params?.ownerIds?.filter(Boolean).length;
        const isBusinessIds = !!params?.businessIds?.filter(Boolean).length;
        const isIDs = !!params?.IDs?.length;
        const isTags = !!params?.tags?.length;
        const isAccountantStatuses = !!params?.accountantStatuses?.length;
        const isBusinessTripIds = !!params?.businessTripIds?.filter(Boolean).length;
        const isAccountIds = !!params?.accountIds?.filter(Boolean).length;
        const isExcludedBusinessIds = !!params?.excludedBusinessIds?.filter(Boolean).length;
        const isExcludedTags = !!params?.excludedTags?.length;
        const isExcludedAccountIds = !!params?.excludedAccountIds?.filter(Boolean).length;
        // A value in both an include and its exclude list is dropped: the predicates are
        // ANDed in SQL, so exclude wins.
        const freeText = normalizeSearchText(params.freeText);
        const excludedFreeText = normalizeSearchText(params.excludedFreeText);
        const defaults = {
            asc: false,
            sortColumn: 'event_date',
        };
        const fullParams = {
            ...defaults,
            isOwnerIds: isOwnerIds ? 1 : 0,
            isBusinessIds: isBusinessIds ? 1 : 0,
            isIDs: isIDs ? 1 : 0,
            isTags: isTags ? 1 : 0,
            isAccountantStatuses: isAccountantStatuses ? 1 : 0,
            isBusinessTripIds: isBusinessTripIds ? 1 : 0,
            isAccountIds: isAccountIds ? 1 : 0,
            isExcludedBusinessIds: isExcludedBusinessIds ? 1 : 0,
            isExcludedTags: isExcludedTags ? 1 : 0,
            isExcludedAccountIds: isExcludedAccountIds ? 1 : 0,
            ...params,
            fromDate: params.fromDate ?? null,
            toDate: params.toDate ?? null,
            ownerIds: isOwnerIds ? params.ownerIds : [null],
            businessIds: isBusinessIds ? params.businessIds : null,
            IDs: isIDs ? params.IDs : [null],
            tags: isTags ? params.tags : null,
            businessTripIds: isBusinessTripIds ? params.businessTripIds : null,
            accountIds: isAccountIds ? params.accountIds : null,
            excludedBusinessIds: isExcludedBusinessIds ? params.excludedBusinessIds : null,
            excludedTags: isExcludedTags ? params.excludedTags : null,
            excludedAccountIds: isExcludedAccountIds ? params.excludedAccountIds : null,
            withMissingCounterparty: params.withMissingCounterparty ?? false,
            chargeType: params.chargeType ?? 'ALL',
            withoutInvoice: params.withoutInvoice ?? false,
            withoutReceipt: params.withoutReceipt ?? false,
            withoutDocuments: params.withoutDocuments ?? false,
            withOpenDocuments: params.withOpenDocuments ?? false,
            withoutTransactions: params.withoutTransactions ?? false,
            withoutLedger: params.withoutLedger ?? false,
            withoutTags: params.withoutTags ?? false,
            accountantStatuses: isAccountantStatuses ? params.accountantStatuses : null,
            // Normalized here rather than at the callers: this is the single chokepoint in
            // front of the SQL, and several resolvers build these params independently.
            freeText,
            freeTextNumeric: toNumericSearchText(freeText),
            excludedFreeText,
            excludedFreeTextNumeric: toNumericSearchText(excludedFreeText),
        };
        return getChargesByFilters.run(fullParams, this.db).then(charges => {
            // The enriched rows are supersets of the plain charge rows — prime the
            // by-id loader so field-resolver helpers don't re-fetch the same charges.
            charges.map(charge => this.getChargeByIdLoader.prime(charge.id, charge));
            return charges;
        });
    }
    async getSimilarCharges(params) {
        try {
            return getSimilarCharges.run(params, this.db);
        }
        catch (error) {
            const message = 'Failed to fetch similar charges';
            console.error(message, error);
            throw new Error(message, { cause: error });
        }
    }
    async deleteChargesByIds(params) {
        const chargeIds = params.chargeIds.filter((chargeId) => !!chargeId);
        await this.auth.canDeleteChargesByIds(chargeIds);
        return deleteChargesByIds.run({ chargeIds }, this.db);
    }
    async deleteCharge(chargeId) {
        return this.deleteChargesByIds({ chargeIds: [chargeId] });
    }
    async invalidateCharge(chargeId) {
        this.getChargeByIdLoader.clear(chargeId);
    }
    clearCache() {
        this.getChargeByIdLoader.clearAll();
    }
};
ChargesProvider = __decorate([
    Injectable({
        scope: Scope.Operation,
        global: true,
    }),
    __metadata("design:paramtypes", [TenantAwareDBClient,
        ChargesAuthorizationProvider])
], ChargesProvider);
export { ChargesProvider };
//# sourceMappingURL=charges.provider.js.map