2019-10-11 15:25:50 +05:30
|
|
|
<template>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div class="flex flex-col" v-if="doc">
|
2019-10-11 15:25:50 +05:30
|
|
|
<PageHeader>
|
2019-11-20 00:25:32 +05:30
|
|
|
<a
|
|
|
|
class="cursor-pointer font-semibold flex items-center"
|
|
|
|
slot="title"
|
|
|
|
@click="routeToList"
|
|
|
|
>
|
|
|
|
<feather-icon name="chevron-left" class="w-5 h-5" />
|
|
|
|
<span class="ml-1">{{ _('Back') }}</span>
|
|
|
|
</a>
|
2019-10-11 15:25:50 +05:30
|
|
|
<template slot="actions">
|
2019-11-20 00:25:32 +05:30
|
|
|
<Button class="text-gray-900 text-xs" @click="openInvoiceSettings">
|
|
|
|
{{ _('Customise') }}
|
|
|
|
</Button>
|
2019-10-30 01:03:26 +05:30
|
|
|
<Dropdown
|
2019-11-27 12:16:15 +05:30
|
|
|
v-if="actions && actions.length"
|
2019-11-20 00:25:32 +05:30
|
|
|
class="text-xs"
|
2019-11-27 12:16:15 +05:30
|
|
|
:items="actions"
|
2019-10-30 01:03:26 +05:30
|
|
|
right
|
|
|
|
>
|
|
|
|
<template v-slot="{ toggleDropdown }">
|
2019-11-20 00:25:32 +05:30
|
|
|
<Button
|
|
|
|
class="text-gray-900 text-xs ml-2"
|
|
|
|
:icon="true"
|
|
|
|
@click="toggleDropdown()"
|
|
|
|
>
|
|
|
|
<feather-icon name="more-horizontal" class="w-4 h-4" />
|
2019-10-30 01:03:26 +05:30
|
|
|
</Button>
|
|
|
|
</template>
|
|
|
|
</Dropdown>
|
2019-10-14 02:49:28 +05:30
|
|
|
<Button
|
2019-11-20 00:25:32 +05:30
|
|
|
v-if="showSave"
|
2019-10-14 02:49:28 +05:30
|
|
|
type="primary"
|
|
|
|
class="text-white text-xs ml-2"
|
|
|
|
@click="onSaveClick"
|
2019-11-20 00:25:32 +05:30
|
|
|
>{{ _('Save') }}</Button
|
|
|
|
>
|
2019-10-14 02:49:28 +05:30
|
|
|
<Button
|
|
|
|
v-if="!doc._dirty && !doc._notInserted && !doc.submitted"
|
|
|
|
type="primary"
|
|
|
|
class="text-white text-xs ml-2"
|
|
|
|
@click="onSubmitClick"
|
2019-11-20 00:25:32 +05:30
|
|
|
>{{ _('Submit') }}</Button
|
|
|
|
>
|
2019-10-11 15:25:50 +05:30
|
|
|
</template>
|
|
|
|
</PageHeader>
|
2019-10-14 02:49:28 +05:30
|
|
|
<div
|
|
|
|
class="flex justify-center flex-1 mb-8 mt-6"
|
|
|
|
v-if="meta"
|
|
|
|
:class="doc.submitted && 'pointer-events-none'"
|
|
|
|
>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div
|
|
|
|
class="border rounded-lg shadow h-full flex flex-col justify-between"
|
|
|
|
style="width: 600px"
|
|
|
|
>
|
2019-10-11 15:25:50 +05:30
|
|
|
<div>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div class="px-6 pt-6" v-if="printSettings">
|
|
|
|
<div class="flex text-sm text-gray-900 border-b pb-4">
|
2019-10-11 15:25:50 +05:30
|
|
|
<div class="w-1/3">
|
2019-11-20 00:25:32 +05:30
|
|
|
<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>
|
|
|
|
{{ companyName }}
|
|
|
|
</div>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
|
|
|
<div class="w-1/3">
|
2019-11-20 00:25:32 +05:30
|
|
|
<div>{{ printSettings.email }}</div>
|
|
|
|
<div class="mt-1">{{ printSettings.phone }}</div>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
|
|
|
<div class="w-1/3">
|
2019-11-20 00:25:32 +05:30
|
|
|
<div v-if="address">{{ address.addressDisplay }}</div>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="mt-8 px-6">
|
|
|
|
<div class="flex justify-between">
|
|
|
|
<div class="w-1/3">
|
2019-11-27 12:16:15 +05:30
|
|
|
<h1 class="text-2xl font-semibold">
|
|
|
|
{{ doc._notInserted ? _('New Invoice') : doc.name }}
|
|
|
|
</h1>
|
2019-10-11 15:25:50 +05:30
|
|
|
<FormControl
|
|
|
|
class="mt-2"
|
2019-11-20 00:25:32 +05:30
|
|
|
input-class="bg-gray-100 rounded-lg px-3 py-2 text-base"
|
2019-10-11 15:25:50 +05:30
|
|
|
:df="meta.getField('date')"
|
|
|
|
:value="doc.date"
|
|
|
|
:placeholder="'Date'"
|
|
|
|
@change="value => doc.set('date', value)"
|
2019-11-20 00:25:32 +05:30
|
|
|
:read-only="doc.submitted"
|
2019-10-11 15:25:50 +05:30
|
|
|
/>
|
|
|
|
<FormControl
|
2019-11-27 12:16:15 +05:30
|
|
|
class="mt-2 text-base"
|
2019-11-20 00:25:32 +05:30
|
|
|
input-class="bg-gray-100 rounded-lg px-3 py-2 text-base"
|
2019-10-11 15:25:50 +05:30
|
|
|
:df="meta.getField('account')"
|
|
|
|
:value="doc.account"
|
|
|
|
:placeholder="'Account'"
|
|
|
|
@change="value => doc.set('account', value)"
|
2019-11-20 00:25:32 +05:30
|
|
|
:read-only="doc.submitted"
|
2019-10-11 15:25:50 +05:30
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div class="w-1/3">
|
|
|
|
<FormControl
|
2019-11-27 12:16:15 +05:30
|
|
|
class="text-base"
|
2019-11-20 00:25:32 +05:30
|
|
|
input-class="bg-gray-100 rounded-lg p-2 text-right text-lg font-semibold"
|
2019-10-14 02:49:28 +05:30
|
|
|
:df="meta.getField(partyField.fieldname)"
|
|
|
|
:value="doc[partyField.fieldname]"
|
|
|
|
:placeholder="partyField.label"
|
|
|
|
@change="value => doc.set(partyField.fieldname, value)"
|
2019-11-27 12:16:15 +05:30
|
|
|
@new-doc="party => doc.set(partyField.fieldname, party.name)"
|
2019-11-20 00:25:32 +05:30
|
|
|
:read-only="doc.submitted"
|
2019-10-11 15:25:50 +05:30
|
|
|
/>
|
|
|
|
<div
|
2019-11-20 00:25:32 +05:30
|
|
|
v-if="partyDoc"
|
|
|
|
class="mt-1 text-xs text-gray-600 text-right"
|
|
|
|
>
|
|
|
|
{{ partyDoc.addressDisplay }}
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
v-if="partyDoc && partyDoc.gstin"
|
2019-10-11 15:25:50 +05:30
|
|
|
class="mt-1 text-xs text-gray-600 text-right"
|
2019-11-20 00:25:32 +05:30
|
|
|
>
|
|
|
|
GSTIN: {{ partyDoc.gstin }}
|
|
|
|
</div>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div class="px-6 text-base">
|
2019-10-13 17:33:01 +05:30
|
|
|
<FormControl
|
|
|
|
:df="meta.getField('items')"
|
|
|
|
:value="doc.items"
|
|
|
|
:showHeader="true"
|
|
|
|
@change="value => doc.set('items', value)"
|
2019-11-20 00:25:32 +05:30
|
|
|
:read-only="doc.submitted"
|
2019-10-13 17:33:01 +05:30
|
|
|
/>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
|
|
|
</div>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div class="px-6 mb-6 flex justify-end text-base">
|
2019-10-11 15:25:50 +05:30
|
|
|
<div class="w-64">
|
|
|
|
<div class="flex pl-2 justify-between py-3 border-b">
|
2019-11-27 12:16:15 +05:30
|
|
|
<div>{{ _('Subtotal') }}</div>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div>{{ frappe.format(doc.netTotal, 'Currency') }}</div>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div
|
|
|
|
class="flex pl-2 justify-between py-3"
|
|
|
|
v-for="tax in doc.taxes"
|
|
|
|
:key="tax.name"
|
|
|
|
>
|
2019-10-11 15:25:50 +05:30
|
|
|
<div>{{ tax.account }} ({{ tax.rate }}%)</div>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div>{{ frappe.format(tax.amount, 'Currency') }}</div>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
2019-10-14 02:49:28 +05:30
|
|
|
<div
|
|
|
|
class="flex pl-2 justify-between py-3 border-t text-green-600 font-semibold text-base"
|
|
|
|
>
|
2019-11-27 12:16:15 +05:30
|
|
|
<div>{{ _('Grand Total') }}</div>
|
2019-11-20 00:25:32 +05:30
|
|
|
<div>{{ frappe.format(doc.grandTotal, 'Currency') }}</div>
|
2019-10-11 15:25:50 +05:30
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
<script>
|
|
|
|
import PageHeader from '@/components/PageHeader';
|
|
|
|
import Button from '@/components/Button';
|
|
|
|
import FormControl from '@/components/Controls/FormControl';
|
2019-10-13 17:33:01 +05:30
|
|
|
import Row from '@/components/Row';
|
2019-10-30 01:03:26 +05:30
|
|
|
import Dropdown from '@/components/Dropdown';
|
2019-11-20 00:25:32 +05:30
|
|
|
import { openSettings } from '@/pages/Settings/utils';
|
2019-11-28 00:09:16 +05:30
|
|
|
import { showMessageDialog } from '@/utils';
|
2019-10-11 15:25:50 +05:30
|
|
|
|
|
|
|
export default {
|
|
|
|
name: 'InvoiceForm',
|
2019-10-14 02:49:28 +05:30
|
|
|
props: ['doctype', 'name'],
|
2019-10-11 15:25:50 +05:30
|
|
|
components: {
|
|
|
|
PageHeader,
|
|
|
|
Button,
|
|
|
|
FormControl,
|
2019-10-13 17:33:01 +05:30
|
|
|
Row,
|
2019-10-30 01:03:26 +05:30
|
|
|
Dropdown
|
2019-10-11 15:25:50 +05:30
|
|
|
},
|
|
|
|
provide() {
|
|
|
|
return {
|
2019-10-14 02:49:28 +05:30
|
|
|
doctype: this.doctype,
|
2019-10-11 15:25:50 +05:30
|
|
|
name: this.name
|
|
|
|
};
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
2019-11-20 00:25:32 +05:30
|
|
|
doc: null,
|
|
|
|
partyDoc: null,
|
|
|
|
printSettings: null,
|
|
|
|
companyName: null
|
2019-10-11 15:25:50 +05:30
|
|
|
};
|
|
|
|
},
|
|
|
|
computed: {
|
2019-11-28 00:09:16 +05:30
|
|
|
meta() {
|
|
|
|
return frappe.getMeta(this.doctype);
|
|
|
|
},
|
|
|
|
itemsMeta() {
|
|
|
|
return frappe.getMeta(`${this.doctype}Item`);
|
|
|
|
},
|
2019-10-11 15:25:50 +05:30
|
|
|
itemTableFields() {
|
2019-10-13 17:33:01 +05:30
|
|
|
return this.itemsMeta.tableFields.map(fieldname =>
|
2019-10-11 15:25:50 +05:30
|
|
|
this.itemsMeta.getField(fieldname)
|
|
|
|
);
|
|
|
|
},
|
|
|
|
itemTableColumnRatio() {
|
|
|
|
return [0.3].concat(this.itemTableFields.map(_ => 1));
|
2019-10-14 02:49:28 +05:30
|
|
|
},
|
|
|
|
partyField() {
|
|
|
|
let fieldname = {
|
|
|
|
SalesInvoice: 'customer',
|
|
|
|
PurchaseInvoice: 'supplier'
|
|
|
|
}[this.doctype];
|
|
|
|
return this.meta.getField(fieldname);
|
2019-11-20 00:25:32 +05:30
|
|
|
},
|
|
|
|
address() {
|
|
|
|
return this.printSettings && this.printSettings.getLink('address');
|
|
|
|
},
|
|
|
|
showSave() {
|
|
|
|
return this.doc && (this.doc._notInserted || this.doc._dirty);
|
2019-11-27 12:16:15 +05:30
|
|
|
},
|
|
|
|
actions() {
|
|
|
|
if (!this.doc) return null;
|
|
|
|
let deleteAction = {
|
|
|
|
component: {
|
|
|
|
template: `<span class="text-red-700">{{ _('Delete') }}</span>`
|
|
|
|
},
|
|
|
|
condition: doc => !doc.isNew() && !doc.submitted,
|
|
|
|
action: () => {
|
2019-11-28 00:09:16 +05:30
|
|
|
showMessageDialog({
|
|
|
|
message: this._('Are you sure you want to delete {0} "{1}"?', [
|
|
|
|
this.doctype,
|
|
|
|
this.name
|
|
|
|
]),
|
|
|
|
description: this._('This action is permanent'),
|
|
|
|
buttons: [
|
|
|
|
{
|
|
|
|
label: 'Delete',
|
|
|
|
action: () => {
|
|
|
|
this.doc.delete().then(() => {
|
|
|
|
this.routeToList();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
label: 'Cancel',
|
|
|
|
action() {}
|
|
|
|
}
|
|
|
|
]
|
2019-11-27 12:16:15 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
let actions = [...(this.meta.actions || []), deleteAction]
|
|
|
|
.filter(d => (d.condition ? d.condition(this.doc) : true))
|
|
|
|
.map(d => {
|
|
|
|
return {
|
|
|
|
label: d.label,
|
|
|
|
component: d.component,
|
|
|
|
action: d.action.bind(this, this.doc, this)
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return actions;
|
2019-10-30 01:03:26 +05:30
|
|
|
}
|
2019-10-11 15:25:50 +05:30
|
|
|
},
|
|
|
|
async mounted() {
|
2019-10-14 02:49:28 +05:30
|
|
|
this.doc = await frappe.getDoc(this.doctype, this.name);
|
2019-11-20 00:25:32 +05:30
|
|
|
this.doc.on('change', ({ changed }) => {
|
|
|
|
if (changed === this.partyField.fieldname) {
|
|
|
|
this.fetchPartyDoc();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.fetchPartyDoc();
|
|
|
|
this.printSettings = await frappe.getSingle('PrintSettings');
|
|
|
|
this.companyName = (
|
|
|
|
await frappe.getSingle('AccountingSettings')
|
|
|
|
).companyName;
|
|
|
|
|
2019-11-27 12:16:15 +05:30
|
|
|
let query = this.$route.query;
|
|
|
|
if (query.values && query.doctype === this.doctype) {
|
2019-11-20 00:25:32 +05:30
|
|
|
this.doc.set(this.$router.currentRoute.query.values);
|
|
|
|
}
|
2019-10-11 15:25:50 +05:30
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
async addNewItem() {
|
|
|
|
this.doc.append('items');
|
|
|
|
},
|
|
|
|
async onSaveClick() {
|
2019-11-20 00:25:32 +05:30
|
|
|
await this.doc.set(
|
|
|
|
'items',
|
|
|
|
this.doc.items.filter(row => row.item)
|
|
|
|
);
|
|
|
|
await this.doc.insertOrUpdate();
|
2019-10-14 02:49:28 +05:30
|
|
|
},
|
|
|
|
async onSubmitClick() {
|
|
|
|
await this.doc.submit();
|
2019-10-30 01:03:26 +05:30
|
|
|
},
|
|
|
|
async makePayment() {
|
|
|
|
let payment = await frappe.getNewDoc('Payment');
|
|
|
|
payment.once('afterInsert', () => {
|
|
|
|
payment.submit();
|
|
|
|
});
|
|
|
|
this.$router.push({
|
|
|
|
query: {
|
|
|
|
edit: 1,
|
|
|
|
doctype: 'Payment',
|
|
|
|
name: payment.name,
|
|
|
|
hideFields: ['party', 'date', 'account', 'paymentType', 'for'],
|
|
|
|
values: {
|
2019-11-08 16:16:02 +05:30
|
|
|
party: this.doc[this.partyField.fieldname],
|
2019-10-30 01:03:26 +05:30
|
|
|
account: this.doc.account,
|
|
|
|
date: new Date().toISOString().slice(0, 10),
|
2019-11-08 16:16:02 +05:30
|
|
|
paymentType: this.doctype === 'SalesInvoice' ? 'Receive' : 'Pay',
|
2019-10-30 01:03:26 +05:30
|
|
|
for: [
|
|
|
|
{
|
2019-11-08 16:16:02 +05:30
|
|
|
referenceType: this.doctype,
|
2019-10-30 01:03:26 +05:30
|
|
|
referenceName: this.doc.name,
|
|
|
|
amount: this.doc.outstandingAmount
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2019-11-20 00:25:32 +05:30
|
|
|
},
|
|
|
|
async fetchPartyDoc() {
|
|
|
|
if (this.doc[this.partyField.fieldname]) {
|
|
|
|
this.partyDoc = await frappe.getDoc(
|
|
|
|
'Party',
|
|
|
|
this.doc[this.partyField.fieldname]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
openInvoiceSettings() {
|
|
|
|
openSettings('Invoice');
|
|
|
|
},
|
|
|
|
routeToList() {
|
|
|
|
this.$router.push(`/list/${this.doctype}`);
|
2019-10-11 15:25:50 +05:30
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
</script>
|