2
0
mirror of https://github.com/frappe/books.git synced 2024-09-20 03:29:00 +00:00

fix: Commonify Transaction code

for SalesInvoice and PurchaseInvoice
This commit is contained in:
Faris Ansari 2019-12-04 22:51:31 +05:30
parent 4e0af18983
commit 4904632549
16 changed files with 312 additions and 435 deletions

View File

@ -1,10 +1,8 @@
let router = require('@/router').default;
module.exports = { module.exports = {
ledgerLink: { ledgerLink: {
label: 'Ledger Entries', label: 'Ledger Entries',
condition: doc => doc.submitted, condition: doc => doc.submitted,
action: doc => { action: (doc, router) => {
router.push({ router.push({
name: 'Report', name: 'Report',
params: { params: {

View File

@ -112,9 +112,9 @@ module.exports = {
quickEditFields: [ quickEditFields: [
'party', 'party',
'date', 'date',
'paymentMethod',
'account', 'account',
'paymentType', 'paymentType',
'paymentMethod',
'paymentAccount', 'paymentAccount',
'referenceId', 'referenceId',
'referenceDate', 'referenceDate',

View File

@ -22,47 +22,31 @@ module.exports = class PaymentServer extends BaseDocument {
} }
async beforeSubmit() { async beforeSubmit() {
if (this.for.length > 0) { if (!this.for.length) {
for (let row of this.for) {
if (['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
let { outstandingAmount, grandTotal } = await frappe.getDoc(
row.referenceType,
row.referenceName
);
if (outstandingAmount === null) {
outstandingAmount = grandTotal;
}
if (this.amount <= 0 || this.amount > outstandingAmount) {
// frappe.call({
// method: 'show-dialog',
// args: {
// title: 'Invalid Payment Entry',
// message: `Payment amount (${this.amount}) should be greater than 0 and less than Outstanding amount (${outstandingAmount})`
// }
// });
throw new Error(
`Payment amount (${this.amount}) should be greater than 0 and less than Outstanding amount (${outstandingAmount})`
);
} else {
await frappe.db.setValue(
row.referenceType,
row.referenceName,
'outstandingAmount',
outstandingAmount - this.amount
);
}
}
}
} else {
// frappe.call({
// method: 'show-dialog',
// args: {
// title: 'Invalid Payment Entry',
// message: `No reference for the payment.`
// }
// });
throw new Error(`No reference for the payment.`); throw new Error(`No reference for the payment.`);
} }
for (let row of this.for) {
if (!['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
continue;
}
let referenceDoc = await frappe.getDoc(
row.referenceType,
row.referenceName
);
let { outstandingAmount, baseGrandTotal } = referenceDoc;
if (outstandingAmount == null) {
outstandingAmount = baseGrandTotal;
}
if (this.amount <= 0 || this.amount > outstandingAmount) {
throw new Error(
`Payment amount (${this.amount}) should be greater than 0 and less than Outstanding amount (${outstandingAmount})`
);
} else {
let newOutstanding = outstandingAmount - this.amount;
await referenceDoc.set('outstandingAmount', newOutstanding);
await referenceDoc.update();
}
}
} }
async afterSubmit() { async afterSubmit() {

View File

@ -1,11 +1,12 @@
const frappe = require('frappejs'); const { getActions } = require('../Transaction/Transaction');
const utils = require('../../../accounting/utils'); const InvoiceTemplate = require('../SalesInvoice/InvoiceTemplate.vue').default;
module.exports = { module.exports = {
name: 'PurchaseInvoice', name: 'PurchaseInvoice',
doctype: 'DocType', doctype: 'DocType',
label: 'Purchase Invoice', label: 'Purchase Invoice',
documentClass: require('./PurchaseInvoiceDocument'), documentClass: require('./PurchaseInvoiceDocument'),
printTemplate: InvoiceTemplate,
isSingle: 0, isSingle: 0,
isChild: 0, isChild: 0,
isSubmittable: 1, isSubmittable: 1,
@ -30,15 +31,8 @@ module.exports = {
fieldname: 'supplier', fieldname: 'supplier',
label: 'Supplier', label: 'Supplier',
fieldtype: 'Link', fieldtype: 'Link',
target: 'Party', target: 'Supplier',
required: 1, required: 1
getFilters: (query, control) => {
if (!query) return { supplier: 1 };
return {
keywords: ['like', query],
supplier: 1
};
}
}, },
{ {
fieldname: 'account', fieldname: 'account',
@ -46,10 +40,8 @@ module.exports = {
fieldtype: 'Link', fieldtype: 'Link',
target: 'Account', target: 'Account',
formula: doc => doc.getFrom('Party', doc.supplier, 'defaultAccount'), formula: doc => doc.getFrom('Party', doc.supplier, 'defaultAccount'),
getFilters: (query, control) => { getFilters: () => {
if (!query) return { isGroup: 0, accountType: 'Payable' };
return { return {
keywords: ['like', query],
isGroup: 0, isGroup: 0,
accountType: 'Payable' accountType: 'Payable'
}; };
@ -61,14 +53,15 @@ module.exports = {
fieldtype: 'Link', fieldtype: 'Link',
target: 'Currency', target: 'Currency',
hidden: 1, hidden: 1,
formula: doc => doc.getFrom('Party', doc.supplier, 'currency') formula: doc => doc.getFrom('Party', doc.supplier, 'currency'),
formulaDependsOn: ['supplier']
}, },
{ {
fieldname: 'exchangeRate', fieldname: 'exchangeRate',
label: 'Exchange Rate', label: 'Exchange Rate',
fieldtype: 'Float', fieldtype: 'Float',
description: '1 USD = [?] INR', formula: doc => doc.getExchangeRate(),
hidden: doc => !doc.isForeignTransaction() required: true
}, },
{ {
fieldname: 'items', fieldname: 'items',
@ -78,18 +71,18 @@ module.exports = {
required: true required: true
}, },
{ {
fieldname: 'baseNetTotal', fieldname: 'netTotal',
label: 'Net Total (INR)', label: 'Net Total',
fieldtype: 'Currency', fieldtype: 'Currency',
formula: async doc => await doc.getBaseNetTotal(), formula: doc => doc.getSum('items', 'amount'),
readOnly: 1 readOnly: 1,
getCurrency: doc => doc.currency
}, },
{ {
fieldname: 'netTotal', fieldname: 'baseNetTotal',
label: 'Net Total (USD)', label: 'Net Total (Company Currency)',
fieldtype: 'Currency', fieldtype: 'Currency',
hidden: doc => !doc.isForeignTransaction(), formula: doc => doc.netTotal * doc.exchangeRate,
formula: doc => doc.getSum('items', 'amount'),
readOnly: 1 readOnly: 1
}, },
{ {
@ -97,110 +90,40 @@ module.exports = {
label: 'Taxes', label: 'Taxes',
fieldtype: 'Table', fieldtype: 'Table',
childtype: 'TaxSummary', childtype: 'TaxSummary',
readOnly: 1, formula: doc => doc.getTaxSummary(),
template: (doc, row) => {
return `<div class='row'>
<div class='col-6'></div>
<div class='col-6'>
<div class='row' v-for='row in value'>
<div class='col-6'>{{ row.account }} ({{row.rate}}%)</div>
<div class='col-6 text-right'>
{{ frappe.format(row.amount, 'Currency') }}
</div>
</div>
</div>
</div>`;
}
},
{
fieldname: 'baseGrandTotal',
label: 'Grand Total (INR)',
fieldtype: 'Currency',
formula: async doc => await doc.getBaseGrandTotal(),
readOnly: 1 readOnly: 1
}, },
{ {
fieldname: 'grandTotal', fieldname: 'grandTotal',
label: 'Grand Total (USD)', label: 'Grand Total',
fieldtype: 'Currency', fieldtype: 'Currency',
hidden: doc => !doc.isForeignTransaction(), formula: doc => doc.getGrandTotal(),
formula: async doc => await doc.getGrandTotal(), readOnly: 1,
getCurrency: doc => doc.currency
},
{
fieldname: 'baseGrandTotal',
label: 'Grand Total (Company Currency)',
fieldtype: 'Currency',
formula: doc => doc.grandTotal * doc.exchangeRate,
readOnly: 1
},
{
fieldname: 'outstandingAmount',
label: 'Outstanding Amount',
fieldtype: 'Currency',
formula: doc => {
if (doc.submitted) return;
return doc.baseGrandTotal;
},
readOnly: 1 readOnly: 1
}, },
{ {
fieldname: 'terms', fieldname: 'terms',
label: 'Terms', label: 'Terms',
fieldtype: 'Text' fieldtype: 'Text'
},
{
fieldname: 'outstandingAmount',
label: 'Outstanding Amount',
fieldtype: 'Currency',
hidden: 1
} }
], ],
layout: [ actions: getActions('PurchaseInvoice')
// section 1
{
columns: [
{ fields: ['supplier', 'account'] },
{ fields: ['date', 'exchangeRate'] }
]
},
// section 2
{
columns: [{ fields: ['items'] }]
},
// section 3
{
columns: [
{
fields: [
'baseNetTotal',
'netTotal',
'taxes',
'baseGrandTotal',
'grandTotal'
]
}
]
},
// section 4
{
columns: [{ fields: ['terms'] }]
}
],
links: [
utils.ledgerLink,
{
label: 'Make Payment',
condition: form =>
form.doc.submitted && form.doc.outstandingAmount != 0.0,
action: async form => {
const payment = await frappe.getNewDoc('Payment');
payment.paymentType = 'Pay';
payment.party = form.doc.supplier;
payment.paymentAccount = form.doc.account;
payment.for = [
{
referenceType: form.doc.doctype,
referenceName: form.doc.name,
amount: form.doc.outstandingAmount
}
];
payment.on('afterInsert', async () => {
form.$formModal.close();
form.$router.push({
path: `/edit/Payment/${payment.name}`
});
});
await form.$formModal.open(payment);
}
}
]
}; };

View File

@ -1,4 +1,3 @@
const SalesInvoiceDocument = require('../SalesInvoice/SalesInvoiceDocument'); const TransactionDocument = require('../Transaction/TransactionDocument');
const frappe = require('frappejs');
module.exports = class PurchaseInvoice extends SalesInvoiceDocument {}; module.exports = class PurchaseInvoice extends TransactionDocument {};

View File

@ -1,5 +1,5 @@
import { _ } from 'frappejs/utils'; import { _ } from 'frappejs/utils';
import Badge from '@/components/Badge'; import { getStatusColumn } from '../Transaction/Transaction';
export default { export default {
doctype: 'PurchaseInvoice', doctype: 'PurchaseInvoice',
@ -8,24 +8,7 @@ export default {
columns: [ columns: [
'supplier', 'supplier',
'name', 'name',
{ getStatusColumn('PurchaseInvoice'),
label: 'Status',
fieldname: 'status',
fieldtype: 'Select',
size: 'small',
render(doc) {
let status = 'Pending';
let color = 'orange';
if (doc.submitted === 1 && doc.outstandingAmount === 0.0) {
status = 'Paid';
color = 'green';
}
return {
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
components: { Badge }
};
}
},
'date', 'date',
'grandTotal', 'grandTotal',
'outstandingAmount' 'outstandingAmount'

View File

@ -1,66 +1,27 @@
const frappe = require('frappejs'); const TransactionServer = require('../Transaction/TransactionServer');
const PurchaseInvoice = require('./PurchaseInvoiceDocument'); const PurchaseInvoice = require('./PurchaseInvoiceDocument');
const LedgerPosting = require('../../../accounting/ledgerPosting'); const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class PurchaseInvoiceServer extends PurchaseInvoice { class PurchaseInvoiceServer extends PurchaseInvoice {
async getPosting() { async getPosting() {
let entries = new LedgerPosting({ reference: this, party: this.supplier }); let entries = new LedgerPosting({ reference: this, party: this.supplier });
await entries.credit(this.account, this.baseGrandTotal); await entries.credit(this.account, this.baseGrandTotal);
for (let item of this.items) { for (let item of this.items) {
const baseItemAmount = item.amount * this.exchangeRate; await entries.debit(item.account, item.baseAmount);
await entries.debit(item.account, baseItemAmount);
} }
if (this.taxes) { if (this.taxes) {
for (let tax of this.taxes) { for (let tax of this.taxes) {
const baseTaxAmount = tax.amount * this.exchangeRate; await entries.debit(tax.account, tax.baseAmount);
await entries.debit(tax.account, baseTaxAmount);
} }
} }
entries.makeRoundOffEntry();
return entries; return entries;
} }
}
async getPayments() { // apply common methods from TransactionServer
let payments = await frappe.db.getAll({ Object.assign(PurchaseInvoiceServer.prototype, TransactionServer);
doctype: 'PaymentFor',
fields: ['parent'],
filters: { referenceName: this.name },
orderBy: 'name'
});
if (payments.length != 0) {
return payments;
}
return [];
}
async beforeInsert() { module.exports = PurchaseInvoiceServer;
const entries = await this.getPosting();
await entries.validateEntries();
}
async beforeSubmit() {
const entries = await this.getPosting();
await entries.post();
await frappe.db.setValue(
'PurchaseInvoice',
this.name,
'outstandingAmount',
this.baseGrandTotal
);
}
async afterRevert() {
let paymentRefList = await this.getPayments();
for (let paymentFor of paymentRefList) {
const paymentReference = paymentFor.parent;
const payment = await frappe.getDoc('Payment', paymentReference);
const paymentEntries = await payment.getPosting();
await paymentEntries.postReverse();
// To set the payment status as unsubmitted.
payment.revert();
}
const entries = await this.getPosting();
await entries.postReverse();
}
};

View File

@ -6,21 +6,14 @@ module.exports = {
isChild: 1, isChild: 1,
keywordFields: [], keywordFields: [],
layout: 'ratio', layout: 'ratio',
tableFields: [ tableFields: ['item', 'tax', 'quantity', 'rate', 'amount'],
'item',
'tax',
'quantity',
'rate',
'amount'
],
fields: [ fields: [
{ {
fieldname: 'item', fieldname: 'item',
label: 'Item', label: 'Item',
fieldtype: 'Link', fieldtype: 'Link',
target: 'Item', target: 'Item',
required: 1, required: 1
width: 2
}, },
{ {
fieldname: 'description', fieldname: 'description',
@ -40,7 +33,18 @@ module.exports = {
label: 'Rate', label: 'Rate',
fieldtype: 'Currency', fieldtype: 'Currency',
required: 1, required: 1,
formula: (row, doc) => doc.getFrom('Item', row.item, 'rate') formula: async (row, doc) => {
let baseRate = await doc.getFrom('Item', row.item, 'rate');
return baseRate / doc.exchangeRate;
},
getCurrency: (row, doc) => doc.currency
},
{
fieldname: 'baseRate',
label: 'Rate (Company Currency)',
fieldtype: 'Currency',
formula: (row, doc) => row.rate * doc.exchangeRate,
readOnly: 1
}, },
{ {
fieldname: 'account', fieldname: 'account',
@ -65,15 +69,15 @@ module.exports = {
label: 'Amount', label: 'Amount',
fieldtype: 'Currency', fieldtype: 'Currency',
readOnly: 1, readOnly: 1,
formula: (row, doc) => row.quantity * row.rate formula: (row, doc) => row.quantity * row.rate,
getCurrency: (row, doc) => doc.currency
}, },
{ {
fieldname: 'taxAmount', fieldname: 'baseAmount',
label: 'Tax Amount', label: 'Amount (Company Currency)',
hidden: 1, fieldtype: 'Currency',
readOnly: 1, readOnly: 1,
fieldtype: 'Text', formula: (row, doc) => row.amount * doc.exchangeRate
formula: (row, doc) => doc.getRowTax(row)
} }
] ]
}; };

View File

@ -1,7 +1,4 @@
const frappe = require('frappejs'); const { getActions } = require('../Transaction/Transaction');
const utils = require('../../../accounting/utils');
const { openQuickEdit } = require('@/utils');
const router = require('@/router').default;
const InvoiceTemplate = require('./InvoiceTemplate.vue').default; const InvoiceTemplate = require('./InvoiceTemplate.vue').default;
module.exports = { module.exports = {
@ -127,50 +124,5 @@ module.exports = {
} }
], ],
actions: [ actions: getActions('SalesInvoice')
{
label: 'Make Payment',
condition: doc => doc.submitted && doc.outstandingAmount > 0,
action: async function makePayment(doc) {
let payment = await frappe.getNewDoc('Payment');
payment.once('afterInsert', () => {
payment.submit();
});
openQuickEdit({
doctype: 'Payment',
name: payment.name,
hideFields: ['party', 'date', 'account', 'paymentType', 'for'],
defaults: {
party: doc.customer,
account: doc.account,
date: new Date().toISOString().slice(0, 10),
paymentType: 'Receive',
for: [
{
referenceType: doc.doctype,
referenceName: doc.name,
amount: doc.outstandingAmount
}
]
}
});
}
},
{
label: 'Revert',
condition: doc =>
doc.submitted && doc.baseGrandTotal === doc.outstandingAmount,
action(doc) {
doc.revert();
}
},
{
label: 'Print',
condition: doc => doc.submitted,
action(doc) {
router.push(`/print/${doc.doctype}/${doc.name}`);
}
},
utils.ledgerLink
]
}; };

View File

@ -1,68 +1,3 @@
const BaseDocument = require('frappejs/model/document'); const TransactionDocument = require('../Transaction/TransactionDocument');
const frappe = require('frappejs');
const { round } = require('frappejs/utils/numberFormat');
const { getExchangeRate } = require('../../../accounting/exchangeRate');
module.exports = class SalesInvoice extends BaseDocument { module.exports = class SalesInvoice extends TransactionDocument {};
async getExchangeRate() {
if (!this.currency) return;
let accountingSettings = await frappe.getSingle('AccountingSettings');
const companyCurrency = accountingSettings.currency;
if (this.currency === companyCurrency) {
return 1.0;
}
return await getExchangeRate({
fromCurrency: this.currency,
toCurrency: companyCurrency
});
}
async getTaxSummary() {
let taxes = {};
for (let row of this.items) {
if (row.tax) {
let tax = await this.getTax(row.tax);
for (let d of tax.details) {
let amount = (row.amount * d.rate) / 100;
taxes[d.account] = taxes[d.account] || {
account: d.account,
rate: d.rate,
amount: 0
};
// collect amount
taxes[d.account].amount += amount;
}
}
}
return (
Object.keys(taxes)
.map(account => {
let tax = taxes[account];
tax.baseAmount = round(tax.amount * this.exchangeRate, 2);
return tax;
})
// clear rows with 0 amount
.filter(tax => tax.amount)
);
}
async getTax(tax) {
if (!this._taxes) this._taxes = {};
if (!this._taxes[tax]) this._taxes[tax] = await frappe.getDoc('Tax', tax);
return this._taxes[tax];
}
async getGrandTotal() {
let grandTotal = this.netTotal;
if (this.taxes) {
for (let row of this.taxes) {
grandTotal += row.amount;
}
}
return grandTotal;
}
};

View File

@ -1,5 +1,5 @@
import { _ } from 'frappejs/utils'; import { _ } from 'frappejs/utils';
import Badge from '@/components/Badge'; import { getStatusColumn } from '../Transaction/Transaction';
export default { export default {
doctype: 'SalesInvoice', doctype: 'SalesInvoice',
@ -8,28 +8,7 @@ export default {
columns: [ columns: [
'customer', 'customer',
'name', 'name',
{ getStatusColumn('SalesInvoice'),
label: 'Status',
fieldname: 'status',
fieldtype: 'Select',
size: 'small',
render(doc) {
let status = 'Pending';
let color = 'orange';
if (!doc.submitted) {
status = 'Draft';
color = 'gray';
}
if (doc.submitted === 1 && doc.outstandingAmount === 0.0) {
status = 'Paid';
color = 'green';
}
return {
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
components: { Badge }
};
}
},
'date', 'date',
'grandTotal', 'grandTotal',
'outstandingAmount' 'outstandingAmount'

View File

@ -1,7 +1,8 @@
const TransactionServer = require('../Transaction/TransactionServer');
const SalesInvoice = require('./SalesInvoiceDocument'); const SalesInvoice = require('./SalesInvoiceDocument');
const LedgerPosting = require('../../../accounting/ledgerPosting'); const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class SalesInvoiceServer extends SalesInvoice { class SalesInvoiceServer extends SalesInvoice {
async getPosting() { async getPosting() {
let entries = new LedgerPosting({ reference: this, party: this.customer }); let entries = new LedgerPosting({ reference: this, party: this.customer });
await entries.debit(this.account, this.baseGrandTotal); await entries.debit(this.account, this.baseGrandTotal);
@ -18,52 +19,9 @@ module.exports = class SalesInvoiceServer extends SalesInvoice {
entries.makeRoundOffEntry(); entries.makeRoundOffEntry();
return entries; return entries;
} }
}
async getPayments() { // apply common methods from TransactionServer
let payments = await frappe.db.getAll({ Object.assign(SalesInvoiceServer.prototype, TransactionServer);
doctype: 'PaymentFor',
fields: ['parent'],
filters: { referenceName: this.name },
orderBy: 'name'
});
if (payments.length != 0) {
return payments;
}
return [];
}
async beforeUpdate() { module.exports = SalesInvoiceServer;
const entries = await this.getPosting();
await entries.validateEntries();
}
async beforeInsert() {
const entries = await this.getPosting();
await entries.validateEntries();
}
async beforeSubmit() {
const entries = await this.getPosting();
await entries.post();
await frappe.db.setValue(
'SalesInvoice',
this.name,
'outstandingAmount',
this.baseGrandTotal
);
}
async afterRevert() {
let paymentRefList = await this.getPayments();
for (let paymentFor of paymentRefList) {
const paymentReference = paymentFor.parent;
const payment = await frappe.getDoc('Payment', paymentReference);
const paymentEntries = await payment.getPosting();
await paymentEntries.postReverse();
// To set the payment status as unsubmitted.
payment.revert();
}
const entries = await this.getPosting();
await entries.postReverse();
}
};

View File

@ -0,0 +1,82 @@
const frappe = require('frappejs');
const utils = require('../../../accounting/utils');
const { openQuickEdit } = require('@/utils');
const Badge = require('@/components/Badge').default;
module.exports = {
getStatusColumn() {
return {
label: 'Status',
fieldname: 'status',
fieldtype: 'Select',
render(doc) {
let status = 'Pending';
let color = 'orange';
if (!doc.submitted) {
status = 'Draft';
color = 'gray';
}
if (doc.submitted === 1 && doc.outstandingAmount === 0.0) {
status = 'Paid';
color = 'green';
}
return {
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
components: { Badge }
};
}
};
},
getActions(doctype) {
return [
{
label: 'Make Payment',
condition: doc => doc.submitted && doc.outstandingAmount > 0,
action: async function makePayment(doc) {
let payment = await frappe.getNewDoc('Payment');
payment.once('afterInsert', async () => {
await payment.submit();
});
let isSales = doctype === 'SalesInvoice';
let party = isSales ? doc.customer : doc.supplier;
let paymentType = isSales ? 'Receive' : 'Pay';
let hideAccountField = isSales ? 'account' : 'paymentAccount';
openQuickEdit({
doctype: 'Payment',
name: payment.name,
// hideFields: ['party', 'date', hideAccountField, 'paymentType', 'for'],
defaults: {
party,
[hideAccountField]: doc.account,
date: new Date().toISOString().slice(0, 10),
paymentType,
for: [
{
referenceType: doc.doctype,
referenceName: doc.name,
amount: doc.outstandingAmount
}
]
}
});
}
},
{
label: 'Revert',
condition: doc =>
doc.submitted && doc.baseGrandTotal === doc.outstandingAmount,
action(doc) {
doc.revert();
}
},
{
label: 'Print',
condition: doc => doc.submitted,
action(doc, router) {
router.push(`/print/${doc.doctype}/${doc.name}`);
}
},
utils.ledgerLink
];
}
};

View File

@ -0,0 +1,68 @@
const BaseDocument = require('frappejs/model/document');
const frappe = require('frappejs');
const { round } = require('frappejs/utils/numberFormat');
const { getExchangeRate } = require('../../../accounting/exchangeRate');
module.exports = class TransactionDocument extends BaseDocument {
async getExchangeRate() {
if (!this.currency) return;
let accountingSettings = await frappe.getSingle('AccountingSettings');
const companyCurrency = accountingSettings.currency;
if (this.currency === companyCurrency) {
return 1.0;
}
return await getExchangeRate({
fromCurrency: this.currency,
toCurrency: companyCurrency
});
}
async getTaxSummary() {
let taxes = {};
for (let row of this.items) {
if (row.tax) {
let tax = await this.getTax(row.tax);
for (let d of tax.details) {
let amount = (row.amount * d.rate) / 100;
taxes[d.account] = taxes[d.account] || {
account: d.account,
rate: d.rate,
amount: 0
};
// collect amount
taxes[d.account].amount += amount;
}
}
}
return (
Object.keys(taxes)
.map(account => {
let tax = taxes[account];
tax.baseAmount = round(tax.amount * this.exchangeRate, 2);
return tax;
})
// clear rows with 0 amount
.filter(tax => tax.amount)
);
}
async getTax(tax) {
if (!this._taxes) this._taxes = {};
if (!this._taxes[tax]) this._taxes[tax] = await frappe.getDoc('Tax', tax);
return this._taxes[tax];
}
async getGrandTotal() {
let grandTotal = this.netTotal;
if (this.taxes) {
for (let row of this.taxes) {
grandTotal += row.amount;
}
}
return grandTotal;
}
};

View File

@ -0,0 +1,51 @@
const frappe = require('frappejs');
module.exports = {
async getPayments() {
let payments = await frappe.db.getAll({
doctype: 'PaymentFor',
fields: ['parent'],
filters: { referenceName: this.name },
orderBy: 'name'
});
if (payments.length != 0) {
return payments;
}
return [];
},
async beforeUpdate() {
const entries = await this.getPosting();
await entries.validateEntries();
},
async beforeInsert() {
const entries = await this.getPosting();
await entries.validateEntries();
},
async beforeSubmit() {
const entries = await this.getPosting();
await entries.post();
await frappe.db.setValue(
this.doctype,
this.name,
'outstandingAmount',
this.baseGrandTotal
);
},
async afterRevert() {
let paymentRefList = await this.getPayments();
for (let paymentFor of paymentRefList) {
const paymentReference = paymentFor.parent;
const payment = await frappe.getDoc('Payment', paymentReference);
const paymentEntries = await payment.getPosting();
await paymentEntries.postReverse();
// To set the payment status as unsubmitted.
payment.revert();
}
const entries = await this.getPosting();
await entries.postReverse();
}
};

View File

@ -163,7 +163,7 @@ export default {
return { return {
label: d.label, label: d.label,
component: d.component, component: d.component,
action: d.action.bind(this, this.doc) action: d.action.bind(this, this.doc, this.$router)
}; };
}); });