mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
incr: show discount note
This commit is contained in:
parent
eb66317dce
commit
ff085d6766
@ -566,6 +566,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
const formulaFields = Object.keys(this.formulas).map(
|
||||
(fn) => this.fieldMap[fn]
|
||||
);
|
||||
|
||||
changed ||= await this._applyFormulaForFields(
|
||||
formulaFields,
|
||||
doc,
|
||||
|
@ -1,6 +1,11 @@
|
||||
import { DocValue } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { FiltersMap, FormulaMap, ValidationMap } from 'fyo/model/types';
|
||||
import {
|
||||
FiltersMap,
|
||||
FormulaMap,
|
||||
HiddenMap,
|
||||
ValidationMap,
|
||||
} from 'fyo/model/types';
|
||||
import { ValidationError } from 'fyo/utils/errors';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
@ -17,6 +22,8 @@ export abstract class InvoiceItem extends Doc {
|
||||
rate?: Money;
|
||||
quantity?: number;
|
||||
tax?: string;
|
||||
itemTaxedTotal?: Money;
|
||||
itemDiscountedTotal?: Money;
|
||||
|
||||
get isSales() {
|
||||
return this.schemaName === 'SalesInvoiceItem';
|
||||
@ -48,113 +55,64 @@ export abstract class InvoiceItem extends Doc {
|
||||
)) as string,
|
||||
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;
|
||||
const taxedTotal = getTaxedTotalBeforeDiscounting(
|
||||
totalTaxRate,
|
||||
rate,
|
||||
quantity
|
||||
);
|
||||
|
||||
return itemDiscountAmount.div(taxedTotal).mul(100).float;
|
||||
},
|
||||
dependsOn: ['itemDiscountAmount'],
|
||||
},
|
||||
itemDiscountedTotal: {
|
||||
formula: async (fieldname) => {
|
||||
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.discountAfterTax) {
|
||||
return getDiscountedTotalBeforeTaxation(
|
||||
rate,
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
);
|
||||
}
|
||||
|
||||
return getDiscountedTotalAfterTaxation(
|
||||
totalTaxRate,
|
||||
rate,
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
);
|
||||
},
|
||||
dependsOn: [
|
||||
'item',
|
||||
'rate',
|
||||
'tax',
|
||||
'quantity',
|
||||
'itemDiscountAmount',
|
||||
'itemDiscountPercent',
|
||||
],
|
||||
},
|
||||
itemTaxedTotal: {
|
||||
formula: async (fieldname) => {
|
||||
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.discountAfterTax) {
|
||||
return getTaxedTotalAfterDiscounting(
|
||||
totalTaxRate,
|
||||
rate,
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
);
|
||||
}
|
||||
|
||||
return getTaxedTotalBeforeDiscounting(totalTaxRate, rate, quantity);
|
||||
},
|
||||
dependsOn: [
|
||||
'item',
|
||||
'rate',
|
||||
'tax',
|
||||
'quantity',
|
||||
'itemDiscountAmount',
|
||||
'itemDiscountPercent',
|
||||
],
|
||||
},
|
||||
rate: {
|
||||
formula: async () => {
|
||||
const rate = (await this.fyo.getValue(
|
||||
formula: async (fieldname) => {
|
||||
let rate = (await this.fyo.getValue(
|
||||
'Item',
|
||||
this.item as string,
|
||||
'rate'
|
||||
)) as undefined | Money;
|
||||
if (
|
||||
fieldname !== 'itemTaxedTotal' &&
|
||||
fieldname !== 'itemDiscountedTotal'
|
||||
) {
|
||||
return rate ?? this.fyo.pesa(0);
|
||||
}
|
||||
|
||||
const quantity = this.quantity ?? 0;
|
||||
const taxedTotal = this.itemTaxedTotal ?? this.fyo.pesa(0);
|
||||
const discountedTotal = this.itemDiscountedTotal ?? this.fyo.pesa(0);
|
||||
const totalTaxRate = await this.getTotalTaxRate();
|
||||
const discountAmount = this.itemDiscountAmount ?? this.fyo.pesa(0);
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
console.log(rate?.float, fieldname, this.discountAfterTax);
|
||||
return rate ?? this.fyo.pesa(0);
|
||||
},
|
||||
dependsOn: ['item'],
|
||||
dependsOn: ['item', 'itemTaxedTotal', 'itemDiscountedTotal'],
|
||||
},
|
||||
baseRate: {
|
||||
formula: () =>
|
||||
@ -215,6 +173,122 @@ 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) => {
|
||||
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
|
||||
) {
|
||||
return rate.mul(quantity);
|
||||
}
|
||||
|
||||
if (!this.discountAfterTax) {
|
||||
return getDiscountedTotalBeforeTaxation(
|
||||
rate,
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
);
|
||||
}
|
||||
|
||||
return getDiscountedTotalAfterTaxation(
|
||||
totalTaxRate,
|
||||
rate,
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
);
|
||||
},
|
||||
dependsOn: [
|
||||
'itemDiscountAmount',
|
||||
'itemDiscountPercent',
|
||||
'itemTaxedTotal',
|
||||
'tax',
|
||||
'rate',
|
||||
'quantity',
|
||||
'item',
|
||||
],
|
||||
},
|
||||
itemTaxedTotal: {
|
||||
formula: async (fieldname) => {
|
||||
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.discountAfterTax) {
|
||||
return getTaxedTotalAfterDiscounting(
|
||||
totalTaxRate,
|
||||
rate,
|
||||
quantity,
|
||||
itemDiscountAmount,
|
||||
itemDiscountPercent,
|
||||
fieldname
|
||||
);
|
||||
}
|
||||
|
||||
return getTaxedTotalBeforeDiscounting(totalTaxRate, rate, quantity);
|
||||
},
|
||||
dependsOn: [
|
||||
'itemDiscountAmount',
|
||||
'itemDiscountPercent',
|
||||
'itemDiscountedTotal',
|
||||
'tax',
|
||||
'rate',
|
||||
'quantity',
|
||||
'item',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
validations: ValidationMap = {
|
||||
@ -232,6 +306,15 @@ export abstract class InvoiceItem extends Doc {
|
||||
},
|
||||
};
|
||||
|
||||
hidden: HiddenMap = {
|
||||
itemDiscountedTotal: () => {
|
||||
return (
|
||||
!this.discountAfterTax &&
|
||||
(this.itemDiscountAmount?.isZero() || this.itemDiscountPercent === 0)
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
static filters: FiltersMap = {
|
||||
item: (doc: Doc) => {
|
||||
const itemList = doc.parentdoc!.items as Doc[];
|
||||
@ -253,6 +336,7 @@ export abstract class InvoiceItem extends Doc {
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
static createFilters: FiltersMap = {
|
||||
item: (doc: Doc) => {
|
||||
return { for: doc.isSales ? 'Sales' : 'Purchases' };
|
||||
@ -344,3 +428,46 @@ function getTaxedTotalBeforeDiscounting(
|
||||
|
||||
return rate.mul(quantity).mul(1 + totalTaxRate / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate Rate if any of the final amounts is set
|
||||
*/
|
||||
function getRateFromDiscountedTotalWhenDiscountingBeforeTaxation(
|
||||
quantity: number,
|
||||
discountAmount: Money,
|
||||
discountedTotal: Money
|
||||
) {
|
||||
return discountedTotal.add(discountAmount).div(quantity);
|
||||
}
|
||||
|
||||
function getRateFromTaxedTotalWhenDiscountingBeforeTaxation(
|
||||
quantity: number,
|
||||
discountAmount: Money,
|
||||
taxedTotal: Money,
|
||||
totalTaxRatio: number
|
||||
) {
|
||||
return taxedTotal
|
||||
.div(1 + totalTaxRatio / 100)
|
||||
.add(discountAmount)
|
||||
.div(quantity);
|
||||
}
|
||||
|
||||
function getRateFromDiscountedTotalWhenDiscountingAfterTaxation(
|
||||
quantity: number,
|
||||
discountAmount: Money,
|
||||
discountedTotal: Money,
|
||||
totalTaxRatio: number
|
||||
) {
|
||||
return discountedTotal
|
||||
.add(discountAmount)
|
||||
.div(1 + totalTaxRatio / 100)
|
||||
.div(quantity);
|
||||
}
|
||||
|
||||
function getRateFromTaxedTotalWhenDiscountingAfterTaxation(
|
||||
quantity: number,
|
||||
taxedTotal: Money,
|
||||
totalTaxRatio: number
|
||||
) {
|
||||
return taxedTotal.div(1 + totalTaxRatio / 100).div(quantity);
|
||||
}
|
||||
|
@ -124,16 +124,22 @@
|
||||
<div v-if="doc.items?.length ?? 0" class="mt-auto">
|
||||
<hr />
|
||||
<div class="flex justify-between text-base m-4 gap-12">
|
||||
<!-- Form Terms-->
|
||||
<FormControl
|
||||
class="w-1/2 self-end"
|
||||
v-if="!doc?.submitted || doc.terms"
|
||||
:df="getField('terms')"
|
||||
:value="doc.terms"
|
||||
input-class="bg-gray-100"
|
||||
@change="(value) => doc.set('terms', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
<div class="w-1/2 relative">
|
||||
<!-- Discount Note -->
|
||||
<p v-if="discountNote?.length" class="text-gray-600">
|
||||
{{ discountNote }}
|
||||
</p>
|
||||
<!-- Form Terms-->
|
||||
<FormControl
|
||||
v-if="!doc?.submitted || doc.terms"
|
||||
:df="getField('terms')"
|
||||
:value="doc.terms"
|
||||
input-class="bg-gray-100"
|
||||
class="absolute bottom-0 w-full"
|
||||
@change="(value) => doc.set('terms', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Totals -->
|
||||
<div class="w-1/2 gap-2 flex flex-col self-end ml-auto">
|
||||
@ -270,6 +276,28 @@ export default {
|
||||
this.chstatus;
|
||||
return getDocStatus(this.doc);
|
||||
},
|
||||
discountNote() {
|
||||
const zeroInvoiceDiscount =
|
||||
this.doc?.discountAmount?.isZero() && this.doc?.discountPercent === 0;
|
||||
|
||||
const zeroItemDiscount = (this.doc?.items ?? []).every(
|
||||
(i) => i?.itemDiscountAmount?.isZero() && i?.itemDiscountPercent === 0
|
||||
);
|
||||
if (zeroInvoiceDiscount && zeroItemDiscount) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!this.doc?.taxes?.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let text = this.t`Discount applied before taxation`;
|
||||
if (this.doc.discountAfterTax) {
|
||||
text = this.t`Discount applied after taxation`;
|
||||
}
|
||||
|
||||
return text;
|
||||
},
|
||||
},
|
||||
activated() {
|
||||
docsPath.value = docsPathMap[this.schemaName];
|
||||
|
Loading…
Reference in New Issue
Block a user