Commit Graph

6 Commits

Author SHA1 Message Date
Vasyka a2026f640a Stage 6 — Purchase System: partial receipt + supplier analytics
Schema:
- purchase_items.qty_received (backfilled from `received` boolean)
- purchases.warehouse_id (target warehouse FK)
- supplier_part_prices (price history per supplier/part with purchase ref)
- New status `partial` between ordered and received

Purchase ↔ Warehouse integration:
- Purchase::receiveItem(item, qty, warehouse?) — routes through
  WarehouseService::receive: creates batch + receipt event + supplier price row
- Purchase::receiveAllRemaining(warehouse?) — receives all outstanding lines
- Purchase::recomputeStatus() — auto: ordered → partial → received

Old flat markReceived() removed — every receipt now writes batches + ledger.

Filament:
- Purchase list: progress %, partial badge, warehouse picker on form
- ItemsRelationManager: per-line "Recepționează" with qty + warehouse modal,
  qty_received shown as "X.XX / Y.YY" with colour
- PartResource: new PriceHistoryRelationManager (supplier price history)
- SupplierResource: derived columns onTimeRate / avgDeliveryDays / spend(90d)
  + "Rerating" action

Analytics:
- App\Services\Warehouse\SupplierAnalytics (onTimeRate, avgDeliveryDays,
  spend, count, computedRating)
- `suppliers:rate` artisan command + weekly schedule (Mon 04:00)
- Computed rating: 70% on-time + 20% volume + 10% speed bonus

Tests (6 new, all pass):
- Partial receipt of 3/10 → status=partial + 1 batch + 1 price row
- receiveAllRemaining → status=received with received_at set
- Over-receive throws InvalidArgumentException
- Two partial receipts (4+6) → 2 batches FIFO + status=received
- onTimeRate 50% with 1 on-time + 1 late
- computedRating null when <2 deliveries

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:37:12 +00:00
Vasyka 426156fe45 Stage 5.1 — Warehouse ERP: batches + FIFO + reservations + multi-warehouse
Schema:
- warehouses (multi-warehouse, code unique per company, is_default)
- part_batches (lot per receipt, qty_in/qty_remaining, buy_price, FIFO-indexed)
- warehouse_events (immutable ledger: opening/receipt/issue/transfer/adjustment/write_off)
- part_reservations (per-WO allocations from specific batches, active/consumed/released)
- companies.default_warehouse_id + parts.qty_reserved

Backfill: 1 default warehouse + 1 opening batch per existing part per company.

WarehouseService:
- receive / issue (FIFO) / reserve / release / consume / transfer / adjust
- DB::transaction + lockForUpdate on batch rows
- InsufficientStockException with requested + available context
- Auto-syncs parts.qty as aggregate cache (source of truth = sum(qty_remaining))

WO integration:
- WorkOrderPart created/updated → reserve from FIFO batches
- WorkOrderPart deleted → release
- WorkOrder status=done → consume reservations into issue events
- WorkOrder status=cancelled → release reservations

Filament:
- WarehouseResource (CRUD)
- BatchesRelationManager on PartResource (FIFO list with qty_remaining + cost)
- "Recepție" action on parts list → calls WarehouseService::receive
- qty_reserved column added on parts list

Tests (8 new, all pass):
- receipt creates batch + event
- FIFO order verified across 3 batches with different received_at
- InsufficientStockException on over-issue
- Reservations block other reservations but don't deplete on-hand
- WO done consumes; WO cancelled releases
- Batches tenant-isolated
- Transfer between warehouses with weighted-avg cost

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:29:19 +00:00
Vasyka d5680f78dc fix: column names in globalSearch (leads.source, vehicles.make, parts.article, WO.complaint) 2026-05-08 11:12:16 +00:00
Vasyka 0399262514 Deploy 3: Onboarding wizard + empty states + docs operationale
- 3-step onboarding wizard at /app/onboarding (auto-redirected via
  RequireOnboarding middleware on first login per tenant)
- Empty states with icon + heading + description on Client, Vehicle,
  WorkOrder, Lead, Part lists
- Docs: operations/{api,i18n,2fa,monitoring}.md, stack/reverb.md
- Updated 00-index.md and journal.md with status of all 15 items
2026-05-07 20:16:03 +00:00
Vasyka d1e0695930 Deploy 1: i18n + Notifications + Global Search + Tests
- SetLocale middleware (ro/ru/en, session-first, user-persisted)
- Lang switcher in topbar (Filament render hook USER_MENU_BEFORE)
- POST /locale/{lang} route persists to user.locale + session
- Database notifications enabled on tenant panel (30s polling)
- GlobalSearch (Cmd+K / Ctrl+K) on Client, Vehicle, WorkOrder, Lead, Part
- Tests: TenantIsolation (4), AuthFlow (2), WorkOrderCalc (3), MarkupRule (3)
2026-05-07 18:22:48 +00:00
Vasyka 7264dccffa Faza 3.3: Depozit — Furnizori + Catalog piese + Achiziții
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).
2026-05-06 21:58:30 +00:00