Commit Graph

5 Commits

Author SHA1 Message Date
Vasyka 8fdfc9ef85 feat: Part product images + seasonal tire-swap reminders
Part (HasMedia):
- Spatie media `image` single-file collection + imageUrl() helper
- PartResource form: image upload section (image editor, 2 MB max)
- Parts list: circular thumbnail column
- Shop catalog cards: square thumbnail + 📦 placeholder
- Shop part detail: 260px image alongside info, single column when no image

Seasonal tire-swap reminders:
- NotificationDispatcher::tireSeasonalSwap(TireSet) — Telegram first, email
  fallback (when set has a vehicle, via ServiceReminderMail with 'tire_swap'
  type and a size-aware note)
- tires:remind-seasonal artisan command, self-gating to Feb 15-Mar 15
  (notify winter sets stored) and Sep 15-Oct 15 (notify summer sets stored).
  60-day cooldown per client via service_reminders_sent. --force / --dry-run.
- Schedule: weekly Mon 09:30

Tests (6 new):
- outside window no-ops; spring window notifies winter; spring ignores summer;
  autumn notifies summer; cooldown blocks doubles; --force overrides window

Full suite: 106 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 19:31:24 +00:00
Vasyka 954ba8f059 Stage 12 — Online Store: public catalog + cart + orders
Schema:
- online_orders (token-tracked, status workflow, delivery method/fee)
- online_order_items (price snapshot, fulfilled flag)
- part_cross_refs (OEM/equivalent codes for search)
- parts.is_published (shop visibility)

Storefront (ShopController, tenant subdomain, /shop):
- Catalog with search across name/article/brand/cross-refs, category +
  in-stock filters, live stock, white-label themed layout
- Part detail page with cross-ref codes
- VIN search → VinDecoder → guided catalog search
- Session cart (per-tenant key), guest checkout, order confirmation page
- Respects settings.shop.enabled (404 when off); tenant-guarded

Part::searchPublished matches cross-ref articles via whereHas.

Order notifications (ShopOrderNotifier, best-effort):
- Staff: Web Push to active users
- Customer: Telegram if phone matches a linked client

Filament (tenant):
- OnlineOrderResource under "Magazin" nav group, status workflow,
  items relation, "Onorează" action issues stock via WarehouseService (FIFO)
- PartResource: is_published toggle + column + bulk publish/unpublish +
  CrossRefsRelationManager
- Settings: shop section (enable, delivery methods, fee, free-over)
- Landing page: shop button when enabled

Tests (6 new):
- catalog 404 when disabled; lists published only; cross-ref search;
  order placement (token + items + total); fulfill issues stock;
  cross-tenant token isolation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 05:27:51 +00:00
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 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