2
0
mirror of https://github.com/frappe/books.git synced 2025-01-12 02:59:11 +00:00
books/src/pages/InvoiceForm.vue
2022-05-23 16:18:22 +05:30

357 lines
10 KiB
Vue

<template>
<div class="flex flex-col" v-if="doc">
<!-- Page Header (Title, Buttons, etc) -->
<PageHeader :backLink="true">
<StatusBadge :status="status" />
<Button
v-if="doc?.submitted"
class="text-gray-900 text-xs"
:icon="true"
@click="routeTo(`/print/${doc.schemaName}/${doc.name}`)"
>
{{ t`Print` }}
</Button>
<DropdownWithActions :actions="actions()" />
<Button
v-if="doc?.notInserted || doc?.dirty"
type="primary"
class="text-white text-xs"
@click="sync"
>
{{ t`Save` }}
</Button>
<Button
v-if="!doc?.dirty && !doc?.notInserted && !doc?.submitted"
type="primary"
class="text-white text-xs"
@click="submit"
>{{ t`Submit` }}</Button
>
</PageHeader>
<!-- Invoice Form -->
<div class="flex justify-center flex-1 mb-8 mt-2" v-if="doc">
<div
class="
border
rounded-lg
shadow
h-full
flex flex-col
justify-between
w-600
"
>
<div>
<!-- Print Settings Info (Logo, Address, Etc) -->
<div class="flex text-sm text-gray-900 p-6" v-if="printSettings">
<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>
{{ 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>
<hr />
<!-- Invoice Form Data Entry -->
<div class="m-6 flex flex-col gap-2">
<h1 class="text-2xl font-semibold">
{{
doc.notInserted
? doc.schemaName === 'SalesInvoice'
? t`New Sales Invoice`
: t`New Purchase Invoice`
: doc.name
}}
</h1>
<!-- First Row of Fields -->
<div class="flex flex-row justify-between gap-2">
<FormControl
class="bg-gray-100 rounded text-base w-1/3"
input-class="text-lg font-semibold bg-transparent"
:df="getField('party')"
:value="doc.party"
:placeholder="getField('party').label"
@change="(value) => doc.set('party', value)"
@new-doc="(party) => doc.set('party', party.name)"
:read-only="doc?.submitted"
/>
<div class="w-1/3" />
<FormControl
class="w-1/3"
input-class="bg-gray-100 px-3 py-2 text-base text-right"
:df="getField('date')"
:value="doc.date"
:placeholder="'Date'"
@change="(value) => doc.set('date', value)"
:read-only="doc?.submitted"
/>
</div>
<!-- Second Row of Fields -->
<div class="flex flex-row justify-between gap-2">
<FormControl
class="text-base bg-gray-100 rounded w-1/3"
input-class="px-3 py-2 text-base bg-transparent"
:df="getField('account')"
:value="doc.account"
:placeholder="'Account'"
@change="(value) => doc.set('account', value)"
:read-only="doc?.submitted"
/>
<div class="w-1/3" />
<FormControl
class="text-base bg-gray-100 rounded w-1/3"
input-class="bg-transparent px-3 py-2 text-base text-right"
:df="getField('numberSeries')"
:value="doc.numberSeries"
@change="(value) => doc.set('numberSeries', value)"
:read-only="!doc.notInserted || doc?.submitted"
/>
</div>
</div>
<hr />
<!-- Invoice Items Table -->
<Table
class="px-6 text-base mt-4"
:df="getField('items')"
:value="doc.items"
:showHeader="true"
:max-rows-before-overflow="4"
@change="(value) => doc.set('items', value)"
:read-only="doc?.submitted"
/>
</div>
<!-- Invoice Form Footer -->
<div v-if="doc.items?.length ?? 0">
<hr />
<div class="flex justify-between text-base m-6 gap-12">
<!-- Form Terms-->
<FormControl
class="w-1/2 self-end"
v-if="!doc?.submitted || doc.terms"
:df="getField('terms')"
:value="doc.terms"
input-class="bg-gray-100"
@change="(value) => doc.set('terms', value)"
:read-only="doc?.submitted"
/>
<!-- Totals -->
<div class="w-1/2 gap-2 flex flex-col self-end ml-auto">
<!-- Subtotal -->
<div class="flex justify-between">
<div>{{ t`Subtotal` }}</div>
<div>{{ formattedValue('netTotal') }}</div>
</div>
<hr />
<!-- Taxes -->
<div
class="flex justify-between"
v-for="tax in doc.taxes"
:key="tax.name"
>
<div>{{ tax.account }}</div>
<div>
{{
fyo.format(tax.amount, {
fieldtype: 'Currency',
currency: doc.currency,
})
}}
</div>
</div>
<hr v-if="doc.taxes?.length" />
<!-- Grand Total -->
<div
class="
flex
justify-between
text-green-600
font-semibold
text-base
"
>
<div>{{ t`Grand Total` }}</div>
<div>{{ formattedValue('grandTotal') }}</div>
</div>
<!-- Outstanding Amount -->
<hr v-if="doc.outstandingAmount > 0" />
<div
v-if="doc.outstandingAmount > 0"
class="
flex
justify-between
text-red-600
font-semibold
text-base
"
>
<div>{{ t`Outstanding Amount` }}</div>
<div>{{ formattedValue('outstandingAmount') }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { computed } from '@vue/reactivity';
import { getInvoiceStatus } from 'models/helpers';
import { ModelNameEnum } from 'models/types';
import Button from 'src/components/Button.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import Table from 'src/components/Controls/Table.vue';
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
import PageHeader from 'src/components/PageHeader.vue';
import StatusBadge from 'src/components/StatusBadge.vue';
import { fyo } from 'src/initFyo';
import {
getActionsForDocument,
openSettings,
routeTo,
showMessageDialog
} from 'src/utils/ui';
import { handleErrorWithDialog } from '../errorHandling';
export default {
name: 'InvoiceForm',
props: { schemaName: String, name: String },
components: {
PageHeader,
StatusBadge,
Button,
FormControl,
DropdownWithActions,
Table,
},
provide() {
return {
schemaName: this.schemaName,
name: this.name,
doc: computed(() => this.doc),
};
},
data() {
return {
doc: null,
status: null,
color: null,
printSettings: null,
companyName: null,
};
},
computed: {
address() {
return this.printSettings && this.printSettings.getLink('address');
},
},
async mounted() {
try {
this.doc = await fyo.doc.getDoc(this.schemaName, this.name);
} catch (error) {
if (error instanceof fyo.errors.NotFoundError) {
routeTo(`/list/${this.schemaName}`);
return;
}
await this.handleError(error);
}
this.printSettings = await fyo.doc.getSingle('PrintSettings');
this.companyName = (
await fyo.doc.getSingle('AccountingSettings')
).companyName;
let query = this.$route.query;
if (query.values && query.schemaName === this.schemaName) {
this.doc.set(this.$router.currentRoute.value.query.values);
}
this.status = getInvoiceStatus(this.doc);
if (fyo.store.isDevelopment) {
window.inv = this;
}
},
updated() {
this.status = getInvoiceStatus(this.doc);
},
methods: {
routeTo,
actions() {
return getActionsForDocument(this.doc);
},
getField(fieldname) {
return fyo.getField(this.schemaName, fieldname);
},
async sync() {
try {
await this.doc.sync();
} catch (err) {
await this.handleError(err);
}
},
async submit() {
const message =
this.schemaName === ModelNameEnum.SalesInvoice
? this.t`Submit Sales Invoice?`
: this.t`Submit Purchase Invoice?`;
const ref = this
await showMessageDialog({
message,
buttons: [
{
label: this.t`Yes`,
async action() {
try {
await ref.doc.submit();
} catch (err) {
await ref.handleError(err);
}
},
},
{
label: this.t`No`,
action() {},
},
],
});
},
async handleError(e) {
await handleErrorWithDialog(e, this.doc);
},
openInvoiceSettings() {
openSettings('Invoice');
},
formattedValue(fieldname, doc) {
if (!doc) {
doc = this.doc;
}
const df = this.getField(fieldname);
return fyo.format(doc[fieldname], df, doc);
},
},
};
</script>