Commit Graph

299 Commits

Author SHA1 Message Date
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
Vasyka 0efe2e0bff Add diagnostic button to WO list for admins to inspect DB state
Shows total WO count broken down by tenant/other-company/deleted across
all records without global scopes, helping debug missing WO visibility.
Also adds a recover action when orphaned WOs are found in wrong company.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:20:23 +00:00
Vasyka 416b379d27 feat: add one-click orphaned WO recovery button for admins
Instead of relying on terminal/artisan commands, admins see a warning
button on the WO list when orphaned WOs exist (wrong company_id due to
Octane singleton bug). One click moves them all to the current tenant.
Button only shows if there are orphaned WOs and user is admin/owner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:11:45 +00:00
Vasyka 6e18da755d fix(data): v2 WO company_id fix — also covers test companies with 1-2 users
Previous migration skipped companies with ≥1 user. Now moves WOs from
any company whose slug/name contains 'test' or has ≤2 users, which
covers the single test account scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:06:28 +00:00
Vasyka 2323f61d43 fix(data): migrate WOs from ghost tenants into psauto company
One-time data migration that moves work orders created with wrong
company_id (Octane singleton race condition) back into psauto.
Only moves WOs from companies that have zero users (ghost/test tenants),
so legitimate separate companies are not affected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:02:39 +00:00
Vasyka c2f87a468f fix: TenantManager scoped() + explicit company_id in WO create
FrankenPHP/Octane reuses the same PHP process for concurrent requests.
TenantManager registered as singleton() was shared between requests —
a concurrent request from another tenant could overwrite the current
tenant context mid-flight, causing records to be saved with the wrong
company_id.

Fix 1: Change singleton() to scoped() so Octane flushes TenantManager
at the end of each request (fresh instance per request lifecycle).

Fix 2: Explicitly set company_id in mutateFormDataBeforeCreate() as an
extra guard — even if the creating event fires in the wrong context,
the value from the request's own TenantManager call takes precedence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:49:18 +00:00
Vasyka c359c66827 chore: add tenant:fix-wo-company command to reassign WOs to correct tenant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:45:32 +00:00
Vasyka ebc4fe0f23 fix: use role column directly in WO query — drop isMechanic() Spatie dep
isMechanic() calls hasAnyRole(['mechanic']) via Spatie which checks the
model_has_roles table with team_id scoping. If roles are not correctly
seeded for a tenant, admins/owners could accidentally pass the mechanic
filter and see only WOs where master_id = their own id (i.e. nothing).

Now uses $user->role === 'mechanic' directly — no Spatie involved —
so admin/owner/manager always see all WOs in their tenant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:40:19 +00:00
Vasyka 7b1d296bd4 chore: add tenant:diagnose artisan command for production DB debugging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:33:38 +00:00
Vasyka 4a896118b9 fix: simplify WO visibility — only mechanics are restricted to own WOs
Previous implementation relied on Spatie canDo() for all roles, which
caused admins/managers to see nothing if permissions weren't seeded.
Now: mechanics see only their assigned WOs (master_id = self); every
other role (admin, manager, receptionist, etc.) sees all WOs in the
tenant — TenantScope already prevents cross-tenant access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:25:55 +00:00
Vasyka a618f0af32 feat: implement per-role WO visibility via getEloquentQuery
Admin/owner and users with work_orders.view_all see all work orders.
Users with only work_orders.view_own_assigned (mechanics) see only
work orders where master_id = their own user ID.
Roles without either permission see nothing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:16:55 +00:00
Vasyka 0af8f2a755 fix: revert client_id to options() — relationship() broke initial list loading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-04 20:09:44 +00:00
Vasyka 3c7e5fffbf improve WO form: inline client/vehicle create, mechanic filter, category translation, PDF emoji fix
- client_id field: switched to relationship() with createOptionForm so new clients
  can be created inline (name, phone, email); search also matches by phone
- vehicle_id field: added createOptionForm (make, model, plate, year, VIN); newly
  created vehicle is automatically linked to the currently selected client
- master_id field: now shows only active users with role mechanic/master
- WorksRelationManager: labor catalog select marked optional; added createOptionForm
  so new catalog entries can be added without leaving the WO form
- SubcontractJobsRelationManager: category table column now translates value via __()
  and shows explicit label(__('Categorie'))
- PDF work-order: replaced 📞 / ✉ / ⚠ emojis with plain-text equivalents so
  DomPDF/DejaVu Sans renders them correctly instead of showing "????"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-04 19:56:40 +00:00
Vasyka 2ca6ab58a1 feat(nav): FAQ shows all 8 sections + drag-drop reorder in Settings
FAQ sections were only 4 (service/crm/depozit/finante) even though
the sidebar has 8. Added the missing 4 to Hints::AREAS with proper
icons + accent colors:
  📊 Analiză (pink)   📣 Marketing (orange)
  🛒 Magazin (cyan)   ⚙️ Admin (slate)

Retagged 15 hints that were shoved into wrong areas as a workaround
(admin.*/marketing.*/shop.*/analytics.* now use the matching bucket).
FAQ blade reads accent color from the AREAS registry instead of a
hardcoded local map.

Also added per-tenant navigation group ordering:
- Settings → new "Ordine meniu lateral" section with a Filament
  Repeater that has reorderable buttons (drag/arrow buttons) but no
  add/delete (fixed list of 8 groups)
- Persisted to settings.nav_group_order as an array of keys
- New ConfigureNavGroups middleware (runs after ResolveTenant on
  the web stack) reads the current tenant's order and calls
  $panel->navigationGroups([...]) so the sidebar renders in that
  order per-request

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 11:09:24 +00:00
Vasyka 346fcf968f fix(faq): toggle worked server-side but click did nothing — nested forms
Filament pages wrap their content in an outer <form>. My inline
<form method="POST"> for the hint toggle + reset was nested inside
that outer form — invalid HTML, so browsers dropped my submit and
either did nothing or ran the outer Livewire form's handler.

Replaced both forms with plain <button type="button"> + onclick
fetch() POST + window.location.reload(). Controller now also returns
JSON when Accept: application/json (fetch sends that) instead of a
back() redirect.

The server-side flip was always correct — verified via a Feature
test that posts to /app/hints/toggle and asserts the user column
flipped from true → false.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 11:04:24 +00:00