From 983ceb5cc64476fccc5262e53e4983a59ae1ec29 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 6 Sep 2023 14:01:40 +0200 Subject: [PATCH] feat: sale invoice model tax attributes --- .../api/controllers/Sales/SalesInvoices.ts | 2 +- .../20230810191606_create_tax_rates.js | 3 + packages/server/src/interfaces/ItemEntry.ts | 5 + packages/server/src/interfaces/SaleInvoice.ts | 21 ++- .../server/src/lib/Transformer/Transformer.ts | 4 +- packages/server/src/models/ItemEntry.ts | 76 +++++++-- packages/server/src/models/SaleInvoice.ts | 158 +++++++----------- .../CommandSaleInvoiceDTOTransformer.ts | 39 ++++- .../Sales/Invoices/InvoiceGLEntries.ts | 4 +- .../SaleInvoiceTaxEntryTransformer.ts | 36 +++- .../Sales/Invoices/SaleInvoiceTransformer.ts | 128 +++++++++++--- .../TaxRates/ItemEntriesTaxTransactions.ts | 1 - packages/server/src/utils/index.ts | 11 +- packages/server/src/utils/taxRate.ts | 19 +++ 14 files changed, 346 insertions(+), 161 deletions(-) create mode 100644 packages/server/src/utils/taxRate.ts diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts index e55aeeb7d..4a449f4ca 100644 --- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts @@ -169,7 +169,7 @@ export default class SaleInvoicesController extends BaseController { check('branch_id').optional({ nullable: true }).isNumeric().toInt(), check('project_id').optional({ nullable: true }).isNumeric().toInt(), - check('is_tax_exclusive').optional().isBoolean().toBoolean(), + check('is_inclusive_tax').optional().isBoolean().toBoolean(), check('entries').exists().isArray({ min: 1 }), check('entries.*.index').exists().isNumeric().toInt(), diff --git a/packages/server/src/database/migrations/20230810191606_create_tax_rates.js b/packages/server/src/database/migrations/20230810191606_create_tax_rates.js index 717567d16..7baf6be4e 100644 --- a/packages/server/src/database/migrations/20230810191606_create_tax_rates.js +++ b/packages/server/src/database/migrations/20230810191606_create_tax_rates.js @@ -43,6 +43,9 @@ exports.up = (knex) => { .references('id') .inTable('tax_rates'); table.decimal('tax_rate').unsigned(); + }) + .table('sales_invoices', (table) => { + table.rename('balance', 'amount'); }); }; diff --git a/packages/server/src/interfaces/ItemEntry.ts b/packages/server/src/interfaces/ItemEntry.ts index bb67df9e8..aab0b154d 100644 --- a/packages/server/src/interfaces/ItemEntry.ts +++ b/packages/server/src/interfaces/ItemEntry.ts @@ -18,6 +18,11 @@ export interface IItemEntry { rate: number; amount: number; + total: number; + amountInclusingTax: number; + amountExludingTax: number; + discountAmount: number; + landedCost: number; allocatedCostAmount: number; unallocatedCostAmount: number; diff --git a/packages/server/src/interfaces/SaleInvoice.ts b/packages/server/src/interfaces/SaleInvoice.ts index 4aa072655..7ef8fdea2 100644 --- a/packages/server/src/interfaces/SaleInvoice.ts +++ b/packages/server/src/interfaces/SaleInvoice.ts @@ -5,7 +5,8 @@ import { IItemEntry, IItemEntryDTO } from './ItemEntry'; export interface ISaleInvoice { id: number; - balance: number; + amount: number; + amountLocal?: number; paymentAmount: number; currencyCode: string; exchangeRate?: number; @@ -27,15 +28,21 @@ export interface ISaleInvoice { branchId?: number; projectId?: number; - localAmount?: number; - - localWrittenoffAmount?: number; + writtenoffAmount?: number; + writtenoffAmountLocal?: number; writtenoffExpenseAccountId?: number; - writtenoffExpenseAccount?: IAccount; taxAmountWithheld: number; - taxes: ITaxTransaction[] + taxAmountWithheldLocal: number; + taxes: ITaxTransaction[]; + + total: number; + totalLocal: number; + + subtotal: number; + subtotalLocal: number; + subtotalExludingTax: number; } export interface ISaleInvoiceDTO { @@ -54,6 +61,8 @@ export interface ISaleInvoiceDTO { warehouseId?: number | null; projectId?: number; branchId?: number | null; + + isInclusiveTax?: boolean; } export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO { diff --git a/packages/server/src/lib/Transformer/Transformer.ts b/packages/server/src/lib/Transformer/Transformer.ts index cb9538a64..dfec3391f 100644 --- a/packages/server/src/lib/Transformer/Transformer.ts +++ b/packages/server/src/lib/Transformer/Transformer.ts @@ -1,8 +1,7 @@ import moment from 'moment'; import * as R from 'ramda'; import { includes, isFunction, isObject, isUndefined, omit } from 'lodash'; -import { formatNumber } from 'utils'; -import { isArrayLikeObject } from 'lodash/fp'; +import { formatNumber, sortObjectKeysAlphabetically } from 'utils'; export class Transformer { public context: any; @@ -82,6 +81,7 @@ export class Transformer { const normlizedItem = this.normalizeModelItem(item); return R.compose( + sortObjectKeysAlphabetically, this.transform, R.when(this.hasExcludeAttributes, this.excludeAttributesTransformed), this.includeAttributesTransformed diff --git a/packages/server/src/models/ItemEntry.ts b/packages/server/src/models/ItemEntry.ts index 2d7e5de95..7ac7910e0 100644 --- a/packages/server/src/models/ItemEntry.ts +++ b/packages/server/src/models/ItemEntry.ts @@ -1,11 +1,17 @@ import { Model } from 'objection'; import TenantModel from 'models/TenantModel'; +import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate'; export default class ItemEntry extends TenantModel { public taxRate: number; + public discount: number; + public quantity: number; + public rate: number; + public isInclusiveTax: number; /** * Table name. + * @returns {string} */ static get tableName() { return 'items_entries'; @@ -13,24 +19,66 @@ export default class ItemEntry extends TenantModel { /** * Timestamps columns. + * @returns {string[]} */ get timestamps() { return ['created_at', 'updated_at']; } + /** + * Virtual attributes. + * @returns {string[]} + */ static get virtualAttributes() { - return ['amount', 'taxAmount']; + return [ + 'amount', + 'taxAmount', + 'amountExludingTax', + 'amountInclusingTax', + 'total', + ]; } + /** + * Item entry total. + * Amount of item entry includes tax and subtracted discount amount. + * @returns {number} + */ + get total() { + return this.amountInclusingTax; + } + + /** + * Item entry amount. + * Amount of item entry that may include or exclude tax. + * @returns {number} + */ get amount() { - return ItemEntry.calcAmount(this); + return this.quantity * this.rate; } - static calcAmount(itemEntry) { - const { discount, quantity, rate } = itemEntry; - const total = quantity * rate; + /** + * Item entry amount including tax. + * @returns {number} + */ + get amountInclusingTax() { + return this.isInclusiveTax ? this.amount : this.amount + this.taxAmount; + } - return discount ? total - total * discount * 0.01 : total; + /** + * Item entry amount excluding tax. + * @returns {number} + */ + get amountExludingTax() { + return this.isInclusiveTax ? this.amount - this.taxAmount : this.amount; + } + + /** + * Discount amount. + * @returns {number} + */ + get discountAmount() { + return this.amount * (this.discount / 100); } /** @@ -46,9 +94,14 @@ export default class ItemEntry extends TenantModel { * @returns {number} */ get taxAmount() { - return this.amount * this.tagRateFraction; + return this.isInclusiveTax + ? getInclusiveTaxAmount(this.amount, this.taxRate) + : getExlusiveTaxAmount(this.amount, this.taxRate); } + /** + * Item entry relations. + */ static get relationMappings() { const Item = require('models/Item'); const BillLandedCostEntry = require('models/BillLandedCostEntry'); @@ -104,6 +157,9 @@ export default class ItemEntry extends TenantModel { }, }, + /** + * Sale receipt reference. + */ receipt: { relation: Model.BelongsToOneRelation, modelClass: SaleReceipt.default, @@ -114,7 +170,7 @@ export default class ItemEntry extends TenantModel { }, /** - * + * Project task reference. */ projectTaskRef: { relation: Model.HasManyRelation, @@ -126,7 +182,7 @@ export default class ItemEntry extends TenantModel { }, /** - * + * Project expense reference. */ projectExpenseRef: { relation: Model.HasManyRelation, @@ -138,7 +194,7 @@ export default class ItemEntry extends TenantModel { }, /** - * + * Project bill reference. */ projectBillRef: { relation: Model.HasManyRelation, diff --git a/packages/server/src/models/SaleInvoice.ts b/packages/server/src/models/SaleInvoice.ts index 5bbeb51fc..828fb08c7 100644 --- a/packages/server/src/models/SaleInvoice.ts +++ b/packages/server/src/models/SaleInvoice.ts @@ -1,5 +1,5 @@ import { mixin, Model, raw } from 'objection'; -import { castArray } from 'lodash'; +import { castArray, takeWhile } from 'lodash'; import moment from 'moment'; import TenantModel from 'models/TenantModel'; import ModelSetting from './ModelSetting'; @@ -13,10 +13,16 @@ export default class SaleInvoice extends mixin(TenantModel, [ CustomViewBaseModel, ModelSearchable, ]) { - taxAmountWithheld: number; - balance: number; - paymentAmount: number; - exchangeRate: number; + public taxAmountWithheld: number; + public amount: number; + public paymentAmount: number; + public exchangeRate: number; + public writtenoffAmount: number; + public creditedAmount: number; + public isInclusiveTax: boolean; + public writtenoffAt: Date; + public dueDate: Date; + public deliveredAt: Date; /** * Table name @@ -32,6 +38,9 @@ export default class SaleInvoice extends mixin(TenantModel, [ return ['created_at', 'updated_at']; } + /** + * + */ get pluralName() { return 'asdfsdf'; } @@ -41,140 +50,82 @@ export default class SaleInvoice extends mixin(TenantModel, [ */ static get virtualAttributes() { return [ - 'localAmount', - 'dueAmount', - 'balanceAmount', 'isDelivered', 'isOverdue', 'isPartiallyPaid', 'isFullyPaid', - 'isPaid', 'isWrittenoff', + 'isPaid', + + 'dueAmount', + 'balanceAmount', 'remainingDays', 'overdueDays', - 'filterByBranches', + + 'subtotal', + 'subtotalLocal', + 'subtotalExludingTax', + + 'taxAmountWithheldLocal', + 'total', + 'totalLocal', + + 'writtenoffAmountLocal', ]; } /** - * Invoice total FCY. + * Subtotal. (Tax inclusive) if the tax inclusive is enabled. * @returns {number} */ - get totalFcy() { - return this.amountFcy + this.taxAmountWithheldFcy; + get subtotal() { + return this.amount; } /** - * Invoice total BCY. + * Subtotal in base currency. (Tax inclusive) if the tax inclusive is enabled. * @returns {number} */ - get totalBcy() { - return this.amountBcy + this.taxAmountWithheldBcy; + get subtotalLocal() { + return this.amount * this.exchangeRate; } /** - * Tax amount withheld FCY. + * Sale invoice amount excluding tax. * @returns {number} */ - get taxAmountWithheldFcy() { - return this.taxAmountWithheld; + get subtotalExludingTax() { + return this.isInclusiveTax + ? this.subtotal - this.taxAmountWithheld + : this.subtotal; } /** - * Tax amount withheld BCY. + * Tax amount withheld in base currency. * @returns {number} */ - get taxAmountWithheldBcy() { - return this.taxAmountWithheld; + get taxAmountWithheldLocal() { + return this.taxAmountWithheld * this.exchangeRate; } /** - * Subtotal FCY. + * Invoice total. (Tax included) * @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; + return this.isInclusiveTax + ? this.subtotal + : this.subtotal + this.taxAmountWithheld; } /** - * Invoice amount in local currency. + * Invoice total in local currency. (Tax included) * @returns {number} */ - get localAmount() { + get totalLocal() { return this.total * this.exchangeRate; } - /** - * Invoice local written-off amount. - * @returns {number} - */ - get localWrittenoffAmount() { - return this.writtenoffAmount * this.exchangeRate; - } - /** * Detarmines whether the invoice is delivered. * @return {boolean} @@ -205,7 +156,7 @@ export default class SaleInvoice extends mixin(TenantModel, [ * @return {boolean} */ get dueAmount() { - return Math.max(this.balance - this.balanceAmount, 0); + return Math.max(this.total - this.balanceAmount, 0); } /** @@ -213,7 +164,7 @@ export default class SaleInvoice extends mixin(TenantModel, [ * @return {boolean} */ get isPartiallyPaid() { - return this.dueAmount !== this.balance && this.dueAmount > 0; + return this.dueAmount !== this.total && this.dueAmount > 0; } /** @@ -491,7 +442,7 @@ export default class SaleInvoice extends mixin(TenantModel, [ }, /** - * + * Invoice may has associated cost transactions. */ costTransactions: { relation: Model.HasManyRelation, @@ -506,7 +457,7 @@ export default class SaleInvoice extends mixin(TenantModel, [ }, /** - * + * Invoice may has associated payment entries. */ paymentEntries: { relation: Model.HasManyRelation, @@ -529,6 +480,9 @@ export default class SaleInvoice extends mixin(TenantModel, [ }, }, + /** + * Invoice may has associated written-off expense account. + */ writtenoffExpenseAccount: { relation: Model.BelongsToOneRelation, modelClass: Account.default, @@ -539,7 +493,7 @@ export default class SaleInvoice extends mixin(TenantModel, [ }, /** - * + * Invoice may has associated tax rate transactions. */ taxes: { relation: Model.HasManyRelation, diff --git a/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts b/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts index 867e7c660..ddb942553 100644 --- a/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/CommandSaleInvoiceDTOTransformer.ts @@ -13,17 +13,14 @@ import { import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform'; import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform'; import ItemsEntriesService from '@/services/Items/ItemsEntriesService'; -import HasTenancyService from '@/services/Tenancy/TenancyService'; import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators'; import { SaleInvoiceIncrement } from './SaleInvoiceIncrement'; import { formatDateFields } from 'utils'; import { ItemEntriesTaxTransactions } from '@/services/TaxRates/ItemEntriesTaxTransactions'; +import { ItemEntry } from '@/models'; @Service() export class CommandSaleInvoiceDTOTransformer { - @Inject() - private tenancy: HasTenancyService; - @Inject() private branchDTOTransform: BranchTransactionDTOTransform; @@ -55,11 +52,9 @@ export class CommandSaleInvoiceDTOTransformer { authorizedUser: ITenantUser, oldSaleInvoice?: ISaleInvoice ): Promise { - const { ItemEntry } = this.tenancy.models(tenantId); + const entriesModels = this.transformDTOEntriesToModels(saleInvoiceDTO); + const amount = this.getDueBalanceItemEntries(entriesModels); - const balance = sumBy(saleInvoiceDTO.entries, (e) => - ItemEntry.calcAmount(e) - ); // Retreive the next invoice number. const autoNextNumber = this.invoiceIncrement.getNextInvoiceNumber(tenantId); @@ -72,6 +67,7 @@ export class CommandSaleInvoiceDTOTransformer { const initialEntries = saleInvoiceDTO.entries.map((entry) => ({ referenceType: 'SaleInvoice', + isInclusiveTax: saleInvoiceDTO.isInclusiveTax, ...entry, })); const entries = await composeAsync( @@ -87,7 +83,7 @@ export class CommandSaleInvoiceDTOTransformer { ['invoiceDate', 'dueDate'] ), // Avoid rewrite the deliver date in edit mode when already published. - balance, + amount, currencyCode: customer.currencyCode, exchangeRate: saleInvoiceDTO.exchangeRate || 1, ...(saleInvoiceDTO.delivered && @@ -107,4 +103,29 @@ export class CommandSaleInvoiceDTOTransformer { this.warehouseDTOTransform.transformDTO(tenantId) )(initialDTO); } + + /** + * Transforms the DTO entries to invoice entries models. + * @param {ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO} entries + * @returns {IItemEntry[]} + */ + private transformDTOEntriesToModels = ( + saleInvoiceDTO: ISaleInvoiceCreateDTO | ISaleInvoiceEditDTO + ): ItemEntry[] => { + return saleInvoiceDTO.entries.map((entry) => { + return ItemEntry.fromJson({ + ...entry, + isInclusiveTax: saleInvoiceDTO.isInclusiveTax, + }); + }); + }; + + /** + * Gets the due balance from the invoice entries. + * @param {IItemEntry[]} entries + * @returns {number} + */ + private getDueBalanceItemEntries = (entries: ItemEntry[]) => { + return sumBy(entries, (e) => e.amount); + }; } diff --git a/packages/server/src/services/Sales/Invoices/InvoiceGLEntries.ts b/packages/server/src/services/Sales/Invoices/InvoiceGLEntries.ts index 6017e643e..6f38ad454 100644 --- a/packages/server/src/services/Sales/Invoices/InvoiceGLEntries.ts +++ b/packages/server/src/services/Sales/Invoices/InvoiceGLEntries.ts @@ -93,7 +93,7 @@ export class SaleInvoiceGLEntries { 'SaleInvoice', trx ); - }; +}; /** * Retrieves the given invoice ledger. @@ -156,7 +156,7 @@ export class SaleInvoiceGLEntries { return { ...commonEntry, - debit: saleInvoice.totalBcy, + debit: saleInvoice.totalLocal, accountId: ARAccountId, contactId: saleInvoice.customerId, accountNormal: AccountNormal.DEBIT, diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoiceTaxEntryTransformer.ts b/packages/server/src/services/Sales/Invoices/SaleInvoiceTaxEntryTransformer.ts index b61242ca5..6f028423d 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoiceTaxEntryTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoiceTaxEntryTransformer.ts @@ -1,4 +1,7 @@ import { Transformer } from '@/lib/Transformer/Transformer'; +import { formatNumber } from '@/utils'; +import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate'; +import { format } from 'mathjs'; export class SaleInvoiceTaxEntryTransformer extends Transformer { /** @@ -6,7 +9,14 @@ export class SaleInvoiceTaxEntryTransformer extends Transformer { * @returns {Array} */ public includeAttributes = (): string[] => { - return ['name', 'taxRateCode', 'raxRate', 'taxRateId']; + return [ + 'name', + 'taxRateCode', + 'taxRate', + 'taxRateId', + 'taxRateAmount', + 'taxRateAmountFormatted', + ]; }; /** @@ -31,7 +41,7 @@ export class SaleInvoiceTaxEntryTransformer extends Transformer { * @param taxEntry * @returns {number} */ - protected raxRate = (taxEntry) => { + protected taxRate = (taxEntry) => { return taxEntry.taxAmount || taxEntry.taxRate.rate; }; @@ -43,4 +53,26 @@ export class SaleInvoiceTaxEntryTransformer extends Transformer { protected name = (taxEntry) => { return taxEntry.taxRate.name; }; + + /** + * Retrieve tax rate amount. + * @param taxEntry + */ + protected taxRateAmount = (taxEntry) => { + const taxRate = this.taxRate(taxEntry); + + return this.options.isInclusiveTax + ? getInclusiveTaxAmount(this.options.amount, taxRate) + : getExlusiveTaxAmount(this.options.amount, taxRate); + }; + + /** + * Retrieve formatted tax rate amount. + * @returns {string} + */ + protected taxRateAmountFormatted = (taxEntry) => { + return formatNumber(this.taxRateAmount(taxEntry), { + currencyCode: this.options.currencyCode, + }); + }; } diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts b/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts index c40320c33..ffb2d8391 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoiceTransformer.ts @@ -9,13 +9,19 @@ export class SaleInvoiceTransformer extends Transformer { */ public includeAttributes = (): string[] => { return [ - 'formattedInvoiceDate', - 'formattedDueDate', - 'formattedAmount', - 'formattedDueAmount', - 'formattedPaymentAmount', - 'formattedBalanceAmount', - 'formattedExchangeRate', + 'invoiceDateFormatted', + 'dueDateFormatted', + 'dueAmountFormatted', + 'paymentAmountFormatted', + 'balanceAmountFormatted', + 'exchangeRateFormatted', + 'subtotalFormatted', + 'subtotalLocalFormatted', + 'subtotalExludingTaxFormatted', + 'taxAmountWithheldFormatted', + 'taxAmountWithheldLocalFormatted', + 'totalFormatted', + 'totalLocalFormatted', 'taxes', ]; }; @@ -25,7 +31,7 @@ export class SaleInvoiceTransformer extends Transformer { * @param {ISaleInvoice} invoice * @returns {String} */ - protected formattedInvoiceDate = (invoice): string => { + protected invoiceDateFormatted = (invoice): string => { return this.formatDate(invoice.invoiceDate); }; @@ -34,27 +40,16 @@ export class SaleInvoiceTransformer extends Transformer { * @param {ISaleInvoice} invoice * @returns {string} */ - protected formattedDueDate = (invoice): string => { + protected dueDateFormatted = (invoice): string => { return this.formatDate(invoice.dueDate); }; - /** - * Retrieve formatted invoice amount. - * @param {ISaleInvoice} invoice - * @returns {string} - */ - protected formattedAmount = (invoice): string => { - return formatNumber(invoice.balance, { - currencyCode: invoice.currencyCode, - }); - }; - /** * Retrieve formatted invoice due amount. * @param {ISaleInvoice} invoice * @returns {string} */ - protected formattedDueAmount = (invoice): string => { + protected dueAmountFormatted = (invoice): string => { return formatNumber(invoice.dueAmount, { currencyCode: invoice.currencyCode, }); @@ -65,7 +60,7 @@ export class SaleInvoiceTransformer extends Transformer { * @param {ISaleInvoice} invoice * @returns {string} */ - protected formattedPaymentAmount = (invoice): string => { + protected paymentAmountFormatted = (invoice): string => { return formatNumber(invoice.paymentAmount, { currencyCode: invoice.currencyCode, }); @@ -76,7 +71,7 @@ export class SaleInvoiceTransformer extends Transformer { * @param {ISaleInvoice} invoice * @returns {string} */ - protected formattedBalanceAmount = (invoice): string => { + protected balanceAmountFormatted = (invoice): string => { return formatNumber(invoice.balanceAmount, { currencyCode: invoice.currencyCode, }); @@ -87,15 +82,98 @@ export class SaleInvoiceTransformer extends Transformer { * @param {ISaleInvoice} invoice * @returns {string} */ - protected formattedExchangeRate = (invoice): string => { + protected exchangeRateFormatted = (invoice): string => { return formatNumber(invoice.exchangeRate, { money: false }); }; + /** + * Retrieves formatted subtotal in base currency. + * (Tax inclusive if the tax inclusive is enabled) + * @param invoice + * @returns {string} + */ + protected subtotalFormatted = (invoice): string => { + return formatNumber(invoice.subtotal, { + currencyCode: this.context.organization.baseCurrency, + }); + }; + + /** + * Retrieves formatted subtotal in foreign currency. + * (Tax inclusive if the tax inclusive is enabled) + * @param invoice + * @returns {string} + */ + protected subtotalLocalFormatted = (invoice): string => { + return formatNumber(invoice.subtotalLocal, { + currencyCode: invoice.currencyCode, + }); + }; + + /** + * Retrieves formatted subtotal excluding tax in foreign currency. + * @param invoice + * @returns {string} + */ + protected subtotalExludingTaxFormatted = (invoice): string => { + return formatNumber(invoice.subtotalExludingTax, { + currencyCode: invoice.currencyCode, + }); + }; + + /** + * Retrieves formatted tax amount withheld in foreign currency. + * @param invoice + * @returns {string} + */ + protected taxAmountWithheldFormatted = (invoice): string => { + return formatNumber(invoice.taxAmountWithheld, { + currencyCode: invoice.currencyCode, + }); + }; + + /** + * Retrieves formatted tax amount withheld in base currency. + * @param invoice + * @returns {string} + */ + protected taxAmountWithheldLocalFormatted = (invoice): string => { + return formatNumber(invoice.taxAmountWithheldLocal, { + currencyCode: this.context.organization.baseCurrency, + }); + }; + + /** + * Retrieves formatted total in foreign currency. + * @param invoice + * @returns {string} + */ + protected totalFormatted = (invoice): string => { + return formatNumber(invoice.total, { + currencyCode: invoice.currencyCode, + }); + }; + + /** + * Retrieves formatted total in base currency. + * @param invoice + * @returns {string} + */ + protected totalLocalFormatted = (invoice): string => { + return formatNumber(invoice.totalLocal, { + currencyCode: this.context.organization.baseCurrency, + }); + }; + /** * Retrieve the taxes lines of sale invoice. * @param {ISaleInvoice} invoice */ protected taxes = (invoice) => { - return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer()); + return this.item(invoice.taxes, new SaleInvoiceTaxEntryTransformer(), { + amount: invoice.amount, + isInclusiveTax: invoice.isInclusiveTax, + currencyCode: invoice.currencyCode, + }); }; } diff --git a/packages/server/src/services/TaxRates/ItemEntriesTaxTransactions.ts b/packages/server/src/services/TaxRates/ItemEntriesTaxTransactions.ts index a85d1c998..a9bd5c683 100644 --- a/packages/server/src/services/TaxRates/ItemEntriesTaxTransactions.ts +++ b/packages/server/src/services/TaxRates/ItemEntriesTaxTransactions.ts @@ -1,6 +1,5 @@ import { Inject, Service } from 'typedi'; import { keyBy, sumBy } from 'lodash'; -import * as R from 'ramda'; import { ItemEntry } from '@/models'; import HasTenancyService from '../Tenancy/TenancyService'; diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index fa2bc6772..2b09381d1 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -471,6 +471,15 @@ const castCommaListEnvVarToArray = (envVar: string): Array => { return envVar ? envVar?.split(',')?.map(_.trim) : []; }; +export const sortObjectKeysAlphabetically = (object) => { + return Object.keys(object) + .sort() + .reduce((objEntries, key) => { + objEntries[key] = object[key]; + return objEntries; + }, {}); +}; + export { templateRender, accumSum, @@ -503,5 +512,5 @@ export { mergeObjectsBykey, nestedArrayToFlatten, assocDepthLevelToObjectTree, - castCommaListEnvVarToArray + castCommaListEnvVarToArray, }; diff --git a/packages/server/src/utils/taxRate.ts b/packages/server/src/utils/taxRate.ts new file mode 100644 index 000000000..15d9e9d36 --- /dev/null +++ b/packages/server/src/utils/taxRate.ts @@ -0,0 +1,19 @@ +/** + * Get inclusive tax amount. + * @param {number} amount + * @param {number} taxRate + * @returns {number} + */ +export const getInclusiveTaxAmount = (amount: number, taxRate: number) => { + return (amount * taxRate) / (100 + taxRate); +}; + +/** + * Get exclusive tax amount. + * @param {number} amount + * @param {number} taxRate + * @returns {number} + */ +export const getExlusiveTaxAmount = (amount: number, taxRate: number) => { + return (amount * taxRate) / 100; +};