@accounter/server
Version:
915 lines (833 loc) • 31.4 kB
text/typescript
import { GraphQLError } from 'graphql';
import { BusinessTripsProvider } from '@modules/business-trips/providers/business-trips.provider.js';
import { DocumentsProvider } from '@modules/documents/providers/documents.provider.js';
import { ledgerGenerationByCharge } from '@modules/ledger/helpers/ledger-by-charge-type.helper.js';
import { ledgerRecordsGenerationFullMatchComparison } from '@modules/ledger/helpers/ledgrer-storage.helper.js';
import { LedgerProvider } from '@modules/ledger/providers/ledger.provider.js';
import { generateLedgerRecordsForFinancialCharge } from '@modules/ledger/resolvers/ledger-generation/financial-ledger-generation.resolver.js';
import { MiscExpensesProvider } from '@modules/misc-expenses/providers/misc-expenses.provider.js';
import { ChargeTagsProvider } from '@modules/tags/providers/charge-tags.provider.js';
import { TagsProvider } from '@modules/tags/providers/tags.provider.js';
import { TransactionsProvider } from '@modules/transactions/providers/transactions.provider.js';
import { EMPTY_UUID } from '@shared/constants';
import { ChargeSortByField, ChargeTypeEnum } from '@shared/enums';
import type { Resolvers } from '@shared/gql-types';
import { getChargeType } from '../helpers/charge-type.js';
import { deleteCharges } from '../helpers/delete-charges.helper.js';
import { mergeChargesExecutor } from '../helpers/merge-charges.hepler.js';
import { ChargeSpreadProvider } from '../providers/charge-spread.provider.js';
import { ChargeRequiredWrapper, ChargesProvider } from '../providers/charges.provider.js';
import type {
accountant_statusArray,
ChargesModule,
IGetChargesByIdsResult,
IUpdateChargeParams,
} from '../types.js';
import {
commonChargeFields,
commonDocumentsFields,
commonFinancialAccountFields,
commonFinancialEntityFields,
} from './common.js';
export const chargesResolvers: ChargesModule.Resolvers &
Pick<Resolvers, 'UpdateChargeResult' | 'MergeChargeResult'> = {
Query: {
chargesByIDs: async (_, { chargeIDs }, { injector }) => {
if (chargeIDs.length === 0) {
return [];
}
const dbCharges = await injector.get(ChargesProvider).getChargeByIdLoader.loadMany(chargeIDs);
if (!dbCharges) {
if (chargeIDs.length === 1) {
throw new GraphQLError(`Charge ID="${chargeIDs[0]}" not found`);
} else {
throw new GraphQLError(`Couldn't find any charges`);
}
}
const charges = chargeIDs.map(id => {
const charge = dbCharges.find(charge => charge && 'id' in charge && charge.id === id);
if (!charge) {
throw new GraphQLError(`Charge ID="${id}" not found`);
}
return charge as ChargeRequiredWrapper<IGetChargesByIdsResult>;
});
return charges;
},
allCharges: async (_, { filters, page, limit }, { injector }) => {
// handle sort column
let sortColumn: 'event_date' | 'event_amount' | 'abs_event_amount' = 'event_date';
switch (filters?.sortBy?.field) {
case ChargeSortByField.Amount:
sortColumn = 'event_amount';
break;
case ChargeSortByField.AbsAmount:
sortColumn = 'abs_event_amount';
break;
case ChargeSortByField.Date:
sortColumn = 'event_date';
break;
}
const charges = await injector
.get(ChargesProvider)
.getChargesByFilters({
ownerIds: filters?.byOwners,
fromAnyDate: filters?.fromAnyDate,
toAnyDate: filters?.toAnyDate,
sortColumn,
asc: filters?.sortBy?.asc !== false,
chargeType: filters?.chargesType,
businessIds: filters?.byBusinesses,
withoutInvoice: filters?.withoutInvoice,
withoutReceipt: filters?.withoutReceipt,
withoutDocuments: filters?.withoutDocuments,
withoutTransactions: filters?.withoutTransactions,
withoutLedger: filters?.withoutLedger,
tags: filters?.byTags,
accountantStatuses: filters?.accountantStatus as accountant_statusArray | undefined,
})
.catch(e => {
throw new Error(e.message);
});
const pageCharges = charges.slice(page * limit - limit, page * limit);
return {
__typename: 'PaginatedCharges',
nodes: pageCharges,
pageInfo: {
totalPages: Math.ceil(charges.length / limit),
},
};
},
chargesWithMissingRequiredInfo: async (_, { page, limit }, { injector, adminContext }) => {
const chargeIds = new Set<string>();
// get by transactions
const getByTransactionsPromise = injector
.get(TransactionsProvider)
.getTransactionsByMissingRequiredInfo()
.then(transactions => {
transactions.map(transaction => {
if (transaction.charge_id) {
chargeIds.add(transaction.charge_id);
}
});
});
// get by documents
const getByDocumentsPromise = injector
.get(DocumentsProvider)
.getDocumentsByMissingRequiredInfo()
.then(documents => {
documents.map(document => {
if (document.charge_id) {
chargeIds.add(document.charge_id);
}
});
});
// get by charge
const getByChargesPromise = injector
.get(ChargesProvider)
.getChargesByMissingRequiredInfo()
.then(charges => {
charges.map(charge => {
if (charge.id) {
chargeIds.add(charge.id);
}
});
});
await Promise.all([getByTransactionsPromise, getByDocumentsPromise, getByChargesPromise]);
const charges = await Promise.all(
Array.from(chargeIds).map(
async id =>
await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(id)
.then(charge => {
if (!charge) {
throw new GraphQLError(`Charge ID="${id}" not found`);
}
return charge;
})
.catch(e => {
const message = `Error loading charge ID="${id}"`;
console.error(`${message}: ${e}`);
throw new GraphQLError(message);
}),
),
);
const pageCharges = charges
.filter(charge => charge.owner_id === adminContext.defaultAdminBusinessId)
.sort((chargeA, chargeB) => {
const dateA =
(
chargeA.documents_min_date ||
chargeA.transactions_min_debit_date ||
chargeA.transactions_min_event_date ||
chargeA.ledger_min_value_date ||
chargeA.ledger_min_invoice_date
)?.getTime() ?? 0;
const dateB =
(
chargeB.documents_min_date ||
chargeB.transactions_min_debit_date ||
chargeB.transactions_min_event_date ||
chargeB.ledger_min_value_date ||
chargeB.ledger_min_invoice_date
)?.getTime() ?? 0;
if (dateA > dateB) {
return -1;
}
if (dateA < dateB) {
return 1;
}
return chargeA.id.localeCompare(chargeB.id);
})
.slice(page * limit - limit, page * limit);
return {
__typename: 'PaginatedCharges',
nodes: pageCharges,
pageInfo: {
totalPages: Math.ceil(charges.length / limit),
},
};
},
},
Mutation: {
updateCharge: async (_, { chargeId, fields }, { injector }) => {
const adjustedFields: IUpdateChargeParams = {
accountantStatus: fields.accountantApproval,
type: fields.isConversion ? 'CONVERSION' : undefined,
isProperty: fields.isProperty,
isInvoicePaymentDifferentCurrency: fields.isInvoicePaymentDifferentCurrency,
ownerId: fields.ownerId,
userDescription: fields.userDescription,
taxCategoryId: fields.defaultTaxCategoryID,
optionalVAT: fields.optionalVAT,
chargeId,
};
try {
injector.get(ChargesProvider).getChargeByIdLoader.clear(chargeId);
const res = await injector
.get(ChargesProvider)
.updateCharge({ ...adjustedFields })
.catch(e => {
console.error(e);
throw new GraphQLError(`Error updating charge ID="${chargeId}"`);
});
const updatedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(res[0].id)
.catch(e => {
console.error(e);
throw new GraphQLError(`Error loading updated charge ID="${chargeId}"`);
});
if (!updatedCharge) {
throw new Error(`Charge ID="${chargeId}" not found`);
}
const indirectUpdatesPromises: Array<Promise<unknown>> = [];
// handle tags
if (fields?.tags) {
const newTagIDs = fields.tags.map(t => t.id);
const updateTagsPromise = async () => {
const tagsPromises: Array<Promise<unknown>> = [];
const pastTags = await injector
.get(ChargeTagsProvider)
.getTagsByChargeIDLoader.load(chargeId)
.then(res => res.map(tag => tag.id!));
// clear removed tags
const tagsToRemove = pastTags.filter(tagId => !newTagIDs.includes(tagId));
if (tagsToRemove.length) {
tagsPromises.push(
injector
.get(ChargeTagsProvider)
.clearChargeTags({ chargeId, tagIDs: tagsToRemove })
.catch(e => {
console.error(e);
throw new GraphQLError(
`Error clearing tags IDs="${tagsToRemove}" to charge ID="${chargeId}"`,
);
}),
);
}
// add new tags
const tagIDsToAdd = newTagIDs.filter(tagId => !pastTags.includes(tagId));
for (const tagId of tagIDsToAdd) {
tagsPromises.push(
injector
.get(ChargeTagsProvider)
.insertChargeTag({ chargeId, tagId })
.catch(e => {
console.error(e);
throw new GraphQLError(
`Error adding tag ID="${tagId}" to charge ID="${chargeId}"`,
);
}),
);
}
await Promise.all(tagsPromises);
};
indirectUpdatesPromises.push(updateTagsPromise());
}
// handle business trip
if (fields?.businessTripID) {
indirectUpdatesPromises.push(
injector
.get(BusinessTripsProvider)
.updateChargeBusinessTrip(
chargeId,
fields.businessTripID === EMPTY_UUID ? null : fields.businessTripID,
)
.catch(e => {
console.error(e);
throw new GraphQLError(`Error updating business trip for charge ID="${chargeId}"`);
}),
);
}
// handle charge spread
if (fields?.yearsOfRelevance?.length) {
const updateSpreadRecords = async () => {
await injector
.get(ChargeSpreadProvider)
.deleteAllChargeSpreadByChargeIds({ chargeIds: [chargeId] })
.catch(e => {
console.error(e);
throw new GraphQLError(`Error deleting spread records for charge ID="${chargeId}"`);
});
return injector
.get(ChargeSpreadProvider)
.insertChargeSpread({
chargeSpread: fields.yearsOfRelevance!.map(record => ({
chargeId,
yearOfRelevance: record.year,
amount: record.amount,
})),
})
.catch(e => {
console.error(e);
throw new GraphQLError(
`Error inserting spread records for charge ID="${chargeId}"`,
);
});
};
indirectUpdatesPromises.push(updateSpreadRecords());
}
await Promise.all(indirectUpdatesPromises);
return { charge: updatedCharge };
} catch (e) {
return {
__typename: 'CommonError',
message:
(e as Error)?.message ??
(e as { errors: Error[] })?.errors.map(e => e.message).toString() ??
'Unknown error',
};
}
},
mergeCharges: async (_, { baseChargeID, chargeIdsToMerge, fields }, { injector }) => {
try {
const charge = await injector.get(ChargesProvider).getChargeByIdLoader.load(baseChargeID);
if (!charge) {
throw new Error(`Charge not found`);
}
if (fields) {
const adjustedFields: IUpdateChargeParams = {
accountantStatus: fields?.accountantApproval,
type: fields?.isConversion ? 'CONVERSION' : undefined,
isProperty: fields?.isProperty,
isInvoicePaymentDifferentCurrency: fields?.isInvoicePaymentDifferentCurrency,
optionalVAT: fields?.optionalVAT,
ownerId: fields?.ownerId,
userDescription: fields?.userDescription,
chargeId: baseChargeID,
};
injector.get(ChargesProvider).getChargeByIdLoader.clear(baseChargeID);
await injector
.get(ChargesProvider)
.updateCharge({ ...adjustedFields })
.catch(e => {
throw new Error(
`Failed to update charge:\n${
(e as Error)?.message ??
(e as { errors: Error[] })?.errors.map(e => e.message).toString()
}`,
);
});
}
await mergeChargesExecutor(chargeIdsToMerge, baseChargeID, injector);
return { charge };
} catch (e) {
return {
__typename: 'CommonError',
message:
(e as Error)?.message ??
(e as { errors: Error[] })?.errors.map(e => e.message).toString() ??
'Unknown error',
};
}
},
deleteCharge: async (_, { chargeId }, { injector }) => {
const charge = await injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId);
if (!charge) {
throw new GraphQLError(`Charge ID="${chargeId}" not found`);
}
if (Number(charge.documents_count ?? 0) > 0 || Number(charge.transactions_count ?? 0) > 0) {
throw new GraphQLError(`Charge ID="${chargeId}" has linked documents/transactions`);
}
await deleteCharges([chargeId], injector);
return true;
},
generateRevaluationCharge: async (_, { date, ownerId }, context, info) => {
const { injector, adminContext } = context;
try {
const [charge] = await injector.get(ChargesProvider).generateCharge({
ownerId,
userDescription: `Revaluation charge for ${date}`,
type: 'FINANCIAL',
taxCategoryId: adminContext.general.taxCategories.exchangeRevaluationTaxCategoryId,
});
if (!charge) {
throw new Error('Error creating new charge');
}
const newExtendedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(charge.id);
if (!newExtendedCharge) {
throw new Error('Error creating new charge');
}
await generateLedgerRecordsForFinancialCharge(
newExtendedCharge,
{ insertLedgerRecordsIfNotExists: true },
context,
info,
);
return newExtendedCharge;
} catch (e) {
console.error(e);
throw new GraphQLError('Error generating revaluation charge');
}
},
generateBankDepositsRevaluationCharge: async (_, { date, ownerId }, context, info) => {
const { injector, adminContext } = context;
try {
const { bankDepositInterestIncomeTaxCategoryId } = adminContext.bankDeposits;
if (!bankDepositInterestIncomeTaxCategoryId) {
throw new GraphQLError('Bank deposit interest income tax category missing');
}
const [charge] = await injector.get(ChargesProvider).generateCharge({
ownerId,
userDescription: `Bank deposits revaluation charge for ${date}`,
type: 'FINANCIAL',
taxCategoryId: bankDepositInterestIncomeTaxCategoryId,
});
if (!charge) {
throw new Error('Error creating new charge');
}
const newExtendedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(charge.id);
if (!newExtendedCharge) {
throw new Error('Error creating new charge');
}
await generateLedgerRecordsForFinancialCharge(
newExtendedCharge,
{ insertLedgerRecordsIfNotExists: true },
context,
info,
);
return newExtendedCharge;
} catch (e) {
console.error(e);
throw new GraphQLError('Error generating bank deposits revaluation charge');
}
},
generateTaxExpensesCharge: async (_, { year, ownerId }, context, info) => {
const {
injector,
adminContext: {
authorities: { taxExpensesTaxCategoryId },
},
} = context;
try {
const [charge] = await injector.get(ChargesProvider).generateCharge({
ownerId,
userDescription: `Tax expenses charge for ${year.substring(0, 4)}`,
type: 'FINANCIAL',
taxCategoryId: taxExpensesTaxCategoryId,
});
if (!charge) {
throw new Error('Error creating new charge');
}
const newExtendedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(charge.id);
if (!newExtendedCharge) {
throw new Error('Error creating new charge');
}
const tagName = 'financial';
const addTagPromise = async () => {
const tag = await injector
.get(TagsProvider)
.getTagByNameLoader.load(tagName)
.catch(() => {
throw new GraphQLError(`Error adding "${tagName}" tag`);
});
if (!tag) {
throw new GraphQLError(`"${tagName}" tag not found`);
}
await injector
.get(ChargeTagsProvider)
.insertChargeTag({ chargeId: newExtendedCharge.id, tagId: tag.id })
.catch(() => {
throw new GraphQLError(
`Error adding "${tagName}" tag to charge ID="${newExtendedCharge.id}"`,
);
});
};
const generateLedgerPromise = generateLedgerRecordsForFinancialCharge(
newExtendedCharge,
{ insertLedgerRecordsIfNotExists: true },
context,
info,
);
await Promise.all([addTagPromise(), generateLedgerPromise]);
return newExtendedCharge;
} catch (e) {
console.error(e);
throw new GraphQLError('Error generating tax expenses charge');
}
},
generateDepreciationCharge: async (_, { year, ownerId }, context, info) => {
const {
injector,
adminContext: {
depreciation: { accumulatedDepreciationTaxCategoryId },
},
} = context;
if (!accumulatedDepreciationTaxCategoryId) {
throw new GraphQLError('Accumulated depreciation tax category missing');
}
try {
const [charge] = await injector.get(ChargesProvider).generateCharge({
ownerId,
userDescription: `Depreciation charge for ${year.substring(0, 4)}`,
type: 'FINANCIAL',
taxCategoryId: accumulatedDepreciationTaxCategoryId,
});
if (!charge) {
throw new Error('Error creating new charge');
}
const newExtendedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(charge.id);
if (!newExtendedCharge) {
throw new Error('Error creating new charge');
}
const tagName = 'financial';
const addTagPromise = async () => {
const tag = await injector
.get(TagsProvider)
.getTagByNameLoader.load(tagName)
.catch(() => {
throw new GraphQLError(`Error adding "${tagName}" tag`);
});
if (!tag) {
throw new GraphQLError(`"${tagName}" tag not found`);
}
await injector
.get(ChargeTagsProvider)
.insertChargeTag({ chargeId: newExtendedCharge.id, tagId: tag.id })
.catch(() => {
throw new GraphQLError(
`Error adding "${tagName}" tag to charge ID="${newExtendedCharge.id}"`,
);
});
};
const generateLedgerPromise = generateLedgerRecordsForFinancialCharge(
newExtendedCharge,
{ insertLedgerRecordsIfNotExists: true },
context,
info,
);
await Promise.all([addTagPromise(), generateLedgerPromise]);
return newExtendedCharge;
} catch (e) {
console.error(e);
throw new GraphQLError('Error generating depreciation charge');
}
},
generateRecoveryReserveCharge: async (_, { year, ownerId }, context, info) => {
const { injector, adminContext } = context;
const { recoveryReserveTaxCategoryId } = adminContext.salaries;
if (!recoveryReserveTaxCategoryId) {
throw new GraphQLError('Recovery reserve tax category missing');
}
try {
const [charge] = await injector.get(ChargesProvider).generateCharge({
ownerId,
userDescription: `Recovery reserve charge for ${year.substring(0, 4)}`,
type: 'FINANCIAL',
taxCategoryId: recoveryReserveTaxCategoryId,
});
if (!charge) {
throw new Error('Error creating new charge');
}
const newExtendedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(charge.id);
if (!newExtendedCharge) {
throw new Error('Error creating new charge');
}
const tagName = 'financial';
const addTagPromise = async () => {
const tag = await injector
.get(TagsProvider)
.getTagByNameLoader.load(tagName)
.catch(() => {
throw new GraphQLError(`Error adding "${tagName}" tag`);
});
if (!tag) {
throw new GraphQLError(`"${tagName}" tag not found`);
}
await injector
.get(ChargeTagsProvider)
.insertChargeTag({ chargeId: newExtendedCharge.id, tagId: tag.id })
.catch(() => {
throw new GraphQLError(
`Error adding "${tagName}" tag to charge ID="${newExtendedCharge.id}"`,
);
});
};
const generateLedgerPromise = generateLedgerRecordsForFinancialCharge(
newExtendedCharge,
{ insertLedgerRecordsIfNotExists: true },
context,
info,
);
await Promise.all([addTagPromise(), generateLedgerPromise]);
return newExtendedCharge;
} catch (e) {
console.error(e);
throw new GraphQLError('Error generating recovery reserve charge');
}
},
generateVacationReserveCharge: async (_, { year, ownerId }, context, info) => {
const { injector, adminContext } = context;
const { vacationReserveTaxCategoryId } = adminContext.salaries;
if (!vacationReserveTaxCategoryId) {
throw new GraphQLError('Vacation reserve tax category missing');
}
try {
const [charge] = await injector.get(ChargesProvider).generateCharge({
ownerId,
userDescription: `Vacation reserves charge for ${year.substring(0, 4)}`,
type: 'FINANCIAL',
taxCategoryId: vacationReserveTaxCategoryId,
});
if (!charge) {
throw new Error('Error creating new charge');
}
const newExtendedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(charge.id);
if (!newExtendedCharge) {
throw new Error('Error creating new charge');
}
const tagName = 'financial';
const addTagPromise = async () => {
const tag = await injector
.get(TagsProvider)
.getTagByNameLoader.load(tagName)
.catch(() => {
throw new GraphQLError(`Error adding "${tagName}" tag`);
});
if (!tag) {
throw new GraphQLError(`"${tagName}" tag not found`);
}
await injector
.get(ChargeTagsProvider)
.insertChargeTag({ chargeId: newExtendedCharge.id, tagId: tag.id })
.catch(() => {
throw new GraphQLError(
`Error adding "${tagName}" tag to charge ID="${newExtendedCharge.id}"`,
);
});
};
const generateLedgerPromise = generateLedgerRecordsForFinancialCharge(
newExtendedCharge,
{ insertLedgerRecordsIfNotExists: true },
context,
info,
);
await Promise.all([addTagPromise(), generateLedgerPromise]);
return newExtendedCharge;
} catch (e) {
console.error(e);
throw new GraphQLError('Error generating vacation reserves charge');
}
},
},
UpdateChargeResult: {
__resolveType: (obj, _context, _info) => {
if (('__typename' in obj && obj.__typename === 'CommonError') || 'message' in obj)
return 'CommonError';
return 'UpdateChargeSuccessfulResult';
},
},
MergeChargeResult: {
__resolveType: (obj, _context, _info) => {
if (('__typename' in obj && obj.__typename === 'CommonError') || 'message' in obj)
return 'CommonError';
return 'MergeChargeSuccessfulResult';
},
},
CommonCharge: {
__isTypeOf: (DbCharge, context) => getChargeType(DbCharge, context) === ChargeTypeEnum.Common,
...commonChargeFields,
},
FinancialCharge: {
__isTypeOf: (DbCharge, context) =>
getChargeType(DbCharge, context) === ChargeTypeEnum.Financial,
...commonChargeFields,
vat: () => null,
totalAmount: () => null,
property: () => false,
conversion: () => false,
salary: () => false,
isInvoicePaymentDifferentCurrency: () => false,
minEventDate: DbCharge => DbCharge.ledger_min_invoice_date,
minDebitDate: DbCharge => DbCharge.ledger_min_value_date,
// minDocumentsDate:
// validationData:
// metadata:
yearsOfRelevance: () => null,
},
ConversionCharge: {
__isTypeOf: (DbCharge, context) =>
getChargeType(DbCharge, context) === ChargeTypeEnum.Conversion,
...commonChargeFields,
},
SalaryCharge: {
__isTypeOf: (DbCharge, context) => getChargeType(DbCharge, context) === ChargeTypeEnum.Salary,
...commonChargeFields,
},
InternalTransferCharge: {
__isTypeOf: (DbCharge, context) =>
getChargeType(DbCharge, context) === ChargeTypeEnum.InternalTransfer,
...commonChargeFields,
},
DividendCharge: {
__isTypeOf: (DbCharge, context) => getChargeType(DbCharge, context) === ChargeTypeEnum.Dividend,
...commonChargeFields,
},
BusinessTripCharge: {
__isTypeOf: (DbCharge, context) =>
getChargeType(DbCharge, context) === ChargeTypeEnum.BusinessTrip,
...commonChargeFields,
},
MonthlyVatCharge: {
__isTypeOf: (DbCharge, context) =>
getChargeType(DbCharge, context) === ChargeTypeEnum.MonthlyVat,
...commonChargeFields,
},
BankDepositCharge: {
__isTypeOf: (DbCharge, context) =>
getChargeType(DbCharge, context) === ChargeTypeEnum.BankDeposit,
...commonChargeFields,
},
CreditcardBankCharge: {
__isTypeOf: (DbCharge, context) =>
getChargeType(DbCharge, context) === ChargeTypeEnum.CreditcardBankCharge,
...commonChargeFields,
},
Invoice: {
...commonDocumentsFields,
},
InvoiceReceipt: {
...commonDocumentsFields,
},
CreditInvoice: {
...commonDocumentsFields,
},
Proforma: {
...commonDocumentsFields,
},
Unprocessed: {
...commonDocumentsFields,
},
OtherDocument: {
...commonDocumentsFields,
},
Receipt: {
...commonDocumentsFields,
},
BankFinancialAccount: {
...commonFinancialAccountFields,
},
CardFinancialAccount: {
...commonFinancialAccountFields,
},
LtdFinancialEntity: {
...commonFinancialEntityFields,
},
PersonalFinancialEntity: {
...commonFinancialEntityFields,
},
ChargeMetadata: {
createdAt: DbCharge => DbCharge.created_at,
updatedAt: DbCharge => DbCharge.updated_at,
invoicesCount: DbCharge => (DbCharge.invoices_count ? Number(DbCharge.invoices_count) : 0),
receiptsCount: DbCharge => (DbCharge.receipts_count ? Number(DbCharge.receipts_count) : 0),
documentsCount: DbCharge => (DbCharge.documents_count ? Number(DbCharge.documents_count) : 0),
invalidDocuments: DbCharge => DbCharge.invalid_documents ?? true,
transactionsCount: DbCharge => {
return DbCharge.transactions_count ? Number(DbCharge.transactions_count) : 0;
},
invalidTransactions: DbCharge => DbCharge.invalid_transactions ?? true,
ledgerCount: DbCharge => (DbCharge.ledger_count ? Number(DbCharge.ledger_count) : 0),
invalidLedger: async (DbCharge, _, context, info) => {
try {
const generatedLedgerPromise = ledgerGenerationByCharge(DbCharge, context)(
DbCharge,
{ insertLedgerRecordsIfNotExists: false },
context,
info,
);
const currentRecordPromise = context.injector
.get(LedgerProvider)
.getLedgerRecordsByChargesIdLoader.load(DbCharge.id);
const [currentRecord, generated] = await Promise.all([
currentRecordPromise,
generatedLedgerPromise,
]);
if (
!generated ||
'message' in generated ||
generated.balance?.isBalanced === false ||
generated.errors?.length > 0
) {
return 'INVALID';
}
const fullMatching = ledgerRecordsGenerationFullMatchComparison(
currentRecord,
generated.records,
);
return fullMatching.isFullyMatched ? 'VALID' : 'DIFF';
} catch (err) {
console.error(err);
return 'INVALID';
}
},
miscExpensesCount: async (DbCharge, _, { injector }) => {
try {
return injector
.get(MiscExpensesProvider)
.getExpensesByChargeIdLoader.load(DbCharge.id)
.then(res => res.length);
} catch (err) {
const message = 'Error loading misc expenses';
console.error(`${message}: ${err}`);
throw new GraphQLError(message);
}
},
optionalBusinesses: DbCharge =>
DbCharge.business_array && DbCharge.business_array.length > 1 ? DbCharge.business_array : [],
isSalary: DbCharge => DbCharge.type === 'PAYROLL',
},
};