From acd8d6575c581a5dae9d530b2ffbf53467988181 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Tue, 21 Jul 2026 11:32:30 +0200
Subject: [PATCH 01/11] chore(wizard): update demo license keys in lookup panel
---
.../wizard/license-lookup-panel.tsx | 146 +++++++++---------
1 file changed, 73 insertions(+), 73 deletions(-)
diff --git a/shop/components/wizard/license-lookup-panel.tsx b/shop/components/wizard/license-lookup-panel.tsx
index e8f4dab..edab1ab 100644
--- a/shop/components/wizard/license-lookup-panel.tsx
+++ b/shop/components/wizard/license-lookup-panel.tsx
@@ -25,47 +25,47 @@ import { lookupLicenseFromLicServer } from '@/lib/actions/licserver-config'
// ─── MOCK DATA (until real LicServer API is wired up) ───────────────────────
const MOCK_LICENSES: Record = {
- 'CAS-2023-PRO-00123': {
- licenseKey: 'CAS-2023-PRO-00123',
- status: 'active',
- product: 'CAS genesisWorld Professional',
- edition: 'Professional',
- version: '14.1.2',
- seats: 10,
- customer: 'Mustermann GmbH',
- contact: 'Max Mustermann',
- issuedAt: '2023-04-01',
- expiresAt: '2026-03-31',
+ '995500-0005': {
+ licenseKey: '995500-0005',
+ status: 'active',
+ product: 'CASPOS DEMO',
+ edition: 'Professional',
+ version: '14.1.2',
+ seats: 10,
+ customer: 'Mustermann GmbH',
+ contact: 'Max Mustermann',
+ issuedAt: '2023-04-01',
+ expiresAt: '2026-03-31',
maintenanceUntil: '2025-03-31',
- modules: ['CRM', 'ERP-Link', 'Mobile Client', 'Web Client'],
+ modules: ['CRM', 'ERP-Link', 'Mobile Client', 'Web Client'],
},
'CAS-2020-STD-00456': {
- licenseKey: 'CAS-2020-STD-00456',
- status: 'expired',
- product: 'CAS genesisWorld Standard',
- edition: 'Standard',
- version: '12.0.0',
- seats: 5,
- customer: 'Beispiel AG',
- contact: 'Erika Muster',
- issuedAt: '2020-01-15',
- expiresAt: '2023-01-14',
+ licenseKey: 'CAS-2020-STD-00456',
+ status: 'expired',
+ product: 'CAS genesisWorld Standard',
+ edition: 'Standard',
+ version: '12.0.0',
+ seats: 5,
+ customer: 'Beispiel AG',
+ contact: 'Erika Muster',
+ issuedAt: '2020-01-15',
+ expiresAt: '2023-01-14',
maintenanceUntil: '2022-01-14',
- modules: ['CRM'],
+ modules: ['CRM'],
},
'CAS-2024-ENT-00789': {
- licenseKey: 'CAS-2024-ENT-00789',
- status: 'active',
- product: 'CAS genesisWorld Enterprise',
- edition: 'Enterprise',
- version: '15.0.0',
- seats: 50,
- customer: 'Tech Solutions GmbH & Co. KG',
- contact: 'Julia Schneider',
- issuedAt: '2024-01-01',
- expiresAt: '2026-12-31',
+ licenseKey: 'CAS-2024-ENT-00789',
+ status: 'active',
+ product: 'CAS genesisWorld Enterprise',
+ edition: 'Enterprise',
+ version: '15.0.0',
+ seats: 50,
+ customer: 'Tech Solutions GmbH & Co. KG',
+ contact: 'Julia Schneider',
+ issuedAt: '2024-01-01',
+ expiresAt: '2026-12-31',
maintenanceUntil: '2026-12-31',
- modules: ['CRM', 'ERP-Link', 'Mobile Client', 'Web Client', 'AI Assistant', 'Analytics Pro'],
+ modules: ['CRM', 'ERP-Link', 'Mobile Client', 'Web Client', 'AI Assistant', 'Analytics Pro'],
},
}
@@ -73,18 +73,18 @@ const MOCK_LICENSES: Record = {
export type LicenseStatus = 'active' | 'expired' | 'invalid' | 'suspended'
export interface LicenseInfo {
- licenseKey: string
- status: LicenseStatus
- product: string
- edition: string
- version: string
- seats: number
- customer: string
- contact: string
- issuedAt: string
- expiresAt: string
- maintenanceUntil: string
- modules: string[]
+ licenseKey: string
+ status: LicenseStatus
+ product: string
+ edition: string
+ version: string
+ seats: number
+ customer: string
+ contact: string
+ issuedAt: string
+ expiresAt: string
+ maintenanceUntil: string
+ modules: string[]
}
interface LicenseLookupPanelProps {
@@ -98,34 +98,34 @@ function statusConfig(status: LicenseStatus) {
case 'active':
return {
label: 'Aktiv',
- icon: ,
- cls: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/40',
- ring: 'border-emerald-500/30',
- glow: 'shadow-emerald-500/10',
+ icon: ,
+ cls: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/40',
+ ring: 'border-emerald-500/30',
+ glow: 'shadow-emerald-500/10',
}
case 'expired':
return {
label: 'Abgelaufen',
- icon: ,
- cls: 'bg-amber-500/20 text-amber-300 border-amber-500/40',
- ring: 'border-amber-500/30',
- glow: 'shadow-amber-500/10',
+ icon: ,
+ cls: 'bg-amber-500/20 text-amber-300 border-amber-500/40',
+ ring: 'border-amber-500/30',
+ glow: 'shadow-amber-500/10',
}
case 'suspended':
return {
label: 'Gesperrt',
- icon: ,
- cls: 'bg-red-500/20 text-red-300 border-red-500/40',
- ring: 'border-red-500/30',
- glow: 'shadow-red-500/10',
+ icon: ,
+ cls: 'bg-red-500/20 text-red-300 border-red-500/40',
+ ring: 'border-red-500/30',
+ glow: 'shadow-red-500/10',
}
default:
return {
label: 'Ungültig',
- icon: ,
- cls: 'bg-red-500/20 text-red-300 border-red-500/40',
- ring: 'border-red-500/30',
- glow: 'shadow-red-500/10',
+ icon: ,
+ cls: 'bg-red-500/20 text-red-300 border-red-500/40',
+ ring: 'border-red-500/30',
+ glow: 'shadow-red-500/10',
}
}
}
@@ -149,13 +149,13 @@ async function lookupLicense(key: string): Promise {
// ─── MAIN COMPONENT ───────────────────────────────────────────────────────────
export function LicenseLookupPanel({ onLicenseResolved }: LicenseLookupPanelProps) {
- const [licenseKey, setLicenseKey] = useState('')
- const [result, setResult] = useState(null)
- const [notFound, setNotFound] = useState(false)
- const [isPending, startTransition] = useTransition()
- const [expanded, setExpanded] = useState(true)
+ const [licenseKey, setLicenseKey] = useState('')
+ const [result, setResult] = useState(null)
+ const [notFound, setNotFound] = useState(false)
+ const [isPending, startTransition] = useTransition()
+ const [expanded, setExpanded] = useState(true)
const [modulesOpen, setModulesOpen] = useState(false)
- const inputRef = useRef(null)
+ const inputRef = useRef(null)
const handleSearch = () => {
if (!licenseKey.trim()) return
@@ -242,7 +242,7 @@ export function LicenseLookupPanel({ onLicenseResolved }: LicenseLookupPanelProp
setNotFound(false)
}}
onKeyDown={handleKeyDown}
- placeholder="z. B. CAS-2023-PRO-00123"
+ placeholder="z. B. 995500-00"
className="pl-9 pr-8 bg-white/5 border-white/10 text-white placeholder:text-slate-500
focus:border-violet-500/60 focus:ring-violet-500/20 rounded-xl text-sm h-10"
/>
@@ -278,16 +278,16 @@ export function LicenseLookupPanel({ onLicenseResolved }: LicenseLookupPanelProp
Demo:
setLicenseKey('CAS-2023-PRO-00123')}
+ onClick={() => setLicenseKey('995500-00')}
>
- CAS-2023-PRO-00123
+ 995500-00
{' · '}
setLicenseKey('CAS-2020-STD-00456')}
+ onClick={() => setLicenseKey('995500-01')}
>
- CAS-2020-STD-00456
+ 995500-01
From 1c3191876828d85a71bc96d8fb63ff5efd5304c8 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 15:09:05 +0200
Subject: [PATCH 02/11] feat(shop): add dynamic linked fees to modules
---
shop/app/admin/wysiwyg/wysiwyg-client.tsx | 27 +++-
shop/app/api/orders/checkout/route.ts | 40 ++++++
shop/components/order-wizard.tsx | 115 ++++++++++++++++--
shop/components/wizard/step-summary.tsx | 26 ++++
shop/lib/actions/products.ts | 2 +
shop/lib/types.ts | 1 +
...260722100000_add_linked_fee_to_modules.sql | 3 +
shop/supabase/seed.sql | 15 ++-
8 files changed, 208 insertions(+), 21 deletions(-)
create mode 100644 shop/supabase/migrations/20260722100000_add_linked_fee_to_modules.sql
diff --git a/shop/app/admin/wysiwyg/wysiwyg-client.tsx b/shop/app/admin/wysiwyg/wysiwyg-client.tsx
index 1a9c3b8..f403542 100644
--- a/shop/app/admin/wysiwyg/wysiwyg-client.tsx
+++ b/shop/app/admin/wysiwyg/wysiwyg-client.tsx
@@ -302,8 +302,31 @@ export function WysiwygAdminClient({
{(prod.modules || []).length > 0 ? (
(prod.modules || []).map((m) => (
-
- {m.name}
+
+ {m.name}
+
))}
+ {linkedFeeProducts && linkedFeeProducts.length > 0 && (
+
+
+ Zusätzliche Dienste & Gebühren
+ Einmalig berechnet
+
+ {linkedFeeProducts.map(p => (
+
+
+ {p.name}
+
+ {new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(p.base_price)}
+ {' '}{p.billing_interval === 'monthly' ? '/ mtl.' : 'einmalig'}
+
+
+ {p.description && (
+
+ {p.description}
+
+ )}
+
+ ))}
+
+ )}
{selectedEndCustomer ? (
diff --git a/shop/lib/actions/products.ts b/shop/lib/actions/products.ts
index 13ec6f5..f44f169 100644
--- a/shop/lib/actions/products.ts
+++ b/shop/lib/actions/products.ts
@@ -90,6 +90,7 @@ export async function createProduct(
requirements: m.requirements || [],
exclusions: m.exclusions || [],
has_quantity: m.has_quantity ?? false,
+ linked_fee_product_id: m.linked_fee_product_id || null,
}))
const { error: modulesError } = await supabase
.from('product_modules')
@@ -127,6 +128,7 @@ export async function updateProduct(id: string, product: Partial
, modul
requirements: m.requirements || [],
exclusions: m.exclusions || [],
has_quantity: m.has_quantity ?? false,
+ linked_fee_product_id: m.linked_fee_product_id || null,
}))
const { error: modulesError } = await supabase
.from('product_modules')
diff --git a/shop/lib/types.ts b/shop/lib/types.ts
index 8d5fe5c..f7d4971 100644
--- a/shop/lib/types.ts
+++ b/shop/lib/types.ts
@@ -46,6 +46,7 @@ export type ProductModule = {
exclusions: string[] // UUID[] von Modulen, die nicht gleichzeitig aktiv sein dürfen
created_at: string
has_quantity?: boolean
+ linked_fee_product_id?: string | null
}
export type Profile = {
diff --git a/shop/supabase/migrations/20260722100000_add_linked_fee_to_modules.sql b/shop/supabase/migrations/20260722100000_add_linked_fee_to_modules.sql
new file mode 100644
index 0000000..331de7d
--- /dev/null
+++ b/shop/supabase/migrations/20260722100000_add_linked_fee_to_modules.sql
@@ -0,0 +1,3 @@
+-- Add linked_fee_product_id to product_modules table
+ALTER TABLE public.product_modules
+ADD COLUMN IF NOT EXISTS linked_fee_product_id UUID REFERENCES public.products(id) ON DELETE SET NULL;
diff --git a/shop/supabase/seed.sql b/shop/supabase/seed.sql
index 5637f91..42519f9 100644
--- a/shop/supabase/seed.sql
+++ b/shop/supabase/seed.sql
@@ -1,13 +1,16 @@
-- Seed Products
-INSERT INTO public.products (id, name, description, base_price, tax_rate)
+INSERT INTO public.products (id, name, description, base_price, tax_rate, billing_interval, show_in_abo, show_in_kauf)
VALUES
-('d1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'CASPOS Cloud', 'Die modulare Cloud-Lösung für Ihren Einzelhandel.', 49.00, 19.00),
-('d2a2a2a2-a2a2-a2a2-a2a2-a2a2a2a2a2a2', 'CASPOS Gastro', 'Spezialisiert auf Gastronomie mit Tischplan und Funkbonieren.', 79.00, 19.00);
+('d1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'CASPOS Cloud', 'Die modulare Cloud-Lösung für Ihren Einzelhandel.', 49.00, 19.00, 'monthly', true, true),
+('d2a2a2a2-a2a2-a2a2-a2a2-a2a2-a2a2-a2a2a2a2', 'CASPOS Gastro', 'Spezialisiert auf Gastronomie mit Tischplan und Funkbonieren.', 79.00, 19.00, 'monthly', true, true),
+('prod-poscloud-fee', 'POS Cloud Grundgebühr', 'Monatliche Grundgebühr für die POS Cloud Nutzung.', 19.00, 19.00, 'monthly', false, false);
-- Seed Modules for CASPOS Cloud
INSERT INTO public.product_modules (id, product_id, name, description, price, requirements, exclusions)
VALUES
('m1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'Bestandsführung Pro', 'Erweiterte Lagerverwaltung.', 15.00, '{}', '{}'),
-('m2a2a2a2-a2a2-a2a2-a2a2-a2a2a2a2a2a2', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'DATEV Export', 'Direkte Schnittstelle zum Steuerberater.', 10.00, '{}', '{}'),
-('m3a3a3a3-a3a3-a3a3-a3a3-a3a3a3a3a3a3', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'Filialverwaltung', 'Zentrale Steuerung mehrerer Standorte.', 25.00, '{"m1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1"}', '{}'),
-('m4a4a4a4-a4a4-a4a4-a4a4-a4a4a4a4a4a4', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'Small Business Modus', 'Reduzierter Funktionsumfang für Kleinunternehmer.', 0.00, '{}', '{"m1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1", "m3a3a3a3-a3a3-a3a3-a3a3-a3a3a3a3a3a3"}');
+('m2a2a2a2-a2a2-a2a2-a2a2-a2a2-a2a2-a2a2a2a2', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'DATEV Export', 'Direkte Schnittstelle zum Steuerberater.', 10.00, '{}', '{}'),
+('m3a3a3a3-a3a3-a3a3-a3a3-a3a3-a3a3-a3a3-a3a3', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'Filialverwaltung', 'Zentrale Steuerung mehrerer Standorte.', 25.00, '{"m1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1"}', '{}'),
+('m4a4a4a4-a4a4-a4a4-a4a4-a4a4-a4a4-a4a4-a4a4', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'Small Business Modus', 'Reduzierter Funktionsumfang für Kleinunternehmer.', 0.00, '{}', '{"m1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1", "m3a3a3a3-a3a3-a3a3-a3a3-a3a3-a3a3-a3a3-a3a3"}'),
+('m-poscloud-cloud', 'd1a1a1a1-a1a1-a1a1-a1a1-a1a1a1a1a1a1', 'Schnittstelle POS Cloud', 'Anbindung an die POS Cloud.', 0.00, '{}', '{}'),
+('m-poscloud-gastro', 'd2a2a2a2-a2a2-a2a2-a2a2-a2a2-a2a2-a2a2a2a2', 'Schnittstelle POS Cloud', 'Anbindung an die POS Cloud.', 0.00, '{}', '{}');
From 06cba405f3aea32e6f7687f1a2383b1dee8211f1 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 15:27:38 +0200
Subject: [PATCH 03/11] fix(shop): resolve postgrest join ambiguity for
product_modules
---
shop/lib/actions/products.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shop/lib/actions/products.ts b/shop/lib/actions/products.ts
index f44f169..1a31643 100644
--- a/shop/lib/actions/products.ts
+++ b/shop/lib/actions/products.ts
@@ -9,7 +9,7 @@ export async function getProducts() {
const supabase = await createClient()
const { data, error } = await supabase
.from('products')
- .select('*, category:categories(*), modules:product_modules(*)')
+ .select('*, category:categories(*), modules:product_modules!product_modules_product_id_fkey(*)')
.order('created_at', { ascending: false })
if (error) throw error
From f130f24c869cf662eed03949ca180b359caab348 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:04:38 +0200
Subject: [PATCH 04/11] feat(shop): move linked fee configuration to product
level
---
shop/app/admin/wysiwyg/wysiwyg-client.tsx | 51 ++++++++++---------
shop/app/api/orders/checkout/route.ts | 18 ++++---
shop/components/order-wizard.tsx | 8 +--
shop/lib/actions/products.ts | 4 +-
shop/lib/types.ts | 2 +-
...0722110000_move_linked_fee_to_products.sql | 7 +++
6 files changed, 51 insertions(+), 39 deletions(-)
create mode 100644 shop/supabase/migrations/20260722110000_move_linked_fee_to_products.sql
diff --git a/shop/app/admin/wysiwyg/wysiwyg-client.tsx b/shop/app/admin/wysiwyg/wysiwyg-client.tsx
index f403542..2ff5872 100644
--- a/shop/app/admin/wysiwyg/wysiwyg-client.tsx
+++ b/shop/app/admin/wysiwyg/wysiwyg-client.tsx
@@ -302,31 +302,8 @@ export function WysiwygAdminClient({
{(prod.modules || []).length > 0 ? (
(prod.modules || []).map((m) => (
-
- {m.name}
-
+
+ {m.name}
+ {/* Verknüpfte monatliche Gebühr */}
+
+ Verknüpfte Gebühr:
+
+
+
{/* Modul hinzufügen Popover */}
{
- for (const p of products) {
- const mod = p.modules?.find((m: any) => m.id === mId);
- if (mod && mod.linked_fee_product_id) {
- feeProductIds.add(mod.linked_fee_product_id);
- }
+
+ if (sel.productId) {
+ const p = products.find((prod: any) => prod.id === sel.productId);
+ if (p && p.linked_fee_product_id) {
+ feeProductIds.add(p.linked_fee_product_id);
+ }
+ }
+
+ sel.productIds?.forEach((pId: string) => {
+ const p = products.find((prod: any) => prod.id === pId);
+ if (p && p.linked_fee_product_id) {
+ feeProductIds.add(p.linked_fee_product_id);
}
});
}
diff --git a/shop/components/order-wizard.tsx b/shop/components/order-wizard.tsx
index 9cd62f4..c985fe3 100644
--- a/shop/components/order-wizard.tsx
+++ b/shop/components/order-wizard.tsx
@@ -429,6 +429,10 @@ export function OrderWizard({
oneTime += basePrice
}
+ if (prod.linked_fee_product_id) {
+ feeProductIds.add(prod.linked_fee_product_id)
+ }
+
// Module
sel.moduleIds?.forEach((mId: string) => {
const mod = prod.modules?.find(m => m.id === mId)
@@ -439,10 +443,6 @@ export function OrderWizard({
} else {
oneTime += mod.price * qty
}
-
- if (mod.linked_fee_product_id) {
- feeProductIds.add(mod.linked_fee_product_id)
- }
}
})
})
diff --git a/shop/lib/actions/products.ts b/shop/lib/actions/products.ts
index 1a31643..13ec6f5 100644
--- a/shop/lib/actions/products.ts
+++ b/shop/lib/actions/products.ts
@@ -9,7 +9,7 @@ export async function getProducts() {
const supabase = await createClient()
const { data, error } = await supabase
.from('products')
- .select('*, category:categories(*), modules:product_modules!product_modules_product_id_fkey(*)')
+ .select('*, category:categories(*), modules:product_modules(*)')
.order('created_at', { ascending: false })
if (error) throw error
@@ -90,7 +90,6 @@ export async function createProduct(
requirements: m.requirements || [],
exclusions: m.exclusions || [],
has_quantity: m.has_quantity ?? false,
- linked_fee_product_id: m.linked_fee_product_id || null,
}))
const { error: modulesError } = await supabase
.from('product_modules')
@@ -128,7 +127,6 @@ export async function updateProduct(id: string, product: Partial, modul
requirements: m.requirements || [],
exclusions: m.exclusions || [],
has_quantity: m.has_quantity ?? false,
- linked_fee_product_id: m.linked_fee_product_id || null,
}))
const { error: modulesError } = await supabase
.from('product_modules')
diff --git a/shop/lib/types.ts b/shop/lib/types.ts
index f7d4971..dff24b5 100644
--- a/shop/lib/types.ts
+++ b/shop/lib/types.ts
@@ -18,6 +18,7 @@ export type Product = {
requirements?: string[]
exclusions?: string[]
allow_update_discount?: boolean
+ linked_fee_product_id?: string | null
}
export type Category = {
@@ -46,7 +47,6 @@ export type ProductModule = {
exclusions: string[] // UUID[] von Modulen, die nicht gleichzeitig aktiv sein dürfen
created_at: string
has_quantity?: boolean
- linked_fee_product_id?: string | null
}
export type Profile = {
diff --git a/shop/supabase/migrations/20260722110000_move_linked_fee_to_products.sql b/shop/supabase/migrations/20260722110000_move_linked_fee_to_products.sql
new file mode 100644
index 0000000..212a29a
--- /dev/null
+++ b/shop/supabase/migrations/20260722110000_move_linked_fee_to_products.sql
@@ -0,0 +1,7 @@
+-- Remove linked_fee_product_id from product_modules
+ALTER TABLE public.product_modules
+DROP COLUMN IF EXISTS linked_fee_product_id;
+
+-- Add linked_fee_product_id to products
+ALTER TABLE public.products
+ADD COLUMN IF NOT EXISTS linked_fee_product_id UUID REFERENCES public.products(id) ON DELETE SET NULL;
From 66d5d8fc987170c1f971dddb5034696e63fef3dd Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:20:16 +0200
Subject: [PATCH 05/11] fix(shop): map correct orderSnapshot and labels in
InvoicePDF and routes
---
shop/app/api/admin/orders/[id]/download/route.ts | 6 ++----
shop/app/api/orders/[id]/download/route.ts | 6 ++----
shop/components/invoice-pdf.tsx | 2 +-
3 files changed, 5 insertions(+), 9 deletions(-)
diff --git a/shop/app/api/admin/orders/[id]/download/route.ts b/shop/app/api/admin/orders/[id]/download/route.ts
index f94014b..e2c22b8 100644
--- a/shop/app/api/admin/orders/[id]/download/route.ts
+++ b/shop/app/api/admin/orders/[id]/download/route.ts
@@ -82,11 +82,9 @@ export async function GET(
const buffer = await renderToBuffer(
React.createElement(InvoicePDF, {
- orderNumber: order.order_number || `AE-${id.slice(0, 8)}`,
- dateStr: formattedDate,
+ order: order,
+ orderSnapshot: order.order_data,
customer: order.customer_data,
- orderData: order.order_data,
- totalPrice: Number(order.total_price),
})
);
diff --git a/shop/app/api/orders/[id]/download/route.ts b/shop/app/api/orders/[id]/download/route.ts
index e846b8e..933ac85 100644
--- a/shop/app/api/orders/[id]/download/route.ts
+++ b/shop/app/api/orders/[id]/download/route.ts
@@ -82,11 +82,9 @@ export async function GET(
const buffer = await renderToBuffer(
React.createElement(InvoicePDF, {
- orderNumber: order.order_number || `AE-${id.slice(0, 8)}`,
- dateStr: formattedDate,
+ order: order,
+ orderSnapshot: order.order_data,
customer: order.customer_data,
- orderData: order.order_data,
- totalPrice: Number(order.total_price),
})
);
diff --git a/shop/components/invoice-pdf.tsx b/shop/components/invoice-pdf.tsx
index 951121b..49cd9d2 100644
--- a/shop/components/invoice-pdf.tsx
+++ b/shop/components/invoice-pdf.tsx
@@ -176,7 +176,7 @@ export const InvoicePDF = ({ order, orderSnapshot, customer }: any) => {
- Kasse: {deviceName}
+ {deviceName === 'Zusatzleistung' ? 'Backoffice' : `Kasse: ${deviceName}`}
{devItems.map((item: any, idx: number) => (
From dfdb5d90247f51a78aaf6f7ba3935385facc5d72 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:23:19 +0200
Subject: [PATCH 06/11] feat(shop): reset customer on partner change and
enforce customer selection
---
shop/components/wizard/step-customer.tsx | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/shop/components/wizard/step-customer.tsx b/shop/components/wizard/step-customer.tsx
index 396dd75..a44efa0 100644
--- a/shop/components/wizard/step-customer.tsx
+++ b/shop/components/wizard/step-customer.tsx
@@ -202,7 +202,10 @@ export function StepCustomer({
{/* Reserved Fixed Card: Alle Partner */}
setSelectedCompanyId('all')}
+ onClick={() => {
+ setSelectedCompanyId('all')
+ setSelectedEndCustomerId(null)
+ }}
className={`flex items-center justify-between p-3.5 rounded-xl border-2 cursor-pointer transition-all select-none ${
selectedCompanyId === 'all' || !selectedCompanyId
? 'border-primary bg-primary/10 shadow-lg shadow-primary/5'
@@ -240,7 +243,10 @@ export function StepCustomer({
return (
setSelectedCompanyId(c.id)}
+ onClick={() => {
+ setSelectedCompanyId(c.id)
+ setSelectedEndCustomerId(null)
+ }}
className={`flex items-center justify-between p-3.5 rounded-xl border-2 cursor-pointer transition-all select-none ${
isSelected
? 'border-primary bg-primary/10 shadow-lg shadow-primary/5'
@@ -592,9 +598,7 @@ export function StepCustomer({
onClick={nextStep}
disabled={
customerMode === 'create' ||
- (customerMode === 'select' &&
- customersByCompany.length > 0 &&
- !selectedEndCustomerId)
+ !selectedEndCustomerId
}
>
Weiter zum Abrechnungsmodell
From 061147701e6b002e077ac056239776349866d805 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:23:49 +0200
Subject: [PATCH 07/11] style(shop): rename linked fee section to Backoffice in
StepSummary
---
shop/components/wizard/step-summary.tsx | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/shop/components/wizard/step-summary.tsx b/shop/components/wizard/step-summary.tsx
index af706e8..f28aa22 100644
--- a/shop/components/wizard/step-summary.tsx
+++ b/shop/components/wizard/step-summary.tsx
@@ -147,8 +147,7 @@ export function StepSummary({
{linkedFeeProducts && linkedFeeProducts.length > 0 && (
- Zusätzliche Dienste & Gebühren
- Einmalig berechnet
+ Backoffice
{linkedFeeProducts.map(p => (
@@ -159,11 +158,6 @@ export function StepSummary({
{' '}{p.billing_interval === 'monthly' ? '/ mtl.' : 'einmalig'}
- {p.description && (
-
- {p.description}
-
- )}
))}
From 6cd5eb917c67e49d15a674f1aa6edcaabe491641 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:25:08 +0200
Subject: [PATCH 08/11] feat(shop): display company address instead of id in
partner selection
---
shop/components/wizard/step-customer.tsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/shop/components/wizard/step-customer.tsx b/shop/components/wizard/step-customer.tsx
index a44efa0..22b0c9a 100644
--- a/shop/components/wizard/step-customer.tsx
+++ b/shop/components/wizard/step-customer.tsx
@@ -177,7 +177,7 @@ export function StepCustomer({
setPartnerSearchTerm(e.target.value)}
className="pl-9 bg-white/5 border-white/10 text-white placeholder:text-slate-500 focus:border-primary text-sm h-9"
@@ -259,7 +259,11 @@ export function StepCustomer({
{c.name}
-
ID: {c.id}
+ {(c.street || c.zip || c.city) && (
+
+ {[c.street, [c.zip, c.city].filter(Boolean).join(' ')].filter(Boolean).join(', ')}
+
+ )}
From c5285dc907056d925b51a6f0081cf07a90ac7d5d Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:32:02 +0200
Subject: [PATCH 09/11] feat(shop): list order items individually by device
name in orders view
---
shop/app/my-orders/page.tsx | 11 +++++++----
shop/components/admin/orders-table.tsx | 21 +++++++++++++--------
2 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/shop/app/my-orders/page.tsx b/shop/app/my-orders/page.tsx
index 971fe51..cf1aaf2 100644
--- a/shop/app/my-orders/page.tsx
+++ b/shop/app/my-orders/page.tsx
@@ -126,14 +126,17 @@ export default async function MyOrdersPage() {
{/* Produkte kurz */}
{items.length > 0 && (
- {items.map((item, i) => (
+ {items.map((item, idx) => (
+ {item.device_name || 'Kasse'}:
{item.product_name}
{item.selected_modules.length > 0 && (
- +{item.selected_modules.length} Module
+
+ (+{item.selected_modules.length} {item.selected_modules.length === 1 ? 'Modul' : 'Module'})
+
)}
))}
diff --git a/shop/components/admin/orders-table.tsx b/shop/components/admin/orders-table.tsx
index 3c885ee..4ce3b8b 100644
--- a/shop/components/admin/orders-table.tsx
+++ b/shop/components/admin/orders-table.tsx
@@ -182,17 +182,22 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
-
- {items.map((item: any) => (
-
+ {items.map((item: any, idx: number) => (
+
- {item.product_name}
+
+ {item.device_name || 'Kasse'}:
+
+ {item.product_name}
{item.selected_modules?.length > 0 && (
- +{item.selected_modules.length}
+
+ + {item.selected_modules.map((m: any) => m.module_name || m.name).join(', ')}
+
)}
-
+
))}
From 623dab192b252b422f86f223fbf014e9a69ad16f Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:36:11 +0200
Subject: [PATCH 10/11] feat(shop): display device_name for items in
CustomerAccordionList
---
shop/components/customer-accordion-list.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/shop/components/customer-accordion-list.tsx b/shop/components/customer-accordion-list.tsx
index 52c5964..4b79db4 100644
--- a/shop/components/customer-accordion-list.tsx
+++ b/shop/components/customer-accordion-list.tsx
@@ -222,10 +222,11 @@ export function CustomerAccordionList({ customers }: CustomerAccordionListProps)
key={idx}
className="text-xs bg-white/5 border border-white/10 rounded-md px-2 py-0.5 text-slate-300"
>
+ {item.device_name || 'Kasse'}:
{item.product_name}
{item.selected_modules?.length > 0 && (
- (+{item.selected_modules.length} Module)
+ (+{item.selected_modules.length} {item.selected_modules.length === 1 ? 'Modul' : 'Module'})
)}
From c6190b34a858bb8ce3444b88777fa74a0eeb0945 Mon Sep 17 00:00:00 2001
From: DanielS
Date: Wed, 22 Jul 2026 17:36:51 +0200
Subject: [PATCH 11/11] fix(shop): display Backoffice for Zusatzleistung group
in OrderSuccessPage
---
shop/app/order/success/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/shop/app/order/success/page.tsx b/shop/app/order/success/page.tsx
index c8693a3..d1be63a 100644
--- a/shop/app/order/success/page.tsx
+++ b/shop/app/order/success/page.tsx
@@ -118,7 +118,7 @@ export default async function OrderSuccessPage({
{Object.entries(groupedItems).map(([deviceName, devItems]) => (
- Kasse: {deviceName}
+ {deviceName === 'Zusatzleistung' ? 'Backoffice' : `Kasse: ${deviceName}`}
{devItems.map((item) => (