mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat: sale invoice model tax attributes
This commit is contained in:
@@ -169,7 +169,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
check('branch_id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
check('project_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').exists().isArray({ min: 1 }),
|
||||||
check('entries.*.index').exists().isNumeric().toInt(),
|
check('entries.*.index').exists().isNumeric().toInt(),
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ exports.up = (knex) => {
|
|||||||
.references('id')
|
.references('id')
|
||||||
.inTable('tax_rates');
|
.inTable('tax_rates');
|
||||||
table.decimal('tax_rate').unsigned();
|
table.decimal('tax_rate').unsigned();
|
||||||
|
})
|
||||||
|
.table('sales_invoices', (table) => {
|
||||||
|
table.rename('balance', 'amount');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ export interface IItemEntry {
|
|||||||
rate: number;
|
rate: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
|
||||||
|
total: number;
|
||||||
|
amountInclusingTax: number;
|
||||||
|
amountExludingTax: number;
|
||||||
|
discountAmount: number;
|
||||||
|
|
||||||
landedCost: number;
|
landedCost: number;
|
||||||
allocatedCostAmount: number;
|
allocatedCostAmount: number;
|
||||||
unallocatedCostAmount: number;
|
unallocatedCostAmount: number;
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
|||||||
|
|
||||||
export interface ISaleInvoice {
|
export interface ISaleInvoice {
|
||||||
id: number;
|
id: number;
|
||||||
balance: number;
|
amount: number;
|
||||||
|
amountLocal?: number;
|
||||||
paymentAmount: number;
|
paymentAmount: number;
|
||||||
currencyCode: string;
|
currencyCode: string;
|
||||||
exchangeRate?: number;
|
exchangeRate?: number;
|
||||||
@@ -27,15 +28,21 @@ export interface ISaleInvoice {
|
|||||||
branchId?: number;
|
branchId?: number;
|
||||||
projectId?: number;
|
projectId?: number;
|
||||||
|
|
||||||
localAmount?: number;
|
writtenoffAmount?: number;
|
||||||
|
writtenoffAmountLocal?: number;
|
||||||
localWrittenoffAmount?: number;
|
|
||||||
writtenoffExpenseAccountId?: number;
|
writtenoffExpenseAccountId?: number;
|
||||||
|
|
||||||
writtenoffExpenseAccount?: IAccount;
|
writtenoffExpenseAccount?: IAccount;
|
||||||
|
|
||||||
taxAmountWithheld: number;
|
taxAmountWithheld: number;
|
||||||
taxes: ITaxTransaction[]
|
taxAmountWithheldLocal: number;
|
||||||
|
taxes: ITaxTransaction[];
|
||||||
|
|
||||||
|
total: number;
|
||||||
|
totalLocal: number;
|
||||||
|
|
||||||
|
subtotal: number;
|
||||||
|
subtotalLocal: number;
|
||||||
|
subtotalExludingTax: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoiceDTO {
|
export interface ISaleInvoiceDTO {
|
||||||
@@ -54,6 +61,8 @@ export interface ISaleInvoiceDTO {
|
|||||||
warehouseId?: number | null;
|
warehouseId?: number | null;
|
||||||
projectId?: number;
|
projectId?: number;
|
||||||
branchId?: number | null;
|
branchId?: number | null;
|
||||||
|
|
||||||
|
isInclusiveTax?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO {
|
export interface ISaleInvoiceCreateDTO extends ISaleInvoiceDTO {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as R from 'ramda';
|
import * as R from 'ramda';
|
||||||
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
|
||||||
import { formatNumber } from 'utils';
|
import { formatNumber, sortObjectKeysAlphabetically } from 'utils';
|
||||||
import { isArrayLikeObject } from 'lodash/fp';
|
|
||||||
|
|
||||||
export class Transformer {
|
export class Transformer {
|
||||||
public context: any;
|
public context: any;
|
||||||
@@ -82,6 +81,7 @@ export class Transformer {
|
|||||||
const normlizedItem = this.normalizeModelItem(item);
|
const normlizedItem = this.normalizeModelItem(item);
|
||||||
|
|
||||||
return R.compose(
|
return R.compose(
|
||||||
|
sortObjectKeysAlphabetically,
|
||||||
this.transform,
|
this.transform,
|
||||||
R.when(this.hasExcludeAttributes, this.excludeAttributesTransformed),
|
R.when(this.hasExcludeAttributes, this.excludeAttributesTransformed),
|
||||||
this.includeAttributesTransformed
|
this.includeAttributesTransformed
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
|
import { getExlusiveTaxAmount, getInclusiveTaxAmount } from '@/utils/taxRate';
|
||||||
|
|
||||||
export default class ItemEntry extends TenantModel {
|
export default class ItemEntry extends TenantModel {
|
||||||
public taxRate: number;
|
public taxRate: number;
|
||||||
|
public discount: number;
|
||||||
|
public quantity: number;
|
||||||
|
public rate: number;
|
||||||
|
public isInclusiveTax: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name.
|
* Table name.
|
||||||
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
static get tableName() {
|
static get tableName() {
|
||||||
return 'items_entries';
|
return 'items_entries';
|
||||||
@@ -13,24 +19,66 @@ export default class ItemEntry extends TenantModel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamps columns.
|
* Timestamps columns.
|
||||||
|
* @returns {string[]}
|
||||||
*/
|
*/
|
||||||
get timestamps() {
|
get timestamps() {
|
||||||
return ['created_at', 'updated_at'];
|
return ['created_at', 'updated_at'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual attributes.
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
static get virtualAttributes() {
|
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() {
|
get amount() {
|
||||||
return ItemEntry.calcAmount(this);
|
return this.quantity * this.rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
static calcAmount(itemEntry) {
|
/**
|
||||||
const { discount, quantity, rate } = itemEntry;
|
* Item entry amount including tax.
|
||||||
const total = quantity * rate;
|
* @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}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get taxAmount() {
|
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() {
|
static get relationMappings() {
|
||||||
const Item = require('models/Item');
|
const Item = require('models/Item');
|
||||||
const BillLandedCostEntry = require('models/BillLandedCostEntry');
|
const BillLandedCostEntry = require('models/BillLandedCostEntry');
|
||||||
@@ -104,6 +157,9 @@ export default class ItemEntry extends TenantModel {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sale receipt reference.
|
||||||
|
*/
|
||||||
receipt: {
|
receipt: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: SaleReceipt.default,
|
modelClass: SaleReceipt.default,
|
||||||
@@ -114,7 +170,7 @@ export default class ItemEntry extends TenantModel {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Project task reference.
|
||||||
*/
|
*/
|
||||||
projectTaskRef: {
|
projectTaskRef: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
@@ -126,7 +182,7 @@ export default class ItemEntry extends TenantModel {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Project expense reference.
|
||||||
*/
|
*/
|
||||||
projectExpenseRef: {
|
projectExpenseRef: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
@@ -138,7 +194,7 @@ export default class ItemEntry extends TenantModel {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Project bill reference.
|
||||||
*/
|
*/
|
||||||
projectBillRef: {
|
projectBillRef: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { mixin, Model, raw } from 'objection';
|
import { mixin, Model, raw } from 'objection';
|
||||||
import { castArray } from 'lodash';
|
import { castArray, takeWhile } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import TenantModel from 'models/TenantModel';
|
import TenantModel from 'models/TenantModel';
|
||||||
import ModelSetting from './ModelSetting';
|
import ModelSetting from './ModelSetting';
|
||||||
@@ -13,10 +13,16 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
CustomViewBaseModel,
|
CustomViewBaseModel,
|
||||||
ModelSearchable,
|
ModelSearchable,
|
||||||
]) {
|
]) {
|
||||||
taxAmountWithheld: number;
|
public taxAmountWithheld: number;
|
||||||
balance: number;
|
public amount: number;
|
||||||
paymentAmount: number;
|
public paymentAmount: number;
|
||||||
exchangeRate: number;
|
public exchangeRate: number;
|
||||||
|
public writtenoffAmount: number;
|
||||||
|
public creditedAmount: number;
|
||||||
|
public isInclusiveTax: boolean;
|
||||||
|
public writtenoffAt: Date;
|
||||||
|
public dueDate: Date;
|
||||||
|
public deliveredAt: Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table name
|
* Table name
|
||||||
@@ -32,6 +38,9 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
return ['created_at', 'updated_at'];
|
return ['created_at', 'updated_at'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
get pluralName() {
|
get pluralName() {
|
||||||
return 'asdfsdf';
|
return 'asdfsdf';
|
||||||
}
|
}
|
||||||
@@ -41,140 +50,82 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
*/
|
*/
|
||||||
static get virtualAttributes() {
|
static get virtualAttributes() {
|
||||||
return [
|
return [
|
||||||
'localAmount',
|
|
||||||
'dueAmount',
|
|
||||||
'balanceAmount',
|
|
||||||
'isDelivered',
|
'isDelivered',
|
||||||
'isOverdue',
|
'isOverdue',
|
||||||
'isPartiallyPaid',
|
'isPartiallyPaid',
|
||||||
'isFullyPaid',
|
'isFullyPaid',
|
||||||
'isPaid',
|
|
||||||
'isWrittenoff',
|
'isWrittenoff',
|
||||||
|
'isPaid',
|
||||||
|
|
||||||
|
'dueAmount',
|
||||||
|
'balanceAmount',
|
||||||
'remainingDays',
|
'remainingDays',
|
||||||
'overdueDays',
|
'overdueDays',
|
||||||
'filterByBranches',
|
|
||||||
|
'subtotal',
|
||||||
|
'subtotalLocal',
|
||||||
|
'subtotalExludingTax',
|
||||||
|
|
||||||
|
'taxAmountWithheldLocal',
|
||||||
|
'total',
|
||||||
|
'totalLocal',
|
||||||
|
|
||||||
|
'writtenoffAmountLocal',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoice total FCY.
|
* Subtotal. (Tax inclusive) if the tax inclusive is enabled.
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get totalFcy() {
|
get subtotal() {
|
||||||
return this.amountFcy + this.taxAmountWithheldFcy;
|
return this.amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoice total BCY.
|
* Subtotal in base currency. (Tax inclusive) if the tax inclusive is enabled.
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get totalBcy() {
|
get subtotalLocal() {
|
||||||
return this.amountBcy + this.taxAmountWithheldBcy;
|
return this.amount * this.exchangeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tax amount withheld FCY.
|
* Sale invoice amount excluding tax.
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get taxAmountWithheldFcy() {
|
get subtotalExludingTax() {
|
||||||
return this.taxAmountWithheld;
|
return this.isInclusiveTax
|
||||||
|
? this.subtotal - this.taxAmountWithheld
|
||||||
|
: this.subtotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tax amount withheld BCY.
|
* Tax amount withheld in base currency.
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get taxAmountWithheldBcy() {
|
get taxAmountWithheldLocal() {
|
||||||
return this.taxAmountWithheld;
|
return this.taxAmountWithheld * this.exchangeRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subtotal FCY.
|
* Invoice total. (Tax included)
|
||||||
* @returns {number}
|
* @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() {
|
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}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
get localAmount() {
|
get totalLocal() {
|
||||||
return this.total * this.exchangeRate;
|
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.
|
* Detarmines whether the invoice is delivered.
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
@@ -205,7 +156,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
get dueAmount() {
|
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}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
get isPartiallyPaid() {
|
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: {
|
costTransactions: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
@@ -506,7 +457,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Invoice may has associated payment entries.
|
||||||
*/
|
*/
|
||||||
paymentEntries: {
|
paymentEntries: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
@@ -529,6 +480,9 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice may has associated written-off expense account.
|
||||||
|
*/
|
||||||
writtenoffExpenseAccount: {
|
writtenoffExpenseAccount: {
|
||||||
relation: Model.BelongsToOneRelation,
|
relation: Model.BelongsToOneRelation,
|
||||||
modelClass: Account.default,
|
modelClass: Account.default,
|
||||||
@@ -539,7 +493,7 @@ export default class SaleInvoice extends mixin(TenantModel, [
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Invoice may has associated tax rate transactions.
|
||||||
*/
|
*/
|
||||||
taxes: {
|
taxes: {
|
||||||
relation: Model.HasManyRelation,
|
relation: Model.HasManyRelation,
|
||||||
|
|||||||
@@ -13,17 +13,14 @@ import {
|
|||||||
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
import { BranchTransactionDTOTransform } from '@/services/Branches/Integrations/BranchTransactionDTOTransform';
|
||||||
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
import { WarehouseTransactionDTOTransform } from '@/services/Warehouses/Integrations/WarehouseTransactionDTOTransform';
|
||||||
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
import ItemsEntriesService from '@/services/Items/ItemsEntriesService';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|
||||||
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||||
import { SaleInvoiceIncrement } from './SaleInvoiceIncrement';
|
import { SaleInvoiceIncrement } from './SaleInvoiceIncrement';
|
||||||
import { formatDateFields } from 'utils';
|
import { formatDateFields } from 'utils';
|
||||||
import { ItemEntriesTaxTransactions } from '@/services/TaxRates/ItemEntriesTaxTransactions';
|
import { ItemEntriesTaxTransactions } from '@/services/TaxRates/ItemEntriesTaxTransactions';
|
||||||
|
import { ItemEntry } from '@/models';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class CommandSaleInvoiceDTOTransformer {
|
export class CommandSaleInvoiceDTOTransformer {
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private branchDTOTransform: BranchTransactionDTOTransform;
|
private branchDTOTransform: BranchTransactionDTOTransform;
|
||||||
|
|
||||||
@@ -55,11 +52,9 @@ export class CommandSaleInvoiceDTOTransformer {
|
|||||||
authorizedUser: ITenantUser,
|
authorizedUser: ITenantUser,
|
||||||
oldSaleInvoice?: ISaleInvoice
|
oldSaleInvoice?: ISaleInvoice
|
||||||
): Promise<ISaleInvoice> {
|
): Promise<ISaleInvoice> {
|
||||||
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.
|
// Retreive the next invoice number.
|
||||||
const autoNextNumber = this.invoiceIncrement.getNextInvoiceNumber(tenantId);
|
const autoNextNumber = this.invoiceIncrement.getNextInvoiceNumber(tenantId);
|
||||||
|
|
||||||
@@ -72,6 +67,7 @@ export class CommandSaleInvoiceDTOTransformer {
|
|||||||
|
|
||||||
const initialEntries = saleInvoiceDTO.entries.map((entry) => ({
|
const initialEntries = saleInvoiceDTO.entries.map((entry) => ({
|
||||||
referenceType: 'SaleInvoice',
|
referenceType: 'SaleInvoice',
|
||||||
|
isInclusiveTax: saleInvoiceDTO.isInclusiveTax,
|
||||||
...entry,
|
...entry,
|
||||||
}));
|
}));
|
||||||
const entries = await composeAsync(
|
const entries = await composeAsync(
|
||||||
@@ -87,7 +83,7 @@ export class CommandSaleInvoiceDTOTransformer {
|
|||||||
['invoiceDate', 'dueDate']
|
['invoiceDate', 'dueDate']
|
||||||
),
|
),
|
||||||
// Avoid rewrite the deliver date in edit mode when already published.
|
// Avoid rewrite the deliver date in edit mode when already published.
|
||||||
balance,
|
amount,
|
||||||
currencyCode: customer.currencyCode,
|
currencyCode: customer.currencyCode,
|
||||||
exchangeRate: saleInvoiceDTO.exchangeRate || 1,
|
exchangeRate: saleInvoiceDTO.exchangeRate || 1,
|
||||||
...(saleInvoiceDTO.delivered &&
|
...(saleInvoiceDTO.delivered &&
|
||||||
@@ -107,4 +103,29 @@ export class CommandSaleInvoiceDTOTransformer {
|
|||||||
this.warehouseDTOTransform.transformDTO<ISaleInvoice>(tenantId)
|
this.warehouseDTOTransform.transformDTO<ISaleInvoice>(tenantId)
|
||||||
)(initialDTO);
|
)(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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ export class SaleInvoiceGLEntries {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...commonEntry,
|
...commonEntry,
|
||||||
debit: saleInvoice.totalBcy,
|
debit: saleInvoice.totalLocal,
|
||||||
accountId: ARAccountId,
|
accountId: ARAccountId,
|
||||||
contactId: saleInvoice.customerId,
|
contactId: saleInvoice.customerId,
|
||||||
accountNormal: AccountNormal.DEBIT,
|
accountNormal: AccountNormal.DEBIT,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
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 {
|
export class SaleInvoiceTaxEntryTransformer extends Transformer {
|
||||||
/**
|
/**
|
||||||
@@ -6,7 +9,14 @@ export class SaleInvoiceTaxEntryTransformer extends Transformer {
|
|||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
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
|
* @param taxEntry
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
protected raxRate = (taxEntry) => {
|
protected taxRate = (taxEntry) => {
|
||||||
return taxEntry.taxAmount || taxEntry.taxRate.rate;
|
return taxEntry.taxAmount || taxEntry.taxRate.rate;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,4 +53,26 @@ export class SaleInvoiceTaxEntryTransformer extends Transformer {
|
|||||||
protected name = (taxEntry) => {
|
protected name = (taxEntry) => {
|
||||||
return taxEntry.taxRate.name;
|
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,
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,19 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
*/
|
*/
|
||||||
public includeAttributes = (): string[] => {
|
public includeAttributes = (): string[] => {
|
||||||
return [
|
return [
|
||||||
'formattedInvoiceDate',
|
'invoiceDateFormatted',
|
||||||
'formattedDueDate',
|
'dueDateFormatted',
|
||||||
'formattedAmount',
|
'dueAmountFormatted',
|
||||||
'formattedDueAmount',
|
'paymentAmountFormatted',
|
||||||
'formattedPaymentAmount',
|
'balanceAmountFormatted',
|
||||||
'formattedBalanceAmount',
|
'exchangeRateFormatted',
|
||||||
'formattedExchangeRate',
|
'subtotalFormatted',
|
||||||
|
'subtotalLocalFormatted',
|
||||||
|
'subtotalExludingTaxFormatted',
|
||||||
|
'taxAmountWithheldFormatted',
|
||||||
|
'taxAmountWithheldLocalFormatted',
|
||||||
|
'totalFormatted',
|
||||||
|
'totalLocalFormatted',
|
||||||
'taxes',
|
'taxes',
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
@@ -25,7 +31,7 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
* @returns {String}
|
* @returns {String}
|
||||||
*/
|
*/
|
||||||
protected formattedInvoiceDate = (invoice): string => {
|
protected invoiceDateFormatted = (invoice): string => {
|
||||||
return this.formatDate(invoice.invoiceDate);
|
return this.formatDate(invoice.invoiceDate);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,27 +40,16 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedDueDate = (invoice): string => {
|
protected dueDateFormatted = (invoice): string => {
|
||||||
return this.formatDate(invoice.dueDate);
|
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.
|
* Retrieve formatted invoice due amount.
|
||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedDueAmount = (invoice): string => {
|
protected dueAmountFormatted = (invoice): string => {
|
||||||
return formatNumber(invoice.dueAmount, {
|
return formatNumber(invoice.dueAmount, {
|
||||||
currencyCode: invoice.currencyCode,
|
currencyCode: invoice.currencyCode,
|
||||||
});
|
});
|
||||||
@@ -65,7 +60,7 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedPaymentAmount = (invoice): string => {
|
protected paymentAmountFormatted = (invoice): string => {
|
||||||
return formatNumber(invoice.paymentAmount, {
|
return formatNumber(invoice.paymentAmount, {
|
||||||
currencyCode: invoice.currencyCode,
|
currencyCode: invoice.currencyCode,
|
||||||
});
|
});
|
||||||
@@ -76,7 +71,7 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedBalanceAmount = (invoice): string => {
|
protected balanceAmountFormatted = (invoice): string => {
|
||||||
return formatNumber(invoice.balanceAmount, {
|
return formatNumber(invoice.balanceAmount, {
|
||||||
currencyCode: invoice.currencyCode,
|
currencyCode: invoice.currencyCode,
|
||||||
});
|
});
|
||||||
@@ -87,15 +82,98 @@ export class SaleInvoiceTransformer extends Transformer {
|
|||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
protected formattedExchangeRate = (invoice): string => {
|
protected exchangeRateFormatted = (invoice): string => {
|
||||||
return formatNumber(invoice.exchangeRate, { money: false });
|
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.
|
* Retrieve the taxes lines of sale invoice.
|
||||||
* @param {ISaleInvoice} invoice
|
* @param {ISaleInvoice} invoice
|
||||||
*/
|
*/
|
||||||
protected taxes = (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,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { keyBy, sumBy } from 'lodash';
|
import { keyBy, sumBy } from 'lodash';
|
||||||
import * as R from 'ramda';
|
|
||||||
import { ItemEntry } from '@/models';
|
import { ItemEntry } from '@/models';
|
||||||
import HasTenancyService from '../Tenancy/TenancyService';
|
import HasTenancyService from '../Tenancy/TenancyService';
|
||||||
|
|
||||||
|
|||||||
@@ -471,6 +471,15 @@ const castCommaListEnvVarToArray = (envVar: string): Array<string> => {
|
|||||||
return envVar ? envVar?.split(',')?.map(_.trim) : [];
|
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 {
|
export {
|
||||||
templateRender,
|
templateRender,
|
||||||
accumSum,
|
accumSum,
|
||||||
@@ -503,5 +512,5 @@ export {
|
|||||||
mergeObjectsBykey,
|
mergeObjectsBykey,
|
||||||
nestedArrayToFlatten,
|
nestedArrayToFlatten,
|
||||||
assocDepthLevelToObjectTree,
|
assocDepthLevelToObjectTree,
|
||||||
castCommaListEnvVarToArray
|
castCommaListEnvVarToArray,
|
||||||
};
|
};
|
||||||
|
|||||||
19
packages/server/src/utils/taxRate.ts
Normal file
19
packages/server/src/utils/taxRate.ts
Normal file
@@ -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;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user