Move REST calls from invoice table to invoice dialog, fixes #56
This commit is contained in:
@@ -9,9 +9,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { ref, computed, watch, onMounted } from "vue"
|
||||
import { Customer, Invoice, Contact, PaymentTerms, Address, LineItem } from "@/types"
|
||||
import { newInvoice, newCustomer, newContact, newBillingData } from '@/types/index.d'
|
||||
import { ref, computed, watch, onMounted, onUpdated, toRaw } from "vue"
|
||||
import { Customer, Invoice, Contact, PaymentTerms, Address, LineItem, PaymentStatus } from "@/types"
|
||||
import { newCustomer, newContact, newBillingData } from '@/types/index.d'
|
||||
import { toCurrency, toLocalDate, toShortISOString, calcDueDate, toFixedRounded } from '@/lib/utils'
|
||||
import axios from 'axios'
|
||||
import { type DateValue, getLocalTimeZone, fromDate } from "@internationalized/date"
|
||||
@@ -32,7 +32,6 @@ import { Kbd, KbdGroup } from '@/components/ui/kbd';
|
||||
import DialogClose from "../ui/dialog/DialogClose.vue"
|
||||
import DialogCloseButton from "../DialogCloseButton/DialogCloseButton.vue";
|
||||
import SendMailDialog from "../ui/send-mail-dialog/SendMailDialog.vue"
|
||||
import Skeleton from "../ui/skeleton/Skeleton.vue"
|
||||
|
||||
const props = defineProps<{
|
||||
invoiceData?: Invoice,
|
||||
@@ -42,16 +41,33 @@ const props = defineProps<{
|
||||
const invoice = ref<Invoice>()
|
||||
const customers = ref([] as Customer[])
|
||||
const paymentTermsData = ref([] as PaymentTerms[])
|
||||
const isDirty = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const isDirty = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const isSaving = ref(false)
|
||||
const itemsLoading = ref(false)
|
||||
const importContact = ref(newContact() as Contact)
|
||||
const importCustomer = ref(newCustomer() as Customer)
|
||||
const alert = alertStore()
|
||||
const reminderDialogOpen = ref(false)
|
||||
const reminderLoading = ref(false)
|
||||
|
||||
const value = ref<DateValue>() // TODO: name properly
|
||||
const emit = defineEmits(['update:modelValue', 'save', 'cancel', 'delete'])
|
||||
|
||||
const isOpen = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const title = computed<string>(() => {
|
||||
if (invoice.value && invoice.value.id !== 0) {
|
||||
return `Rechnung ${invoice.value.nr || ''}`
|
||||
} else {
|
||||
return 'Neue Rechnung'
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
// Load customers and payment terms
|
||||
try {
|
||||
@@ -65,108 +81,103 @@ onMounted(async () => {
|
||||
} catch (error) {
|
||||
toast.error('Fehler beim Laden der Daten', error || String(error))
|
||||
}
|
||||
})
|
||||
|
||||
console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
console.groupEnd()
|
||||
onUpdated(() => {
|
||||
if (isLoading.value) isLoading.value = false
|
||||
// console.group('onUpdated')
|
||||
// console.error(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
// console.groupEnd()
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (open) => {
|
||||
// on open
|
||||
if (open) {
|
||||
console.group('watch props.modelValue')
|
||||
console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
// console.group('on open')
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
|
||||
// Reset state flags
|
||||
isDirty.value = false;
|
||||
isLoading.value = true;
|
||||
|
||||
// Get invoice data from props
|
||||
// console.warn('trigger invoice watcher')
|
||||
invoice.value = props.invoiceData
|
||||
|
||||
|
||||
// Load line items
|
||||
try {
|
||||
axios.get('/api/lineitems/' + invoice.value.id).then(response => {
|
||||
if (invoice.value) invoice.value.items = response.data as LineItem[]
|
||||
})
|
||||
} catch (error) {
|
||||
toast.error('Fehler beim Laden der Positionen', error || String(error))
|
||||
if (invoice.value && invoice.value.id !== 0) {
|
||||
itemsLoading.value = true
|
||||
|
||||
try {
|
||||
itemsLoading.value = true
|
||||
axios.get('/api/lineitems/' + invoice.value.id).then(response => {
|
||||
if (invoice.value) invoice.value.items = response.data as LineItem[]
|
||||
})
|
||||
} catch (error) {
|
||||
toast.error('Fehler beim Laden der Positionen', error || String(error))
|
||||
}
|
||||
} else {
|
||||
itemsLoading.value = false
|
||||
}
|
||||
}
|
||||
// on close
|
||||
else {
|
||||
invoice.value = undefined
|
||||
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
// console.groupEnd()
|
||||
}
|
||||
})
|
||||
|
||||
const isOpen = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const title = computed<string>(() => {
|
||||
if (isLoading.value) {
|
||||
return 'Rechnung'
|
||||
} else if (invoice.value && invoice.value.id > 0) {
|
||||
return `Rechnung ${invoice.value.nr}`
|
||||
} else {
|
||||
return 'Neue Rechnung'
|
||||
}
|
||||
})
|
||||
|
||||
const value = ref<DateValue>()
|
||||
|
||||
|
||||
// watch changes on local invoice date
|
||||
watch(invoice,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue == oldValue) return
|
||||
|
||||
console.group('watch invoice')
|
||||
console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
|
||||
if (isLoading.value) {
|
||||
if (!invoice.value) return;
|
||||
|
||||
if (invoice.value.id === 0) {
|
||||
isLoading.value = false;
|
||||
if (!newValue) {
|
||||
// console.groupEnd()
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial load of invoice data
|
||||
if (!invoice.value.billingData) {
|
||||
// Set default billing data from customer
|
||||
invoice.value.billingData = {
|
||||
companyName: invoice.value.customer?.companyName || "",
|
||||
vatId: invoice.value.customer?.vatId || "",
|
||||
// Set default billing data from customer
|
||||
if (!newValue.billingData) {
|
||||
// console.warn('trigger invoice watcher')
|
||||
newValue.billingData = {
|
||||
companyName: newValue.customer?.companyName || "",
|
||||
vatId: newValue.customer?.vatId || "",
|
||||
billingAddress: {
|
||||
lineOne: invoice.value.customer?.billingAddress?.lineOne || "",
|
||||
lineTwo: invoice.value.customer?.billingAddress?.lineTwo || "",
|
||||
city: invoice.value.customer?.billingAddress?.city || "",
|
||||
postalCode: invoice.value.customer?.billingAddress?.postalCode || "",
|
||||
countryCode: invoice.value.customer?.billingAddress?.countryCode || "",
|
||||
lineOne: newValue.customer?.billingAddress?.lineOne || "",
|
||||
lineTwo: newValue.customer?.billingAddress?.lineTwo || "",
|
||||
city: newValue.customer?.billingAddress?.city || "",
|
||||
postalCode: newValue.customer?.billingAddress?.postalCode || "",
|
||||
countryCode: newValue.customer?.billingAddress?.countryCode || "",
|
||||
},
|
||||
contactSalutation: invoice.value.customer?.contacts && invoice.value.customer.contacts.length > 0 ? invoice.value.customer.contacts[0].salutation : "",
|
||||
contactFirstName: invoice.value.customer?.contacts && invoice.value.customer.contacts.length > 0 ? invoice.value.customer.contacts[0].firstName : "",
|
||||
contactLastName: invoice.value.customer?.contacts && invoice.value.customer.contacts.length > 0 ? invoice.value.customer.contacts[0].lastName : "",
|
||||
paymentTerms: invoice.value.customer?.paymentTerms || paymentTermsData.value.length > 0 ? paymentTermsData.value[2] : null,
|
||||
contactSalutation: newValue.customer?.contacts && newValue.customer.contacts.length > 0 ? newValue.customer.contacts[0].salutation : "",
|
||||
contactFirstName: newValue.customer?.contacts && newValue.customer.contacts.length > 0 ? newValue.customer.contacts[0].firstName : "",
|
||||
contactLastName: newValue.customer?.contacts && newValue.customer.contacts.length > 0 ? newValue.customer.contacts[0].lastName : "",
|
||||
paymentTerms: newValue.customer?.paymentTerms || paymentTermsData.value.length > 0 ? paymentTermsData.value[2] : null,
|
||||
}
|
||||
}
|
||||
|
||||
if (invoice.value.customer?.id !== 0) {
|
||||
importCustomer.value = invoice.value.customer as Customer
|
||||
|
||||
// console.log("billingData contact", invoice.value?.billingData?.contactFirstName, invoice.value?.billingData?.contactLastName)
|
||||
invoice.value.customer?.contacts.find(contact => {
|
||||
if (newValue.customer?.id !== 0) {
|
||||
// if (importCustomer.value != newValue.customer)
|
||||
// console.warn('trigger importCustomer watcher')
|
||||
importCustomer.value = newValue.customer as Customer
|
||||
newValue.customer?.contacts.find(contact => {
|
||||
if (
|
||||
contact.firstName === invoice.value?.billingData?.contactFirstName &&
|
||||
contact.lastName === invoice.value?.billingData?.contactLastName
|
||||
contact.firstName === newValue?.billingData?.contactFirstName &&
|
||||
contact.lastName === newValue?.billingData?.contactLastName
|
||||
) {
|
||||
// if (importContact.value != contact)
|
||||
// console.warn('trigger importContact watcher')
|
||||
importContact.value = contact
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
value.value = fromDate(new Date(invoice.value.invoiceDate), getLocalTimeZone())
|
||||
value.value = fromDate(new Date(newValue.invoiceDate), getLocalTimeZone())
|
||||
}
|
||||
else {
|
||||
isDirty.value = true
|
||||
@@ -180,43 +191,50 @@ watch(invoice,
|
||||
|
||||
watch(importCustomer,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue == oldValue) return
|
||||
|
||||
if (!invoice.value) return
|
||||
|
||||
console.group('watch importCustomer')
|
||||
console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
// console.group('watch importCustomer')
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
|
||||
// Don't overwrite these values during loading
|
||||
// they can intentionally be different from customer data
|
||||
if (!isLoading.value) {
|
||||
if (!invoice.value.billingData) invoice.value.billingData = newBillingData()
|
||||
|
||||
// console.warn('trigger invoice watcher')
|
||||
invoice.value.billingData.companyName = newValue.companyName
|
||||
invoice.value.billingData.vatId = newValue.vatId
|
||||
|
||||
// Don't overwrite these values during loading
|
||||
// they can intentionally be different from customer data
|
||||
if (!invoice.value.billingData.billingAddress || !isLoading.value)
|
||||
if (!invoice.value.billingData.billingAddress)
|
||||
invoice.value.billingData.billingAddress = newValue.billingAddress as Address
|
||||
if (!invoice.value.billingData.contactFirstName || !isLoading.value)
|
||||
if (!invoice.value.billingData.contactFirstName)
|
||||
invoice.value.billingData.contactFirstName = newValue.contacts && newValue.contacts.length > 0 ? newValue.contacts[0].firstName : ''
|
||||
if (!invoice.value.billingData.contactLastName || !isLoading.value)
|
||||
if (!invoice.value.billingData.contactLastName)
|
||||
invoice.value.billingData.contactLastName = newValue.contacts && newValue.contacts.length > 0 ? newValue.contacts[0].lastName : ''
|
||||
if (!invoice.value.billingData.paymentTerms || !isLoading.value)
|
||||
if (!invoice.value.billingData.paymentTerms)
|
||||
invoice.value.billingData.paymentTerms = newValue.paymentTerms as PaymentTerms
|
||||
|
||||
// console.warn('trigger invoice watcher')
|
||||
invoice.value.customer = newValue
|
||||
|
||||
isDirty.value = true;
|
||||
}
|
||||
|
||||
console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
console.groupEnd()
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
// console.groupEnd()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
watch(importContact,
|
||||
(newValue, oldValue) => {
|
||||
if (newValue == oldValue) return
|
||||
if (!invoice.value) return
|
||||
|
||||
console.group('watch importContact')
|
||||
console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
// console.group('watch importContact')
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
|
||||
if (!isLoading.value) {
|
||||
if (newValue.id !== 0) {
|
||||
@@ -228,12 +246,10 @@ watch(importContact,
|
||||
}
|
||||
|
||||
isDirty.value = true;
|
||||
} else {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
console.groupEnd()
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`)
|
||||
// console.groupEnd()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
@@ -246,14 +262,73 @@ const billingContactEmail = computed<string | undefined>(() => {
|
||||
else return ""
|
||||
})
|
||||
|
||||
const saveChanges = () => {
|
||||
const save = async () => {
|
||||
if (invoice.value) {
|
||||
emit('save', invoice.value)
|
||||
// isOpen.value = false
|
||||
// add spinner to save button
|
||||
isSaving.value = true
|
||||
|
||||
try {
|
||||
// Prepare the invoice data for API request
|
||||
const invoiceToSave = {
|
||||
nr: invoice.value.nr,
|
||||
invoiceDate: invoice.value.invoiceDate,
|
||||
dueDate: invoice.value.dueDate,
|
||||
serviceStartDate: invoice.value.serviceStartDate,
|
||||
serviceEndDate: invoice.value.serviceEndDate,
|
||||
isRecurring: invoice.value.isRecurring,
|
||||
isPartialService: invoice.value.isPartialService,
|
||||
paymentStatus: invoice.value.paymentStatus,
|
||||
totalAmount: invoice.value.totalAmount,
|
||||
title: invoice.value.title,
|
||||
text: invoice.value.text,
|
||||
customerId: invoice.value.customer ? invoice.value.customer.id : null,
|
||||
billingData: {
|
||||
companyName: invoice.value.billingData?.companyName,
|
||||
vatId: invoice.value.billingData?.vatId,
|
||||
billingAddress: invoice.value.billingData?.billingAddress,
|
||||
contactSalutation: invoice.value.billingData?.contactSalutation,
|
||||
contactFirstName: invoice.value.billingData?.contactFirstName,
|
||||
contactLastName: invoice.value.billingData?.contactLastName,
|
||||
paymentTerms: invoice.value.billingData?.paymentTerms
|
||||
},
|
||||
|
||||
// Items will be handled separately in the controller
|
||||
items: invoice.value.items.map(item => ({
|
||||
id: item.id, // Include ID for existing items
|
||||
position: item.position,
|
||||
type: item.type,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
quantity: item.quantity,
|
||||
unit: item.unit,
|
||||
price: item.price
|
||||
}))
|
||||
}
|
||||
|
||||
if (invoice.value.id === 0) {
|
||||
// Create new invoice
|
||||
const response = await axios.post('/api/invoices', invoiceToSave);
|
||||
invoice.value = response.data;
|
||||
} else {
|
||||
// Update existing invoice
|
||||
const response = await axios.put(`/api/invoices/${invoice.value.id}`, invoiceToSave);
|
||||
}
|
||||
|
||||
emit('save', invoice.value)
|
||||
// isOpen.value = false
|
||||
} catch (error) {
|
||||
toast.error("Rechnung konnte nicht gespeichert werden", {
|
||||
description: (error as Error).message,
|
||||
})
|
||||
} finally {
|
||||
// remove spinner from save button
|
||||
isSaving.value = false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const cancelChanges = (event: Event | null) => {
|
||||
const cancel = (event: Event | null) => {
|
||||
if (!event) return
|
||||
|
||||
event.preventDefault()
|
||||
@@ -300,21 +375,30 @@ const issueInvoice = function () {
|
||||
const deleteInvoice = function () {
|
||||
alert.show(
|
||||
"Möchtest Du diese Rechnung wirklich löschen?",
|
||||
(invoice.value?.paymentStatus == "draft") ? null : "Nach GoBD musst Du alle Belege und Daten in unveränderter Form aufbewahren",
|
||||
(invoice.value?.paymentStatus == "draft") ? null : "Nach GoBD musst Du alle Belege und Daten in unveränderter Form aufbewahren.",
|
||||
{
|
||||
actionText: "Löschen",
|
||||
actionVariant: "destructive",
|
||||
onAction: () => {
|
||||
emit('delete', invoice.value?.id)
|
||||
isOpen.value = false
|
||||
onAction: async () => {
|
||||
try {
|
||||
if (!invoice.value || !invoice.value.id) return
|
||||
await axios.delete('/api/invoices/' + invoice.value.id)
|
||||
emit('delete', invoice.value?.id)
|
||||
isOpen.value = false
|
||||
} catch (error) {
|
||||
toast.error("Rechnung konnte nicht gelöscht werden", {
|
||||
description: (error as Error).message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const cancelInvoice = function () {
|
||||
const updateStatus = function (status: PaymentStatus) {
|
||||
if (!invoice.value) return;
|
||||
invoice.value.paymentStatus = 'cancelled'
|
||||
invoice.value.paymentStatus = status
|
||||
isDirty.value = true
|
||||
}
|
||||
|
||||
const openReminderDialog = function () {
|
||||
@@ -343,13 +427,37 @@ const sendReminder = async function (recipient: string | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
const updateTotalAmount = () => {
|
||||
const updateLineItems = (newItems: LineItem[]) => {
|
||||
if (isLoading.value) return;
|
||||
if (!invoice.value) return;
|
||||
|
||||
// console.group('updateLineItems');
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`);
|
||||
|
||||
// Konvertiere die neuen Items in normale Objekte
|
||||
const rawItems = toRaw(newItems) || [];
|
||||
|
||||
// Sortiere die Items nach position
|
||||
const sortedItems = [...rawItems].sort((a, b) => a.position - b.position);
|
||||
|
||||
// Erstellen Sie eine tiefe Kopie der neuen Items
|
||||
const updatedItems = JSON.parse(JSON.stringify(sortedItems));
|
||||
|
||||
// Aktualisieren Sie die Items in der Rechnung
|
||||
invoice.value.items = updatedItems;
|
||||
|
||||
// Berechnen Sie den neuen Gesamtbetrag
|
||||
let total = 0;
|
||||
invoice.value.items.forEach(item => {
|
||||
updatedItems.forEach(item => {
|
||||
total += item.quantity * item.price;
|
||||
});
|
||||
invoice.value.totalAmount = total;
|
||||
|
||||
// Erzwingen Sie eine Aktualisierung der Benutzeroberfläche
|
||||
invoice.value = { ...invoice.value };
|
||||
|
||||
// console.log(`isDirty: ${isDirty.value}\tisLoading: ${isLoading.value}`);
|
||||
// console.groupEnd();
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -358,14 +466,7 @@ const updateTotalAmount = () => {
|
||||
<Dialog id="invoice-dialog" v-model:open="isOpen">
|
||||
<DialogContent
|
||||
class="sm:max-w-[min((100%-2rem),1152px)] grid-rows-[auto_minmax(0,1fr)_auto] h-[calc(100dvh-2rem)] gap-0 p-0 outline-none"
|
||||
@escapeKeyDown="cancelChanges" @interactOutside="cancelChanges">
|
||||
|
||||
<!-- <div :class="{ 'opacity-100': isLoading }"
|
||||
class="absolute inset-[1rem_0_0_0] flex justify-center items-center z-10 pointer-events-none bg-background opacity-0 transition-opacity rounded-lg">
|
||||
<div class="bg-sidebar rounded-lg p-6">
|
||||
<Loader2 class="h-6 w-6 animate-spin text-muted-foreground" stroke-width="1.5" />
|
||||
</div>
|
||||
</div> -->
|
||||
@escapeKeyDown="cancel" @interactOutside="cancel">
|
||||
|
||||
<DialogHeader class="p-4 md:p-6 lg:p-12 pb-0 md:pb-2 lg:pb-8 flex flex-row items-start gap-6">
|
||||
|
||||
@@ -383,8 +484,10 @@ const updateTotalAmount = () => {
|
||||
<div class="flex gap-2 items-center">
|
||||
<TooltipProvider>
|
||||
<!-- Save -->
|
||||
<Button v-if="invoice && isDirty" class="grow md:grow-0" size="sm" @click="saveChanges">
|
||||
<Check stroke-width="1.5" />
|
||||
<Button v-if="invoice && isDirty" class="grow md:grow-0" size="sm" @click="save"
|
||||
:disabled="isSaving">
|
||||
<Loader2 v-if="isSaving" stroke-width="1.5" class="animate-spin" />
|
||||
<Check v-else stroke-width="1.5" />
|
||||
Speichern
|
||||
</Button>
|
||||
|
||||
@@ -405,7 +508,7 @@ const updateTotalAmount = () => {
|
||||
<!-- Paid -->
|
||||
<Tooltip v-if="invoice && ['issued', 'due', 'reminded'].includes(invoice.paymentStatus)">
|
||||
<TooltipTrigger>
|
||||
<Button size="sm" variant="success" @click="invoice.paymentStatus = 'paid'">
|
||||
<Button size="sm" variant="success" @click="updateStatus('paid')">
|
||||
<FileCheck stroke-width="1.5" /> Bezahlt
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
@@ -488,7 +591,7 @@ const updateTotalAmount = () => {
|
||||
<!-- Cancel -->
|
||||
<DropdownMenuItem
|
||||
v-if="invoice && ['issued', 'due', 'reminded'].includes(invoice.paymentStatus)"
|
||||
class="flex justify-between" @click="deleteInvoice">
|
||||
class="flex justify-between" @click="updateStatus('cancelled')">
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- <FileX stroke-width="1.5" class="text-muted-foreground"/> -->
|
||||
<Ban :strokeWidth="1.5" class="text-current" />
|
||||
@@ -515,7 +618,7 @@ const updateTotalAmount = () => {
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="overflow-y-auto p-4 md:p-6 lg:p-12 pt-0!" v-if="invoice">
|
||||
<div class="overflow-y-scroll p-4 md:p-6 lg:p-12 pt-0!" v-if="invoice">
|
||||
|
||||
<div id="document">
|
||||
<div id="document-header"
|
||||
@@ -553,11 +656,11 @@ const updateTotalAmount = () => {
|
||||
<div class="grid grid-cols-[auto_auto_auto_auto] items-end gap-x-6 gap-y-0">
|
||||
<label class="text-muted-foreground text-xs pb-[0.4rem]">Netto</label>
|
||||
<span class="text-lg text-muted-foreground place-self-end">{{
|
||||
toCurrency(invoice.totalAmount) }}</span>
|
||||
toCurrency(invoice?.totalAmount || 0) }}</span>
|
||||
<label class="text-muted-foreground text-xs pb-[0.4rem]">Brutto</label>
|
||||
<span class="text-xl font-bold place-self-end">{{
|
||||
toCurrency(toFixedRounded(Number(invoice.totalAmount *
|
||||
1.19), 2)) }}</span>
|
||||
toCurrency(toFixedRounded(Number(invoice?.totalAmount || 0) *
|
||||
1.19, 2)) }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -716,8 +819,9 @@ const updateTotalAmount = () => {
|
||||
class="font-light bg-transparent dark:bg-transparent hover:bg-accent dark:hover:bg-accent/30 border-none shadow-none" />
|
||||
</div>
|
||||
|
||||
<LineItemTable :lineItems="invoice.items" @update:lineItems="updateTotalAmount" sticky-top="7"
|
||||
class="mt-4" />
|
||||
|
||||
<LineItemTable :lineItems="invoice.items" @update:lineItems="updateLineItems" sticky-top="7"
|
||||
:isLoading="itemsLoading" class="mt-4" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user