2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 03:19:01 +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
},
{
fieldname: 'numberFormat',
fieldtype: 'Data'
},
{
fieldname: 'symbol',
fieldtype: 'Data'
},
{
fieldname: 'fullname',
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',
label: 'Customer',

View File

@ -3,6 +3,16 @@ const frappe = require('frappejs');
const LedgerPosting = require('../../../accounting/ledgerPosting');
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() {
let entries = new LedgerPosting({ reference: this, party: this.party });
await entries.debit(this.paymentAccount, this.amount);
@ -23,12 +33,18 @@ module.exports = class PaymentServer extends BaseDocument {
if (outstandingAmount === null) {
outstandingAmount = grandTotal;
}
if (0 >= this.amount || this.amount > outstandingAmount) {
if (
0 >= frappe.parseNumber(this.amount) ||
frappe.parseNumber(this.amount) >
frappe.parseNumber(outstandingAmount)
) {
frappe.call({
method: 'show-dialog',
args: {
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();

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',
label: 'Items',
@ -56,10 +71,20 @@ module.exports = {
required: true
},
{
fieldname: 'netTotal',
label: 'Net Total',
fieldname: 'baseNetTotal',
label: 'Net Total (INR)',
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,
readOnly: 1
},
@ -74,9 +99,9 @@ module.exports = {
<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'>{{ row.account }} ({{row.rate}}%)</div>
<div class='col-6 text-right'>
{{frappe.format(row.amount, 'Currency')}}
{{ row.amount }}
</div>
</div>
</div>
@ -84,10 +109,19 @@ module.exports = {
}
},
{
fieldname: 'grandTotal',
label: 'Grand Total',
fieldname: 'baseGrandTotal',
label: 'Grand Total (INR)',
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,
readOnly: 1
},
@ -107,7 +141,10 @@ module.exports = {
layout: [
// section 1
{
columns: [{ fields: ['supplier', 'account'] }, { fields: ['date'] }]
columns: [
{ fields: ['supplier', 'account'] },
{ fields: ['date', 'exchangeRate'] }
]
},
// section 2
@ -117,7 +154,17 @@ module.exports = {
// section 3
{
columns: [{ fields: ['netTotal', 'taxes', 'grandTotal'] }]
columns: [
{
fields: [
'baseNetTotal',
'netTotal',
'taxes',
'baseGrandTotal',
'grandTotal'
]
}
]
},
// section 4
@ -141,7 +188,7 @@ module.exports = {
{
referenceType: form.doc.doctype,
referenceName: form.doc.name,
amount: form.doc.grandTotal
amount: form.doc.outstandingAmount
}
];
payment.on('afterInsert', async () => {

View File

@ -5,15 +5,23 @@ const LedgerPosting = require('../../../accounting/ledgerPosting');
module.exports = class PurchaseInvoiceServer extends PurchaseInvoice {
async getPosting() {
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) {
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) {
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;
@ -32,6 +40,11 @@ module.exports = class PurchaseInvoiceServer extends PurchaseInvoice {
return [];
}
async beforeInsert() {
const entries = await this.getPosting();
await entries.validateEntries();
}
async afterSubmit() {
const entries = await this.getPosting();
await entries.post();
@ -39,7 +52,7 @@ module.exports = class PurchaseInvoiceServer extends PurchaseInvoice {
'PurchaseInvoice',
this.name,
'outstandingAmount',
this.grandTotal
this.baseGrandTotal
);
}

View File

@ -59,7 +59,11 @@ module.exports = {
fieldtype: 'Currency',
readOnly: 1,
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',

View File

@ -47,18 +47,29 @@ module.exports = {
target: 'Account',
formula: doc => doc.getFrom('Party', doc.customer, 'defaultAccount'),
getFilters: (query, control) => {
if (query)
return {
keywords: ['like', query],
isGroup: 0,
accountType: 'Receivable'
};
if (!query) return { isGroup: 0, accountType: 'Receivable' };
return {
keywords: ['like', query],
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',
label: 'Items',
@ -67,10 +78,20 @@ module.exports = {
required: true
},
{
fieldname: 'netTotal',
label: 'Net Total',
fieldname: 'baseNetTotal',
label: 'Net Total (INR)',
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,
readOnly: 1
},
@ -87,7 +108,7 @@ module.exports = {
<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')}}
{{ row.amount }}
</div>
</div>
</div>
@ -95,10 +116,19 @@ module.exports = {
}
},
{
fieldname: 'grandTotal',
label: 'Grand Total',
fieldname: 'baseGrandTotal',
label: 'Grand Total (INR)',
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,
readOnly: 1
},
@ -118,7 +148,10 @@ module.exports = {
layout: [
// section 1
{
columns: [{ fields: ['customer', 'account'] }, { fields: ['date'] }]
columns: [
{ fields: ['customer', 'account'] },
{ fields: ['date', 'exchangeRate'] }
]
},
// section 2
@ -128,7 +161,17 @@ module.exports = {
// section 3
{
columns: [{ fields: ['netTotal', 'taxes', 'grandTotal'] }]
columns: [
{
fields: [
'baseNetTotal',
'netTotal',
'taxes',
'baseGrandTotal',
'grandTotal'
]
}
]
},
// section 4
@ -152,7 +195,7 @@ module.exports = {
{
referenceType: form.doc.doctype,
referenceName: form.doc.name,
amount: form.doc.grandTotal
amount: form.doc.outstandingAmount
}
];
payment.on('afterInsert', async () => {

View File

@ -2,15 +2,98 @@ const BaseDocument = require('frappejs/model/document');
const frappe = require('frappejs');
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) {
if (row.tax) {
let tax = await this.getTax(row.tax);
let taxAmount = [];
for (let d of tax.details || []) {
const amt = (frappe.parseNumber(row.amount) * d.rate) / 100;
taxAmount.push({
account: d.account,
rate: d.rate,
amount: (row.amount * d.rate) / 100
amount: await this.formatIntoCustomerCurrency(amt)
});
}
return JSON.stringify(taxAmount);
@ -25,7 +108,7 @@ module.exports = class SalesInvoice extends BaseDocument {
return this._taxes[tax];
}
makeTaxSummary() {
async makeTaxSummary() {
if (!this.taxes) this.taxes = [];
// reset tax amount
@ -45,7 +128,12 @@ module.exports = class SalesInvoice extends BaseDocument {
for (let taxDetail of this.taxes) {
if (taxDetail.account === rowTaxDetail.account) {
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;
}
}
@ -66,16 +154,29 @@ module.exports = class SalesInvoice extends BaseDocument {
this.taxes = this.taxes.filter(d => d.amount);
}
getGrandTotal() {
this.makeTaxSummary();
async getGrandTotal() {
await this.makeTaxSummary();
let grandTotal = frappe.parseNumber(this.netTotal);
if (this.taxes) {
for (let row of this.taxes) {
grandTotal += row.amount;
grandTotal += frappe.parseNumber(row.amount);
}
}
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 {
async getPosting() {
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) {
await entries.credit(item.account, item.amount);
@ -31,6 +52,11 @@ module.exports = class SalesInvoiceServer extends SalesInvoice {
return [];
}
async beforeInsert() {
const entries = await this.getPosting();
await entries.validateEntries();
}
async afterSubmit() {
const entries = await this.getPosting();
await entries.post();
@ -38,7 +64,7 @@ module.exports = class SalesInvoiceServer extends SalesInvoice {
'SalesInvoice',
this.name,
'outstandingAmount',
this.grandTotal
this.baseGrandTotal
);
}

View File

@ -1,108 +1,128 @@
<template>
<div :style="$.font" style="font-family: sans-serif;">
<div class="row no-gutters pl-5 pr-5 mt-5">
<div :style="$.regularFontSize" class="col-6">
<company-address />
</div>
<div :style="$.regularFontSize" class="col-6 text-right">
<h2 :style="$.headerFontColor">INVOICE</h2>
<p :style="$.paraStyle"><strong>{{ doc.name }}</strong></p>
<p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p>
</div>
</div>
<div class="row pl-5 mt-5">
<div :style="$.regularFontSize" class="col-6 mt-1">
<customer-address :customer="doc.customer" />
</div>
</div>
<div :style="$.regularFontSize" class="row pl-5 pr-5 mt-5">
<div class="col-12">
<table class="table p-0">
<thead>
<tr :style="$.showBorderBottom">
<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 pl-0" style="width: 20%">{{ _("RATE") }}</th>
<th :style="$.hideBorderTop" class="text-left" style="width: 15%">{{ _("QTY") }}</th>
<th :style="$.hideBorderTop" class="text-right pr-1" style="width: 20%">{{ _("AMOUNT") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in doc.items" :key="row.idx">
<td class="text-left pl-1">{{ row.idx + 1 }}</td>
<td class="text-left">{{ row.item }}</td>
<td class="text-left pl-0">{{ frappe.format(row.rate, 'Currency') }}</td>
<td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ frappe.format(row.amount, 'Currency') }}</td>
</tr>
<tr>
<td colspan="2" class="text-left pl-1"></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>
</tr>
<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="$.bold" class="text-left pl-0">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(tax.amount, 'Currency') }}</td>
</tr>
<tr>
<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 :style="[$.bold, $.mediumFontSize, $.showBorderTop]" class="text-right pr-1" style="color: green;">
{{ frappe.format(doc.grandTotal, 'Currency') }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row pl-5 pr-5 mt-5">
<div :style="$.regularFontSize" class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showBorderBottom]" ><td :style="$.hideBorderTop" class="pl-0">NOTICE</td></tr>
<tr><td class="pl-0">{{ doc.terms }}</td></tr>
</tbody>
</table>
</div>
</div>
<div :style="$.font" style="font-family: sans-serif;">
<div class="row no-gutters pl-5 pr-5 mt-5">
<div :style="$.regularFontSize" class="col-6">
<company-address />
</div>
<div :style="$.regularFontSize" class="col-6 text-right">
<h2 :style="$.headerFontColor">INVOICE</h2>
<p :style="$.paraStyle">
<strong>{{ doc.name }}</strong>
</p>
<p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p>
</div>
</div>
<div class="row pl-5 mt-5">
<div :style="$.regularFontSize" class="col-6 mt-1">
<customer-address :customer="doc.customer" />
</div>
</div>
<div :style="$.regularFontSize" class="row pl-5 pr-5 mt-5">
<div class="col-12">
<table class="table p-0">
<thead>
<tr :style="$.showBorderBottom">
<th :style="$.hideBorderTop" class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th>
<th :style="$.hideBorderTop" class="text-left" style="width: 50%">{{ _("ITEM") }}</th>
<th :style="$.hideBorderTop" class="text-left pl-0" style="width: 15%">{{ _("RATE") }}</th>
<th :style="$.hideBorderTop" class="text-left" style="width: 10%">{{ _("QTY") }}</th>
<th
:style="$.hideBorderTop"
class="text-right pr-1"
style="width: 30%"
>{{ _("AMOUNT") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in doc.items" :key="row.idx">
<td class="text-left pl-1">{{ row.idx + 1 }}</td>
<td class="text-left">{{ row.item }}</td>
<td class="text-left pl-0">{{ row.rate }}</td>
<td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ row.amount }}</td>
</tr>
<tr>
<td colspan="2" class="text-left pl-1"></td>
<td colspan="2" :style="$.bold" class="text-left pl-0">SUBTOTAL</td>
<td :style="$.bold" class="text-right pr-1">{{ doc.netTotal }}</td>
</tr>
<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="$.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>
<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
:style="[$.bold, $.mediumFontSize, $.showBorderTop]"
class="text-right pr-1"
style="color: green;"
>{{ doc.grandTotal }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row pl-5 pr-5 mt-5">
<div :style="$.regularFontSize" class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showBorderBottom]">
<td :style="$.hideBorderTop" class="pl-0">NOTICE</td>
</tr>
<tr>
<td class="pl-0">{{ doc.terms }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import Styles from './InvoiceStyles';
import CompanyAddress from './CompanyAddress';
import CustomerAddress from './CustomerAddress';
export default {
name: 'InvoiceTemplate1',
components: {
CompanyAddress,
CustomerAddress
name: 'InvoiceTemplate1',
components: {
CompanyAddress,
CustomerAddress
},
props: ['doc', 'themeColor', 'font'],
data() {
return {
$: Styles
};
},
watch: {
themeColor: function() {
this.setTheme();
},
props: ['doc', 'themeColor', 'font'],
data() {
return {
$: Styles
}
},
watch: {
themeColor: function() {
this.setTheme();
},
font: function() {
this.setTheme();
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.headerFontColor.color = this.themeColor;
this.$.showBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.showBorderTop.borderTop = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
font: function() {
this.setTheme();
}
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.headerFontColor.color = this.themeColor;
this.$.showBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.showBorderTop.borderTop = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
}
};
</script>

View File

@ -1,117 +1,132 @@
<template>
<div :style="[$.regularFontSize, $.font]" style="font-family: sans-serif;">
<div class="row no-gutters p-5" :style="$.headerColor">
<div class="col-8 text-left">
<h1>INVOICE</h1>
</div>
<div class="col-4 text-right">
<company-address />
</div>
</div>
<div class="row p-5 mt-4">
<div class="col-4">
<customer-address :customer="doc.customer" />
</div>
<div class="col-4">
<p :style="[$.bold, $.mediumFontSize]">Invoice Number</p>
<p :style="$.paraStyle">{{ doc.name }}</p><br>
<p :style="[$.bold, $.mediumFontSize]">Date</p>
<p :style="$.paraStyle">{{doc.date}}</p>
</div>
<div class="col-4 text-right">
<p :style="[$.bold, $.mediumFontSize]">Invoice Total</p>
<h2 :style="$.fontColor">{{ frappe.format(doc.grandTotal, 'Currency') }}</h2>
</div>
</div>
<div class="row pl-5 pr-5 mt-3">
<div class="col-12">
<table class="table table-borderless p-0">
<thead>
<tr :style="[$.showBorderTop, $.fontColor]">
<th class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th>
<th class="text-left" style="width: 60%">{{ _("ITEM") }}</th>
<th class="text-left pl-0" style="width: 20%">{{ _("RATE") }}</th>
<th class="text-left" style="width: 15%">{{ _("QTY") }}</th>
<th class="text-right pr-1" style="width: 20%">{{ _("AMOUNT") }}</th>
</tr>
</thead>
<tbody>
<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">{{ row.item }}</td>
<td class="text-left pl-0">{{ frappe.format(row.rate, 'Currency') }}</td>
<td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ frappe.format(row.amount, 'Currency') }}</td>
</tr>
<tr><td colspan="5" style="padding: 4%"></td></tr>
<tr>
<td colspan="2" class="text-left pl-1"></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>
</tr>
<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="[$.bold, $.fontColor]" class="text-left pl-0">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.bold" class="text-right pr-1">{{ frappe.format(tax.amount, 'Currency') }}</td>
</tr>
<tr>
<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 :style="[$.bold, $.mediumFontSize]" class="text-right pr-1">{{ frappe.format(doc.grandTotal, 'Currency') }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row pl-5 pr-5 mt-5">
<div class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showNoticeBorderBottom]" ><td :style="$.hideBorderTop" class="pl-0">NOTICE</td></tr>
<tr><td class="pl-0">{{ doc.terms }}</td></tr>
</tbody>
</table>
</div>
</div>
<div :style="[$.regularFontSize, $.font]" style="font-family: sans-serif;">
<div class="row no-gutters p-5" :style="$.headerColor">
<div class="col-8 text-left">
<h1>INVOICE</h1>
</div>
<div class="col-4 text-right">
<company-address />
</div>
</div>
<div class="row p-5 mt-4">
<div class="col-4">
<customer-address :customer="doc.customer" />
</div>
<div class="col-4">
<p :style="[$.bold, $.mediumFontSize]">Invoice Number</p>
<p :style="$.paraStyle">{{ doc.name }}</p>
<br />
<p :style="[$.bold, $.mediumFontSize]">Date</p>
<p :style="$.paraStyle">{{doc.date}}</p>
</div>
<div class="col-4 text-right">
<p :style="[$.bold, $.mediumFontSize]">Invoice Total</p>
<h2 :style="$.fontColor">{{ doc.grandTotal }}</h2>
</div>
</div>
<div class="row pl-5 pr-5 mt-3">
<div class="col-12">
<table class="table table-borderless p-0">
<thead>
<tr :style="[$.showBorderTop, $.fontColor]">
<th class="text-left pl-0" style="width: 10%">{{ _("NO") }}</th>
<th class="text-left" style="width: 50%">{{ _("ITEM") }}</th>
<th class="text-left pl-0" style="width: 15%">{{ _("RATE") }}</th>
<th class="text-left" style="width: 10%">{{ _("QTY") }}</th>
<th class="text-right pr-1" style="width: 30%">{{ _("AMOUNT") }}</th>
</tr>
</thead>
<tbody>
<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">{{ row.item }}</td>
<td class="text-left pl-0">{{ row.rate }}</td>
<td class="text-left">{{ row.quantity }}</td>
<td class="text-right pr-1">{{ row.amount }}</td>
</tr>
<tr>
<td colspan="5" style="padding: 4%"></td>
</tr>
<tr>
<td colspan="2" class="text-left pl-1"></td>
<td colspan="2" :style="[$.bold, $.fontColor]" class="text-left pl-0">SUBTOTAL</td>
<td :style="$.bold" class="text-right pr-1">{{ doc.netTotal }}</td>
</tr>
<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="[$.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>
<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 :style="[$.bold, $.mediumFontSize]" class="text-right pr-1">{{ doc.grandTotal }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row pl-5 pr-5 mt-5">
<div class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showNoticeBorderBottom]">
<td :style="$.hideBorderTop" class="pl-0">NOTICE</td>
</tr>
<tr>
<td class="pl-0">{{ doc.terms }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import Styles from './InvoiceStyles';
import CompanyAddress from './CompanyAddress';
import CustomerAddress from './CustomerAddress';
export default {
name: 'InvoicePrint',
props: ['doc', 'themeColor', 'font'],
components: {
CompanyAddress,
CustomerAddress
name: 'InvoicePrint',
props: ['doc', 'themeColor', 'font'],
components: {
CompanyAddress,
CustomerAddress
},
data() {
return {
$: Styles
};
},
watch: {
themeColor: function() {
this.setTheme();
},
data() {
return {
$: Styles,
}
},
watch: {
themeColor: function() {
this.setTheme();
},
font: function() {
this.setTheme();
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.fontColor.color = this.themeColor;
this.$.headerColor.backgroundColor = this.themeColor;
this.$.showBorderBottom.borderBottom = '0.1rem solid #e0e0d1';
this.$.showNoticeBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.showBorderTop.borderTop = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
font: function() {
this.setTheme();
}
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.fontColor.color = this.themeColor;
this.$.headerColor.backgroundColor = this.themeColor;
this.$.showBorderBottom.borderBottom = '0.1rem solid #e0e0d1';
this.$.showNoticeBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.showBorderTop.borderTop = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
}
};
</script>

View File

@ -1,113 +1,145 @@
<template>
<div :style="[$.regularFontSize, $.font]" style="font-family: sans-serif;">
<div class="row no-gutters mt-5">
<div class="col-6" :style="$.bgColor"></div>
<div class="col-4 text-center" style="vertical-align: middle">
<h1>INVOICE</h1>
</div>
<div class="col-2" :style="$.bgColor"></div>
</div>
<div class="row no-gutters mt-5">
<div class="col-6 text-left pl-5">
<company-address />
</div>
<div class="col-6 pr-5 text-right">
<p :style="[$.bold, $.paraStyle, $.mediumFontSize]">{{ doc.name }}</p>
<p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p>
</div>
</div>
<div class="row no-gutters mt-5">
<div class="col-6 text-left pl-5">
<customer-address :customer="doc.customer" />
</div>
<div class="col-6"></div>
</div>
<div class="row mt-5 no-gutters">
<div class="col-12">
<table class="table">
<tbody>
<tr>
<td :style="[$.bold, $.showBorderRight, $.tablePadding]" style="width: 15" class="pl-5">{{ _("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 v-for="row in doc.items" :key="row.idx">
<td :style="$.tablePadding" class="pl-5 pr-5">{{ row.idx + 1 }}</td>
<td :style="$.tablePadding">{{ row.item }}</td>
<td :style="$.tablePadding" class="text-left">{{ frappe.format(row.rate, 'Currency') }}</td>
<td :style="$.tablePadding">{{ row.quantity }}</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(row.amount, 'Currency') }}</td>
</tr>
<tr><td colspan="5" style="padding: 4%"></td></tr>
<tr>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="$.tablePadding" colspan="2">SUBTOTAL</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(doc.netTotal, 'Currency') }}</td>
</tr>
<tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="$.tablePadding" med colspan="2">{{ tax.account.toUpperCase() }} ({{ tax.rate }}%)</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ frappe.format(tax.amount, 'Currency') }}</td>
</tr>
<tr>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></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>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row mt-5">
<div :style="$.regularFontSize" class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showBorderBottom]" >
<td :style="$.hideBorderTop" class="pl-5">NOTICE</td>
</tr>
<tr><td class="pl-5">{{ doc.terms }}</td></tr>
</tbody>
</table>
</div>
</div>
<div :style="[$.regularFontSize, $.font]" style="font-family: sans-serif;">
<div class="row no-gutters mt-5">
<div class="col-6" :style="$.bgColor"></div>
<div class="col-4 text-center" style="vertical-align: middle">
<h1>INVOICE</h1>
</div>
<div class="col-2" :style="$.bgColor"></div>
</div>
<div class="row no-gutters mt-5">
<div class="col-6 text-left pl-5">
<company-address />
</div>
<div class="col-6 pr-5 text-right">
<p :style="[$.bold, $.paraStyle, $.mediumFontSize]">{{ doc.name }}</p>
<p :style="$.paraStyle">{{ frappe.format(doc.date, 'Date') }}</p>
</div>
</div>
<div class="row no-gutters mt-5">
<div class="col-6 text-left pl-5">
<customer-address :customer="doc.customer" />
</div>
<div class="col-6"></div>
</div>
<div class="row mt-5 no-gutters">
<div class="col-12">
<table class="table">
<tbody>
<tr>
<td
:style="[$.bold, $.showBorderRight, $.tablePadding]"
style="width: 15"
class="pl-5"
>{{ _("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 v-for="row in doc.items" :key="row.idx">
<td :style="$.tablePadding" class="pl-5 pr-5">{{ row.idx + 1 }}</td>
<td :style="$.tablePadding">{{ row.item }}</td>
<td
:style="$.tablePadding"
class="text-left"
>{{ frappe.format(row.rate, 'Currency') }}</td>
<td :style="$.tablePadding">{{ row.quantity }}</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ row.amount }}</td>
</tr>
<tr>
<td colspan="5" style="padding: 4%"></td>
</tr>
<tr>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="$.tablePadding" colspan="2">SUBTOTAL</td>
<td :style="$.tablePadding" class="text-right pr-5">{{ doc.netTotal }}</td>
</tr>
<tr v-for="tax in doc.taxes" :key="tax.name">
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<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>
<td colspan="2" :style="[$.hideBorderTop, $.tablePadding]"></td>
<td :style="[$.bold, $.tablePadding, $.mediumFontSize]" colspan="2">TOTAL</td>
<td
:style="[$.bold, $.tablePadding, $.mediumFontSize]"
class="text-right pr-5"
>{{ doc.grandTotal }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row mt-5">
<div :style="$.regularFontSize" class="col-12">
<table class="table">
<tbody>
<tr :style="[$.bold, $.showBorderBottom]">
<td :style="$.hideBorderTop" class="pl-5">NOTICE</td>
</tr>
<tr>
<td class="pl-5">{{ doc.terms }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script>
import Styles from './InvoiceStyles';
import CompanyAddress from './CompanyAddress';
import CustomerAddress from './CustomerAddress';
export default {
name: 'InvoicePrint',
props: ['doc', 'themeColor', 'font'],
components: {
CompanyAddress,
CustomerAddress
name: 'InvoicePrint',
props: ['doc', 'themeColor', 'font'],
components: {
CompanyAddress,
CustomerAddress
},
data() {
return {
$: Styles
};
},
watch: {
themeColor: function() {
this.setTheme();
},
data() {
return {
$: Styles,
}
},
watch: {
themeColor: function() {
this.setTheme();
},
font: function() {
this.setTheme();
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.bgColor.backgroundColor = this.themeColor;
this.$.showBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
font: function() {
this.setTheme();
}
}
},
async created() {
this.$ = Styles;
this.setTheme();
},
methods: {
setTheme() {
this.$.bgColor.backgroundColor = this.themeColor;
this.$.showBorderBottom.borderBottom = `0.22rem solid ${this.themeColor}`;
this.$.font.fontFamily = this.font;
}
}
};
</script>

View File

@ -59,7 +59,11 @@ module.exports = {
fieldtype: 'Currency',
readOnly: 1,
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',

View File

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

View File

@ -13,7 +13,7 @@
<span
style="width: 100%"
:class="['Float', 'Currency'].includes(column.fieldtype) ? 'text-right':''"
>{{ frappe.format(column.getValue(doc), column.fieldtype || {}) }}</span>
>{{ getColumnValue(column, doc) }}</span>
</list-cell>
</list-row>
</div>
@ -55,6 +55,15 @@ export default {
frappe.listView.on('filterList', this.updateData.bind(this));
},
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() {
this.doctype = this.listConfig.doctype;
await this.updateData();