@accounter/server
Version:
Accounter GraphQL server
894 lines (840 loc) • 27.5 kB
text/typescript
import { GraphQLError } from 'graphql';
import { Injector } from 'graphql-modules';
import {
AccountingMethod,
AccountingSystem,
AuditOpinionType,
BALANCE_SHEET_CODE_NAMES,
BusinessType,
CurrencyType,
IFRSReportingOption,
IndividualOrCompanyEnum,
NEGATIVE_ALLOWED_BALANCE_SHEET_CODES,
PROFIT_LOSS_CODE_NAMES,
ReportData,
ReportEntry,
ReportingMethod,
SECTION_1_1,
SECTION_1_4,
SECTION_1_9,
SECTION_1_10,
SECTION_1_11,
SECTION_1_12,
SECTION_1_14,
SECTION_1_15,
SECTION_1_16,
SECTION_1_21,
SECTION_1_27,
SECTION_1_28,
SECTION_1_30,
SECTION_1_32,
SECTION_1_33,
SECTION_1_36,
SECTION_1_37,
SECTION_1_38,
SECTION_1_39,
SECTION_1_40,
SECTION_1_41,
SECTION_1_43,
SECTION_1_44,
SECTION_1_45,
SECTION_1_46,
SECTION_1_48,
SECTION_1_50,
SECTION_1_51,
TAX_ADJUSTMENT_CODE_NAMES,
YesNo,
} from '@accounter/shaam6111-generator';
import type * as SchemaTypes from '../../../__generated__/types.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { BusinessesProvider } from '../../financial-entities/providers/businesses.provider.js';
import { FinancialEntitiesProvider } from '../../financial-entities/providers/financial-entities.provider.js';
import { IGetAllFinancialEntitiesResult } from '../../financial-entities/types.js';
import { LedgerProvider } from '../../ledger/providers/ledger.provider.js';
import { amountByFinancialEntityIdAndSortCodeValidations } from './misc.helper.js';
import {
DecoratedLedgerRecord,
decorateLedgerRecords,
getProfitLossReportAmounts,
} from './profit-and-loss.helper.js';
import { calculateCumulativeRnDExpenses, calculateTaxAmounts } from './tax.helper.js';
function cumulativeCodeFormula(
code: number,
sectionCodes: readonly number[],
subtractSectionCodes?: readonly number[],
) {
return (map: Map<number, { amount: number }>) => {
const sectionCodeAmounts = sectionCodes
.filter(sectionCode => sectionCode !== code)
.map(sectionCode => map.get(sectionCode)?.amount || 0)
.reduce((a, b) => a + b, 0);
const subtractSectionCodeAmounts = subtractSectionCodes?.length
? subtractSectionCodes
.filter(sectionCode => sectionCode !== code)
.map(sectionCode => map.get(sectionCode)?.amount || 0)
.reduce((a, b) => a + b, 0)
: 0;
return sectionCodeAmounts - subtractSectionCodeAmounts;
};
}
function getProfitAndLossEntries(
decoratedLedgerRecords: DecoratedLedgerRecord[],
financialEntitiesByIrsCodeDict: Map<number, IGetAllFinancialEntitiesResult[]>,
): SchemaTypes.Shaam6111ReportEntry[] {
const profitAndLossAmounts = amountByFinancialEntityIdAndSortCodeValidations(
decoratedLedgerRecords,
Object.entries(PROFIT_LOSS_CODE_NAMES).map(([code]) => {
const codeInt = parseInt(code, 10);
return {
rule: financialEntityId => {
const financialEntitiesByIrsCode = financialEntitiesByIrsCodeDict.get(codeInt) || [];
return financialEntitiesByIrsCode.some(entity => entity.id === financialEntityId);
},
negate: codeInt >= 1300 && codeInt < 5100,
};
}),
);
const profitAndLossCodesMap = new Map<number, { amount: number; label: string }>(
Object.entries(PROFIT_LOSS_CODE_NAMES).map(([code, label], index) => [
parseInt(code, 10),
{
label,
amount: Math.round(profitAndLossAmounts[index] ?? 0),
},
]),
);
const rules: {
code: number;
rule: (codesMap: Map<number, { amount: number; label: string }>) => number;
}[] = [
{
code: 1000,
rule: cumulativeCodeFormula(1000, SECTION_1_1),
},
{
code: 1300,
rule: map => {
return (
[1306, 1308, 1307, 1310, 1320, 1330, 1340, 1350, 1360, 1371, 1372, 1390, 1400]
.map(code => map.get(code)?.amount || 0)
.reduce((a, b) => a + b, 0) - (map.get(1450)?.amount || 0)
);
},
},
{
code: 2000,
rule: map =>
[
2005, 2006, 2011, 2012, 2015, 2020, 2025, 2030, 2035, 2040, 2045, 2050, 2060, 2066, 2067,
2070, 2075, 2080, 2085, 2090,
]
.map(code => map.get(code)?.amount || 0)
.reduce((a, b) => a + b, 0) -
(map.get(2068)?.amount || 0) -
(map.get(2095)?.amount || 0),
},
{
code: 2500,
rule: cumulativeCodeFormula(2500, SECTION_1_4),
},
{
code: 3000,
rule: map =>
[
3011, 3013, 3012, 3015, 3020, 3025, 3030, 3040, 3045, 3050, 3060, 3066, 3067, 3070, 3075,
3080, 3085, 3090, 3100, 3120, 3190,
]
.map(code => map.get(code)?.amount || 0)
.reduce((a, b) => a + b, 0) - (map.get(3068)?.amount || 0),
},
{
code: 3500,
rule: cumulativeCodeFormula(
3500,
[
3511, 3513, 3512, 3515, 3520, 3530, 3535, 3540, 3545, 3550, 3560, 3566, 3567, 3570, 3575,
3580, 3590, 3595, 3600, 3610, 3620, 3625, 3631, 3632, 3640, 3650, 3660, 3665, 3680, 3685,
3690,
],
[3568],
),
},
{
code: 5000,
rule: cumulativeCodeFormula(5000, SECTION_1_9),
},
{
code: 5100,
rule: cumulativeCodeFormula(5100, SECTION_1_10),
},
{
code: 5200,
rule: cumulativeCodeFormula(5200, SECTION_1_11),
},
{
code: 5300,
rule: cumulativeCodeFormula(5300, SECTION_1_12),
},
{
code: 5600,
rule: cumulativeCodeFormula(5600, SECTION_1_14),
},
{
code: 5700,
rule: cumulativeCodeFormula(5700, SECTION_1_15),
},
{
code: 5800,
rule: cumulativeCodeFormula(5800, SECTION_1_16),
},
{
code: 6666,
rule: map =>
(map.get(1000)?.amount || 0) -
(map.get(1300)?.amount || 0) -
(map.get(2000)?.amount || 0) -
(map.get(2500)?.amount || 0) -
(map.get(3000)?.amount || 0) -
(map.get(3500)?.amount || 0) -
(map.get(5000)?.amount || 0) +
(map.get(5100)?.amount || 0) +
(map.get(5200)?.amount || 0) -
(map.get(5300)?.amount || 0),
},
];
// Apply the rules to the tax adjustment codes map
for (const { code, rule } of rules) {
const entry = profitAndLossCodesMap.get(code);
if (entry) {
entry.amount = rule(profitAndLossCodesMap);
}
}
const profitAndLoss = Array.from(profitAndLossCodesMap.entries())
.map(([code, { amount, label }]) => ({
code,
label,
amount,
}))
.sort((a, b) => a.code - b.code); // Sort by code
return profitAndLoss;
}
async function getTaxAdjustmentEntries(
injector: Injector,
year: number,
decoratedLedgerByYear: Map<number, DecoratedLedgerRecord[]>,
): Promise<SchemaTypes.Shaam6111ReportEntry[]> {
const decoratedLedgerRecords = decoratedLedgerByYear.get(year) ?? [];
const profitLossByYear = new Map<number, ReturnType<typeof getProfitLossReportAmounts>>();
let profitAndLoss = profitLossByYear.get(year);
if (!profitAndLoss) {
profitAndLoss = getProfitLossReportAmounts(decoratedLedgerRecords);
profitLossByYear.set(year, profitAndLoss);
}
const { profitBeforeTax, researchAndDevelopmentExpenses } = profitAndLoss;
const adjustedResearchAndDevelopmentExpensesAmount = researchAndDevelopmentExpenses.amount * -1;
const cumulativeResearchAndDevelopmentExpensesAmount = calculateCumulativeRnDExpenses(
year,
decoratedLedgerByYear,
profitLossByYear,
);
const taxableCumulativeResearchAndDevelopmentExpensesAmount =
cumulativeResearchAndDevelopmentExpensesAmount / 3;
const {
researchAndDevelopmentExpensesForTax,
fines,
untaxableGifts,
businessTripsExcessExpensesAmount,
salaryExcessExpensesAmount,
reserves,
nontaxableLinkage,
taxableIncomeAmount,
} = await calculateTaxAmounts(
injector,
year,
decoratedLedgerRecords,
adjustedResearchAndDevelopmentExpensesAmount,
taxableCumulativeResearchAndDevelopmentExpensesAmount,
profitBeforeTax.amount,
);
const amount190 =
adjustedResearchAndDevelopmentExpensesAmount +
researchAndDevelopmentExpensesForTax +
fines.amount +
untaxableGifts.amount +
businessTripsExcessExpensesAmount +
salaryExcessExpensesAmount +
nontaxableLinkage.amount;
const taxAdjustmentCodesMap = new Map<number, { amount: number; label: string }>(
Object.entries(TAX_ADJUSTMENT_CODE_NAMES).map(([code, label]) => [
parseInt(code, 10),
{
label,
amount: 0,
},
]),
);
const rules: {
code: number;
rule: (codesMap: Map<number, { amount: number; label: string }>) => number;
}[] = [
{
code: 100,
rule: () => {
return Math.round(profitBeforeTax.amount);
},
},
{
code: 104,
rule: () => {
return Math.round(profitBeforeTax.amount);
},
},
{
code: 150,
rule: () => {
return Math.round(reserves.amount);
},
},
{
code: 190,
rule: () => {
return Math.round(amount190);
},
},
{
code: 400,
rule: () => {
return Math.round(taxableIncomeAmount);
},
},
{
code: 510,
rule: () => {
return Math.round(taxableIncomeAmount);
},
},
// summation rules
{
code: 370,
rule: map => {
return (
[110, 120, 140, 150, 160, 181, 182, 183, 184, 190, 310, 330, 350, 360]
.map(code => map.get(code)?.amount || 0)
.reduce((a, b) => a + b, 0) -
[130, 135, 170, 180, 200, 300, 320]
.map(code => map.get(code)?.amount || 0)
.reduce((a, b) => a + b, 0)
);
},
},
{
code: 500,
rule: map => {
return (
(map.get(400)?.amount || 0) -
(map.get(430)?.amount || 0) -
(map.get(480)?.amount || 0) +
(map.get(490)?.amount || 0)
);
},
},
];
// Apply the rules to the tax adjustment codes map
for (const { code, rule } of rules) {
const entry = taxAdjustmentCodesMap.get(code);
if (entry) {
entry.amount = rule(taxAdjustmentCodesMap);
}
}
const taxAdjustment = Array.from(taxAdjustmentCodesMap.entries())
.map(([code, { amount, label }]) => ({
code,
label,
amount,
}))
.sort((a, b) => a.code - b.code); // Sort by code
return taxAdjustment;
}
function getBalanceSheetEntries(
decoratedLedgerRecords: DecoratedLedgerRecord[],
financialEntitiesByIrsCodeDict: Map<number, IGetAllFinancialEntitiesResult[]>,
): SchemaTypes.Shaam6111ReportEntry[] {
const balanceSheetAmounts = amountByFinancialEntityIdAndSortCodeValidations(
decoratedLedgerRecords,
Object.entries(BALANCE_SHEET_CODE_NAMES).map(([stringCode]) => {
const code = parseInt(stringCode, 10);
return {
rule: financialEntityId => {
const financialEntitiesByIrsCode = financialEntitiesByIrsCodeDict.get(code) || [];
return financialEntitiesByIrsCode.some(entity => entity.id === financialEntityId);
},
negate: code >= 7000 && code < 9000,
};
}),
);
const balanceSheetCodesMap = new Map<number, { amount: number; label: string }>(
Object.entries(BALANCE_SHEET_CODE_NAMES).map(([code, label], index) => {
const codeInt = parseInt(code, 10);
let amount = Math.round(balanceSheetAmounts[index] ?? 0);
// for codes that are not allowed to be negative, we ensure the amount is positive
if (!(NEGATIVE_ALLOWED_BALANCE_SHEET_CODES as readonly number[]).includes(codeInt)) {
amount = Math.abs(amount);
}
return [
codeInt,
{
label,
amount,
},
];
}),
);
const cumulativeProfit =
getProfitAndLossEntries(decoratedLedgerRecords, financialEntitiesByIrsCodeDict).find(
entry => entry.code === 6666,
)?.amount || 0;
// NOTE: the rules order is important here, as some codes depend on others
// e.g. 7000 depends on 7100, 7200, etc.
const rules: {
code: number;
rule: (codesMap: Map<number, { amount: number; label: string }>) => number;
}[] = [
{
code: 9980,
rule: () => cumulativeProfit,
},
{
code: 7100,
rule: cumulativeCodeFormula(7100, SECTION_1_27),
},
{
code: 7200,
rule: cumulativeCodeFormula(7200, SECTION_1_28),
},
{
code: 7300,
rule: cumulativeCodeFormula(7300, [7310, 7320, 7330, 7350, 7360, 7390], [7380]),
},
{
code: 7400,
rule: cumulativeCodeFormula(7400, SECTION_1_30),
},
{
code: 7600,
rule: cumulativeCodeFormula(7600, SECTION_1_21),
},
{
code: 7700,
rule: cumulativeCodeFormula(7700, SECTION_1_32),
},
{
code: 7800,
rule: cumulativeCodeFormula(7800, SECTION_1_33),
},
{
code: 7000,
rule: cumulativeCodeFormula(7000, [7100, 7200, 7300, 7400, 7600, 7700, 7800]),
},
{
code: 8000,
rule: cumulativeCodeFormula(
8000,
[8010, 8020, 8025, 8030, 8040, 8050, 8060, 8080, 8090, 8095, 8100, 8105, 8170],
[8110, 8120, 8130, 8140, 8150, 8160, 8180, 8190],
),
},
{
code: 8200,
rule: cumulativeCodeFormula(8200, SECTION_1_37),
},
{
code: 8300,
rule: cumulativeCodeFormula(8300, SECTION_1_38),
},
{
code: 8400,
rule: cumulativeCodeFormula(8400, SECTION_1_39),
},
{
code: 8500,
rule: cumulativeCodeFormula(8500, SECTION_1_40),
},
{
code: 8600,
rule: cumulativeCodeFormula(8600, SECTION_1_41),
},
{
code: 8700,
rule: cumulativeCodeFormula(8700, SECTION_1_36),
},
{
code: 8888,
rule: cumulativeCodeFormula(8888, [7000, 8000, 8200, 8300, 8400, 8500, 8600, 8700]),
},
{
code: 9100,
rule: cumulativeCodeFormula(9100, SECTION_1_43),
},
{
code: 9200,
rule: cumulativeCodeFormula(9200, SECTION_1_44),
},
{
code: 9400,
rule: cumulativeCodeFormula(9400, SECTION_1_45),
},
{
code: 9500,
rule: cumulativeCodeFormula(9500, SECTION_1_46),
},
{
code: 9000,
rule: cumulativeCodeFormula(9000, [9100, 9200, 9400, 9500]),
},
{
code: 9600,
rule: cumulativeCodeFormula(9600, SECTION_1_48),
},
{
code: 9700,
rule: map =>
(map.get(9710)?.amount || 0) - (map.get(9720)?.amount || 0) + (map.get(9790)?.amount || 0),
},
{
code: 9800,
rule: cumulativeCodeFormula(9800, SECTION_1_50),
},
{
code: 9900,
rule: cumulativeCodeFormula(9900, SECTION_1_51),
},
{
code: 9999,
rule: cumulativeCodeFormula(9999, [9000, 9600, 9700, 9800, 9900]),
},
];
// Apply the rules to the tax adjustment codes map
for (const { code, rule } of rules) {
const entry = balanceSheetCodesMap.get(code);
if (entry) {
entry.amount = rule(balanceSheetCodesMap);
}
}
const balanceSheet = Array.from(balanceSheetCodesMap.entries())
.map(([code, { amount, label }]) => ({
code,
label,
amount,
}))
.sort((a, b) => a.code - b.code); // Sort by code
return balanceSheet;
}
export async function getShaam6111Data(
injector: Injector,
businessId: string,
year: number,
): Promise<SchemaTypes.Shaam6111Data> {
const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const financialEntityId = businessId || ownerId;
const businessesProvider = injector.get(BusinessesProvider);
const business = await businessesProvider.getBusinessByIdLoader.load(financialEntityId);
if (!business) {
throw new Error('Business not found');
}
if (!business?.vat_number) {
throw new Error('Business does not have a VAT number');
}
if (!Number.isInteger(year) || year < 2000 || year > 2100) {
throw new Error(`Invalid year. Must be between 2000 and 2100. Received: ${year}`);
}
const fromDate = new Date(1990, 0, 1, 0, 0, 0, 1); // TODO: maybe take company init date as from date
const toDate = new Date(year + 1, 0, 0);
const ledgerRecordsPromise = injector
.get(LedgerProvider)
.getLedgerRecordsByDates({ fromDate, toDate });
const financialEntitiesPromise = injector
.get(FinancialEntitiesProvider)
.getAllFinancialEntities();
const [ledgerRecords, financialEntities] = await Promise.all([
ledgerRecordsPromise,
financialEntitiesPromise,
]);
const financialEntitiesDict = new Map<string, IGetAllFinancialEntitiesResult>();
const financialEntitiesByIrsCodeDict = new Map<number, IGetAllFinancialEntitiesResult[]>();
financialEntities.map(entity => {
financialEntitiesDict.set(entity.id, entity);
if (entity.irs_code) {
const entities = financialEntitiesByIrsCodeDict.get(entity.irs_code) || [];
entities.push(entity);
financialEntitiesByIrsCodeDict.set(entity.irs_code, entities);
}
});
const decoratedLedgerByYear = new Map<number, DecoratedLedgerRecord[]>();
ledgerRecords.map(record => {
const year = record.invoice_date.getFullYear();
const [decoratedRecord] = decorateLedgerRecords([record], financialEntitiesDict);
if (!decoratedLedgerByYear.has(year)) {
decoratedLedgerByYear.set(year, []);
}
// Add the decorated record to the corresponding year
decoratedLedgerByYear.get(year)?.push(decoratedRecord);
});
const profitAndLoss = getProfitAndLossEntries(
decoratedLedgerByYear.get(year) || [],
financialEntitiesByIrsCodeDict,
);
const taxAdjustment = await getTaxAdjustmentEntries(injector, year, decoratedLedgerByYear);
const ledgerSinceForever = Array.from(decoratedLedgerByYear.entries()).reduce(
(acc, [ledgerYear, records]) => {
if (ledgerYear <= year) {
return acc.concat(records);
}
return acc;
},
[] as DecoratedLedgerRecord[],
);
const balanceSheet = getBalanceSheetEntries(ledgerSinceForever, financialEntitiesByIrsCodeDict);
const header: SchemaTypes.Shaam6111Header = {
taxFileNumber: business.vat_number,
taxYear: year.toString(),
idNumber: business.vat_number,
vatFileNumber: business.vat_number,
withholdingTaxFileNumber: undefined, // TODO: fetch from admin business
industryCode: '6201', // TODO: migrate to DB
businessDescription: 'פיתוח פלטפורמת תוכנה', // TODO: migrate to DB
businessType: 'MULTIPLE', // TODO: migrate to DB
reportingMethod: 'ACCRUAL', // TODO: migrate to DB
accountingMethod: 'DOUBLE_ENTRY', // TODO: migrate to DB
accountingSystem: 'COMPUTERIZED',
includesProfitLoss: !!profitAndLoss.length,
includesTaxAdjustment: !!taxAdjustment.length,
includesBalanceSheet: !!balanceSheet.length,
profitLossEntryCount: profitAndLoss.length ?? undefined,
taxAdjustmentEntryCount: taxAdjustment.length ?? undefined,
balanceSheetEntryCount: balanceSheet.length ?? undefined,
softwareRegistrationNumber: '99999999',
isPartnership: false, // TODO: migrate to DB
partnershipCount: undefined, // TODO: migrate to DB
partnershipProfitShare: undefined, // TODO: migrate to DB
ifrsImplementationYear: '9999', // TODO: migrate to DB
ifrsReportingOption: 'NONE', // TODO: migrate to DB
currencyType: 'SHEKELS', // TODO: migrate to DB
amountsInThousands: false, // TODO: migrate to DB
auditOpinionType: 'UNQUALIFIED', // TODO: migrate to DB
};
const shaam6111Data: SchemaTypes.Shaam6111Data = {
id: `shaam6111-${business.vat_number}-${year}-data`,
header,
profitAndLoss,
taxAdjustment,
balanceSheet,
individualOrCompany: 'INDIVIDUAL', // TODO: migrate to DB
};
return shaam6111Data;
}
export function accountingMethodSafeParser(
accountingMethod: SchemaTypes.AccountingMethod,
): AccountingMethod {
switch (accountingMethod) {
case 'DOUBLE_ENTRY':
return AccountingMethod.DOUBLE_ENTRY;
case 'SINGLE_ENTRY':
return AccountingMethod.SINGLE_ENTRY;
default:
throw new GraphQLError(`Unknown accounting method: ${accountingMethod}`);
}
}
export function accountingSystemSafeParser(
accountingSystem: SchemaTypes.AccountingSystem,
): AccountingSystem {
switch (accountingSystem) {
case 'COMPUTERIZED':
return AccountingSystem.COMPUTERIZED;
case 'MANUAL':
return AccountingSystem.MANUAL;
default:
throw new GraphQLError(`Unknown accounting system: ${accountingSystem}`);
}
}
export function auditOpinionTypeSafeParser(
auditOpinionType?: SchemaTypes.AuditOpinionType | null,
): AuditOpinionType | undefined {
if (auditOpinionType == null) {
return undefined;
}
switch (auditOpinionType) {
case 'UNQUALIFIED':
return AuditOpinionType.UNQUALIFIED;
case 'UNQUALIFIED_WITH_GOING_CONCERN':
return AuditOpinionType.UNQUALIFIED_WITH_GOING_CONCERN;
case 'UNQUALIFIED_WITH_OTHER_EMPHASES':
return AuditOpinionType.UNQUALIFIED_WITH_OTHER_EMPHASES;
case 'QUALIFIED':
return AuditOpinionType.QUALIFIED;
case 'ADVERSE':
return AuditOpinionType.ADVERSE;
case 'DISCLAIMER':
return AuditOpinionType.DISCLAIMER;
case 'NONE':
return AuditOpinionType.NONE;
default:
throw new GraphQLError(`Unknown audit opinion type: ${auditOpinionType}`);
}
}
export function businessTypeSafeParser(businessType: SchemaTypes.BusinessType): BusinessType {
switch (businessType) {
case 'COMMERCIAL':
return BusinessType.COMMERCIAL;
case 'INDUSTRIAL':
return BusinessType.INDUSTRIAL;
case 'SERVICE':
return BusinessType.SERVICE;
case 'MULTIPLE':
return BusinessType.MULTIPLE;
case 'COMBINATION':
return BusinessType.COMBINATION;
default:
throw new GraphQLError(`Unknown business type: ${businessType}`);
}
}
export function currencyTypeSafeParser(currencyType: SchemaTypes.CurrencyType): CurrencyType {
switch (currencyType) {
case 'SHEKELS':
return CurrencyType.SHEKELS;
case 'DOLLARS':
return CurrencyType.DOLLARS;
default:
throw new GraphQLError(`Unknown currency type: ${currencyType}`);
}
}
export function ifrsReportingOptionSafeParser(
ifrsReportingOption?: SchemaTypes.IfrsReportingOption,
): IFRSReportingOption | undefined {
if (ifrsReportingOption == null) {
return undefined;
}
switch (ifrsReportingOption) {
case 'OPTION_1':
return IFRSReportingOption.OPTION_1;
case 'OPTION_2_ADJUSTMENTS':
return IFRSReportingOption.OPTION_2_ADJUSTMENTS;
case 'OPTION_3_ADJUSTMENTS':
return IFRSReportingOption.OPTION_3_ADJUSTMENTS;
case 'NONE':
return IFRSReportingOption.NONE;
default:
throw new GraphQLError(`Unknown IFRS reporting option: ${ifrsReportingOption}`);
}
}
export function reportingMethodSafeParser(
reportingMethod: SchemaTypes.ReportingMethod,
): ReportingMethod {
switch (reportingMethod) {
case 'CASH':
return ReportingMethod.CASH;
case 'ACCRUAL':
return ReportingMethod.ACCRUAL;
case 'DOLLAR_REGULATIONS':
return ReportingMethod.DOLLAR_REGULATIONS;
default:
throw new GraphQLError(`Unknown reporting method: ${reportingMethod}`);
}
}
export function yesNoSafeParser(yesNo: boolean): YesNo {
switch (yesNo) {
case true:
return YesNo.YES;
case false:
return YesNo.NO;
default:
throw new GraphQLError(`Unknown Yes/No value: ${yesNo}`);
}
}
export function yesNoOptionalSafeParser(yesNo?: boolean | null): YesNo | undefined {
if (yesNo == null) {
return undefined;
}
return yesNoSafeParser(yesNo);
}
export function individualOrCompanySafeParser(
individualOrCompany: SchemaTypes.IndividualOrCompany,
): IndividualOrCompanyEnum {
switch (individualOrCompany) {
case 'INDIVIDUAL':
return IndividualOrCompanyEnum.INDIVIDUAL;
case 'COMPANY':
return IndividualOrCompanyEnum.COMPANY;
default:
throw new GraphQLError(`Unknown individual or company type: ${individualOrCompany}`);
}
}
export function yesNoToBoolean(yesNo: YesNo): boolean {
switch (yesNo) {
case YesNo.YES:
return true;
case YesNo.NO:
return false;
default:
throw new GraphQLError(`Unknown Yes/No value: ${yesNo}`);
}
}
export function yesNoToOptionalBoolean(yesNo?: YesNo): boolean | undefined {
if (yesNo == null) {
return undefined;
}
return yesNoToBoolean(yesNo);
}
export function convertLocalReportDataToShaam6111ReportData(
localReportData: SchemaTypes.Shaam6111Data,
): ReportData {
const profitAndLoss: ReportEntry[] = localReportData.profitAndLoss.map(entry => ({
code: entry.code,
amount: entry.amount,
}));
const taxAdjustment: ReportEntry[] = localReportData.taxAdjustment.map(entry => ({
code: entry.code,
amount: entry.amount,
}));
const balanceSheet: ReportEntry[] = localReportData.balanceSheet
? localReportData.balanceSheet.map(entry => ({
code: entry.code,
amount: entry.amount,
}))
: [];
const adjustedReportData: ReportData = {
profitAndLoss,
taxAdjustment,
balanceSheet,
header: {
...localReportData.header,
vatFileNumber: localReportData.header.vatFileNumber || undefined,
withholdingTaxFileNumber: localReportData.header.withholdingTaxFileNumber || undefined,
businessDescription: localReportData.header.businessDescription || undefined,
businessType: businessTypeSafeParser(localReportData.header.businessType),
reportingMethod: reportingMethodSafeParser(localReportData.header.reportingMethod),
accountingMethod: accountingMethodSafeParser(localReportData.header.accountingMethod),
accountingSystem: accountingSystemSafeParser(localReportData.header.accountingSystem),
isPartnership: yesNoOptionalSafeParser(localReportData.header.isPartnership),
includesProfitLoss: yesNoSafeParser(localReportData.header.includesProfitLoss),
includesTaxAdjustment: yesNoSafeParser(localReportData.header.includesTaxAdjustment),
includesBalanceSheet: yesNoSafeParser(localReportData.header.includesBalanceSheet),
profitLossEntryCount: localReportData.profitAndLoss.length,
taxAdjustmentEntryCount: localReportData.taxAdjustment.length,
balanceSheetEntryCount: localReportData.balanceSheet?.length,
ifrsImplementationYear: localReportData.header.ifrsImplementationYear || undefined,
ifrsReportingOption: localReportData.header.ifrsReportingOption
? ifrsReportingOptionSafeParser(localReportData.header.ifrsReportingOption)
: undefined,
softwareRegistrationNumber: localReportData.header.softwareRegistrationNumber || undefined,
partnershipCount: localReportData.header.partnershipCount || undefined,
partnershipProfitShare: localReportData.header.partnershipProfitShare || undefined,
currencyType: currencyTypeSafeParser(localReportData.header.currencyType),
auditOpinionType: auditOpinionTypeSafeParser(localReportData.header.auditOpinionType),
amountsInThousands: yesNoSafeParser(localReportData.header.amountsInThousands),
},
individualOrCompany: individualOrCompanySafeParser(
localReportData.individualOrCompany ?? 'COMPANY', // TODO: deduce default value from header
),
};
return adjustedReportData;
}