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

Multicurrency

This commit is contained in:
thefalconx33 2019-09-03 15:11:36 +05:30
parent 562b4759e0
commit 3192cb556a
16 changed files with 754 additions and 350 deletions

View File

@ -41,6 +41,16 @@ module.exports = {
required: 0 required: 0
}, },
{
fieldname: 'numberFormat',
fieldtype: 'Data'
},
{
fieldname: 'symbol',
fieldtype: 'Data'
},
{ {
fieldname: 'fullname', fieldname: 'fullname',
label: 'Name', label: 'Name',

View File

@ -0,0 +1,53 @@
module.exports = {
name: 'Currency',
label: 'Currency',
doctype: 'DocType',
isSingle: 0,
keywordFields: ['name', 'symbol'],
fields: [
{
fieldname: 'name',
label: 'Currency Name',
fieldtype: 'Data',
required: 1
},
{
fieldname: 'fraction',
label: 'Fraction',
fieldtype: 'Data'
},
{
fieldname: 'fractionUnits',
label: 'Fraction Units',
fieldtype: 'Int'
},
{
label: 'Smallest Currency Fraction Value',
fieldname: 'smallestValue',
fieldtype: 'Currency'
},
{
label: 'Symbol',
fieldname: 'symbol',
fieldtype: 'Data'
},
{
fieldname: 'numberFormat',
fieldtype: 'Select',
label: 'Number Format',
options: [
'',
'#,###.##',
'#.###,##',
'# ###.##',
'# ###,##',
"#'###.##",
'#, ###.##',
'#,##,###.##',
'#,###.###',
'#.###',
'#,###'
]
}
]
};

View File

@ -29,6 +29,16 @@ module.exports = {
}; };
} }
}, },
{
fieldname: 'currency',
label: 'Currency',
fieldtype: 'Link',
target: 'Currency',
formula: async doc => {
const { currency } = await frappe.getSingle('AccountingSettings');
return currency;
}
},
{ {
fieldname: 'customer', fieldname: 'customer',
label: 'Customer', label: 'Customer',

View File

@ -3,6 +3,16 @@ const frappe = require('frappejs');
const LedgerPosting = require('../../../accounting/ledgerPosting'); const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class PaymentServer extends BaseDocument { module.exports = class PaymentServer extends BaseDocument {
async change({ changed }) {
if (changed === 'for') {
this.amount = 0;
for (let paymentReference of this.for) {
this.amount += frappe.parseNumber(paymentReference.amount);
}
this.amount = frappe.format(this.amount, 'Currency');
}
}
async getPosting() { async getPosting() {
let entries = new LedgerPosting({ reference: this, party: this.party }); let entries = new LedgerPosting({ reference: this, party: this.party });
await entries.debit(this.paymentAccount, this.amount); await entries.debit(this.paymentAccount, this.amount);
@ -23,12 +33,18 @@ module.exports = class PaymentServer extends BaseDocument {
if (outstandingAmount === null) { if (outstandingAmount === null) {
outstandingAmount = grandTotal; outstandingAmount = grandTotal;
} }
if (0 >= this.amount || this.amount > outstandingAmount) { if (
0 >= frappe.parseNumber(this.amount) ||
frappe.parseNumber(this.amount) >
frappe.parseNumber(outstandingAmount)
) {
frappe.call({ frappe.call({
method: 'show-dialog', method: 'show-dialog',
args: { args: {
title: 'Invalid Payment Entry', title: 'Invalid Payment Entry',
message: `Payment amount is greater than 0 and less than Outstanding amount (${outstandingAmount})` message: `Payment amount (${
this.amount
}) should be greater than 0 and less than Outstanding amount (${outstandingAmount})`
} }
}); });
throw new Error(); throw new Error();

View File

@ -48,6 +48,21 @@ module.exports = {
}; };
} }
}, },
{
fieldname: 'currency',
label: 'Customer Currency',
fieldtype: 'Link',
target: 'Currency',
hidden: 1,
formula: doc => doc.getFrom('Party', doc.supplier, 'currency')
},
{
fieldname: 'exchangeRate',
label: 'Exchange Rate',
fieldtype: 'Float',
placeholder: '1 USD = [?] INR',
hidden: doc => !doc.isForeignTransaction()
},
{ {
fieldname: 'items', fieldname: 'items',
label: 'Items', label: 'Items',
@ -56,10 +71,20 @@ module.exports = {
required: true required: true
}, },
{ {
fieldname: 'netTotal', fieldname: 'baseNetTotal',
label: 'Net Total', label: 'Net Total (INR)',
fieldtype: 'Currency', fieldtype: 'Currency',
formula: doc => doc.getSum('items', 'amount'), formula: async doc => await doc.getBaseNetTotal(),
disabled: true,
readOnly: 1
},
{
fieldname: 'netTotal',
label: 'Net Total (USD)',
fieldtype: 'Currency',
hidden: doc => !doc.isForeignTransaction(),
formula: async doc =>
await doc.formatIntoCustomerCurrency(doc.getSum('items', 'amount')),
disabled: true, disabled: true,
readOnly: 1 readOnly: 1
}, },
@ -76,7 +101,7 @@ module.exports = {
<div class='row' v-for='row in value'> <div class='row' v-for='row in value'>
<div class='col-6'>{{ row.account }} ({{row.rate}}%)</div> <div class='col-6'>{{ row.account }} ({{row.rate}}%)</div>
<div class='col-6 text-right'> <div class='col-6 text-right'>
{{frappe.format(row.amount, 'Currency')}} {{ row.amount }}
</div> </div>
</div> </div>
</div> </div>
@ -84,10 +109,19 @@ module.exports = {
} }
}, },
{ {
fieldname: 'grandTotal', fieldname: 'baseGrandTotal',
label: 'Grand Total', label: 'Grand Total (INR)',
fieldtype: 'Currency', fieldtype: 'Currency',
formula: doc => doc.getGrandTotal(), formula: async doc => await doc.getBaseGrandTotal(),
disabled: true,
readOnly: 1
},
{
fieldname: 'grandTotal',
label: 'Grand Total (USD)',
fieldtype: 'Currency',
hidden: doc => !doc.isForeignTransaction(),
formula: async doc => await doc.getGrandTotal(),
disabled: true, disabled: true,
readOnly: 1 readOnly: 1
}, },
@ -107,7 +141,10 @@ module.exports = {
layout: [ layout: [
// section 1 // section 1
{ {
columns: [{ fields: ['supplier', 'account'] }, { fields: ['date'] }] columns: [
{ fields: ['supplier', 'account'] },
{ fields: ['date', 'exchangeRate'] }
]
}, },
// section 2 // section 2
@ -117,7 +154,17 @@ module.exports = {
// section 3 // section 3
{ {
columns: [{ fields: ['netTotal', 'taxes', 'grandTotal'] }] columns: [
{
fields: [
'baseNetTotal',
'netTotal',
'taxes',
'baseGrandTotal',
'grandTotal'
]
}
]
}, },
// section 4 // section 4
@ -141,7 +188,7 @@ module.exports = {
{ {
referenceType: form.doc.doctype, referenceType: form.doc.doctype,
referenceName: form.doc.name, referenceName: form.doc.name,
amount: form.doc.grandTotal amount: form.doc.outstandingAmount
} }
]; ];
payment.on('afterInsert', async () => { payment.on('afterInsert', async () => {

View File

@ -5,15 +5,23 @@ const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class PurchaseInvoiceServer extends PurchaseInvoice { module.exports = 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.grandTotal); await entries.credit(this.account, this.baseGrandTotal);
for (let item of this.items) { for (let item of this.items) {
await entries.debit(item.account, item.amount); const baseItemAmount = frappe.format(
frappe.parseNumber(item.amount) * this.exchangeRate,
'Currency'
);
await entries.debit(item.account, baseItemAmount);
} }
if (this.taxes) { if (this.taxes) {
for (let tax of this.taxes) { for (let tax of this.taxes) {
await entries.debit(tax.account, tax.amount); const baseTaxAmount = frappe.format(
frappe.parseNumber(tax.amount) * this.exchangeRate,
'Currency'
);
await entries.debit(tax.account, baseTaxAmount);
} }
} }
return entries; return entries;
@ -32,6 +40,11 @@ module.exports = class PurchaseInvoiceServer extends PurchaseInvoice {
return []; return [];
} }
async beforeInsert() {
const entries = await this.getPosting();
await entries.validateEntries();
}
async afterSubmit() { async afterSubmit() {
const entries = await this.getPosting(); const entries = await this.getPosting();
await entries.post(); await entries.post();
@ -39,7 +52,7 @@ module.exports = class PurchaseInvoiceServer extends PurchaseInvoice {
'PurchaseInvoice', 'PurchaseInvoice',
this.name, this.name,
'outstandingAmount', 'outstandingAmount',
this.grandTotal this.baseGrandTotal
); );
} }

View File

@ -59,7 +59,11 @@ module.exports = {
fieldtype: 'Currency', fieldtype: 'Currency',
readOnly: 1, readOnly: 1,
disabled: true, disabled: true,
formula: (row, doc) => row.quantity * row.rate formula: async (row, doc) => {
return await doc.formatIntoCustomerCurrency(
row.quantity * frappe.parseNumber(row.rate)
);
}
}, },
{ {
fieldname: 'taxAmount', fieldname: 'taxAmount',

View File

@ -47,18 +47,29 @@ module.exports = {
target: 'Account', target: 'Account',
formula: doc => doc.getFrom('Party', doc.customer, 'defaultAccount'), formula: doc => doc.getFrom('Party', doc.customer, 'defaultAccount'),
getFilters: (query, control) => { getFilters: (query, control) => {
if (query) if (!query) return { isGroup: 0, accountType: 'Receivable' };
return { return {
keywords: ['like', query], keywords: ['like', query],
isGroup: 0, isGroup: 0,
accountType: 'Receivable' accountType: 'Receivable'
}; };
return {
isGroup: 0,
accountType: 'Receivable'
};
} }
}, },
{
fieldname: 'currency',
label: 'Customer Currency',
fieldtype: 'Link',
target: 'Currency',
hidden: 1,
formula: doc => doc.getFrom('Party', doc.customer, 'currency')
},
{
fieldname: 'exchangeRate',
label: 'Exchange Rate',
fieldtype: 'Float',
placeholder: '1 USD = [?] INR',
hidden: doc => !doc.isForeignTransaction()
},
{ {
fieldname: 'items', fieldname: 'items',
label: 'Items', label: 'Items',
@ -67,10 +78,20 @@ module.exports = {
required: true required: true
}, },
{ {
fieldname: 'netTotal', fieldname: 'baseNetTotal',
label: 'Net Total', label: 'Net Total (INR)',
fieldtype: 'Currency', fieldtype: 'Currency',
formula: doc => frappe.format(doc.getSum('items', 'amount'), 'Currency'), formula: async doc => await doc.getBaseNetTotal(),
disabled: true,
readOnly: 1
},
{
fieldname: 'netTotal',
label: 'Net Total (USD)',
fieldtype: 'Currency',
hidden: doc => !doc.isForeignTransaction(),
formula: async doc =>
await doc.formatIntoCustomerCurrency(doc.getSum('items', 'amount')),
disabled: true, disabled: true,
readOnly: 1 readOnly: 1
}, },
@ -87,7 +108,7 @@ module.exports = {
<div class='row' v-for='row in value'> <div class='row' v-for='row in value'>
<div class='col-6'>{{ row.account }} ({{row.rate}}%)</div> <div class='col-6'>{{ row.account }} ({{row.rate}}%)</div>
<div class='col-6 text-right'> <div class='col-6 text-right'>
{{ frappe.format(row.amount, 'Currency')}} {{ row.amount }}
</div> </div>
</div> </div>
</div> </div>
@ -95,10 +116,19 @@ module.exports = {
} }
}, },
{ {
fieldname: 'grandTotal', fieldname: 'baseGrandTotal',
label: 'Grand Total', label: 'Grand Total (INR)',
fieldtype: 'Currency', fieldtype: 'Currency',
formula: doc => frappe.format(doc.getGrandTotal(), 'Currency'), formula: async doc => await doc.getBaseGrandTotal(),
disabled: true,
readOnly: 1
},
{
fieldname: 'grandTotal',
label: 'Grand Total (USD)',
fieldtype: 'Currency',
hidden: doc => !doc.isForeignTransaction(),
formula: async doc => await doc.getGrandTotal(),
disabled: true, disabled: true,
readOnly: 1 readOnly: 1
}, },
@ -118,7 +148,10 @@ module.exports = {
layout: [ layout: [
// section 1 // section 1
{ {
columns: [{ fields: ['customer', 'account'] }, { fields: ['date'] }] columns: [
{ fields: ['customer', 'account'] },
{ fields: ['date', 'exchangeRate'] }
]
}, },
// section 2 // section 2
@ -128,7 +161,17 @@ module.exports = {
// section 3 // section 3
{ {
columns: [{ fields: ['netTotal', 'taxes', 'grandTotal'] }] columns: [
{
fields: [
'baseNetTotal',
'netTotal',
'taxes',
'baseGrandTotal',
'grandTotal'
]
}
]
}, },
// section 4 // section 4
@ -152,7 +195,7 @@ module.exports = {
{ {
referenceType: form.doc.doctype, referenceType: form.doc.doctype,
referenceName: form.doc.name, referenceName: form.doc.name,
amount: form.doc.grandTotal amount: form.doc.outstandingAmount
} }
]; ];
payment.on('afterInsert', async () => { payment.on('afterInsert', async () => {

View File

@ -2,15 +2,98 @@ const BaseDocument = require('frappejs/model/document');
const frappe = require('frappejs'); const frappe = require('frappejs');
module.exports = class SalesInvoice extends BaseDocument { module.exports = class SalesInvoice extends BaseDocument {
async change({ changed }) {
if (changed === 'items' || changed === 'exchangeRate') {
const companyCurrency = frappe.AccountingSettings.currency;
if (this.currency.length && this.currency !== companyCurrency) {
for (let item of this.items) {
if (item.rate && this.exchangeRate) {
const itemRate = await this.getFrom('Item', item.item, 'rate');
item.rate = frappe.parseNumber(itemRate) / this.exchangeRate;
if (item.quantity) {
item.amount = item.rate * item.quantity;
}
item.amount = await this.formatIntoCustomerCurrency(item.amount);
item.rate = await this.formatIntoCustomerCurrency(item.rate);
}
}
this.netTotal = await this.formatIntoCustomerCurrency(this.netTotal);
this.grandTotal = await this.formatIntoCustomerCurrency(
this.grandTotal
);
}
}
if (changed === 'customer' || changed === 'supplier') {
this.currency = await this.getFrom('Party', this[changed], 'currency');
this.exchangeRate = await this.getExchangeRate();
}
}
async formatIntoCustomerCurrency(value) {
const companyCurrency = frappe.AccountingSettings.currency;
if (this.currency.length && this.currency !== companyCurrency) {
const { numberFormat, symbol } = await this.getCustomerCurrencyInfo();
return frappe.format(value, {
fieldtype: 'Currency',
currencyInfo: { numberFormat, symbol }
});
} else {
return frappe.format(value, 'Currency');
}
}
isForeignTransaction() {
return this.currency
? this.currency !== frappe.AccountingSettings.currency
? 1
: 0
: 0;
}
async getCustomerCurrencyInfo() {
if (this.numberFormat || this.symbol) {
return { numberFormat: this.numberFormat, symbol: this.symbol };
}
const { numberFormat, symbol } = await frappe.getDoc(
'Currency',
this.currency
);
this.numberFormat = numberFormat;
this.symbol = symbol;
return { numberFormat, symbol };
}
async getExchangeRate() {
const companyCurrency = frappe.AccountingSettings.currency;
return this.currency === companyCurrency ? 1.0 : undefined;
}
async getBaseNetTotal() {
if (this.isForeignTransaction()) {
return frappe.format(
this.getSum('items', 'amount') * (this.exchangeRate || 0),
'Currency'
);
} else {
return await this.formatIntoCustomerCurrency(
this.getSum('items', 'amount')
);
}
}
async getRowTax(row) { async getRowTax(row) {
if (row.tax) { if (row.tax) {
let tax = await this.getTax(row.tax); let tax = await this.getTax(row.tax);
let taxAmount = []; let taxAmount = [];
for (let d of tax.details || []) { for (let d of tax.details || []) {
const amt = (frappe.parseNumber(row.amount) * d.rate) / 100;
taxAmount.push({ taxAmount.push({
account: d.account, account: d.account,
rate: d.rate, rate: d.rate,
amount: (row.amount * d.rate) / 100 amount: await this.formatIntoCustomerCurrency(amt)
}); });
} }
return JSON.stringify(taxAmount); return JSON.stringify(taxAmount);
@ -25,7 +108,7 @@ module.exports = class SalesInvoice extends BaseDocument {
return this._taxes[tax]; return this._taxes[tax];
} }
makeTaxSummary() { async makeTaxSummary() {
if (!this.taxes) this.taxes = []; if (!this.taxes) this.taxes = [];
// reset tax amount // reset tax amount
@ -45,7 +128,12 @@ module.exports = class SalesInvoice extends BaseDocument {
for (let taxDetail of this.taxes) { for (let taxDetail of this.taxes) {
if (taxDetail.account === rowTaxDetail.account) { if (taxDetail.account === rowTaxDetail.account) {
taxDetail.rate = rowTaxDetail.rate; taxDetail.rate = rowTaxDetail.rate;
taxDetail.amount += rowTaxDetail.amount; taxDetail.amount =
frappe.parseNumber(taxDetail.amount) +
frappe.parseNumber(rowTaxDetail.amount);
taxDetail.amount = await this.formatIntoCustomerCurrency(
taxDetail.amount
);
found = true; found = true;
} }
} }
@ -66,16 +154,29 @@ module.exports = class SalesInvoice extends BaseDocument {
this.taxes = this.taxes.filter(d => d.amount); this.taxes = this.taxes.filter(d => d.amount);
} }
getGrandTotal() { async getGrandTotal() {
this.makeTaxSummary(); await this.makeTaxSummary();
let grandTotal = frappe.parseNumber(this.netTotal); let grandTotal = frappe.parseNumber(this.netTotal);
if (this.taxes) { if (this.taxes) {
for (let row of this.taxes) { for (let row of this.taxes) {
grandTotal += row.amount; grandTotal += frappe.parseNumber(row.amount);
} }
} }
grandTotal = Math.floor(grandTotal * 100) / 100; grandTotal = Math.floor(grandTotal * 100) / 100;
return grandTotal; return await this.formatIntoCustomerCurrency(grandTotal);
}
async getBaseGrandTotal() {
await this.makeTaxSummary();
let baseGrandTotal = frappe.parseNumber(this.baseNetTotal);
if (this.taxes) {
for (let row of this.taxes) {
baseGrandTotal += frappe.parseNumber(row.amount) * this.exchangeRate;
}
}
baseGrandTotal = Math.floor(baseGrandTotal * 100) / 100;
return frappe.format(baseGrandTotal, 'Currency');
} }
}; };

View File

@ -4,7 +4,28 @@ const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class SalesInvoiceServer extends SalesInvoice { module.exports = 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.grandTotal); await entries.debit(this.account, this.baseGrandTotal);
if (this.isForeignTransaction()) {
for (let item of this.items) {
const baseItemAmount = frappe.format(
frappe.parseNumber(item.amount) * this.exchangeRate,
'Currency'
);
await entries.credit(item.account, baseItemAmount);
}
if (this.taxes) {
for (let tax of this.taxes) {
const baseTaxAmount = frappe.format(
frappe.parseNumber(tax.amount) * this.exchangeRate,
'Currency'
);
await entries.credit(tax.account, baseTaxAmount);
}
}
return entries;
}
for (let item of this.items) { for (let item of this.items) {
await entries.credit(item.account, item.amount); await entries.credit(item.account, item.amount);
@ -31,6 +52,11 @@ module.exports = class SalesInvoiceServer extends SalesInvoice {
return []; return [];
} }
async beforeInsert() {
const entries = await this.getPosting();
await entries.validateEntries();
}
async afterSubmit() { async afterSubmit() {
const entries = await this.getPosting(); const entries = await this.getPosting();
await entries.post(); await entries.post();
@ -38,7 +64,7 @@ module.exports = class SalesInvoiceServer extends SalesInvoice {
'SalesInvoice', 'SalesInvoice',
this.name, this.name,
'outstandingAmount', 'outstandingAmount',
this.grandTotal this.baseGrandTotal
); );
} }

View File

@ -6,7 +6,9 @@
</div> </div>
<div :style="$.regularFontSize" class="col-6 text-right"> <div :style="$.regularFontSize" class="col-6 text-right">
<h2 :style="$.headerFontColor">INVOICE</h2> <h2 :style="$.headerFontColor">INVOICE</h2>
<p :style="$.paraStyle"><strong>{{ doc.name }}</strong></p> <p :style="$.paraStyle">
<strong>{{ doc.name }}</strong>
</p>
<p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p> <p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p>
</div> </div>
</div> </div>
@ -21,36 +23,50 @@
<thead> <thead>
<tr :style="$.showBorderBottom"> <tr :style="$.showBorderBottom">
<th :style="$.hideBorderTop" class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th> <th :style="$.hideBorderTop" class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th>
<th :style="$.hideBorderTop" class="text-left" style="width: 60%">{{ _("ITEM") }}</th> <th :style="$.hideBorderTop" class="text-left" style="width: 50%">{{ _("ITEM") }}</th>
<th :style="$.hideBorderTop" class="text-left pl-0" style="width: 20%">{{ _("RATE") }}</th> <th :style="$.hideBorderTop" class="text-left pl-0" style="width: 15%">{{ _("RATE") }}</th>
<th :style="$.hideBorderTop" class="text-left" style="width: 15%">{{ _("QTY") }}</th> <th :style="$.hideBorderTop" class="text-left" style="width: 10%">{{ _("QTY") }}</th>
<th :style="$.hideBorderTop" class="text-right pr-1" style="width: 20%">{{ _("AMOUNT") }}</th> <th
:style="$.hideBorderTop"
class="text-right pr-1"
style="width: 30%"
>{{ _("AMOUNT") }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="row in doc.items" :key="row.idx"> <tr v-for="row in doc.items" :key="row.idx">
<td class="text-left pl-1">{{ row.idx + 1 }}</td> <td class="text-left pl-1">{{ row.idx + 1 }}</td>
<td class="text-left">{{ row.item }}</td> <td class="text-left">{{ row.item }}</td>
<td class="text-left pl-0">{{ frappe.format(row.rate, 'Currency') }}</td> <td class="text-left pl-0">{{ row.rate }}</td>
<td class="text-left">{{ row.quantity }}</td> <td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ frappe.format(row.amount, 'Currency') }}</td> <td class="text-right pr-1">{{ row.amount }}</td>
</tr> </tr>
<tr> <tr>
<td colspan="2" class="text-left pl-1"></td> <td colspan="2" class="text-left pl-1"></td>
<td colspan="2" :style="$.bold" class="text-left pl-0">SUBTOTAL</td> <td colspan="2" :style="$.bold" class="text-left pl-0">SUBTOTAL</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(doc.netTotal, 'Currency') }}</td> <td :style="$.bold" class="text-right pr-1">{{ doc.netTotal }}</td>
</tr> </tr>
<tr v-for="tax in doc.taxes" :key="tax.name"> <tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td> <td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="$.bold" class="text-left pl-0">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td> <td
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(tax.amount, 'Currency') }}</td> colspan="2"
:style="$.bold"
class="text-left pl-0"
>{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.bold" class="text-right pr-1">{{ tax.amount }}</td>
</tr> </tr>
<tr> <tr>
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td> <td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.mediumFontSize, $.showBorderTop]" class="text-left pl-0">TOTAL</td> <td
<td :style="[$.bold, $.mediumFontSize, $.showBorderTop]" class="text-right pr-1" style="color: green;"> colspan="2"
{{ frappe.format(doc.grandTotal, 'Currency') }} :style="[$.bold, $.mediumFontSize, $.showBorderTop]"
</td> class="text-left pl-0"
>TOTAL</td>
<td
:style="[$.bold, $.mediumFontSize, $.showBorderTop]"
class="text-right pr-1"
style="color: green;"
>{{ doc.grandTotal }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -60,8 +76,12 @@
<div :style="$.regularFontSize" class="col-12"> <div :style="$.regularFontSize" class="col-12">
<table class="table"> <table class="table">
<tbody> <tbody>
<tr :style="[$.bold, $.showBorderBottom]" ><td :style="$.hideBorderTop" class="pl-0">NOTICE</td></tr> <tr :style="[$.bold, $.showBorderBottom]">
<tr><td class="pl-0">{{ doc.terms }}</td></tr> <td :style="$.hideBorderTop" class="pl-0">NOTICE</td>
</tr>
<tr>
<td class="pl-0">{{ doc.terms }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -82,7 +102,7 @@ export default {
data() { data() {
return { return {
$: Styles $: Styles
} };
}, },
watch: { watch: {
themeColor: function() { themeColor: function() {
@ -104,5 +124,5 @@ export default {
this.$.font.fontFamily = this.font; this.$.font.fontFamily = this.font;
} }
} }
} };
</script> </script>

View File

@ -14,13 +14,14 @@
</div> </div>
<div class="col-4"> <div class="col-4">
<p :style="[$.bold, $.mediumFontSize]">Invoice Number</p> <p :style="[$.bold, $.mediumFontSize]">Invoice Number</p>
<p :style="$.paraStyle">{{ doc.name }}</p><br> <p :style="$.paraStyle">{{ doc.name }}</p>
<br />
<p :style="[$.bold, $.mediumFontSize]">Date</p> <p :style="[$.bold, $.mediumFontSize]">Date</p>
<p :style="$.paraStyle">{{doc.date}}</p> <p :style="$.paraStyle">{{doc.date}}</p>
</div> </div>
<div class="col-4 text-right"> <div class="col-4 text-right">
<p :style="[$.bold, $.mediumFontSize]">Invoice Total</p> <p :style="[$.bold, $.mediumFontSize]">Invoice Total</p>
<h2 :style="$.fontColor">{{ frappe.format(doc.grandTotal, 'Currency') }}</h2> <h2 :style="$.fontColor">{{ doc.grandTotal }}</h2>
</div> </div>
</div> </div>
<div class="row pl-5 pr-5 mt-3"> <div class="row pl-5 pr-5 mt-3">
@ -29,35 +30,45 @@
<thead> <thead>
<tr :style="[$.showBorderTop, $.fontColor]"> <tr :style="[$.showBorderTop, $.fontColor]">
<th class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th> <th class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th>
<th class="text-left" style="width: 60%">{{ _("ITEM") }}</th> <th class="text-left" style="width: 50%">{{ _("ITEM") }}</th>
<th class="text-left pl-0" style="width: 20%">{{ _("RATE") }}</th> <th class="text-left pl-0" style="width: 15%">{{ _("RATE") }}</th>
<th class="text-left" style="width: 15%">{{ _("QTY") }}</th> <th class="text-left" style="width: 10%">{{ _("QTY") }}</th>
<th class="text-right pr-1" style="width: 20%">{{ _("AMOUNT") }}</th> <th class="text-right pr-1" style="width: 30%">{{ _("AMOUNT") }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr :style="$.showBorderBottom" v-for="row in doc.items" :key="row.idx"> <tr :style="$.showBorderBottom" v-for="row in doc.items" :key="row.idx">
<td class="text-left pl-1">{{ row.idx + 1 }}</td> <td class="text-left pl-1">{{ row.idx + 1 }}</td>
<td class="text-left">{{ row.item }}</td> <td class="text-left">{{ row.item }}</td>
<td class="text-left pl-0">{{ frappe.format(row.rate, 'Currency') }}</td> <td class="text-left pl-0">{{ row.rate }}</td>
<td class="text-left">{{ row.quantity }}</td> <td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ frappe.format(row.amount, 'Currency') }}</td> <td class="text-right pr-1">{{ row.amount }}</td>
</tr>
<tr>
<td colspan="5" style="padding: 4%"></td>
</tr> </tr>
<tr><td colspan="5" style="padding: 4%"></td></tr>
<tr> <tr>
<td colspan="2" class="text-left pl-1"></td> <td colspan="2" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.fontColor]" class="text-left pl-0">SUBTOTAL</td> <td colspan="2" :style="[$.bold, $.fontColor]" class="text-left pl-0">SUBTOTAL</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(doc.netTotal, 'Currency') }}</td> <td :style="$.bold" class="text-right pr-1">{{ doc.netTotal }}</td>
</tr> </tr>
<tr v-for="tax in doc.taxes" :key="tax.name"> <tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td> <td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.fontColor]" class="text-left pl-0">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td> <td
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(tax.amount, 'Currency') }}</td> colspan="2"
:style="[$.bold, $.fontColor]"
class="text-left pl-0"
>{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.bold" class="text-right pr-1">{{ tax.amount }}</td>
</tr> </tr>
<tr> <tr>
<td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td> <td colspan="2" :style="$.hideBorderTop" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.fontColor, $.mediumFontSize]" class="text-left pl-0">TOTAL</td> <td
<td :style="[$.bold, $.mediumFontSize]" class="text-right pr-1">{{ frappe.format(doc.grandTotal, 'Currency') }}</td> colspan="2"
:style="[$.bold, $.fontColor, $.mediumFontSize]"
class="text-left pl-0"
>TOTAL</td>
<td :style="[$.bold, $.mediumFontSize]" class="text-right pr-1">{{ doc.grandTotal }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -67,8 +78,12 @@
<div class="col-12"> <div class="col-12">
<table class="table"> <table class="table">
<tbody> <tbody>
<tr :style="[$.bold, $.showNoticeBorderBottom]" ><td :style="$.hideBorderTop" class="pl-0">NOTICE</td></tr> <tr :style="[$.bold, $.showNoticeBorderBottom]">
<tr><td class="pl-0">{{ doc.terms }}</td></tr> <td :style="$.hideBorderTop" class="pl-0">NOTICE</td>
</tr>
<tr>
<td class="pl-0">{{ doc.terms }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -88,8 +103,8 @@ export default {
}, },
data() { data() {
return { return {
$: Styles, $: Styles
} };
}, },
watch: { watch: {
themeColor: function() { themeColor: function() {
@ -113,5 +128,5 @@ export default {
this.$.font.fontFamily = this.font; this.$.font.fontFamily = this.font;
} }
} }
} };
</script> </script>

View File

@ -27,34 +27,64 @@
<table class="table"> <table class="table">
<tbody> <tbody>
<tr> <tr>
<td :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 15" class="pl-5">{{ _("NO") }}</td> <td
<td :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 40%">{{ _("ITEM") }}</td> :style="[$.bold, $.showBorderRight, $.tablePadding]"
<td class="text-left" :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 20%">{{ _("RATE") }}</td> style="width: 15"
<td :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 10%">{{ _("QTY") }}</td> class="pl-5"
<td class="text-right pr-5" :style="[$.bold, $.tablePadding]" style="width: 20%">{{ _("AMOUNT") }}</td> >{{ _("NO") }}</td>
<td
:style="[$.bold, $.showBorderRight, $.tablePadding]"
style="width: 40%"
>{{ _("ITEM") }}</td>
<td
class="text-left"
:style="[$.bold, $.showBorderRight, $.tablePadding]"
style="width: 20%"
>{{ _("RATE") }}</td>
<td
:style="[$.bold, $.showBorderRight, $.tablePadding]"
style="width: 10%"
>{{ _("QTY") }}</td>
<td
class="text-right pr-5"
:style="[$.bold, $.tablePadding]"
style="width: 20%"
>{{ _("AMOUNT") }}</td>
</tr> </tr>
<tr v-for="row in doc.items" :key="row.idx"> <tr v-for="row in doc.items" :key="row.idx">
<td :style="$.tablePadding" class="pl-5 pr-5">{{ row.idx + 1 }}</td> <td :style="$.tablePadding" class="pl-5 pr-5">{{ row.idx + 1 }}</td>
<td :style="$.tablePadding">{{ row.item }}</td> <td :style="$.tablePadding">{{ row.item }}</td>
<td :style="$.tablePadding" class="text-left">{{ frappe.format(row.rate, 'Currency') }}</td> <td
:style="$.tablePadding"
class="text-left"
>{{ frappe.format(row.rate, 'Currency') }}</td>
<td :style="$.tablePadding">{{ row.quantity }}</td> <td :style="$.tablePadding">{{ row.quantity }}</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(row.amount, 'Currency') }}</td> <td :style="$.tablePadding" class="text-right pr-5">{{ row.amount }}</td>
</tr>
<tr>
<td colspan="5" style="padding: 4%"></td>
</tr> </tr>
<tr><td colspan="5" style="padding: 4%"></td></tr>
<tr> <tr>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td> <td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="$.tablePadding" colspan="2">SUBTOTAL</td> <td :style="$.tablePadding" colspan="2">SUBTOTAL</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(doc.netTotal, 'Currency') }}</td> <td :style="$.tablePadding" class="text-right pr-5">{{ doc.netTotal }}</td>
</tr> </tr>
<tr v-for="tax in doc.taxes" :key="tax.name"> <tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td> <td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="$.tablePadding" med colspan="2">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td> <td
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(tax.amount, 'Currency') }}</td> :style="$.tablePadding"
med
colspan="2"
>{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ tax.amount }}</td>
</tr> </tr>
<tr> <tr>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td> <td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="[$.bold, $.tablePadding, $.mediumFontSize]" colspan="2">TOTAL</td> <td :style="[$.bold, $.tablePadding, $.mediumFontSize]" colspan="2">TOTAL</td>
<td :style="[$.bold, $.tablePadding, $.mediumFontSize]" class="text-right pr-5">{{ frappe.format(doc.grandTotal, 'Currency') }}</td> <td
:style="[$.bold, $.tablePadding, $.mediumFontSize]"
class="text-right pr-5"
>{{ doc.grandTotal }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -67,7 +97,9 @@
<tr :style="[$.bold, $.showBorderBottom]"> <tr :style="[$.bold, $.showBorderBottom]">
<td :style="$.hideBorderTop" class="pl-5">NOTICE</td> <td :style="$.hideBorderTop" class="pl-5">NOTICE</td>
</tr> </tr>
<tr><td class="pl-5">{{ doc.terms }}</td></tr> <tr>
<td class="pl-5">{{ doc.terms }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -87,8 +119,8 @@ export default {
}, },
data() { data() {
return { return {
$: Styles, $: Styles
} };
}, },
watch: { watch: {
themeColor: function() { themeColor: function() {
@ -109,5 +141,5 @@ export default {
this.$.font.fontFamily = this.font; this.$.font.fontFamily = this.font;
} }
} }
} };
</script> </script>

View File

@ -59,7 +59,11 @@ module.exports = {
fieldtype: 'Currency', fieldtype: 'Currency',
readOnly: 1, readOnly: 1,
disabled: true, disabled: true,
formula: (row, doc) => row.quantity * row.rate formula: async (row, doc) => {
return await doc.formatIntoCustomerCurrency(
row.quantity * frappe.parseNumber(row.rate)
);
}
}, },
{ {
fieldname: 'taxAmount', fieldname: 'taxAmount',

View File

@ -3,6 +3,7 @@ module.exports = {
SetupWizard: require('./doctype/SetupWizard/SetupWizard'), SetupWizard: require('./doctype/SetupWizard/SetupWizard'),
DashboardSettings: require('./doctype/DashboardSettings/DashboardSettings'), DashboardSettings: require('./doctype/DashboardSettings/DashboardSettings'),
DashboardChart: require('./doctype/DashboardChart/DashboardChart'), DashboardChart: require('./doctype/DashboardChart/DashboardChart'),
Currency: require('./doctype/Currency/Currency'),
Color: require('./doctype/Color/Color'), Color: require('./doctype/Color/Color'),
Account: require('./doctype/Account/Account.js'), Account: require('./doctype/Account/Account.js'),
AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'), AccountingSettings: require('./doctype/AccountingSettings/AccountingSettings'),

View File

@ -13,7 +13,7 @@
<span <span
style="width: 100%" style="width: 100%"
:class="['Float', 'Currency'].includes(column.fieldtype) ? 'text-right':''" :class="['Float', 'Currency'].includes(column.fieldtype) ? 'text-right':''"
>{{ frappe.format(column.getValue(doc), column.fieldtype || {}) }}</span> >{{ getColumnValue(column, doc) }}</span>
</list-cell> </list-cell>
</list-row> </list-row>
</div> </div>
@ -55,6 +55,15 @@ export default {
frappe.listView.on('filterList', this.updateData.bind(this)); frappe.listView.on('filterList', this.updateData.bind(this));
}, },
methods: { methods: {
getColumnValue(column, doc) {
// Since currency is formatted in customer currency
// frappe.format parses it back into company currency
if (['Float', 'Currency'].includes(column.fieldtype)) {
return column.getValue(doc);
} else {
return frappe.format(column.getValue(doc), column.fieldtype);
}
},
async setupColumnsAndData() { async setupColumnsAndData() {
this.doctype = this.listConfig.doctype; this.doctype = this.listConfig.doctype;
await this.updateData(); await this.updateData();