From 554a573c98ba58c40ceff7e2fff4e43c4fc3e56a Mon Sep 17 00:00:00 2001 From: DeathKaioken Date: Tue, 28 Apr 2026 19:23:03 +0200 Subject: [PATCH 1/2] uid change --- .../summary/hooks/subscribeAbo.ts | 6 + src/app/coffee-abonnements/summary/page.tsx | 118 ++++++++++++++---- .../company/page.tsx | 36 ++++-- 3 files changed, 123 insertions(+), 37 deletions(-) diff --git a/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts b/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts index 4f6537c..a4bfda4 100644 --- a/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts +++ b/src/app/coffee-abonnements/summary/hooks/subscribeAbo.ts @@ -33,6 +33,9 @@ export type SubscribeAboInput = { invoiceCity?: string invoicePhone?: string invoiceEmail?: string + uidNumber?: string + atuNumber?: string + taxMode?: 'standard_vat' | 'reverse_charge' signingCity?: string signatureDataUrl?: string // logged-in user id @@ -99,6 +102,9 @@ export async function subscribeAbo(input: SubscribeAboInput) { paymentMethod: input.paymentMethod || undefined, invoiceByEmail: input.invoiceByEmail ?? false, invoiceSameAsShipping: input.invoiceSameAsShipping ?? true, + uidNumber: input.uidNumber || undefined, + atuNumber: input.atuNumber || undefined, + taxMode: input.taxMode || undefined, signingCity: input.signingCity || undefined, signatureDataUrl: input.signatureDataUrl || undefined, } diff --git a/src/app/coffee-abonnements/summary/page.tsx b/src/app/coffee-abonnements/summary/page.tsx index f87ad1c..0f2e502 100644 --- a/src/app/coffee-abonnements/summary/page.tsx +++ b/src/app/coffee-abonnements/summary/page.tsx @@ -43,6 +43,24 @@ function hashString(value: string): number { return hash >>> 0 } +const HOME_COUNTRY_CODE = 'AT' + +function normalizeUid(value: unknown): string { + if (typeof value !== 'string') return '' + return value.replace(/\s+/g, '').toUpperCase() +} + +function isLikelyValidUid(value: string): boolean { + return /^[A-Z]{2}[A-Z0-9]{4,14}$/.test(value) +} + +function pickFirstString(...values: unknown[]): string { + for (const value of values) { + if (typeof value === 'string' && value.trim() !== '') return value.trim() + } + return '' +} + export default function SummaryPage() { const router = useRouter(); const { coffees, loading, error } = useActiveCoffees(); @@ -75,6 +93,7 @@ export default function SummaryPage() { invoiceCity: '', invoicePhone: '', invoiceEmail: '', + uidNumber: '', signingCity: '', }); const [showThanks, setShowThanks] = useState(false); @@ -90,12 +109,27 @@ export default function SummaryPage() { const templateVariableNames = useMemo(() => extractTemplateVariables(contractHtml), [contractHtml]) const templateVariableNamesKey = useMemo(() => templateVariableNames.join('|'), [templateVariableNames]) const [contractVariables, setContractVariables] = useState>({}) + const isCompanyCustomer = user?.userType === 'company' || user?.user_type === 'company' + + const profileUidNumber = useMemo(() => normalizeUid(pickFirstString( + user?.uidNumber, + user?.uid_number, + user?.atuNumber, + user?.atu_number, + user?.companyProfile?.uid_number, + user?.companyProfile?.atu_number + )), [user]) + + const enteredUidNumber = useMemo(() => normalizeUid(form.uidNumber), [form.uidNumber]) + const effectiveUidNumber = enteredUidNumber || profileUidNumber + const hasValidCompanyUid = isCompanyCustomer && isLikelyValidUid(effectiveUidNumber) + const isForeignInvoiceCountry = form.country.toUpperCase() !== HOME_COUNTRY_CODE + const isReverseCharge = isCompanyCustomer && hasValidCompanyUid && isForeignInvoiceCountry // Auto-compute contract variables from form state for preview useEffect(() => { if (!templateVariableNamesKey) return const fullName = `${form.firstName} ${form.lastName}`.trim() - const isCompany = user?.userType === 'company' || user?.user_type === 'company' const invoiceSame = form.invoiceSameAsShipping const computed: Record = { @@ -103,8 +137,8 @@ export default function SummaryPage() { currentDate: new Date().toLocaleDateString('de-AT', { day: '2-digit', month: '2-digit', year: 'numeric' }), recipientName: fullName, recipientAddress: `${form.street}, ${form.postalCode} ${form.city}`.trim(), - shippingCustomerClass: isCompany ? '' : 'checked', - shippingCompanyClass: isCompany ? 'checked' : '', + shippingCustomerClass: isCompanyCustomer ? '' : 'checked', + shippingCompanyClass: isCompanyCustomer ? 'checked' : '', shippingFullName: fullName, shippingStreet: form.street, shippingPostalCode: form.postalCode, @@ -112,8 +146,8 @@ export default function SummaryPage() { shippingPhone: form.phone, shippingEmail: form.email, invoiceSameAsShippingMark: invoiceSame ? '✓' : '', - invoiceCompanyClass: isCompany ? 'checked' : '', - invoiceCustomerClass: isCompany ? '' : 'checked', + invoiceCompanyClass: isCompanyCustomer ? 'checked' : '', + invoiceCustomerClass: isCompanyCustomer ? '' : 'checked', invoiceFullName: invoiceSame ? fullName : form.invoiceFullName, invoiceStreet: invoiceSame ? form.street : form.invoiceStreet, invoicePostalCode: invoiceSame ? form.postalCode : form.invoicePostalCode, @@ -122,10 +156,10 @@ export default function SummaryPage() { invoiceEmail: invoiceSame ? form.email : form.invoiceEmail, fnCheckedClass: '', fnNumber: '', - atuCheckedClass: '', - atuNumber: '', - entrepreneurClass: isCompany ? 'checked' : '', - consumerClass: isCompany ? '' : 'checked', + atuCheckedClass: hasValidCompanyUid ? 'checked' : '', + atuNumber: effectiveUidNumber, + entrepreneurClass: isCompanyCustomer ? 'checked' : '', + consumerClass: isCompanyCustomer ? '' : 'checked', paymentSepaClass: form.paymentMethod === 'sepa' ? 'checked' : '', paymentCardClass: form.paymentMethod === 'card' ? 'checked' : '', paymentSofortClass: form.paymentMethod === 'sofort' ? 'checked' : '', @@ -134,7 +168,7 @@ export default function SummaryPage() { fullName, } setContractVariables(computed) - }, [templateVariableNamesKey, form, user, signatureDataUrl]) + }, [templateVariableNamesKey, form, signatureDataUrl, effectiveUidNumber, hasValidCompanyUid, isCompanyCustomer]) const populatedContractHtml = useMemo(() => { if (!contractHtml) return null @@ -458,8 +492,9 @@ export default function SummaryPage() { [totalPrice, shippingFee] ); - const taxAmount = useMemo(() => totalPrice * taxRate, [totalPrice, taxRate]); - const taxAmountWithShipping = useMemo(() => netWithShipping * taxRate, [netWithShipping, taxRate]); + const effectiveTaxRate = isReverseCharge ? 0 : taxRate + const taxAmount = useMemo(() => totalPrice * effectiveTaxRate, [totalPrice, effectiveTaxRate]); + const taxAmountWithShipping = useMemo(() => netWithShipping * effectiveTaxRate, [netWithShipping, effectiveTaxRate]); const totalWithTax = useMemo(() => netWithShipping + taxAmountWithShipping, [netWithShipping, taxAmountWithShipping]); const handleInput = (e: React.ChangeEvent) => { @@ -478,23 +513,24 @@ export default function SummaryPage() { return; } - const pick = (...values: any[]) => { - for (const value of values) { - if (typeof value === 'string' && value.trim() !== '') return value.trim(); - } - return ''; - }; - setSubmitError(null); setForm(prev => ({ ...prev, - firstName: pick(user.firstName, user.firstname, user.givenName, user.first_name) || prev.firstName, - lastName: pick(user.lastName, user.lastname, user.familyName, user.last_name) || prev.lastName, - email: pick(user.email, user.mail) || prev.email, - street: pick(user.street, user.addressStreet, user.address?.street, user.address_line_1) || prev.street, - postalCode: pick(user.postalCode, user.zipCode, user.zip, user.addressPostalCode, user.address?.postalCode) || prev.postalCode, - city: pick(user.city, user.addressCity, user.town, user.address?.city) || prev.city, - country: (pick(user.country, user.countryCode, user.addressCountry, user.address?.country) || prev.country).toUpperCase(), + firstName: pickFirstString(user.firstName, user.firstname, user.givenName, user.first_name) || prev.firstName, + lastName: pickFirstString(user.lastName, user.lastname, user.familyName, user.last_name) || prev.lastName, + email: pickFirstString(user.email, user.mail) || prev.email, + street: pickFirstString(user.street, user.addressStreet, user.address?.street, user.address_line_1) || prev.street, + postalCode: pickFirstString(user.postalCode, user.zipCode, user.zip, user.addressPostalCode, user.address?.postalCode) || prev.postalCode, + city: pickFirstString(user.city, user.addressCity, user.town, user.address?.city) || prev.city, + country: (pickFirstString(user.country, user.countryCode, user.addressCountry, user.address?.country) || prev.country).toUpperCase(), + uidNumber: normalizeUid(pickFirstString( + user.uidNumber, + user.uid_number, + user.atuNumber, + user.atu_number, + user.companyProfile?.uid_number, + user.companyProfile?.atu_number + ) || prev.uidNumber), })); }; @@ -576,6 +612,9 @@ export default function SummaryPage() { } : {}), signingCity: form.signingCity.trim() || undefined, signatureDataUrl: signatureDataUrl || undefined, + uidNumber: effectiveUidNumber || undefined, + atuNumber: effectiveUidNumber || undefined, + taxMode: isReverseCharge ? 'reverse_charge' : 'standard_vat', referred_by: typeof currentUserId === 'number' ? currentUserId : undefined, } console.info('[SummaryPage] subscribeAbo payload:', payload) @@ -730,10 +769,30 @@ export default function SummaryPage() { {/* Invoice address */}

Invoice address

+ {isCompanyCustomer && ( +
+ Unternehmer mit gueltiger UID und Rechnungsland ausserhalb von {HOME_COUNTRY_CODE} werden per Reverse Charge ohne ausgewiesene MwSt verrechnet. +
+ )} + {isCompanyCustomer && ( +
+ + +

+ Ohne gueltige UID wird die Rechnung mit normaler MwSt erstellt. +

+
+ )} {!form.invoiceSameAsShipping && (
@@ -902,13 +961,18 @@ export default function SummaryPage() { €{netWithShipping.toFixed(2)}
- Tax ({(taxRate * 100).toFixed(1)}%) + {isReverseCharge ? 'Tax (Reverse Charge)' : `Tax (${(effectiveTaxRate * 100).toFixed(1)}%)`} €{taxAmountWithShipping.toFixed(2)}
Total incl. tax €{totalWithTax.toFixed(2)}
+ {isReverseCharge && ( +
+ Reverse Charge aktiv: gueltige UID und auslaendisches Rechnungsland erkannt. +
+ )} {/* Validation summary (refined design) */}
Selected: {totalCapsules} capsules ({totalPacks} packs of 10). Target: {selectedPlanCapsules} capsules ({requiredPacks} packs). diff --git a/src/app/quickaction-dashboard/register-additional-information/company/page.tsx b/src/app/quickaction-dashboard/register-additional-information/company/page.tsx index 771ea97..55aad22 100644 --- a/src/app/quickaction-dashboard/register-additional-information/company/page.tsx +++ b/src/app/quickaction-dashboard/register-additional-information/company/page.tsx @@ -15,7 +15,8 @@ interface CompanyProfileData { companyPhone: string contactPersonName: string contactPersonPhone: string - vatNumber: string + registrationNumber: string + uidNumber: string street: string postalCode: string city: string @@ -44,7 +45,8 @@ const init: CompanyProfileData = { companyPhone: '', contactPersonName: '', contactPersonPhone: '', - vatNumber: '', + registrationNumber: '', + uidNumber: '', street: '', postalCode: '', city: '', @@ -216,7 +218,8 @@ export default function CompanyAdditionalInformationPage() { companyPhone: profile?.phone || me?.companyPhone || prev.companyPhone, contactPersonName: profile?.contact_person_name || me?.contactPersonName || prev.contactPersonName, contactPersonPhone: profile?.contact_person_phone || me?.contactPersonPhone || prev.contactPersonPhone, - vatNumber: profile?.registration_number || prev.vatNumber, + registrationNumber: profile?.registration_number || prev.registrationNumber, + uidNumber: profile?.uid_number || profile?.atu_number || prev.uidNumber, street: profile?.address || prev.street, postalCode: profile?.zip_code || prev.postalCode, city: profile?.city || prev.city, @@ -281,7 +284,7 @@ export default function CompanyAdditionalInformationPage() { const validate = () => { const required: (keyof CompanyProfileData)[] = [ 'companyName','companyEmail','companyPhone','contactPersonName','contactPersonPhone', - 'vatNumber','street','postalCode','city','country','accountHolder','iban' + 'street','postalCode','city','country','accountHolder','iban' ] for (const k of required) { if (!form[k].trim()) { @@ -414,7 +417,9 @@ export default function CompanyAdditionalInformationPage() { zip_code: form.postalCode, // Backend expects 'zip_code' city: form.city, country: form.country, - registrationNumber: form.vatNumber, // Map VAT number to registration number + registrationNumber: form.registrationNumber || undefined, + uidNumber: form.uidNumber || undefined, + atuNumber: form.uidNumber || undefined, businessType: 'company', // Default business type branch: null, // Not collected in form, set to null numberOfEmployees: null, // Not collected in form, set to null @@ -580,15 +585,26 @@ export default function CompanyAdditionalInformationPage() {
+
+
+ +
From 8b3cf18ea6c4999fa8b4ebf0eb836fe1dfdd1131 Mon Sep 17 00:00:00 2001 From: DeathKaioken Date: Tue, 28 Apr 2026 19:30:24 +0200 Subject: [PATCH 2/2] admin dashboard --- src/app/admin/page.tsx | 72 +++++++++++++++++ src/app/coffee-abonnements/summary/page.tsx | 4 +- src/app/components/nav/Header.tsx | 85 +-------------------- 3 files changed, 78 insertions(+), 83 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 639f8c2..dfa8d0e 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -315,6 +315,78 @@ export default function AdminDashboardPage() { + {/* User Verify */} + + + {/* Finance Management */} + + + {/* Pool Management */} + + + {/* Affiliate Management */} + + {/* News Management */} - - - - {DISPLAY_MATRIX && ( - - )} - - {DISPLAY_ABONEMENTS && ( - <> - - - - )} - {DISPLAY_POOLS && ( - - )} - - {DISPLAY_NEWS && ( - - )} - - {/* ADDED: Dev Management in hamburger admin nav */} - {isAdminOrSuper && ( - - )} +

+ Open the dashboard to access all admin modules via icon panels. +