mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
feat: Invoice Template Customizer
- Template, Color and Font fields in PrintSettings - font-manager for loading system fonts - 3 Invoice Templates: Default, Minimal and Business - Delete unused old Templates - Dont show Delete button in Singles - Rename font family "Inter var experimental" to "Inter"
This commit is contained in:
parent
5316756843
commit
138b0673a8
@ -1,3 +1,8 @@
|
||||
const theme = require('@/theme');
|
||||
const fontManager = require('font-manager');
|
||||
const uniq = require('lodash/uniq');
|
||||
let fonts = [];
|
||||
|
||||
module.exports = {
|
||||
name: 'PrintSettings',
|
||||
label: 'Print Settings',
|
||||
@ -54,21 +59,63 @@ module.exports = {
|
||||
fieldname: 'template',
|
||||
label: 'Template',
|
||||
fieldtype: 'Select',
|
||||
options: ['Basic', 'Modern'],
|
||||
default: 'Basic'
|
||||
options: ['Default', 'Minimal', 'Business'],
|
||||
default: 'Default'
|
||||
},
|
||||
{
|
||||
fieldname: 'color',
|
||||
label: 'Theme Color',
|
||||
fieldtype: 'Data'
|
||||
label: 'Color',
|
||||
placeholder: 'Select Color',
|
||||
fieldtype: 'Color',
|
||||
colors: [
|
||||
'red',
|
||||
'orange',
|
||||
'yellow',
|
||||
'green',
|
||||
'teal',
|
||||
'blue',
|
||||
'indigo',
|
||||
'purple',
|
||||
'pink'
|
||||
]
|
||||
.map(color => {
|
||||
let label = color[0].toUpperCase() + color.slice(1);
|
||||
return {
|
||||
label,
|
||||
value: theme.colors[color]['500']
|
||||
};
|
||||
})
|
||||
.concat({
|
||||
label: 'Black',
|
||||
value: theme.colors['black']
|
||||
})
|
||||
},
|
||||
{
|
||||
fieldname: 'font',
|
||||
label: 'Font',
|
||||
fieldtype: 'Select',
|
||||
options: ['Inter', 'Roboto'],
|
||||
fieldtype: 'AutoComplete',
|
||||
getList() {
|
||||
return new Promise(resolve => {
|
||||
if (fonts.length > 0) {
|
||||
resolve(fonts);
|
||||
} else {
|
||||
fontManager.getAvailableFonts(_fonts => {
|
||||
fonts = ['Inter'].concat(uniq(_fonts.map(f => f.family)).sort());
|
||||
resolve(fonts);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
default: 'Inter'
|
||||
}
|
||||
],
|
||||
quickEditFields: ['email', 'phone', 'address', 'gstin']
|
||||
quickEditFields: [
|
||||
'template',
|
||||
'color',
|
||||
'font',
|
||||
'email',
|
||||
'phone',
|
||||
'address',
|
||||
'gstin'
|
||||
]
|
||||
};
|
||||
|
@ -1,153 +1,24 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<div class="px-6 pt-6" v-if="printSettings && accountingSettings">
|
||||
<div class="flex text-sm text-gray-900 border-b pb-4">
|
||||
<div class="w-1/3">
|
||||
<div v-if="printSettings.displayLogo">
|
||||
<img
|
||||
class="h-12 max-w-32 object-contain"
|
||||
:src="printSettings.logo"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xl text-gray-700 font-semibold" v-else>
|
||||
{{ accountingSettings.companyName }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div>{{ printSettings.email }}</div>
|
||||
<div class="mt-1">{{ printSettings.phone }}</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div v-if="address">{{ address.addressDisplay }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 px-6">
|
||||
<div class="flex justify-between">
|
||||
<div class="w-1/3">
|
||||
<h1 class="text-2xl font-semibold">
|
||||
{{ doc.name }}
|
||||
</h1>
|
||||
<div class="py-2 text-base">
|
||||
{{ frappe.format(doc.date, 'Date') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div class="py-1 text-right text-lg font-semibold">
|
||||
{{ doc[partyField.fieldname] }}
|
||||
</div>
|
||||
<div v-if="partyDoc" class="mt-1 text-xs text-gray-600 text-right">
|
||||
{{ partyDoc.addressDisplay }}
|
||||
</div>
|
||||
<div
|
||||
v-if="partyDoc && partyDoc.gstin"
|
||||
class="mt-1 text-xs text-gray-600 text-right"
|
||||
>
|
||||
GSTIN: {{ partyDoc.gstin }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 px-6 text-base">
|
||||
<div>
|
||||
<Row class="text-gray-600 w-full" :ratio="ratio">
|
||||
<div class="py-4">
|
||||
{{ _('No') }}
|
||||
</div>
|
||||
<div class="py-4" 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" :key="row.name" :ratio="ratio">
|
||||
<div class="py-4">
|
||||
{{ row.idx + 1 }}
|
||||
</div>
|
||||
<div class="py-4" v-for="df in itemFields" :key="df.fieldname" :class="textAlign(df)">
|
||||
{{ frappe.format(row[df.fieldname], df) }}
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 mt-2 flex justify-end text-base">
|
||||
<div class="w-64">
|
||||
<div class="flex pl-2 justify-between py-3 border-b">
|
||||
<div>{{ _('Subtotal') }}</div>
|
||||
<div>{{ frappe.format(doc.netTotal, 'Currency') }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex pl-2 justify-between py-3"
|
||||
v-for="tax in doc.taxes"
|
||||
:key="tax.name"
|
||||
>
|
||||
<div>{{ tax.account }} ({{ tax.rate }}%)</div>
|
||||
<div>{{ frappe.format(tax.amount, 'Currency') }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex pl-2 justify-between py-3 border-t text-green-600 font-semibold text-base"
|
||||
>
|
||||
<div>{{ _('Grand Total') }}</div>
|
||||
<div>{{ frappe.format(doc.grandTotal, 'Currency') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<component :is="printComponent" :doc="doc" :print-settings="printSettings" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Row from '@/components/Row';
|
||||
import Default from './Templates/Default';
|
||||
import Minimal from './Templates/Minimal';
|
||||
import Business from './Templates/Business';
|
||||
|
||||
export default {
|
||||
name: 'InvoiceTemplate',
|
||||
props: ['doc'],
|
||||
components: {
|
||||
Row
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
accountingSettings: null,
|
||||
printSettings: null
|
||||
};
|
||||
},
|
||||
props: ['doc', 'printSettings'],
|
||||
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 itemsMeta.tableFields.map(fieldname =>
|
||||
itemsMeta.getField(fieldname)
|
||||
);
|
||||
},
|
||||
ratio() {
|
||||
return [0.3].concat(this.itemFields.map(_ => 1));
|
||||
printComponent() {
|
||||
let type = this.printSettings.template || 'Default';
|
||||
return {
|
||||
Default,
|
||||
Minimal,
|
||||
Business
|
||||
}[type];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
textAlign(df) {
|
||||
return ['Currency', 'Int', 'Float'].includes(df.fieldtype)
|
||||
? 'text-right'
|
||||
: '';
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
this.printSettings = await frappe.getSingle('PrintSettings');
|
||||
this.accountingSettings = await frappe.getSingle('AccountingSettings');
|
||||
await this.doc.loadLink(this.partyField.fieldname);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -119,7 +119,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
fieldname: 'terms',
|
||||
label: 'Terms',
|
||||
label: 'Notes',
|
||||
fieldtype: 'Text'
|
||||
}
|
||||
],
|
||||
|
@ -1,28 +0,0 @@
|
||||
async function getCompanyDetails() {
|
||||
let companyDetails = {
|
||||
name: null,
|
||||
address: {}
|
||||
};
|
||||
|
||||
let companySettings = await frappe.getDoc('CompanySettings');
|
||||
companyDetails.name = companySettings.companyName;
|
||||
|
||||
let companyAddress = await getAddress(companySettings.companyAddress);
|
||||
companyDetails.address = companyAddress;
|
||||
return companyDetails;
|
||||
}
|
||||
|
||||
async function getCustomerAddress(customer) {
|
||||
let customers = await frappe.db.getAll({ doctype: 'Party', fields:['name, address'], filters: { name: customer }});
|
||||
let customerDetails = await frappe.getDoc('Party', customers[0].name);
|
||||
return await getAddress(customerDetails.address);
|
||||
}
|
||||
|
||||
async function getAddress(addressName) {
|
||||
return await frappe.getDoc('Address', addressName);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCompanyDetails,
|
||||
getCustomerAddress
|
||||
}
|
25
models/doctype/SalesInvoice/Templates/Base.vue
Normal file
25
models/doctype/SalesInvoice/Templates/Base.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script>
|
||||
import frappe from 'frappejs';
|
||||
export default {
|
||||
name: 'Base',
|
||||
props: ['doc', 'printSettings'],
|
||||
data: () => ({ party: null, companyAddress: null }),
|
||||
methods: {
|
||||
format(row, fieldname) {
|
||||
let value = row.get(fieldname);
|
||||
return frappe.format(value, row.meta.getField(fieldname));
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.doc.loadLink(this.partyField);
|
||||
this.party = this.doc.getLink(this.partyField);
|
||||
await this.printSettings.loadLink('address');
|
||||
this.companyAddress = this.printSettings.getLink('address');
|
||||
},
|
||||
computed: {
|
||||
partyField() {
|
||||
return this.doc.doctype === 'SalesInvoice' ? 'customer' : 'supplier';
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
125
models/doctype/SalesInvoice/Templates/Business.vue
Normal file
125
models/doctype/SalesInvoice/Templates/Business.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<div class="bg-white border" :style="{ 'font-family': printSettings.font }">
|
||||
<div class="bg-gray-100 px-12 py-10">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center rounded h-16">
|
||||
<div class="mr-4" v-if="printSettings.displayLogo">
|
||||
<img
|
||||
class="h-12 max-w-32 object-contain"
|
||||
:src="printSettings.logo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="font-semibold text-xl"
|
||||
:style="{ color: printSettings.color }"
|
||||
>
|
||||
{{ frappe.AccountingSettings.companyName }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-800" v-if="companyAddress">
|
||||
{{ companyAddress.addressDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 text-lg">
|
||||
<div class="flex">
|
||||
<div class="w-1/3 font-semibold">
|
||||
{{ doc.doctype === 'SalesInvoice' ? 'Invoice' : 'Bill' }}
|
||||
</div>
|
||||
<div class="w-2/3 text-gray-800">
|
||||
<div class="font-semibold">
|
||||
{{ doc.name }}
|
||||
</div>
|
||||
<div>
|
||||
{{ frappe.format(doc.date, 'Date') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex">
|
||||
<div class="w-1/3 font-semibold">
|
||||
{{ doc.doctype === 'SalesInvoice' ? 'Customer' : 'Supplier' }}
|
||||
</div>
|
||||
<div class="w-2/3 text-gray-800" v-if="party">
|
||||
<div class="font-semibold">
|
||||
{{ party.name }}
|
||||
</div>
|
||||
<div>
|
||||
{{ party.addressDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 py-12 text-lg">
|
||||
<div class="mb-4 flex font-semibold ">
|
||||
<div class="w-4/12">Item</div>
|
||||
<div class="w-2/12 text-right">Quantity</div>
|
||||
<div class="w-3/12 text-right">Rate</div>
|
||||
<div class="w-3/12 text-right">Amount</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex py-1 text-gray-800"
|
||||
v-for="row in doc.items"
|
||||
:key="row.name"
|
||||
>
|
||||
<div class="w-4/12">{{ row.item }}</div>
|
||||
<div class="w-2/12 text-right">{{ format(row, 'quantity') }}</div>
|
||||
<div class="w-3/12 text-right">{{ format(row, 'rate') }}</div>
|
||||
<div class="w-3/12 text-right">{{ format(row, 'amount') }}</div>
|
||||
</div>
|
||||
<div class="mt-12">
|
||||
<div class="flex -mx-3">
|
||||
<div class="flex flex-1 justify-between p-3 bg-gray-100">
|
||||
<div>
|
||||
<div class="text-gray-800">{{ _('Subtotal') }}</div>
|
||||
<div class="text-xl mt-2">
|
||||
{{ frappe.format(doc.netTotal, 'Currency') }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="tax in doc.taxes" :key="tax.name">
|
||||
<div class="text-gray-800">
|
||||
{{ tax.account }} ({{ tax.rate }}%)
|
||||
</div>
|
||||
<div class="text-xl mt-2">
|
||||
{{ frappe.format(tax.amount, 'Currency') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="p-3 text-right text-white"
|
||||
:style="{ backgroundColor: printSettings.color }"
|
||||
>
|
||||
<div>
|
||||
<div>{{ _('Grand Total') }}</div>
|
||||
<div class="text-2xl mt-2 font-semibold">
|
||||
{{ frappe.format(doc.grandTotal, 'Currency') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-12" v-if="doc.terms">
|
||||
<template>
|
||||
<div
|
||||
class="uppercase text-sm tracking-widest font-semibold text-gray-800"
|
||||
>
|
||||
Notes
|
||||
</div>
|
||||
<div class="mt-4 text-lg whitespace-pre-line">
|
||||
{{ doc.terms }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Base from './Base';
|
||||
|
||||
export default {
|
||||
name: 'Business',
|
||||
extends: Base
|
||||
};
|
||||
</script>
|
@ -1,38 +0,0 @@
|
||||
<template>
|
||||
<div v-if="detailsPresent">
|
||||
<p :style="[$.bold]" style="font-size: 1.3em">{{ companyDetails.name }}</p>
|
||||
<p :style="$.paraStyle">{{ companyDetails.address.addressLine1 }}</p>
|
||||
<p :style="$.paraStyle">{{ companyDetails.address.addressLine2 }}</p>
|
||||
<p :style="$.paraStyle">
|
||||
{{ companyDetails.address.city + ' ' + companyDetails.address.state }}
|
||||
</p>
|
||||
<p :style="$.paraStyle">
|
||||
{{ companyDetails.address.country + ' - ' + companyDetails.address.postalCode }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import addressDetails from './AddressDetails';
|
||||
import styles from './InvoiceStyles';
|
||||
export default {
|
||||
name: 'CompanyAddress',
|
||||
data() {
|
||||
return {
|
||||
$: styles,
|
||||
detailsPresent: true,
|
||||
companyDetails: {
|
||||
name: null,
|
||||
address: {}
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$ = styles;
|
||||
try {
|
||||
this.companyDetails = await addressDetails.getCompanyDetails();
|
||||
} catch(e) {
|
||||
this.detailsPresent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<div v-if="this.detailsPresent">
|
||||
<p :style="[$.bold, $.mediumFontSize]">Billed To</p>
|
||||
<p :style="[$.bold, $.paraStyle]">{{ customer }}</p>
|
||||
<p :style="$.paraStyle">{{ customerAddress.addressLine1 }}</p>
|
||||
<p :style="$.paraStyle">{{ customerAddress.addressLine2 }}</p>
|
||||
<p :style="$.paraStyle">
|
||||
{{ customerAddress.city + ' ' + customerAddress.state }}
|
||||
</p>
|
||||
<p :style="$.paraStyle">
|
||||
{{ customerAddress.country + ' - ' + customerAddress.postalCode }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import addressDetails from './AddressDetails';
|
||||
import styles from './InvoiceStyles';
|
||||
export default {
|
||||
name: 'CustomerAddress',
|
||||
props: ['customer'],
|
||||
data() {
|
||||
return {
|
||||
$: styles,
|
||||
detailsPresent: true,
|
||||
customerAddress: {}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$ = styles;
|
||||
try {
|
||||
this.customerAddress = await addressDetails.getCustomerAddress(this.customer);
|
||||
} catch(e) {
|
||||
this.detailsPresent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
169
models/doctype/SalesInvoice/Templates/Default.vue
Normal file
169
models/doctype/SalesInvoice/Templates/Default.vue
Normal file
@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="border">
|
||||
<div>
|
||||
<div class="px-6 pt-6" v-if="printSettings && accountingSettings">
|
||||
<div class="flex text-sm text-gray-900 border-b pb-4">
|
||||
<div class="w-1/3">
|
||||
<div v-if="printSettings.displayLogo">
|
||||
<img
|
||||
class="h-12 max-w-32 object-contain"
|
||||
:src="printSettings.logo"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xl text-gray-700 font-semibold" v-else>
|
||||
{{ accountingSettings.companyName }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div>{{ printSettings.email }}</div>
|
||||
<div class="mt-1">{{ printSettings.phone }}</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div v-if="address">{{ address.addressDisplay }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 px-6">
|
||||
<div class="flex justify-between">
|
||||
<div class="w-1/3">
|
||||
<h1 class="text-2xl font-semibold">
|
||||
{{ doc.name }}
|
||||
</h1>
|
||||
<div class="py-2 text-base">
|
||||
{{ frappe.format(doc.date, 'Date') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<div class="py-1 text-right text-lg font-semibold">
|
||||
{{ doc[partyField.fieldname] }}
|
||||
</div>
|
||||
<div v-if="partyDoc" class="mt-1 text-xs text-gray-600 text-right">
|
||||
{{ partyDoc.addressDisplay }}
|
||||
</div>
|
||||
<div
|
||||
v-if="partyDoc && partyDoc.gstin"
|
||||
class="mt-1 text-xs text-gray-600 text-right"
|
||||
>
|
||||
GSTIN: {{ partyDoc.gstin }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 px-6 text-base">
|
||||
<div>
|
||||
<Row class="text-gray-600 w-full" :ratio="ratio">
|
||||
<div class="py-4">
|
||||
{{ _('No') }}
|
||||
</div>
|
||||
<div
|
||||
class="py-4"
|
||||
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"
|
||||
:key="row.name"
|
||||
:ratio="ratio"
|
||||
>
|
||||
<div class="py-4">
|
||||
{{ row.idx + 1 }}
|
||||
</div>
|
||||
<div
|
||||
class="py-4"
|
||||
v-for="df in itemFields"
|
||||
:key="df.fieldname"
|
||||
:class="textAlign(df)"
|
||||
>
|
||||
{{ frappe.format(row[df.fieldname], df) }}
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 mt-2 flex justify-end text-base">
|
||||
<div class="w-64">
|
||||
<div class="flex pl-2 justify-between py-3 border-b">
|
||||
<div>{{ _('Subtotal') }}</div>
|
||||
<div>{{ frappe.format(doc.netTotal, 'Currency') }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex pl-2 justify-between py-3"
|
||||
v-for="tax in doc.taxes"
|
||||
:key="tax.name"
|
||||
>
|
||||
<div>{{ tax.account }} ({{ tax.rate }}%)</div>
|
||||
<div>{{ frappe.format(tax.amount, 'Currency') }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex pl-2 justify-between py-3 border-t text-green-600 font-semibold text-base"
|
||||
>
|
||||
<div>{{ _('Grand Total') }}</div>
|
||||
<div>{{ frappe.format(doc.grandTotal, 'Currency') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import frappe from 'frappejs';
|
||||
import Row from '@/components/Row';
|
||||
|
||||
export default {
|
||||
name: 'Default',
|
||||
props: ['doc', 'printSettings'],
|
||||
components: {
|
||||
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>
|
||||
|
||||
<style></style>
|
@ -1,54 +0,0 @@
|
||||
module.exports = {
|
||||
bold: {
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
font: {
|
||||
fontFamily: null
|
||||
},
|
||||
regularFontSize: {
|
||||
fontSize: '0.8rem'
|
||||
},
|
||||
mediumFontSize: {
|
||||
fontSize: '1rem'
|
||||
},
|
||||
paraStyle: {
|
||||
margin: '0.5rem',
|
||||
marginLeft: 0,
|
||||
marginRight: 0
|
||||
},
|
||||
bgColor: {
|
||||
backgroundColor: null
|
||||
},
|
||||
showBorderBottom: {
|
||||
borderBottom: null
|
||||
},
|
||||
hideBorderTop: {
|
||||
borderTop: '0px solid black'
|
||||
},
|
||||
showBorderRight: {
|
||||
borderRight: '1px solid #e0e0d1'
|
||||
},
|
||||
tablePadding: {
|
||||
paddingTop: '3%',
|
||||
paddingBottom: '3%'
|
||||
},
|
||||
fontColor: {
|
||||
color: null
|
||||
},
|
||||
headerColor: {
|
||||
backgroundColor: null,
|
||||
color: 'white'
|
||||
},
|
||||
showNoticeBorderBottom: {
|
||||
borderBottom: null
|
||||
},
|
||||
showBorderTop: {
|
||||
borderTop: null
|
||||
},
|
||||
headerFontColor: {
|
||||
color: null
|
||||
},
|
||||
showBorderTop: {
|
||||
borderTop: null
|
||||
}
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
<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: 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
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,132 +0,0 @@
|
||||
<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">{{ 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
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
@ -1,145 +0,0 @@
|
||||
<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">{{ 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
|
||||
},
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
121
models/doctype/SalesInvoice/Templates/Minimal.vue
Normal file
121
models/doctype/SalesInvoice/Templates/Minimal.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div class="bg-white border" :style="{ 'font-family': printSettings.font }">
|
||||
<div class="flex items-center justify-between px-12 py-10 border-b">
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center rounded h-16">
|
||||
<div class="mr-4" v-if="printSettings.displayLogo">
|
||||
<img
|
||||
class="h-12 max-w-32 object-contain"
|
||||
:src="printSettings.logo"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="font-semibold text-xl"
|
||||
:style="{ color: printSettings.color }"
|
||||
>
|
||||
{{ frappe.AccountingSettings.companyName }}
|
||||
</div>
|
||||
<div>
|
||||
{{ frappe.format(doc.date, 'Date') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div
|
||||
class="font-semibold text-xl"
|
||||
:style="{ color: printSettings.color }"
|
||||
>
|
||||
{{ doc.doctype === 'SalesInvoice' ? 'Invoice' : 'Bill' }}
|
||||
</div>
|
||||
<div>
|
||||
{{ doc.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex px-12 py-10 border-b">
|
||||
<div class="w-1/2" v-if="party">
|
||||
<div
|
||||
class="uppercase text-sm font-semibold tracking-widest text-gray-800"
|
||||
>
|
||||
To
|
||||
</div>
|
||||
<div class="mt-4 text-black leading-relaxed text-lg">
|
||||
{{ party.name }} <br />
|
||||
{{ party.addressDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/2" v-if="companyAddress">
|
||||
<div
|
||||
class="uppercase text-sm font-semibold tracking-widest text-gray-800"
|
||||
>
|
||||
From
|
||||
</div>
|
||||
<div class="mt-4 text-black leading-relaxed text-lg">
|
||||
{{ companyAddress.addressDisplay }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-12 py-10 border-b">
|
||||
<div
|
||||
class="mb-4 flex uppercase text-sm tracking-widest font-semibold text-gray-800"
|
||||
>
|
||||
<div class="w-4/12">Item</div>
|
||||
<div class="w-2/12 text-right">Quantity</div>
|
||||
<div class="w-3/12 text-right">Rate</div>
|
||||
<div class="w-3/12 text-right">Amount</div>
|
||||
</div>
|
||||
<div class="flex py-1 text-lg" v-for="row in doc.items" :key="row.name">
|
||||
<div class="w-4/12">{{ row.item }}</div>
|
||||
<div class="w-2/12 text-right">{{ format(row, 'quantity') }}</div>
|
||||
<div class="w-3/12 text-right">{{ format(row, 'rate') }}</div>
|
||||
<div class="w-3/12 text-right">{{ format(row, 'amount') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex px-12 py-10">
|
||||
<div class="w-1/2">
|
||||
<template v-if="doc.terms">
|
||||
<div
|
||||
class="uppercase text-sm tracking-widest font-semibold text-gray-800"
|
||||
>
|
||||
Notes
|
||||
</div>
|
||||
<div class="mt-4 text-lg whitespace-pre-line">
|
||||
{{ doc.terms }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="w-1/2 text-lg">
|
||||
<div class="flex pl-2 justify-between py-1">
|
||||
<div>{{ _('Subtotal') }}</div>
|
||||
<div>{{ frappe.format(doc.netTotal, 'Currency') }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex pl-2 justify-between py-1"
|
||||
v-for="tax in doc.taxes"
|
||||
:key="tax.name"
|
||||
>
|
||||
<div>{{ tax.account }} ({{ tax.rate }}%)</div>
|
||||
<div>{{ frappe.format(tax.amount, 'Currency') }}</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex pl-2 justify-between py-1 font-semibold"
|
||||
:style="{ color: printSettings.color }"
|
||||
>
|
||||
<div>{{ _('Grand Total') }}</div>
|
||||
<div>{{ frappe.format(doc.grandTotal, 'Currency') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Base from './Base';
|
||||
|
||||
export default {
|
||||
name: 'Minimal',
|
||||
extends: Base
|
||||
};
|
||||
</script>
|
@ -20,6 +20,7 @@
|
||||
"@popperjs/core": "^2.0.3",
|
||||
"core-js": "^3.4.3",
|
||||
"electron-store": "^5.1.0",
|
||||
"font-manager": "https://github.com/q-lukasz/font-manager#issue-45--compile-error-mac-os-x--node-13-7",
|
||||
"frappe-charts": "^1.3.0",
|
||||
"frappejs": "https://github.com/frappe/frappejs",
|
||||
"knex": "^0.20.4",
|
||||
|
@ -3,16 +3,13 @@
|
||||
<PageHeader>
|
||||
<BackLink slot="title" />
|
||||
<template slot="actions">
|
||||
<Button class="text-gray-900 text-xs" @click="openInvoiceSettings">
|
||||
{{ _('Customise') }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="doc.submitted"
|
||||
class="text-gray-900 text-xs ml-2"
|
||||
:icon="true"
|
||||
@click="$router.push(`/print/${doc.doctype}/${doc.name}`)"
|
||||
>
|
||||
<feather-icon name="printer" class="w-4 h-4" />
|
||||
Print
|
||||
</Button>
|
||||
<DropdownWithActions class="ml-2" :actions="actions" />
|
||||
<Button
|
||||
@ -32,11 +29,7 @@
|
||||
>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<div
|
||||
class="flex justify-center flex-1 mb-8 mt-6"
|
||||
v-if="meta"
|
||||
:class="doc.submitted && 'pointer-events-none'"
|
||||
>
|
||||
<div class="flex justify-center flex-1 mb-8 mt-2" v-if="meta">
|
||||
<div
|
||||
class="border rounded-lg shadow h-full flex flex-col justify-between"
|
||||
style="width: 600px"
|
||||
|
@ -1,37 +1,56 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<PageHeader>
|
||||
<a
|
||||
class="cursor-pointer font-semibold flex items-center"
|
||||
slot="title"
|
||||
@click="$router.back()"
|
||||
>
|
||||
<feather-icon name="chevron-left" class="w-5 h-5" />
|
||||
<span class="ml-1">{{ _('Back') }}</span>
|
||||
</a>
|
||||
<template slot="actions">
|
||||
<Button class="text-gray-900 text-xs ml-2" @click="makePDF">
|
||||
{{ _('Save as PDF') }}
|
||||
</Button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<div class="flex justify-center flex-1 mb-8 mt-6">
|
||||
<div class="flex">
|
||||
<div class="flex flex-col flex-1">
|
||||
<PageHeader class="bg-white z-10">
|
||||
<BackLink slot="title" />
|
||||
<template slot="actions">
|
||||
<Button
|
||||
class="text-gray-900 text-xs ml-2"
|
||||
@click="showCustomiser = !showCustomiser"
|
||||
>
|
||||
{{ _('Customise') }}
|
||||
</Button>
|
||||
<Button class="text-gray-900 text-xs ml-2" @click="makePDF">
|
||||
{{ _('Save as PDF') }}
|
||||
</Button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<div
|
||||
v-if="doc"
|
||||
class="border rounded-lg shadow h-full flex flex-col justify-between"
|
||||
style="width: 600px"
|
||||
ref="printContainer"
|
||||
v-if="doc && printSettings"
|
||||
class="flex justify-center flex-1 -mt-32 overflow-auto relative"
|
||||
>
|
||||
<component :is="printTemplate" v-bind="{ doc }" />
|
||||
<div
|
||||
class="h-full shadow-lg mb-12 absolute"
|
||||
style="width: 21cm; min-height: 29.7cm; height: max-content; transform: scale(0.755);"
|
||||
ref="printContainer"
|
||||
>
|
||||
<component
|
||||
class="flex-1"
|
||||
:is="printTemplate"
|
||||
v-bind="{ doc, printSettings }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-l w-80" v-if="showCustomiser">
|
||||
<div class="mt-4 px-4 flex items-center justify-between">
|
||||
<h2 class="font-semibold">{{ _('Customise') }}</h2>
|
||||
<Button :icon="true" @click="showCustomiser = false">
|
||||
<feather-icon name="x" class="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<TwoColumnForm class="mt-4" :doc="printSettings" :autosave="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import frappe from 'frappejs';
|
||||
import PageHeader from '@/components/PageHeader';
|
||||
import SearchBar from '@/components/SearchBar';
|
||||
import DropdownWithAction from '@/components/DropdownWithAction';
|
||||
import Button from '@/components/Button';
|
||||
import BackLink from '@/components/BackLink';
|
||||
import TwoColumnForm from '@/components/TwoColumnForm';
|
||||
import { makePDF } from '@/utils';
|
||||
import { remote } from 'electron';
|
||||
|
||||
@ -42,15 +61,20 @@ export default {
|
||||
PageHeader,
|
||||
SearchBar,
|
||||
DropdownWithAction,
|
||||
Button
|
||||
Button,
|
||||
BackLink,
|
||||
TwoColumnForm
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
doc: null
|
||||
doc: null,
|
||||
showCustomiser: false,
|
||||
printSettings: null
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.doc = await frappe.getDoc(this.doctype, this.name);
|
||||
this.printSettings = await frappe.getSingle('PrintSettings');
|
||||
},
|
||||
computed: {
|
||||
meta() {
|
||||
@ -66,7 +90,6 @@ export default {
|
||||
let html = this.$refs.printContainer.innerHTML;
|
||||
makePDF(html, destination);
|
||||
},
|
||||
|
||||
getSavePath() {
|
||||
return new Promise(resolve => {
|
||||
remote.dialog.showSaveDialog(
|
||||
|
@ -1,5 +1,5 @@
|
||||
@font-face {
|
||||
font-family: 'Inter var experimental';
|
||||
font-family: 'Inter';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: oblique 0deg 10deg;
|
||||
|
@ -262,7 +262,7 @@ export function getActionsForDocument(doc) {
|
||||
component: {
|
||||
template: `<span class="text-red-700">{{ _('Delete') }}</span>`
|
||||
},
|
||||
condition: doc => !doc.isNew() && !doc.submitted,
|
||||
condition: doc => !doc.isNew() && !doc.submitted && !doc.meta.isSingle,
|
||||
action: () =>
|
||||
deleteDocWithPrompt(doc).then(res => {
|
||||
if (res) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
theme: {
|
||||
fontFamily: {
|
||||
sans: ['Inter var experimental', 'sans-serif']
|
||||
sans: ['Inter', 'sans-serif']
|
||||
},
|
||||
fontSize: {
|
||||
xs: '11px',
|
||||
@ -29,6 +29,7 @@ module.exports = {
|
||||
'7': '1.75rem',
|
||||
'14': '3.5rem',
|
||||
'18': '4.5rem',
|
||||
'28': '7rem',
|
||||
'72': '18rem',
|
||||
'80': '20rem'
|
||||
},
|
||||
|
@ -4830,6 +4830,12 @@ follow-redirects@^1.0.0:
|
||||
dependencies:
|
||||
debug "^3.0.0"
|
||||
|
||||
"font-manager@https://github.com/q-lukasz/font-manager#issue-45--compile-error-mac-os-x--node-13-7":
|
||||
version "0.3.1"
|
||||
resolved "https://github.com/q-lukasz/font-manager#523d3b3c2b535a5003c2a631bf6d2ec342cc6c8f"
|
||||
dependencies:
|
||||
nan ">=2.14.0"
|
||||
|
||||
for-in@^1.0.1, for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
|
||||
@ -7288,7 +7294,7 @@ mz@^2.4.0:
|
||||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nan@^2.12.1:
|
||||
nan@>=2.14.0, nan@^2.12.1:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||
integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==
|
||||
|
Loading…
Reference in New Issue
Block a user