mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 18:34:13 +00:00
- Add Products collection with comprehensive fields (pricing, inventory, SEO, CTA) - Add ProductCategories collection with hierarchical structure - Implement CI/CD pipeline with GitHub Actions (lint, typecheck, test, build, e2e) - Add access control test utilities and unit tests - Fix Posts API to include category field for backwards compatibility - Update ESLint config with ignores for migrations and admin components - Add centralized access control functions in src/lib/access - Add db-direct.sh utility script for database access 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
497 lines
13 KiB
TypeScript
497 lines
13 KiB
TypeScript
import type { CollectionConfig } from 'payload'
|
|
import { tenantScopedPublicRead, authenticatedOnly } from '../lib/tenantAccess'
|
|
|
|
export const Products: CollectionConfig = {
|
|
slug: 'products',
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
group: 'Produkte',
|
|
defaultColumns: ['title', 'category', 'status', 'price', 'isFeatured', 'updatedAt'],
|
|
description: 'Produkte und Artikel',
|
|
},
|
|
access: {
|
|
read: tenantScopedPublicRead,
|
|
create: authenticatedOnly,
|
|
update: authenticatedOnly,
|
|
delete: authenticatedOnly,
|
|
},
|
|
fields: [
|
|
// =========================================================================
|
|
// Grundinformationen
|
|
// =========================================================================
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
required: true,
|
|
label: 'Produktname',
|
|
localized: true,
|
|
},
|
|
{
|
|
name: 'slug',
|
|
type: 'text',
|
|
required: true,
|
|
unique: false, // Uniqueness per tenant
|
|
label: 'URL-Slug',
|
|
admin: {
|
|
description: 'URL-freundlicher Name (z.B. "premium-widget")',
|
|
},
|
|
},
|
|
{
|
|
name: 'sku',
|
|
type: 'text',
|
|
label: 'Artikelnummer (SKU)',
|
|
admin: {
|
|
description: 'Eindeutige Artikelnummer für interne Verwaltung',
|
|
},
|
|
},
|
|
{
|
|
name: 'shortDescription',
|
|
type: 'textarea',
|
|
label: 'Kurzbeschreibung',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Kurze Beschreibung für Produktlisten (max. 200 Zeichen)',
|
|
},
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'richText',
|
|
label: 'Produktbeschreibung',
|
|
localized: true,
|
|
},
|
|
|
|
// =========================================================================
|
|
// Kategorisierung
|
|
// =========================================================================
|
|
{
|
|
name: 'category',
|
|
type: 'relationship',
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
relationTo: 'product-categories' as any,
|
|
label: 'Kategorie',
|
|
admin: {
|
|
description: 'Hauptkategorie des Produkts',
|
|
},
|
|
},
|
|
{
|
|
name: 'tags',
|
|
type: 'array',
|
|
label: 'Tags',
|
|
admin: {
|
|
description: 'Zusätzliche Schlagworte für Filterung',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'tag',
|
|
type: 'text',
|
|
required: true,
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Medien
|
|
// =========================================================================
|
|
{
|
|
name: 'featuredImage',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
label: 'Hauptbild',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'gallery',
|
|
type: 'array',
|
|
label: 'Bildergalerie',
|
|
admin: {
|
|
description: 'Zusätzliche Produktbilder',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'image',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'caption',
|
|
type: 'text',
|
|
label: 'Bildunterschrift',
|
|
localized: true,
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Preisgestaltung
|
|
// =========================================================================
|
|
{
|
|
name: 'pricing',
|
|
type: 'group',
|
|
label: 'Preisgestaltung',
|
|
fields: [
|
|
{
|
|
type: 'row',
|
|
fields: [
|
|
{
|
|
name: 'price',
|
|
type: 'number',
|
|
label: 'Preis',
|
|
admin: {
|
|
width: '33%',
|
|
description: 'Regulärer Preis in Euro',
|
|
},
|
|
},
|
|
{
|
|
name: 'salePrice',
|
|
type: 'number',
|
|
label: 'Angebotspreis',
|
|
admin: {
|
|
width: '33%',
|
|
description: 'Reduzierter Preis (optional)',
|
|
},
|
|
},
|
|
{
|
|
name: 'currency',
|
|
type: 'select',
|
|
label: 'Währung',
|
|
defaultValue: 'EUR',
|
|
options: [
|
|
{ label: 'Euro (EUR)', value: 'EUR' },
|
|
{ label: 'US-Dollar (USD)', value: 'USD' },
|
|
{ label: 'Schweizer Franken (CHF)', value: 'CHF' },
|
|
],
|
|
admin: {
|
|
width: '34%',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'priceType',
|
|
type: 'select',
|
|
label: 'Preistyp',
|
|
defaultValue: 'fixed',
|
|
options: [
|
|
{ label: 'Festpreis', value: 'fixed' },
|
|
{ label: 'Ab Preis', value: 'from' },
|
|
{ label: 'Auf Anfrage', value: 'on_request' },
|
|
{ label: 'Kostenlos', value: 'free' },
|
|
],
|
|
},
|
|
{
|
|
name: 'priceNote',
|
|
type: 'text',
|
|
label: 'Preishinweis',
|
|
localized: true,
|
|
admin: {
|
|
description: 'z.B. "zzgl. MwSt.", "pro Monat", "Einmalzahlung"',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Produktdetails
|
|
// =========================================================================
|
|
{
|
|
name: 'details',
|
|
type: 'group',
|
|
label: 'Produktdetails',
|
|
admin: {
|
|
description: 'Technische Daten und Spezifikationen',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'specifications',
|
|
type: 'array',
|
|
label: 'Spezifikationen',
|
|
admin: {
|
|
description: 'Technische Daten als Schlüssel-Wert-Paare',
|
|
},
|
|
fields: [
|
|
{
|
|
type: 'row',
|
|
fields: [
|
|
{
|
|
name: 'key',
|
|
type: 'text',
|
|
label: 'Eigenschaft',
|
|
required: true,
|
|
localized: true,
|
|
admin: {
|
|
width: '50%',
|
|
placeholder: 'z.B. Gewicht, Maße, Material',
|
|
},
|
|
},
|
|
{
|
|
name: 'value',
|
|
type: 'text',
|
|
label: 'Wert',
|
|
required: true,
|
|
localized: true,
|
|
admin: {
|
|
width: '50%',
|
|
placeholder: 'z.B. 500g, 10x20cm, Aluminium',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'features',
|
|
type: 'array',
|
|
label: 'Highlights / Features',
|
|
admin: {
|
|
description: 'Wichtigste Produktvorteile',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'feature',
|
|
type: 'text',
|
|
required: true,
|
|
localized: true,
|
|
},
|
|
{
|
|
name: 'icon',
|
|
type: 'text',
|
|
label: 'Icon',
|
|
admin: {
|
|
description: 'Optional: Lucide-Icon Name',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Lager & Verfügbarkeit
|
|
// =========================================================================
|
|
{
|
|
name: 'inventory',
|
|
type: 'group',
|
|
label: 'Lager & Verfügbarkeit',
|
|
fields: [
|
|
{
|
|
type: 'row',
|
|
fields: [
|
|
{
|
|
name: 'stockStatus',
|
|
type: 'select',
|
|
label: 'Verfügbarkeit',
|
|
defaultValue: 'in_stock',
|
|
options: [
|
|
{ label: 'Auf Lager', value: 'in_stock' },
|
|
{ label: 'Nur noch wenige', value: 'low_stock' },
|
|
{ label: 'Nicht auf Lager', value: 'out_of_stock' },
|
|
{ label: 'Auf Bestellung', value: 'on_order' },
|
|
{ label: 'Vorbestellung', value: 'preorder' },
|
|
],
|
|
admin: {
|
|
width: '50%',
|
|
},
|
|
},
|
|
{
|
|
name: 'stockQuantity',
|
|
type: 'number',
|
|
label: 'Lagerbestand',
|
|
admin: {
|
|
width: '50%',
|
|
description: 'Aktuelle Stückzahl (optional)',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'deliveryTime',
|
|
type: 'text',
|
|
label: 'Lieferzeit',
|
|
localized: true,
|
|
admin: {
|
|
description: 'z.B. "1-3 Werktage", "Sofort lieferbar"',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Verknüpfungen
|
|
// =========================================================================
|
|
{
|
|
name: 'relatedProducts',
|
|
type: 'relationship',
|
|
relationTo: 'products',
|
|
hasMany: true,
|
|
label: 'Ähnliche Produkte',
|
|
admin: {
|
|
description: 'Produktempfehlungen für Cross-Selling',
|
|
},
|
|
},
|
|
{
|
|
name: 'downloadFiles',
|
|
type: 'array',
|
|
label: 'Downloads',
|
|
admin: {
|
|
description: 'Produktdatenblätter, Anleitungen, etc.',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'file',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
label: 'Titel',
|
|
localized: true,
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Call-to-Action
|
|
// =========================================================================
|
|
{
|
|
name: 'cta',
|
|
type: 'group',
|
|
label: 'Call-to-Action',
|
|
admin: {
|
|
description: 'Handlungsaufforderung für das Produkt',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'type',
|
|
type: 'select',
|
|
label: 'CTA-Typ',
|
|
defaultValue: 'contact',
|
|
options: [
|
|
{ label: 'Kontakt aufnehmen', value: 'contact' },
|
|
{ label: 'Angebot anfordern', value: 'quote' },
|
|
{ label: 'In den Warenkorb', value: 'cart' },
|
|
{ label: 'Externer Link', value: 'external' },
|
|
{ label: 'Download', value: 'download' },
|
|
],
|
|
},
|
|
{
|
|
name: 'buttonText',
|
|
type: 'text',
|
|
label: 'Button-Text',
|
|
localized: true,
|
|
admin: {
|
|
description: 'z.B. "Jetzt anfragen", "Kaufen", "Herunterladen"',
|
|
},
|
|
},
|
|
{
|
|
name: 'externalUrl',
|
|
type: 'text',
|
|
label: 'Externer Link',
|
|
admin: {
|
|
condition: (_, siblingData) => siblingData?.type === 'external',
|
|
description: 'URL für externen Shop oder Bestellseite',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// SEO
|
|
// =========================================================================
|
|
{
|
|
name: 'seo',
|
|
type: 'group',
|
|
label: 'SEO',
|
|
fields: [
|
|
{
|
|
name: 'metaTitle',
|
|
type: 'text',
|
|
label: 'Meta-Titel',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Überschreibt den Produktnamen für Suchmaschinen',
|
|
},
|
|
},
|
|
{
|
|
name: 'metaDescription',
|
|
type: 'textarea',
|
|
label: 'Meta-Beschreibung',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Kurze Beschreibung für Suchergebnisse (max. 160 Zeichen)',
|
|
},
|
|
},
|
|
{
|
|
name: 'ogImage',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
label: 'Social Media Bild',
|
|
admin: {
|
|
description: 'Bild für Social Media Shares (1200x630px empfohlen)',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
|
|
// =========================================================================
|
|
// Status & Sortierung (Sidebar)
|
|
// =========================================================================
|
|
{
|
|
name: 'status',
|
|
type: 'select',
|
|
label: 'Status',
|
|
defaultValue: 'draft',
|
|
options: [
|
|
{ label: 'Entwurf', value: 'draft' },
|
|
{ label: 'Veröffentlicht', value: 'published' },
|
|
{ label: 'Archiviert', value: 'archived' },
|
|
],
|
|
admin: {
|
|
position: 'sidebar',
|
|
},
|
|
},
|
|
{
|
|
name: 'isFeatured',
|
|
type: 'checkbox',
|
|
label: 'Hervorgehoben',
|
|
defaultValue: false,
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: 'Auf Startseite oder in Highlights anzeigen',
|
|
},
|
|
},
|
|
{
|
|
name: 'isNew',
|
|
type: 'checkbox',
|
|
label: 'Neu',
|
|
defaultValue: false,
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: '"Neu"-Badge anzeigen',
|
|
},
|
|
},
|
|
{
|
|
name: 'order',
|
|
type: 'number',
|
|
label: 'Sortierung',
|
|
defaultValue: 0,
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: 'Kleinere Zahlen erscheinen zuerst',
|
|
},
|
|
},
|
|
{
|
|
name: 'publishedAt',
|
|
type: 'date',
|
|
label: 'Veröffentlichungsdatum',
|
|
admin: {
|
|
position: 'sidebar',
|
|
date: {
|
|
pickerAppearance: 'dayAndTime',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
}
|