UNPKG

@accounter/server

Version:
1,018 lines (1,000 loc) • 40.2 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 *; `; 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