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 = {
|
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: {
|
||||||
|
@ -112,9 +112,9 @@ module.exports = {
|
|||||||
quickEditFields: [
|
quickEditFields: [
|
||||||
'party',
|
'party',
|
||||||
'date',
|
'date',
|
||||||
|
'paymentMethod',
|
||||||
'account',
|
'account',
|
||||||
'paymentType',
|
'paymentType',
|
||||||
'paymentMethod',
|
|
||||||
'paymentAccount',
|
'paymentAccount',
|
||||||
'referenceId',
|
'referenceId',
|
||||||
'referenceDate',
|
'referenceDate',
|
||||||
|
@ -22,48 +22,32 @@ module.exports = class PaymentServer extends BaseDocument {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async beforeSubmit() {
|
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) {
|
for (let row of this.for) {
|
||||||
if (['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
|
if (!['SalesInvoice', 'PurchaseInvoice'].includes(row.referenceType)) {
|
||||||
let { outstandingAmount, grandTotal } = await frappe.getDoc(
|
continue;
|
||||||
|
}
|
||||||
|
let referenceDoc = await frappe.getDoc(
|
||||||
row.referenceType,
|
row.referenceType,
|
||||||
row.referenceName
|
row.referenceName
|
||||||
);
|
);
|
||||||
if (outstandingAmount === null) {
|
let { outstandingAmount, baseGrandTotal } = referenceDoc;
|
||||||
outstandingAmount = grandTotal;
|
if (outstandingAmount == null) {
|
||||||
|
outstandingAmount = baseGrandTotal;
|
||||||
}
|
}
|
||||||
if (this.amount <= 0 || this.amount > outstandingAmount) {
|
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(
|
throw new Error(
|
||||||
`Payment amount (${this.amount}) should be greater than 0 and less than Outstanding amount (${outstandingAmount})`
|
`Payment amount (${this.amount}) should be greater than 0 and less than Outstanding amount (${outstandingAmount})`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await frappe.db.setValue(
|
let newOutstanding = outstandingAmount - this.amount;
|
||||||
row.referenceType,
|
await referenceDoc.set('outstandingAmount', newOutstanding);
|
||||||
row.referenceName,
|
await referenceDoc.update();
|
||||||
'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.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async afterSubmit() {
|
async afterSubmit() {
|
||||||
const entries = await this.getPosting();
|
const entries = await this.getPosting();
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
@ -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 {};
|
||||||
|
@ -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'
|
||||||
|
@ -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() {
|
|
||||||
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() {
|
// apply common methods from TransactionServer
|
||||||
const entries = await this.getPosting();
|
Object.assign(PurchaseInvoiceServer.prototype, TransactionServer);
|
||||||
await entries.validateEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
async beforeSubmit() {
|
module.exports = PurchaseInvoiceServer;
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
@ -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
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -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'
|
||||||
|
@ -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() {
|
|
||||||
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() {
|
// apply common methods from TransactionServer
|
||||||
const entries = await this.getPosting();
|
Object.assign(SalesInvoiceServer.prototype, TransactionServer);
|
||||||
await entries.validateEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
async beforeInsert() {
|
module.exports = SalesInvoiceServer;
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
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 {
|
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)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user