diff --git a/models/doctype/Payment/Payment.js b/models/doctype/Payment/Payment.js index 503dd7a2..35cc16fb 100644 --- a/models/doctype/Payment/Payment.js +++ b/models/doctype/Payment/Payment.js @@ -38,6 +38,11 @@ export default { } } }, + formula: (doc) => { + if (doc.paymentMethod === 'Cash' && doc.paymentType === 'Pay') { + return 'Cash'; + } + }, }, { fieldname: 'paymentType', @@ -63,7 +68,7 @@ export default { } }, formula: (doc) => { - if (doc.paymentMethod === 'Cash') { + if (doc.paymentMethod === 'Cash' && doc.paymentType === 'Receive') { return 'Cash'; } }, @@ -107,7 +112,7 @@ export default { if (value < 0) { throw new frappe.errors.ValidationError( frappe._( - `Payment amount cannot be less than zero. Amount has been reset to max viable amount.` + `Payment amount cannot be less than zero. Amount has been reset.` ) ); } @@ -121,7 +126,7 @@ export default { `Payment amount cannot exceed ${frappe.format( amount, 'Currency' - )}. Amount has been reset to max viable amount.` + )}. Amount has been reset.` ) ); } else if (value === 0) { @@ -130,7 +135,7 @@ export default { `Payment amount cannot be ${frappe.format( value, 'Currency' - )}. Amount has been reset to max viable amount.` + )}. Amount has been reset.` ) ); } @@ -140,6 +145,7 @@ export default { fieldname: 'writeoff', label: 'Write Off / Refund', fieldtype: 'Currency', + default: 0, }, { fieldname: 'for', diff --git a/models/doctype/Payment/PaymentServer.js b/models/doctype/Payment/PaymentServer.js index 7ffe4aaf..55025a91 100644 --- a/models/doctype/Payment/PaymentServer.js +++ b/models/doctype/Payment/PaymentServer.js @@ -1,14 +1,91 @@ -import BaseDocument from 'frappejs/model/document'; import frappe from 'frappejs'; +import BaseDocument from 'frappejs/model/document'; import LedgerPosting from '../../../accounting/ledgerPosting'; export default class PaymentServer extends BaseDocument { async change({ changed }) { - if (changed === 'for') { - this.amount = 0; - for (let paymentReference of this.for) { - this.amount += paymentReference.amount; + switch (changed) { + case 'for': { + this.updateAmountOnReferenceUpdate(); + await this.updateDetailsOnReferenceUpdate(); } + case 'amount': { + this.updateReferenceOnAmountUpdate(); + } + } + } + + async updateDetailsOnReferenceUpdate() { + const { referenceType, referenceName } = this.for[0]; + if ( + this.for?.length !== 1 || + this.party || + this.paymentType || + !referenceName || + !referenceType + ) { + return; + } + + const doctype = referenceType; + const doc = await frappe.getDoc(doctype, referenceName); + + let party; + let paymentType; + + if (doctype === 'SalesInvoice') { + party = doc.customer; + paymentType = 'Receive'; + } else if (doctype === 'PurchaseInvoice') { + party = doc.supplier; + paymentType = 'Pay'; + } + + this.party = party; + this.paymentType = paymentType; + } + + updateAmountOnReferenceUpdate() { + this.amount = 0; + for (let paymentReference of this.for) { + this.amount += paymentReference.amount; + } + } + + updateReferenceOnAmountUpdate() { + if (this.for?.length !== 1) return; + this.for[0].amount = this.amount; + } + + async validate() { + this.validateAccounts(); + this.validateReferenceAmount(); + } + + validateAccounts() { + if (this.paymentAccount !== this.account || !this.account) return; + throw new Error( + `To Account and From Account can't be the same: ${this.account}` + ); + } + + validateReferenceAmount() { + const referenceAmountTotal = this.for + .map(({ amount }) => amount) + .reduce((a, b) => a + b); + + if (this.amount + (this.writeoff ?? 0) < referenceAmountTotal) { + const writeoff = frappe.format(this.writeoff, 'Currency'); + const payment = frappe.format(this.amount, 'Currency'); + const refAmount = frappe.format(referenceAmountTotal, 'Currency'); + const writeoffString = + this.writeoff > 0 ? `and writeoff: ${writeoff} ` : ''; + + throw new Error( + frappe._( + `Amount: ${payment} ${writeoffString}is less than the total amount allocated to references: ${refAmount}` + ) + ); } } @@ -55,15 +132,16 @@ export default class PaymentServer extends BaseDocument { } } - async afterSubmit() { - const entries = await this.getPosting(); - await entries.post(); - } - async afterRevert() { + this.updateReferenceOutstandingAmount(); const entries = await this.getPosting(); await entries.postReverse(); + } - // Maybe revert outstanding amount of invoice too? + async updateReferenceOutstandingAmount() { + await this.for.forEach(async ({ amount, referenceType, referenceName }) => { + const refDoc = await frappe.getDoc(referenceType, referenceName); + refDoc.update({ outstandingAmount: refDoc.outstandingAmount + amount }); + }); } } diff --git a/models/doctype/PaymentFor/PaymentFor.js b/models/doctype/PaymentFor/PaymentFor.js index 33015b05..fb509e65 100644 --- a/models/doctype/PaymentFor/PaymentFor.js +++ b/models/doctype/PaymentFor/PaymentFor.js @@ -4,34 +4,40 @@ export default { isSingle: 0, isChild: 1, keywordFields: [], - tableFields: [ - 'referenceType', - 'referenceName', - 'amount' - ], + tableFields: ['referenceType', 'referenceName', 'amount'], fields: [ { fieldname: 'referenceType', label: 'Reference Type', - fieldtype: 'AutoComplete', + fieldtype: 'Select', options: ['SalesInvoice', 'PurchaseInvoice'], - required: 1 + map: { SalesInvoice: 'Invoice', PurchaseInvoice: 'Bill' }, + required: 1, }, { fieldname: 'referenceName', label: 'Reference Name', fieldtype: 'DynamicLink', references: 'referenceType', - required: 1 + getFilters() { + return { + outstandingAmount: ['>', 0], + }; + }, + required: 1, }, { fieldname: 'amount', label: 'Amount', fieldtype: 'Currency', formula: (row, doc) => { - return doc.getFrom(row.referenceType, row.referenceName, 'outstandingAmount'); + return doc.getFrom( + row.referenceType, + row.referenceName, + 'outstandingAmount' + ); }, - required: 1 - } - ] + required: 1, + }, + ], }; diff --git a/src/components/Controls/Select.vue b/src/components/Controls/Select.vue index 83a341b6..71f75c58 100644 --- a/src/components/Controls/Select.vue +++ b/src/components/Controls/Select.vue @@ -4,15 +4,22 @@ {{ df.label }}