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) => (