Commit Graph

2 Commits

Author SHA1 Message Date
Vasyka 3911012c65 feat(i18n): mega-wrap 494 raw RO strings + 269 lang entries + 161 human RU/163 EN
Broad sweep across the whole codebase:
- Blade views (39 files, 315 wraps): tag-text and title/placeholder/alt
  attributes wrapped with {{ __() }}. Excludes scripts, styles, @php,
  @verbatim, {{ }}, {!! !!}, comments to avoid touching interpolations.
- PHP (54 files, 179 wraps): array 'key' => 'RO value' patterns and
  list items with diacritics wrapped with __(). Reverted __() inside
  const arrays (PHP disallows non-constant expressions).
- Added 269 new keys to lang/{ru,en}.json (identity fallback for
  unknowns → 161 human RU + 163 EN translations added for the most
  common enums, stages, roles, statuses, payment methods, vehicle
  categories, warehouse, portal, form actions.

Missing translations fall back to RO so the UI never breaks. All 306
tests pass; view cache compiles cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-15 05:53:22 +00:00
Vasyka 0e3119a6e2 feat: M14 Excel import wizard + M15 client approval via tracking link
Top-ROI items from CONFORMITY-12-15.md. Together: ~40h of TZ work
delivered in one pass.

== M14 — Excel/CSV invoice import wizard ==

phpoffice/phpspreadsheet ^5.7 added as composer dep — parses both XLSX
and CSV cleanly.

ExcelInvoiceImportService (app/Services/ExcelInvoiceImportService.php):
- headersPreview($path)          → first 5 rows + detected column letters
- preview($path, $mapping)       → all rows classified as found/new/no_article
- import($supplier, $rows, $createNew=true) → creates Purchase + items,
                                    auto-creates Parts for "new" rows
- rememberMapping / rememberedMappingFor($supplier) — round-trips JSON
  config (article_col / name_col / qty_col / price_col / brand_col? /
  header_row / sheet_name?) per supplier so the second import is
  instant

Decimal parser tolerates European formats: "1 234,56", "1,234.56",
non-breaking spaces (U+00A0 NBSP common in copy-pastes from PDF).
Article matching uses single batch query (Part::whereIn) — O(1) for
the whole sheet, not O(rows).

ExcelImportWizard Filament page (/app/excel-import-wizard) — 4-step
Livewire wizard:
  1. Upload + supplier select (saved mapping auto-loads if exists)
  2. Column mapping with first-3-rows preview table + per-column
     dropdowns
  3. Preview with status badges per row ( Found / ⚠️ New /  Missing)
     + summary counts
  4. Confirmation → "Open Purchase" CTA

Stored in nav group "Stoc & Finanțe", sort 65. Width Full.

Migration: supplier_invoice_mappings (id, company_id, supplier_id UNIQUE,
mapping_config JSON, sample_file_name, last_used_at, timestamps).
Per-tenant scope via BelongsToTenant.

== M15 — Client approval via tracking link (the P0 from TZ §15) ==

Migration: adds 4 columns to wo_works AND wo_parts:
- requires_approval boolean default false
- approved_at timestamp nullable
- approval_token varchar(32) nullable (indexed for fast lookup)
- declined_at timestamp nullable

Both model booted hooks: when a row is saved with requires_approval=true
and no token yet, auto-generate Str::random(24). Models gain
isPendingApproval() helper returning true only while not yet approved
nor declined.

Public route: POST /t/{token}/approve/{kind}/{lineToken}
  kind = 'work' | 'part'
  body: decision = 'approve' | 'decline'
The line's approval_token IS the credential — anyone with the URL can
act. No CSRF token required since this is the unauthed public tracking
flow (the tracking_token + line approval_token combo functions as
shared-secret). Form-encoded POST with csrf_field() on the public form
keeps Laravel happy.

TrackingController::show() now eager-loads works + parts, computes
pendingWorks and pendingParts collections, passes them to the view.
TrackingController::approve() validates kind, locates the line by
(work_order_id, approval_token), idempotently marks approved_at or
declined_at, redirects back to /t/{token} with a flash status.

UI banner (tracking/show.blade.php) at the top of the page:
- Amber warning "⚠ Necesită aprobarea ta"
- Per-line card: title + amount (ore/qty + total MDL) + two buttons
  (green Aprob / outline-red Nu aprob)
- Disappears as soon as approved/declined
- Success/error flash above the banner after each action

== Tests ==

ExcelInvoiceImportTest (5):
- headers_preview returns first 5 rows + column letters
- preview classifies rows as found/new/no_article based on Part DB
- import creates Purchase with items + auto-creates parts for "new"
- remember_mapping upserts, no duplicate per supplier
- decimal parser tolerates "1 234,56" European format with NBSP

TrackingApprovalTest (7):
- creating a work with requires_approval auto-generates 24-char token
- POST /t/{token}/approve/work/{lineToken} marks approved_at
- POST with decision=decline marks declined_at instead
- wrong line token redirects with error flash (no leak)
- already-approved line cannot be approved again (idempotent)
- tracking page renders "Necesită aprobarea ta" banner when pending
- approved line vanishes from banner on next page load

Suite: 246 passed (700 assertions). Was 234.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 04:56:06 +00:00