# Page Review — Services (P4) + Packages (P5)

- **Routes:** `/shop-service/index` (services, routines, addons, bundles tabs) · `/package` (packages + subscriptions)
- **Controllers:** `ShopServiceController` (19 actions) · `PackageController` (14 actions) · models ShopService, ServiceFreebie, ServiceBundle, Package, SubscriptionPackage
- **Reviewed:** 2026-07-01 · **Status:** ✅

## 2. Design findings
| # | What | Actual | Sev | Evidence |
|---|------|--------|-----|----------|
| D1 | Services list | ✅ RTL cards (category, price, VAT "شامل الضريبة", duration, specialist count, discount strikethrough 75→70); count chips; "تخصيص صور التصنيفات" | — | ss_0734fmj1f |
| D2 | Service count chips | possible **duplicate label "الباقات"** appearing twice (bundles vs packages?) — verify | LOW | ss_0734fmj1f |
| D3 | Packages list | ✅ RTL card (active badge, gift icon, "متاح دائماً", price, included-services list, edit/delete) | — | ss_3324cvo32 |

## 3. Logic findings
| Action | Result |
|---|---|
| create/update/toggle/delete service·freebie·bundle·package·subscription (🟡/🔴) | not live-triggered (would mutate catalogue); covered by code-review |
| VAT calc / check-price / period calc (🟢 AJAX) | server-side rate (verified by code-review) |

## 4. Code trace
**Code-review agent (ShopServiceController + PackageController + models) — 0 CRIT, 2 HIGH, 1 MED:**
| Sev | File:line | Issue | Status |
|---|---|---|---|
| HIGH | ShopServiceController.php:584 freebieSave / :645 bundleSave | `shop_id` mass-assignable (model `integer` rule) + no re-assert after `load()` → forged `shop_id` orphans freebie/bundle to another shop | ✅ FIXED (R-H007) |
| HIGH | PackageController.php:313 saveSubscription | same — `shop_id` not re-asserted after `load()` | ✅ FIXED (R-H008) |
| MED | base/{ServiceFreebie,ServiceBundle,SubscriptionPackage}.php | `status` mass-assignable → owner force-archives own record bypassing `archive()` (own-data only) | ⚠️ LOGGED R-L11 (model `scenarios()` fix) |

**Confirmed clean:** service/freebie/bundle/package `findModel` double-scoped (`id`+`shop_id`)+checkOwnership; VAT/commission rates from `Settings` server-side (not POST); `calculatePeriod/getPeriod` shop-scoped; `updateCategoryImage` upload validated (ext/mime/size + `isContentSafe` + Flysystem path); bundle members + freebie links whitelisted to own shop (cross-shop IDs dropped); discount clamped [0,100].

## 5. Fixes applied
| Issue | File:line | Change | Re-verified |
|-------|-----------|--------|-------------|
| R-H007 freebie/bundle shop_id mass-assign | ShopServiceController.php:~584,~647 | re-assert `$model->shop_id = $shop->id` after `load()` | ✅ php -l clean |
| R-H008 subscription shop_id mass-assign | PackageController.php:~316 | re-assert `$model->shop_id = $this->currentShopId()` after `load()` | ✅ php -l clean |

## 6. Sign-off
- [x] Services + Packages lists render (RTL)
- [x] 2 HIGH shop_id mass-assignment fixed + lint clean
- [x] code-review clean areas confirmed
- [ ] status MED (R-L11) — model scenarios() fix deferred (own-data, low sev)
- [ ] D2 duplicate-label verify
