From 4904632549ec2f5a815f95ed84a045c04ba37585 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 4 Dec 2019 22:51:31 +0530 Subject: [PATCH] fix: Commonify Transaction code for SalesInvoice and PurchaseInvoice --- accounting/utils.js | 4 +- models/doctype/Payment/Payment.js | 2 +- models/doctype/Payment/PaymentServer.js | 62 +++---- .../PurchaseInvoice/PurchaseInvoice.js | 157 +++++------------- .../PurchaseInvoiceDocument.js | 5 +- .../PurchaseInvoice/PurchaseInvoiceList.js | 21 +-- .../PurchaseInvoice/PurchaseInvoiceServer.js | 57 +------ .../PurchaseInvoiceItem.js | 36 ++-- models/doctype/SalesInvoice/SalesInvoice.js | 52 +----- .../SalesInvoice/SalesInvoiceDocument.js | 69 +------- .../doctype/SalesInvoice/SalesInvoiceList.js | 25 +-- .../SalesInvoice/SalesInvoiceServer.js | 54 +----- models/doctype/Transaction/Transaction.js | 82 +++++++++ .../Transaction/TransactionDocument.js | 68 ++++++++ .../doctype/Transaction/TransactionServer.js | 51 ++++++ src/pages/QuickEditForm.vue | 2 +- 16 files changed, 312 insertions(+), 435 deletions(-) create mode 100644 models/doctype/Transaction/Transaction.js create mode 100644 models/doctype/Transaction/TransactionDocument.js create mode 100644 models/doctype/Transaction/TransactionServer.js diff --git a/accounting/utils.js b/accounting/utils.js index eabcd274..b461f18b 100644 --- a/accounting/utils.js +++ b/accounting/utils.js @@ -1,10 +1,8 @@ -let router = require('@/router').default; - module.exports = { ledgerLink: { label: 'Ledger Entries', condition: doc => doc.submitted, - action: doc => { + action: (doc, router) => { router.push({ name: 'Report', params: { diff --git a/models/doctype/Payment/Payment.js b/models/doctype/Payment/Payment.js index 5c3b5147..1894bfc7 100644 --- a/models/doctype/Payment/Payment.js +++ b/models/doctype/Payment/Payment.js @@ -112,9 +112,9 @@ module.exports = { quickEditFields: [ 'party', 'date', + 'paymentMethod', 'account', 'paymentType', - 'paymentMethod', 'paymentAccount', 'referenceId', 'referenceDate', diff --git a/models/doctype/Payment/PaymentServer.js b/models/doctype/Payment/PaymentServer.js index 55e1dda7..3eb2644b 100644 --- a/models/doctype/Payment/PaymentServer.js +++ b/models/doctype/Payment/PaymentServer.js @@ -22,47 +22,31 @@ module.exports = class PaymentServer extends BaseDocument { } async beforeSubmit() { - if (this.for.length > 0) { - 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.` - // } - // }); + if (!this.for.length) { 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() { diff --git a/models/doctype/PurchaseInvoice/PurchaseInvoice.js b/models/doctype/PurchaseInvoice/PurchaseInvoice.js index 1e1821a2..9090ded6 100644 --- a/models/doctype/PurchaseInvoice/PurchaseInvoice.js +++ b/models/doctype/PurchaseInvoice/PurchaseInvoice.js @@ -1,11 +1,12 @@ -const frappe = require('frappejs'); -const utils = require('../../../accounting/utils'); +const { getActions } = require('../Transaction/Transaction'); +const InvoiceTemplate = require('../SalesInvoice/InvoiceTemplate.vue').default; module.exports = { name: 'PurchaseInvoice', doctype: 'DocType', label: 'Purchase Invoice', documentClass: require('./PurchaseInvoiceDocument'), + printTemplate: InvoiceTemplate, isSingle: 0, isChild: 0, isSubmittable: 1, @@ -30,15 +31,8 @@ module.exports = { fieldname: 'supplier', label: 'Supplier', fieldtype: 'Link', - target: 'Party', - required: 1, - getFilters: (query, control) => { - if (!query) return { supplier: 1 }; - return { - keywords: ['like', query], - supplier: 1 - }; - } + target: 'Supplier', + required: 1 }, { fieldname: 'account', @@ -46,10 +40,8 @@ module.exports = { fieldtype: 'Link', target: 'Account', formula: doc => doc.getFrom('Party', doc.supplier, 'defaultAccount'), - getFilters: (query, control) => { - if (!query) return { isGroup: 0, accountType: 'Payable' }; + getFilters: () => { return { - keywords: ['like', query], isGroup: 0, accountType: 'Payable' }; @@ -61,14 +53,15 @@ module.exports = { fieldtype: 'Link', target: 'Currency', hidden: 1, - formula: doc => doc.getFrom('Party', doc.supplier, 'currency') + formula: doc => doc.getFrom('Party', doc.supplier, 'currency'), + formulaDependsOn: ['supplier'] }, { fieldname: 'exchangeRate', label: 'Exchange Rate', fieldtype: 'Float', - description: '1 USD = [?] INR', - hidden: doc => !doc.isForeignTransaction() + formula: doc => doc.getExchangeRate(), + required: true }, { fieldname: 'items', @@ -78,18 +71,18 @@ module.exports = { required: true }, { - fieldname: 'baseNetTotal', - label: 'Net Total (INR)', + fieldname: 'netTotal', + label: 'Net Total', fieldtype: 'Currency', - formula: async doc => await doc.getBaseNetTotal(), - readOnly: 1 + formula: doc => doc.getSum('items', 'amount'), + readOnly: 1, + getCurrency: doc => doc.currency }, { - fieldname: 'netTotal', - label: 'Net Total (USD)', + fieldname: 'baseNetTotal', + label: 'Net Total (Company Currency)', fieldtype: 'Currency', - hidden: doc => !doc.isForeignTransaction(), - formula: doc => doc.getSum('items', 'amount'), + formula: doc => doc.netTotal * doc.exchangeRate, readOnly: 1 }, { @@ -97,110 +90,40 @@ module.exports = { label: 'Taxes', fieldtype: 'Table', childtype: 'TaxSummary', - readOnly: 1, - template: (doc, row) => { - return `
-
-
-
-
{{ row.account }} ({{row.rate}}%)
-
- {{ frappe.format(row.amount, 'Currency') }} -
-
-
-
`; - } - }, - { - fieldname: 'baseGrandTotal', - label: 'Grand Total (INR)', - fieldtype: 'Currency', - formula: async doc => await doc.getBaseGrandTotal(), + formula: doc => doc.getTaxSummary(), readOnly: 1 }, { fieldname: 'grandTotal', - label: 'Grand Total (USD)', + label: 'Grand Total', fieldtype: 'Currency', - hidden: doc => !doc.isForeignTransaction(), - formula: async doc => await doc.getGrandTotal(), + formula: doc => 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 }, { fieldname: 'terms', label: 'Terms', fieldtype: 'Text' - }, - { - fieldname: 'outstandingAmount', - label: 'Outstanding Amount', - fieldtype: 'Currency', - hidden: 1 } ], - layout: [ - // 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); - } - } - ] + actions: getActions('PurchaseInvoice') }; diff --git a/models/doctype/PurchaseInvoice/PurchaseInvoiceDocument.js b/models/doctype/PurchaseInvoice/PurchaseInvoiceDocument.js index b2752ec2..b7f9d3d2 100644 --- a/models/doctype/PurchaseInvoice/PurchaseInvoiceDocument.js +++ b/models/doctype/PurchaseInvoice/PurchaseInvoiceDocument.js @@ -1,4 +1,3 @@ -const SalesInvoiceDocument = require('../SalesInvoice/SalesInvoiceDocument'); -const frappe = require('frappejs'); +const TransactionDocument = require('../Transaction/TransactionDocument'); -module.exports = class PurchaseInvoice extends SalesInvoiceDocument {}; +module.exports = class PurchaseInvoice extends TransactionDocument {}; diff --git a/models/doctype/PurchaseInvoice/PurchaseInvoiceList.js b/models/doctype/PurchaseInvoice/PurchaseInvoiceList.js index 09d02f40..f63b09e9 100644 --- a/models/doctype/PurchaseInvoice/PurchaseInvoiceList.js +++ b/models/doctype/PurchaseInvoice/PurchaseInvoiceList.js @@ -1,5 +1,5 @@ import { _ } from 'frappejs/utils'; -import Badge from '@/components/Badge'; +import { getStatusColumn } from '../Transaction/Transaction'; export default { doctype: 'PurchaseInvoice', @@ -8,24 +8,7 @@ export default { columns: [ 'supplier', 'name', - { - 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: `${status}`, - components: { Badge } - }; - } - }, + getStatusColumn('PurchaseInvoice'), 'date', 'grandTotal', 'outstandingAmount' diff --git a/models/doctype/PurchaseInvoice/PurchaseInvoiceServer.js b/models/doctype/PurchaseInvoice/PurchaseInvoiceServer.js index fb094355..669d727f 100644 --- a/models/doctype/PurchaseInvoice/PurchaseInvoiceServer.js +++ b/models/doctype/PurchaseInvoice/PurchaseInvoiceServer.js @@ -1,66 +1,27 @@ -const frappe = require('frappejs'); +const TransactionServer = require('../Transaction/TransactionServer'); const PurchaseInvoice = require('./PurchaseInvoiceDocument'); const LedgerPosting = require('../../../accounting/ledgerPosting'); -module.exports = class PurchaseInvoiceServer extends PurchaseInvoice { +class PurchaseInvoiceServer extends PurchaseInvoice { async getPosting() { let entries = new LedgerPosting({ reference: this, party: this.supplier }); await entries.credit(this.account, this.baseGrandTotal); for (let item of this.items) { - const baseItemAmount = item.amount * this.exchangeRate; - await entries.debit(item.account, baseItemAmount); + await entries.debit(item.account, item.baseAmount); } if (this.taxes) { for (let tax of this.taxes) { - const baseTaxAmount = tax.amount * this.exchangeRate; - await entries.debit(tax.account, baseTaxAmount); + await entries.debit(tax.account, tax.baseAmount); } } + entries.makeRoundOffEntry(); return entries; } +} - 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 []; - } +// apply common methods from TransactionServer +Object.assign(PurchaseInvoiceServer.prototype, TransactionServer); - async beforeInsert() { - 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(); - } -}; +module.exports = PurchaseInvoiceServer; diff --git a/models/doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js b/models/doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js index c1ce1b29..e7a8cb78 100644 --- a/models/doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js +++ b/models/doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js @@ -6,21 +6,14 @@ module.exports = { isChild: 1, keywordFields: [], layout: 'ratio', - tableFields: [ - 'item', - 'tax', - 'quantity', - 'rate', - 'amount' - ], + tableFields: ['item', 'tax', 'quantity', 'rate', 'amount'], fields: [ { fieldname: 'item', label: 'Item', fieldtype: 'Link', target: 'Item', - required: 1, - width: 2 + required: 1 }, { fieldname: 'description', @@ -40,7 +33,18 @@ module.exports = { label: 'Rate', fieldtype: 'Currency', 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', @@ -65,15 +69,15 @@ module.exports = { label: 'Amount', fieldtype: 'Currency', readOnly: 1, - formula: (row, doc) => row.quantity * row.rate + formula: (row, doc) => row.quantity * row.rate, + getCurrency: (row, doc) => doc.currency }, { - fieldname: 'taxAmount', - label: 'Tax Amount', - hidden: 1, + fieldname: 'baseAmount', + label: 'Amount (Company Currency)', + fieldtype: 'Currency', readOnly: 1, - fieldtype: 'Text', - formula: (row, doc) => doc.getRowTax(row) + formula: (row, doc) => row.amount * doc.exchangeRate } ] }; diff --git a/models/doctype/SalesInvoice/SalesInvoice.js b/models/doctype/SalesInvoice/SalesInvoice.js index 9a1914d6..72a9cc85 100644 --- a/models/doctype/SalesInvoice/SalesInvoice.js +++ b/models/doctype/SalesInvoice/SalesInvoice.js @@ -1,7 +1,4 @@ -const frappe = require('frappejs'); -const utils = require('../../../accounting/utils'); -const { openQuickEdit } = require('@/utils'); -const router = require('@/router').default; +const { getActions } = require('../Transaction/Transaction'); const InvoiceTemplate = require('./InvoiceTemplate.vue').default; module.exports = { @@ -127,50 +124,5 @@ module.exports = { } ], - actions: [ - { - 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 - ] + actions: getActions('SalesInvoice') }; diff --git a/models/doctype/SalesInvoice/SalesInvoiceDocument.js b/models/doctype/SalesInvoice/SalesInvoiceDocument.js index c544f164..d3275619 100644 --- a/models/doctype/SalesInvoice/SalesInvoiceDocument.js +++ b/models/doctype/SalesInvoice/SalesInvoiceDocument.js @@ -1,68 +1,3 @@ -const BaseDocument = require('frappejs/model/document'); -const frappe = require('frappejs'); -const { round } = require('frappejs/utils/numberFormat'); -const { getExchangeRate } = require('../../../accounting/exchangeRate'); +const TransactionDocument = require('../Transaction/TransactionDocument'); -module.exports = class SalesInvoice 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; - } -}; +module.exports = class SalesInvoice extends TransactionDocument {}; diff --git a/models/doctype/SalesInvoice/SalesInvoiceList.js b/models/doctype/SalesInvoice/SalesInvoiceList.js index 1ea01952..ecfa5123 100644 --- a/models/doctype/SalesInvoice/SalesInvoiceList.js +++ b/models/doctype/SalesInvoice/SalesInvoiceList.js @@ -1,5 +1,5 @@ import { _ } from 'frappejs/utils'; -import Badge from '@/components/Badge'; +import { getStatusColumn } from '../Transaction/Transaction'; export default { doctype: 'SalesInvoice', @@ -8,28 +8,7 @@ export default { columns: [ 'customer', 'name', - { - 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: `${status}`, - components: { Badge } - }; - } - }, + getStatusColumn('SalesInvoice'), 'date', 'grandTotal', 'outstandingAmount' diff --git a/models/doctype/SalesInvoice/SalesInvoiceServer.js b/models/doctype/SalesInvoice/SalesInvoiceServer.js index fe9bbbfc..b615fa3f 100644 --- a/models/doctype/SalesInvoice/SalesInvoiceServer.js +++ b/models/doctype/SalesInvoice/SalesInvoiceServer.js @@ -1,7 +1,8 @@ +const TransactionServer = require('../Transaction/TransactionServer'); const SalesInvoice = require('./SalesInvoiceDocument'); const LedgerPosting = require('../../../accounting/ledgerPosting'); -module.exports = class SalesInvoiceServer extends SalesInvoice { +class SalesInvoiceServer extends SalesInvoice { async getPosting() { let entries = new LedgerPosting({ reference: this, party: this.customer }); await entries.debit(this.account, this.baseGrandTotal); @@ -18,52 +19,9 @@ module.exports = class SalesInvoiceServer extends SalesInvoice { entries.makeRoundOffEntry(); return entries; } +} - 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 []; - } +// apply common methods from TransactionServer +Object.assign(SalesInvoiceServer.prototype, TransactionServer); - 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( - '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(); - } -}; +module.exports = SalesInvoiceServer; diff --git a/models/doctype/Transaction/Transaction.js b/models/doctype/Transaction/Transaction.js new file mode 100644 index 00000000..d2505863 --- /dev/null +++ b/models/doctype/Transaction/Transaction.js @@ -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: `${status}`, + 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 + ]; + } +}; diff --git a/models/doctype/Transaction/TransactionDocument.js b/models/doctype/Transaction/TransactionDocument.js new file mode 100644 index 00000000..a5863f13 --- /dev/null +++ b/models/doctype/Transaction/TransactionDocument.js @@ -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; + } +}; diff --git a/models/doctype/Transaction/TransactionServer.js b/models/doctype/Transaction/TransactionServer.js new file mode 100644 index 00000000..f7b2945d --- /dev/null +++ b/models/doctype/Transaction/TransactionServer.js @@ -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(); + } +}; diff --git a/src/pages/QuickEditForm.vue b/src/pages/QuickEditForm.vue index a37a5eed..dc987e9a 100644 --- a/src/pages/QuickEditForm.vue +++ b/src/pages/QuickEditForm.vue @@ -163,7 +163,7 @@ export default { return { label: d.label, component: d.component, - action: d.action.bind(this, this.doc) + action: d.action.bind(this, this.doc, this.$router) }; });