mirror of
https://github.com/frappe/books.git
synced 2025-01-22 22:58:28 +00:00
incr: add computed, update row amount display vals
This commit is contained in:
parent
3dfdf22f9f
commit
eb66317dce
@ -610,7 +610,9 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
|
|
||||||
async #createTable(schemaName: string, tableName?: string) {
|
async #createTable(schemaName: string, tableName?: string) {
|
||||||
tableName ??= schemaName;
|
tableName ??= schemaName;
|
||||||
const fields = this.schemaMap[schemaName]!.fields;
|
const fields = this.schemaMap[schemaName]!.fields.filter(
|
||||||
|
(f) => !f.computed
|
||||||
|
);
|
||||||
return await this.#runCreateTableQuery(tableName, fields);
|
return await this.#runCreateTableQuery(tableName, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,18 +384,27 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
await validator(value);
|
await validator(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValidDict(filterMeta: boolean = false): DocValueMap {
|
getValidDict(
|
||||||
|
filterMeta: boolean = false,
|
||||||
|
filterComputed: boolean = false
|
||||||
|
): DocValueMap {
|
||||||
let fields = this.schema.fields;
|
let fields = this.schema.fields;
|
||||||
if (filterMeta) {
|
if (filterMeta) {
|
||||||
fields = this.schema.fields.filter((f) => !f.meta);
|
fields = this.schema.fields.filter((f) => !f.meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filterComputed) {
|
||||||
|
fields = this.schema.fields.filter((f) => !f.computed);
|
||||||
|
}
|
||||||
|
|
||||||
const data: DocValueMap = {};
|
const data: DocValueMap = {};
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
let value = this[field.fieldname] as DocValue | DocValueMap[];
|
let value = this[field.fieldname] as DocValue | DocValueMap[];
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
value = value.map((doc) => (doc as Doc).getValidDict(filterMeta));
|
value = value.map((doc) =>
|
||||||
|
(doc as Doc).getValidDict(filterMeta, filterComputed)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPesa(value)) {
|
if (isPesa(value)) {
|
||||||
@ -577,7 +586,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newVal = await this._getValueFromFormula(field, doc);
|
const newVal = await this._getValueFromFormula(field, doc, fieldname);
|
||||||
const previousVal = doc.get(field.fieldname);
|
const previousVal = doc.get(field.fieldname);
|
||||||
const isSame = areDocValuesEqual(newVal as DocValue, previousVal);
|
const isSame = areDocValuesEqual(newVal as DocValue, previousVal);
|
||||||
if (newVal === undefined || isSame) {
|
if (newVal === undefined || isSame) {
|
||||||
@ -591,7 +600,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _getValueFromFormula(field: Field, doc: Doc) {
|
async _getValueFromFormula(field: Field, doc: Doc, fieldname?: string) {
|
||||||
const { formula } = doc.formulas[field.fieldname] ?? {};
|
const { formula } = doc.formulas[field.fieldname] ?? {};
|
||||||
if (formula === undefined) {
|
if (formula === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -599,7 +608,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
|
|
||||||
let value: FormulaReturn;
|
let value: FormulaReturn;
|
||||||
try {
|
try {
|
||||||
value = await formula();
|
value = await formula(fieldname);
|
||||||
} catch {
|
} catch {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -623,7 +632,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
this._setBaseMetaValues();
|
this._setBaseMetaValues();
|
||||||
await this._preSync();
|
await this._preSync();
|
||||||
|
|
||||||
const validDict = this.getValidDict();
|
const validDict = this.getValidDict(false, true);
|
||||||
const data = await this.fyo.db.insert(this.schemaName, validDict);
|
const data = await this.fyo.db.insert(this.schemaName, validDict);
|
||||||
this._syncValues(data);
|
this._syncValues(data);
|
||||||
|
|
||||||
@ -636,7 +645,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
this._updateModifiedMetaValues();
|
this._updateModifiedMetaValues();
|
||||||
await this._preSync();
|
await this._preSync();
|
||||||
|
|
||||||
const data = this.getValidDict();
|
const data = this.getValidDict(false, true);
|
||||||
await this.fyo.db.update(this.schemaName, data);
|
await this.fyo.db.update(this.schemaName, data);
|
||||||
this._syncValues(data);
|
this._syncValues(data);
|
||||||
|
|
||||||
@ -750,7 +759,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
duplicate(): Doc {
|
duplicate(): Doc {
|
||||||
const updateMap = this.getValidDict(true);
|
const updateMap = this.getValidDict(true, true);
|
||||||
for (const field in updateMap) {
|
for (const field in updateMap) {
|
||||||
const value = updateMap[field];
|
const value = updateMap[field];
|
||||||
if (!Array.isArray(value)) {
|
if (!Array.isArray(value)) {
|
||||||
|
@ -18,7 +18,7 @@ import { Doc } from './doc';
|
|||||||
* - `Required`: Regular function used to decide if a value is mandatory (there are !notnul in the db).
|
* - `Required`: Regular function used to decide if a value is mandatory (there are !notnul in the db).
|
||||||
*/
|
*/
|
||||||
export type FormulaReturn = DocValue | DocValueMap[] | undefined | Doc[];
|
export type FormulaReturn = DocValue | DocValueMap[] | undefined | Doc[];
|
||||||
export type Formula = () => Promise<FormulaReturn> | FormulaReturn;
|
export type Formula = (fieldname?: string) => Promise<FormulaReturn> | FormulaReturn;
|
||||||
export type FormulaConfig = { dependsOn?: string[]; formula: Formula };
|
export type FormulaConfig = { dependsOn?: string[]; formula: Formula };
|
||||||
export type Default = () => DocValue;
|
export type Default = () => DocValue;
|
||||||
export type Validation = (value: DocValue) => Promise<void> | void;
|
export type Validation = (value: DocValue) => Promise<void> | void;
|
||||||
|
@ -11,12 +11,33 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
amount?: Money;
|
amount?: Money;
|
||||||
baseAmount?: Money;
|
baseAmount?: Money;
|
||||||
exchangeRate?: number;
|
exchangeRate?: number;
|
||||||
|
itemDiscountPercent?: number;
|
||||||
|
itemDiscountAmount?: Money;
|
||||||
parentdoc?: Invoice;
|
parentdoc?: Invoice;
|
||||||
|
rate?: Money;
|
||||||
|
quantity?: number;
|
||||||
|
tax?: string;
|
||||||
|
|
||||||
get isSales() {
|
get isSales() {
|
||||||
return this.schemaName === 'SalesInvoiceItem';
|
return this.schemaName === 'SalesInvoiceItem';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get discountAfterTax() {
|
||||||
|
return !!this?.parentdoc?.discountAfterTax;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTotalTaxRate(): Promise<number> {
|
||||||
|
if (!this.tax) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const details =
|
||||||
|
((await this.fyo.getValue('Tax', this.tax, 'details')) as Doc[]) ?? [];
|
||||||
|
return details.reduce((acc, doc) => {
|
||||||
|
return (doc.rate as number) + acc;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
formulas: FormulaMap = {
|
formulas: FormulaMap = {
|
||||||
description: {
|
description: {
|
||||||
formula: async () =>
|
formula: async () =>
|
||||||
@ -27,6 +48,102 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
)) as string,
|
)) as string,
|
||||||
dependsOn: ['item'],
|
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: {
|
rate: {
|
||||||
formula: async () => {
|
formula: async () => {
|
||||||
const rate = (await this.fyo.getValue(
|
const rate = (await this.fyo.getValue(
|
||||||
@ -142,3 +259,88 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDiscountedTotalBeforeTaxation(
|
||||||
|
rate: Money,
|
||||||
|
quantity: number,
|
||||||
|
itemDiscountAmount: Money,
|
||||||
|
itemDiscountPercent: number,
|
||||||
|
fieldname?: string
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* If Discount is applied before taxation
|
||||||
|
* Use different formulas depending on how discount is set
|
||||||
|
* - if amount : Quantity * Rate - DiscountAmount
|
||||||
|
* - if percent: Quantity * Rate (1 - DiscountPercent / 100)
|
||||||
|
*/
|
||||||
|
const amount = rate.mul(quantity);
|
||||||
|
if (fieldname === 'itemDiscountAmount') {
|
||||||
|
return amount.sub(itemDiscountAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return amount.mul(1 - itemDiscountPercent / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTaxedTotalAfterDiscounting(
|
||||||
|
totalTaxRate: number,
|
||||||
|
rate: Money,
|
||||||
|
quantity: number,
|
||||||
|
itemDiscountAmount: Money,
|
||||||
|
itemDiscountPercent: number,
|
||||||
|
fieldname?: string
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* If Discount is applied before taxation
|
||||||
|
* Formula: Discounted Total * (1 + TotalTaxRate / 100)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const discountedTotal = getDiscountedTotalBeforeTaxation(
|
||||||
|
rate,
|
||||||
|
quantity,
|
||||||
|
itemDiscountAmount,
|
||||||
|
itemDiscountPercent,
|
||||||
|
fieldname
|
||||||
|
);
|
||||||
|
|
||||||
|
return discountedTotal.mul(1 + totalTaxRate / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDiscountedTotalAfterTaxation(
|
||||||
|
totalTaxRate: number,
|
||||||
|
rate: Money,
|
||||||
|
quantity: number,
|
||||||
|
itemDiscountAmount: Money,
|
||||||
|
itemDiscountPercent: number,
|
||||||
|
fieldname?: string
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* If Discount is applied after taxation
|
||||||
|
* Use different formulas depending on how discount is set
|
||||||
|
* - if amount : Taxed Total - Discount Amount
|
||||||
|
* - if percent: Taxed Total * (1 - Discount Percent / 100)
|
||||||
|
*/
|
||||||
|
const taxedTotal = getTaxedTotalBeforeDiscounting(
|
||||||
|
totalTaxRate,
|
||||||
|
rate,
|
||||||
|
quantity
|
||||||
|
);
|
||||||
|
|
||||||
|
if (fieldname === 'itemDiscountAmount') {
|
||||||
|
return taxedTotal.sub(itemDiscountAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return taxedTotal.mul(1 - itemDiscountPercent / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTaxedTotalBeforeDiscounting(
|
||||||
|
totalTaxRate: number,
|
||||||
|
rate: Money,
|
||||||
|
quantity: number
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* If Discount is applied after taxation
|
||||||
|
* Formula: Rate * Quantity * (1 + Total Tax Rate / 100)
|
||||||
|
*/
|
||||||
|
|
||||||
|
return rate.mul(quantity).mul(1 + totalTaxRate / 100);
|
||||||
|
}
|
||||||
|
@ -66,16 +66,30 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "itemDiscountAmount",
|
"fieldname": "itemDiscountAmount",
|
||||||
"label": "Item Discount Amount",
|
"label": "Discount Amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "itemDiscountPercent",
|
"fieldname": "itemDiscountPercent",
|
||||||
"label": "Item Discount Percent",
|
"label": "Discount Percent",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "itemDiscountedTotal",
|
||||||
|
"label": "Discounted Total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"readOnly": false,
|
||||||
|
"computed": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "itemTaxedTotal",
|
||||||
|
"label": "Taxed Total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"readOnly": false,
|
||||||
|
"computed": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "hsnCode",
|
"fieldname": "hsnCode",
|
||||||
"label": "HSN/SAC",
|
"label": "HSN/SAC",
|
||||||
@ -88,12 +102,16 @@
|
|||||||
"keywordFields": ["item", "tax"],
|
"keywordFields": ["item", "tax"],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
"item",
|
"item",
|
||||||
|
"account",
|
||||||
"description",
|
"description",
|
||||||
"hsnCode",
|
"hsnCode",
|
||||||
"tax",
|
"tax",
|
||||||
"quantity",
|
"quantity",
|
||||||
"rate",
|
"rate",
|
||||||
|
"amount",
|
||||||
"itemDiscountAmount",
|
"itemDiscountAmount",
|
||||||
"itemDiscountPercent"
|
"itemDiscountPercent",
|
||||||
|
"itemDiscountedTotal",
|
||||||
|
"itemTaxedTotal"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text"
|
||||||
"hidden": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "quantity",
|
"fieldname": "quantity",
|
||||||
@ -39,12 +38,9 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
"label": "Account",
|
"label": "Account",
|
||||||
"hidden": true,
|
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "Account",
|
"target": "Account",
|
||||||
"create": true,
|
"required": true
|
||||||
"required": true,
|
|
||||||
"readOnly": true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "tax",
|
"fieldname": "tax",
|
||||||
@ -67,34 +63,51 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "itemDiscountAmount",
|
"fieldname": "itemDiscountAmount",
|
||||||
"label": "Item Discount Amount",
|
"label": "Discount Amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "itemDiscountPercent",
|
"fieldname": "itemDiscountPercent",
|
||||||
"label": "Item Discount Percent",
|
"label": "Discount Percent",
|
||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"readOnly": false
|
"readOnly": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "itemDiscountedTotal",
|
||||||
|
"label": "Discounted Total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"readOnly": false,
|
||||||
|
"computed": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "itemTaxedTotal",
|
||||||
|
"label": "Taxed Total",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"readOnly": false,
|
||||||
|
"computed": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "hsnCode",
|
"fieldname": "hsnCode",
|
||||||
"label": "HSN/SAC",
|
"label": "HSN/SAC",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"placeholder": "HSN/SAC Code",
|
"placeholder": "HSN/SAC Code"
|
||||||
"hidden": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tableFields": ["item", "tax", "quantity", "rate", "amount"],
|
"tableFields": ["item", "tax", "quantity", "rate", "amount"],
|
||||||
"keywordFields": ["item", "tax"],
|
"keywordFields": ["item", "tax"],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
"item",
|
"item",
|
||||||
|
"account",
|
||||||
"description",
|
"description",
|
||||||
"hsnCode",
|
"hsnCode",
|
||||||
"tax",
|
"tax",
|
||||||
"quantity",
|
"quantity",
|
||||||
"rate",
|
"rate",
|
||||||
|
"amount",
|
||||||
"itemDiscountAmount",
|
"itemDiscountAmount",
|
||||||
"itemDiscountPercent"
|
"itemDiscountPercent",
|
||||||
|
"itemDiscountedTotal",
|
||||||
|
"itemTaxedTotal"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ export interface BaseField {
|
|||||||
groupBy?: string; // UI Facing used in dropdowns fields
|
groupBy?: string; // UI Facing used in dropdowns fields
|
||||||
meta?: boolean; // Field is a meta field, i.e. only for the db, not UI
|
meta?: boolean; // Field is a meta field, i.e. only for the db, not UI
|
||||||
inline?: boolean; // UI Facing config, whether to display doc inline.
|
inline?: boolean; // UI Facing config, whether to display doc inline.
|
||||||
|
computed?: boolean; // Computed values are not stored in the database.
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectOption = { value: string; label: string };
|
export type SelectOption = { value: string; label: string };
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
:background="false"
|
:background="false"
|
||||||
@click="openRowQuickEdit"
|
@click="openRowQuickEdit"
|
||||||
v-if="canEditRow"
|
v-if="canEditRow"
|
||||||
|
:disabled="isEditing"
|
||||||
>
|
>
|
||||||
<feather-icon name="edit" class="w-4 h-4 text-gray-600" />
|
<feather-icon name="edit" class="w-4 h-4 text-gray-600" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -97,6 +98,9 @@ export default {
|
|||||||
doc: this.row,
|
doc: this.row,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
inject: {
|
||||||
|
isEditing: { default: false },
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange(df, value) {
|
onChange(df, value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="flex items-stretch window-no-drag gap-2 ml-auto">
|
<div class="flex items-stretch window-no-drag gap-2 ml-auto">
|
||||||
<slot />
|
<slot />
|
||||||
|
<div class="border-r" v-if="showBorder" />
|
||||||
<BackLink v-if="backLink" class="window-no-drag" />
|
<BackLink v-if="backLink" class="window-no-drag" />
|
||||||
<SearchBar v-if="!hideSearch" />
|
<SearchBar v-if="!hideSearch" />
|
||||||
</div>
|
</div>
|
||||||
@ -33,5 +34,10 @@ export default {
|
|||||||
border: { type: Boolean, default: true },
|
border: { type: Boolean, default: true },
|
||||||
},
|
},
|
||||||
components: { SearchBar, BackLink },
|
components: { SearchBar, BackLink },
|
||||||
|
computed: {
|
||||||
|
showBorder() {
|
||||||
|
return !!this.$slots.default;
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<keep-alive>
|
<keep-alive>
|
||||||
<component
|
<component
|
||||||
:is="Component"
|
:is="Component"
|
||||||
class="w-80 flex-1"
|
class="w-quick-edit flex-1"
|
||||||
:key="$route.query.schemaName + $route.query.name"
|
:key="$route.query.schemaName + $route.query.name"
|
||||||
/>
|
/>
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
|
@ -10,6 +10,13 @@
|
|||||||
>
|
>
|
||||||
{{ t`Print` }}
|
{{ t`Print` }}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
:icon="true"
|
||||||
|
v-if="!doc?.isSubmitted && !quickEditDoc"
|
||||||
|
@click="toggleInvoiceSettings"
|
||||||
|
>
|
||||||
|
<feather-icon name="settings" class="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
<DropdownWithActions :actions="actions()" />
|
<DropdownWithActions :actions="actions()" />
|
||||||
<Button
|
<Button
|
||||||
v-if="doc?.notInserted || doc?.dirty"
|
v-if="doc?.notInserted || doc?.dirty"
|
||||||
@ -60,7 +67,6 @@
|
|||||||
input-class="text-lg font-semibold bg-transparent"
|
input-class="text-lg font-semibold bg-transparent"
|
||||||
:df="getField('party')"
|
:df="getField('party')"
|
||||||
:value="doc.party"
|
:value="doc.party"
|
||||||
:placeholder="getField('party').label"
|
|
||||||
@change="(value) => doc.set('party', value)"
|
@change="(value) => doc.set('party', value)"
|
||||||
@new-doc="(party) => doc.set('party', party.name)"
|
@new-doc="(party) => doc.set('party', party.name)"
|
||||||
:read-only="doc?.submitted"
|
:read-only="doc?.submitted"
|
||||||
@ -69,7 +75,6 @@
|
|||||||
input-class="bg-gray-100 px-3 py-2 text-base text-right"
|
input-class="bg-gray-100 px-3 py-2 text-base text-right"
|
||||||
:df="getField('date')"
|
:df="getField('date')"
|
||||||
:value="doc.date"
|
:value="doc.date"
|
||||||
:placeholder="'Date'"
|
|
||||||
@change="(value) => doc.set('date', value)"
|
@change="(value) => doc.set('date', value)"
|
||||||
:read-only="doc?.submitted"
|
:read-only="doc?.submitted"
|
||||||
/>
|
/>
|
||||||
@ -86,10 +91,18 @@
|
|||||||
input-class="px-3 py-2 text-base bg-transparent"
|
input-class="px-3 py-2 text-base bg-transparent"
|
||||||
:df="getField('account')"
|
:df="getField('account')"
|
||||||
:value="doc.account"
|
:value="doc.account"
|
||||||
:placeholder="'Account'"
|
|
||||||
@change="(value) => doc.set('account', value)"
|
@change="(value) => doc.set('account', value)"
|
||||||
:read-only="doc?.submitted"
|
:read-only="doc?.submitted"
|
||||||
/>
|
/>
|
||||||
|
<FormControl
|
||||||
|
v-if="doc.discountPercent > 0"
|
||||||
|
class="text-base bg-gray-100 rounded"
|
||||||
|
input-class="px-3 py-2 text-base bg-transparent text-right"
|
||||||
|
:df="getField('discountPercent')"
|
||||||
|
:value="doc.discountPercent"
|
||||||
|
@change="(value) => doc.set('discountPercent', value)"
|
||||||
|
:read-only="doc?.submitted"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
@ -101,7 +114,7 @@
|
|||||||
:showHeader="true"
|
:showHeader="true"
|
||||||
:max-rows-before-overflow="4"
|
:max-rows-before-overflow="4"
|
||||||
@change="(value) => doc.set('items', value)"
|
@change="(value) => doc.set('items', value)"
|
||||||
@editrow="(r) => (row = r)"
|
@editrow="toggleQuickEditDoc"
|
||||||
:read-only="doc?.submitted"
|
:read-only="doc?.submitted"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -177,14 +190,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #quickedit v-if="row">
|
<template #quickedit v-if="quickEditDoc">
|
||||||
<QuickEditForm
|
<QuickEditForm
|
||||||
class="w-80"
|
class="w-quick-edit"
|
||||||
:name="row.name"
|
:name="quickEditDoc.name"
|
||||||
:source-doc="row"
|
:show-name="false"
|
||||||
:schema-name="row.schemaName"
|
:show-save="false"
|
||||||
|
:source-doc="quickEditDoc"
|
||||||
|
:source-fields="quickEditFields"
|
||||||
|
:schema-name="quickEditDoc.schemaName"
|
||||||
|
:white="true"
|
||||||
:route-back="false"
|
:route-back="false"
|
||||||
@close="row = null"
|
:load-on-close="false"
|
||||||
|
@close="toggleQuickEditDoc(null)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</FormContainer>
|
</FormContainer>
|
||||||
@ -227,13 +245,15 @@ export default {
|
|||||||
schemaName: this.schemaName,
|
schemaName: this.schemaName,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
doc: computed(() => this.doc),
|
doc: computed(() => this.doc),
|
||||||
|
isEditing: computed(() => !!this.quickEditDoc),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
chstatus: false,
|
chstatus: false,
|
||||||
doc: null,
|
doc: null,
|
||||||
row: null,
|
quickEditDoc: null,
|
||||||
|
quickEditFields: [],
|
||||||
color: null,
|
color: null,
|
||||||
printSettings: null,
|
printSettings: null,
|
||||||
companyName: null,
|
companyName: null,
|
||||||
@ -281,6 +301,23 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
routeTo,
|
routeTo,
|
||||||
|
toggleInvoiceSettings() {
|
||||||
|
if (this.quickEditDoc || !this.schemaName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = [
|
||||||
|
'discountAfterTax',
|
||||||
|
'discountAmount',
|
||||||
|
'discountPercent',
|
||||||
|
].map((fn) => fyo.getField(this.schemaName, fn));
|
||||||
|
|
||||||
|
this.toggleQuickEditDoc(this.doc, fields);
|
||||||
|
},
|
||||||
|
toggleQuickEditDoc(doc, fields = []) {
|
||||||
|
this.quickEditDoc = doc;
|
||||||
|
this.quickEditFields = fields;
|
||||||
|
},
|
||||||
actions() {
|
actions() {
|
||||||
return getActionsForDocument(this.doc);
|
return getActionsForDocument(this.doc);
|
||||||
},
|
},
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Printview Customizer -->
|
<!-- Printview Customizer -->
|
||||||
<div class="border-l w-80" v-if="showCustomiser">
|
<div class="border-l w-quick-edit" v-if="showCustomiser">
|
||||||
<div
|
<div
|
||||||
class="px-4 flex items-center justify-between h-row-largest border-b"
|
class="px-4 flex items-center justify-between h-row-largest border-b"
|
||||||
>
|
>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<!-- Quick edit Tool bar -->
|
<!-- Quick edit Tool bar -->
|
||||||
<div
|
<div
|
||||||
class="flex items-center justify-between px-4 h-row-largest"
|
class="flex items-center justify-between px-4 h-row-largest"
|
||||||
:class="{ 'border-b': !isChild }"
|
:class="{ 'border-b': showName }"
|
||||||
>
|
>
|
||||||
<!-- Close Button and Status Text -->
|
<!-- Close Button and Status Text -->
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Actions, Badge and Status Change Buttons -->
|
<!-- Actions, Badge and Status Change Buttons -->
|
||||||
<div class="flex items-stretch gap-2" v-if="!isChild">
|
<div class="flex items-stretch gap-2" v-if="showSave">
|
||||||
<StatusBadge :status="status" />
|
<StatusBadge :status="status" />
|
||||||
<DropdownWithActions :actions="actions" />
|
<DropdownWithActions :actions="actions" />
|
||||||
<Button
|
<Button
|
||||||
@ -52,7 +52,7 @@
|
|||||||
<div
|
<div
|
||||||
class="px-4 flex-center flex flex-col items-center gap-1.5"
|
class="px-4 flex-center flex flex-col items-center gap-1.5"
|
||||||
style="height: calc(var(--h-row-mid) * 2 + 1px)"
|
style="height: calc(var(--h-row-mid) * 2 + 1px)"
|
||||||
v-if="doc && !isChild"
|
v-if="doc && showName"
|
||||||
>
|
>
|
||||||
<FormControl
|
<FormControl
|
||||||
v-if="imageField"
|
v-if="imageField"
|
||||||
@ -110,8 +110,12 @@ export default {
|
|||||||
schemaName: String,
|
schemaName: String,
|
||||||
defaults: String,
|
defaults: String,
|
||||||
white: { type: Boolean, default: false },
|
white: { type: Boolean, default: false },
|
||||||
sourceDoc: { type: Doc, default: null },
|
|
||||||
routeBack: { type: Boolean, default: true },
|
routeBack: { type: Boolean, default: true },
|
||||||
|
showName: { type: Boolean, default: true },
|
||||||
|
showSave: { type: Boolean, default: true },
|
||||||
|
sourceDoc: { type: Doc, default: null },
|
||||||
|
loadOnClose: { type: Boolean, default: true },
|
||||||
|
sourceFields: { type: Array, default: () => [] },
|
||||||
hideFields: { type: Array, default: () => [] },
|
hideFields: { type: Array, default: () => [] },
|
||||||
showFields: { type: Array, default: () => [] },
|
showFields: { type: Array, default: () => [] },
|
||||||
},
|
},
|
||||||
@ -162,6 +166,10 @@ export default {
|
|||||||
return getDocStatus(this.doc);
|
return getDocStatus(this.doc);
|
||||||
},
|
},
|
||||||
fields() {
|
fields() {
|
||||||
|
if (this.sourceFields?.length) {
|
||||||
|
return this.sourceFields;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.schema) {
|
if (!this.schema) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -294,7 +302,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
routeToPrevious() {
|
routeToPrevious() {
|
||||||
if (this.doc.dirty && !this.doc.notInserted) {
|
if (this.loadOnClose && this.doc.dirty && !this.doc.notInserted) {
|
||||||
this.doc.load();
|
this.doc.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ html {
|
|||||||
--w-sidebar: 12rem;
|
--w-sidebar: 12rem;
|
||||||
--w-desk: calc(100vw - var(--w-sidebar));
|
--w-desk: calc(100vw - var(--w-sidebar));
|
||||||
--w-desk-fixed: calc(var(--w-app) - var(--w-sidebar));
|
--w-desk-fixed: calc(var(--w-app) - var(--w-sidebar));
|
||||||
|
--w-quick-edit: 22rem;
|
||||||
--w-scrollbar: 0.5rem;
|
--w-scrollbar: 0.5rem;
|
||||||
|
|
||||||
/* Row Heights */
|
/* Row Heights */
|
||||||
@ -73,6 +74,10 @@ html {
|
|||||||
width: 600px;
|
width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.w-quick-edit {
|
||||||
|
width: var(--w-quick-edit)
|
||||||
|
}
|
||||||
|
|
||||||
.h-form {
|
.h-form {
|
||||||
height: 800px;
|
height: 800px;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user