mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
incr: use flags in invoiceitem discounting
- update grand total calculation
This commit is contained in:
parent
7f928ca712
commit
bab18dc8f7
@ -133,25 +133,29 @@ export abstract class Invoice extends Transactional {
|
||||
}
|
||||
> = {};
|
||||
|
||||
for (const row of this.items as Doc[]) {
|
||||
if (!row.tax) {
|
||||
type TaxDetail = { account: string; rate: number };
|
||||
|
||||
for (const item of this.items ?? []) {
|
||||
if (!item.tax) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tax = await this.getTax(row.tax as string);
|
||||
for (const d of tax.details as Doc[]) {
|
||||
const account = d.account as string;
|
||||
const rate = d.rate as number;
|
||||
|
||||
taxes[account] = taxes[account] || {
|
||||
const tax = await this.getTax(item.tax!);
|
||||
for (const { account, rate } of tax.details as TaxDetail[]) {
|
||||
taxes[account] ??= {
|
||||
account,
|
||||
rate,
|
||||
amount: this.fyo.pesa(0),
|
||||
baseAmount: this.fyo.pesa(0),
|
||||
};
|
||||
|
||||
const amount = (row.amount as Money).mul(rate).div(100);
|
||||
taxes[account].amount = taxes[account].amount.add(amount);
|
||||
let amount = item.amount!;
|
||||
if (this.enableDiscounting && !this.discountAfterTax) {
|
||||
amount = item.itemDiscountedTotal!;
|
||||
}
|
||||
|
||||
const taxAmount = amount.mul(rate / 100);
|
||||
taxes[account].amount = taxes[account].amount.add(taxAmount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,10 +192,28 @@ export abstract class Invoice extends Transactional {
|
||||
return this.fyo.pesa(0);
|
||||
}
|
||||
|
||||
return this.items.reduce(
|
||||
(acc, i) => acc.add(i.itemDiscountAmount ?? this.fyo.pesa(0)),
|
||||
this.fyo.pesa(0)
|
||||
);
|
||||
let discountAmount = this.fyo.pesa(0);
|
||||
for (const item of this.items) {
|
||||
if (item.setItemDiscountAmount) {
|
||||
discountAmount = discountAmount.add(
|
||||
item.itemDiscountAmount ?? this.fyo.pesa(0)
|
||||
);
|
||||
} else if (!this.discountAfterTax) {
|
||||
discountAmount = discountAmount.add(
|
||||
(item.amount ?? this.fyo.pesa(0)).mul(
|
||||
(item.itemDiscountPercent ?? 0) / 100
|
||||
)
|
||||
);
|
||||
} else if (this.discountAfterTax) {
|
||||
discountAmount = discountAmount.add(
|
||||
(item.itemTaxedTotal ?? this.fyo.pesa(0)).mul(
|
||||
(item.itemDiscountPercent ?? 0) / 100
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return discountAmount;
|
||||
}
|
||||
|
||||
formulas: FormulaMap = {
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
FiltersMap,
|
||||
FormulaMap,
|
||||
HiddenMap,
|
||||
ValidationMap,
|
||||
ValidationMap
|
||||
} from 'fyo/model/types';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
@ -63,11 +63,12 @@ export abstract class InvoiceItem extends Doc {
|
||||
},
|
||||
rate: {
|
||||
formula: async (fieldname) => {
|
||||
let rate = (await this.fyo.getValue(
|
||||
const rate = (await this.fyo.getValue(
|
||||
'Item',
|
||||
this.item as string,
|
||||
'rate'
|
||||
)) as undefined | Money;
|
||||
|
||||
if (
|
||||
fieldname !== 'itemTaxedTotal' &&
|
||||
fieldname !== 'itemDiscountedTotal'
|
||||
@ -76,49 +77,36 @@ export abstract class InvoiceItem extends Doc {
|
||||
}
|
||||
|
||||
const quantity = this.quantity ?? 0;
|
||||
const taxedTotal = this.itemTaxedTotal ?? this.fyo.pesa(0);
|
||||
const discountedTotal = this.itemDiscountedTotal ?? this.fyo.pesa(0);
|
||||
const itemDiscountPercent = this.itemDiscountPercent ?? 0;
|
||||
const itemDiscountAmount = this.itemDiscountAmount ?? this.fyo.pesa(0);
|
||||
const totalTaxRate = await this.getTotalTaxRate();
|
||||
const discountAmount = this.itemDiscountAmount ?? this.fyo.pesa(0);
|
||||
const itemTaxedTotal = this.itemTaxedTotal ?? this.fyo.pesa(0);
|
||||
const itemDiscountedTotal =
|
||||
this.itemDiscountedTotal ?? this.fyo.pesa(0);
|
||||
const isItemTaxedTotal = fieldname === 'itemTaxedTotal';
|
||||
const discountAfterTax = this.discountAfterTax;
|
||||
const setItemDiscountAmount = !!this.setItemDiscountAmount;
|
||||
|
||||
if (fieldname === 'itemTaxedTotal' && this.discountAfterTax) {
|
||||
rate = getRateFromTaxedTotalWhenDiscountingAfterTaxation(
|
||||
quantity,
|
||||
taxedTotal,
|
||||
totalTaxRate
|
||||
);
|
||||
} else if (
|
||||
fieldname === 'itemDiscountedTotal' &&
|
||||
this.discountAfterTax
|
||||
) {
|
||||
rate = getRateFromDiscountedTotalWhenDiscountingAfterTaxation(
|
||||
quantity,
|
||||
discountAmount,
|
||||
discountedTotal,
|
||||
totalTaxRate
|
||||
);
|
||||
} else if (fieldname === 'itemTaxedTotal' && !this.discountAfterTax) {
|
||||
rate = getRateFromTaxedTotalWhenDiscountingBeforeTaxation(
|
||||
quantity,
|
||||
discountAmount,
|
||||
taxedTotal,
|
||||
totalTaxRate
|
||||
);
|
||||
} else if (
|
||||
fieldname === 'itemDiscountedTotal' &&
|
||||
!this.discountAfterTax
|
||||
) {
|
||||
rate = getRateFromDiscountedTotalWhenDiscountingBeforeTaxation(
|
||||
quantity,
|
||||
discountAmount,
|
||||
discountedTotal
|
||||
);
|
||||
}
|
||||
const rateFromTotals = getRate(
|
||||
quantity,
|
||||
itemDiscountPercent,
|
||||
itemDiscountAmount,
|
||||
totalTaxRate,
|
||||
itemTaxedTotal,
|
||||
itemDiscountedTotal,
|
||||
isItemTaxedTotal,
|
||||
discountAfterTax,
|
||||
setItemDiscountAmount
|
||||
);
|
||||
|
||||
console.log(rate?.float, fieldname, this.discountAfterTax);
|
||||
return rate ?? this.fyo.pesa(0);
|
||||
return rateFromTotals ?? rate ?? this.fyo.pesa(0);
|
||||
},
|
||||
dependsOn: ['item', 'itemTaxedTotal', 'itemDiscountedTotal'],
|
||||
dependsOn: [
|
||||
'item',
|
||||
'itemTaxedTotal',
|
||||
'itemDiscountedTotal',
|
||||
'setItemDiscountAmount',
|
||||
],
|
||||
},
|
||||
baseRate: {
|
||||
formula: () =>
|
||||
@ -179,59 +167,19 @@ export abstract class InvoiceItem extends Doc {
|
||||
await this.fyo.getValue('Item', this.item as string, 'hsnCode'),
|
||||
dependsOn: ['item'],
|
||||
},
|
||||
itemDiscountAmount: {
|
||||
formula: async (fieldname) => {
|
||||
if (fieldname === 'itemDiscountPercent') {
|
||||
return this.amount!.percent(this.itemDiscountPercent ?? 0);
|
||||
}
|
||||
|
||||
return this.fyo.pesa(0);
|
||||
},
|
||||
dependsOn: ['itemDiscountPercent'],
|
||||
},
|
||||
itemDiscountPercent: {
|
||||
formula: async (fieldname) => {
|
||||
const itemDiscountAmount = this.itemDiscountAmount ?? this.fyo.pesa(0);
|
||||
if (!this.discountAfterTax) {
|
||||
return itemDiscountAmount.div(this.amount ?? 0).mul(100).float;
|
||||
}
|
||||
|
||||
const totalTaxRate = await this.getTotalTaxRate();
|
||||
const rate = this.rate ?? this.fyo.pesa(0);
|
||||
const quantity = this.quantity ?? 1;
|
||||
|
||||
let itemTaxedTotal = this.itemTaxedTotal;
|
||||
if (fieldname !== 'itemTaxedTotal' || !itemTaxedTotal) {
|
||||
itemTaxedTotal = getTaxedTotalBeforeDiscounting(
|
||||
totalTaxRate,
|
||||
rate,
|
||||
quantity
|
||||
);
|
||||
}
|
||||
|
||||
return itemDiscountAmount.div(itemTaxedTotal).mul(100).float;
|
||||
},
|
||||
dependsOn: [
|
||||
'itemDiscountAmount',
|
||||
'item',
|
||||
'rate',
|
||||
'quantity',
|
||||
'itemTaxedTotal',
|
||||
'itemDiscountedTotal',
|
||||
],
|
||||
},
|
||||
itemDiscountedTotal: {
|
||||
formula: async (fieldname) => {
|
||||
formula: async () => {
|
||||
const totalTaxRate = await this.getTotalTaxRate();
|
||||
const rate = this.rate ?? this.fyo.pesa(0);
|
||||
const quantity = this.quantity ?? 1;
|
||||
const itemDiscountAmount = this.itemDiscountAmount ?? this.fyo.pesa(0);
|
||||
const itemDiscountPercent = this.itemDiscountPercent ?? 0;
|
||||
|
||||
if (
|
||||
this.itemDiscountAmount?.isZero() ||
|
||||
this.itemDiscountPercent === 0
|
||||
) {
|
||||
if (this.setItemDiscountAmount && this.itemDiscountAmount?.isZero()) {
|
||||
return rate.mul(quantity);
|
||||
}
|
||||
|
||||
if (!this.setItemDiscountAmount && this.itemDiscountPercent === 0) {
|
||||
return rate.mul(quantity);
|
||||
}
|
||||
|
||||
@ -241,7 +189,7 @@ export abstract class InvoiceItem extends Doc {
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
!!this.setItemDiscountAmount
|
||||
);
|
||||
}
|
||||
|
||||
@ -251,13 +199,14 @@ export abstract class InvoiceItem extends Doc {
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
!!this.setItemDiscountAmount
|
||||
);
|
||||
},
|
||||
dependsOn: [
|
||||
'itemDiscountAmount',
|
||||
'itemDiscountPercent',
|
||||
'itemTaxedTotal',
|
||||
'setItemDiscountAmount',
|
||||
'tax',
|
||||
'rate',
|
||||
'quantity',
|
||||
@ -279,7 +228,7 @@ export abstract class InvoiceItem extends Doc {
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
!!this.setItemDiscountAmount
|
||||
);
|
||||
}
|
||||
|
||||
@ -289,6 +238,7 @@ export abstract class InvoiceItem extends Doc {
|
||||
'itemDiscountAmount',
|
||||
'itemDiscountPercent',
|
||||
'itemDiscountedTotal',
|
||||
'setItemDiscountAmount',
|
||||
'tax',
|
||||
'rate',
|
||||
'quantity',
|
||||
@ -310,14 +260,49 @@ export abstract class InvoiceItem extends Doc {
|
||||
)}) cannot be less zero.`
|
||||
);
|
||||
},
|
||||
itemDiscountAmount: async (value: DocValue) => {
|
||||
if ((value as Money).lte(this.amount!)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ValidationError(
|
||||
this.fyo.t`Discount Amount (${this.fyo.format(
|
||||
value,
|
||||
'Currency'
|
||||
)}) cannot be greated than Amount (${this.fyo.format(
|
||||
this.amount!,
|
||||
'Currency'
|
||||
)}).`
|
||||
);
|
||||
},
|
||||
itemDiscountPercent: async (value: DocValue) => {
|
||||
if ((value as number) < 100) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ValidationError(
|
||||
this.fyo.t`Discount Percent (${
|
||||
value as number
|
||||
}) cannot be greater than 100.`
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
hidden: HiddenMap = {
|
||||
itemDiscountedTotal: () => {
|
||||
return (
|
||||
!this.discountAfterTax &&
|
||||
(this.itemDiscountAmount?.isZero() || this.itemDiscountPercent === 0)
|
||||
);
|
||||
if (!this.enableDiscounting) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!!this.setItemDiscountAmount && this.itemDiscountAmount?.isZero()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.setItemDiscountAmount && this.itemDiscountPercent === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
setItemDiscountAmount: () => !this.enableDiscounting,
|
||||
itemDiscountAmount: () =>
|
||||
@ -360,7 +345,7 @@ function getDiscountedTotalBeforeTaxation(
|
||||
quantity: number,
|
||||
itemDiscountAmount: Money,
|
||||
itemDiscountPercent: number,
|
||||
fieldname?: string
|
||||
setDiscountAmount: boolean
|
||||
) {
|
||||
/**
|
||||
* If Discount is applied before taxation
|
||||
@ -368,8 +353,9 @@ function getDiscountedTotalBeforeTaxation(
|
||||
* - if amount : Quantity * Rate - DiscountAmount
|
||||
* - if percent: Quantity * Rate (1 - DiscountPercent / 100)
|
||||
*/
|
||||
|
||||
const amount = rate.mul(quantity);
|
||||
if (fieldname === 'itemDiscountAmount') {
|
||||
if (setDiscountAmount) {
|
||||
return amount.sub(itemDiscountAmount);
|
||||
}
|
||||
|
||||
@ -382,7 +368,7 @@ function getTaxedTotalAfterDiscounting(
|
||||
quantity: number,
|
||||
itemDiscountAmount: Money,
|
||||
itemDiscountPercent: number,
|
||||
fieldname?: string
|
||||
setItemDiscountAmount: boolean
|
||||
) {
|
||||
/**
|
||||
* If Discount is applied before taxation
|
||||
@ -394,7 +380,7 @@ function getTaxedTotalAfterDiscounting(
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
setItemDiscountAmount
|
||||
);
|
||||
|
||||
return discountedTotal.mul(1 + totalTaxRate / 100);
|
||||
@ -406,7 +392,7 @@ function getDiscountedTotalAfterTaxation(
|
||||
quantity: number,
|
||||
itemDiscountAmount: Money,
|
||||
itemDiscountPercent: number,
|
||||
fieldname?: string
|
||||
setItemDiscountAmount: boolean
|
||||
) {
|
||||
/**
|
||||
* If Discount is applied after taxation
|
||||
@ -420,7 +406,7 @@ function getDiscountedTotalAfterTaxation(
|
||||
quantity
|
||||
);
|
||||
|
||||
if (fieldname === 'itemDiscountAmount') {
|
||||
if (setItemDiscountAmount) {
|
||||
return taxedTotal.sub(itemDiscountAmount);
|
||||
}
|
||||
|
||||
@ -440,45 +426,62 @@ function getTaxedTotalBeforeDiscounting(
|
||||
return rate.mul(quantity).mul(1 + totalTaxRate / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Rate if any of the final amounts is set
|
||||
*/
|
||||
function getRateFromDiscountedTotalWhenDiscountingBeforeTaxation(
|
||||
function getRate(
|
||||
quantity: number,
|
||||
discountAmount: Money,
|
||||
discountedTotal: Money
|
||||
itemDiscountPercent: number,
|
||||
itemDiscountAmount: Money,
|
||||
totalTaxRate: number,
|
||||
itemTaxedTotal: Money,
|
||||
itemDiscountedTotal: Money,
|
||||
isItemTaxedTotal: boolean,
|
||||
discountAfterTax: boolean,
|
||||
setItemDiscountAmount: boolean
|
||||
) {
|
||||
return discountedTotal.add(discountAmount).div(quantity);
|
||||
}
|
||||
const isItemDiscountedTotal = !isItemTaxedTotal;
|
||||
const discountBeforeTax = !discountAfterTax;
|
||||
|
||||
function getRateFromTaxedTotalWhenDiscountingBeforeTaxation(
|
||||
quantity: number,
|
||||
discountAmount: Money,
|
||||
taxedTotal: Money,
|
||||
totalTaxRatio: number
|
||||
) {
|
||||
return taxedTotal
|
||||
.div(1 + totalTaxRatio / 100)
|
||||
.add(discountAmount)
|
||||
.div(quantity);
|
||||
}
|
||||
/**
|
||||
* Rate calculated from itemDiscountedTotal
|
||||
*/
|
||||
if (isItemDiscountedTotal && discountBeforeTax && setItemDiscountAmount) {
|
||||
return itemDiscountedTotal.add(itemDiscountAmount).div(quantity);
|
||||
}
|
||||
|
||||
function getRateFromDiscountedTotalWhenDiscountingAfterTaxation(
|
||||
quantity: number,
|
||||
discountAmount: Money,
|
||||
discountedTotal: Money,
|
||||
totalTaxRatio: number
|
||||
) {
|
||||
return discountedTotal
|
||||
.add(discountAmount)
|
||||
.div(1 + totalTaxRatio / 100)
|
||||
.div(quantity);
|
||||
}
|
||||
if (isItemDiscountedTotal && discountBeforeTax && !setItemDiscountAmount) {
|
||||
return itemDiscountedTotal.div(quantity * (1 - itemDiscountPercent / 100));
|
||||
}
|
||||
|
||||
function getRateFromTaxedTotalWhenDiscountingAfterTaxation(
|
||||
quantity: number,
|
||||
taxedTotal: Money,
|
||||
totalTaxRatio: number
|
||||
) {
|
||||
return taxedTotal.div(1 + totalTaxRatio / 100).div(quantity);
|
||||
if (isItemDiscountedTotal && discountAfterTax && setItemDiscountAmount) {
|
||||
return itemDiscountedTotal
|
||||
.add(itemDiscountAmount)
|
||||
.div(quantity * (1 + totalTaxRate / 100));
|
||||
}
|
||||
|
||||
if (isItemDiscountedTotal && discountAfterTax && !setItemDiscountAmount) {
|
||||
return itemDiscountedTotal.div(
|
||||
(quantity * (100 - itemDiscountPercent) * (100 + totalTaxRate)) / 100
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate calculated from itemTaxedTotal
|
||||
*/
|
||||
if (isItemTaxedTotal && discountAfterTax) {
|
||||
return itemTaxedTotal.div(quantity * (1 + totalTaxRate / 100));
|
||||
}
|
||||
|
||||
if (isItemTaxedTotal && discountBeforeTax && setItemDiscountAmount) {
|
||||
return itemTaxedTotal
|
||||
.div(1 + totalTaxRate / 100)
|
||||
.add(itemDiscountAmount)
|
||||
.div(quantity);
|
||||
}
|
||||
|
||||
if (isItemTaxedTotal && discountBeforeTax && !setItemDiscountAmount) {
|
||||
return itemTaxedTotal.div(
|
||||
quantity * (1 - itemDiscountPercent / 100) * (1 + totalTaxRate / 100)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -84,14 +84,14 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "itemDiscountedTotal",
|
||||
"label": "Discounted Total",
|
||||
"label": "Discounted Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": false,
|
||||
"computed": true
|
||||
},
|
||||
{
|
||||
"fieldname": "itemTaxedTotal",
|
||||
"label": "Taxed Total",
|
||||
"label": "Taxed Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": false,
|
||||
"computed": true
|
||||
|
@ -81,14 +81,14 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "itemDiscountedTotal",
|
||||
"label": "Discounted Total",
|
||||
"label": "Discounted Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": false,
|
||||
"computed": true
|
||||
},
|
||||
{
|
||||
"fieldname": "itemTaxedTotal",
|
||||
"label": "Taxed Total",
|
||||
"label": "Taxed Amount",
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": false,
|
||||
"computed": true
|
||||
|
@ -62,10 +62,8 @@
|
||||
height: getFieldHeight(df),
|
||||
}"
|
||||
>
|
||||
<div class="py-2 pl-4 flex text-gray-600">
|
||||
<div class="py-1">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
<div class="pl-4 flex text-gray-600">
|
||||
{{ df.label }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -12,7 +12,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
:icon="true"
|
||||
v-if="!doc?.isSubmitted && !quickEditDoc"
|
||||
v-if="!doc?.isSubmitted && !quickEditDoc && doc.enableDiscounting"
|
||||
@click="toggleInvoiceSettings"
|
||||
>
|
||||
<feather-icon name="settings" class="w-4 h-4" />
|
||||
@ -334,10 +334,8 @@ export default {
|
||||
},
|
||||
discountNote() {
|
||||
const zeroInvoiceDiscount = this.doc?.discountAmount?.isZero();
|
||||
const zeroItemDiscount = this.itemDiscountAmount?.isZero();
|
||||
|
||||
const zeroItemDiscount = (this.doc?.items ?? []).every(
|
||||
(i) => i?.itemDiscountAmount?.isZero() && i?.itemDiscountPercent === 0
|
||||
);
|
||||
if (zeroInvoiceDiscount && zeroItemDiscount) {
|
||||
return '';
|
||||
}
|
||||
@ -355,12 +353,7 @@ export default {
|
||||
},
|
||||
totalDiscount() {
|
||||
const discountAmount = this.doc?.discountAmount ?? fyo.pesa(0);
|
||||
const itemDiscount = (this.doc?.items ?? []).reduce(
|
||||
(acc, i) => acc.add(i.itemDiscountAmount),
|
||||
fyo.pesa(0)
|
||||
);
|
||||
|
||||
return discountAmount.add(itemDiscount);
|
||||
return discountAmount.add(this.itemDiscountAmount);
|
||||
},
|
||||
discountAmount() {
|
||||
return this.doc?.discountAmount ?? fyo.pesa(0);
|
||||
|
Loading…
Reference in New Issue
Block a user