Commit Graph

104 Commits

Author SHA1 Message Date
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 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 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 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 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 29e5481fc3 refactor(nav): collapse single-item groups + fix ungrouped CRM resources
Previously nav had 12 groups, several with just 1 item. Now 8 clean
groups, no orphans:

- Anvelope (1 item) → merged into Service (Seturi anvelope)
- Stoc & Finanțe (1 item) → merged into Depozit (Import factură Excel)
- Tinichigerie (1 item) → merged into Service (Tinichigerie / Detailing)
- Subcontractare (2 items) → merged into Service (Lucrări terți,
  Subcontractori)
- Integrări moved from Marketing → Admin (it's a config concern, not
  a marketing feature)
- ClientResource + VehicleResource were ungrouped → added to CRM

Final groups:
  Admin(8) · Analiză(4) · CRM(5) · Depozit(10) · Finanțe(6) ·
  Magazin(2) · Marketing(3) · Service(11)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-10 19:29:03 +00:00
Vasyka 1b8ccb116f feat(work-orders): dashboard becomes single hub — list/create/edit funnel through it
- WO list row click now opens /work-orders/{id}/dashboard instead of
  /edit (via ->recordUrl on the table). Edit remains reachable from
  the per-row Filament EditAction.
- CreateWorkOrder redirects to the dashboard after save instead of
  the edit page.
- Dashboard top bar exposes: "Listă" (back to WO list), "+ Nou"
  (create), and "Editare completă" (full edit form) so users don't
  have to hop through the sidebar to move between related WO screens.

The old /work-orders, /create, and /{id}/edit routes stay intact —
just the default navigation flow now converges on the dashboard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-10 19:20:36 +00:00
Vasyka 517d32c4d4 feat(nav): single Calendar menu — hide Appointments resource from sidebar
AppointmentResource::\$shouldRegisterNavigation = false so only
CalendarBoard shows in the sidebar as "Calendar". The board already
has a "Listă" view-mode button alongside Zi/Săpt/Lună/Custom, so both
use cases are covered from a single entry. CRUD routes
(/app/appointments/create, /{id}/edit) stay accessible from the
board's click handlers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-10 19:04:54 +00:00
Vasyka c9d400fd6e feat(pdf): preview in Filament modal (iframe) instead of new tab
All "Vizualizare PDF" actions now open a wide (7xl) Filament modal
containing an iframe pointing at the inline PDF endpoint, with a
top-right "Descarcă" and "Deschide în tab nou" fallback.

- WorkOrder edit action + WO table row action
- InjectorProtocol edit action + protocols table row action
- Dashboard Docs tab preview button (Alpine.js overlay since it's
  a plain Blade view, not a Filament action)

The underlying /pdf routes stay unchanged (Content-Disposition: inline,
?download=1 for attachment) — the iframe just loads the same URL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-06 05:47:07 +00:00
Vasyka e289fbc068 feat(vehicle): dedicated photo upload with MediaLibrary + form field
Vehicle model now implements HasMedia + InteractsWithMedia and
registers a 'photos' collection. VehicleResource form gets a
SpatieMediaLibraryFileUpload section (collapsible, max 6 photos,
imageEditor enabled).

Dashboard already checks vehicle->getFirstMediaUrl('photos') first
and falls back to the WO's photos, so uploading here immediately
populates the vehicle card on the dashboard without any view changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-06 05:41:09 +00:00
Vasyka 5a5f43f2de feat(pdf): inline preview by default with ?download=1 override + vehicle photo fallback
- WorkOrder and InjectorProtocol PDF routes now respond with
  Content-Disposition: inline (opens in browser tab so the user can
  read/print/save from the built-in PDF viewer). ?download=1 forces
  the classic attachment download.
- CalendarBoard exportPdf() switched to the same inline default.
- WO edit action + WO table row action + injector protocol actions
  now use ->url(...)->openUrlInNewTab() instead of streaming the PDF
  inline into the current tab.
- Dashboard Docs tab has both a preview link and a "Descarcă" link.
- Vehicle card photo falls back to WO's first uploaded photo when
  Vehicle model has no MediaLibrary integration of its own.

Test CalendarEnhancementsTest updated to assert the new inline
response headers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:56:24 +00:00
Vasyka f82a3ebc57 fix(work-order-dashboard): rename route param to {wo} to bypass model binding
Root cause of the "Fișa #0" bug: the page had
    public ?WorkOrder \$record = null;
and the route slug was work-orders/{record}/dashboard. Livewire/Laravel
saw the {record} param and the typed \$record property with the same
name and attempted route model binding via BelongsToTenant scope. When
that resolution didn't return a Model instance in a Livewire hydration
context, mount() ended up being called with 0, so the button worked
but the destination page redirected saying "Fișa #0".

Fix: rename the route parameter to {wo} so it no longer collides with
the property name, receive it as int|string in mount(), and do the
find() ourselves. The property stays as \$record for the Blade view
but is now always populated by our own code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:27:38 +00:00
Vasyka fbb0bcb7e1 fix(edit-wo): pass panel='tenant' to Page::getUrl()
Without an explicit panel, WorkOrderDashboard::getUrl() resolves the
route name against Filament's current-or-default panel, which in some
Livewire lifecycles or from ambiguous contexts is 'central' — where
the page isn't registered. Symptom: the produced URL ended up empty
or malformed, and clicking the button hit our friendly redirect
claiming WO #0 doesn't exist.

Also removes the temporary /__wo-diag/{id} route added for diagnosis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:19:19 +00:00
Vasyka 098b8e5204 fix(edit-wo): use Filament's Page::getUrl() for dashboard button
Filament's static ::getUrl(['record' => id]) is the canonical way to
build a page URL with parameters, and it uses the actual route resolver
(handling tenant panel prefix, etc). This is cleaner than hand-building
the path and avoids any \$this-binding ambiguity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 20:08:40 +00:00
Vasyka 6aa9bfb769 fix(edit-wo): fall back to URL segment for dashboard link
Under some Livewire lifecycles \$this->record isn't hydrated when
getHeaderActions() runs, so \$this->record->id was null and the URL
came out as /app/work-orders//dashboard. Fall back to the URL segment
(always present because we're at /app/work-orders/{id}/edit) and log
which path was taken to help diagnose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:57:28 +00:00
Vasyka 5301c98621 fix(edit-wo): build dashboard URL eagerly, outside action closure
The action ->url() closure was resolving \$this to the Action instance
and its ->getRecord() returned null in header-action context, so the URL
came out as /app/work-orders//dashboard.

getHeaderActions() runs after mount(), so \$this->record is guaranteed
populated. Compute the URL string once and pass it as a plain value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:52:23 +00:00
Vasyka 67a87c5a67 fix(edit-wo): resolve dashboard URL via $livewire, not $this
Inside a Filament Action's ->url() closure, \$this can bind to the
Action instance rather than the Livewire page — so \$this->record was
null and the generated URL became /app/work-orders//dashboard, which
routed as record='' and hit our friendly redirect for id 0.

Switch to \$livewire and prefer getRecord() which is always populated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:46:52 +00:00
Vasyka 5fff3d1a70 fix(dashboard): replace route() with url() — Filament auto-name has literal {record}
Filament auto-generates route names from the URL slug, so my custom
Page with slug 'work-orders/{record}/dashboard' got the ugly name
'filament.tenant.pages.work-orders.{record}.dashboard' — where
'{record}' is treated as a literal segment in the name, breaking
route() lookups.

Switch all links + the header-action URL to url('/app/work-orders/'
. \$id . '/dashboard') so they resolve correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 19:05:42 +00:00
Vasyka fa8704f8b6 feat(work-orders): new Mitchell1-style dashboard view (Phase 1 — layout + tabs)
Custom Filament Page at /app/work-orders/{id}/dashboard renders a
Mitchell1-inspired 3-column layout:
  - Top bar: WO#, status badge, actions (Edit / Tracking link)
  - Meta header row: creation / opened / ETA / responsible / urgency /
    paid amount (6 cells)
  - Left sidebar (300px): Client card (avatar, phone, email, status
    tag, 3-stat grid: visits/total/debt) + Vehicle card (photo, plate,
    VIN, mileage, engine, gearbox) + Repair history (latest 5 for this
    vehicle, links to dashboards)
  - Middle: tab bar (Lucrări/Piese/Diagnostic/Foto/Documente/Note)
    with Alpine-driven switching. Works & Parts show the tables read-
    only; add/edit still goes through existing EditWorkOrder Filament
    resource. Photos tab shows gallery from spatie/media-library.
  - Right (300px): Finance summary card (works cost, parts cost,
    discount, total, paid, balance) + placeholder for Timeline+Chat
    (Phase 2)
  - Responsive: right column collapses <1280px, left <900px.

'Vizualizare dashboard' button added on top of the existing
EditWorkOrder page so users can switch between edit form and info-
dense dashboard.

Fixed getSlug() signature (must match parent with ?Panel $panel).

+14 translations. All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-08-05 18:48:52 +00:00
Vasyka 47eb4f62c1 feat(injector-protocols): full module — resource + PDF export via Browsershot
Implements the injector diagnostic protocol module per spec:
- Migration: injector_protocols + injector_protocol_rows with company_id
  (multi-tenant), vehicle_id (not car_id — this CRM uses Vehicle),
  master_id FK to users, snapshot fields (car_model/plate/vin/mileage/year/
  injector_brand) captured at protocol time.
- Models with BelongsToTenant; auto-generate protocol_number as
  PS-{tenantId}-{year}-{seq6}; auto-seed 8 empty rows on create.
- Permissions: injector_protocols.view + injector_protocols.conclude.
  Default roles: owner/admin (both), manager (both), receptionist (view
  only), mechanic (both). Test permission count updated 52→54.
- InjectorProtocolResource under Service nav group with 4 sections:
  data auto+client (with client_id/vehicle_id lookups auto-filling
  snapshot fields), reason checkboxes, 8-row repeater for measurements
  (Repeater with position hidden; fixed 8, non-addable/deletable),
  conclusion + comment + master signature.
- Table with columns, badge filters, PDF action.
- Blade PDF template: pixel-close to reference (graphite/gold/blue
  brand tokens, striped rows, rotated 'ФОРСУНКИ' header).
- Route /app/injector-protocols/{id}/pdf with permission gate,
  streams PDF via new InjectorProtocolPdfService (Browsershot).
- Dockerfile: install nodejs 22 + chromium + noto fonts + puppeteer-core
  in /opt/browsershot; env vars for Browsershot binary paths.
- +71 RU/EN translations covering resource + PDF template.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 11:27:53 +00:00
Vasyka de2964c854 i18n(wo relations): translated snapshot names + explicit empty states
- WorksRelationManager Name column: getStateUsing(labor?->label()) so
  the row shows the labor's localized name (not the raw snapshot);
  fallback to stored snapshot when labor is missing.
- 4 explicit empty states with helper description + icon on Works,
  Parts, SubcontractJobs, Payments — replaces Filament's auto-generated
  'Не найдено X / Создать X для старта.'
- 4 CreateAction titles / modal headings for all four relation managers.
- Notification 'Piesa returnată în stoc' / 'Nimic de restituit' wrapped.
- +11 translation keys.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:59:47 +00:00
Vasyka 9609295a9e feat(labors): trilingual name — add name_en column + Labor::label() helper
- Migration: add nullable `labors.name_en` alongside existing name_ro/
  name_ru.
- Labor model: label(?locale) accessor returns name_{locale} with
  name_ro fallback.
- LaborResource form: expose 3rd 'Nume (EN)' field.
- Table + Select displays now go through label() so options show the
  locale-appropriate name:
    /app/labors table column,
    WorkOrderResource works Select ('[category] name (Nh)'),
    ServiceTemplate items Select,
    ServiceComposer WorkOrderWork snapshot on create.
- Snapshot name saved on wo_works.name at creation reflects the
  current locale; existing rows keep their stored snapshot untouched.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:54:00 +00:00
Vasyka 64a170b7f9 i18n(work-order works): translate labor Select category + h suffix + modal titles
- Labor Select options now build '[__(category)] name_ro (hours __(h))'
  so category chip and hour suffix translate on RU/EN. Labor name stays
  as name_ro (user data — separate long-term concern).
- CreateAction gets explicit label + modalHeading (was 'Create Work
  Order Work' auto-generated).
- Notification 'Piese implicite adăugate (N)' rewritten with __(':n')
  placeholder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:47:00 +00:00
Vasyka 6b5ab02e68 i18n(masters): translate Specializare column tokens (Edit was skipped)
Prior 'Everything up-to-date' commit was a no-op because Edit failed.
Now the change lands. Splits specialization by '/' and __()-translates
each token, matching Reports.php + CalendarBoard.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-26 10:42:26 +00:00
Vasyka 9296d9db11 i18n(permission-overrides): explicit empty state (was auto-generated class name)
Filament default rendered 'Не найдено user permission overrides /
Создать user permission override для старта.' from the model class
name. Overridden with translated heading + explanatory description
+ shield-exclamation icon.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:24:41 +00:00
Vasyka 07398dcbf4 fix(roles): actually apply permissionFields human labels (Edit was silently skipped)
Prior 'fix' commit was a no-op because git diff was empty. This time
the Edit lands the change on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:20:44 +00:00
Vasyka 49b491a7bd feat(rbac): human labels + descriptions for all 50 permissions
- Add Permissions::permissionLabels() mapping every slug to a
  human-readable RO label (e.g. 'clients.view_all' → 'Vezi toți
  clienții').
- RoleResource permission checkboxes now show translated labels
  as titles + technical slug as description (so admins see both
  the meaning and the key).
- PermissionOverridesRelationManager Select 'permission_id' now
  shows 'Human label (technical.slug)' format, plus helper text
  explaining GRANT/DENY. Same format on the display column.
- +57 translations (50 permission labels in RU/EN + role page
  supporting keys + module labels).

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:16:21 +00:00
Vasyka af0327d27d i18n(permission-overrides): wrap DENY option + explicit CreateAction labels
- 'DENY — interzice dreptul' select option was missing __() wrap
- CreateAction/EditAction now have explicit label/modalHeading so
  the modal shows 'Добавить исключение прав' instead of
  'Создать User Permission Override' (auto-generated from class name)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:11:37 +00:00
Vasyka 3d60e1d368 i18n(users): translate 'status' badge column + relabel Locale → Язык / Language
Status column showed raw DB enum values (active/inactive/blocked) with
no formatStateUsing → readers saw 'active' instead of the translated
'Активный / Активно' visible in the Select. Added formatStateUsing with
a match() that returns the appropriate __() key.

Locale header re-translated to 'Язык' (RU) / 'Language' (EN) since
'Локаль' is unusual for end users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:08:46 +00:00
Vasyka 58f168dd97 i18n(users): wrap role Select options with I18n::opts + 2FA status + Locale
- Both role selects (primary + additional roles multi) passed the raw
  roleLabels() array without translation; wrap with I18n::opts() so
  Proprietar/Administrator/… now render translated in RU/EN.
- 2FA status text ('✓ Activat (TOTP)' / '✗ Dezactivat') split so
  the checkmark stays and label runs through __().
- +3 translations (Activat (TOTP), Dezactivat, Locale) — Locale is
  Filament's auto-generated headline from the `locale` field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-18 11:04:30 +00:00
Vasyka 98f71d782c i18n(payroll-runs): +8 translations + fix hardcoded notification title
Notification 'Calculat salariul YYYY-MM pentru N utilizatori' was
built via string interpolation → couldn't translate. Now uses __()
with :period / :count placeholders. Adds 7 more translations for
section titles and column labels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-17 07:01:42 +00:00
Vasyka 4ba003f25c i18n(payments): explicit ->label('Metodă') on method select
Field 'method' auto-generated 'Method' as label. Since RO is the source
language, __('Method') fell back to English on RO locale. Now explicitly
labeled with 'Metodă' so all 3 langs resolve properly (Metodă / Метод /
Method).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-17 06:36:49 +00:00
Vasyka f45df71410 i18n(pricing-coefficients): wrap BODY_TYPES/TRANSMISSION_TYPES CheckboxList options
The two CheckboxList options passed raw Vehicle::BODY_TYPES and
Vehicle::TRANSMISSION_TYPES arrays — now they run through I18n::opts()
so the RO values translate at display. +6 translations for the enum
values that weren't yet in dict (Crossover, Pickup, Minivan, DSG,
DCT, AMT).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 20:59:33 +00:00
Vasyka 2985c38d93 i18n(markup-rules): +11 translations + wrap key label & categories
Wrapped the dynamic Select 'key' label (Brand/Categorie) with __(),
switched the category options to I18n::opts(). Notification 'Recalculat
preț pentru N piese' now uses __(:n) placeholder.

+11 keys: regulă, reguli markup, Cheie, Mai mic = aplicat primul.,
'Va recalcula sell_price…' confirm text, notification, 'Interval preț'
enum, and missing Part.CATEGORIES (Ulei, Filtre, Lichide, Distribuție).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 20:55:13 +00:00
Vasyka 00fb4a304a feat(units): add Unit-of-measure nomenclator (per-tenant, 3 langs)
- Migration: create `units` table + nullable unit_id FK on
  parts / wo_parts / purchase_items / labor_parts. Seeds ~11 standard
  units (buc/set/l/ml/kg/g/m/cm/m²/oră/pack) for every existing
  tenant and backfills unit_id by matching the legacy string `unit`
  column. Keeps `unit` string as fallback so old code paths keep
  rendering.
- Unit model: label(locale), forSelect(locale), labelFor(id, code)
  helpers. All 4 owner models get unitModel() relation + unitLabel()
  accessor.
- UnitResource under Depozit group with Filament UI: code, sort,
  is_active + separate name_ro/name_ru/name_en fields.
- Filament forms/tables updated: Part / PurchaseItem / LaborPart /
  WorkOrderPart now use Select('unit_id')->options(Unit::forSelect())
  for input and TextColumn->getStateUsing(unitLabel()) for display.
  Selecting a part auto-fills unit_id when the source has one.
- +10 translations for the new resource + defaults; nav.label
  'Unități de măsură' added to all 3 lang files.

All 306 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 20:52:06 +00:00
Vasyka c864b112b5 fix(purchases): actually apply CreateAction override
Previous commit added the translation entries but the Edit call
failed silently (file-not-read guard). Now the CreateAction has
->label(__('Adaugă articol')) + ->modalHeading(__('Adaugă articol
în comandă')) so RU/EN see translated modal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-16 20:27:34 +00:00