mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat: integrate tax rates to bills (#260)
This commit is contained in:
@@ -115,6 +115,8 @@ export default class BillsController extends BaseController {
|
||||
check('note').optional().trim().escape(),
|
||||
check('open').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('is_inclusive_tax').default(false).isBoolean().toBoolean(),
|
||||
|
||||
check('entries').isArray({ min: 1 }),
|
||||
|
||||
check('entries.*.index').exists().isNumeric().toInt(),
|
||||
@@ -137,6 +139,15 @@ export default class BillsController extends BaseController {
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
check('entries.*.tax_code')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.escape()
|
||||
.isString(),
|
||||
check('entries.*.tax_rate_id')
|
||||
.optional({ nullable: true })
|
||||
.isNumeric()
|
||||
.toInt(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -542,6 +553,16 @@ export default class BillsController extends BaseController {
|
||||
],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_CODE_NOT_FOUND', code: 1800 }],
|
||||
});
|
||||
}
|
||||
if (error.errorType === 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND') {
|
||||
return res.boom.badRequest(null, {
|
||||
errors: [{ type: 'ITEM_ENTRY_TAX_RATE_ID_NOT_FOUND', code: 1900 }],
|
||||
});
|
||||
}
|
||||
}
|
||||
next(error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
exports.up = (knex) => {
|
||||
return knex.schema.table('bills', (table) => {
|
||||
table.boolean('is_inclusive_tax').defaultTo(false);
|
||||
table.decimal('tax_amount_withheld');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = (knex) => {
|
||||
return knex.schema.table('bills', () => {});
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { Knex } from 'knex';
|
||||
import { IDynamicListFilterDTO } from './DynamicFilter';
|
||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||
import { IBillLandedCost } from './LandedCost';
|
||||
|
||||
export interface IBillDTO {
|
||||
vendorId: number;
|
||||
billNumber: string;
|
||||
@@ -15,10 +16,10 @@ export interface IBillDTO {
|
||||
exchangeRate?: number;
|
||||
open: boolean;
|
||||
entries: IItemEntryDTO[];
|
||||
|
||||
branchId?: number;
|
||||
warehouseId?: number;
|
||||
projectId?: number;
|
||||
isInclusiveTax?: boolean;
|
||||
}
|
||||
|
||||
export interface IBillEditDTO {
|
||||
@@ -80,6 +81,15 @@ export interface IBill {
|
||||
|
||||
localAmount?: number;
|
||||
locatedLandedCosts?: IBillLandedCost[];
|
||||
|
||||
amountLocal: number;
|
||||
subtotal: number;
|
||||
subtotalLocal: number;
|
||||
subtotalExcludingTax: number;
|
||||
taxAmountWithheld: number;
|
||||
taxAmountWithheldLocal: number;
|
||||
total: number;
|
||||
totalLocal: number;
|
||||
}
|
||||
|
||||
export interface IBillsFilter extends IDynamicListFilterDTO {
|
||||
|
||||
@@ -81,6 +81,8 @@ import { ProjectBillableBillSubscriber } from '@/services/Projects/Projects/Proj
|
||||
import { SyncActualTimeTaskSubscriber } from '@/services/Projects/Times/SyncActualTimeTaskSubscriber';
|
||||
import { SaleInvoiceTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/SaleInvoiceTaxRateValidateSubscriber';
|
||||
import { WriteInvoiceTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteInvoiceTaxTransactionsSubscriber';
|
||||
import { BillTaxRateValidateSubscriber } from '@/services/TaxRates/subscribers/BillTaxRateValidateSubscriber';
|
||||
import { WriteBillTaxTransactionsSubscriber } from '@/services/TaxRates/subscribers/WriteBillTaxTransactionsSubscriber';
|
||||
|
||||
export default () => {
|
||||
return new EventPublisher();
|
||||
@@ -188,8 +190,12 @@ export const susbcribers = () => {
|
||||
ProjectBillableExpensesSubscriber,
|
||||
ProjectBillableBillSubscriber,
|
||||
|
||||
// Tax Rates
|
||||
// Tax Rates - Sale Invoice
|
||||
SaleInvoiceTaxRateValidateSubscriber,
|
||||
WriteInvoiceTaxTransactionsSubscriber,
|
||||
|
||||
// Tax Rates - Bills
|
||||
BillTaxRateValidateSubscriber,
|
||||
WriteBillTaxTransactionsSubscriber,
|
||||
];
|
||||
};
|
||||
|
||||
@@ -13,6 +13,109 @@ export default class Bill extends mixin(TenantModel, [
|
||||
CustomViewBaseModel,
|
||||
ModelSearchable,
|
||||
]) {
|
||||
public amount: number;
|
||||
public paymentAmount: number;
|
||||
public landedCostAmount: number;
|
||||
public allocatedCostAmount: number;
|
||||
public isInclusiveTax: boolean;
|
||||
public taxAmountWithheld: number;
|
||||
public exchangeRate: number;
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'balance',
|
||||
'dueAmount',
|
||||
'isOpen',
|
||||
'isPartiallyPaid',
|
||||
'isFullyPaid',
|
||||
'isPaid',
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
'isOverdue',
|
||||
'unallocatedCostAmount',
|
||||
'localAmount',
|
||||
'localAllocatedCostAmount',
|
||||
'billableAmount',
|
||||
'amountLocal',
|
||||
'subtotal',
|
||||
'subtotalLocal',
|
||||
'subtotalExludingTax',
|
||||
'taxAmountWithheldLocal',
|
||||
'total',
|
||||
'totalLocal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get amountLocal() {
|
||||
return this.amount * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotal() {
|
||||
return this.amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtotal in base currency. (Tax inclusive) if the tax inclusive is enabled.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalLocal() {
|
||||
return this.amountLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sale invoice amount excluding tax.
|
||||
* @returns {number}
|
||||
*/
|
||||
get subtotalExcludingTax() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal - this.taxAmountWithheld
|
||||
: this.subtotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tax amount withheld in base currency.
|
||||
* @returns {number}
|
||||
*/
|
||||
get taxAmountWithheldLocal() {
|
||||
return this.taxAmountWithheld * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get total() {
|
||||
return this.isInclusiveTax
|
||||
? this.subtotal
|
||||
: this.subtotal + this.taxAmountWithheld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice total in local currency. (Tax included)
|
||||
* @returns {number}
|
||||
*/
|
||||
get totalLocal() {
|
||||
return this.total * this.exchangeRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table name
|
||||
*/
|
||||
@@ -158,40 +261,13 @@ export default class Bill extends mixin(TenantModel, [
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamps columns.
|
||||
*/
|
||||
get timestamps() {
|
||||
return ['createdAt', 'updatedAt'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attributes.
|
||||
*/
|
||||
static get virtualAttributes() {
|
||||
return [
|
||||
'balance',
|
||||
'dueAmount',
|
||||
'isOpen',
|
||||
'isPartiallyPaid',
|
||||
'isFullyPaid',
|
||||
'isPaid',
|
||||
'remainingDays',
|
||||
'overdueDays',
|
||||
'isOverdue',
|
||||
'unallocatedCostAmount',
|
||||
'localAmount',
|
||||
'localAllocatedCostAmount',
|
||||
'billableAmount',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice amount in organization base currency.
|
||||
* @deprecated
|
||||
* @returns {number}
|
||||
*/
|
||||
get localAmount() {
|
||||
return this.amount * this.exchangeRate;
|
||||
return this.amountLocal;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,7 +307,7 @@ export default class Bill extends mixin(TenantModel, [
|
||||
* @return {number}
|
||||
*/
|
||||
get dueAmount() {
|
||||
return Math.max(this.amount - this.balance, 0);
|
||||
return Math.max(this.total - this.balance, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,7 +323,7 @@ export default class Bill extends mixin(TenantModel, [
|
||||
* @return {boolean}
|
||||
*/
|
||||
get isPartiallyPaid() {
|
||||
return this.dueAmount !== this.amount && this.dueAmount > 0;
|
||||
return this.dueAmount !== this.total && this.dueAmount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -308,7 +384,7 @@ export default class Bill extends mixin(TenantModel, [
|
||||
* Retrieves the calculated amount which have not been invoiced.
|
||||
*/
|
||||
get billableAmount() {
|
||||
return Math.max(this.amount - this.invoicedAmount, 0);
|
||||
return Math.max(this.total - this.invoicedAmount, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,6 +402,7 @@ export default class Bill extends mixin(TenantModel, [
|
||||
const ItemEntry = require('models/ItemEntry');
|
||||
const BillLandedCost = require('models/BillLandedCost');
|
||||
const Branch = require('models/Branch');
|
||||
const TaxRateTransaction = require('models/TaxRateTransaction');
|
||||
|
||||
return {
|
||||
vendor: {
|
||||
@@ -373,6 +450,21 @@ export default class Bill extends mixin(TenantModel, [
|
||||
to: 'branches.id',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Bill may has associated tax rate transactions.
|
||||
*/
|
||||
taxes: {
|
||||
relation: Model.HasManyRelation,
|
||||
modelClass: TaxRateTransaction.default,
|
||||
join: {
|
||||
from: 'bills.id',
|
||||
to: 'tax_rate_transactions.referenceId',
|
||||
},
|
||||
filter(builder) {
|
||||
builder.where('reference_type', 'Bill');
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -203,8 +203,8 @@ export class BillPaymentGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the payment GL payable entry.
|
||||
* @param {IBillPayment} billPayment
|
||||
* @param {number} APAccountId
|
||||
* @param {IBillPayment} billPayment
|
||||
* @param {number} APAccountId
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getPaymentGLPayableEntry = (
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { ItemEntriesTaxTransactions } from '@/services/TaxRates/ItemEntriesTaxTransactions';
|
||||
|
||||
@Service()
|
||||
export class BillDTOTransformer {
|
||||
@@ -23,6 +24,9 @@ export class BillDTOTransformer {
|
||||
@Inject()
|
||||
private warehouseDTOTransform: WarehouseTransactionDTOTransform;
|
||||
|
||||
@Inject()
|
||||
private taxDTOTransformer: ItemEntriesTaxTransactions;
|
||||
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@@ -73,14 +77,24 @@ export class BillDTOTransformer {
|
||||
const billNumber = billDTO.billNumber || oldBill?.billNumber;
|
||||
|
||||
const initialEntries = billDTO.entries.map((entry) => ({
|
||||
reference_type: 'Bill',
|
||||
referenceType: 'Bill',
|
||||
isInclusiveTax: billDTO.isInclusiveTax,
|
||||
...omit(entry, ['amount']),
|
||||
}));
|
||||
const entries = await composeAsync(
|
||||
const asyncEntries = await composeAsync(
|
||||
// Associate tax rate from tax id to entries.
|
||||
this.taxDTOTransformer.assocTaxRateFromTaxIdToEntries(tenantId),
|
||||
// Associate tax rate id from tax code to entries.
|
||||
this.taxDTOTransformer.assocTaxRateIdFromCodeToEntries(tenantId),
|
||||
// Sets the default cost account to the bill entries.
|
||||
this.setBillEntriesDefaultAccounts(tenantId)
|
||||
)(initialEntries);
|
||||
|
||||
const entries = R.compose(
|
||||
// Remove tax code from entries.
|
||||
R.map(R.omit(['taxCode']))
|
||||
)(asyncEntries);
|
||||
|
||||
const initialDTO = {
|
||||
...formatDateFields(omit(billDTO, ['open', 'entries']), [
|
||||
'billDate',
|
||||
@@ -100,6 +114,8 @@ export class BillDTOTransformer {
|
||||
userId: authorizedUser.id,
|
||||
};
|
||||
return R.compose(
|
||||
// Associates tax amount withheld to the model.
|
||||
this.taxDTOTransformer.assocTaxAmountWithheldFromEntries,
|
||||
this.branchDTOTransform.transformDTO(tenantId),
|
||||
this.warehouseDTOTransform.transformDTO(tenantId)
|
||||
)(initialDTO);
|
||||
|
||||
@@ -7,6 +7,7 @@ import { AccountNormal, IBill, IItemEntry, ILedgerEntry } from '@/interfaces';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import Ledger from '@/services/Accounting/Ledger';
|
||||
import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||
|
||||
@Service()
|
||||
export class BillGLEntries {
|
||||
@@ -16,6 +17,9 @@ export class BillGLEntries {
|
||||
@Inject()
|
||||
private ledgerStorage: LedgerStorageService;
|
||||
|
||||
@Inject()
|
||||
private itemsEntriesService: ItemsEntriesService;
|
||||
|
||||
/**
|
||||
* Creates bill GL entries.
|
||||
* @param {number} tenantId -
|
||||
@@ -43,8 +47,16 @@ export class BillGLEntries {
|
||||
{},
|
||||
trx
|
||||
);
|
||||
const billLedger = this.getBillLedger(bill, APAccount.id);
|
||||
|
||||
// Find or create tax payable account.
|
||||
const taxPayableAccount = await accountRepository.findOrCreateTaxPayable(
|
||||
{},
|
||||
trx
|
||||
);
|
||||
const billLedger = this.getBillLedger(
|
||||
bill,
|
||||
APAccount.id,
|
||||
taxPayableAccount.id
|
||||
);
|
||||
// Commit the GL enties on the storage.
|
||||
await this.ledgerStorage.commit(tenantId, billLedger, trx);
|
||||
};
|
||||
@@ -83,7 +95,7 @@ export class BillGLEntries {
|
||||
|
||||
/**
|
||||
* Retrieves the bill common entry.
|
||||
* @param {IBill} bill
|
||||
* @param {IBill} bill
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getBillCommonEntry = (bill: IBill) => {
|
||||
@@ -119,7 +131,7 @@ export class BillGLEntries {
|
||||
(bill: IBill, entry: IItemEntry, index: number): ILedgerEntry => {
|
||||
const commonJournalMeta = this.getBillCommonEntry(bill);
|
||||
|
||||
const localAmount = bill.exchangeRate * entry.amount;
|
||||
const localAmount = bill.exchangeRate * entry.amountExludingTax;
|
||||
const landedCostAmount = sumBy(entry.allocatedCostEntries, 'cost');
|
||||
|
||||
return {
|
||||
@@ -173,7 +185,7 @@ export class BillGLEntries {
|
||||
|
||||
return {
|
||||
...commonJournalMeta,
|
||||
credit: bill.localAmount,
|
||||
credit: bill.totalLocal,
|
||||
accountId: payableAccountId,
|
||||
contactId: bill.vendorId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
@@ -182,15 +194,62 @@ export class BillGLEntries {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the bill tax GL entry.
|
||||
* @param {IBill} bill -
|
||||
* @param {number} taxPayableAccountId -
|
||||
* @param {IItemEntry} entry -
|
||||
* @param {number} index -
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getBillTaxEntry = R.curry(
|
||||
(
|
||||
bill: IBill,
|
||||
taxPayableAccountId: number,
|
||||
entry: IItemEntry,
|
||||
index: number
|
||||
): ILedgerEntry => {
|
||||
const commonJournalMeta = this.getBillCommonEntry(bill);
|
||||
|
||||
return {
|
||||
...commonJournalMeta,
|
||||
debit: entry.taxAmount,
|
||||
index,
|
||||
indexGroup: 30,
|
||||
accountId: taxPayableAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
taxRateId: entry.taxRateId,
|
||||
taxRate: entry.taxRate,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieves the bill tax GL entries.
|
||||
* @param {IBill} bill
|
||||
* @param {number} taxPayableAccountId
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
private getBillTaxEntries = (bill: IBill, taxPayableAccountId: number) => {
|
||||
// Retrieves the non-zero tax entries.
|
||||
const nonZeroTaxEntries = this.itemsEntriesService.getNonZeroEntries(
|
||||
bill.entries
|
||||
);
|
||||
const transformTaxEntry = this.getBillTaxEntry(bill, taxPayableAccountId);
|
||||
|
||||
return nonZeroTaxEntries.map(transformTaxEntry);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given bill GL entries.
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
private getBillGLEntries = (
|
||||
bill: IBill,
|
||||
payableAccountId: number
|
||||
payableAccountId: number,
|
||||
taxPayableAccountId: number
|
||||
): ILedgerEntry[] => {
|
||||
const payableEntry = this.getBillPayableEntry(payableAccountId, bill);
|
||||
|
||||
@@ -201,18 +260,28 @@ export class BillGLEntries {
|
||||
const landedCostEntries = bill.locatedLandedCosts.map(
|
||||
landedCostTransformer
|
||||
);
|
||||
const taxEntries = this.getBillTaxEntries(bill, taxPayableAccountId);
|
||||
|
||||
// Allocate cost entries journal entries.
|
||||
return [payableEntry, ...itemsEntries, ...landedCostEntries];
|
||||
return [payableEntry, ...itemsEntries, ...landedCostEntries, ...taxEntries];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the given bill ledger.
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @param {IBill} bill
|
||||
* @param {number} payableAccountId
|
||||
* @returns {Ledger}
|
||||
*/
|
||||
private getBillLedger = (bill: IBill, payableAccountId: number) => {
|
||||
const entries = this.getBillGLEntries(bill, payableAccountId);
|
||||
private getBillLedger = (
|
||||
bill: IBill,
|
||||
payableAccountId: number,
|
||||
taxPayableAccountId: number
|
||||
) => {
|
||||
const entries = this.getBillGLEntries(
|
||||
bill,
|
||||
payableAccountId,
|
||||
taxPayableAccountId
|
||||
);
|
||||
|
||||
return new Ledger(entries);
|
||||
};
|
||||
|
||||
@@ -28,7 +28,8 @@ export class GetBill {
|
||||
.findById(billId)
|
||||
.withGraphFetched('vendor')
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('branch');
|
||||
.withGraphFetched('branch')
|
||||
.withGraphFetched('taxes.taxRate');
|
||||
|
||||
// Validates the bill existance.
|
||||
this.validators.validateBillExistance(bill);
|
||||
|
||||
@@ -1,27 +1,42 @@
|
||||
import { IBill } from '@/interfaces';
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { SaleInvoiceTaxEntryTransformer } from '@/services/Sales/Invoices/SaleInvoiceTaxEntryTransformer';
|
||||
import { formatNumber } from 'utils';
|
||||
|
||||
export class PurchaseInvoiceTransformer extends Transformer {
|
||||
/**
|
||||
* Include these attributes to sale invoice object.
|
||||
* Include these attributes to sale bill object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'formattedBillDate',
|
||||
'formattedDueDate',
|
||||
'formattedAmount',
|
||||
'formattedPaymentAmount',
|
||||
'formattedBalance',
|
||||
'formattedDueAmount',
|
||||
'formattedExchangeRate',
|
||||
'subtotalFormatted',
|
||||
'subtotalLocalFormatted',
|
||||
'subtotalExcludingTaxFormatted',
|
||||
'taxAmountWithheldLocalFormatted',
|
||||
'totalFormatted',
|
||||
'totalLocalFormatted',
|
||||
'taxes',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted invoice date.
|
||||
* @param {IBill} invoice
|
||||
* Excluded attributes.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['amount', 'amountLocal', 'localAmount'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill date.
|
||||
* @param {IBill} bill
|
||||
* @returns {String}
|
||||
*/
|
||||
protected formattedBillDate = (bill: IBill): string => {
|
||||
@@ -29,8 +44,8 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted invoice date.
|
||||
* @param {IBill} invoice
|
||||
* Retrieve formatted bill date.
|
||||
* @param {IBill} bill
|
||||
* @returns {String}
|
||||
*/
|
||||
protected formattedDueDate = (bill: IBill): string => {
|
||||
@@ -39,7 +54,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill amount.
|
||||
* @param {IBill} invoice
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedAmount = (bill): string => {
|
||||
@@ -48,7 +63,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill amount.
|
||||
* @param {IBill} invoice
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedPaymentAmount = (bill): string => {
|
||||
@@ -59,7 +74,7 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve formatted bill amount.
|
||||
* @param {IBill} invoice
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedDueAmount = (bill): string => {
|
||||
@@ -77,10 +92,90 @@ export class PurchaseInvoiceTransformer extends Transformer {
|
||||
|
||||
/**
|
||||
* Retrieve the formatted exchange rate.
|
||||
* @param {ISaleInvoice} invoice
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected formattedExchangeRate = (invoice): string => {
|
||||
return formatNumber(invoice.exchangeRate, { money: false });
|
||||
protected formattedExchangeRate = (bill): string => {
|
||||
return formatNumber(bill.exchangeRate, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted subtotal.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalFormatted = (bill): string => {
|
||||
return formatNumber(bill.subtotal, {
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the local subtotal formatted.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalLocalFormatted = (bill): string => {
|
||||
return formatNumber(bill.subtotalLocal, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the formatted subtotal tax excluded.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected subtotalExcludingTaxFormatted = (bill): string => {
|
||||
return formatNumber(bill.subtotalExludingTax, {
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the local formatted tax amount withheld
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected taxAmountWithheldLocalFormatted = (bill): string => {
|
||||
return formatNumber(bill.taxAmountWithheldLocal, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the total formatted.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected totalFormatted = (bill): string => {
|
||||
return formatNumber(bill.total, {
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the local total formatted.
|
||||
* @param {IBill} bill
|
||||
* @returns {string}
|
||||
*/
|
||||
protected totalLocalFormatted = (bill): string => {
|
||||
return formatNumber(bill.totalLocal, {
|
||||
currencyCode: this.context.organization.baseCurrency,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve the taxes lines of bill.
|
||||
* @param {Bill} bill
|
||||
*/
|
||||
protected taxes = (bill) => {
|
||||
return this.item(bill.taxes, new SaleInvoiceTaxEntryTransformer(), {
|
||||
subtotal: bill.subtotal,
|
||||
isInclusiveTax: bill.isInclusiveTax,
|
||||
currencyCode: bill.currencyCode,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -218,7 +218,8 @@ export class SaleInvoiceGLEntries {
|
||||
...commonEntry,
|
||||
credit: entry.taxAmount,
|
||||
accountId: taxPayableAccountId,
|
||||
index: index + 3,
|
||||
index: index + 1,
|
||||
indexGroup: 30,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
taxRateId: entry.taxRateId,
|
||||
taxRate: entry.taxRate,
|
||||
|
||||
@@ -62,8 +62,8 @@ export class SaleInvoiceTaxEntryTransformer extends Transformer {
|
||||
const taxRate = this.taxRate(taxEntry);
|
||||
|
||||
return this.options.isInclusiveTax
|
||||
? getInclusiveTaxAmount(this.options.amount, taxRate)
|
||||
: getExlusiveTaxAmount(this.options.amount, taxRate);
|
||||
? getInclusiveTaxAmount(this.options.subtotal, taxRate)
|
||||
: getExlusiveTaxAmount(this.options.subtotal, taxRate);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -171,7 +171,7 @@ export class SaleInvoiceTransformer extends Transformer {
|
||||
*/
|
||||
protected taxes = (invoice) => {
|
||||
return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer(), {
|
||||
amount: invoice.amount,
|
||||
subtotal: invoice.subtotal,
|
||||
isInclusiveTax: invoice.isInclusiveTax,
|
||||
currencyCode: invoice.currencyCode,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { IBillCreatingPayload, IBillEditingPayload } from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { CommandTaxRatesValidators } from '../CommandTaxRatesValidators';
|
||||
|
||||
@Service()
|
||||
export class BillTaxRateValidateSubscriber {
|
||||
@Inject()
|
||||
private taxRateDTOValidator: CommandTaxRatesValidators;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onCreating,
|
||||
this.validateBillEntriesTaxCodeExistanceOnCreating
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onCreating,
|
||||
this.validateBillEntriesTaxIdExistanceOnCreating
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEditing,
|
||||
this.validateBillEntriesTaxCodeExistanceOnEditing
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEditing,
|
||||
this.validateBillEntriesTaxIdExistanceOnEditing
|
||||
);
|
||||
return bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate bill entries tax rate code existance when creating.
|
||||
* @param {IBillCreatingPayload}
|
||||
*/
|
||||
private validateBillEntriesTaxCodeExistanceOnCreating = async ({
|
||||
billDTO,
|
||||
tenantId,
|
||||
}: IBillCreatingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the tax rate id existance when creating.
|
||||
* @param {IBillCreatingPayload}
|
||||
*/
|
||||
private validateBillEntriesTaxIdExistanceOnCreating = async ({
|
||||
billDTO,
|
||||
tenantId,
|
||||
}: IBillCreatingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate bill entries tax rate code existance when editing.
|
||||
* @param {IBillEditingPayload}
|
||||
*/
|
||||
private validateBillEntriesTaxCodeExistanceOnEditing = async ({
|
||||
tenantId,
|
||||
billDTO,
|
||||
}: IBillEditingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCode(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the bill entries tax rate id existance when editing.
|
||||
* @param {ISaleInvoiceEditingPayload} payload -
|
||||
*/
|
||||
private validateBillEntriesTaxIdExistanceOnEditing = async ({
|
||||
tenantId,
|
||||
billDTO,
|
||||
}: IBillEditingPayload) => {
|
||||
await this.taxRateDTOValidator.validateItemEntriesTaxCodeId(
|
||||
tenantId,
|
||||
billDTO.entries
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import {
|
||||
IBIllEventDeletedPayload,
|
||||
IBillCreatedPayload,
|
||||
IBillEditedPayload,
|
||||
ISaleInvoiceCreatedPayload,
|
||||
ISaleInvoiceDeletedPayload,
|
||||
ISaleInvoiceEditedPayload,
|
||||
} from '@/interfaces';
|
||||
import events from '@/subscribers/events';
|
||||
import { WriteTaxTransactionsItemEntries } from '../WriteTaxTransactionsItemEntries';
|
||||
|
||||
@Service()
|
||||
export class WriteBillTaxTransactionsSubscriber {
|
||||
@Inject()
|
||||
private writeTaxTransactions: WriteTaxTransactionsItemEntries;
|
||||
|
||||
/**
|
||||
* Attaches events with handlers.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onCreated,
|
||||
this.writeInvoiceTaxTransactionsOnCreated
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEdited,
|
||||
this.rewriteInvoiceTaxTransactionsOnEdited
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onDeleted,
|
||||
this.removeInvoiceTaxTransactionsOnDeleted
|
||||
);
|
||||
return bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the bill tax transactions on invoice created.
|
||||
* @param {ISaleInvoiceCreatingPaylaod}
|
||||
*/
|
||||
private writeInvoiceTaxTransactionsOnCreated = async ({
|
||||
tenantId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillCreatedPayload) => {
|
||||
await this.writeTaxTransactions.writeTaxTransactionsFromItemEntries(
|
||||
tenantId,
|
||||
bill.entries,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrites the bill tax transactions on invoice edited.
|
||||
* @param {IBillEditedPayload} payload -
|
||||
*/
|
||||
private rewriteInvoiceTaxTransactionsOnEdited = async ({
|
||||
tenantId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillEditedPayload) => {
|
||||
await this.writeTaxTransactions.rewriteTaxRateTransactionsFromItemEntries(
|
||||
tenantId,
|
||||
bill.entries,
|
||||
'Bill',
|
||||
bill.id,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the invoice tax transactions on invoice deleted.
|
||||
* @param {IBIllEventDeletedPayload}
|
||||
*/
|
||||
private removeInvoiceTaxTransactionsOnDeleted = async ({
|
||||
tenantId,
|
||||
oldBill,
|
||||
trx,
|
||||
}: IBIllEventDeletedPayload) => {
|
||||
await this.writeTaxTransactions.removeTaxTransactionsFromItemEntries(
|
||||
tenantId,
|
||||
oldBill.id,
|
||||
'Bill',
|
||||
trx
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import events from '@/subscribers/events';
|
||||
import TenancyService from '@/services/Tenancy/TenancyService';
|
||||
import BillsService from '@/services/Purchases/Bills';
|
||||
import {
|
||||
IBillCreatedPayload,
|
||||
IBillEditedPayload,
|
||||
IBIllEventDeletedPayload,
|
||||
} from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export default class BillWriteGLEntriesSubscriber {
|
||||
@Inject()
|
||||
tenancy: TenancyService;
|
||||
|
||||
@Inject()
|
||||
billsService: BillsService;
|
||||
|
||||
/**
|
||||
* Attaches events with handles.
|
||||
*/
|
||||
public attach(bus) {
|
||||
bus.subscribe(
|
||||
events.bill.onCreated,
|
||||
this.handlerWriteJournalEntriesOnCreate
|
||||
);
|
||||
bus.subscribe(
|
||||
events.bill.onEdited,
|
||||
this.handleOverwriteJournalEntriesOnEdit
|
||||
);
|
||||
bus.subscribe(events.bill.onDeleted, this.handlerDeleteJournalEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing journal entries once bill created.
|
||||
* @param {IBillCreatedPayload} payload -
|
||||
*/
|
||||
private handlerWriteJournalEntriesOnCreate = async ({
|
||||
tenantId,
|
||||
billId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillCreatedPayload) => {
|
||||
// Can't continue if the bill is not opened yet.
|
||||
if (!bill.openedAt) return null;
|
||||
|
||||
await this.billsService.recordJournalTransactions(
|
||||
tenantId,
|
||||
billId,
|
||||
false,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the overwriting journal entries once bill edited.
|
||||
* @param {IBillEditedPayload} payload -
|
||||
*/
|
||||
private handleOverwriteJournalEntriesOnEdit = async ({
|
||||
tenantId,
|
||||
billId,
|
||||
bill,
|
||||
trx,
|
||||
}: IBillEditedPayload) => {
|
||||
// Can't continue if the bill is not opened yet.
|
||||
if (!bill.openedAt) return null;
|
||||
|
||||
await this.billsService.recordJournalTransactions(
|
||||
tenantId,
|
||||
billId,
|
||||
true,
|
||||
trx
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles revert journal entries on bill deleted.
|
||||
* @param {IBIllEventDeletedPayload} payload -
|
||||
*/
|
||||
private handlerDeleteJournalEntries = async ({
|
||||
tenantId,
|
||||
billId,
|
||||
trx,
|
||||
}: IBIllEventDeletedPayload) => {
|
||||
await this.billsService.revertJournalEntries(tenantId, billId, trx);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user