Commit Graph

5 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 edcdba9d53 Stage 3 — WO photos + ETA + QR + public tracking page
- HasMedia (Spatie) on WorkOrder with `photos` collection
- eta_at + tracking_token columns; token auto-generated on create
- Public /t/{token} page — tenant-scoped via subdomain, white-label themed
- QR code SVG via chillerlan/php-qrcode (inline modal + download)
- Filament: SpatieMediaLibraryFileUpload + ETA picker + tracking section
- EditWorkOrder header action "Link client (QR)" modal
- Fix: Auditable::dontSubmitEmptyLogs() → dontLogEmptyChanges() (removed in activitylog)
- Tests: TrackingPageTest (4 pass) covering token gen + cross-tenant isolation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 19:21:23 +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 5e32f82b3a Initial Laravel 12 + Filament 5 + Octane skeleton
- Laravel 12 base
- Filament 5 (default admin panel)
- Stancl/Tenancy v3 (config + migrations only)
- Spatie Permission
- Octane FrankenPHP runtime
- Sanctum
- Dockerfile multi-stage (composer + node + frankenphp:8.4)
- Entrypoint runs migrations + caches on boot
- .env.example pre-completat cu hosturi interne Coolify
- Health endpoint /up

Repo init pentru multi-tenant SaaS pe Coolify Hetzner.
2026-05-04 12:19:55 +00:00