Files
webshop/shop/components/invoice-pdf.tsx
DanielS ccdfa7402b
All checks were successful
Staging Build / build (push) Successful in 3m22s
refactor(order): rename Bestellbestaetigung to Angebotsbestaetigung
2026-06-26 09:06:45 +02:00

230 lines
7.8 KiB
TypeScript

import { Document, Page, Text, View, StyleSheet, Font } from '@react-pdf/renderer';
// Create styles
const styles = StyleSheet.create({
page: {
padding: 50,
fontSize: 10,
color: '#333',
fontFamily: 'Helvetica',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 40,
},
companyInfo: {
textAlign: 'right',
},
title: {
fontSize: 24,
color: '#000',
marginBottom: 20,
},
section: {
marginBottom: 20,
},
label: {
fontSize: 8,
color: '#999',
textTransform: 'uppercase',
marginBottom: 2,
},
table: {
width: 'auto',
borderStyle: 'solid',
borderWidth: 1,
borderColor: '#eee',
borderRightWidth: 0,
borderBottomWidth: 0,
marginBottom: 20,
},
tableRow: {
margin: 'auto',
flexDirection: 'row',
},
tableCol: {
width: '60%',
borderStyle: 'solid',
borderWidth: 1,
borderColor: '#eee',
borderLeftWidth: 0,
borderTopWidth: 0,
padding: 8,
},
tableColPrice: {
width: '40%',
borderStyle: 'solid',
borderWidth: 1,
borderColor: '#eee',
borderLeftWidth: 0,
borderTopWidth: 0,
padding: 8,
textAlign: 'right',
},
tableCell: {
margin: 'auto',
marginTop: 5,
fontSize: 10,
},
total: {
flexDirection: 'row',
justifyContent: 'flex-end',
marginTop: 20,
borderTop: 1,
borderColor: '#000',
paddingTop: 10,
},
totalLabel: {
fontSize: 14,
fontWeight: 'bold',
},
footer: {
position: 'absolute',
bottom: 30,
left: 50,
right: 50,
textAlign: 'center',
color: '#999',
fontSize: 8,
borderTop: 1,
borderColor: '#eee',
paddingTop: 10,
},
});
export const InvoicePDF = ({ order, orderSnapshot, customer }: any) => {
const items = orderSnapshot?.items ?? [];
const subtotal = orderSnapshot?.subtotal ?? 0;
const taxAmount = orderSnapshot?.tax_amount ?? 0;
const taxRate = orderSnapshot?.tax_rate ?? 19;
const total = orderSnapshot?.total ?? 0;
return (
<Document>
<Page size="A4" style={styles.page}>
<View style={styles.header}>
<View>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>CASPOS</Text>
<Text>Software Solutions</Text>
</View>
<View style={styles.companyInfo}>
<Text>CASPOS GmbH</Text>
<Text>Musterstraße 1</Text>
<Text>12345 Musterstadt</Text>
<Text>Deutschland</Text>
</View>
</View>
<Text style={styles.title}>Angebotsbestätigung</Text>
<View style={styles.section}>
<Text style={styles.label}>Besteller / Kunde</Text>
<Text>{customer.company_name}</Text>
<Text>{customer.first_name} {customer.last_name}</Text>
<Text>{customer.address}</Text>
<Text>{customer.zip_code} {customer.city}</Text>
<Text>USt-IdNr: {customer.vat_id}</Text>
</View>
<View style={styles.section}>
<Text style={styles.label}>Bestelldetails</Text>
<Text>Bestellnummer: {order.order_number || order.id}</Text>
<Text>Datum: {new Date(order.created_at || Date.now()).toLocaleDateString('de-DE')}</Text>
</View>
{orderSnapshot?.price_multiplier !== null && orderSnapshot?.price_multiplier !== undefined && (
<View style={[styles.section, { backgroundColor: '#f9f9f9', padding: 8 }]}>
<Text style={styles.label}>Update-Konditionen</Text>
<Text style={{ fontWeight: 'bold' }}>
{orderSnapshot.price_multiplier === 0
? "Update (0-12 Monate): 100% Rabatt (inklusive)"
: orderSnapshot.price_multiplier === 0.15
? "Update (13-14 Monate): 15% vom Listenpreis"
: orderSnapshot.price_multiplier === 0.30
? "Update (15-25 Monate): 30% vom Listenpreis"
: orderSnapshot.price_multiplier === 0.45
? "Update (26-37 Monate): 45% vom Listenpreis"
: orderSnapshot.price_multiplier === 0.60
? "Update (38-49 Monate): 60% vom Listenpreis"
: orderSnapshot.price_multiplier === 0.90
? "Neukauf (>= 50 Monate): 10% Rabatt auf Neukauf"
: `Gebühr: ${orderSnapshot.price_multiplier * 100}%`}
</Text>
{orderSnapshot.last_license_date && (
<Text style={{ marginTop: 2, color: '#666' }}>
Letzte Lizenz vom: {new Date(orderSnapshot.last_license_date).toLocaleDateString('de-DE')}
</Text>
)}
</View>
)}
<View style={styles.table}>
<View style={[styles.tableRow, { backgroundColor: '#f9f9f9' }]}>
<View style={styles.tableCol}><Text style={{ fontWeight: 'bold' }}>Beschreibung</Text></View>
<View style={styles.tableColPrice}><Text style={{ fontWeight: 'bold' }}>Preis (netto)</Text></View>
</View>
{items.map((item: any, idx: number) => (
<View key={idx}>
<View style={styles.tableRow}>
<View style={styles.tableCol}>
<Text style={{ fontWeight: 'bold' }}>{item.product_name} ({item.category_name})</Text>
</View>
<View style={styles.tableColPrice}>
<Text>
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(item.base_price)}
{' '}{item.billing_interval === 'one_time' ? 'einmalig' : 'mtl.'}
</Text>
</View>
</View>
{item.selected_modules?.map((mod: any, mIdx: number) => {
const qty = mod.quantity || 1;
const price = mod.total_price ?? (mod.price * qty);
const hasQty = qty > 1;
return (
<View key={mIdx} style={styles.tableRow}>
<View style={[styles.tableCol, { paddingLeft: 20 }]}>
<Text style={{ color: '#666' }}>
+ {mod.module_name} {hasQty ? `(x${qty})` : ''}
</Text>
</View>
<View style={styles.tableColPrice}>
<Text style={{ color: '#666' }}>
+{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(price)}
</Text>
</View>
</View>
);
})}
</View>
))}
</View>
<View style={styles.total}>
<View style={{ width: '50%' }}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 3 }}>
<Text>Netto-Zwischensumme:</Text>
<Text>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(subtotal)}</Text>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginBottom: 3 }}>
<Text>zzgl. {taxRate}% USt:</Text>
<Text>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(taxAmount)}</Text>
</View>
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 5, borderTopWidth: 1, borderTopColor: '#000', paddingTop: 5 }}>
<Text style={styles.totalLabel}>Gesamtbetrag (brutto):</Text>
<Text style={styles.totalLabel}>{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(total)}</Text>
</View>
</View>
</View>
<View style={styles.footer}>
<Text>Vielen Dank für Ihre Bestellung!</Text>
<Text>Dies ist ein automatisch generiertes Dokument.</Text>
</View>
</Page>
</Document>
);
};