mirror of
https://github.com/frappe/books.git
synced 2024-11-10 07:40:55 +00:00
fix: Commonify Transaction code
for SalesInvoice and PurchaseInvoice
This commit is contained in:
parent
4e0af18983
commit
4904632549
@ -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: {
|
||||
|
@ -112,9 +112,9 @@ module.exports = {
|
||||
quickEditFields: [
|
||||
'party',
|
||||
'date',
|
||||
'paymentMethod',
|
||||
'account',
|
||||
'paymentType',
|
||||
'paymentMethod',
|
||||
'paymentAccount',
|
||||
'referenceId',
|
||||
'referenceDate',
|
||||
|
@ -22,48 +22,32 @@ module.exports = class PaymentServer extends BaseDocument {
|
||||
}
|
||||
|
||||
async beforeSubmit() {
|
||||
if (this.for.length > 0) {
|
||||
if (!this.for.length) {
|
||||
throw new Error(`No reference for the payment.`);
|
||||
}
|
||||
for (let row of this.for) {
|
||||
if (['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
|
||||
let { outstandingAmount, grandTotal } = await frappe.getDoc(
|
||||
if (!['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
|
||||
continue;
|
||||
}
|
||||
let referenceDoc = await frappe.getDoc(
|
||||
row.referenceType,
|
||||
row.referenceName
|
||||
);
|
||||
if (outstandingAmount === null) {
|
||||
outstandingAmount = grandTotal;
|
||||
let { outstandingAmount, baseGrandTotal } = referenceDoc;
|
||||
if (outstandingAmount == null) {
|
||||
outstandingAmount = baseGrandTotal;
|
||||
}
|
||||
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
|
||||
);
|
||||
let newOutstanding = outstandingAmount - this.amount;
|
||||
await referenceDoc.set('outstandingAmount', newOutstanding);
|
||||
await referenceDoc.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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.`);
|
||||
}
|
||||
}
|
||||
|
||||
async afterSubmit() {
|
||||
const entries = await this.getPosting();
|
||||
|
@ -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 `<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(),
|
||||
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')
|
||||
};
|
||||
|
@ -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 {};
|
||||
|
@ -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: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
|
||||
components: { Badge }
|
||||
};
|
||||
}
|
||||
},
|
||||
getStatusColumn('PurchaseInvoice'),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount'
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
||||
async beforeInsert() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
}
|
||||
// apply common methods from TransactionServer
|
||||
Object.assign(PurchaseInvoiceServer.prototype, TransactionServer);
|
||||
|
||||
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;
|
||||
|
@ -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
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -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')
|
||||
};
|
||||
|
@ -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 {};
|
||||
|
@ -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: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
|
||||
components: { Badge }
|
||||
};
|
||||
}
|
||||
},
|
||||
getStatusColumn('SalesInvoice'),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount'
|
||||
|
@ -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 [];
|
||||
}
|
||||
|
||||
async beforeUpdate() {
|
||||
const entries = await this.getPosting();
|
||||
await entries.validateEntries();
|
||||
}
|
||||
// apply common methods from TransactionServer
|
||||
Object.assign(SalesInvoiceServer.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(
|
||||
'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;
|
||||
|
82
models/doctype/Transaction/Transaction.js
Normal file
82
models/doctype/Transaction/Transaction.js
Normal 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
|
||||
];
|
||||
}
|
||||
};
|
68
models/doctype/Transaction/TransactionDocument.js
Normal file
68
models/doctype/Transaction/TransactionDocument.js
Normal 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;
|
||||
}
|
||||
};
|
51
models/doctype/Transaction/TransactionServer.js
Normal file
51
models/doctype/Transaction/TransactionServer.js
Normal 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();
|
||||
}
|
||||
};
|
@ -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)
|
||||
};
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user