# Feature: Company Settings

## Purpose

Per-team company configuration for timezone, weekly business hours, cancellation notice window, and cancellation fee rules (residential/commercial, percentage or flat, by cutoff hours). Used by the customer portal when scheduling and cancelling appointments.

## Boundaries

- **Owns:** `company_settings`, `business_hours`, `cancellation_fee_rules`; CompanySettings domain Actions, Policies, HTTP (web + API), Inertia page `Pages/CompanySettings/Edit.vue`
- **Does not own:** Jobs/Customers CRM, billing/invoicing of fees
- **Depends on (platform only):** User / Team / auth (Jetstream abilities; owners/admins/editors manage settings)
- **Depends on (other domains):** none required
- **Consumed by (soft):** Jobs injects `BusinessHoursGuard` and `CancellationPolicy` when the domain is present (`class_exists` / constructor DI). Portal customer type for fees is resolved via DB (`customers.type`), not Customers model imports.
- **Platform UI touchpoint:** Settings link in `resources/js/Layouts/AppLayout.vue` (hidden for `staff` / `customer` roles)

## Models

| Model | Table | Notes |
|-------|-------|-------|
| `CompanySetting` | `company_settings` | One row per team; `timezone`; `cancellation_notice_hours`; nullable `sms_gateway_email` (Email-to-SMS gateway) |
| `BusinessHour` | `business_hours` | One row per weekday `0–6`; `is_closed`; `opens_at` / `closes_at` |
| `CancellationFeeRule` | `cancellation_fee_rules` | `customer_type` residential\|commercial; `cutoff_hours`; `fee_type` percentage\|flat; `fee_value` |

Schema is normalised (3NF). Migration: `database/migrations/2026_08_12_140000_create_company_settings_and_portal_tables.php` (also adds portal link + appointment cancel columns owned by Customers/Jobs docs). Factories: `database/factories/Domains/CompanySettings/`.

### Fee rule matching

When a portal customer cancels with `hours_until` remaining until start:

1. **Hard block** if `hours_until < cancellation_notice_hours`.
2. Otherwise apply the fee rule for that customer type where `hours_until < cutoff_hours`, choosing the **smallest** matching `cutoff_hours`.
3. Percentage fees use an optional job value (currently `0` if not passed); flat fees use `fee_value` as currency amount.

Cutoffs should typically be **greater than** the notice window so a successful cancel can still incur a fee (e.g. notice 24h, fee within 48h).

### Defaults

`EnsureCompanySettings` creates settings (`Europe/London`, notice 24h) and Mon–Fri 09:00–17:00 / weekend closed when missing.

## Routes

### Web

| Method | URI | Name | Controller |
|--------|-----|------|------------|
| GET | `/company-settings` | `company-settings.edit` | Web\CompanySettingsController@edit |
| PUT | `/company-settings` | `company-settings.update` | Web\CompanySettingsController@update |
| PUT | `/company-settings/business-hours` | `company-settings.business-hours.sync` | Web\CompanySettingsController@syncHours |
| POST | `/company-settings/fee-rules` | `company-settings.fee-rules.store` | Web\CompanySettingsController@storeFeeRule |
| PUT | `/company-settings/fee-rules/{feeRule}` | `company-settings.fee-rules.update` | Web\CompanySettingsController@updateFeeRule |
| DELETE | `/company-settings/fee-rules/{feeRule}` | `company-settings.fee-rules.destroy` | Web\CompanySettingsController@destroyFeeRule |

### API

| Method | URI | Name | Controller |
|--------|-----|------|------------|
| GET | `/api/company-settings` | `api.company-settings.show` | Api\CompanySettingsController@show |
| PUT | `/api/company-settings` | `api.company-settings.update` | Api\CompanySettingsController@update |
| PUT | `/api/company-settings/business-hours` | `api.company-settings.business-hours.sync` | Api\CompanySettingsController@syncHours |
| POST | `/api/company-settings/fee-rules` | `api.company-settings.fee-rules.store` | Api\CompanySettingsController@storeFeeRule |
| PUT | `/api/company-settings/fee-rules/{feeRule}` | `api.company-settings.fee-rules.update` | Api\CompanySettingsController@updateFeeRule |
| DELETE | `/api/company-settings/fee-rules/{feeRule}` | `api.company-settings.fee-rules.destroy` | Api\CompanySettingsController@destroyFeeRule |

## Permissions

- `staff` and `customer` roles: no view/update
- Owners and members with `read`/`update` (admin/editor): manage settings

## Tests

- Path: `laravel/tests/Feature/Domains/CompanySettings/`
- Cover: settings update, business hours sync, fee rule CRUD, staff/customer forbidden

## Add / remove checklist

### Add

- [x] Domain folder + `DomainServiceProvider`
- [x] Provider registered
- [x] This wiki page linked from `docs/README.md`
- [x] Feature tests

### Remove

- [ ] Provider unregistered
- [ ] Domain folder deleted
- [ ] This page and index link removed
- [ ] Feature tests deleted
- [ ] Revert Settings nav in `AppLayout.vue`
- [ ] Jobs soft-consumers (`BusinessHoursGuard` / `CancellationPolicy`) become no-ops / remove DI
- [ ] Remaining suite still green
