feat: add business hours to SiteSettings for WhatsApp Bot auto-away

Adds businessHours group with timezone, weekly schedule array, and
localized autoAwayMessage field. Migration creates schedule array
table and adds columns to site_settings + site_settings_locales.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Porwoll 2026-03-02 16:13:49 +00:00
parent 29bb3d4ecc
commit 39c59b3b2e
3 changed files with 142 additions and 0 deletions

View file

@ -187,6 +187,93 @@ export const SiteSettings: CollectionConfig = {
}, },
], ],
}, },
// Business Hours (für WhatsApp Bot Auto-Away)
{
name: 'businessHours',
type: 'group',
label: 'Geschäftszeiten',
admin: {
description: 'Öffnungszeiten für automatische Antworten (z.B. WhatsApp Bot)',
},
fields: [
{
name: 'timezone',
type: 'text',
label: 'Zeitzone',
defaultValue: 'Europe/Berlin',
admin: {
description: 'IANA Zeitzone, z.B. Europe/Berlin, Europe/Vienna',
},
},
{
name: 'schedule',
type: 'array',
label: 'Wochenplan',
maxRows: 7,
admin: {
description: 'Öffnungszeiten pro Wochentag',
},
fields: [
{
name: 'day',
type: 'select',
label: 'Tag',
required: true,
options: [
{ label: 'Montag', value: 'monday' },
{ label: 'Dienstag', value: 'tuesday' },
{ label: 'Mittwoch', value: 'wednesday' },
{ label: 'Donnerstag', value: 'thursday' },
{ label: 'Freitag', value: 'friday' },
{ label: 'Samstag', value: 'saturday' },
{ label: 'Sonntag', value: 'sunday' },
],
},
{
name: 'closed',
type: 'checkbox',
label: 'Geschlossen',
defaultValue: false,
},
{
type: 'row',
fields: [
{
name: 'open',
type: 'text',
label: 'Öffnet',
admin: {
description: 'z.B. 09:00',
width: '50%',
condition: (_data, siblingData) => !siblingData?.closed,
},
},
{
name: 'close',
type: 'text',
label: 'Schließt',
admin: {
description: 'z.B. 17:00',
width: '50%',
condition: (_data, siblingData) => !siblingData?.closed,
},
},
],
},
],
},
{
name: 'autoAwayMessage',
type: 'textarea',
label: 'Auto-Away Nachricht',
localized: true,
admin: {
description:
'Wird außerhalb der Geschäftszeiten gesendet. Platzhalter: {{nextOpen}} (nächste Öffnungszeit)',
},
},
],
},
{ {
name: 'seo', name: 'seo',
type: 'group', type: 'group',

View file

@ -0,0 +1,49 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
/**
* Migration: Add business hours fields to site_settings
*
* Adds:
* - business_hours_timezone (varchar) on site_settings
* - business_hours_auto_away_message (varchar) on site_settings_locales (localized)
* - site_settings_business_hours_schedule table (array field)
*/
export async function up({ db }: MigrateUpArgs): Promise<void> {
// Add timezone column to main table
await db.execute(sql`
ALTER TABLE "site_settings"
ADD COLUMN IF NOT EXISTS "business_hours_timezone" varchar DEFAULT 'Europe/Berlin';
`)
// Add localized auto-away message to locales table
await db.execute(sql`
ALTER TABLE "site_settings_locales"
ADD COLUMN IF NOT EXISTS "business_hours_auto_away_message" varchar;
`)
// Create schedule array table
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "site_settings_business_hours_schedule" (
"id" serial PRIMARY KEY NOT NULL,
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL REFERENCES site_settings(id) ON DELETE CASCADE,
"day" varchar,
"closed" boolean DEFAULT false,
"open" varchar,
"close" varchar
);
CREATE INDEX IF NOT EXISTS "site_settings_bh_schedule_order_idx"
ON "site_settings_business_hours_schedule" USING btree ("_order");
CREATE INDEX IF NOT EXISTS "site_settings_bh_schedule_parent_idx"
ON "site_settings_business_hours_schedule" USING btree ("_parent_id");
`)
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
DROP TABLE IF EXISTS "site_settings_business_hours_schedule";
ALTER TABLE "site_settings" DROP COLUMN IF EXISTS "business_hours_timezone";
ALTER TABLE "site_settings_locales" DROP COLUMN IF EXISTS "business_hours_auto_away_message";
`)
}

View file

@ -40,6 +40,7 @@ import * as migration_20260216_150000_add_card_grid_icon_fields from './20260216
import * as migration_20260217_120000_add_tenant_to_forms from './20260217_120000_add_tenant_to_forms'; import * as migration_20260217_120000_add_tenant_to_forms from './20260217_120000_add_tenant_to_forms';
import * as migration_20260228_150000_add_html_embed_block from './20260228_150000_add_html_embed_block'; import * as migration_20260228_150000_add_html_embed_block from './20260228_150000_add_html_embed_block';
import * as migration_20260302_180000_add_social_platforms_missing_columns from './20260302_180000_add_social_platforms_missing_columns'; import * as migration_20260302_180000_add_social_platforms_missing_columns from './20260302_180000_add_social_platforms_missing_columns';
import * as migration_20260303_120000_add_site_settings_business_hours from './20260303_120000_add_site_settings_business_hours';
export const migrations = [ export const migrations = [
{ {
@ -252,4 +253,9 @@ export const migrations = [
down: migration_20260302_180000_add_social_platforms_missing_columns.down, down: migration_20260302_180000_add_social_platforms_missing_columns.down,
name: '20260302_180000_add_social_platforms_missing_columns' name: '20260302_180000_add_social_platforms_missing_columns'
}, },
{
up: migration_20260303_120000_add_site_settings_business_hours.up,
down: migration_20260303_120000_add_site_settings_business_hours.down,
name: '20260303_120000_add_site_settings_business_hours'
},
]; ];