Blog categories (hasMany), shipping-address contact fields, product SKU on invoices

- Posts.categories is now hasMany — blog list/detail/live-preview render
  a comma-joined list instead of a single category.
- Checkout's "Abweichende Lieferadresse" gains optional Firma + Kontakt-
  E-Mail/Telefon fields (handed to the shipping carrier, not used for
  customer communication).
- Customer profile can now store its own shipping address (mirroring
  Customers.ts's new "Lieferadresse" tab), prefilling the checkout
  override instead of always starting blank.
- Product-level optional SKU (previously only on variants) snapshots onto
  each order item and shows up on invoices (visual + EN16931 XML),
  the confirmation email, and the account order detail page.
This commit is contained in:
Marco
2026-07-30 08:41:39 +00:00
parent 1467750db6
commit e3352d7e32
18 changed files with 403 additions and 34 deletions
+60
View File
@@ -203,6 +203,23 @@ export type CustomerAddress = {
// /checkout's Firma/USt-IdNr. fields for a returning customer.
companyName: string | null;
vatId: string | null;
// Optional second/shipping address — see Customers.ts's "Lieferadresse"
// tab. Prefills /checkout's "Abweichende Lieferadresse" section once
// hasDifferentShippingAddress is set here; still fully overwritable per
// order (Orders keeps its own shipping* snapshot regardless).
hasDifferentShippingAddress: boolean;
shippingFirstName: string | null;
shippingLastName: string | null;
shippingCompanyName: string | null;
shippingDeliveryMethod: "address" | "packstation" | null;
shippingStreet: string | null;
shippingPackstationNumber: string | null;
shippingPostNumber: string | null;
shippingZip: string | null;
shippingCity: string | null;
shippingCountry: string | null;
shippingContactEmail: string | null;
shippingContactPhone: string | null;
};
export type CustomerProfile = CustomerSummary & CustomerAddress;
@@ -223,6 +240,19 @@ type PayloadCustomerMe = {
country: string | null;
companyName: string | null;
vatId: string | null;
hasDifferentShippingAddress: boolean | null;
shippingFirstName: string | null;
shippingLastName: string | null;
shippingCompanyName: string | null;
shippingDeliveryMethod: "address" | "packstation" | null;
shippingStreet: string | null;
shippingPackstationNumber: string | null;
shippingPostNumber: string | null;
shippingZip: string | null;
shippingCity: string | null;
shippingCountry: string | null;
shippingContactEmail: string | null;
shippingContactPhone: string | null;
cart: { product: number; productSlug: string; quantity: number; variantName: string | null }[] | null;
};
@@ -251,6 +281,19 @@ export async function getCustomerProfile(token: string): Promise<CustomerProfile
country: u.country,
companyName: u.companyName,
vatId: u.vatId,
hasDifferentShippingAddress: Boolean(u.hasDifferentShippingAddress),
shippingFirstName: u.shippingFirstName,
shippingLastName: u.shippingLastName,
shippingCompanyName: u.shippingCompanyName,
shippingDeliveryMethod: u.shippingDeliveryMethod,
shippingStreet: u.shippingStreet,
shippingPackstationNumber: u.shippingPackstationNumber,
shippingPostNumber: u.shippingPostNumber,
shippingZip: u.shippingZip,
shippingCity: u.shippingCity,
shippingCountry: u.shippingCountry,
shippingContactEmail: u.shippingContactEmail,
shippingContactPhone: u.shippingContactPhone,
};
}
@@ -269,6 +312,19 @@ export async function updateCustomerProfile(
country: string;
companyName?: string;
vatId?: string;
hasDifferentShippingAddress?: boolean;
shippingFirstName?: string;
shippingLastName?: string;
shippingCompanyName?: string;
shippingDeliveryMethod?: "address" | "packstation";
shippingStreet?: string;
shippingPackstationNumber?: string;
shippingPostNumber?: string;
shippingZip?: string;
shippingCity?: string;
shippingCountry?: string;
shippingContactEmail?: string;
shippingContactPhone?: string;
},
): Promise<{ ok: true } | { ok: false; reason: string }> {
const res = await fetch(`${PAYLOAD_URL}/api/customers/${customerId}`, {
@@ -495,6 +551,7 @@ export type CustomerOrderDetail = CustomerOrder & {
hasDifferentShippingAddress: boolean;
shippingFirstName: string | null;
shippingLastName: string | null;
shippingCompanyName: string | null;
shippingDeliveryMethod: "address" | "packstation" | null;
shippingStreet: string | null;
shippingPackstationNumber: string | null;
@@ -502,6 +559,8 @@ export type CustomerOrderDetail = CustomerOrder & {
shippingZip: string | null;
shippingCity: string | null;
shippingCountry: string | null;
shippingContactEmail: string | null;
shippingContactPhone: string | null;
subtotal: number;
shippingCost: number;
shippingMethodTitle: string;
@@ -521,6 +580,7 @@ export type CustomerOrderItem = {
bundleContents: string | null;
variantName: string | null;
returnQuantity: number;
sku: string | null;
};
// Access control (Orders.ts) already scopes a customer's own JWT to only