@accounter/server
Version:
320 lines (293 loc) • 11.4 kB
text/typescript
import DataLoader from 'dataloader';
import { Injectable, Scope } from 'graphql-modules';
import { DBProvider } from '@modules/app-providers/db.provider.js';
import { sql } from '@pgtyped/runtime';
import type { Optional, TimelessDateString } from '@shared/types';
import type {
IDeleteChargesByIdsParams,
IDeleteChargesByIdsQuery,
IGenerateChargeParams,
IGenerateChargeQuery,
IGenerateChargeResult,
IGetChargesByFiltersParams,
IGetChargesByFiltersQuery,
IGetChargesByFiltersResult,
IGetChargesByFinancialAccountIdsParams,
IGetChargesByFinancialAccountIdsQuery,
IGetChargesByFinancialAccountIdsResult,
IGetChargesByFinancialEntityIdsParams,
IGetChargesByFinancialEntityIdsQuery,
IGetChargesByFinancialEntityIdsResult,
IGetChargesByIdsQuery,
IGetChargesByIdsResult,
IUpdateAccountantApprovalParams,
IUpdateAccountantApprovalQuery,
IUpdateAccountantApprovalResult,
IUpdateChargeParams,
IUpdateChargeQuery,
IUpdateChargeResult,
tags_enumArray,
} from '../types.js';
export type ChargeRequiredWrapper<
T extends {
id: unknown;
owner_id: unknown;
is_conversion: unknown;
is_property: unknown;
accountant_reviewed: unknown;
},
> = Omit<T, 'id' | 'owner_id' | 'is_conversion' | 'is_property' | 'accountant_reviewed'> & {
id: NonNullable<T['id']>;
owner_id: NonNullable<T['owner_id']>;
is_conversion: NonNullable<T['is_conversion']>;
is_property: NonNullable<T['is_property']>;
accountant_reviewed: NonNullable<T['accountant_reviewed']>;
};
const getChargesByIds = sql<IGetChargesByIdsQuery>`
SELECT *
FROM accounter_schema.extended_charges
WHERE id IN $$chargeIds;`;
const getChargesByFinancialAccountIds = sql<IGetChargesByFinancialAccountIdsQuery>`
SELECT c.*, t.account_id
FROM accounter_schema.extended_charges c
LEFT JOIN accounter_schema.transactions t
ON c.id = t.charge_id
WHERE c.id IN (
SELECT charge_id
FROM accounter_schema.transactions
WHERE account_id IN $$financialAccountIDs
AND ($fromDate ::TEXT IS NULL OR event_date::TEXT::DATE >= date_trunc('day', $fromDate ::DATE))
AND ($toDate ::TEXT IS NULL OR event_date::TEXT::DATE <= date_trunc('day', $toDate ::DATE))
)
AND t.event_date = (
SELECT MIN(event_date)
FROM accounter_schema.transactions as t2
WHERE t2.charge_id = c.id
)
ORDER BY t.event_date DESC;`;
const getChargesByFinancialEntityIds = sql<IGetChargesByFinancialEntityIdsQuery>`
SELECT c.*
FROM accounter_schema.extended_charges c
LEFT JOIN accounter_schema.transactions t
ON c.id = t.charge_id
WHERE owner_id IN $$ownerIds
AND t.event_date = (
SELECT MIN(event_date)
FROM accounter_schema.transactions as t2
WHERE t2.charge_id = c.id
)
AND ($fromDate ::TEXT IS NULL OR t.event_date::TEXT::DATE >= date_trunc('day', $fromDate ::DATE))
AND ($toDate ::TEXT IS NULL OR t.event_date::TEXT::DATE <= date_trunc('day', $toDate ::DATE))
ORDER BY t.event_date DESC;`;
const updateCharge = sql<IUpdateChargeQuery>`
UPDATE accounter_schema.charges
SET
owner_id = COALESCE(
$ownerId,
owner_id
),
user_description = COALESCE(
$userDescription,
user_description
),
is_conversion = COALESCE(
$isConversion,
is_conversion
),
is_property = COALESCE(
$isProperty,
is_property
),
accountant_reviewed = COALESCE(
$accountantReviewed,
accountant_reviewed
),
tax_category_id = COALESCE(
$taxCategoryId,
tax_category_id
),
year_of_relevance = COALESCE(
$yearOfRelevance,
year_of_relevance
)
WHERE
id = $chargeId
RETURNING *;
`;
const updateAccountantApproval = sql<IUpdateAccountantApprovalQuery>`
UPDATE accounter_schema.charges
SET
accountant_reviewed = $accountantReviewed
WHERE
id = $chargeId
RETURNING *;
`;
const generateCharge = sql<IGenerateChargeQuery>`
INSERT INTO accounter_schema.charges (owner_id, is_conversion, is_property, accountant_reviewed, user_description)
VALUES ($ownerId, $isConversion, $isProperty, $accountantReviewed, $userDescription)
RETURNING *;
`;
const getChargesByFilters = sql<IGetChargesByFiltersQuery>`
SELECT
ec.*,
ABS(ec.event_amount) as abs_event_amount
FROM accounter_schema.charges c
LEFT JOIN accounter_schema.extended_charges ec
ON c.id = ec.id
WHERE
($isIDs = 0 OR c.id IN $$IDs)
AND ($isOwnerIds = 0 OR c.owner_id IN $$ownerIds)
AND ($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)::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)::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 ($withoutInvoice = FALSE OR COALESCE(ec.invoices_count, 0) = 0)
AND ($withoutDocuments = FALSE OR COALESCE(ec.documents_count, 0) = 0)
AND ($accountantApproval::BOOLEAN IS NULL OR ec.accountant_reviewed = $accountantApproval)
AND ($isTags = 0 OR ec.tags && $tags)
ORDER BY
CASE WHEN $asc = true AND $sortColumn = 'event_date' THEN COALESCE(ec.documents_min_date, ec.transactions_min_debit_date, ec.transactions_min_event_date) END ASC,
CASE WHEN $asc = false AND $sortColumn = 'event_date' THEN COALESCE(ec.documents_min_date, ec.transactions_min_debit_date, ec.transactions_min_event_date) END DESC,
CASE WHEN $asc = true AND $sortColumn = 'event_amount' THEN ec.event_amount END ASC,
CASE WHEN $asc = false AND $sortColumn = 'event_amount' THEN ec.event_amount 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;
`;
type IGetAdjustedChargesByFiltersParams = Optional<
Omit<
IGetChargesByFiltersParams,
'isOwnerIds' | 'isBusinessIds' | 'businessIds' | 'isIDs' | 'isTags' | 'tags'
>,
'ownerIds' | 'IDs' | 'asc' | 'sortColumn' | 'toDate' | 'fromDate'
> & {
toDate?: TimelessDateString | null;
fromDate?: TimelessDateString | null;
tags?: readonly string[] | null;
businessIds?: readonly string[] | null;
};
const deleteChargesByIds = sql<IDeleteChargesByIdsQuery>`
DELETE FROM accounter_schema.charges
WHERE id IN $$chargeIds;`;
@Injectable({
scope: Scope.Singleton,
global: true,
})
export class ChargesProvider {
constructor(private dbProvider: DBProvider) {}
private async batchChargesByIds(ids: readonly string[]) {
const charges = (await getChargesByIds.run(
{
chargeIds: ids,
},
this.dbProvider,
)) as ChargeRequiredWrapper<IGetChargesByIdsResult>[];
return ids.map(id => charges.find(charge => charge.id === id));
}
public getChargeByIdLoader = new DataLoader(
(keys: readonly string[]) => this.batchChargesByIds(keys),
{ cache: false },
);
public getChargesByFinancialAccountIds(params: IGetChargesByFinancialAccountIdsParams) {
return getChargesByFinancialAccountIds.run(params, this.dbProvider) as Promise<
ChargeRequiredWrapper<IGetChargesByFinancialAccountIdsResult>[]
>;
}
private async batchChargesByFinancialAccountIds(financialAccountIDs: readonly string[]) {
const charges = (await getChargesByFinancialAccountIds.run(
{
financialAccountIDs,
fromDate: null,
toDate: null,
},
this.dbProvider,
)) as ChargeRequiredWrapper<IGetChargesByFinancialAccountIdsResult>[];
return financialAccountIDs.map(accountId =>
charges.filter(charge => charge.account_id === accountId),
);
}
public getChargeByFinancialAccountIDsLoader = new DataLoader(
(keys: readonly string[]) => this.batchChargesByFinancialAccountIds(keys),
{
cache: false,
},
);
public getChargesByFinancialEntityIds(params: IGetChargesByFinancialEntityIdsParams) {
return getChargesByFinancialEntityIds.run(params, this.dbProvider) as Promise<
ChargeRequiredWrapper<IGetChargesByFinancialEntityIdsResult>[]
>;
}
private async batchChargesByFinancialEntityIds(ownerIds: readonly string[]) {
const charges = (await getChargesByFinancialEntityIds.run(
{
ownerIds,
fromDate: null,
toDate: null,
},
this.dbProvider,
)) as ChargeRequiredWrapper<IGetChargesByFinancialEntityIdsResult>[];
return ownerIds.map(id => charges.filter(charge => charge.owner_id === id));
}
public getChargeByFinancialEntityIdLoader = new DataLoader(
(keys: readonly string[]) => this.batchChargesByFinancialEntityIds(keys),
{
cache: false,
},
);
public updateCharge(params: IUpdateChargeParams) {
return updateCharge.run(params, this.dbProvider) as Promise<
ChargeRequiredWrapper<IUpdateChargeResult>[]
>;
}
public updateAccountantApproval(params: IUpdateAccountantApprovalParams) {
return updateAccountantApproval.run(params, this.dbProvider) as Promise<
ChargeRequiredWrapper<IUpdateAccountantApprovalResult>[]
>;
}
public generateCharge(params: IGenerateChargeParams) {
const fullParams = {
accountantReviewed: false,
isConversion: false,
isProperty: false,
userDescription: null,
...params,
};
return generateCharge.run(fullParams, this.dbProvider) as Promise<
ChargeRequiredWrapper<IGenerateChargeResult>[]
>;
}
public getChargesByFilters(params: IGetAdjustedChargesByFiltersParams) {
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 defaults = {
asc: false,
sortColumn: 'event_date',
};
const fullParams: IGetChargesByFiltersParams = {
...defaults,
isOwnerIds: isOwnerIds ? 1 : 0,
isBusinessIds: isBusinessIds ? 1 : 0,
isIDs: isIDs ? 1 : 0,
isTags: isTags ? 1 : 0,
...params,
fromDate: params.fromDate ?? null,
toDate: params.toDate ?? null,
ownerIds: isOwnerIds ? params.ownerIds! : [null],
businessIds: isBusinessIds ? (params.businessIds! as string[]) : null,
IDs: isIDs ? params.IDs! : [null],
tags: isTags ? (params.tags! as tags_enumArray) : null,
chargeType: params.chargeType ?? 'ALL',
withoutInvoice: params.withoutInvoice ?? false,
withoutDocuments: params.withoutDocuments ?? false,
};
return getChargesByFilters.run(fullParams, this.dbProvider) as Promise<
IGetChargesByFiltersResult[]
>;
}
public deleteChargesByIds(params: IDeleteChargesByIdsParams) {
return deleteChargesByIds.run(params, this.dbProvider);
}
}