# New features: admin and setup

Settings, Backups, Users, My account, the five configuration lists (departments, stages,
product types, units, vendors), the licence dashboard, sign in, error pages and the shared
empty state. Everything below did not exist before this redesign. Automated tests are in
`tests/Feature/AdminRedesignTest.php` (21 tests); "visual" means checked in light and dark at
1366×768 and 390×844 on the 2-year test data.

| # | Feature | Where | Why (audit ID) | How it was tested |
|---|---------|-------|----------------|-------------------|
| 1 | Restore from a backup: `php artisan app:restore {id or file} [--force] [--by=user]`. Checks the zip and checksum, takes a "Before restore" backup, maintenance mode on, replays `database.sql` through PDO (no `mysql` binary), runs newer migrations, keeps the backup list and open sessions, empties caches and queued jobs, swaps `storage/app/private` for the zip's `files/`, maintenance off, logs who restored what (activity log, laravel.log, "Restored … by …" on the backup row). If anything fails, the safety backup is replayed automatically. | `app/Console/Commands/RestoreCommand.php`, `app/Support/RestoreService.php`, `app/Support/SqlStatementReader.php` | BAK-7 | Pest (SQLite): restore brings back data and files, list survives with the safety backup, restoring the safety backup undoes it; damaged/failed backups refused. Manually on MySQL/MariaDB in throwaway databases (`restore_test`, and a private copy of the 2-year data), never on `workflowmgmt`. |
| 2 | "Restore…" in each backup's ⋮ menu, owner only, type the company name to confirm | Backups page | BAK-7 | Pest: hidden for a manager with backup access; wrong company name refused. Visual. |
| 3 | "Delete" in the backup ⋮ menu (removes file and row) | Backups page | BAK-7 | Pest |
| 4 | Health line: last nightly backup with size, next run, red "Nightly backup did not run" after 26 h or "Last nightly backup failed" | Backups page; `App\Support\BackupHealth::check()` for the dashboard | BAK-2 | Pest (on time, overdue, failed). Visual. |
| 5 | New retention: nightly 7 daily / 4 weekly / 12 monthly; manual, before-update and before-restore backups kept apart (newest 5 of each); failed attempts kept 30 days | `App\Support\BackupService::idsToKeep()` / `prune()` | BAK-2, BAK-8 | Pest: 400 days of nightly backups + manual + release + failures; real prune with files. |
| 6 | Backup kinds and who made it: `backups.kind` (nightly, manual, release, pre_restore), `created_by`, `restored_at`, `restored_by`; `app:backup --kind=nightly|manual|release` (default nightly = the scheduler) | migration `2026_11_02_000300_admin_redesign_columns.php` | BAK-3 | Pest |
| 7 | Backup dump now writes column names in each INSERT, so a restore still works after later migrations add columns | `BackupService::dumpRows()` | BAK-7 | Pest + MySQL restore |
| 8 | Backup list: "Nightly / Manual by name / Before update / Before restore", age ("· 9 h ago"), grey tick for OK, red "Failed" with reason, "Backing up…" spinner, retention rule and restore steps beside the table | Backups page | BAK-3…BAK-6, BAK-8 | Visual |
| 9 | Next order number preview ("Next order number: SC-06255 · Affects new orders only"), live | Settings | SET-4 | Pest |
| 10 | One Save at the bottom, disabled until something changes, "Unsaved changes", Ctrl+S | Settings | SET-3 | Pest |
| 11 | Auto-assign mode as three option cards; unit suffixes (days, min, sec) in narrow fields; Advanced section (collapsed) with the refresh interval | Settings | SET-5…SET-8 | Pest (save), visual |
| 11c | Orders section: "Default lead time" (`default_lead_days`, integer days, default 14: new orders' due date = received + this) and "Due date is required" (`require_due_date`, default off); both defaults added to `Setting::DEFAULTS` | Settings → Orders | SET-9 (coordinator request) | Pest |
| 11b | Company details for printouts: address (textarea), GSTIN (15 characters, checked, saved upper-case), phone, logo (image upload on the public disk, relative path saved) as settings `company_address`, `company_gstin`, `company_phone`, `company_logo` | Settings → Company | PRINT-1 (coordinator request) | Pest (bad GSTIN refused, upper-casing, logo stored) |
| 11a | Calendar section with "Weekly off" as seven day toggle buttons; saved as setting `weekly_off_days` (array of weekday numbers, 0 = Sunday; default [0]) for the attendance / floor screens | Settings | SET-9 (coordinator request) | Pest |
| 12 | "Delete…" that counts what refers to the record and offers Deactivate instead; Deactivate / Activate in every row menu | Stages, product types, departments, units, vendors, users (`App\Support\SafeDelete::action()`, `App\Support\Usage`) | Brief: block deleting things history refers to; STG-11 | Pest: used stage, unit, department, user deactivated; unused unit and vendor deleted; own account can't be deleted |
| 13 | Users list tabs All · Office · Supervisors · Workers · Inactive with counts; inactive rows greyed with badge; working hours column ("09:30–18:30 Mon–Sat" or "Any"); "Never" for no login, exact time on hover | Users | USR-2, USR-3, USR-4, USR-6 | Pest (tabs, hours label), visual |
| 14 | User edit: title = person, role and last login underneath; Lock / Unlock / Sign out now / Deactivate as grey header buttons; one-column layout; working days as Mon–Sun toggle buttons | Users → edit | USR-9, USR-10, USR-11 | Pest (save days), visual |
| 15 | Permissions: summary "Role: Worker + 1 extra"; role permissions shown ticked and disabled, extras as checkboxes; role permissions are no longer copied onto the person when saving | Users → edit | USR-13 | Pest |
| 16 | My account page for everyone: name, language, theme (Light / Dark / Auto), change own password or PIN (current one required) | `/my-account` (`App\Filament\Pages\MyAccount`) | SHELL-11 | Pest (wrong current PIN refused, PIN, password, name, language) |
| 17 | Stages list: drag to reorder, variant names, "Used in" (product types, names on hover), grey ticks for Counts qty / Skippable, behaviour as text with icon | Stages | STG-1…STG-6 | Visual; ScreensTest |
| 18 | Stage edit: name as title with behaviour and variant count; behaviour option cards; floor toggles in one row; variants as a full-width table; only one default (radio-like, enforced on save); a used variant is made inactive instead of removed, with the count | Stages → edit | STG-7…STG-12 | Pest (Livewire: remove used variant, default switch, save), visual |
| 19 | Product types list: route ("Casting → Polishing → … (8)", full route on hover), Designs links to the filtered designs list, Version hidden by default, unit names | Product types | PT-2, PT-6, PT-8 | Pest, visual |
| 20 | Product type edit: details in one row, steps as a compact table with step numbers, version in the subtitle and "Saving creates version N+1…" note, suggested short code on create | Product types → edit | PT-3, PT-4, PT-5, PT-7 | Pest, visual |
| 21 | Departments: Supervisor, Members (links to filtered users), Stages, Open work (links to the department's queue), 8+ days (red when above 0), drag to reorder | Departments | DEPT-2, DEPT-3, DEPT-4 | Pest, visual |
| 22 | Units: "Equals" (e.g. 144 pcs) field and column (`units.equals_qty`, `units.equals_unit_id`), "Shown as" with an example (0.000 kg) | Units | UNIT-2, UNIT-3 | Pest |
| 23 | Vendors: tap-to-call phone, "Works on" (linked stage variants), Out now, Overdue (red above 0), Avg days out; no footer when one page | Vendors | VEN-1…VEN-5 | Pest, visual |
| 24 | Configuration lists share one look: codes as grey text, no Active column (inactive rows greyed), counts right-aligned, grey ⋮ menu, search only from 15 rows, sentence-case titles | `App\Support\ListUi` | DEPT-1, DEPT-5, DEPT-6, UNIT-1, UNIT-4, PT-1 | Visual |
| 25 | Licence dashboard: Status with the reason (`license_clients.status_changed_at/by`), "Paid until … · 370 d left" (amber/red), summary tabs Active · Due soon · In grace · Locked · Not checked in for 24 h, address link or "No address set", version amber when older than the server's, grey Record payment, filter badge only when active, no breadcrumb | Licence dashboard | LICD-2…LICD-8 | Pest (reasons, days left, outdated, summary) |
| 26 | Sign in: company name (or logo) with "Sign in to continue", no required stars, "Forgot your PIN? Ask your manager to reset it.", English · हिन्दी switch before signing in, 44 px fields on phones | `/login` | LOGIN-1…LOGIN-6 | Pest |
| 27 | Branded error pages 403, 404, 419, 500, 503 that follow the saved theme; Dashboard or Work queue by role, Go back; 404 offers "Search for SC-06002" for order/design-like addresses; 419 "Your session ended" with Sign in that returns to the page; 503 retries by itself | `resources/views/errors/*` | ERR-1…ERR-4 | Pest (404, 403 by role, 419, 500, 503), visual |
| 28 | Reusable empty state `<x-empty-state message hint icon compact>` with a slot for the actions | `resources/views/components/empty-state.blade.php` | EMPTY-1 | Pest; used on Backups |
| 29 | Backups on phones: one stacked row per backup (name, date · age · size, failure reason, ⋮ menu), no sideways scroll; desktop table aligned to its card | `resources/views/filament/pages/backups.blade.php` | QA Z-1 | Visual 390/1366 light+dark (scrollWidth 390) |
| 30 | Tests run maintenance mode in memory (cache driver, array store), so a restore test can never put the live app into 503 | `phpunit.xml` | QA Z-2 | Pest: driver is CacheBasedMaintenanceMode, no `storage/framework/down` after restore / full suite |
| 31 | App-wide list rows 41 px (row-actions cell slimmed); list tabs styled like Orders (left, underline, grey counts, colour only when > 0); placeholders and grey text ≥ 4.5:1; 44 px phone touch targets; page titles left on phones; neutral multi-select chips; toggle buttons brand colour ≥ 4.5:1; table-repeater headers aligned; forms left-aligned ≤ 960 px | `resources/css/app.css`, `resources/js/app.js` | QA Z-3/4/5/6/9/10/11/15 | Visual (row heights measured 41 px on Users/Customers/Units/Orders) |
| 32 | User menu shows role and departments under the name; theme switch labelled Light / Dark / Auto; divider before Sign out; phone search placeholder "Search" without Ctrl+K; phone drawer has a 44 px close button and a name/role/Sign out block; menu keeps the current page in view; a menu group with one item has no header | `AppPanelProvider`, `resources/views/filament/partials/{user-identity,drawer-close,drawer-user,ui-strings}.blade.php`, `app.js`, `app.css` | QA Z-7 | Pest (rendered markup) + visual 390/1366 |
| 33 | Licence panel loads the app stylesheet (compact header, Orders-style tabs); Status reason line no longer repeats "Locked" when no reason is known | `LicensingPanelProvider`, `LicenseClientResource::statusReason` | QA Z-8 | Pest (existing reason test) + visual |
| 34 | Stage variants: a variant in use has no bin, it gets a "Make inactive" button instead; same labels on phones as desktop headers | `StageResource` | QA Z-10 | Pest: delete hidden / Make inactive visible and deactivates |
| 35 | Vendors "Avg days out" shows "N so far" (average age of open send-outs) when nothing has come back yet; send-outs count in normal numerals | `VendorResource::workload/avgDaysLabel` | QA Z-12 | Pest |
| 36 | Data migration: Dozens = 12 pcs, Gross (144) = 144 pcs, any "Name (N)" unit = N pcs, only where Equals was empty | `database/migrations/2026_11_01_120000_derive_unit_equivalents.php` | QA Z-13 | Pest; run on the shared test DB |
| 37 | 404 for /orders/<id> offers "Search all orders, closed ones too" and "Orders list" (designs likewise) | `resources/views/errors/404.blade.php` | QA Z-14 | Pest |
| 38 | Departments: "Reorder" is a labelled button; Product types "Version" column always shown (no lone column-manager icon); Vendors no toggleable column | `DepartmentResource`, `ProductTypeResource`, `VendorResource` | QA Z-11 | Visual |
| 39 | Settings logo drop zone reads "Drop a logo here or browse (PNG/JPG, 2 MB)" | `Settings` page | QA Z-9 | Visual |
| 40 | No Ctrl+D shortcut on any Delete action (global DeleteAction default; licence payments row Delete explicitly) | `AppServiceProvider`, `PaymentsRelationManager` | QA Z (coordinator) | Pest: DeleteAction has no key bindings |
