feat(frontend): add VAT breakdown, print receipt, and VAT indicators
Add print receipt feature to UserBookingModal with full VAT breakdown. Fix VAT rate display (remove double *100). Show VAT breakdown (net + VAT) on booking confirmed page. Add VAT info to EditBookingModal. Show incl. VAT label on BookingFlow total, schedule, and today dashboard. Add VAT note to prices page. Show booking total amount on account page. Improve VAT enable toast in BusinessSettings. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import PolicyPopover from '$lib/components/ui/policyPopover.svelte';
|
||||
import { POLICY } from '$lib/constants/policy';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
@@ -11,7 +12,6 @@
|
||||
import EditRequestModal from '$lib/components/account/EditRequestModal.svelte';
|
||||
import { computeBalanceDue } from '$lib/utils/booking';
|
||||
import type { Booking, BookingDiscount, Payment } from '$lib/types/booking';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
bookingId: string;
|
||||
@@ -20,6 +20,7 @@
|
||||
let { open = $bindable(), bookingId }: Props = $props();
|
||||
|
||||
let selectedBooking = $state<Booking | null>(null);
|
||||
let businessSettings = $derived(getBusinessInfo());
|
||||
let loading = $state(false);
|
||||
let hasPendingEditRequest = $state(false);
|
||||
let pendingEditRequest = $state<{
|
||||
@@ -77,6 +78,20 @@
|
||||
|
||||
let balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
||||
|
||||
let totalVAT = $derived(
|
||||
selectedBooking?.payments
|
||||
?.filter((p) => p.status === 'completed' && p.vat_amount)
|
||||
.reduce((sum, p) => sum + (p.vat_amount ?? 0), 0) || 0
|
||||
);
|
||||
|
||||
let totalNet = $derived(
|
||||
selectedBooking?.payments
|
||||
?.filter((p) => p.status === 'completed' && p.net_amount)
|
||||
.reduce((sum, p) => sum + (p.net_amount ?? 0), 0) || 0
|
||||
);
|
||||
|
||||
let hasVAT = $derived(totalVAT > 0);
|
||||
|
||||
let depositOutstanding = $derived(
|
||||
selectedBooking?.deposit_required && !selectedBooking?.deposit_paid
|
||||
);
|
||||
@@ -194,9 +209,9 @@
|
||||
|
||||
async function fetchBookingDetails() {
|
||||
if (!bookingId) return;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/bookings/${bookingId}`, {
|
||||
const bookingResp = await fetch(`/api/bookings/${bookingId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -204,10 +219,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (bookingResp.ok) {
|
||||
const data = await bookingResp.json();
|
||||
selectedBooking = data as Booking;
|
||||
|
||||
// Ensure business info is loaded (cached by shared store)
|
||||
ensureBusinessInfo();
|
||||
|
||||
const editResp = await fetch(`/api/bookings/${bookingId}/edit-request`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
@@ -215,7 +233,7 @@
|
||||
hasPendingEditRequest = editData.edit_request != null;
|
||||
pendingEditRequest = editData.edit_request || null;
|
||||
} else {
|
||||
const text = await response.text();
|
||||
const text = await bookingResp.text();
|
||||
toast.error('Failed to load booking: ' + text);
|
||||
open = false;
|
||||
}
|
||||
@@ -242,6 +260,76 @@
|
||||
}
|
||||
});
|
||||
|
||||
function printReceipt() {
|
||||
if (!selectedBooking) { toast.error('No booking data to print'); return; }
|
||||
const pw = window.open('', '_blank');
|
||||
if (!pw) { toast.error('Please allow pop-ups to print the receipt'); return; }
|
||||
|
||||
const biz = businessSettings;
|
||||
const paidPayments = selectedBooking.payments?.filter((p) => p.status === 'completed' && p.payment_method !== 'discount') ?? [];
|
||||
const discountPayments = selectedBooking.payments?.filter((p) => p.payment_method === 'discount' && p.status === 'completed') ?? [];
|
||||
const refunds = selectedBooking.refunds?.filter((r) => r.status === 'completed') ?? [];
|
||||
const grossTotal = paidPayments.reduce((s, p) => s + p.amount, 0);
|
||||
|
||||
const esc = (str: string | null | undefined): string => {
|
||||
if (!str) return '';
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
};
|
||||
|
||||
const fmt = (val: number | null | undefined, fallback = '\u2014'): string => {
|
||||
return val != null ? '\u00a3' + val.toFixed(2) : fallback;
|
||||
};
|
||||
|
||||
pw.document.write(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Receipt</title>
|
||||
<style>
|
||||
@page { margin: 12mm; }
|
||||
* { color: #000 !important; background: transparent !important; }
|
||||
body { font-family: 'Segoe UI', Arial, sans-serif; font-size: 12px; line-height: 1.5; max-width: 700px; margin: 0 auto; padding: 20px; }
|
||||
h1 { font-size: 18px; margin: 0 0 2px 0; }
|
||||
.header { border-bottom: 2px solid #000; padding-bottom: 10px; margin-bottom: 14px; }
|
||||
.header p { margin: 1px 0; font-size: 11px; }
|
||||
table { width: 100%; border-collapse: collapse; margin: 10px 0; }
|
||||
th, td { padding: 5px 8px; text-align: left; border-bottom: 1px solid #000; font-size: 11px; }
|
||||
th { font-weight: 600; }
|
||||
.total td { font-weight: 700; border-top: 2px solid #000; }
|
||||
.warning { font-size: 10px; margin-top: 4px; }
|
||||
.footer { margin-top: 20px; padding-top: 10px; border-top: 1px solid #000; font-size: 10px; text-align: center; }
|
||||
@media print { body { padding: 0; } }
|
||||
</style></head><body>
|
||||
<div class="header">
|
||||
<h1>${esc(biz?.business_name ?? 'Crussell Nail Art Studio')}</h1>
|
||||
<p>${esc(biz?.business_address ?? '')}</p>
|
||||
${biz?.is_vat_registered && biz?.vat_registration_number ? `<p>VAT Reg: ${esc(biz.vat_registration_number)}</p>` : ''}
|
||||
${biz?.business_phone ? `<p>Tel: ${esc(biz.business_phone)}</p>` : ''}
|
||||
${biz?.business_email ? `<p>Email: ${esc(biz.business_email)}</p>` : ''}
|
||||
</div>
|
||||
<h2>Receipt</h2>
|
||||
<table>
|
||||
<tr><td style="width:110px;font-weight:600">Booking Ref</td><td>${esc(selectedBooking.id)}</td></tr>
|
||||
<tr><td style="font-weight:600">Date</td><td>${new Date(selectedBooking.start_time).toLocaleDateString('en-GB', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}</td></tr>
|
||||
<tr><td style="font-weight:600">Status</td><td style="text-transform:capitalize">${selectedBooking.status.replace('_', ' ')}</td></tr>
|
||||
</table>
|
||||
<h2>Services</h2>
|
||||
<table>
|
||||
<tr><th>Service</th><th style="text-align:right">Price</th></tr>
|
||||
${(selectedBooking.services ?? []).map(s => `<tr><td>${esc(s.service_name)}${s.duration_minutes ? ' (' + s.duration_minutes + ' min)' : ''}</td><td style="text-align:right">\u00a3${(s.price ?? 0).toFixed(2)}</td></tr>`).join('')}
|
||||
</table>
|
||||
<h2>Payments</h2>
|
||||
<table>
|
||||
<tr><th>Type</th><th>Method</th><th style="text-align:right">Net</th><th style="text-align:right">VAT</th><th style="text-align:right">Gross</th></tr>
|
||||
${paidPayments.map(p => `<tr><td style="text-transform:capitalize">${p.payment_type}</td><td style="text-transform:capitalize">${p.payment_method ?? '\u2014'}</td><td style="text-align:right">${fmt(p.net_amount)}</td><td style="text-align:right">${p.is_vat_applicable && p.vat_amount != null ? fmt(p.vat_amount) : '\u2014'}</td><td style="text-align:right">\u00a3${p.amount.toFixed(2)}</td></tr>`).join('')}
|
||||
${discountPayments.map(d => `<tr><td>Discount</td><td>\u2014</td><td style="text-align:right;color:#059669">-\u00a3${Math.abs(d.amount).toFixed(2)}</td><td style="text-align:right;color:#059669">\u2014</td><td style="text-align:right;color:#059669">\u2014</td></tr>`).join('')}
|
||||
${refunds.map(r => `<tr><td>Refund</td><td>\u2014</td><td style="text-align:right;color:#dc2626">-\u00a3${r.amount.toFixed(2)}</td><td style="text-align:right;color:#dc2626">\u2014</td><td style="text-align:right;color:#dc2626">\u2014</td></tr>`).join('')}
|
||||
${hasVAT ? `<tr class="total"><td colspan="2"></td><td style="text-align:right">\u00a3${totalNet.toFixed(2)}</td><td style="text-align:right">\u00a3${totalVAT.toFixed(2)}</td><td style="text-align:right">\u00a3${(totalNet + totalVAT).toFixed(2)}</td></tr>` : ''}
|
||||
<tr class="total"><td colspan="4" style="text-align:right">Total Paid</td><td style="text-align:right">\u00a3${grossTotal.toFixed(2)}</td></tr>
|
||||
</table>
|
||||
${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}% where shown above.</p>` : '<p class="warning">VAT is not applicable for this transaction.</p>'}
|
||||
<div class="footer"><p>Thank you for visiting ${esc(biz?.business_name ?? 'Crussell')}.</p></div>
|
||||
</body></html>`);
|
||||
pw.document.close();
|
||||
pw.onload = () => pw.print();
|
||||
}
|
||||
|
||||
async function cancelBooking() {
|
||||
if (!selectedBooking) return;
|
||||
cancelling = true;
|
||||
@@ -416,6 +504,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{#if selectedBooking.services && selectedBooking.services.length > 0}
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
||||
<h3 class="mb-3 text-sm font-semibold tracking-wide text-gray-600 uppercase">
|
||||
@@ -516,6 +606,19 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if hasVAT}
|
||||
<div class="border-t border-gray-200 pt-2 mt-2">
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>Net amount (excl. VAT)</span>
|
||||
<span class="font-medium text-gray-700">£{totalNet.toFixed(2)}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>VAT ({businessSettings?.default_vat_rate ?? 20}%)</span>
|
||||
<span class="font-medium text-gray-700">£{totalVAT.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-gray-600">Amount Paid (Card/Cash)</span>
|
||||
<span class="font-semibold text-green-700">
|
||||
@@ -584,7 +687,7 @@
|
||||
<div>Net: £{payment.net_amount?.toFixed(2) || '0.00'}</div>
|
||||
{#if payment.vat_amount}
|
||||
<div>
|
||||
VAT ({(payment.vat_rate || 0) * 100}%): £{payment.vat_amount.toFixed(
|
||||
VAT ({payment.vat_rate || 0}%): £{payment.vat_amount.toFixed(
|
||||
2
|
||||
)}
|
||||
</div>
|
||||
@@ -696,6 +799,16 @@
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-2">
|
||||
{#if selectedBooking}
|
||||
<Button
|
||||
size="sm"
|
||||
class="flex-1 hover:bg-gray-50"
|
||||
variant="outline"
|
||||
onclick={printReceipt}
|
||||
>
|
||||
Print Receipt
|
||||
</Button>
|
||||
{/if}
|
||||
{#if isCompleted}
|
||||
<Button
|
||||
size="sm"
|
||||
|
||||
@@ -645,7 +645,7 @@
|
||||
<div class="mt-2 text-xs text-gray-600">
|
||||
<div>Net: £{payment.net_amount?.toFixed(2) || '0.00'}</div>
|
||||
<div>
|
||||
VAT ({(payment.vat_rate || 0) * 100}%): £{payment.vat_amount?.toFixed(
|
||||
VAT ({payment.vat_rate || 0}%): £{payment.vat_amount?.toFixed(
|
||||
2
|
||||
) || '0.00'}
|
||||
</div>
|
||||
|
||||
@@ -230,8 +230,14 @@
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
settings = await res.json();
|
||||
toast.success('Business settings updated');
|
||||
const wasVatEnabled = settings?.is_vat_registered;
|
||||
const updated = await res.json() as BusinessSettings;
|
||||
settings = updated;
|
||||
if (!wasVatEnabled && updated.is_vat_registered) {
|
||||
toast.success('VAT enabled — past payments and till sales updated retroactively');
|
||||
} else {
|
||||
toast.success('Business settings updated');
|
||||
}
|
||||
showEditModal = false;
|
||||
} else {
|
||||
const errText = await res.text();
|
||||
|
||||
@@ -583,6 +583,14 @@
|
||||
<span class="mx-1">|</span>
|
||||
<span class="text-gray-500">Inv: {payment.invoice_number}</span>
|
||||
{/if}
|
||||
{#if payment.is_vat_applicable && payment.net_amount != null}
|
||||
<span class="mx-1">|</span>
|
||||
<span class="text-gray-500">Net: £{payment.net_amount.toFixed(2)}</span>
|
||||
<span class="mx-1">|</span>
|
||||
<span class="text-gray-500">
|
||||
VAT ({payment.vat_rate || 0}%): £{payment.vat_amount?.toFixed(2)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-1 text-xs text-gray-400">
|
||||
{new SvelteDate(payment.created_at).toLocaleDateString('en-GB', {
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Components
|
||||
import BookingActions from '$lib/components/booking/BookingActions.svelte';
|
||||
@@ -94,6 +96,9 @@
|
||||
newCardCVC.length >= 3)
|
||||
);
|
||||
|
||||
// VAT registration status from public business info (via shared store)
|
||||
let vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||
|
||||
// Email existence check (guest flow only)
|
||||
let emailChecking = $state(false);
|
||||
let emailSuggestion = $state<string | null>(null);
|
||||
@@ -584,6 +589,7 @@
|
||||
|
||||
$effect(() => {
|
||||
fetchServices();
|
||||
ensureBusinessInfo();
|
||||
});
|
||||
|
||||
// Track which months are currently being fetched (prevents duplicate requests)
|
||||
@@ -1630,7 +1636,7 @@
|
||||
</div>
|
||||
<div class="flex justify-between text-sm font-semibold">
|
||||
<span>Total Cost:</span>
|
||||
<span>£{getTotalPrice()}</span>
|
||||
<span>£{getTotalPrice()}{#if vatRegistered} <span class="text-xs font-normal text-gray-400">incl. VAT</span>{/if}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2131,7 +2137,7 @@
|
||||
{:else}
|
||||
<div class="flex justify-between font-semibold">
|
||||
<span>Total (estimated)</span>
|
||||
<span>£{getTotalPrice()}</span>
|
||||
<span>£{getTotalPrice()}{#if vatRegistered} <span class="text-xs font-normal text-gray-400">incl. VAT</span>{/if}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { formatDuration } from '$lib/utils/format';
|
||||
@@ -88,6 +89,9 @@
|
||||
let summary = $state<DailySummary | null>(null);
|
||||
let weekSummary = $state<DailySummary | null>(null);
|
||||
|
||||
// VAT registration status from public business info (via shared store)
|
||||
let vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||
|
||||
// Data diffing — only update UI when payload actually changes
|
||||
let prevDataJson = $state('');
|
||||
|
||||
@@ -226,6 +230,7 @@
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
ensureBusinessInfo();
|
||||
fetchCurrentAndNext();
|
||||
|
||||
// Recalculate time display every 15s (no data fetch)
|
||||
@@ -433,16 +438,17 @@
|
||||
day: 'numeric'
|
||||
})}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<!-- Stats grid -->
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
||||
<div class="rounded-md border border-teal-100 bg-white p-3">
|
||||
<div class="text-xs font-medium text-gray-500">Payments Taken</div>
|
||||
<div class="mt-1 text-lg font-bold text-gray-900">
|
||||
£{summary.total_payments_today.toFixed(2)}
|
||||
</div>
|
||||
<!-- Stats grid -->
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
||||
<div class="rounded-md border border-teal-100 bg-white p-3">
|
||||
<div class="text-xs font-medium text-gray-500">Payments Taken</div>
|
||||
<div class="mt-1 text-lg font-bold text-gray-900">
|
||||
£{summary.total_payments_today.toFixed(2)}
|
||||
</div>
|
||||
{#if vatRegistered}<div class="text-[10px] text-gray-400">incl. VAT</div>{/if}
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border border-teal-100 bg-white p-3">
|
||||
<div class="text-xs font-medium text-gray-500">Tips</div>
|
||||
@@ -569,12 +575,13 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
|
||||
<div class="rounded-md border border-gray-200 bg-white p-3">
|
||||
<div class="text-xs font-medium text-gray-500">Payments Taken</div>
|
||||
<div class="mt-1 text-lg font-bold text-gray-900">
|
||||
£{weekSummary.total_payments_today.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-md border border-gray-200 bg-white p-3">
|
||||
<div class="text-xs font-medium text-gray-500">Payments Taken</div>
|
||||
<div class="mt-1 text-lg font-bold text-gray-900">
|
||||
£{weekSummary.total_payments_today.toFixed(2)}
|
||||
</div>
|
||||
{#if vatRegistered}<div class="text-[10px] text-gray-400">incl. VAT</div>{/if}
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border border-gray-200 bg-white p-3">
|
||||
<div class="text-xs font-medium text-gray-500">Tips</div>
|
||||
|
||||
@@ -1859,6 +1859,12 @@
|
||||
})()}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if b.total_amount}
|
||||
<span class="font-medium text-gray-700">
|
||||
— £{b.total_amount.toFixed(2)}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" onclick={() => openBookingModal(b.id)}>View</Button>
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
paid_amount: number;
|
||||
refunded_amount: number;
|
||||
remaining_amount: number;
|
||||
total_vat_amount: number;
|
||||
total_net_amount: number;
|
||||
payments: Array<{
|
||||
id: string;
|
||||
status: string;
|
||||
@@ -357,6 +359,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if paymentSummary && paymentSummary.total_vat_amount > 0}
|
||||
<div class="mt-2 rounded-lg bg-gray-50 p-3">
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600">Net amount</span>
|
||||
<span class="font-medium">{formatPence(paymentSummary.total_net_amount)}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm">
|
||||
<span class="text-gray-600">VAT</span>
|
||||
<span class="font-medium">{formatPence(paymentSummary.total_vat_amount)}</span>
|
||||
</div>
|
||||
<div class="mt-1 flex justify-between border-t border-gray-200 pt-1 text-sm font-semibold">
|
||||
<span class="text-gray-800">Total paid</span>
|
||||
<span class="text-gray-800">{formatPence(paymentSummary.paid_amount)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<Button class="w-full" onclick={() => toast.info('Payment integration coming soon')}>
|
||||
Pay Now
|
||||
|
||||
@@ -205,6 +205,9 @@
|
||||
<li>
|
||||
<span class="font-medium">Deposit</span> may be required for frequent no-shows
|
||||
</li>
|
||||
<li>
|
||||
<span class="font-medium">VAT</span> is included in all displayed prices where applicable
|
||||
</li>
|
||||
</ul>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
@@ -15,6 +16,7 @@
|
||||
let loading = $state(false);
|
||||
let selectedBookingId = $state<string | null>(null);
|
||||
let showBookingModal = $state(false);
|
||||
let businessSettings = $derived(getBusinessInfo());
|
||||
|
||||
$effect(() => {
|
||||
if (!browser) return;
|
||||
@@ -28,6 +30,7 @@
|
||||
return;
|
||||
}
|
||||
pageState = 'authorized';
|
||||
ensureBusinessInfo();
|
||||
fetchBookings();
|
||||
});
|
||||
|
||||
@@ -323,6 +326,9 @@
|
||||
{#if booking.total_amount}
|
||||
<span class="text-lg font-bold text-gray-900">
|
||||
£{booking.total_amount.toFixed(2)}
|
||||
{#if businessSettings?.is_vat_registered}
|
||||
<span class="text-xs font-normal text-gray-400"> incl. VAT</span>
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
<span></span>
|
||||
|
||||
Reference in New Issue
Block a user