Files
UbuntuandClaude Opus 5 5c30467fbd Add massage as a third vertical, driven by a config registry
Introduces /massage alongside barber and beauty: landing page, booking
flow, admin support, home tile and SEO entries, in all three languages.

Architecture
- application/config/verticals.php + vertical_helper.php: one registry
  entry per vertical (branding, assets, views, behaviour flags). A fourth
  vertical is a config entry plus content files.
- Strangler: barber and beauty keep pointing at their existing view files,
  so their rendered HTML is unchanged. Only massage uses the new generic
  pages/vertical-*.php and includes/vertical-*.php views, which collapse
  the four duplicated per-language nav/footer branches into one.
- Pages::vertical() + one route; booking(), booking_finished(),
  _booking_error() and manage_booking() are now registry-driven.

Worker/vertical coupling
- getActiveWorkers() derives the vertical from services.service_category_id
  instead of the workers.is_barber / is_beauty flags, which were a
  hand-maintained cache of exactly that fact. Verified against production
  data: the derived set reproduced the stored flags for every worker, in
  both verticals. No schema change was needed for massage.
- The legacy flags are now written through from the category so a rollback
  cannot strand a new worker, and the admin worker UI shows the derived
  verticals read-only instead of two dropdowns that controlled nothing.

Bug fixes found along the way (all pre-existing)
- booking_process() had no server-side category guard; cross-vertical
  mixing was prevented only by client-side JS.
- add-service-form / add-worker-form emitted `selected` on every category
  option, so the newest category silently became the default.
- update-service-form offered only barber/beauty, so editing a service of
  any other type silently rewrote it.
- getWorkerScheduleByDay ignored schedule overrides while getAvailableTimes
  honoured them, so slots could be shown and then rejected. Added an
  override-aware getWorkerScheduleForDate() and used it in both guards.
- Booking lists dereferenced a null service if one had been hard-deleted.
- main.css: .tiles was tuned for exactly two tiles, including an
  absolutely-positioned .style1 at the 1280px breakpoint.

Massage-specific behaviour, opt-in per vertical
- strip_category_prefix: grouped service lists show "50 min" under the
  treatment heading rather than repeating the full name. The full name is
  carried in data-service-name so the totals panel stays unambiguous, and
  services.service_name is untouched for emails and admin.
- single_service_booking: one treatment per booking, enforced in the UI and
  in booking_process(). Re-clicking the selection releases it.
- Displayed treatment time (50/80/110 min) is in the service name; the
  booked slot (60/90/120 min) is service_time and covers changing and
  payment. service_time is never shown to the guest.

DB migrations for dev/prod are in documents/ - additive only, no ALTER.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 16:34:47 +00:00

169 lines
7.5 KiB
PHP

<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/*
| -------------------------------------------------------------------------
| VERTICAL REGISTRY
| -------------------------------------------------------------------------
|
| One entry per service area ("vertical") the salon offers. Adding a fourth
| vertical should be one entry here plus its content files — not edits
| scattered across the controller, the model and twenty views.
|
| STRANGLER NOTE
| --------------
| 'barber' and 'beauty' deliberately point their view_* keys at the dedicated
| files that already exist. Their rendered HTML is therefore byte-identical
| after this registry was introduced. Only 'massage' uses the generic
| parameterised views (pages/vertical-*.php). Once those are proven in
| production, barber/beauty can be repointed at the generic views and their
| eight dedicated files deleted — with a before/after HTML diff as the test.
|
| KEYS
| ----
| slug URL segment, services.service_type value, registry key.
| These three MUST agree; see below.
| enabled FALSE removes the vertical from routing entirely (404).
| public TRUE = appears as a tile on the home page. A vertical can
| be routable but not yet advertised — that is the launch
| switch.
| sort tile / listing order, ascending.
| label per-language display name, used for the home tile <h2>.
| heading the "- BEAUTY" text beside the logo in the header.
| category_slug service_categories.serv_cat_slug that this vertical's
| workers belong to. Informational: the runtime derives the
| worker set from the DB, never from this value.
|
| ONE SLUG ACROSS ALL LANGUAGES
| -----------------------------
| The language switcher rebuilds URLs by exploding $_SERVER['REQUEST_URI'] and
| reusing segment 2 verbatim (see *-header.php). A per-language slug would
| therefore produce a 404 the moment a visitor switched language. Do not add
| one. If a localised URL is ever needed, add an alias route pointing at the
| same canonical slug.
|
| Note also config.php permitted_uri_chars excludes æ/ø/å, so a Norwegian
| slug containing them would be rejected by CodeIgniter before routing.
*/
$config['verticals'] = array(
'barber' => array(
'slug' => 'barber',
'enabled' => TRUE,
'public' => TRUE,
'sort' => 10,
'category_slug' => 'cosmeric',
'label' => array('no' => 'BARBER', 'en' => 'BARBER', 'hu' => 'BARBER'),
'heading' => '- BARBER',
'title' => 'Studio Beve - Beauty & Barber Shop',
'og_site_name' => 'Studio Beve Barber',
'og_description' => 'Vippestylist, make up artist, PMU-artist',
'meta_description' => 'Skjeggtrimming, Barneklipp, Skjeggfarging, Herreklipp, Skinfade Herreklipp, Voks, Beard Trimming, Child Haircut, Beard Dying, Traditional Shave, Men\'s Haircut, Skinfade, Wax, herrefrisør, Trondheim, hårklipp, haircut, barber, barberer, Men\'s hairdresser',
'css' => 'barber.css',
'logo' => 'logo_gold.png',
'img_dir' => 'barber',
'map_img' => 'assets/img/barber/map.jpg',
'facebook' => 'https://www.facebook.com/studiobevebarber',
'instagram' => 'https://www.instagram.com/studio.beve.barber/',
'tile_class' => 'style2',
'tile_img' => 'assets/img/barber.jpg',
// The <img> in the home tile is only a size spacer; the visible art is
// the CSS .image:before background. Kept per-vertical so the existing
// tiles render exactly as before.
'tile_spacer' => 'assets/img/pic03.jpg',
// Strangler: existing dedicated files, untouched.
'skeleton' => 'barber',
'view_landing' => 'pages/barber',
'view_booking' => 'pages/barber-booking',
'view_finished' => 'pages/barber-booking-finished',
'view_error' => 'pages/barber-booking-error',
),
'beauty' => array(
'slug' => 'beauty',
'enabled' => TRUE,
'public' => TRUE,
'sort' => 20,
'category_slug' => 'cosmeric',
'label' => array('no' => 'BEAUTY', 'en' => 'BEAUTY', 'hu' => 'BEAUTY'),
'heading' => '- BEAUTY',
'title' => 'Studio Beve - Beauty',
'og_site_name' => 'Studio Beve',
'og_description' => 'Vippestylist, make up artist, PMU-artist',
'meta_description' => 'Voksing, Sminke, Vippeextensions, Pudderbryn, PMU-Tatovering, Øyenbrynstyling, Vippestyling, Powder Brows, permanent, PMU Makeup, Makeup, Wax, Eyelash Extension, Eyebrow styling, Eyelash Styling, Wedding makeup, Brow lamination, Lash dying, Brow forming, Henna dying, Lash lift, Naturlig sminke, Bryllupssminke, Laminering, Henna farging, Vippeløft, Forming av bryn, Farging av vipper, Trondheim',
'css' => 'beauty.css',
'logo' => 'logo.png',
'img_dir' => 'beauty',
'map_img' => 'assets/img/beauty/map.jpg',
'facebook' => 'https://www.facebook.com/studiobevebeauty',
'instagram' => 'https://www.instagram.com/studio.beve.beauty/',
'tile_class' => 'style1',
'tile_img' => 'assets/img/beauty.jpg',
'tile_spacer' => 'assets/img/pic09.jpg',
// Strangler: existing dedicated files, untouched.
'skeleton' => 'beauty',
'view_landing' => 'pages/beauty',
'view_booking' => 'pages/beauty-booking',
'view_finished' => 'pages/beauty-booking-finished',
'view_error' => 'pages/beauty-booking-error',
),
'massage' => array(
'slug' => 'massage',
'enabled' => TRUE,
// Launch switch: the tile CSS now supports three tiles, so massage is live.
'public' => TRUE,
'sort' => 30,
'category_slug' => 'massage',
// Row labels in grouped service lists drop the category prefix, so a
// heading of "Klassisk svensk massasje" is followed by "50 min" rather
// than the whole name again. barber/beauty omit this and are unchanged.
'strip_category_prefix' => TRUE,
// A guest books one massage at a time: selecting a treatment clears and
// disables the others. Enforced in the UI *and* in booking_process().
// barber/beauty omit this and keep multi-select.
'single_service_booking' => TRUE,
'label' => array('no' => 'MASSASJE', 'en' => 'MASSAGE', 'hu' => 'MASSZÁZS'),
'heading' => '- MASSAGE',
'title' => 'Studio Beve - Massage',
'og_site_name' => 'Studio Beve Massage',
'og_description' => 'Massasje, klassisk massasje, dyp vevsmassasje',
'meta_description' => 'Massasje, Klassisk massasje, Dypvevsmassasje, Ryggmassasje, Nakkemassasje, Idrettsmassasje, Avslappende massasje, Massage, Deep tissue massage, Sports massage, Relaxing massage, Back massage, Masszázs, Svédmasszázs, Frissítő masszázs, Trondheim',
'css' => 'massage.css',
'logo' => 'logo.png',
'img_dir' => 'massage',
'map_img' => 'assets/img/massage/map.jpg',
'facebook' => 'https://www.facebook.com/studiobevebeauty',
'instagram' => 'https://www.instagram.com/studio.beve.beauty/',
'tile_class' => 'style3',
'tile_img' => 'assets/img/massage.jpg',
'tile_spacer' => 'assets/img/pic03.jpg',
// The generic parameterised views.
'skeleton' => 'vertical',
'view_landing' => 'pages/vertical-landing',
'view_booking' => 'pages/vertical-booking',
'view_finished' => 'pages/vertical-booking-finished',
'view_error' => 'pages/vertical-booking-error',
),
);