mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
incr: add a few calculations
- add enableDiscounting - add toggle between amount and percent - minor ui fixes
This commit is contained in:
parent
be44333e64
commit
7f928ca712
@ -453,7 +453,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
if (data && data.name) {
|
||||
this._syncValues(data);
|
||||
await this._syncValues(data);
|
||||
await this.loadLinks();
|
||||
} else {
|
||||
throw new NotFoundError(`Not Found: ${this.schemaName} ${this.name}`);
|
||||
@ -502,15 +502,39 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
return link;
|
||||
}
|
||||
|
||||
_syncValues(data: DocValueMap) {
|
||||
async _syncValues(data: DocValueMap) {
|
||||
this._clearValues();
|
||||
this._setValuesWithoutChecks(data);
|
||||
await this._setComputedValuesFromFormulas();
|
||||
this._dirty = false;
|
||||
this.trigger('change', {
|
||||
doc: this,
|
||||
});
|
||||
}
|
||||
|
||||
async _setComputedValuesFromFormulas() {
|
||||
for (const field of this.schema.fields) {
|
||||
await this._setComputedValuesForChildren(field);
|
||||
if (!field.computed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = await this._getValueFromFormula(field, this);
|
||||
this[field.fieldname] = value ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
async _setComputedValuesForChildren(field: Field) {
|
||||
if (field.fieldtype !== 'Table') {
|
||||
return;
|
||||
}
|
||||
|
||||
const childDocs: Doc[] = (this[field.fieldname] as Doc[]) ?? [];
|
||||
for (const doc of childDocs) {
|
||||
await doc._setComputedValuesFromFormulas();
|
||||
}
|
||||
}
|
||||
|
||||
_clearValues() {
|
||||
for (const { fieldname } of this.schema.fields) {
|
||||
this[fieldname] = null;
|
||||
@ -635,7 +659,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
|
||||
const validDict = this.getValidDict(false, true);
|
||||
const data = await this.fyo.db.insert(this.schemaName, validDict);
|
||||
this._syncValues(data);
|
||||
await this._syncValues(data);
|
||||
|
||||
this.fyo.telemetry.log(Verb.Created, this.schemaName);
|
||||
return this;
|
||||
@ -648,7 +672,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
|
||||
const data = this.getValidDict(false, true);
|
||||
await this.fyo.db.update(this.schemaName, data);
|
||||
this._syncValues(data);
|
||||
await this._syncValues(data);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ export function getMissingMandatoryMessage(doc: Doc) {
|
||||
const value = doc.get(f.fieldname);
|
||||
const isNullOrUndef = getIsNullOrUndef(value);
|
||||
|
||||
console.log(f.fieldname, value);
|
||||
if (f.fieldtype === FieldTypeEnum.Table) {
|
||||
return isNullOrUndef || (value as Doc[])?.length === 0;
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { FiltersMap, ListsMap, ValidationMap } from 'fyo/model/types';
|
||||
import {
|
||||
FiltersMap,
|
||||
ListsMap,
|
||||
ReadOnlyMap,
|
||||
ValidationMap,
|
||||
} from 'fyo/model/types';
|
||||
import { validateEmail } from 'fyo/model/validationFunction';
|
||||
import { getCountryInfo } from 'utils/misc';
|
||||
|
||||
@ -22,4 +27,10 @@ export class AccountingSettings extends Doc {
|
||||
static lists: ListsMap = {
|
||||
country: () => Object.keys(getCountryInfo()),
|
||||
};
|
||||
|
||||
readOnly: ReadOnlyMap = {
|
||||
enableDiscounting: () => {
|
||||
return !!this.enableDiscounting;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { DocValue } from 'fyo/core/types';
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { DefaultMap, FiltersMap, FormulaMap } from 'fyo/model/types';
|
||||
import { DefaultMap, FiltersMap, FormulaMap, HiddenMap } from 'fyo/model/types';
|
||||
import { getExchangeRate } from 'models/helpers';
|
||||
import { Transactional } from 'models/Transactional/Transactional';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import { Money } from 'pesa';
|
||||
import { getIsNullOrUndef } from 'utils';
|
||||
import { InvoiceItem } from '../InvoiceItem/InvoiceItem';
|
||||
import { Party } from '../Party/Party';
|
||||
import { Payment } from '../Payment/Payment';
|
||||
import { Tax } from '../Tax/Tax';
|
||||
@ -15,6 +16,7 @@ export abstract class Invoice extends Transactional {
|
||||
_taxes: Record<string, Tax> = {};
|
||||
taxes?: TaxSummary[];
|
||||
|
||||
items?: InvoiceItem[];
|
||||
party?: string;
|
||||
account?: string;
|
||||
currency?: string;
|
||||
@ -23,6 +25,10 @@ export abstract class Invoice extends Transactional {
|
||||
baseGrandTotal?: Money;
|
||||
outstandingAmount?: Money;
|
||||
exchangeRate?: number;
|
||||
setDiscountAmount?: boolean;
|
||||
discountAmount?: Money;
|
||||
discountPercent?: number;
|
||||
discountAfterTax?: boolean;
|
||||
|
||||
submitted?: boolean;
|
||||
cancelled?: boolean;
|
||||
@ -31,6 +37,10 @@ export abstract class Invoice extends Transactional {
|
||||
return this.schemaName === 'SalesInvoice';
|
||||
}
|
||||
|
||||
get enableDiscounting() {
|
||||
return !!this.fyo.singles?.AccountingSettings?.enableDiscounting;
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
await super.afterSubmit();
|
||||
|
||||
@ -163,9 +173,25 @@ export abstract class Invoice extends Transactional {
|
||||
}
|
||||
|
||||
async getGrandTotal() {
|
||||
const itemDiscountAmount = this.getItemDiscountAmount();
|
||||
const discountAmount = this.discountAmount ?? this.fyo.pesa(0);
|
||||
const totalDiscount = itemDiscountAmount.add(discountAmount);
|
||||
|
||||
return ((this.taxes ?? []) as Doc[])
|
||||
.map((doc) => doc.amount as Money)
|
||||
.reduce((a, b) => a.add(b), this.netTotal!);
|
||||
.reduce((a, b) => a.add(b), this.netTotal!)
|
||||
.sub(totalDiscount);
|
||||
}
|
||||
|
||||
getItemDiscountAmount() {
|
||||
if (!this?.items?.length) {
|
||||
return this.fyo.pesa(0);
|
||||
}
|
||||
|
||||
return this.items.reduce(
|
||||
(acc, i) => acc.add(i.itemDiscountAmount ?? this.fyo.pesa(0)),
|
||||
this.fyo.pesa(0)
|
||||
);
|
||||
}
|
||||
|
||||
formulas: FormulaMap = {
|
||||
@ -200,6 +226,24 @@ export abstract class Invoice extends Transactional {
|
||||
formula: async () => this.netTotal!.mul(this.exchangeRate!),
|
||||
},
|
||||
taxes: { formula: async () => await this.getTaxSummary() },
|
||||
discountAmount: {
|
||||
formula: async (fieldname) => {
|
||||
if (fieldname !== 'discountPercent') {
|
||||
return this.discountAmount!;
|
||||
}
|
||||
|
||||
return this.getDiscountAmountFromPercent();
|
||||
},
|
||||
},
|
||||
discountPercent: {
|
||||
formula: async (fieldname) => {
|
||||
if (fieldname === 'discountAmount') {
|
||||
return this.getDiscountPercentFromAmount();
|
||||
}
|
||||
|
||||
return this.discountPercent!;
|
||||
},
|
||||
},
|
||||
grandTotal: { formula: async () => await this.getGrandTotal() },
|
||||
baseGrandTotal: {
|
||||
formula: async () => (this.grandTotal as Money).mul(this.exchangeRate!),
|
||||
@ -215,6 +259,35 @@ export abstract class Invoice extends Transactional {
|
||||
},
|
||||
};
|
||||
|
||||
getDiscountPercentFromAmount() {
|
||||
const discountAmount = this.discountAmount ?? this.fyo.pesa(0);
|
||||
const itemDiscountedAmounts = this.getItemDiscountedAmounts();
|
||||
return discountAmount.mul(100).div(itemDiscountedAmounts).float;
|
||||
}
|
||||
|
||||
getDiscountAmountFromPercent() {
|
||||
const discountPercent = this.discountPercent ?? 0;
|
||||
const itemDiscountedAmounts = this.getItemDiscountedAmounts();
|
||||
return itemDiscountedAmounts.percent(discountPercent);
|
||||
}
|
||||
|
||||
getItemDiscountedAmounts() {
|
||||
let itemDiscountedAmounts = this.fyo.pesa(0);
|
||||
for (const item of this.items ?? []) {
|
||||
itemDiscountedAmounts = itemDiscountedAmounts.add(
|
||||
item.itemDiscountedTotal ?? item.amount!
|
||||
);
|
||||
}
|
||||
return itemDiscountedAmounts;
|
||||
}
|
||||
|
||||
hidden: HiddenMap = {
|
||||
setDiscountAmount: () => !this.enableDiscounting,
|
||||
discountAmount: () => !(this.enableDiscounting && !!this.setDiscountAmount),
|
||||
discountPercent: () => !(this.enableDiscounting && !this.setDiscountAmount),
|
||||
discountAfterTax: () => !this.enableDiscounting,
|
||||
};
|
||||
|
||||
static defaults: DefaultMap = {
|
||||
date: () => new Date().toISOString().slice(0, 10),
|
||||
};
|
||||
|
@ -16,14 +16,16 @@ export abstract class InvoiceItem extends Doc {
|
||||
amount?: Money;
|
||||
baseAmount?: Money;
|
||||
exchangeRate?: number;
|
||||
itemDiscountPercent?: number;
|
||||
itemDiscountAmount?: Money;
|
||||
parentdoc?: Invoice;
|
||||
rate?: Money;
|
||||
quantity?: number;
|
||||
tax?: string;
|
||||
itemTaxedTotal?: Money;
|
||||
|
||||
setItemDiscountAmount?: boolean;
|
||||
itemDiscountAmount?: Money;
|
||||
itemDiscountPercent?: number;
|
||||
itemDiscountedTotal?: Money;
|
||||
itemTaxedTotal?: Money;
|
||||
|
||||
get isSales() {
|
||||
return this.schemaName === 'SalesInvoiceItem';
|
||||
@ -33,6 +35,10 @@ export abstract class InvoiceItem extends Doc {
|
||||
return !!this?.parentdoc?.discountAfterTax;
|
||||
}
|
||||
|
||||
get enableDiscounting() {
|
||||
return !!this.fyo.singles?.AccountingSettings?.enableDiscounting;
|
||||
}
|
||||
|
||||
async getTotalTaxRate(): Promise<number> {
|
||||
if (!this.tax) {
|
||||
return 0;
|
||||
@ -313,6 +319,11 @@ export abstract class InvoiceItem extends Doc {
|
||||
(this.itemDiscountAmount?.isZero() || this.itemDiscountPercent === 0)
|
||||
);
|
||||
},
|
||||
setItemDiscountAmount: () => !this.enableDiscounting,
|
||||
itemDiscountAmount: () =>
|
||||
!(this.enableDiscounting && !!this.setItemDiscountAmount),
|
||||
itemDiscountPercent: () =>
|
||||
!(this.enableDiscounting && !this.setItemDiscountAmount),
|
||||
};
|
||||
|
||||
static filters: FiltersMap = {
|
||||
|
@ -36,7 +36,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "fullname",
|
||||
"label": "Name",
|
||||
"label": "Full Name",
|
||||
"fieldtype": "Data",
|
||||
"required": true
|
||||
},
|
||||
@ -65,6 +65,12 @@
|
||||
"fieldtype": "Date",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"fieldname": "enableDiscounting",
|
||||
"label": "Enable Discounting",
|
||||
"fieldtype": "Check",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"fieldname": "setupComplete",
|
||||
"label": "Setup Complete",
|
||||
|
@ -93,6 +93,12 @@
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "setDiscountAmount",
|
||||
"label": "Set Discount Amount",
|
||||
"fieldtype": "Check",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"fieldname": "discountAmount",
|
||||
"label": "Discount Amount",
|
||||
|
@ -64,6 +64,12 @@
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "setItemDiscountAmount",
|
||||
"label": "Set Discount Amount",
|
||||
"fieldtype": "Check",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"fieldname": "itemDiscountAmount",
|
||||
"label": "Discount Amount",
|
||||
@ -109,6 +115,7 @@
|
||||
"quantity",
|
||||
"rate",
|
||||
"amount",
|
||||
"setItemDiscountAmount",
|
||||
"itemDiscountAmount",
|
||||
"itemDiscountPercent",
|
||||
"itemDiscountedTotal",
|
||||
|
@ -92,6 +92,12 @@
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "setDiscountAmount",
|
||||
"label": "Set Discount Amount",
|
||||
"fieldtype": "Check",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"fieldname": "discountAmount",
|
||||
"label": "Discount Amount",
|
||||
|
@ -61,6 +61,12 @@
|
||||
"fieldtype": "Currency",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "setItemDiscountAmount",
|
||||
"label": "Set Discount Amount",
|
||||
"fieldtype": "Check",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"fieldname": "itemDiscountAmount",
|
||||
"label": "Discount Amount",
|
||||
@ -105,6 +111,7 @@
|
||||
"quantity",
|
||||
"rate",
|
||||
"amount",
|
||||
"setItemDiscountAmount",
|
||||
"itemDiscountAmount",
|
||||
"itemDiscountPercent",
|
||||
"itemDiscountedTotal",
|
||||
|
@ -62,7 +62,7 @@ export default {
|
||||
'px-3 py-2': this.size !== 'small',
|
||||
'px-2 py-1': this.size === 'small',
|
||||
},
|
||||
'focus:outline-none focus:bg-gray-200 rounded w-full placeholder-gray-400',
|
||||
'focus:outline-none focus:bg-gray-200 rounded w-full placeholder-gray-500',
|
||||
this.isReadOnly ? 'text-gray-800' : 'text-gray-900',
|
||||
];
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
:class="['resize-none', inputClasses]"
|
||||
:value="value"
|
||||
:placeholder="inputPlaceholder"
|
||||
style="vertical-align: top"
|
||||
@blur="(e) => triggerChange(e.target.value)"
|
||||
@focus="(e) => $emit('focus', e)"
|
||||
@input="(e) => $emit('input', e)"
|
||||
|
@ -32,7 +32,7 @@
|
||||
text-2xl
|
||||
focus:outline-none
|
||||
w-full
|
||||
placeholder-gray-700
|
||||
placeholder-gray-500
|
||||
text-gray-900
|
||||
rounded-md
|
||||
p-3
|
||||
|
@ -95,7 +95,7 @@
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="doc.discountPercent > 0"
|
||||
v-if="doc.discountPercent > 0 && !doc.setDiscountAmount"
|
||||
class="text-base bg-gray-100 rounded"
|
||||
input-class="px-3 py-2 text-base bg-transparent text-right"
|
||||
:df="getField('discountPercent')"
|
||||
@ -103,6 +103,15 @@
|
||||
@change="(value) => doc.set('discountPercent', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
<FormControl
|
||||
v-if="doc.discountAmount.float > 0 && doc.setDiscountAmount"
|
||||
class="text-base bg-gray-100 rounded"
|
||||
input-class="px-3 py-2 text-base bg-transparent text-right"
|
||||
:df="getField('discountAmount')"
|
||||
:value="doc.discountAmount"
|
||||
@change="(value) => doc.set('discountAmount', value)"
|
||||
:read-only="doc?.submitted"
|
||||
/>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
@ -357,12 +366,7 @@ export default {
|
||||
return this.doc?.discountAmount ?? fyo.pesa(0);
|
||||
},
|
||||
itemDiscountAmount() {
|
||||
const itemDiscountAmount = (this.doc?.items ?? []).reduce(
|
||||
(acc, i) => acc.add(i.itemDiscountAmount),
|
||||
fyo.pesa(0)
|
||||
);
|
||||
|
||||
return itemDiscountAmount;
|
||||
return this.doc.getItemDiscountAmount();
|
||||
},
|
||||
},
|
||||
activated() {
|
||||
@ -402,6 +406,7 @@ export default {
|
||||
|
||||
const fields = [
|
||||
'discountAfterTax',
|
||||
'setDiscountAmount',
|
||||
'discountAmount',
|
||||
'discountPercent',
|
||||
].map((fn) => fyo.getField(this.schemaName, fn));
|
||||
|
@ -95,7 +95,8 @@ export default {
|
||||
if (
|
||||
fieldnames.includes('displayPrecision') ||
|
||||
fieldnames.includes('hideGetStarted') ||
|
||||
fieldnames.includes('displayPrecision')
|
||||
fieldnames.includes('displayPrecision') ||
|
||||
fieldnames.includes('enableDiscounting')
|
||||
) {
|
||||
this.showReloadToast();
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ export default {
|
||||
computed: {
|
||||
fields() {
|
||||
return [
|
||||
'fullname',
|
||||
'companyName',
|
||||
'country',
|
||||
'bankName',
|
||||
@ -46,6 +47,7 @@ export default {
|
||||
'roundOffAccount',
|
||||
'fiscalYearStart',
|
||||
'fiscalYearEnd',
|
||||
'enableDiscounting',
|
||||
'gstin',
|
||||
].map((fieldname) => fyo.getField('AccountingSettings', fieldname));
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user