@accounter/server
Version:
Accounter GraphQL server
309 lines (303 loc) • 15.1 kB
text/typescript
import { GraphQLError } from 'graphql';
import { type Injector } from 'graphql-modules';
import type { Resolvers } from '../../../__generated__/types.js';
import type { Currency } from '../../../shared/enums.js';
import { formatFinancialAmount } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { ScopeProvider } from '../../auth/providers/scope.provider.js';
import { ChargesProvider } from '../../charges/providers/charges.provider.js';
import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js';
import { SalariesProvider } from '../providers/salaries.provider.js';
import type { SalariesModule } from '../types.js';
import { insertSalaryRecordsFromFile } from './insert-salary-records-from-file.resolvers.js';
import { insertSalaryRecords } from './insert-salary.resolver.js';
import { updateSalaryRecord } from './update-salary.resolver.js';
// Resolve a salary record's local currency from its OWNING business so
// multi-business reads format each record correctly; falls back to the
// request's primary business currency when the per-business value is absent.
async function salaryLocalCurrency(
injector: Injector,
ownerId: string | null | undefined,
): Promise<Currency> {
if (ownerId) {
const currency = await injector
.get(ScopeProvider)
.getBusinessPreference(ownerId, 'defaultLocalCurrency');
if (currency) {
return currency;
}
}
const { defaultLocalCurrency } = await injector
.get(AdminContextProvider)
.getVerifiedAdminContext();
return defaultLocalCurrency;
}
export const salariesResolvers: SalariesModule.Resolvers &
Pick<Resolvers, 'UpdateSalaryRecordResult' | 'InsertSalaryRecordsResult'> = {
Query: {
salaryRecordsByCharge: async (_, { chargeId }, { injector }) => {
const salaryRecords = await injector
.get(SalariesProvider)
.getSalaryRecordsByChargeIdLoader.load(chargeId);
return salaryRecords;
},
salaryRecordsByDates: async (_, { fromDate, toDate, employeeIDs }, { injector }) => {
const fromDateMonth = fromDate.slice(0, 7);
const toDateMonth = toDate.slice(0, 7);
try {
const salaryRecords = await injector
.get(SalariesProvider)
.getSalaryRecordsByDates({ fromDate: fromDateMonth, toDate: toDateMonth });
return salaryRecords
.filter(record => (employeeIDs ? employeeIDs.includes(record.employee_id) : true))
.sort((a, b) => a.month.localeCompare(b.month));
} catch (e) {
throw new GraphQLError(`Failed to get salary records by dates: ${(e as Error).message}`);
}
},
},
Mutation: {
insertSalaryRecords,
updateSalaryRecord,
insertOrUpdateSalaryRecords: async (_, params, { injector }) => {
try {
const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const recordsPromises = params.salaryRecords.map(async salaryRecord => {
try {
const res = await injector.get(SalariesProvider).updateSalaryRecord(salaryRecord);
if (res.length !== 1) {
throw new Error('Failed to update salary record');
}
return res[0];
} catch (e) {
const message = `Failed to update salary record of employee ID=${salaryRecord.employeeId} on month ${salaryRecord.month}`;
console.error(`${message}: ${e}`);
}
try {
const salaryRecordToUpdate = {
...salaryRecord,
ownerId,
employee: salaryRecord.employee ?? null,
addedVacationDays: salaryRecord.addedVacationDays ?? null,
baseSalary: salaryRecord.baseSalary ?? null,
bonus: salaryRecord.bonus ?? null,
chargeId: salaryRecord.chargeId ?? null,
compensationsEmployerAmount: salaryRecord.compensationsEmployerAmount ?? null,
compensationsEmployerPercentage: salaryRecord.compensationsEmployerPercentage ?? null,
gift: salaryRecord.gift ?? null,
globalAdditionalHours: salaryRecord.globalAdditionalHours ?? null,
healthPaymentAmount: salaryRecord.healthPaymentAmount ?? null,
hourlyRate: salaryRecord.hourlyRate ?? null,
hours: salaryRecord.hours ?? null,
jobPercentage: salaryRecord.jobPercentage ?? null,
pensionEmployeeAmount: salaryRecord.pensionEmployeeAmount ?? null,
pensionEmployeePercentage: salaryRecord.pensionEmployeePercentage ?? null,
pensionEmployerAmount: salaryRecord.pensionEmployerAmount ?? null,
pensionEmployerPercentage: salaryRecord.pensionEmployerPercentage ?? null,
pensionFundId: salaryRecord.pensionFundId ?? null,
recovery: salaryRecord.recovery ?? null,
sicknessDaysBalance: salaryRecord.sicknessDaysBalance ?? null,
socialSecurityAmountEmployee: salaryRecord.socialSecurityAmountEmployee ?? null,
socialSecurityAmountEmployer: salaryRecord.socialSecurityAmountEmployer ?? null,
taxAmount: salaryRecord.taxAmount ?? null,
trainingFundEmployeeAmount: salaryRecord.trainingFundEmployeeAmount ?? null,
trainingFundEmployeePercentage: salaryRecord.trainingFundEmployeePercentage ?? null,
trainingFundEmployerAmount: salaryRecord.trainingFundEmployerAmount ?? null,
trainingFundEmployerPercentage: salaryRecord.trainingFundEmployerPercentage ?? null,
trainingFundId: salaryRecord.trainingFundId ?? null,
travelAndSubsistence: salaryRecord.travelAndSubsistence ?? null,
vacationDaysBalance: salaryRecord.vacationDaysBalance ?? null,
vacationTakeout: salaryRecord.vacationTakeout ?? null,
workDays: salaryRecord.workDays ?? null,
zkufot: salaryRecord.zkufot ?? null,
};
const res = await injector
.get(SalariesProvider)
.insertSalaryRecords({ salaryRecords: [salaryRecordToUpdate] });
if (res.length !== 1) {
throw new Error('Failed to insert salary record');
}
return res[0];
} catch (e) {
const message = `Failed to insert salary record of employee ID=${salaryRecord.employeeId} on month ${salaryRecord.month}`;
console.error(`${message}: ${e}`);
}
throw new Error(
`Failed to update salary record of employee ID=${salaryRecord.employeeId} on month ${salaryRecord.month}`,
);
});
const updatedRecords = await Promise.all(recordsPromises);
return {
salaryRecords: updatedRecords,
};
} catch (e) {
return {
__typename: 'CommonError',
message:
(e as Error)?.message ??
(e as { errors: Error[] })?.errors.map(e => e.message).toString() ??
'Unknown error',
};
}
},
insertSalaryRecordsFromFile,
},
UpdateSalaryRecordResult: {
__resolveType: obj => {
if ('__typename' in obj && obj.__typename === 'CommonError') return 'CommonError';
return 'UpdateSalaryRecordSuccessfulResult';
},
},
InsertSalaryRecordsResult: {
__resolveType: obj => {
if ('__typename' in obj && obj.__typename === 'CommonError') return 'CommonError';
return 'InsertSalaryRecordsSuccessfulResult';
},
},
SalaryCharge: {
salaryRecords: (DbCharge, _, { injector }) => {
return injector.get(SalariesProvider).getSalaryRecordsByChargeIdLoader.load(DbCharge.id);
},
employees: async (DbCharge, _, { injector }) => {
const salaryRecords = await injector
.get(SalariesProvider)
.getSalaryRecordsByChargeIdLoader.load(DbCharge.id);
return injector
.get(BusinessesProvider)
.getBusinessByIdLoader.loadMany(salaryRecords.map(salaryRecord => salaryRecord.employee_id))
.then(
res =>
res.map(employee => {
if (!employee || employee instanceof Error) {
throw new GraphQLError(
`Employee not found for one of the salary record of charge ${DbCharge.id}`,
);
}
return employee;
}) ?? [],
);
},
},
Salary: {
directAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.direct_payment_amount, defaultLocalCurrency);
},
baseAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return DbSalary.base_salary
? formatFinancialAmount(DbSalary.base_salary, defaultLocalCurrency)
: null;
},
employee: (DbSalary, _, { injector }) =>
injector
.get(BusinessesProvider)
.getBusinessByIdLoader.load(DbSalary.employee_id)
.then(res => res ?? null),
employer: (DbSalary, _, { injector }) =>
injector
.get(BusinessesProvider)
.getBusinessByIdLoader.load(DbSalary.employer)
.then(res => res ?? null),
pensionFund: (DbSalary, _, { injector }) =>
DbSalary.pension_fund_id
? injector
.get(BusinessesProvider)
.getBusinessByIdLoader.load(DbSalary.pension_fund_id)
.then(res => res ?? null)
: null,
pensionEmployeeAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.pension_employee_amount, defaultLocalCurrency);
},
pensionEmployeePercentage: DbSalary => DbSalary.pension_employee_percentage,
pensionEmployerAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.pension_employer_amount, defaultLocalCurrency);
},
pensionEmployerPercentage: DbSalary => DbSalary.pension_employer_percentage,
compensationsAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.compensations_employer_amount, defaultLocalCurrency);
},
compensationsPercentage: DbSalary => DbSalary.compensations_employer_percentage,
trainingFund: (DbSalary, _, { injector }) =>
DbSalary.training_fund_id
? injector
.get(BusinessesProvider)
.getBusinessByIdLoader.load(DbSalary.training_fund_id)
.then(res => res ?? null)
: null,
trainingFundEmployeeAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.training_fund_employee_amount, defaultLocalCurrency);
},
trainingFundEmployeePercentage: DbSalary => DbSalary.training_fund_employee_percentage,
trainingFundEmployerAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.training_fund_employer_amount, defaultLocalCurrency);
},
trainingFundEmployerPercentage: DbSalary => DbSalary.training_fund_employer_percentage,
socialSecurityEmployeeAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.social_security_amount_employee, defaultLocalCurrency);
},
socialSecurityEmployerAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.social_security_amount_employer, defaultLocalCurrency);
},
incomeTaxAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.tax_amount, defaultLocalCurrency);
},
healthInsuranceAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.health_payment_amount, defaultLocalCurrency);
},
globalAdditionalHoursAmount: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.global_additional_hours, defaultLocalCurrency);
},
bonus: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.bonus, defaultLocalCurrency);
},
gift: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.gift, defaultLocalCurrency);
},
travelAndSubsistence: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.travel_and_subsistence, defaultLocalCurrency);
},
recovery: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.recovery, defaultLocalCurrency);
},
vacationTakeout: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.vacation_takeout, defaultLocalCurrency);
},
notionalExpense: async (DbSalary, _, { injector }) => {
const defaultLocalCurrency = await salaryLocalCurrency(injector, DbSalary.owner_id);
return formatFinancialAmount(DbSalary.zkufot, defaultLocalCurrency);
},
vacationDays: DbSalary => ({
added: DbSalary.added_vacation_days ? Number(DbSalary.added_vacation_days) : null,
balance: DbSalary.vacation_days_balance ? Number(DbSalary.vacation_days_balance) : null,
}),
workDays: DbSalary => (DbSalary.work_days ? Number(DbSalary.work_days) : null),
sicknessDays: DbSalary => ({
balance: DbSalary.sickness_days_balance ? Number(DbSalary.sickness_days_balance) : null,
}),
charge: (DbSalary, _, { injector }) => {
if (!DbSalary.charge_id) {
return null;
}
return injector
.get(ChargesProvider)
.getChargeByIdLoader.load(DbSalary.charge_id)
.then(res => res ?? null);
},
},
};