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

@@ -11,14 +11,25 @@ exports.up = (knex) => {
table.timestamps();
})
.table('items_entries', (table) => {
table.boolean(['is_tax_exclusive']);
table.boolean('is_tax_exclusive');
table.string('tax_code');
table.decimal('tax_rate');
table.decimal('tax_amount_withheld')
})
.table('sales_invoices', (table) => {
table.boolean(['is_tax_exclusive']);
table.decimal('tax_amount_withheld')
table.boolean('is_tax_exclusive');
table.decimal('tax_amount_withheld');
})
.createTable('tax_rate_transactions', (table) => {
table.increments('id');
table.string('tax_name');
table.string('tax_code');
table.string('reference_type');
table.integer('reference_id');
table.decimal('tax_amount');
table.integer('tax_account_id').unsigned();
});
};

View File

@@ -1,7 +1,17 @@
export const TaxPayableAccount = {
name: 'Tax Payable',
slug: 'tax-payable',
account_type: 'other-current-liability',
code: '20006',
description: '',
active: 1,
index: 1,
predefined: 1,
};
export default [
{
name:'Bank Account',
name: 'Bank Account',
slug: 'bank-account',
account_type: 'bank',
code: '10001',
@@ -11,7 +21,7 @@ export default [
predefined: 1,
},
{
name:'Saving Bank Account',
name: 'Saving Bank Account',
slug: 'saving-bank-account',
account_type: 'bank',
code: '10002',
@@ -21,7 +31,7 @@ export default [
predefined: 0,
},
{
name:'Undeposited Funds',
name: 'Undeposited Funds',
slug: 'undeposited-funds',
account_type: 'cash',
code: '10003',
@@ -31,7 +41,7 @@ export default [
predefined: 1,
},
{
name:'Petty Cash',
name: 'Petty Cash',
slug: 'petty-cash',
account_type: 'cash',
code: '10004',
@@ -41,7 +51,7 @@ export default [
predefined: 1,
},
{
name:'Computer Equipment',
name: 'Computer Equipment',
slug: 'computer-equipment',
code: '10005',
account_type: 'fixed-asset',
@@ -52,7 +62,7 @@ export default [
description: '',
},
{
name:'Office Equipment',
name: 'Office Equipment',
slug: 'office-equipment',
code: '10006',
account_type: 'fixed-asset',
@@ -63,7 +73,7 @@ export default [
description: '',
},
{
name:'Accounts Receivable (A/R)',
name: 'Accounts Receivable (A/R)',
slug: 'accounts-receivable',
account_type: 'accounts-receivable',
code: '10007',
@@ -73,7 +83,7 @@ export default [
predefined: 1,
},
{
name:'Inventory Asset',
name: 'Inventory Asset',
slug: 'inventory-asset',
code: '10008',
account_type: 'inventory',
@@ -81,12 +91,13 @@ export default [
parent_account_id: null,
index: 1,
active: 1,
description:'An account that holds valuation of products or goods that availiable for sale.',
description:
'An account that holds valuation of products or goods that availiable for sale.',
},
// Libilities
{
name:'Accounts Payable (A/P)',
name: 'Accounts Payable (A/P)',
slug: 'accounts-payable',
account_type: 'accounts-payable',
parent_account_id: null,
@@ -97,38 +108,39 @@ export default [
predefined: 1,
},
{
name:'Owner A Drawings',
name: 'Owner A Drawings',
slug: 'owner-drawings',
account_type: 'other-current-liability',
parent_account_id: null,
code: '20002',
description:'Withdrawals by the owners.',
description: 'Withdrawals by the owners.',
active: 1,
index: 1,
predefined: 0,
},
{
name:'Loan',
name: 'Loan',
slug: 'owner-drawings',
account_type: 'other-current-liability',
code: '20003',
description:'Money that has been borrowed from a creditor.',
description: 'Money that has been borrowed from a creditor.',
active: 1,
index: 1,
predefined: 0,
},
{
name:'Opening Balance Liabilities',
name: 'Opening Balance Liabilities',
slug: 'opening-balance-liabilities',
account_type: 'other-current-liability',
code: '20004',
description:'This account will hold the difference in the debits and credits entered during the opening balance..',
description:
'This account will hold the difference in the debits and credits entered during the opening balance..',
active: 1,
index: 1,
predefined: 0,
},
{
name:'Revenue Received in Advance',
name: 'Revenue Received in Advance',
slug: 'revenue-received-in-advance',
account_type: 'other-current-liability',
parent_account_id: null,
@@ -138,34 +150,27 @@ export default [
index: 1,
predefined: 0,
},
{
name:'Sales Tax Payable',
slug: 'owner-drawings',
account_type: 'other-current-liability',
code: '20006',
description: '',
active: 1,
index: 1,
predefined: 1,
},
TaxPayableAccount,
// Equity
{
name:'Retained Earnings',
name: 'Retained Earnings',
slug: 'retained-earnings',
account_type: 'equity',
code: '30001',
description:'Retained earnings tracks net income from previous fiscal years.',
description:
'Retained earnings tracks net income from previous fiscal years.',
active: 1,
index: 1,
predefined: 1,
},
{
name:'Opening Balance Equity',
name: 'Opening Balance Equity',
slug: 'opening-balance-equity',
account_type: 'equity',
code: '30002',
description:'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
description:
'When you enter opening balances to the accounts, the amounts enter in Opening balance equity. This ensures that you have a correct trial balance sheet for your company, without even specific the second credit or debit entry.',
active: 1,
index: 1,
predefined: 1,
@@ -181,11 +186,12 @@ export default [
predefined: 1,
},
{
name:`Drawings`,
name: `Drawings`,
slug: 'drawings',
account_type: 'equity',
code: '30003',
description:'Goods purchased with the intention of selling these to customers',
description:
'Goods purchased with the intention of selling these to customers',
active: 1,
index: 1,
predefined: 1,
@@ -193,7 +199,7 @@ export default [
// Expenses
{
name:'Other Expenses',
name: 'Other Expenses',
slug: 'other-expenses',
account_type: 'other-expense',
parent_account_id: null,
@@ -204,18 +210,18 @@ export default [
predefined: 1,
},
{
name:'Cost of Goods Sold',
name: 'Cost of Goods Sold',
slug: 'cost-of-goods-sold',
account_type: 'cost-of-goods-sold',
parent_account_id: null,
code: '40002',
description:'Tracks the direct cost of the goods sold.',
description: 'Tracks the direct cost of the goods sold.',
active: 1,
index: 1,
predefined: 1,
},
{
name:'Office expenses',
name: 'Office expenses',
slug: 'office-expenses',
account_type: 'expense',
parent_account_id: null,
@@ -226,7 +232,7 @@ export default [
predefined: 0,
},
{
name:'Rent',
name: 'Rent',
slug: 'rent',
account_type: 'expense',
parent_account_id: null,
@@ -237,29 +243,30 @@ export default [
predefined: 0,
},
{
name:'Exchange Gain or Loss',
name: 'Exchange Gain or Loss',
slug: 'exchange-grain-loss',
account_type: 'other-expense',
parent_account_id: null,
code: '40005',
description:'Tracks the gain and losses of the exchange differences.',
description: 'Tracks the gain and losses of the exchange differences.',
active: 1,
index: 1,
predefined: 1,
},
{
name:'Bank Fees and Charges',
name: 'Bank Fees and Charges',
slug: 'bank-fees-and-charges',
account_type: 'expense',
parent_account_id: null,
code: '40006',
description: 'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
description:
'Any bank fees levied is recorded into the bank fees and charges account. A bank account maintenance fee, transaction charges, a late payment fee are some examples.',
active: 1,
index: 1,
predefined: 0,
},
{
name:'Depreciation Expense',
name: 'Depreciation Expense',
slug: 'depreciation-expense',
account_type: 'expense',
parent_account_id: null,
@@ -272,7 +279,7 @@ export default [
// Income
{
name:'Sales of Product Income',
name: 'Sales of Product Income',
slug: 'sales-of-product-income',
account_type: 'income',
predefined: 1,
@@ -283,7 +290,7 @@ export default [
description: '',
},
{
name:'Sales of Service Income',
name: 'Sales of Service Income',
slug: 'sales-of-service-income',
account_type: 'income',
predefined: 0,
@@ -294,7 +301,7 @@ export default [
description: '',
},
{
name:'Uncategorized Income',
name: 'Uncategorized Income',
slug: 'uncategorized-income',
account_type: 'income',
parent_account_id: null,
@@ -305,14 +312,15 @@ export default [
predefined: 1,
},
{
name:'Other Income',
name: 'Other Income',
slug: 'other-income',
account_type: 'other-income',
parent_account_id: null,
code: '50004',
description:'The income activities are not associated to the core business.',
description:
'The income activities are not associated to the core business.',
active: 1,
index: 1,
predefined: 0,
}
];
},
];

View File

@@ -54,6 +54,14 @@ export interface IItemDTO {
sellDescription: string;
purchaseDescription: string;
// Used as an override if the default Tax Code for the selected `costAccountId` is not correct
purchaseTaxCode: string;
purchaseTaxId: string;
// Used as an override if the default Tax Code for the selected `sellAccountId` is not correct
saleTaxCode: string;
saleTaxId: string;
quantityOnHand: number;
note: string;

View File

@@ -34,6 +34,7 @@ export interface IItemEntry {
taxCode: string;
taxRate: number;
taxAmount: number;
item?: IItem;

View File

@@ -1,5 +1,5 @@
import { Knex } from 'knex';
import { IItemEntry } from './ItemEntry';
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
export interface ISaleEstimate {
@@ -29,7 +29,7 @@ export interface ISaleEstimateDTO {
estimateDate?: Date;
reference?: string;
estimateNumber?: string;
entries: IItemEntry[];
entries: IItemEntryDTO[];
note: string;
termsConditions: string;
sendToEmail: string;

View File

@@ -1,5 +1,5 @@
import { Knex } from 'knex';
import { ISystemUser, IAccount } from '@/interfaces';
import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
@@ -33,6 +33,9 @@ export interface ISaleInvoice {
writtenoffExpenseAccountId?: number;
writtenoffExpenseAccount?: IAccount;
taxAmountWithheld: number;
taxes: ITaxTransaction[]
}
export interface ISaleInvoiceDTO {

View File

@@ -47,3 +47,10 @@ export interface ITaxRateDeletedPayload {
tenantId: number;
trx: Knex.Transaction;
}
export interface ITaxTransaction {
taxAmount: number;
taxName: string;
taxCode: string;
}

View File

@@ -81,6 +81,9 @@ import { ProjectBillableExpensesSubscriber } from '@/services/Projects/Projects/
import { ProjectBillableBillSubscriber } from '@/services/Projects/Projects/ProjectBillableBillSubscriber';
import { SyncActualTimeTaskSubscriber } from '@/services/Projects/Times/SyncActualTimeTaskSubscriber';
import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber';
import { SaleEstimateTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleEstimateTaxRateValidateSubscriber';
import { SaleReceiptTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleReceiptTaxRateValidateSubscriber';
import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber';
export default () => {
return new EventPublisher();
@@ -188,6 +191,11 @@ export const susbcribers = () => {
ProjectBillableTasksSubscriber,
ProjectBillableExpensesSubscriber,
ProjectBillableBillSubscriber,
SaleInvoiceTaxRateValidateSubscriber
// Tax Rates
SaleInvoiceTaxRateValidateSubscriber,
SaleEstimateTaxRateValidateSubscriber,
SaleReceiptTaxRateValidateSubscriber,
WriteInvoiceTaxTransactionsSubscriber
];
};

View File

@@ -59,6 +59,7 @@ import Project from 'models/Project';
import Time from 'models/Time';
import Task from 'models/Task';
import TaxRate from 'models/TaxRate';
import TaxRateTransaction from 'models/TaxRateTransaction';
export default (knex) => {
const models = {
@@ -121,6 +122,7 @@ export default (knex) => {
Time,
Task,
TaxRate,
TaxRateTransaction,
};
return mapValues(models, (model) => model.bindKnex(knex));
};

View File

@@ -2,6 +2,8 @@ import { Model } from 'objection';
import TenantModel from 'models/TenantModel';
export default class ItemEntry extends TenantModel {
public taxRate: number;
/**
* Table name.
*/
@@ -17,7 +19,7 @@ export default class ItemEntry extends TenantModel {
}
static get virtualAttributes() {
return ['amount'];
return ['amount', 'taxAmount'];
}
get amount() {
@@ -31,6 +33,22 @@ export default class ItemEntry extends TenantModel {
return discount ? total - total * discount * 0.01 : total;
}
/**
* Tag rate fraction.
* @returns {number}
*/
get tagRateFraction() {
return this.taxRate / 100;
}
/**
* Tax amount withheld.
* @returns {number}
*/
get taxAmount() {
return this.amount * this.tagRateFraction;
}
static get relationMappings() {
const Item = require('models/Item');
const BillLandedCostEntry = require('models/BillLandedCostEntry');

View File

@@ -13,6 +13,11 @@ export default class SaleInvoice extends mixin(TenantModel, [
CustomViewBaseModel,
ModelSearchable,
]) {
taxAmountWithheld: number;
balance: number;
paymentAmount: number;
exchangeRate: number;
/**
* Table name
*/
@@ -51,12 +56,115 @@ export default class SaleInvoice extends mixin(TenantModel, [
];
}
/**
* Invoice total FCY.
* @returns {number}
*/
get totalFcy() {
return this.amountFcy + this.taxAmountWithheldFcy;
}
/**
* Invoice total BCY.
* @returns {number}
*/
get totalBcy() {
return this.amountBcy + this.taxAmountWithheldBcy;
}
/**
* Tax amount withheld FCY.
* @returns {number}
*/
get taxAmountWithheldFcy() {
return this.taxAmountWithheld;
}
/**
* Tax amount withheld BCY.
* @returns {number}
*/
get taxAmountWithheldBcy() {
return this.taxAmountWithheld;
}
/**
* Subtotal FCY.
* @returns {number}
*/
get subtotalFcy() {
return this.amountFcy;
}
/**
* Subtotal BCY.
* @returns {number}
*/
get subtotalBcy() {
return this.amountBcy;
}
/**
* Invoice due amount FCY.
* @returns {number}
*/
get dueAmountFcy() {
return this.amountFcy - this.paymentAmountFcy;
}
/**
* Invoice due amount BCY.
* @returns {number}
*/
get dueAmountBcy() {
return this.amountBcy - this.paymentAmountBcy;
}
/**
* Invoice amount FCY.
* @returns {number}
*/
get amountFcy() {
return this.balance;
}
/**
* Invoice amount BCY.
* @returns {number}
*/
get amountBcy() {
return this.balance * this.exchangeRate;
}
/**
* Invoice payment amount FCY.
* @returns {number}
*/
get paymentAmountFcy() {
return this.paymentAmount;
}
/**
* Invoice payment amount BCY.
* @returns {number}
*/
get paymentAmountBcy() {
return this.paymentAmount * this.exchangeRate;
}
/**
*
*/
get total() {
return this.balance + this.taxAmountWithheld;
}
/**
* Invoice amount in local currency.
* @returns {number}
*/
get localAmount() {
return this.balance * this.exchangeRate;
return this.total * this.exchangeRate;
}
/**
@@ -333,6 +441,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
const PaymentReceiveEntry = require('models/PaymentReceiveEntry');
const Branch = require('models/Branch');
const Account = require('models/Account');
const TaxRateTransaction = require('models/TaxRateTransaction');
return {
/**
@@ -428,6 +537,21 @@ export default class SaleInvoice extends mixin(TenantModel, [
to: 'accounts.id',
},
},
/**
*
*/
taxes: {
relation: Model.HasManyRelation,
modelClass: TaxRateTransaction.default,
join: {
from: 'sales_invoices.id',
to: 'tax_rate_transactions.referenceId',
},
filter(builder) {
builder.where('reference_type', 'SaleInvoice');
},
},
};
}

View File

@@ -0,0 +1,42 @@
import { mixin, Model, raw } from 'objection';
import TenantModel from 'models/TenantModel';
import ModelSearchable from './ModelSearchable';
export default class TaxRateTransaction extends mixin(TenantModel, [
ModelSearchable,
]) {
/**
* Table name
*/
static get tableName() {
return 'tax_rate_transactions';
}
/**
* Timestamps columns.
*/
get timestamps() {
return [];
}
/**
* Virtual attributes.
*/
static get virtualAttributes() {
return [];
}
/**
* Model modifiers.
*/
static get modifiers() {
return {};
}
/**
* Relationship mapping.
*/
static get relationMappings() {
return {};
}
}

View File

@@ -2,6 +2,7 @@ import { Account } from 'models';
import TenantRepository from '@/repositories/TenantRepository';
import { IAccount } from '@/interfaces';
import { Knex } from 'knex';
import { TaxPayableAccount } from '@/database/seeds/data/accounts';
export default class AccountRepository extends TenantRepository {
/**
@@ -116,7 +117,7 @@ export default class AccountRepository extends TenantRepository {
if (!result) {
result = await this.model.query(trx).insertAndFetch({
name: this.i18n.__('account.accounts_receivable.currency', {
currency: currencyCode
currency: currencyCode,
}),
accountType: 'accounts-receivable',
currencyCode,
@@ -127,6 +128,29 @@ export default class AccountRepository extends TenantRepository {
return result;
};
/**
* Find or create tax payable account.
* @param {Record<string, string>}extraAttrs
* @param {Knex.Transaction} trx
* @returns
*/
async findOrCreateTaxPayable(
extraAttrs: Record<string, string> = {},
trx?: Knex.Transaction
) {
let result = await this.model
.query(trx)
.findOne({ slug: TaxPayableAccount.slug, ...extraAttrs });
if (!result) {
result = await this.model.query(trx).insertAndFetch({
...TaxPayableAccount,
...extraAttrs,
});
}
return result;
}
findOrCreateAccountsPayable = async (
currencyCode: string = '',
extraAttrs = {},

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
);
};
}