@accounter/server
Version:
293 lines (279 loc) • 10.2 kB
text/typescript
import { GraphQLError } from 'graphql';
import { BusinessTripsProvider } from '@modules/business-trips/providers/business-trips.provider.js';
import { TagsProvider } from '@modules/tags/providers/tags.provider.js';
import { tags_enum } from '@modules/tags/types.js';
import { EMPTY_UUID } from '@shared/constants';
import { ChargeSortByField } from '@shared/enums';
import type { Resolvers } from '@shared/gql-types';
import { getChargeType } from '../helpers/charge-type.js';
import { deleteCharge } from '../helpers/delete-charge.helper.js';
import { mergeChargesExecutor } from '../helpers/merge-charges.hepler.js';
import { ChargeRequiredWrapper, ChargesProvider } from '../providers/charges.provider.js';
import type { 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,
fromDate: filters?.fromDate,
toDate: filters?.toDate,
fromAnyDate: filters?.fromAnyDate,
toAnyDate: filters?.toAnyDate,
sortColumn,
asc: filters?.sortBy?.asc !== false,
chargeType: filters?.chargesType,
businessIds: filters?.byBusinesses,
withoutInvoice: filters?.withoutInvoice,
withoutDocuments: filters?.withoutDocuments,
tags: filters?.byTags,
accountantApproval: filters?.accountantApproval,
})
.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),
},
};
},
},
Mutation: {
updateCharge: async (_, { chargeId, fields }, { injector }) => {
const adjustedFields: IUpdateChargeParams = {
accountantReviewed: fields.accountantApproval,
isConversion: fields.isConversion,
isProperty: fields.isProperty,
ownerId: fields.ownerId,
userDescription: fields.userDescription,
taxCategoryId: fields.defaultTaxCategoryID,
yearOfRelevance: fields.yearOfRelevance,
chargeId,
};
try {
injector.get(ChargesProvider).getChargeByIdLoader.clear(chargeId);
const res = await injector.get(ChargesProvider).updateCharge({ ...adjustedFields });
const updatedCharge = await injector
.get(ChargesProvider)
.getChargeByIdLoader.load(res[0].id);
if (!updatedCharge) {
throw new Error(`Charge ID="${chargeId}" not found`);
}
// handle tags
if (fields?.tags?.length) {
const newTags = fields.tags.map(t => t.name);
const pastTagsItems = await injector
.get(TagsProvider)
.getTagsByChargeIDLoader.load(chargeId);
const pastTags = pastTagsItems.map(({ tag_name }) => tag_name);
// clear removed tags
const tagsToRemove = pastTags.filter(tag => !newTags.includes(tag));
if (tagsToRemove.length) {
await injector.get(TagsProvider).clearChargeTags({ chargeId, tagNames: tagsToRemove });
}
// add new tags
for (const tag of newTags as tags_enum[]) {
if (!pastTags.includes(tag)) {
await injector
.get(TagsProvider)
.insertChargeTags({ chargeId, tagName: tag })
.catch(() => {
throw new GraphQLError(`Error adding tag "${tag}" to charge ID="${chargeId}"`);
});
}
}
}
// handle business trip
if (fields?.businessTripID) {
await injector
.get(BusinessTripsProvider)
.updateChargeBusinessTrip(
chargeId,
fields.businessTripID === EMPTY_UUID ? null : fields.businessTripID,
)
.catch(() => {
throw new GraphQLError(`Error updating business trip for charge ID="${chargeId}"`);
});
}
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 = {
accountantReviewed: fields?.accountantApproval,
isConversion: fields?.isConversion,
isProperty: fields?.isProperty,
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 deleteCharge(chargeId, injector.get(ChargesProvider), injector.get(TagsProvider));
return true;
},
},
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 => getChargeType(DbCharge) === 'CommonCharge',
...commonChargeFields,
},
ConversionCharge: {
__isTypeOf: DbCharge => getChargeType(DbCharge) === 'ConversionCharge',
...commonChargeFields,
},
SalaryCharge: {
__isTypeOf: DbCharge => getChargeType(DbCharge) === 'SalaryCharge',
...commonChargeFields,
},
InternalTransferCharge: {
__isTypeOf: DbCharge => getChargeType(DbCharge) === 'InternalTransferCharge',
...commonChargeFields,
},
DividendCharge: {
__isTypeOf: DbCharge => getChargeType(DbCharge) === 'DividendCharge',
...commonChargeFields,
},
BusinessTripCharge: {
__isTypeOf: DbCharge => getChargeType(DbCharge) === 'BusinessTripCharge',
...commonChargeFields,
},
MonthlyVatCharge: {
__isTypeOf: DbCharge => getChargeType(DbCharge) === 'MonthlyVatCharge',
...commonChargeFields,
},
Invoice: {
...commonDocumentsFields,
},
InvoiceReceipt: {
...commonDocumentsFields,
},
CreditInvoice: {
...commonDocumentsFields,
},
Proforma: {
...commonDocumentsFields,
},
Unprocessed: {
...commonDocumentsFields,
},
Receipt: {
...commonDocumentsFields,
},
BankFinancialAccount: {
...commonFinancialAccountFields,
},
CardFinancialAccount: {
...commonFinancialAccountFields,
},
LtdFinancialEntity: {
...commonFinancialEntityFields,
},
PersonalFinancialEntity: {
...commonFinancialEntityFields,
},
};