mirror of
https://github.com/frappe/books.git
synced 2025-01-11 10:38:14 +00:00
Merge pull request #220 from 18alantom/fix-reported-issues-and-improvements
fix: reported issues and improvements
This commit is contained in:
commit
c8732d6d8d
@ -97,8 +97,23 @@ module.exports = {
|
|||||||
return doc.getFrom('Address', doc.address, 'addressDisplay');
|
return doc.getFrom('Address', doc.address, 'addressDisplay');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'gstin',
|
||||||
|
label: 'GSTIN No.',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
hidden: form => {
|
||||||
|
return form.gstType === 'Registered Regular' ? 0 : 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
quickEditFields: ['email', 'phone', 'address', 'defaultAccount', 'currency']
|
quickEditFields: [
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'address',
|
||||||
|
'defaultAccount',
|
||||||
|
'currency',
|
||||||
|
'gstin'
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
@ -102,7 +102,7 @@ module.exports = {
|
|||||||
label: 'Amount',
|
label: 'Amount',
|
||||||
fieldtype: 'Currency',
|
fieldtype: 'Currency',
|
||||||
required: 1,
|
required: 1,
|
||||||
default: doc => doc.getSum('for', 'amount')
|
formula: doc => doc.getSum('for', 'amount')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'writeoff',
|
fieldname: 'writeoff',
|
||||||
|
@ -19,6 +19,9 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
partyField() {
|
partyField() {
|
||||||
return this.doc.doctype === 'SalesInvoice' ? 'customer' : 'supplier';
|
return this.doc.doctype === 'SalesInvoice' ? 'customer' : 'supplier';
|
||||||
|
},
|
||||||
|
isSalesInvoice() {
|
||||||
|
return this.doc.doctype === 'SalesInvoice';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="border h-full">
|
<div class="border h-full">
|
||||||
<div>
|
<div>
|
||||||
<div class="px-6 pt-6" v-if="printSettings && accountingSettings">
|
<div class="px-6 pt-6" v-if="printSettings">
|
||||||
<div class="flex text-sm text-gray-900 border-b pb-4">
|
<div class="flex text-sm text-gray-900 border-b pb-4">
|
||||||
<div class="w-1/3">
|
<div class="w-1/3">
|
||||||
<div v-if="printSettings.displayLogo">
|
<div v-if="printSettings.displayLogo">
|
||||||
@ -11,7 +11,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xl text-gray-700 font-semibold" v-else>
|
<div class="text-xl text-gray-700 font-semibold" v-else>
|
||||||
{{ accountingSettings.companyName }}
|
{{ frappe.AccountingSettings.companyName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/3">
|
<div class="w-1/3">
|
||||||
@ -19,7 +19,10 @@
|
|||||||
<div class="mt-1">{{ printSettings.phone }}</div>
|
<div class="mt-1">{{ printSettings.phone }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/3">
|
<div class="w-1/3">
|
||||||
<div v-if="address">{{ address.addressDisplay }}</div>
|
<div v-if="companyAddress">{{ companyAddress.addressDisplay }}</div>
|
||||||
|
<div v-if="printSettings && printSettings.gstin">
|
||||||
|
GSTIN: {{ printSettings.gstin }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -33,60 +36,62 @@
|
|||||||
{{ frappe.format(doc.date, 'Date') }}
|
{{ frappe.format(doc.date, 'Date') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/3">
|
<div class="w-1/3" v-if="party">
|
||||||
<div class="py-1 text-right text-lg font-semibold">
|
<div class="py-1 text-right text-lg font-semibold">
|
||||||
{{ doc[partyField.fieldname] }}
|
{{ party.name }}
|
||||||
</div>
|
|
||||||
<div v-if="partyDoc" class="mt-1 text-xs text-gray-600 text-right">
|
|
||||||
{{ partyDoc.addressDisplay }}
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="partyDoc && partyDoc.gstin"
|
v-if="party && party.addressDisplay"
|
||||||
class="mt-1 text-xs text-gray-600 text-right"
|
class="mt-1 text-xs text-gray-600 text-right"
|
||||||
>
|
>
|
||||||
GSTIN: {{ partyDoc.gstin }}
|
{{ party.addressDisplay }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="party && party.gstin"
|
||||||
|
class="mt-1 text-xs text-gray-600 text-right"
|
||||||
|
>
|
||||||
|
GSTIN: {{ party.gstin }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 px-6 text-base">
|
<div class="mt-2 px-6 text-base">
|
||||||
<div>
|
<div>
|
||||||
<Row class="text-gray-600 w-full" :ratio="ratio">
|
<div class="text-gray-600 w-full flex border-b">
|
||||||
<div class="py-4">
|
<div class="py-4 w-5/12">Item</div>
|
||||||
{{ _('No') }}
|
<div class="py-4 text-right w-1/12">Quantity</div>
|
||||||
|
<div class="py-4 text-right w-3/12">Rate</div>
|
||||||
|
<div class="py-4 text-right w-3/12">Amount</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="py-4"
|
class="flex py-1 text-gray-900 w-full border-b"
|
||||||
v-for="df in itemFields"
|
|
||||||
:key="df.fieldname"
|
|
||||||
:class="textAlign(df)"
|
|
||||||
>
|
|
||||||
{{ df.label }}
|
|
||||||
</div>
|
|
||||||
</Row>
|
|
||||||
<Row
|
|
||||||
class="text-gray-900 w-full"
|
|
||||||
v-for="row in doc.items"
|
v-for="row in doc.items"
|
||||||
:key="row.name"
|
:key="row.name"
|
||||||
:ratio="ratio"
|
|
||||||
>
|
>
|
||||||
<div class="py-4">
|
<div class="w-5/12 py-4">{{ row.item }}</div>
|
||||||
{{ row.idx + 1 }}
|
<div class="w-1/12 text-right py-4">
|
||||||
|
{{ format(row, 'quantity') }}
|
||||||
|
</div>
|
||||||
|
<div class="w-3/12 text-right py-4">{{ format(row, 'rate') }}</div>
|
||||||
|
<div class="w-3/12 text-right py-4">
|
||||||
|
{{ format(row, 'amount') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="py-4"
|
|
||||||
v-for="df in itemFields"
|
|
||||||
:key="df.fieldname"
|
|
||||||
:class="textAlign(df)"
|
|
||||||
>
|
|
||||||
{{ frappe.format(row[df.fieldname], df) }}
|
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-6 mt-2 flex justify-end text-base">
|
<div class="px-6 mt-2 flex justify-end text-base">
|
||||||
<div class="w-64">
|
<div class="w-1/2 bg-pink">
|
||||||
|
<div
|
||||||
|
class="uppercase text-sm tracking-widest font-semibold text-gray-800 mt-2"
|
||||||
|
>
|
||||||
|
Notes
|
||||||
|
</div>
|
||||||
|
<div class="my-4 text-lg whitespace-pre-line">
|
||||||
|
{{ doc.terms }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/2">
|
||||||
<div class="flex pl-2 justify-between py-3 border-b">
|
<div class="flex pl-2 justify-between py-3 border-b">
|
||||||
<div>{{ _('Subtotal') }}</div>
|
<div>{{ _('Subtotal') }}</div>
|
||||||
<div>{{ frappe.format(doc.netTotal, 'Currency') }}</div>
|
<div>{{ frappe.format(doc.netTotal, 'Currency') }}</div>
|
||||||
@ -111,57 +116,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import frappe from 'frappejs';
|
import Base from './Base';
|
||||||
import Row from '@/components/Row';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
props: ['doc', 'printSettings'],
|
extends: Base,
|
||||||
components: {
|
props: ['doc', 'printSettings']
|
||||||
Row
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
accountingSettings: null
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
meta() {
|
|
||||||
return this.doc && this.doc.meta;
|
|
||||||
},
|
|
||||||
address() {
|
|
||||||
return this.printSettings && this.printSettings.getLink('address');
|
|
||||||
},
|
|
||||||
partyDoc() {
|
|
||||||
return this.doc.getLink(this.partyField.fieldname);
|
|
||||||
},
|
|
||||||
partyField() {
|
|
||||||
let fieldname = {
|
|
||||||
SalesInvoice: 'customer',
|
|
||||||
PurchaseInvoice: 'supplier'
|
|
||||||
}[this.doc.doctype];
|
|
||||||
return this.meta.getField(fieldname);
|
|
||||||
},
|
|
||||||
itemFields() {
|
|
||||||
let itemsMeta = frappe.getMeta(`${this.doc.doctype}Item`);
|
|
||||||
return ['item', 'quantity', 'rate', 'amount'].map(fieldname =>
|
|
||||||
itemsMeta.getField(fieldname)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
ratio() {
|
|
||||||
return [0.3].concat(this.itemFields.map(() => 1));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
textAlign(df) {
|
|
||||||
return ['Currency', 'Int', 'Float'].includes(df.fieldtype)
|
|
||||||
? 'text-right'
|
|
||||||
: '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
this.accountingSettings = await frappe.getSingle('AccountingSettings');
|
|
||||||
await this.doc.loadLink(this.partyField.fieldname);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -23,12 +23,18 @@
|
|||||||
<div class="text-sm text-gray-800" v-if="companyAddress">
|
<div class="text-sm text-gray-800" v-if="companyAddress">
|
||||||
{{ companyAddress.addressDisplay }}
|
{{ companyAddress.addressDisplay }}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-sm text-gray-800"
|
||||||
|
v-if="printSettings && printSettings.gstin"
|
||||||
|
>
|
||||||
|
GSTIN: {{ printSettings.gstin }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-8 text-lg">
|
<div class="mt-8 text-lg">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="w-1/3 font-semibold">
|
<div class="w-1/3 font-semibold">
|
||||||
{{ doc.doctype === 'SalesInvoice' ? 'Invoice' : 'Bill' }}
|
{{ isSalesInvoice ? 'Invoice' : 'Bill' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/3 text-gray-800">
|
<div class="w-2/3 text-gray-800">
|
||||||
<div class="font-semibold">
|
<div class="font-semibold">
|
||||||
@ -41,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex">
|
<div class="mt-4 flex">
|
||||||
<div class="w-1/3 font-semibold">
|
<div class="w-1/3 font-semibold">
|
||||||
{{ doc.doctype === 'SalesInvoice' ? 'Customer' : 'Supplier' }}
|
{{ isSalesInvoice ? 'Customer' : 'Supplier' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-2/3 text-gray-800" v-if="party">
|
<div class="w-2/3 text-gray-800" v-if="party">
|
||||||
<div class="font-semibold">
|
<div class="font-semibold">
|
||||||
@ -50,6 +56,7 @@
|
|||||||
<div>
|
<div>
|
||||||
{{ party.addressDisplay }}
|
{{ party.addressDisplay }}
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="party && party.gstin">GSTIN: {{ party.gstin }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,22 +42,34 @@
|
|||||||
<div
|
<div
|
||||||
class="uppercase text-sm font-semibold tracking-widest text-gray-800"
|
class="uppercase text-sm font-semibold tracking-widest text-gray-800"
|
||||||
>
|
>
|
||||||
To
|
{{ isSalesInvoice ? 'To' : 'From' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 text-black leading-relaxed text-lg">
|
<div class="mt-4 text-black leading-relaxed text-lg">
|
||||||
{{ party.name }} <br />
|
{{ party.name }} <br />
|
||||||
{{ party.addressDisplay }}
|
{{ party.addressDisplay }}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mt-4 text-black leading-relaxed text-lg"
|
||||||
|
v-if="party && party.gstin"
|
||||||
|
>
|
||||||
|
GSTIN: {{ party.gstin }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2" v-if="companyAddress">
|
<div class="w-1/2" v-if="companyAddress">
|
||||||
<div
|
<div
|
||||||
class="uppercase text-sm font-semibold tracking-widest text-gray-800"
|
class="uppercase text-sm font-semibold tracking-widest text-gray-800 ml-8"
|
||||||
>
|
>
|
||||||
From
|
{{ isSalesInvoice ? 'From' : 'To' }}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 text-black leading-relaxed text-lg">
|
<div class="mt-4 ml-8 text-black leading-relaxed text-lg">
|
||||||
{{ companyAddress.addressDisplay }}
|
{{ companyAddress.addressDisplay }}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="mt-4 ml-8 text-black leading-relaxed text-lg"
|
||||||
|
v-if="printSettings && printSettings.gstin"
|
||||||
|
>
|
||||||
|
GSTIN: {{ printSettings.gstin }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-12 py-10 border-b">
|
<div class="px-12 py-10 border-b">
|
||||||
|
@ -25,14 +25,15 @@ module.exports = {
|
|||||||
label: 'Description',
|
label: 'Description',
|
||||||
fieldtype: 'Text',
|
fieldtype: 'Text',
|
||||||
formula: (row, doc) => doc.getFrom('Item', row.item, 'description'),
|
formula: (row, doc) => doc.getFrom('Item', row.item, 'description'),
|
||||||
hidden: 1
|
hidden: 1,
|
||||||
|
formulaDependsOn: ['item']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'quantity',
|
fieldname: 'quantity',
|
||||||
label: 'Quantity',
|
label: 'Quantity',
|
||||||
fieldtype: 'Float',
|
fieldtype: 'Float',
|
||||||
required: 1,
|
required: 1,
|
||||||
formula: () => 1
|
formula: row => row.quantity || 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'rate',
|
fieldname: 'rate',
|
||||||
@ -43,7 +44,8 @@ module.exports = {
|
|||||||
let baseRate = await doc.getFrom('Item', row.item, 'rate');
|
let baseRate = await doc.getFrom('Item', row.item, 'rate');
|
||||||
return baseRate / doc.exchangeRate;
|
return baseRate / doc.exchangeRate;
|
||||||
},
|
},
|
||||||
getCurrency: (row, doc) => doc.currency
|
getCurrency: (row, doc) => doc.currency,
|
||||||
|
formulaDependsOn: ['item']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'baseRate',
|
fieldname: 'baseRate',
|
||||||
@ -66,10 +68,8 @@ module.exports = {
|
|||||||
label: 'Tax',
|
label: 'Tax',
|
||||||
fieldtype: 'Link',
|
fieldtype: 'Link',
|
||||||
target: 'Tax',
|
target: 'Tax',
|
||||||
formula: (row, doc) => {
|
formula: (row, doc) => doc.getFrom('Item', row.item, 'tax'),
|
||||||
if (row.tax) return row.tax;
|
formulaDependsOn: ['item']
|
||||||
return doc.getFrom('Item', row.item, 'tax');
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname: 'amount',
|
fieldname: 'amount',
|
||||||
|
@ -54,23 +54,18 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openFileSelector() {
|
async openFileSelector() {
|
||||||
remote.dialog.showOpenDialog(
|
const options = {
|
||||||
remote.getCurrentWindow(),
|
|
||||||
{
|
|
||||||
title: frappe._('Select Image'),
|
title: frappe._('Select Image'),
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
filters: [
|
filters: [{ name: 'Image', extensions: ['png', 'jpg', 'jpeg', 'webp'] }]
|
||||||
{ name: 'Image', extensions: ['png', 'jpg', 'jpeg', 'webp'] }
|
};
|
||||||
]
|
|
||||||
},
|
const { filePaths } = await remote.dialog.showOpenDialog(options);
|
||||||
async files => {
|
if (filePaths && filePaths[0]) {
|
||||||
if (files && files[0]) {
|
let dataURL = await this.getDataURL(filePaths[0]);
|
||||||
let dataURL = await this.getDataURL(files[0]);
|
|
||||||
this.triggerChange(dataURL);
|
this.triggerChange(dataURL);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
getDataURL(filePath) {
|
getDataURL(filePath) {
|
||||||
let fs = require('fs');
|
let fs = require('fs');
|
||||||
|
@ -9,7 +9,6 @@
|
|||||||
:class="['resize-none', inputClasses]"
|
:class="['resize-none', inputClasses]"
|
||||||
:value="value"
|
:value="value"
|
||||||
:placeholder="inputPlaceholder"
|
:placeholder="inputPlaceholder"
|
||||||
:readonly="isReadOnly"
|
|
||||||
@blur="e => triggerChange(e.target.value)"
|
@blur="e => triggerChange(e.target.value)"
|
||||||
@focus="e => $emit('focus', e)"
|
@focus="e => $emit('focus', e)"
|
||||||
@input="e => $emit('input', e)"
|
@input="e => $emit('input', e)"
|
||||||
|
@ -199,6 +199,13 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
toValue(date) {
|
toValue(date) {
|
||||||
|
// toISOString is buggy and reduces the day by one
|
||||||
|
// this is because it considers the UTC timestamp
|
||||||
|
// in order to circumvent that we need to use luxon/moment
|
||||||
|
// but that refactor could take some time, so fixing the time difference
|
||||||
|
// as suggested in this answer.
|
||||||
|
// https://stackoverflow.com/a/16084846/3541205
|
||||||
|
date.setHours(0, -date.getTimezoneOffset(), 0, 0);
|
||||||
return date.toISOString().slice(0, 10);
|
return date.toISOString().slice(0, 10);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="py-4 px-8 flex justify-between window-drag">
|
<div class="my-4 mx-4 flex justify-between window-drag">
|
||||||
<div class="window-no-drag">
|
<div class="window-no-drag">
|
||||||
<slot name="title" />
|
<slot name="title" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,6 +63,10 @@ import router from './router';
|
|||||||
console.error(err, vm, info);
|
console.error(err, vm, info);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
process.on('unhandledRejection', error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
/* eslint-disable no-new */
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mt-6">
|
<div class="w-96 mx-auto mt-4">
|
||||||
<template v-if="hasData">
|
<template v-if="hasData">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="font-medium">{{ _('Cashflow') }}</div>
|
<div class="font-medium">{{ _('Cashflow') }}</div>
|
||||||
@ -21,7 +21,7 @@
|
|||||||
v-else
|
v-else
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 889 240"
|
viewBox="0 0 889 240"
|
||||||
class="w-full h-full"
|
class="w-10/12 h-5/6 ml-4 mr-4 my-2"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient x1="50%" y1="100%" x2="50%" y2=".889%" id="a">
|
<linearGradient x1="50%" y1="100%" x2="50%" y2=".889%" id="a">
|
||||||
|
@ -6,17 +6,17 @@
|
|||||||
<SearchBar class="ml-2" />
|
<SearchBar class="ml-2" />
|
||||||
</template>
|
</template>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div class="px-8">
|
<div class="mx-4">
|
||||||
<div class="border-t" />
|
<div class="border-t" />
|
||||||
<Cashflow />
|
<Cashflow />
|
||||||
<div class="my-10 border-t" />
|
<div class="my-4 mt-0 border-t" />
|
||||||
<UnpaidInvoices />
|
<UnpaidInvoices />
|
||||||
<div class="my-10 border-t" />
|
<div class="my-4 border-t" />
|
||||||
<div class="flex -mx-4">
|
<div class="w-96 flex mx-auto">
|
||||||
<div class="w-1/2 px-4">
|
<div class="w-1/2 mx-4">
|
||||||
<ProfitAndLoss />
|
<ProfitAndLoss />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 px-4">
|
<div class="w-1/2 mx-4">
|
||||||
<Expenses />
|
<Expenses />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="hasData"
|
v-if="hasData"
|
||||||
class="absolute text-base text-center font-semibold"
|
class="absolute text-base text-center font-semibold"
|
||||||
style="right: 3.8rem; top: 32%;"
|
style="right: 6rem; top: 32%;"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{{ frappe.format(totalExpense, 'Currency') }}
|
{{ frappe.format(totalExpense, 'Currency') }}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex -mx-4">
|
<div class="w-96 flex justify-between mx-auto">
|
||||||
<div
|
<div
|
||||||
class="w-1/2 px-4 flex flex-col justify-between"
|
class="w-5/12 mx-4 flex flex-col justify-between"
|
||||||
v-for="invoice in invoices"
|
v-for="invoice in invoices"
|
||||||
:key="invoice.title"
|
:key="invoice.title"
|
||||||
>
|
>
|
||||||
|
@ -22,11 +22,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<div
|
<div v-if="doc" class="flex justify-center flex-1 mb-8 mt-2">
|
||||||
v-if="doc"
|
|
||||||
class="flex justify-center flex-1 mb-8 mt-2"
|
|
||||||
:class="doc.submitted && 'pointer-events-none'"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="border rounded-lg shadow h-full flex flex-col justify-between"
|
class="border rounded-lg shadow h-full flex flex-col justify-between"
|
||||||
style="width: 600px"
|
style="width: 600px"
|
||||||
@ -45,6 +41,8 @@
|
|||||||
@change="value => doc.set('entryType', value)"
|
@change="value => doc.set('entryType', value)"
|
||||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
|
:read-only="doc.submitted"
|
||||||
|
:class="doc.submitted && 'pointer-events-none'"
|
||||||
/>
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
@ -54,6 +52,8 @@
|
|||||||
@change="value => doc.set('date', value)"
|
@change="value => doc.set('date', value)"
|
||||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
|
:read-only="doc.submitted"
|
||||||
|
:class="doc.submitted && 'pointer-events-none'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/3">
|
<div class="w-1/3">
|
||||||
@ -64,6 +64,8 @@
|
|||||||
@change="value => doc.set('referenceNumber', value)"
|
@change="value => doc.set('referenceNumber', value)"
|
||||||
input-class="bg-gray-100 p-2 text-base"
|
input-class="bg-gray-100 p-2 text-base"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
|
:read-only="doc.submitted"
|
||||||
|
:class="doc.submitted && 'pointer-events-none'"
|
||||||
/>
|
/>
|
||||||
<FormControl
|
<FormControl
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
@ -73,6 +75,8 @@
|
|||||||
@change="value => doc.set('referenceDate', value)"
|
@change="value => doc.set('referenceDate', value)"
|
||||||
input-class="bg-gray-100 px-3 py-2 text-base"
|
input-class="bg-gray-100 px-3 py-2 text-base"
|
||||||
:show-label="true"
|
:show-label="true"
|
||||||
|
:read-only="doc.submitted"
|
||||||
|
:class="doc.submitted && 'pointer-events-none'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -82,6 +86,7 @@
|
|||||||
:df="meta.getField('accounts')"
|
:df="meta.getField('accounts')"
|
||||||
:value="doc.accounts"
|
:value="doc.accounts"
|
||||||
:showHeader="true"
|
:showHeader="true"
|
||||||
|
:max-rows-before-overflow="4"
|
||||||
@change="value => doc.set('accounts', value)"
|
@change="value => doc.set('accounts', value)"
|
||||||
:read-only="doc.submitted"
|
:read-only="doc.submitted"
|
||||||
/>
|
/>
|
||||||
@ -97,6 +102,8 @@
|
|||||||
:df="meta.getField('userRemark')"
|
:df="meta.getField('userRemark')"
|
||||||
:value="doc.userRemark"
|
:value="doc.userRemark"
|
||||||
@change="value => doc.set('userRemark', value)"
|
@change="value => doc.set('userRemark', value)"
|
||||||
|
:class="doc.submitted && 'pointer-events-none'"
|
||||||
|
:read-only="doc.submitted"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right font-semibold text-green-700 px-3">
|
<div class="text-right font-semibold text-green-700 px-3">
|
||||||
|
@ -90,24 +90,20 @@ export default {
|
|||||||
let html = this.$refs.printContainer.innerHTML;
|
let html = this.$refs.printContainer.innerHTML;
|
||||||
makePDF(html, destination);
|
makePDF(html, destination);
|
||||||
},
|
},
|
||||||
getSavePath() {
|
async getSavePath() {
|
||||||
return new Promise(resolve => {
|
const options = {
|
||||||
remote.dialog.showSaveDialog(
|
|
||||||
remote.getCurrentWindow(),
|
|
||||||
{
|
|
||||||
title: this._('Select folder'),
|
title: this._('Select folder'),
|
||||||
defaultPath: `${this.name}.pdf`
|
defaultPath: `${this.name}.pdf`
|
||||||
},
|
};
|
||||||
filePath => {
|
|
||||||
|
let { filePath } = await remote.dialog.showSaveDialog(options);
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
if (!filePath.endsWith('.pdf')) {
|
if (!filePath.endsWith('.pdf')) {
|
||||||
filePath = filePath + '.pdf';
|
filePath = filePath + '.pdf';
|
||||||
}
|
}
|
||||||
resolve(filePath);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
return filePath;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
88
src/utils.js
88
src/utils.js
@ -9,15 +9,13 @@ import router from '@/router';
|
|||||||
import Avatar from '@/components/Avatar';
|
import Avatar from '@/components/Avatar';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
|
||||||
export function createNewDatabase() {
|
export async function createNewDatabase() {
|
||||||
return new Promise(resolve => {
|
const options = {
|
||||||
remote.dialog.showSaveDialog(
|
|
||||||
remote.getCurrentWindow(),
|
|
||||||
{
|
|
||||||
title: _('Select folder'),
|
title: _('Select folder'),
|
||||||
defaultPath: 'frappe-books.db'
|
defaultPath: 'frappe-books.db'
|
||||||
},
|
};
|
||||||
filePath => {
|
|
||||||
|
let { filePath } = await remote.dialog.showSaveDialog(options);
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
if (!filePath.endsWith('.db')) {
|
if (!filePath.endsWith('.db')) {
|
||||||
filePath = filePath + '.db';
|
filePath = filePath + '.db';
|
||||||
@ -31,37 +29,30 @@ export function createNewDatabase() {
|
|||||||
label: _('Overwrite'),
|
label: _('Overwrite'),
|
||||||
action() {
|
action() {
|
||||||
fs.unlinkSync(filePath);
|
fs.unlinkSync(filePath);
|
||||||
resolve(filePath);
|
return filePath;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ label: _('Cancel'), action() {} }
|
{ label: _('Cancel'), action() {} }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve(filePath);
|
return filePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadExistingDatabase() {
|
export async function loadExistingDatabase() {
|
||||||
return new Promise(resolve => {
|
const options = {
|
||||||
remote.dialog.showOpenDialog(
|
|
||||||
remote.getCurrentWindow(),
|
|
||||||
{
|
|
||||||
title: _('Select file'),
|
title: _('Select file'),
|
||||||
properties: ['openFile'],
|
properties: ['openFile'],
|
||||||
filters: [{ name: 'SQLite DB File', extensions: ['db'] }]
|
filters: [{ name: 'SQLite DB File', extensions: ['db'] }]
|
||||||
},
|
};
|
||||||
files => {
|
|
||||||
if (files && files[0]) {
|
let { filePaths } = await remote.dialog.showOpenDialog(options);
|
||||||
resolve(files[0]);
|
|
||||||
|
if (filePaths && filePaths[0]) {
|
||||||
|
return filePaths[0]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function connectToLocalDatabase(filepath) {
|
export async function connectToLocalDatabase(filepath) {
|
||||||
@ -90,22 +81,25 @@ export async function connectToLocalDatabase(filepath) {
|
|||||||
config.set('lastSelectedFilePath', filepath);
|
config.set('lastSelectedFilePath', filepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showMessageDialog({ message, description, buttons = [] }) {
|
export async function showMessageDialog({
|
||||||
|
message,
|
||||||
|
description,
|
||||||
|
buttons = []
|
||||||
|
}) {
|
||||||
let buttonLabels = buttons.map(a => a.label);
|
let buttonLabels = buttons.map(a => a.label);
|
||||||
remote.dialog.showMessageBox(
|
const { response } = await remote.dialog.showMessageBox(
|
||||||
remote.getCurrentWindow(),
|
remote.getCurrentWindow(),
|
||||||
{
|
{
|
||||||
message,
|
message,
|
||||||
detail: description,
|
detail: description,
|
||||||
buttons: buttonLabels
|
buttons: buttonLabels
|
||||||
},
|
}
|
||||||
response => {
|
);
|
||||||
|
|
||||||
let button = buttons[response];
|
let button = buttons[response];
|
||||||
if (button && button.action) {
|
if (button && button.action) {
|
||||||
button.action();
|
button.action();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteDocWithPrompt(doc) {
|
export function deleteDocWithPrompt(doc) {
|
||||||
@ -203,6 +197,16 @@ export function handleErrorWithDialog(e, doc) {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: a hack to find all the css from the current document and inject it to the print version
|
||||||
|
// remove this if you are able to fix and get the default css loading on the page
|
||||||
|
function injectCSS(contents) {
|
||||||
|
const styles = document.getElementsByTagName('style');
|
||||||
|
|
||||||
|
for (let style of styles) {
|
||||||
|
contents.insertCSS(style.innerHTML);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function makePDF(html, destination) {
|
export function makePDF(html, destination) {
|
||||||
const { BrowserWindow } = remote;
|
const { BrowserWindow } = remote;
|
||||||
|
|
||||||
@ -211,7 +215,8 @@ export function makePDF(html, destination) {
|
|||||||
height: 842,
|
height: 842,
|
||||||
show: false,
|
show: false,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION
|
nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
|
||||||
|
enableRemoteModule: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -234,23 +239,24 @@ export function makePDF(html, destination) {
|
|||||||
|
|
||||||
printWindow.webContents.executeJavaScript(code);
|
printWindow.webContents.executeJavaScript(code);
|
||||||
|
|
||||||
return new Promise(resolve => {
|
const printOptions = {
|
||||||
printWindow.webContents.on('did-finish-load', () => {
|
|
||||||
printWindow.webContents.printToPDF(
|
|
||||||
{
|
|
||||||
marginsType: 1, // no margin
|
marginsType: 1, // no margin
|
||||||
pageSize: 'A4',
|
pageSize: 'A4',
|
||||||
printBackground: true
|
printBackground: true,
|
||||||
},
|
printBackgrounds: true,
|
||||||
(error, data) => {
|
printSelectionOnly: false
|
||||||
if (error) throw error;
|
};
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
printWindow.webContents.on('did-finish-load', () => {
|
||||||
|
injectCSS(printWindow.webContents);
|
||||||
|
printWindow.webContents.printToPDF(printOptions).then(data => {
|
||||||
printWindow.close();
|
printWindow.close();
|
||||||
fs.writeFile(destination, data, error => {
|
fs.writeFile(destination, data, error => {
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
resolve(shell.openItem(destination));
|
resolve(shell.openItem(destination));
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user