aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVidhu Kant Sharma <vidhukant@vidhukant.com>2023-12-03 22:16:22 +0530
committerVidhu Kant Sharma <vidhukant@vidhukant.com>2023-12-03 22:16:22 +0530
commit37ef1ab2f544a05b5878c5bdaafd37155a054289 (patch)
treebd2ca480a33b16dd795d7f2a9047d12734f5c61e /src
parent511e5f3badb871a4407a1cfef2d17a8c99660d30 (diff)
added invoice edit page0.1.0
Diffstat (limited to 'src')
-rw-r--r--src/classes/invoice.ts34
-rw-r--r--src/classes/invoice_item.ts21
-rw-r--r--src/classes/item.ts2
-rw-r--r--src/components/invoice_header.vue213
-rw-r--r--src/components/invoice_header_editor.vue151
-rw-r--r--src/components/item_selector.vue176
-rw-r--r--src/components/new_customer.vue22
-rw-r--r--src/router/index.ts7
-rw-r--r--src/views/EditInvoice.vue47
-rw-r--r--src/views/NewInvoice.vue4
10 files changed, 554 insertions, 123 deletions
diff --git a/src/classes/invoice.ts b/src/classes/invoice.ts
new file mode 100644
index 0000000..213eddc
--- /dev/null
+++ b/src/classes/invoice.ts
@@ -0,0 +1,34 @@
+import Address from './address'
+import Item from './item'
+
+export default class Customer {
+ InvoiceDate: string
+ InvoiceNumber: number
+ BillingAddress: Address
+ ShippingAddress: Address
+ IsDraft: boolean
+ Items: Item[]
+
+ CustomerName: string
+ CustomerGstin: string
+ CustomerContactName: string
+ CustomerPhone: string
+ CustomerEmail: string
+ CustomerWebsite: string
+
+ constructor() {
+ this.InvoiceDate = ""
+ this.InvoiceNumber = 0
+ this.BillingAddress = new Address()
+ this.ShippingAddress = new Address()
+ this.IsDraft = true
+ this.Items = []
+
+ this.CustomerName = ""
+ this.CustomerGstin = ""
+ this.CustomerContactName = ""
+ this.CustomerPhone = ""
+ this.CustomerEmail = ""
+ this.CustomerWebsite = ""
+ }
+}
diff --git a/src/classes/invoice_item.ts b/src/classes/invoice_item.ts
new file mode 100644
index 0000000..a7a0a85
--- /dev/null
+++ b/src/classes/invoice_item.ts
@@ -0,0 +1,21 @@
+export default class InvoiceItem {
+ UnitOfMeasure: string
+ Quantity: string
+ Name: string
+ Description: string
+ HSN: string
+ UnitPrice: string
+ GSTPercentage: string
+ BrandName: string
+
+ constructor() {
+ this.Name = ''
+ this.Description = ''
+ this.HSN = ''
+ this.UnitPrice = ''
+ this.GSTPercentage = ''
+ this.UnitOfMeasure = ''
+ this.Quantity = ""
+ this.BrandName = ""
+ }
+}
diff --git a/src/classes/item.ts b/src/classes/item.ts
index 271eb3d..ba437a7 100644
--- a/src/classes/item.ts
+++ b/src/classes/item.ts
@@ -1,6 +1,5 @@
export default class Item {
unitofmeasure: string
- hasdecimalquantity: boolean
name: string
description: string
hsn: string
@@ -15,7 +14,6 @@ export default class Item {
this.unitprice = ''
this.gstpercentage = ''
this.unitofmeasure = ''
- this.hasdecimalquantity = false
this.brandid = 0
}
}
diff --git a/src/components/invoice_header.vue b/src/components/invoice_header.vue
index 2c62f15..2c3c515 100644
--- a/src/components/invoice_header.vue
+++ b/src/components/invoice_header.vue
@@ -1,118 +1,115 @@
<script setup lang="ts">
-import { ref, toRaw, onMounted } from 'vue'
-import axios from 'axios'
-import { useToast } from 'vue-toast-notification'
-
-const toast = useToast({
- position: 'top-right'
-})
-
-const isLoading = ref(true)
-const allCustomers = ref([])
-
-const invoiceCustomer = ref(null)
-const invoiceNumber = ref(0)
-const invoiceDate = ref(new Date().toISOString().substr(0, 10))
-
-const handleDateChange = e => {
- invoiceDate.value = e
-}
-
-const getAllCustomers = async () => {
- allCustomers.value = []
- isLoading.value = true
-
- try {
- const r = await axios.get('/customer')
- if (r.status === 200) {
- allCustomers.value = r.data.data
- } else if (r.status === 204) {
- toast.warning('No customers found')
- }
- } catch (err) {
- toast.error('An unhandled exception occoured. Please check logs')
- console.error(err)
- }
-
- isLoading.value = false
-}
-
-const submit = async (e) => {
- e.preventDefault()
- isLoading.value = true
-
- try {
- const c = toRaw(invoiceCustomer.value)
-
- await axios.post('/invoice', {
- "invoicenumber": toRaw(invoiceNumber.value),
- "invoicedate": new Date(toRaw(invoiceDate.value)).toISOString(),
- "isdraft": true,
- "billingaddress": c.BillingAddress,
- "shippingaddress": c.BillingAddress, // TODO
- "customername": c.Name,
- "customergstin": c.Gstin,
- "customercontactname": c.ContactName,
- "customerphone": c.Phone,
- "customeremail": c.Email,
- "customerwebsite": c.Website,
- })
- } catch (err) {
- const statusCode = err.request.status
- const res = JSON.parse(err.request.response)
-
- switch (statusCode) {
- case (400, 409):
- toast.error(res.error)
- break
- default:
- console.error(err)
- toast.error('An unhandled exception occoured. Please check logs')
- }
- }
-
- isLoading.value = false
-}
-
-onMounted(() => {
- getAllCustomers()
-})
+import { toRaw, defineProps } from 'vue'
+const props = defineProps(["invoice"])
</script>
<template>
- <form class="row g-12" v-on:submit="submit">
- <div class="col-md-4">
- <label for="item-brand-input" class="form-label">Customer</label>
- <select
- v-model="invoiceCustomer"
- class="form-select"
- aria-label="Select Brand"
- id="item-brand-input"
- >
- <option selected disabled value="null">Select Customer</option>
- <option v-for="customer in allCustomers" :value="customer" :key="customer.id">
- {{ customer.Name }}
- </option>
- </select>
+ <div class="row g-3">
+ <div class="col-md-2"><!-- spacer --></div>
+
+ <!-- customer details -->
+ <div class="col-md-3">
+ <table class="table">
+ <tr>
+ <td>Invoice Number</td>
+ <td>{{ props.invoice.InvoiceNumber }}</td>
+ </tr>
+
+ <tr>
+ <td>Invoice Date</td>
+ <td>{{ props.invoice.InvoiceDate }}</td>
+ </tr>
+
+ <tr>
+ <td>Customer</td>
+ <td>{{ props.invoice.CustomerName }}</td>
+ </tr>
+
+ <tr>
+ <td>GSTIN</td>
+ <td>{{ props.invoice.CustomerGstin }}</td>
+ </tr>
+
+ <tr>
+ <td>Contact Name</td>
+ <td>{{ props.invoice.CustomerContactName }}</td>
+ </tr>
+
+ <tr>
+ <td>Phone</td>
+ <td>{{ props.invoice.CustomerPhone }}</td>
+ </tr>
+
+ <tr>
+ <td>Email</td>
+ <td>{{ props.invoice.CustomerEmail }}</td>
+ </tr>
+
+ <tr>
+ <td>Website</td>
+ <td>{{ props.invoice.CustomerWebsite }}</td>
+ </tr>
+ </table>
</div>
- <div class="col-md-4">
- <label for="invoice-number" class="form-label">Invoice Number</label>
- <input id="invoice-number" class="form-control" type="number" v-model="invoiceNumber" min="0"/>
+ <!-- billing address -->
+ <div class="col-md-3">
+ <table class="table">
+ <tr>
+ <td>Address</td>
+ <td>{{ props.invoice.BillingAddress.AddressText }}</td>
+ </tr>
+
+ <tr>
+ <td>City</td>
+ <td>{{ props.invoice.BillingAddress.City }}</td>
+ </tr>
+
+ <tr>
+ <td>State</td>
+ <td>{{ props.invoice.BillingAddress.State }}</td>
+ </tr>
+
+ <tr>
+ <td>Postal Code</td>
+ <td>{{ props.invoice.BillingAddress.PostalCode }}</td>
+ </tr>
+
+ <tr>
+ <td>Country</td>
+ <td>{{ props.invoice.BillingAddress.Country }}</td>
+ </tr>
+ </table>
</div>
- <div class="col-md-4">
- <label for="invoice-date" class="form-label">Invoice Date</label>
- <input
- type="date"
- id="invoice-date"
- class="form-control"
- :value="new Date().toISOString().substr(0, 10)"
- @input="handleDateChange($event.target.value)" />
+ <!-- shipping address -->
+ <div class="col-md-3">
+ <table class="table">
+ <tr>
+ <td>Address</td>
+ <td>{{ props.invoice.ShippingAddress.AddressText }}</td>
+ </tr>
+
+ <tr>
+ <td>City</td>
+ <td>{{ props.invoice.ShippingAddress.City }}</td>
+ </tr>
+
+ <tr>
+ <td>State</td>
+ <td>{{ props.invoice.ShippingAddress.State }}</td>
+ </tr>
+
+ <tr>
+ <td>Postal Code</td>
+ <td>{{ props.invoice.ShippingAddress.PostalCode }}</td>
+ </tr>
+
+ <tr>
+ <td>Country</td>
+ <td>{{ props.invoice.ShippingAddress.Country }}</td>
+ </tr>
+ </table>
</div>
-
- <div class="col-12">
- <input type="submit" value="Continue" class="btn btn-primary" />
- </div>
- </form>
+ </div>
</template>
diff --git a/src/components/invoice_header_editor.vue b/src/components/invoice_header_editor.vue
new file mode 100644
index 0000000..0a39ce5
--- /dev/null
+++ b/src/components/invoice_header_editor.vue
@@ -0,0 +1,151 @@
+<script setup lang="ts">
+import { ref, toRaw, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import axios from 'axios'
+import { useToast } from 'vue-toast-notification'
+import Customer from "./../classes/customer"
+
+const toast = useToast({
+ position: 'top-right'
+})
+
+const route = useRouter()
+
+const gettingData = ref(true) // for when getting all/one customer(s)
+const submitting = ref(false) // shows spinner on continue button
+const allCustomers = ref([])
+
+const customer = ref(new Customer())
+const customerSelection = ref(null)
+const invoiceDate = ref(new Date().toISOString().substr(0, 10))
+
+const getAllCustomers = async () => {
+ allCustomers.value = []
+ gettingData.value = true
+
+ try {
+ const r = await axios.get('/customer')
+ if (r.status === 200) {
+ allCustomers.value = r.data.data
+ } else if (r.status === 204) {
+ toast.warning('No customers found')
+ }
+ } catch (err) {
+ toast.error('An unhandled exception occoured. Please check logs')
+ console.error(err)
+ }
+
+ gettingData.value = false
+}
+
+const submit = async (e: Event) => {
+ e.preventDefault()
+ submitting.value = true
+
+ try {
+ const c = toRaw(customer.value)
+
+ const res = await axios.post('/invoice', {
+ "invoicedate": new Date(toRaw(invoiceDate.value)).toISOString(),
+ "isdraft": true,
+ "billingaddress": c.BillingAddress,
+ "shippingaddress": c.BillingAddress, // TODO
+ "customername": c.Name,
+ "customergstin": c.Gstin,
+ "customercontactname": c.ContactName,
+ "customerphone": c.Phone,
+ "customeremail": c.Email,
+ "customerwebsite": c.Website,
+ })
+
+ route.push({ name: "edit-draft", params: { id: res.data.data.ID }})
+ } catch (err: any) {
+ const statusCode: any = err.request.status
+ const res: any = JSON.parse(err.request.response)
+
+ switch (statusCode) {
+ case 400:
+ toast.error(res.error)
+ break
+ case 409:
+ toast.error(res.error)
+ break
+ default:
+ console.error(err)
+ toast.error('An unhandled exception occoured. Please check logs')
+ }
+ }
+
+ submitting.value = false
+}
+
+// when customer is selected,
+// get the whole customer object from server
+const refreshCustomer = async () => {
+ gettingData.value = true
+
+ const c: any = toRaw(customerSelection.value)
+
+ try {
+ const r = await axios.get(`/customer/${c.ID}`)
+ customer.value = r.data.data
+ } catch (err) {
+ toast.error('An unhandled exception occoured. Please check logs')
+ console.error(err)
+ }
+
+ gettingData.value = false
+}
+
+onMounted(() => {
+ getAllCustomers()
+})
+</script>
+
+<template>
+ <form class="row g-3" v-on:submit="submit">
+ <div class="col-md-3"><!-- spacer --></div>
+
+ <div class="col-md-3">
+ <label for="item-brand-input" class="form-label">Customer</label>
+ <select
+ v-model="customerSelection"
+ @change="refreshCustomer()"
+ class="form-select"
+ aria-label="Select Brand"
+ id="item-brand-input"
+ >
+ <option selected disabled value="null">Select Customer</option>
+ <option v-for="customer in allCustomers" :value="customer" :key="customer['id']">
+ {{ customer["Name"] }}
+ </option>
+ </select>
+ </div>
+
+ <div class="col-md-2">
+ <label for="invoice-date" class="form-label">Invoice Date</label>
+ <input
+ type="date"
+ id="invoice-date"
+ class="form-control"
+ v-model="invoiceDate"/>
+ </div>
+
+ <div class="col-md-1 d-flex align-items-end justify-content-start">
+ <button
+ type="submit"
+ class="btn btn-primary btn-block"
+ :class="{ disabled: submitting || gettingData || customerSelection === null }"
+ >
+ Continue
+ <div v-if="submitting" class="spinner-border spinner-border-sm ms-1" role="status">
+ <span class="sr-only"></span>
+ </div>
+ </button>
+ </div>
+
+ <div class="col-md-2"><!-- spacer --></div>
+ <p>TODO: add address info / shipping address selector</p>
+
+ </form>
+</template>
diff --git a/src/components/item_selector.vue b/src/components/item_selector.vue
new file mode 100644
index 0000000..5dc9b84
--- /dev/null
+++ b/src/components/item_selector.vue
@@ -0,0 +1,176 @@
+<script setup lang="ts">
+import { ref, toRaw, onMounted } from 'vue'
+import axios from 'axios'
+import { useToast } from 'vue-toast-notification'
+import InvoiceItem from "./../classes/invoice_item"
+
+const props = defineProps(["invoiceId"])
+const emit = defineEmits(["added"])
+
+const toast = useToast({
+ position: 'top-right'
+})
+
+const gettingData = ref(true) // for when getting all item(s)
+const submitting = ref(false) // shows spinner on add button
+const allItems = ref([])
+
+const item = ref(new InvoiceItem())
+const itemSelection = ref(null)
+
+const getAllItems = async () => {
+ allItems.value = []
+ gettingData.value = true
+
+ try {
+ const r = await axios.get('/item')
+ if (r.status === 200) {
+ allItems.value = r.data.data
+ } else if (r.status === 204) {
+ toast.warning('No items found')
+ }
+ } catch (err) {
+ toast.error('An unhandled exception occoured. Please check logs')
+ console.error(err)
+ }
+
+ gettingData.value = false
+}
+
+const submit = async (e: Event) => {
+ e.preventDefault()
+ submitting.value = true
+
+ try {
+ await axios.post(`/invoice/${props.invoiceId}/item`, toRaw(item.value))
+ itemSelection.value = null
+ item.value = new InvoiceItem()
+ emit("added")
+ } catch (err: any) {
+ const statusCode: any = err.request.status
+ const res: any = JSON.parse(err.request.response)
+
+ switch (statusCode) {
+ case 400:
+ toast.error(res.error)
+ break
+ case 409:
+ toast.error(res.error)
+ break
+ default:
+ console.error(err)
+ toast.error('An unhandled exception occoured. Please check logs')
+ }
+ }
+
+ submitting.value = false
+}
+
+// when item is selected,
+// set item to the newly selected item
+const itemSelected = async () => {
+ const is: any = toRaw(itemSelection.value)
+ const i = new InvoiceItem()
+ i.Name = is.Name
+ i.Description = is.Description
+ i.HSN = is.HSN
+ i.UnitPrice = is.UnitPrice
+ i.GSTPercentage = is.GSTPercentage
+ i.UnitOfMeasure = is.UnitOfMeasure
+ i.BrandName = is.Brand.Name
+ i.Quantity = "1"
+
+ item.value = i
+}
+
+onMounted(() => {
+ getAllItems()
+})
+</script>
+
+<template>
+ <form class="row g-3" v-on:submit="submit">
+ <div class="col-md-1"><!-- spacer --></div>
+
+ <div class="col-md-3">
+ <label for="item-brand-input" class="form-label">Item</label>
+ <select
+ v-model="itemSelection"
+ @change="itemSelected()"
+ class="form-select"
+ aria-label="Select Item"
+ id="item-input"
+ >
+ <option selected disabled value="null">Select Item</option>
+ <option v-for="item in allItems" :value="item" :key="item['id']">
+ {{ item["Name"] }} ({{ item["Brand"]["Name"] }})
+ </option>
+ </select>
+ </div>
+
+ <div class="col-md-3">
+ <label for="inputDescription" class="form-label">Description</label>
+ <input
+ type="text"
+ class="form-control"
+ id="inputDescription"
+ v-model="item.Description"
+ />
+ </div>
+
+ <div class="col-md-1">
+ <label for="inputHSN" class="form-label">HSN</label>
+ <input
+ type="text"
+ class="form-control"
+ id="inputHSN"
+ v-model="item.HSN"
+ />
+ </div>
+
+ <div class="col-md-1">
+ <label for="inputQty" class="form-label">Quantity {{ item.UnitOfMeasure === "" ? "" : `(${item.UnitOfMeasure})` }}</label>
+ <input
+ type="text"
+ class="form-control"
+ id="inputQty"
+ v-model="item.Quantity"
+ />
+ </div>
+
+ <div class="col-md-1">
+ <label for="inputUnitPrice" class="form-label">Unit Price</label>
+ <input
+ type="text"
+ class="form-control"
+ id="inputUnitPrice"
+ v-model="item.UnitPrice"
+ />
+ </div>
+
+ <div class="col-md-1">
+ <label for="inputGST" class="form-label">GST (%)</label>
+ <input
+ type="text"
+ class="form-control"
+ id="inputGST"
+ v-model="item.GSTPercentage"
+ />
+ </div>
+
+ <div class="col-md-10"><!-- spacer --></div>
+
+ <div class="col-md-1 d-flex align-items-end justify-content-end">
+ <button
+ type="submit"
+ class="btn btn-primary btn-block"
+ :class="{ disabled: submitting || gettingData || itemSelection === null }"
+ >
+ Add
+ <div v-if="submitting" class="spinner-border spinner-border-sm ms-1" role="status">
+ <span class="sr-only"></span>
+ </div>
+ </button>
+ </div>
+ </form>
+</template>
diff --git a/src/components/new_customer.vue b/src/components/new_customer.vue
index cbabac4..abb8736 100644
--- a/src/components/new_customer.vue
+++ b/src/components/new_customer.vue
@@ -48,7 +48,7 @@ const submit = async (e) => {
class="form-control"
id="customer-name-input"
placeholder="Firm Name"
- v-model="customer.name"
+ v-model="customer.Name"
/>
</div>
<div class="col-md-4">
@@ -58,7 +58,7 @@ const submit = async (e) => {
class="form-control"
id="customer-gstin-input"
placeholder="22AAAAA0000A1Z5"
- v-model="customer.gstin"
+ v-model="customer.Gstin"
/>
</div>
<div class="col-md-4">
@@ -68,7 +68,7 @@ const submit = async (e) => {
class="form-control"
id="customer-contactname-input"
placeholder="Contact Name"
- v-model="customer.contactname"
+ v-model="customer.ContactName"
/>
</div>
@@ -79,7 +79,7 @@ const submit = async (e) => {
class="form-control"
id="customer-phone-input"
placeholder="Contact Number"
- v-model="customer.phone"
+ v-model="customer.Phone"
/>
</div>
<div class="col-md-4">
@@ -89,7 +89,7 @@ const submit = async (e) => {
class="form-control"
id="customer-email-input"
placeholder="E-Mail Address"
- v-model="customer.email"
+ v-model="customer.Email"
/>
</div>
<div class="col-md-4">
@@ -99,7 +99,7 @@ const submit = async (e) => {
class="form-control"
id="customer-website-input"
placeholder="Website"
- v-model="customer.website"
+ v-model="customer.Website"
/>
</div>
@@ -110,7 +110,7 @@ const submit = async (e) => {
class="form-control"
id="inputAddress"
placeholder="1234 Main St"
- v-model="customer.billingaddress.addresstext"
+ v-model="customer.BillingAddress.AddressText"
></textarea>
</div>
<div class="col-md-5">
@@ -119,7 +119,7 @@ const submit = async (e) => {
type="text"
class="form-control"
id="inputCity"
- v-model="customer.billingaddress.city"
+ v-model="customer.BillingAddress.city"
/>
</div>
<div class="col-md-3">
@@ -128,7 +128,7 @@ const submit = async (e) => {
type="text"
class="form-control"
id="inputState"
- v-model="customer.billingaddress.state"
+ v-model="customer.BillingAddress.state"
/>
</div>
<div class="col-md-1">
@@ -137,7 +137,7 @@ const submit = async (e) => {
type="text"
class="form-control"
id="inputZip"
- v-model="customer.billingaddress.postalcode"
+ v-model="customer.BillingAddress.postalcode"
/>
</div>
<div class="col-md-3">
@@ -146,7 +146,7 @@ const submit = async (e) => {
type="text"
class="form-control"
id="inputCountry"
- v-model="customer.billingaddress.country"
+ v-model="customer.BillingAddress.country"
/>
</div>
diff --git a/src/router/index.ts b/src/router/index.ts
index 7719220..55e2070 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -9,6 +9,7 @@ import NewCustomer from '../views/NewCustomer.vue'
import AllItems from '../views/AllItems.vue'
import NewItem from '../views/NewItem.vue'
import NewInvoice from '../views/NewInvoice.vue'
+import EditInvoice from '../views/EditInvoice.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -67,6 +68,12 @@ const router = createRouter({
component: NewInvoice,
meta: { isAuth: true }
},
+ {
+ path: '/invoice/edit-draft/:id',
+ name: 'edit-draft',
+ component: EditInvoice,
+ meta: { isAuth: true }
+ },
]
})
diff --git a/src/views/EditInvoice.vue b/src/views/EditInvoice.vue
new file mode 100644
index 0000000..983e9ef
--- /dev/null
+++ b/src/views/EditInvoice.vue
@@ -0,0 +1,47 @@
+<script setup lang="ts">
+import { ref, toRaw, onMounted } from 'vue'
+import { useRoute } from "vue-router"
+import { useToast } from 'vue-toast-notification'
+import axios from 'axios'
+
+import Invoice from "./../classes/invoice"
+
+import invoiceHeader from './../components/invoice_header.vue'
+import itemSelector from './../components/item_selector.vue'
+
+const toast = useToast({
+ position: 'top-right'
+})
+
+const route = useRoute()
+
+const invoice = ref(new Invoice())
+const isLoading = ref(true)
+
+const getInvoice = async () => {
+ isLoading.value = true
+
+ try {
+ const r = await axios.get(`/invoice/${route.params.id}`)
+ invoice.value = r.data.data
+ } catch (err) {
+ toast.error('An unhandled exception occoured. Please check logs')
+ console.error(err)
+ }
+
+ isLoading.value = false
+}
+
+const refreshItems = () => {
+ toast.success("Item was added but what happens after that hasn't been implemented yet!")
+}
+
+onMounted(() => {
+ getInvoice()
+})
+</script>
+
+<template>
+ <invoiceHeader :invoice="invoice" />
+ <itemSelector :invoiceId="invoice.ID" @added="refreshItems()"/>
+</template>
diff --git a/src/views/NewInvoice.vue b/src/views/NewInvoice.vue
index b2f32e2..06d3ec7 100644
--- a/src/views/NewInvoice.vue
+++ b/src/views/NewInvoice.vue
@@ -1,7 +1,7 @@
<script setup lang="ts">
-import invoiceHeader from './../components/invoice_header.vue'
+import invoiceHeaderEditor from './../components/invoice_header_editor.vue'
</script>
<template>
- <invoiceHeader />
+ <invoiceHeaderEditor />
</template>