UNPKG

@accounter/server

Version:
105 lines 4.48 kB
import { __decorate, __metadata } from "tslib"; import { Injectable, Scope } from 'graphql-modules'; import { sql } from '@pgtyped/runtime'; import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js'; // Cap the number of ids bound per query. The combined query references the id array nine // times (one per UNION ALL branch), so each chunk binds up to 9 * CHUNK_SIZE parameters — // kept well under Postgres' 65,535 parameter limit. const CHUNK_SIZE = 1000; // Maps the `source` discriminator emitted by each UNION ALL branch to its counts key. const SOURCE_TO_KEY = { transactions: 'transactions', documents: 'documents', misc_expenses: 'miscExpenses', ledger: 'ledgerRecords', }; // A single statement that counts, per business id, how often the business is referenced // across each source. Every id-bearing column is projected into a common (business_id, // source) shape via UNION ALL so a business that appears in more than one column // (documents/misc_expenses debtor+creditor, ledger debit/credit entities) is attributed // correctly. A row that references the same business on two columns is counted once per // column, which is acceptable for a usage indicator. Combining all sources into one query // matters because TenantAwareDBClient serializes queries on a single mutex-guarded // connection, so four separate queries would be four sequential transactions. const getUsageCountsByBusinessIds = sql ` SELECT business_id, source, COUNT(*) AS count FROM ( SELECT business_id, 'transactions' AS source FROM accounter_schema.transactions WHERE business_id IN $$businessIds UNION ALL SELECT debtor_id AS business_id, 'documents' AS source FROM accounter_schema.documents WHERE debtor_id IN $$businessIds UNION ALL SELECT creditor_id AS business_id, 'documents' AS source FROM accounter_schema.documents WHERE creditor_id IN $$businessIds UNION ALL SELECT debtor_id AS business_id, 'misc_expenses' AS source FROM accounter_schema.misc_expenses WHERE debtor_id IN $$businessIds UNION ALL SELECT creditor_id AS business_id, 'misc_expenses' AS source FROM accounter_schema.misc_expenses WHERE creditor_id IN $$businessIds UNION ALL SELECT debit_entity1 AS business_id, 'ledger' AS source FROM accounter_schema.ledger_records WHERE debit_entity1 IN $$businessIds UNION ALL SELECT debit_entity2 AS business_id, 'ledger' AS source FROM accounter_schema.ledger_records WHERE debit_entity2 IN $$businessIds UNION ALL SELECT credit_entity1 AS business_id, 'ledger' AS source FROM accounter_schema.ledger_records WHERE credit_entity1 IN $$businessIds UNION ALL SELECT credit_entity2 AS business_id, 'ledger' AS source FROM accounter_schema.ledger_records WHERE credit_entity2 IN $$businessIds ) AS usage GROUP BY business_id, source;`; let BusinessUsageProvider = class BusinessUsageProvider { db; constructor(db) { this.db = db; } async getUsageByBusinessIds(ids) { const usage = new Map(); const uniqueIds = [...new Set(ids)]; if (uniqueIds.length === 0) { return usage; } // default every requested id to zero so unused businesses are represented for (const id of uniqueIds) { usage.set(id, { transactions: 0, documents: 0, miscExpenses: 0, ledgerRecords: 0 }); } // chunk the ids to stay within Postgres' bind-parameter limit (see CHUNK_SIZE) for (let i = 0; i < uniqueIds.length; i += CHUNK_SIZE) { const chunk = uniqueIds.slice(i, i + CHUNK_SIZE); const rows = await getUsageCountsByBusinessIds.run({ businessIds: chunk }, this.db); for (const row of rows) { if (!row.business_id || !row.source) { continue; } const key = SOURCE_TO_KEY[row.source]; const counts = usage.get(row.business_id); if (key && counts) { counts[key] = Number(row.count ?? 0); } } } return usage; } }; BusinessUsageProvider = __decorate([ Injectable({ scope: Scope.Operation, global: true, }), __metadata("design:paramtypes", [TenantAwareDBClient]) ], BusinessUsageProvider); export { BusinessUsageProvider }; //# sourceMappingURL=businesses-usage.provider.js.map