feat(server): wip tax rate on sale invoice service

This commit is contained in:
Ahmed Bouhuolia
2023-08-14 14:59:10 +02:00
parent a7644e6481
commit d1121f0b81
18 changed files with 514 additions and 74 deletions

View File

@@ -17,6 +17,7 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
import { SaleInvoiceIncrement } from './SaleInvoiceIncrement';
import { formatDateFields } from 'utils';
import { ItemEntriesTaxTransactions } from '@/services/TaxRates/ItemEntriesTaxTransactions';
@Service()
export class CommandSaleInvoiceDTOTransformer {
@@ -38,6 +39,9 @@ export class CommandSaleInvoiceDTOTransformer {
@Inject()
private invoiceIncrement: SaleInvoiceIncrement;
@Inject()
private taxDTOTransformer: ItemEntriesTaxTransactions;
/**
* Transformes the create DTO to invoice object model.
* @param {ISaleInvoiceCreateDTO} saleInvoiceDTO - Sale invoice DTO.
@@ -96,6 +100,7 @@ export class CommandSaleInvoiceDTOTransformer {
} as ISaleInvoice;
return R.compose(
this.taxDTOTransformer.assocTaxAmountWithheldFromEntries,
this.branchDTOTransform.transformDTO<ISaleInvoice>(tenantId),
this.warehouseDTOTransform.transformDTO<ISaleInvoice>(tenantId)
)(initialDTO);

View File

@@ -1,12 +1,13 @@
import * as R from 'ramda';
import { Knex } from 'knex';
import {
ISaleInvoice,
IItemEntry,
ILedgerEntry,
AccountNormal,
ILedger,
ITaxTransaction,
} from '@/interfaces';
import { Knex } from 'knex';
import { Service, Inject } from 'typedi';
import Ledger from '@/services/Accounting/Ledger';
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
@@ -22,8 +23,8 @@ export class SaleInvoiceGLEntries {
/**
* Writes a sale invoice GL entries.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {number} tenantId - Tenant id.
* @param {number} saleInvoiceId - Sale invoice id.
* @param {Knex.Transaction} trx
*/
public writeInvoiceGLEntries = async (
@@ -42,9 +43,17 @@ export class SaleInvoiceGLEntries {
const ARAccount = await accountRepository.findOrCreateAccountReceivable(
saleInvoice.currencyCode
);
// Find or create tax payable account.
const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
{},
trx
);
// Retrieves the ledger of the invoice.
const ledger = this.getInvoiceGLedger(saleInvoice, ARAccount.id);
const ledger = this.getInvoiceGLedger(
saleInvoice,
ARAccount.id,
taxPayableAccount.id
);
// Commits the ledger entries to the storage as UOW.
await this.ledegrRepository.commit(tenantId, ledger, trx);
};
@@ -94,10 +103,14 @@ export class SaleInvoiceGLEntries {
*/
public getInvoiceGLedger = (
saleInvoice: ISaleInvoice,
ARAccountId: number
ARAccountId: number,
taxPayableAccountId: number
): ILedger => {
const entries = this.getInvoiceGLEntries(saleInvoice, ARAccountId);
const entries = this.getInvoiceGLEntries(
saleInvoice,
ARAccountId,
taxPayableAccountId
);
return new Ledger(entries);
};
@@ -143,7 +156,7 @@ export class SaleInvoiceGLEntries {
return {
...commonEntry,
debit: saleInvoice.localAmount,
debit: saleInvoice.totalBcy,
accountId: ARAccountId,
contactId: saleInvoice.customerId,
accountNormal: AccountNormal.DEBIT,
@@ -176,7 +189,27 @@ export class SaleInvoiceGLEntries {
itemId: entry.itemId,
itemQuantity: entry.quantity,
accountNormal: AccountNormal.CREDIT,
projectId: entry.projectId || saleInvoice.projectId
projectId: entry.projectId || saleInvoice.projectId,
};
}
);
/**
* Retreives the GL entry of tax payable.
* @param {ISaleInvoice} saleInvoice -
* @param {number} taxPayableAccountId -
* @returns {ILedgerEntry}
*/
private getInvoiceTaxEntry = R.curry(
(saleInvoice: ISaleInvoice, taxPayableAccountId: number): ILedgerEntry => {
const commonEntry = this.getInvoiceGLCommonEntry(saleInvoice);
return {
...commonEntry,
credit: saleInvoice.taxAmountWithheld,
accountId: taxPayableAccountId,
index: saleInvoice.entries.length + 3,
accountNormal: AccountNormal.CREDIT,
};
}
);
@@ -189,15 +222,18 @@ export class SaleInvoiceGLEntries {
*/
public getInvoiceGLEntries = (
saleInvoice: ISaleInvoice,
ARAccountId: number
ARAccountId: number,
taxPayableAccountId: number
): ILedgerEntry[] => {
const receivableEntry = this.getInvoiceReceivableEntry(
saleInvoice,
ARAccountId
);
const transformItemEntry = this.getInvoiceItemEntry(saleInvoice);
const creditEntries = saleInvoice.entries.map(transformItemEntry);
return [receivableEntry, ...creditEntries];
const creditEntries = saleInvoice.entries.map(transformItemEntry);
const taxEntry = this.getInvoiceTaxEntry(saleInvoice, taxPayableAccountId);
return [receivableEntry, ...creditEntries, taxEntry];
};
}

View File

@@ -0,0 +1,22 @@
import { ItemEntry } from "@/models";
import { sumBy } from "lodash";
import { Service } from "typedi";
@Service()
export class ItemEntriesTaxTransactions {
/**
*
* @param model
* @returns
*/
public assocTaxAmountWithheldFromEntries(model: any) {
const entries = model.entries.map((entry) => ItemEntry.fromJson(entry));
const taxAmountWithheld = sumBy(entries, 'taxAmount');
if (taxAmountWithheld) {
model.taxAmountWithheld = taxAmountWithheld;
}
return model;
}
}

View File

@@ -0,0 +1,65 @@
import { sumBy, chain } from 'lodash';
import { IItemEntry } from '@/interfaces';
import HasTenancyService from '../Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
@Service()
export class WriteTaxTransactionsItemEntries {
@Inject()
private tenancy: HasTenancyService;
/**
* Writes the tax transactions from the given item entries.
* @param {number} tenantId
* @param {IItemEntry[]} itemEntries
*/
public async writeTaxTransactionsFromItemEntries(
tenantId: number,
itemEntries: IItemEntry[]
) {
const { TaxRateTransaction } = this.tenancy.models(tenantId);
const aggregatedEntries = this.aggregateItemEntriesByTaxCode(itemEntries);
const taxTransactions = aggregatedEntries.map((entry) => ({
taxName: 'TAX NAME',
taxCode: 'TAG_CODE',
referenceType: entry.referenceType,
referenceId: entry.referenceId,
taxAmount: entry.taxAmount,
}));
await TaxRateTransaction.query().upsertGraph(taxTransactions);
}
/**
*
* @param {IItemEntry[]} itemEntries
* @returns {}
*/
private aggregateItemEntriesByTaxCode(itemEntries: IItemEntry[]) {
return chain(itemEntries.filter((item) => item.taxCode))
.groupBy((item) => item.taxCode)
.values()
.map((group) => ({ ...group[0], amount: sumBy(group, 'amount') }))
.value();
}
/**
*
* @param itemEntries
*/
private aggregateItemEntriesByReferenceTypeId(itemEntries: IItemEntry) {}
/**
* Removes the tax transactions from the given item entries.
* @param tenantId
* @param itemEntries
*/
public removeTaxTransactionsFromItemEntries(
tenantId: number,
itemEntries: IItemEntry[]
) {
const { TaxRateTransaction } = this.tenancy.models(tenantId);
const filteredEntries = itemEntries.filter((item) => item.taxCode);
}
}

View File

@@ -0,0 +1,56 @@
import { Inject, Service } from 'typedi';
import {
ISaleInvoiceCreatedPayload,
ISaleInvoiceDeletedPayload,
} from '@/interfaces';
import events from '@/subscribers/events';
import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries';
@Service()
export class WriteInvoiceTaxTransactionsSubscriber {
@Inject()
private writeTaxTransactions: WriteTaxTransactionsItemEntries;
/**
* Attaches events with handlers.
*/
public attach(bus) {
bus.subscribe(
events.saleInvoice.onCreated,
this.writeInvoiceTaxTransactionsOnCreated
);
bus.subscribe(
events.saleInvoice.onDeleted,
this.removeInvoiceTaxTransactionsOnDeleted
);
return bus;
}
/**
* Validate receipt entries tax rate code existance.
* @param {ISaleInvoiceCreatingPaylaod}
*/
private writeInvoiceTaxTransactionsOnCreated = async ({
tenantId,
saleInvoice,
}: ISaleInvoiceCreatedPayload) => {
await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries(
tenantId,
saleInvoice.entries
);
};
/**
* Removes the invoice tax transactions on invoice deleted.
* @param {ISaleInvoiceEditingPayload}
*/
private removeInvoiceTaxTransactionsOnDeleted = async ({
tenantId,
oldSaleInvoice,
}: ISaleInvoiceDeletedPayload) => {
await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries(
tenantId,
oldSaleInvoice.entries
);
};
}