@accounter/server
Version:
Accounter GraphQL server
1,018 lines (1,000 loc) • 40.2 kB
JavaScript
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 *;
`;
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 || '%')
)
),
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)
),
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,
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,
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,
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
) 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(dbc.invoice_event_amount::numeric, dbc.receipt_event_amount::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.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,
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 ($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 ($isBusinessTripIds = 0 OR ec.business_trip_id = ANY ($businessTripIds::uuid[]))
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 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,
...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,
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,
accountantStatuses: isAccountantStatuses ? params.accountantStatuses : null,
};
return getChargesByFilters.run(fullParams, this.db);
}
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