Schema:
- suppliers: name, contact, phone/email/website, pay_terms, delivery_days,
rating (1-5), discount_pct, categories (JSON), is_active, notes
- parts: name, article (UNIQUE per tenant), brand, category, qty/unit/min_qty,
buy_price/sell_price, location (rack/bin), barcode, preferred_supplier_id,
is_active. Index pe (company_id, category) și (company_id, is_active).
- purchases: număr unique per tenant + an, supplier_id, status workflow
(draft/ordered/received/cancelled), order/expected/received/paid_at, total
- purchase_items: name, article, qty, unit, buy_price, total auto, received bool;
link opțional la part_id
- wo_parts + part_id: linkare opțională la catalog (alter migration)
Modele cu logică:
- Part::adjustStock($delta) — modifică qty cu validare ≥ 0
- Part::isLow() / isOut() helpers
- Purchase::markReceived() — atomic: marchează items ca received + creste qty
pe pieces din catalog (DB::transaction)
- WorkOrderPart::updating event — la trecerea status='installed' decrementează
stoc auto. La revenire (ex: storno) incrementează la loc.
- PurchaseItem::saving — total = qty * buy_price; recalc parent total
Filament resources (group Depozit):
- SupplierResource: form 3 secțiuni, rating ★★★★★, TagsInput pentru categorii
- PartResource: form 4 secțiuni, badge nav cu nr. piese sub stoc minim,
filtre low_stock + out_of_stock, coloană qty colorată după stoc
- PurchaseResource: form antet + RelationManager Items.
Action 'Recepționează' care apelează markReceived() — un click = stoc actualizat
WorkOrder PartsRelationManager updated:
- Selector din catalog (Part::active) cu stoc afișat
- Auto-fill name/article/brand/unit/buy_price/sell_price din piesa selectată
- Helper text: la status='installed' se scade din stoc
Widget low-stock:
- TableWidget pe dashboard tenant, listează piesele cu qty <= min_qty
- Span full, sortat după qty (cele mai critice sus)
Seed:
- 2 furnizori (AutoParts Moldova SRL ★5, Inter Cars Moldova ★4)
- 5 piese demo: Ulei Shell, Filtru Mann, Plăcuțe Brembo, Antigel (qty=0!), Bujii NGK
- 1 achiziție recepționată (P-26-0001) cu 2 articole linked la catalog
Total Filament tenant routes: 63 (de la 31).
Schema:
- users + specialization, color, hourly_rate (pentru maistri)
- labors: catalog manopere standard cu category/ore/preț (RO+RU)
- work_orders: nr unique per tenant, status workflow (9 stări),
pay_status (3 stări), client/vehicle/master/deal/appointment refs,
complaint/diagnosis/recommendations, total auto-calculat
- wo_works: manopere per fișă, recalc auto la save/delete
- wo_parts: piese per fișă (free-text deocamdată), discount/total auto
Filament resources (group Service):
- LaborResource: CRUD + grupare pe categorie + filter active
- WorkOrderResource: form complex în 4 secțiuni (antet, diagnostic, plată)
+ 2 RelationManagers (Works, Parts)
- MasterResource: vedere User filtrată role=mechanic, edit specializare/
culoare calendar/tarif oră
Conversie auto: la adaugare manoperă din catalog Labor,
form populează numele + ore + preț/oră derivat (price/hours).
Number generator pentru WO: format WO-{YY}-{NNNN} per tenant per an,
calculat în CreateWorkOrder via WorkOrder::generateNumber().
Seed extins:
- 3 mecanici (Vasile/Andrei/Nicolae) cu culori + specializări
- 10 manopere standard din prototipul AutoCRM.html
- 1 fișă demo (BMW X5 plăcuțe Brembo) cu 1 manoperă + 1 piesă, total auto
Filament panel routes are NOT in the 'web' middleware group; they have
their own middleware list. So registering ResolveTenant in bootstrap
app.php (web group) doesn't apply to /app/* routes.
Filament's Authenticate middleware (from authMiddleware) is inserted
between ShareErrorsFromSession and AuthenticateSession. Auth check
queries User::find($id) which goes through BelongsToTenant's TenantScope.
If tenant isn't resolved at that point, scope's fail-safe returns 0 rows
→ User not found → user appears not authenticated → infinite redirect.
Fix: put ResolveTenant + CheckTenantStatus FIRST in the panel's
middleware() chain, before any auth-related middleware runs.
Symptom that pointed here: force-login set session correctly,
auth('web')->check() returned true on a /__whoami test route, but
visiting /app redirected back to /app/login.
Livewire posts go to /livewire/update on the bare web middleware group,
NOT through the Filament panel middleware. So ResolveTenant didn't fire
during login form submission → tenant not set → TenantScope's fail-safe
returned 0 users → Auth::attempt failed → 'Email/password incorrect'.
Move ResolveTenant + CheckTenantStatus to the global web group via
bootstrap/app.php; remove them from TenantPanelProvider to avoid
running twice.
forceRootUrl forces ALL generated URLs to APP_URL (service.mir.md).
On psauto.service.mir.md, Livewire-generated POST URLs targeted
service.mir.md instead of psauto, so CSRF/session cookies didn't
match → silent auth failure.
Keep forceScheme('https') so Cloudflare → Traefik → Octane proxy
chain doesn't generate http:// links, but let Laravel use the
actual request host for everything else.
Also: TextInput->lowercase() removed (not in Filament v5);
slug uses dehydrateStateUsing(strtolower) + visual CSS lowercase.
Layout components (Section, Grid, Tabs, etc.) au fost mutate din
Filament\Forms\Components în Filament\Schemas\Components.
Forms\Components păstrează doar field-urile (TextInput, Select, etc.).
Forms\Get s-a mutat în Schemas\Components\Utilities\Get.
Filament v5 a unificat actions într-un singur namespace Filament\Actions.
Tables\Actions\EditAction et al. nu mai există → fix global pe toate resources.