2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 03:19:01 +00:00

fix: multiple payment related fixes

- outstanding amount reset on payment cancel
- payment reference type is now select
- map property on select fields for different display values
- multiple payment validations
- sensible binding payment amount and reference amount
This commit is contained in:
18alantom 2021-11-25 14:49:50 +05:30
parent c3760431a1
commit b8671cb2ed
4 changed files with 139 additions and 34 deletions

View File

@ -38,6 +38,11 @@ export default {
} }
} }
}, },
formula: (doc) => {
if (doc.paymentMethod === 'Cash' && doc.paymentType === 'Pay') {
return 'Cash';
}
},
}, },
{ {
fieldname: 'paymentType', fieldname: 'paymentType',
@ -63,7 +68,7 @@ export default {
} }
}, },
formula: (doc) => { formula: (doc) => {
if (doc.paymentMethod === 'Cash') { if (doc.paymentMethod === 'Cash' && doc.paymentType === 'Receive') {
return 'Cash'; return 'Cash';
} }
}, },
@ -107,7 +112,7 @@ export default {
if (value < 0) { if (value < 0) {
throw new frappe.errors.ValidationError( throw new frappe.errors.ValidationError(
frappe._( 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( `Payment amount cannot exceed ${frappe.format(
amount, amount,
'Currency' 'Currency'
)}. Amount has been reset to max viable amount.` )}. Amount has been reset.`
) )
); );
} else if (value === 0) { } else if (value === 0) {
@ -130,7 +135,7 @@ export default {
`Payment amount cannot be ${frappe.format( `Payment amount cannot be ${frappe.format(
value, value,
'Currency' 'Currency'
)}. Amount has been reset to max viable amount.` )}. Amount has been reset.`
) )
); );
} }
@ -140,6 +145,7 @@ export default {
fieldname: 'writeoff', fieldname: 'writeoff',
label: 'Write Off / Refund', label: 'Write Off / Refund',
fieldtype: 'Currency', fieldtype: 'Currency',
default: 0,
}, },
{ {
fieldname: 'for', fieldname: 'for',

View File

@ -1,14 +1,91 @@
import BaseDocument from 'frappejs/model/document';
import frappe from 'frappejs'; import frappe from 'frappejs';
import BaseDocument from 'frappejs/model/document';
import LedgerPosting from '../../../accounting/ledgerPosting'; import LedgerPosting from '../../../accounting/ledgerPosting';
export default class PaymentServer extends BaseDocument { export default class PaymentServer extends BaseDocument {
async change({ changed }) { async change({ changed }) {
if (changed === 'for') { switch (changed) {
this.amount = 0; case 'for': {
for (let paymentReference of this.for) { this.updateAmountOnReferenceUpdate();
this.amount += paymentReference.amount; 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() { async afterRevert() {
this.updateReferenceOutstandingAmount();
const entries = await this.getPosting(); const entries = await this.getPosting();
await entries.postReverse(); 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 });
});
} }
} }

View File

@ -4,34 +4,40 @@ export default {
isSingle: 0, isSingle: 0,
isChild: 1, isChild: 1,
keywordFields: [], keywordFields: [],
tableFields: [ tableFields: ['referenceType', 'referenceName', 'amount'],
'referenceType',
'referenceName',
'amount'
],
fields: [ fields: [
{ {
fieldname: 'referenceType', fieldname: 'referenceType',
label: 'Reference Type', label: 'Reference Type',
fieldtype: 'AutoComplete', fieldtype: 'Select',
options: ['SalesInvoice', 'PurchaseInvoice'], options: ['SalesInvoice', 'PurchaseInvoice'],
required: 1 map: { SalesInvoice: 'Invoice', PurchaseInvoice: 'Bill' },
required: 1,
}, },
{ {
fieldname: 'referenceName', fieldname: 'referenceName',
label: 'Reference Name', label: 'Reference Name',
fieldtype: 'DynamicLink', fieldtype: 'DynamicLink',
references: 'referenceType', references: 'referenceType',
required: 1 getFilters() {
return {
outstandingAmount: ['>', 0],
};
},
required: 1,
}, },
{ {
fieldname: 'amount', fieldname: 'amount',
label: 'Amount', label: 'Amount',
fieldtype: 'Currency', fieldtype: 'Currency',
formula: (row, doc) => { formula: (row, doc) => {
return doc.getFrom(row.referenceType, row.referenceName, 'outstandingAmount'); return doc.getFrom(
row.referenceType,
row.referenceName,
'outstandingAmount'
);
}, },
required: 1 required: 1,
} },
] ],
}; };

View File

@ -4,15 +4,22 @@
{{ df.label }} {{ df.label }}
</div> </div>
<div <div
class="relative flex items-center justify-end bg-white focus-within:bg-gray-200" class="
relative
flex
items-center
justify-end
bg-white
focus-within:bg-gray-200
"
:class="inputClasses" :class="inputClasses"
> >
<select <select
class="appearance-none bg-transparent focus:outline-none w-full" class="appearance-none bg-transparent focus:outline-none w-full"
:class="isReadOnly && 'pointer-events-none'" :class="isReadOnly && 'pointer-events-none'"
:value="value" :value="value"
@change="e => triggerChange(e.target.value)" @change="(e) => triggerChange(e.target.value)"
@focus="e => $emit('focus', e)" @focus="(e) => $emit('focus', e)"
> >
<option <option
v-for="option in options" v-for="option in options"
@ -52,16 +59,24 @@ import Base from './Base';
export default { export default {
name: 'Select', name: 'Select',
extends: Base, extends: Base,
methods: {
map(v) {
if (this.df.map) {
return this.df.map[v] ?? v;
}
return v;
},
},
computed: { computed: {
options() { options() {
let options = this.df.options; let options = this.df.options;
return options.map(o => { return options.map((o) => {
if (typeof o === 'string') { if (typeof o === 'string') {
return { label: o, value: o }; return { label: this.map(o), value: o };
} }
return o; return o;
}); });
} },
} },
}; };
</script> </script>