313 Commits

Author SHA1 Message Date
Vasyka 9b97e101ff security: patch PHP2Shell RCE CVE-2026-109618 + CVE-2026-109619
Pin base image from floating dunglas/frankenphp:1-php8.4 to
dunglas/frankenphp:1.13.4-php8.4.27 which ships PHP 8.4.27.

CVE-2026-109618: OOM memory corruption → unauthenticated RCE (CVSS 9.2)
CVE-2026-109619: use-after-free between requests → unauthenticated RCE (CVSS 9.2)
Both affect PHP-FPM, mod_php, FrankenPHP, php-cgi. Exploited in the wild.
Fixed upstream in PHP 8.2.35, 8.3.36, 8.4.27, 8.5.12.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-10-27 09:17:22 +00:00
Vasyka 16fcdb41ed fix: phone uniqueness validation and DB constraint for users
- Replaced broken ->rules(fn (\$record)) with proper Filament ->unique()
  using ignoreRecord:true + modifyRuleUsing scoped to company_id,
  applied to both phone and email in MasterResource and UserResource
- Added migration 2026_09_26_000001 to add unique(company_id, phone)
  constraint on users table (nulls out duplicate phones before adding,
  MySQL allows multiple NULLs in unique index so blank phones are fine)
- Exception catch in Create/EditMaster and Create/EditUser now also
  handles UniqueConstraintViolationException on phone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-26 20:18:55 +00:00
Vasyka f1afcafa5d fix: duplicate email/phone shows notification instead of crashing
Two-layer protection on MasterResource and UserResource:
1. Form validation via ->rules() with Rule::unique() scoped to company_id
   (prevents DB hit in normal flow, shows inline field error)
2. handleRecordCreation/handleRecordUpdate catches
   UniqueConstraintViolationException as fallback — shows a Filament
   danger notification and halts without showing an error page

Also adds phone uniqueness validation (scoped per tenant).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-26 20:16:13 +00:00
Vasyka fcbb807462 fix: show friendly validation error for duplicate email on user/master create
Added ->unique(ignoreRecord: true) scoped to company_id on the email
field in both MasterResource and UserResource. Previously a DB-level
UniqueConstraintViolationException crashed the page; now Filament shows
"Acest email este deja folosit de un alt utilizator." inline on the field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-26 20:11:56 +00:00
Vasyka ab0b30af11 feat: inject cookie banner into both Filament panels via renderHook
Adds BODY_END renderHook in TenantPanelProvider and CentralPanelProvider
so the cookie consent banner appears on /app/* and /admin/* pages.
Alpine.js is already bundled by Livewire/Filament — no CDN needed.
Tenant panel uses tenant theme color; central panel uses indigo default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:46:45 +00:00
Vasyka c3e4b99d4b feat: complete cookie consent with categories & technical processing
- Full 4-category consent modal: Necesare (locked) / Funcționale /
  Analitice / Marketing with toggle switches per category
- Banner shows simple view first; "Personalizează" opens detailed panel
- Consent stored as JSON in localStorage (cookie_consent_v2)
- window.cookieConsent object set on load; dispatches
  cookie-consent-updated / cookie-analytics-granted / cookie-marketing-granted
  CustomEvents for deferred script loading
- Floating 🍪 FAB button lets users re-open settings at any time
- Policy page: "Personalizează" button resets consent, reopens modal
- Full RO/RU translation for all categories and UI strings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-17 20:39:13 +00:00
Vasyka 68f58cbcdc feat: cookie policy & consent banner per Moldova law 195/2024
- Settings page: new "Politica Cookie" section — toggle banner on/off,
  editable RO/RU policy text (defaults to full 14-section template)
- Public route GET /cookie-policy → CookiePolicyController returns
  locale-aware full policy page with tenant branding
- Cookie banner partial (Alpine.js, localStorage) shown on tracking and
  landing pages; Accept/Reject stores consent, banner dismisses
- Translation keys added to lang/ro/portal.php and lang/ru/portal.php
- Alpine.js (CDN) added to tracking and landing pages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-16 17:52:23 +00:00
Vasyka 753e81d292 fix: calendar board — 4 issues fixed
1. New form: Auto (vehicle) field first, client auto-filled via
   updatedNewApptVehicleId() when a vehicle is selected; getAllVehicleOptions()
   returns all vehicles as «plate — make model» for quick lookup

2. Scroll speed: JS intercepts wheel events on .cv-matrix-wrap and
   applies ×0.45 multiplier for horizontal scroll (shift+wheel or
   trackpad horizontal); scroll-behavior:smooth added to CSS

3. Event cards now show all 4 fields: 🔧 Lucrarea (title) +
   👤 Client + 🚗 Auto · plate + 🕐 Ora; each shown only if present

4. Delete bug fixed: replaced onclick=confirm() (races with wire:click)
   with wire:confirm= (Livewire native dialog — Cancel truly cancels)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 06:46:46 +00:00
Vasyka f885c04f90 fix: rework labor tree/list toggle — use Livewire wire:click not Alpine
Previous approach wrapped {{ $this->table }} in x-show which caused
Livewire/Alpine conflicts and broken toggling. New approach:
- viewMode is a Livewire #[Url] property (persisted in URL as ?view=)
- wire:click="setViewMode('list/tree')" triggers a clean Livewire render
- Only one view is rendered at a time (@if, not x-show)
- Tree: Alpine manages per-category open/close state client-side via
  indexed array; Open/Close all buttons use setAll() helper
- Visual overhaul: tighter cards, chevron arrows, proper hover states

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 05:08:53 +00:00
Vasyka 0180f29023 feat: tree/list view toggle for labor catalog
ListLabors now has a toggle button (persistat în localStorage) care
comută între:
- Listă: tabelul Filament grupat pe categorii (existent)
- Arbore: accordion cu toate categoriile ca noduri colapsabile,
  fiecare cu tabelul manoperelor sale (Manoperă, Mod, Ore, Preț,
  Cost, link Editează); butoane Deschide tot / Închide tot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 05:03:03 +00:00
Vasyka 5a3275c2cf fix: store opened_at and closed_at as DATETIME (not DATE)
DATE columns lose time information, causing timeline to show 00:00 for
car reception and WO closure. Migrated both columns to DATETIME and
updated: model casts (date → datetime), form pickers (DatePicker →
DateTimePicker with seconds:false), default to now() instead of today(),
and cleaned up defensive Carbon::parse() in getTimeline().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 04:52:03 +00:00
Vasyka ce9f082612 feat: per-row and global discount with margin validation
Parts (wo_parts) already had discount_pct; now also added to wo_works.
Labor catalog gets a cost_price field as the hard minimum sell floor.

- WorkOrderWork: discount_pct + cost_price fields; total calculation
  now includes discount: hours × price_per_hour × (1 − disc/100)
- Labor model: cost_price field (used to pre-fill work rows and validate)
- WorksRelationManager: discount_pct field with live helper text showing
  effective price + margin %; validation blocks save if below buy cost
  or below company min_discount_margin_pct; "Reducere globală" header
  action applies % to all work rows (skips rows that would breach limits)
- PartsRelationManager: same validation on existing discount_pct field;
  helper text (🟢/🟠/🔴) shows effective price + margin in real time;
  "Reducere globală" header action for all parts
- Settings page: new "Reduceri & limite preț" section with
  min_discount_margin_pct — 0 = no limit, >0 = hard block below that %
- LaborResource form: cost_price field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-11 04:48:15 +00:00
Vasyka c66b895948 feat: photo lightbox with prev/next navigation on dashboard
Replace link-to-new-tab with Alpine.js lightbox overlay. Clicking a
thumbnail opens a full-screen dark overlay with the full-size image,
prev/next arrows (keyboard ←/→ supported), counter, and close button
(ESC or ✕ or click outside). No external libraries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 12:21:37 +00:00
Vasyka 8eb9d43525 fix: remove redundant Achitat field when pay_status != partial
The read-only Achitat (MDL) display is unnecessary — the Total field
already shows the amount. Only show the editable Sumă achitată field
when partial is selected; rest de achitat remains always visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 12:16:44 +00:00
Vasyka 2e5955beea fix: serve storage files directly via Caddy route (bypass symlink 404)
FrankenPHP/Caddy does not reliably follow the public/storage symlink
when serving static files, causing 404 on all media URLs (/storage/…).
Add CADDY_SERVER_EXTRA_DIRECTIVES with an explicit handle_path block
that maps /storage/* directly to /app/storage/app/public, avoiding the
symlink entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 08:30:50 +00:00
Vasyka 1e7ca0fdfe feat: partial payment field + photo upload from dashboard
- WorkOrderResource: pay_status is now live(); added _partial_amount
  TextInput (visible only when pay_status=partial) that shows current
  paid amount and accepts a new total — dehydrated so it does not save
  directly; _achitat hidden when partial is selected to avoid confusion
- EditWorkOrder.afterSave: creates a Payment record for the difference
  when pay_status=partial and entered amount > existing paidAmount
- WorkOrderDashboard: WithFileUploads + uploadPhotos / deletePhoto
  methods using Spatie MediaLibrary
- Dashboard blade: Foto tab replaced with upload form + gallery with
  per-photo delete buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 08:16:21 +00:00
Vasyka aa231f4b08 Add Achitat + Rest de achitat display fields in payment section
Shows the financial breakdown inline in the Plată & total section:
- Total (MDL) — bold, computed
- Achitat (MDL) — green, from paidAmount()
- Rest de achitat (MDL) — red if unpaid, green if fully paid

All three fields are readOnly/dehydrated (display only). To register
payments, use the Plăți relation manager tab.
Translations added for RO/RU/EN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:56:36 +00:00
Vasyka 1215bc8be2 Fix total readOnly, label pay_status/discount, add RU/EN translations
- pay_status: add label Statut plată (was showing English auto-label)
- discount_pct: rename label to Reducere % (standard RO terminology)
- total: disabled() → readOnly() so field looks active (not grayed out)
  while still preventing manual editing; $set() from discount live works
- Add ru/en translations: Statut plată, Reducere %

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:43:51 +00:00
Vasyka 116495ce0b Fix payment amount editable + discount live recalculation
- PaymentsRelationManager: remove broken default() closure on amount;
  use fillForm() on CreateAction instead (correct Filament API) so the
  modal pre-fills with remaining balance (total - paid) but field stays
  fully editable; add minValue/step for numeric UX
- WorkOrderResource: discount_pct is now live(onBlur) with afterStateUpdated
  that recomputes works+parts+subcontract minus discount and sets the
  total field immediately — no Save required to see the effect

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:41:02 +00:00
Vasyka 7c0c728347 PDF: remove part codes; payments: pre-fill amount with balance due
- PDF parts table: removed Cod/article column, widened Denumire column,
  fixed colspan in tfoot from 4 to 3
- PaymentsRelationManager: amount field now defaults to (total - paidAmount)
  so it pre-fills with the remaining balance; always editable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:23:06 +00:00
Vasyka b0433b66c7 Recalculate WO total after saving discount_pct
afterSave() calls recalcTotal() so discount_pct changes are applied
to the total immediately on save, then fillForm() refreshes the
displayed total field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:12:37 +00:00
Vasyka 5610282078 Fix sort_order: sequential 1,2,3 + renormalize after every save
- saving: always shift >= new position by +1 (no conflict check needed)
- saved: renormalize all records to 1, 2, 3... via direct DB update
  (no model events triggered) to close any gaps left by the move
- Migration updated to use step 1 instead of 10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 06:59:14 +00:00
Vasyka 2e082d4aad Normalize labor category sort_order + fix increment step to 10
Migration renumbers all categories per company as 10, 20, 30… to
eliminate existing duplicates. Observer now increments by 10 instead
of 1 to leave headroom between positions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 06:53:10 +00:00
Vasyka 04cab25ac5 Shift labor category sort_order on conflict when saving
When a category is saved with a sort_order already occupied by another
record in the same company, all records at that position and above are
incremented by 1 before the save, inserting the new one cleanly at the
requested position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 06:45:50 +00:00
Vasyka b7653f600e Disable pagination on labor catalog so all categories are visible
With collapsedGroupsByDefault() the 386 rows are fetched but not
rendered in DOM until expanded — no perf issue, all 17 group headers
now visible without needing to navigate pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:59:03 +00:00
Vasyka 2341dd846b Collapse all labor groups by default — use correct Table method
collapsedGroupsByDefault() is on Table, not Group::collapsedByDefault()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:47:26 +00:00
Vasyka f987071a6d Collapse all labor groups by default on page load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:23:42 +00:00
Vasyka 7ed60b1dbf Restructure catalog to root categories + collapsible groups
- Migration: replace 45 leaf categories (diag.scan, engine.mech…) with
  17 root categories (diag, engine…); update labors.category by taking
  the prefix before the first dot; delete the leaf LaborCategory rows
- ImportLaborCatalogCommand: rewritten to use root categories directly
  so future imports go to the right level from the start
- LaborResource: add ->collapsible() to groups so user can collapse/
  expand each category section to see only headers or only one group
- entrypoint.sh: remove one-time catalog import (was already done)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:17:14 +00:00
Vasyka cbf8deabf3 Fix labor table grouping colspan + auto-import catalog on deploy
- LaborResource: hide category column from data rows by default
  (toggleable) so group headers span all columns cleanly; the group
  header already shows the localized category name
- ImportLaborCatalogCommand: add --all flag to iterate every company,
  refactor into importForCompany() helper, keep idempotent (skips
  existing codes, updates only with --force)
- entrypoint.sh: run catalog:import-labors --all after each deploy so
  all 386 operations + 45 categories are seeded automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:07:32 +00:00
Vasyka 5e923459f7 Three features: labor group headers, status picker, catalog import
- LaborResource: add titlePrefixedWithLabel(false) so group headers show
  just the localized category name (not "Categorie: Шины"); remove static
  cache from closure to avoid Octane cross-request locale bleed
- WorkOrderDashboard: add changeStatus() Livewire method + Alpine.js
  dropdown on the status badge so status can be changed directly from
  the dashboard without going to the full edit form
- New artisan command catalog:import-labors {company} that reads
  database/data/labor-catalog.json (386 operations, 45 leaf categories)
  and upserts labor_categories + labors for the given tenant; supports
  --force to update existing records by code
- Add "Status actualizat" translation to ru/en JSON

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 04:38:35 +00:00
Vasyka 06fac16026 Fix labor catalog group headers to show localized category names
Defines a proper Tables\Grouping\Group with getTitleFromRecordUsing so
group headers use the locale-aware label from LaborCategory::optionsForSelect()
instead of the raw DB value with Filament's hardcoded "Category:" prefix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 13:07:34 +00:00
Vasyka 116a53d81d Localize category column and filter label in LaborResource table
formatStateUsing looks up localized category name from LaborCategory
so 'Anvelope' shows as 'Шины' (ru) / 'Tires' (en). Filter label
uses __('Categorie') to avoid the auto-derived 'Category' (English).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:56:23 +00:00
Vasyka 9ee17fa918 Fix LaborCategoryResource: use Schemas\Components\Section + add translations
- Section was Filament\Schemas\Components\Section not Forms\Components\Section
- Added ru/en translations for page title, form labels, nav label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:46:03 +00:00
Vasyka 1b34a3c217 Add multilingual support to labor categories (RO/RU/EN)
- Migration: adds name_ro, name_ru, name_en columns; copies name→name_ro;
  seeds RU and EN translations for all 11 default categories
- LaborCategory: label() returns localized name, optionsForSelect() keyed
  by canonical name (stored in labors.category) with localized label
- LaborCategoryResource: 3-column form with RO/RU/EN inputs; table shows
  all three columns side by side
- LaborResource: category select and filter use optionsForSelect()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:37:39 +00:00
Vasyka 818ae29b20 Fix LaborCategoryResource: remove unavailable BulkActionGroup class
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:29:53 +00:00
Vasyka a2ddea2529 Add LaborCategory resource — manage labor categories from UI
- Creates labor_categories table (company-scoped) seeded with 11 defaults
- LaborCategory model with BelongsToTenant
- Full Filament CRUD resource with drag-to-reorder (sort_order)
- LaborResource category Select now reads from DB instead of hardcoded const
- Navigation: 'Categorii manopere' in Service group, sort 33

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:25:19 +00:00
Vasyka 1cf1dc2f54 Fix LaborResource breadcrumb/heading to show 'Catalog manopere'
getPluralModelLabel now uses nav.label.Norme-ore (already translated
to 'Catalog manopere'/'Каталог работ') so breadcrumb and page heading
match the sidebar navigation label.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:15:49 +00:00
Vasyka aba1fcc372 Fix client/vehicle inline creation and labor catalog visibility
- Migration: make clients.phone nullable (was NOT NULL, broke inline create)
- Vehicle createOptionUsing: use Get callback instead of $livewire->data
  to reliably get client_id in the vehicle inline form
- Remove plate->required() on inline vehicle form (not required in DB)
- Rename 'Norme-ore' → 'Catalog manopere'/'Каталог работ' in navigation
  so users can find the labor catalog easily

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:08:14 +00:00
Vasyka 6848e58f14 Translate PDF footer 'Document generat automat' for ru/en locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:31:55 +00:00
Vasyka 221e311c08 Fix subcontract jobs not appearing in PDF
Load subcontractJobs via explicit query (bypassing TenantScope but
scoped manually by work_order_id + company_id) to avoid relation
caching issues. Pass as separate blade variable \$subcontractJobs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:29:55 +00:00
Vasyka 2caac4ae8e Add subcontract jobs section to work order PDF
Loads subcontractJobs.subcontractor relation and renders a new table
in the PDF after parts: description, subcontractor, category, client price.
Adds ru/en translations for the new section labels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:24:59 +00:00
Vasyka 4c7cacc765 Fix PDF filename double WO prefix, title translation, view cache clear
- WorkOrderPdfService::filename() removed duplicate 'WO-' prefix
  ($wo->number already starts with 'WO-')
- PDF <title> now uses __('FIȘĂ DE LUCRU') so it shows in user locale
- entrypoint: view:clear before view:cache to avoid stale compiled views

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:19:59 +00:00
Vasyka 9df834ffa1 Fix PDF locale and missing translations for ru/en
- Set app locale from user preference in PDF route before rendering
- Wrap hardcoded strings (TOTAL, Subtotal, Achitat etc.) with __()
- Translate status values via __() in blade
- Add ru/en translations: statuses, totals, payment lines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:14:54 +00:00
Vasyka a556282581 Add missing PDF translations for Russian and English
Strings used in work-order PDF (Nr., Nume:, Telefon:, Marca/Model:,
Data deschiderii:, Km la intrare:, subtotals) were falling back to
Romanian in ru/en locale. Added all missing keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:09:28 +00:00
Vasyka 63ef482514 Fix Romanian diacritics (ș ț) and Cyrillic in DomPDF-generated PDFs
DejaVu Sans lacks glyphs for U+0219 (ș) and U+021B (ț). Fix: render
view to HTML first, replace with cedilla variants (U+015F/U+0163) that
DejaVu Sans supports, then load HTML. Set default_font to DejaVu Sans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:03:24 +00:00
Vasyka 8a359f4440 Fix Romanian diacritics (ș ț) and Cyrillic in DomPDF-generated PDFs
DejaVu Sans lacks glyphs for U+0219 (ș) and U+021B (ț). Fix: render
view to HTML first, replace with cedilla variants (U+015F/U+0163) that
DejaVu Sans supports, then load HTML. Set default_font to DejaVu Sans.
Creates storage/fonts for DomPDF font cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:03:15 +00:00
Vasyka 9be4aab7e5 Refactor WO diagnostic: move all DB queries inside modal, add try/catch
Queries now run only when modal is opened — no risk of page-load
exception hiding the button. Button is always visible for admin/owner.
Adds restore action inside modal footer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:44:12 +00:00
Vasyka 24bdc36913 Expand diagnostic modal to show all companies with WO counts
Helps identify cross-tenant WO leakage — shows each company's slug,
active count, deleted count, and whether it's the current tenant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:39:11 +00:00
Vasyka 5fa55f4bff Add restore button for soft-deleted WOs in admin diagnostic
Soft-deleted WOs (WO-26-0005..0008) are missing from admin view.
A new restore action lets the admin recover them without CLI access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:32:14 +00:00
Vasyka 8e32444a13 Fix diagnostic button visibility for admin users
Replace isAdmin() (Spatie-dependent) with direct role check and add
TenantManager fallback using hostname subdomain so the button appears
even if TenantManager was not resolved for the current request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:22:53 +00:00