# LivePrawn Condo Group Buy — Architecture & Implementation Plan

**Project:** Live Prawn (`liveprawn.com`)  
**Platform:** Magento Open Source 2.4.8-p4  
**Magento root:** `/var/www/html/airbenih_stg`  
**Custom module (proposed):** `LivePrawn_CondoGroupBuy`  
**Document phase:** Planning only — **no implementation**  
**Date:** 2026-07-03  

---

## Executive summary

Condo Group Buy is a **greenfield feature** on top of the existing LivePrawn Magento stack. It lets admins create condo delivery batches, generates one QR URL per condo (`/group-buy/{condo-slug}`), and gives residents a simplified order form that maps to **existing prawn SKUs** (entity IDs 75–94). Orders are tagged with condo metadata for admin packing and payment verification.

This plan deliberately **does not** modify rewards, restaurant commission, pricing rules, customer groups, or the general checkout experience. Phase 1 introduces an isolated module with its own frontend route, admin CRUD, quote/order tagging, and a condo-only Bank In payment path.

---

## 1. Recommended architecture

### 1.1 Module boundary

Create a new module:

```
app/code/LivePrawn/CondoGroupBuy/
```

Keep it **separate from** `LivePrawn_CustomerRewards` and `LivePrawn_Payment` to avoid accidental coupling with rewards/commission observers. Cross-module interaction is limited to:

| Existing module | Interaction | Phase |
|-----------------|-------------|-------|
| `LivePrawn_Payment` | Extend Bank In visibility plugin to allow `banktransfer` when quote flag `is_condo_group_buy = 1` | Phase 1 |
| `LivePrawn_CustomerRewards` | **None** — no ledger writes, no profile changes | — |
| Amasty OSC | Use standard checkout redirect; condo fields pre-set on quote before redirect | Phase 1 |
| Amasty Order Attributes | **Do not reuse** for condo fields (different lifecycle, admin reports) | — |
| Magecomp Mobilelogin | **Do not integrate** in Phase 1 | Phase 2 optional |

### 1.2 High-level flow

```mermaid
flowchart TD
    A[Admin creates Condo Group record] --> B[System generates slug + QR URL]
    B --> C[Customer scans QR]
    C --> D["/group-buy/{slug} page loads condo"]
    D --> E[Customer enters name, phone, unit]
    E --> F[Product matrix form → add existing SKU to cart]
    F --> G[Quote tagged: condo_group_id, unit_number, batch date]
    G --> H[Shipping address built from condo + unit]
    H --> I[Redirect to checkout — Bank In only]
    I --> J[Order created: pending_payment]
    J --> K[Admin verifies payment + exports packing list]
```

### 1.3 Design principles

1. **One dynamic route, many condos** — no CMS pages per condo.
2. **Reuse catalog** — map form rows to existing SKUs; never duplicate products.
3. **Snapshot at order time** — condo address/name can change later; orders keep historical values.
4. **Guest-first** — lowest friction for QR scan → order in under 2 minutes.
5. **Isolated payment path** — Bank In exposed only for condo group-buy quotes; general storefront unchanged.
6. **Report-friendly schema** — admin exports and packing lists query indexed tables, not JSON blobs.

### 1.4 Attribute storage decision (Section B answer)

**Recommended: combination of (1) quote/order extension attributes + (3) custom snapshot table.**

| Data | Storage | Rationale |
|------|---------|-----------|
| `condo_group_id` | Quote + order extension attribute | Required for checkout plugins, order grid filters, FK integrity |
| `unit_number` | Quote + order extension attribute | Needed at checkout validation and packing sort |
| `is_condo_group_buy` | Quote + order extension attribute (smallint flag) | Gates payment method visibility, shipping logic, admin reports |
| `condo_delivery_batch_date` | Quote + order extension attribute | Filter orders by delivery batch in sales grid |
| `condo_name_snapshot` | `liveprawn_condo_group_order` table | Immutable after order; survives condo record edits |
| `collection_point_snapshot` | `liveprawn_condo_group_order` table | Same |
| `customer_name` | `liveprawn_condo_group_order` table | Guest orders may not have a customer account |
| `customer_phone` | `liveprawn_condo_group_order` table | Primary contact for delivery day |
| Full delivery address snapshot | `liveprawn_condo_group_order` table | Audit trail; built from condo + unit at order time |

**Why not customer address attributes?** Guests dominate this flow; address is batch-specific, not a saved customer address book entry.

**Why not quote/order attributes alone?** Snapshots and packing-list queries would bloat `sales_order` and complicate exports. A dedicated 1:1 table keeps reporting fast and schema clean.

**Why not a custom table only?** Checkout and payment plugins need fast access to `is_condo_group_buy` and `condo_group_id` on the active quote without extra joins.

---

## 2. Tables needed

### 2.1 `liveprawn_condo_group` (master data)

Admin-managed condo batch records. One row = one condo × one delivery batch (or recurring batch if business reuses slug).

| Column | Type | Notes |
|--------|------|-------|
| `entity_id` | int PK AI | |
| `condo_name` | varchar(255) | Display name |
| `slug` | varchar(128) UNIQUE | URL segment, auto-generated from name, editable |
| `address_line_1` | varchar(255) | Condo block / street |
| `address_line_2` | varchar(255) nullable | Optional |
| `city` | varchar(128) | |
| `postcode` | varchar(16) | |
| `state` | varchar(64) | MY state |
| `country_id` | varchar(2) | Default `MY` |
| `collection_point` | varchar(255) | e.g. "Lobby Guard House" |
| `delivery_day` | date nullable | Scheduled delivery date |
| `delivery_time_window` | varchar(64) nullable | e.g. "10:00–12:00" |
| `order_cutoff_datetime` | datetime | Orders blocked after this |
| `minimum_group_kg` | decimal(8,2) nullable | Informational in Phase 1; enforce in Phase 2 |
| `status` | varchar(16) | `active` / `inactive` |
| `notes` | text nullable | Admin-only |
| `created_at` | timestamp | |
| `updated_at` | timestamp | |

**Indexes:** `slug`, `status`, `delivery_day`, `order_cutoff_datetime`

### 2.2 `liveprawn_condo_group_order` (order snapshot, 1:1 with order)

| Column | Type | Notes |
|--------|------|-------|
| `entity_id` | int PK AI | |
| `order_id` | int UNIQUE FK → `sales_order.entity_id` | CASCADE on delete |
| `condo_group_id` | int FK → `liveprawn_condo_group.entity_id` | SET NULL if condo deleted |
| `condo_name_snapshot` | varchar(255) | |
| `collection_point_snapshot` | varchar(255) | |
| `unit_number` | varchar(64) | Denormalized from order attr for report speed |
| `customer_name` | varchar(255) | |
| `customer_phone` | varchar(32) | |
| `delivery_batch_date` | date | Copied from condo at order time |
| `delivery_address_snapshot` | text | Full formatted address including unit |
| `payment_verification_status` | varchar(16) | `pending` / `verified` / `rejected` |
| `payment_verified_at` | timestamp nullable | |
| `payment_verified_by` | int nullable | Admin user ID |
| `payment_reference` | varchar(128) nullable | Bank ref / receipt note |
| `created_at` | timestamp | |

**Indexes:** `condo_group_id`, `delivery_batch_date`, `payment_verification_status`, `unit_number`

### 2.3 Quote / order extension columns

Add via `db_schema.xml` on existing Magento tables:

**`quote`**

| Column | Type | Notes |
|--------|------|-------|
| `condo_group_id` | int nullable | FK reference |
| `condo_unit_number` | varchar(64) nullable | |
| `condo_delivery_batch_date` | date nullable | |
| `is_condo_group_buy` | smallint default 0 | 0/1 flag |

**`sales_order`**

| Column | Type | Notes |
|--------|------|-------|
| `condo_group_id` | int nullable | |
| `condo_unit_number` | varchar(64) nullable | |
| `condo_delivery_batch_date` | date nullable | |
| `is_condo_group_buy` | smallint default 0 | |

**Indexes on `sales_order`:** composite `(is_condo_group_buy, condo_group_id, condo_delivery_batch_date)`

### 2.4 `liveprawn_condo_product_map` (product matrix mapping)

Static mapping from form selections to existing catalog products. **Batch 2A (2026-07-03)** confirmed all 20 products (IDs 75–94) are mappable via consistent SKU/name patterns.

| Column | Type | Notes |
|--------|------|-------|
| `entity_id` | int PK AI | |
| `prawn_type` | varchar(16) | `tiger` / `vannamei` |
| `product_state` | varchar(16) | `frozen` / `live` |
| `size_band` | varchar(16) | `40_to_45`, `31_35`, `26_30`, `21_25`, `15_plus` |
| `product_id` | int unsigned FK | → `catalog_product_entity.entity_id` |
| `sku_snapshot` | varchar(64) | SKU at seed time (audit if catalog changes) |
| `display_label` | varchar(128) | e.g. `Frozen Tiger Prawn 31+ / 35+` |
| `sort_order` | int default 0 | Row order in matrix (by state, type, size) |
| `status` | varchar(16) | `active` / `inactive` |
| `created_at` | timestamp | |
| `updated_at` | timestamp | |

**Constraints:** UNIQUE `(prawn_type, product_state, size_band)`; INDEX `product_id`, `status`

**Seed approach (Batch 2B):** One-time data patch inserts 20 rows from verified catalog inspection. Admin-editable grid deferred to Batch 2C+.

**Do not** auto-detect from names at runtime as primary resolver — use mapping table; optional CLI validation script can compare map vs SKU patterns.

### 2.5 Optional Phase 2: `liveprawn_condo_group_session`

Only if analytics needed — tracks QR scans and abandoned carts. **Not required for Phase 1.**

---

## 3. Frontend routes and pages

### 3.1 Public URL

```
https://liveprawn.com/group-buy/{condo-slug}
```

Example: `https://liveprawn.com/group-buy/the-elements-klcc`

### 3.2 Routing implementation

| Component | Purpose |
|-----------|---------|
| `etc/frontend/routes.xml` | `frontName`: `groupbuy` (internal) |
| Custom `Router` (`Model/Router/GroupBuyRouter.php`) | Match `group-buy/{slug}` before standard router; load condo by slug |
| `Controller/GroupBuy/View.php` | Render page or 404 if inactive/expired |
| `etc/frontend/di.xml` | Register router in `routerList` sortOrder ~60 |

**Why custom router?** Clean URLs without creating 100 URL rewrites. Slug uniqueness is enforced in DB.

### 3.3 Page sections

**Template:** `view/frontend/templates/group-buy/view.phtml`

| Section | Content |
|---------|---------|
| Header | Condo name, collection point, delivery day + time window |
| Cutoff banner | "Order by {cutoff datetime}" — hide form after cutoff |
| Customer mini-form | Name*, Phone/WhatsApp*, Unit number*, Email (optional) |
| Product matrix | Rows: type, size, frozen/live, price (from catalog), qty selector |
| Summary | Line items, estimated total kg, subtotal |
| CTA | "Proceed to checkout" |

### 3.4 Quote attachment logic

On page load (or first form interaction):

1. Resolve or create guest quote (standard Magento session).
2. Set `is_condo_group_buy = 1`, `condo_group_id`, `condo_delivery_batch_date` on quote.
3. Store condo context in session (`condo_group_slug`) for validation on add-to-cart.
4. **Reject** add-to-cart if quote's `condo_group_id` differs from page condo (prevent cross-condo cart mixing).

On "Proceed to checkout":

1. Validate required customer fields + at least one line item.
2. Build shipping address from condo record + unit number.
3. Set billing = shipping (guest default).
4. Persist customer name/phone to quote (`customer_firstname`, `customer_lastname` split or use `customer_note` — prefer snapshot table at order placement).
5. Redirect to `/checkout` (Amasty OSC).

### 3.5 Supporting frontend assets

| Asset | Purpose |
|-------|---------|
| `view/frontend/web/js/group-buy-form.js` | Matrix selection, qty (1kg/2kg/custom), AJAX add-to-cart |
| `view/frontend/web/css/group-buy.css` | Mobile-first layout (QR scans are mobile) |
| `view/frontend/layout/groupbuy_groupbuy_view.xml` | Minimal chrome — hide main nav distractions optional |

### 3.6 QR generation (admin-side)

Admin condo edit screen displays:

- Full URL (copy button)
- QR code image (client-side JS library e.g. `qrcode.js`, or link to external QR API)

No file storage required in Phase 1.

---

## 4. Checkout and order attributes

### 4.1 Phase 1 checkout behaviour (design only — not implemented yet)

Condo group buy **reuses Amasty One Step Checkout** with pre-filled data:

| Checkout field | Behaviour |
|----------------|-----------|
| Shipping address | Pre-filled from condo + unit; **read-only or hidden** |
| Shipping method | Single method or filtered set for condo delivery (config) |
| Customer email | Optional; prompt if missing before order submit |
| Payment | **Bank In only** when `is_condo_group_buy = 1` |
| Amasty order attributes | Unchanged — condo fields are separate |

### 4.2 Plugin: Bank In visibility

Extend existing plugin pattern in `LivePrawn_Payment`:

```
HideBankTransferFromFrontendCheckout
  → if quote.is_condo_group_buy == 1: KEEP banktransfer
  → else: hide (current behaviour)
```

Also hide all other payment methods when `is_condo_group_buy = 1` (Phase 1).

### 4.3 Order placement observer

On `sales_order_place_after`:

1. Copy quote extension attrs → order extension attrs.
2. Insert row into `liveprawn_condo_group_order` with snapshots.
3. Set order status from payment method config (`pending_payment` for Bank In).
4. **Do not** trigger rewards ledger, restaurant commission, or premium activation observers — add early-return guard in those observers if quote/order has `is_condo_group_buy = 1` (Phase 1 safety patch, separate from rewards logic changes).

### 4.4 Order grid columns (admin)

Add to Sales → Orders grid via UI component:

- Condo Group
- Unit Number
- Delivery Batch Date
- Payment Verification Status

---

## 5. Simplified registration (Section D)

### 5.1 Options compared

| Option | Pros | Cons | Launch fit |
|--------|------|------|------------|
| **1. Guest checkout + condo fields** | Fastest; no password/OTP; works for one-time buyers | No automatic reorder; harder repeat identification | **Best for Phase 1** |
| **2. Required registration** | Customer record for CRM/rewards later | High friction at QR scan; Magecomp OTP adds steps | Poor for launch |
| **3. Phone OTP (Magecomp)** | Verified phone; account linkage | SMS cost; failure modes; conflicts with "simple" goal | Phase 2 |

### 5.2 Recommended launch flow (Option 1)

```
Scan QR → Enter name + phone + unit → Select products → Checkout as guest → Bank In → Done
```

Details:

- **No account required** to complete order.
- **Customer group:** unchanged — guest checkout uses default; if customer later creates account, assign **Free Member (ID 5)** via standard registration, not during group buy.
- **Address construction:**

  ```
  {address_line_1}, Unit {unit_number}
  {address_line_2}
  {postcode} {city}, {state}
  Malaysia
  ```

- **Phone** stored in snapshot table + quote address telephone field.
- **Email optional** — collect at checkout if empty; needed for order confirmation email.

### 5.3 Post-checkout account creation (Phase 1 optional, Phase 2 polished)

Order success page offer:

> "Create an account to track your order — we'll use your phone number."

Uses standard Magento `createAccount` from order token. **Does not** retroactively change condo order tags.

### 5.4 Phase 2: Phone OTP

Integrate Magecomp Mobilelogin for returning condo buyers ("Enter phone → OTP → see past orders"). Only after guest flow is proven stable.

---

## 6. Product selection form (Section E)

### 6.1 Form matrix

| Dimension | Values |
|-----------|--------|
| Prawn type | Vannamei, Tiger |
| Size | 40–45, 31+/35+, 26+/30+, 21–25+, 15+ |
| Preparation | Frozen, Live |
| Price | Loaded live from catalog (respects customer group if logged in; guest = non-member price in Phase 1) |
| Quantity | 1 kg, 2 kg, Custom (decimal step 0.5 kg min 1 kg) |

### 6.2 SKU resolution

```
(prawn_type, size_band, preparation) → liveprawn_condo_product_map.sku → ProductRepository::get()
```

**Pricing:** Use Magento's native price from the resolved simple product. **Do not** create condo-specific prices in Phase 1. Existing tier/group pricing applies only if customer is logged in as member — for guest launch, display base (non-member) price.

### 6.3 Add to cart

- Each row add = `{sku}` × `{qty}` as decimal quantity (products are sold by kg).
- Validate SKU exists and is enabled before add.
- Show running total kg in cart summary (for admin minimum_group_kg visibility).

### 6.4 Existing catalog reference

| Line | SKU prefix | Entity IDs (staging) |
|------|------------|---------------------|
| Frozen Tiger Prawn | `frozen-tiger-prawn-` | subset of 75–94 |
| Frozen Vannamei | `frozen-vannamei-` | subset of 75–94 |
| Live Vannamei | `live-vannamei-` | 85–94 |
| Live Tiger Prawn | `live-tiger-prawn-` | 85–94 |

Verify exact SKU list on staging via `SELECT sku FROM catalog_product_entity WHERE entity_id BETWEEN 75 AND 94` before seeding product map.

---

## 7. Payment flow (Section F)

### 7.1 Phase 1: Bank In / manual verification

```mermaid
sequenceDiagram
    participant C as Customer
    participant M as Magento
    participant A as Admin

    C->>M: Place order (Bank In)
    M->>M: Status = pending_payment
    M->>C: Order confirmation + bank instructions (CIMB 8606533093)
    C->>C: Bank transfer
    C->>A: WhatsApp proof (offline)
    A->>M: Mark payment verified
    M->>M: Invoice + status → processing
```

| Step | Detail |
|------|--------|
| Payment method | `banktransfer` (Bank In) — **condo quotes only** |
| Initial order status | `pending_payment` (existing deploy config) |
| Customer instructions | Reuse `LivePrawn_Payment` invoice PDF bank details |
| Admin verification | Update `payment_verification_status` on `liveprawn_condo_group_order`; optional bulk action |
| Post-verification | Manual invoice creation or automated on verify (config) |

### 7.2 Phase 2: Online payment

- FPX / card / e-wallet via existing or new gateway.
- Payment method plugin expands allowed methods when `is_condo_group_buy = 1`.
- Auto-capture → `processing` without manual verify.

### 7.3 Safety

- **Do not** change global `payment/banktransfer/active` or the default hide plugin behaviour for normal checkout.
- Condo exception is **quote-flag gated** only.

---

## 8. Admin operations (Section G)

### 8.1 Admin menu

Under top-level **Live Prawn** (new root or sibling to Rewards):

```
Live Prawn
├── … (existing Rewards items)
└── Condo Group Buy
    ├── Condo Groups          (CRUD)
    ├── Orders by Condo       (report)
    ├── Packing Lists         (report + export)
    └── Settings              (system config)
```

**Route namespace:** `liveprawn_groupbuy`  
**ACL resources:** mirror menu structure  
**Pattern:** Follow `LivePrawn_CustomerRewards` (`menu.xml`, `AbstractPlaceholder` controller base, UI grid components)

### 8.2 Condo Groups CRUD

| Action | Detail |
|--------|--------|
| List grid | Filter by status, delivery day, cutoff |
| Create/Edit | All fields from §2.1; auto-slug from name; uniqueness validation |
| QR panel | URL + QR preview |
| Deactivate | Set status inactive — page shows "Orders closed" |

### 8.3 Reports

#### Orders by Condo

Filters: condo group, delivery batch date, payment status (paid/pending), order status  
Columns: Order #, Unit, Customer name, Phone, Products summary, Total kg, Grand total, Payment status

#### Orders by Delivery Date

Aggregate across condos for logistics planning.

#### Paid vs Pending Payment

Counts and totals per condo batch.

#### Total kg by Product

Pivot: SKU / prawn line → sum qty for a condo batch.

#### Packing List

Sorted by `unit_number` ascending:

```
Unit A-10-03 | Kenny | Live Vannamei 2kg | Paid
Unit B-08-11 | Han   | Frozen Tiger 1kg  | Pending
Unit C-05-02 | Lim   | Live Vannamei 1kg, Frozen Vannamei 2kg | Paid
```

**Export:** CSV download with same columns + order ID + phone + payment ref.

### 8.4 Payment verification UI

On condo order detail or grid mass action:

- Mark Verified (with payment reference + timestamp + admin user)
- Mark Rejected (with note)

Optional: upload receipt image (Phase 2 — requires media storage).

---

## 9. Scaling (Section H)

| Concern | Solution |
|---------|----------|
| 100 condos | 100 rows in `liveprawn_condo_group`; 1 router handles all slugs |
| 20–50 buyers × 100 condos = 2,000–5,000 orders/batch | Indexed snapshot table; grid filters by condo + date |
| No CMS sprawl | Zero CMS pages; admin CRUD only |
| QR distribution | Admin copies URL or prints QR from edit screen |
| Concurrent orders same condo | Standard Magento quote/session; no special locking in Phase 1 |
| Cutoff enforcement | Server-side check on add-to-cart and checkout entry |

**Load estimate:** 5,000 orders is well within Magento + MySQL capacity with proper indexes. Cron not required for core flow.

---

## 10. Safety constraints (Section I)

### 10.1 Do not touch (Phase 1)

| Area | Reason |
|------|--------|
| Rewards ledger / Prawn Points | Business rule — separate program |
| Restaurant commission observers | Must not fire on condo orders |
| Premium activation observer | Must not fire on condo orders |
| Product pricing / tier prices | Use catalog as-is |
| Customer group assignment logic | Free Member only via normal registration |
| General checkout payment methods | Bank In stays hidden except condo flag |
| Magecomp registration flow | No integration in Phase 1 |
| Referral capture (`?ref=`) | Ignore on group-buy pages or strip silently |

### 10.2 Observer guard pattern (implement in Phase 1)

In `LivePrawn_CustomerRewards` order observers, add at top:

```php
if ($order->getData('is_condo_group_buy')) {
    return;
}
```

This is a **read-only guard**, not a rewards logic change. Document and implement as a separate tiny patch when condo module ships.

---

## 11. Phase 1 build plan

**Goal:** End-to-end condo group buy on staging with guest checkout and Bank In.

| Batch | Deliverable | Est. |
|-------|-------------|------|
| **1.1 Module skeleton** | `LivePrawn_CondoGroupBuy` registration, `db_schema.xml` (all tables + quote/order columns), di.xml, module.xml | 1 day |
| **1.2 Product map seed** | Data patch seeding 20 SKU mappings; admin read-only grid to verify | 0.5 day |
| **1.3 Admin CRUD** | Condo group list/create/edit/delete; slug generation; QR URL display | 2 days |
| **1.4 Frontend route + page** | Custom router, view controller, mobile template, cutoff validation | 2 days |
| **1.5 Cart integration** | AJAX add-to-cart, quote flagging, address builder, anti cross-condo cart | 1.5 days |
| **1.6 Checkout integration** | Pre-fill shipping; extend Bank In plugin; hide other payments for condo quotes | 1 day |
| **1.7 Order snapshot** | Place-order observer, snapshot insert, extension attr copy | 1 day |
| **1.8 Rewards guard** | Early return in commission/rewards/premium observers for condo orders | 0.5 day |
| **1.9 Admin reports** | Orders by condo grid, packing list, CSV export | 2 days |
| **1.10 Payment verification** | Admin UI to verify/reject; optional status sync | 1 day |
| **1.11 Staging QA** | Full test matrix (§13) | 2 days |

**Phase 1 total:** ~14–15 dev days

**Phase 1 explicitly excludes:** FPX/card, OTP login, minimum kg enforcement, rewards accrual, automated SMS, customer registration changes.

---

## 12. Phase 2 build plan

| Feature | Detail |
|---------|--------|
| Online payments | FPX / e-wallet for condo checkout |
| Minimum group kg | Block checkout or show warning until condo batch reaches threshold |
| Phone OTP login | Magecomp integration for returning buyers |
| Post-checkout account | Polished "create account from order" flow |
| Group buy rewards | `group_buy_reward` ledger entries (per existing rewards plan §2.11) |
| Email/WhatsApp notifications | Batch reminders, cutoff warnings, payment reminders |
| Recurring batches | Reuse slug or clone condo record for next delivery day |
| Receipt upload | Admin/customer payment proof attachment |
| Dashboard analytics | QR scan count, conversion rate, abandoned carts |
| Shipping method rules | Condo-specific carrier or flat fee |
| Cutoff cron | Auto-close + notify admin of batch totals |

---

## 13. Risks

| Risk | Impact | Mitigation |
|------|--------|------------|
| Bank In visible on normal checkout by mistake | Customers bypass intended payment flow | Quote-flag gated plugin; automated test on both paths |
| Rewards/commission fires on condo order | Incorrect ledger/commission entries | Observer guards + staging test with real order |
| Guest duplicate orders (same phone, multiple units) | Packing confusion | Admin report groups by unit; phone is informational only |
| Cross-condo cart contamination | Wrong delivery address | Session validation on every cart mutation |
| Cutoff timezone ambiguity | Orders accepted after deadline | Store cutoff in `Asia/Kuala_Lumpur`; server-side only |
| SKU map drift if catalog changes | Form adds wrong product | Admin product map grid; disable map row if SKU missing |
| Amasty OSC conflicts (hidden address fields) | Broken checkout UX | Test on mobile; fallback layout processor plugin |
| 50 concurrent checkouts same condo | Performance / stock | Simple products — qty is decimal; no inventory strictness assumed |
| Slug collision on similar condo names | 404 or wrong condo | Unique index + admin slug preview |
| Price display mismatch (member vs guest) | Customer confusion | Phase 1: show non-member price for guests; document clearly |

---

## 14. Staging test checklist (before live)

### 14.1 Admin

- [ ] Create condo group with all fields; slug auto-generates
- [ ] Edit condo; slug uniqueness enforced
- [ ] Deactivate condo; frontend shows closed message
- [ ] QR URL copies correctly
- [ ] Product map shows all 20 SKUs resolving

### 14.2 Frontend / QR flow

- [ ] `/group-buy/{slug}` loads correct condo (active)
- [ ] Inactive slug returns appropriate message
- [ ] Expired cutoff blocks add-to-cart and checkout
- [ ] Mobile layout usable on 375px width
- [ ] All matrix combinations add correct SKU to cart
- [ ] 1 kg, 2 kg, custom qty (e.g. 3.5 kg) work
- [ ] Price matches catalog for guest
- [ ] Multiple line items in one order
- [ ] Cannot mix two condos in one cart

### 14.3 Customer data

- [ ] Guest order with name + phone + unit only
- [ ] Email optional — order completes without email
- [ ] Email provided — confirmation email sent
- [ ] Shipping address = condo address + unit number
- [ ] Snapshot table populated correctly after order

### 14.4 Checkout / payment

- [ ] Condo quote shows Bank In only
- [ ] Normal storefront checkout still **hides** Bank In
- [ ] Order status = `pending_payment` after placement
- [ ] Bank instructions appear on confirmation / email / PDF

### 14.5 Isolation (critical)

- [ ] Condo order does **not** create reward ledger entry
- [ ] Condo order does **not** trigger restaurant commission
- [ ] Condo order does **not** trigger premium activation
- [ ] Customer group unchanged for guest

### 14.6 Admin reports

- [ ] Orders appear filtered by condo
- [ ] Packing list sorted by unit number
- [ ] CSV export opens correctly in Excel
- [ ] Paid vs pending counts accurate
- [ ] Total kg by product matches order items
- [ ] Payment verify updates status

### 14.7 Scale smoke test

- [ ] Create 10 condo records; all URLs resolve independently
- [ ] Place 20 test orders across 3 condos; reports filter correctly

---

## 15. Module file structure (reference)

```
app/code/LivePrawn/CondoGroupBuy/
├── Api/
│   ├── CondoGroupRepositoryInterface.php
│   ├── CondoGroupOrderRepositoryInterface.php
│   └── Data/
├── Block/
│   ├── Adminhtml/
│   └── GroupBuy/
├── Controller/
│   ├── Adminhtml/
│   │   ├── CondoGroup/
│   │   ├── Report/
│   │   └── PackingList/
│   ├── GroupBuy/
│   │   └── View.php
│   └── Ajax/
│       └── AddToCart.php
├── Model/
│   ├── CondoGroup.php
│   ├── CondoGroupOrder.php
│   ├── CondoProductMap.php
│   ├── Router/GroupBuyRouter.php
│   ├── Quote/CondoQuoteManager.php
│   └── ResourceModel/
├── Observer/
│   └── SaveCondoGroupOrderSnapshot.php
├── Plugin/
│   └── Payment/AllowBankTransferForCondoGroupBuy.php
├── Setup/Patch/Data/
│   └── SeedCondoProductMap.php
├── Ui/Component/Listing/
├── view/
│   ├── adminhtml/
│   └── frontend/
└── etc/
    ├── module.xml
    ├── db_schema.xml
    ├── di.xml
    ├── events.xml
    ├── frontend/routes.xml
    ├── adminhtml/routes.xml
    ├── adminhtml/menu.xml
    ├── adminhtml/system.xml
    └── acl.xml
```

---

## 16. Open questions for business sign-off

1. **One order per unit per batch** — allow multiple orders from same unit/phone, or warn admin?
2. **Guest pricing** — always non-member price, or condo-specific flat price later?
3. **Minimum group kg** — hard block or informational in Phase 1?
4. **Shipping fee** — free delivery to condo lobby, or flat fee per order?
5. **Order cutoff** — hard stop vs allow admin manual extension?
6. **Same slug recurring** — new record per batch, or reactivate existing condo record?
7. **Stock** — manage inventory on simple products, or unlimited for group buy?

---

## 17. Related documents

| Document | Path |
|----------|------|
| Customer Rewards plan (group buy rewards deferred) | `airbenih_stg/docs/live-prawn-customer-rewards-implementation-plan.md` |
| Staging setup | `airbenih_stg/docs/live-prawn-staging-setup-plan.md` |
| Prawn SKU deploy script | `airbenih_stg/scripts/deploy-prawn-product-names-urls.php` |
| Bank In config | `airbenih_stg/scripts/deploy-bank-in-payment.php` |

---

## 18. Batch 2A — Product Matrix Inspection (2026-07-03)

**Environment:** liveprawn.com production DB (`airbenih_stg`)  
**Scope:** Read-only catalog inspection. No code, price, checkout, or order changes.  
**Batch 1 status:** Complete — module enabled, `liveprawn_condo_group` live, test condo inactive.

### 18.1 Full product inventory (IDs 75–94)

All 20 expected products exist. **No duplicates** found outside this ID range for prawn SKU prefixes.

| ID | SKU | Name | Status | Visibility | Base price (RM/kg) | In stock | Website | Category |
|----|-----|------|--------|------------|-------------------|----------|---------|----------|
| 75 | frozen-tiger-prawn-40-45 | Frozen Tiger Prawn 40 to 45 | Enabled | Catalog, Search | 48.00 | Yes (1000) | 1 | BUY FROZEN PRAWN (3) |
| 76 | frozen-tiger-prawn-30plus | Frozen Tiger Prawn 31+ / 35+ | Enabled | Catalog, Search | 55.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 77 | frozen-tiger-prawn-25plus | Frozen Tiger Prawn 26+ / 30+ | Enabled | Catalog, Search | 65.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 78 | frozen-tiger-prawn-20 | Frozen Tiger Prawn 21 to 25+ | Enabled | Catalog, Search | 75.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 79 | frozen-tiger-prawn-15plus | Frozen Tiger Prawn 15+ | Enabled | Catalog, Search | 99.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 80 | frozen-vannamei-40-45 | Frozen Vannamei 40 to 45 | Enabled | Catalog, Search | 48.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 81 | frozen-vannamei-30plus | Frozen Vannamei 31+ / 35+ | Enabled | Catalog, Search | 55.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 82 | frozen-vannamei-25plus | Frozen Vannamei 26+ / 30+ | Enabled | Catalog, Search | 65.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 83 | frozen-vannamei-20 | Frozen Vannamei 21 to 25+ | Enabled | Catalog, Search | 75.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 84 | frozen-vannamei-15plus | Frozen Vannamei 15+ | Enabled | Catalog, Search | 99.00 | Yes | 1 | BUY FROZEN PRAWN (3) |
| 85 | live-vannamei-40-45 | Live Vannamei 40 to 45 | Enabled | Catalog, Search | 51.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 86 | live-vannamei-30plus | Live Vannamei 31+ / 35+ | Enabled | Catalog, Search | 58.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 87 | live-vannamei-25plus | Live Vannamei 26+ / 30+ | Enabled | Catalog, Search | 69.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 88 | live-vannamei-20 | Live Vannamei 21 to 25+ | Enabled | Catalog, Search | 79.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 89 | live-vannamei-15plus | Live Vannamei 15+ | Enabled | Catalog, Search | 104.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 90 | live-tiger-prawn-40-45 | Live Tiger Prawn 40 to 45 | Enabled | Catalog, Search | 51.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 91 | live-tiger-prawn-30plus | Live Tiger Prawn 31+ / 35+ | Enabled | Catalog, Search | 58.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 92 | live-tiger-prawn-25plus | Live Tiger Prawn 26+ / 30+ | Enabled | Catalog, Search | 69.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 93 | live-tiger-prawn-20 | Live Tiger Prawn 21 to 25+ | Enabled | Catalog, Search | 79.00 | Yes | 1 | BUY LIVE PRAWN (4) |
| 94 | live-tiger-prawn-15plus | Live Tiger Prawn 15+ | Enabled | Catalog, Search | 104.00 | Yes | 1 | BUY LIVE PRAWN (4) |

**Salable:** All 20 are simple products, `manage_stock=1`, `is_in_stock=1`, qty 1000 each.

**Tier prices:** All 20 have member tier prices for groups General (1), Distribution (4), Free Member (5), Premium (6), VIP Silver/Gold/Platinum (7–9), Restaurant (10). Batch 2B must **read price from catalog at render time** — do not hardcode prices in map table.

### 18.2 Detected matrix fields (SKU → map keys)

SKU pattern: `{product_state}-{prawn_type_line}-{size_suffix}`

| product_state | prawn_type | SKU prefix | Name prefix |
|---------------|------------|------------|-------------|
| frozen | tiger | `frozen-tiger-prawn-` | Frozen Tiger Prawn |
| frozen | vannamei | `frozen-vannamei-` | Frozen Vannamei |
| live | vannamei | `live-vannamei-` | Live Vannamei |
| live | tiger | `live-tiger-prawn-` | Live Tiger Prawn |

| size_band (map key) | SKU suffix | Display label (from name) | Sort order |
|---------------------|------------|----------------------------|------------|
| `40_to_45` | `40-45` | 40 to 45 | 10 |
| `31_35` | `30plus` | 31+ / 35+ | 20 |
| `26_30` | `25plus` | 26+ / 30+ | 30 |
| `21_25` | `20` | 21 to 25+ | 40 |
| `15_plus` | `15plus` | 15+ | 50 |

**Note:** SKU suffix `20` maps to display "21 to 25+" and `30plus`/`25plus`/`15plus` do not match display text literally — mapping table avoids fragile runtime parsing.

### 18.3 Automatic mapping feasibility

| Method | Verdict |
|--------|---------|
| **A. Hard-map 20 products (data patch)** | **Recommended for launch** — deterministic, auditable |
| **B. Admin mapping UI** | **Defer to Batch 2C+** — useful for catalog changes without deploy |
| **C. Auto-detect from names/SKUs** | **Helper/validation only** — patterns are consistent but suffix naming is non-obvious |

**Recommendation:** Mapping table seeded once via data patch; admin grid for edit/re-enable later. Optional `bin/magento` or script to validate map rows still match live SKUs.

### 18.4 Duplicates and ambiguities

| Issue | Severity | Detail |
|-------|----------|--------|
| Duplicate SKUs outside 75–94 | None | No extra prawn products found |
| Ambiguous size suffix `20` | Low | Means "21 to 25+" band — document in map, not auto-parse |
| Live vs frozen price difference | Info | Live +RM3–5/kg vs frozen at same size band — expected |
| Tier vs base price for guests | Medium | Guest checkout shows **base price** unless logged in as member group |
| Custom kg qty | Deferred | `weight=1`, no `qty_increments` — 1kg/2kg buttons in Batch 2B; custom kg in Batch 2C |
| Stock qty 1000 | Info | Sufficient for group buy; no inventory block expected |

### 18.5 Proposed seed matrix (20 rows)

| prawn_type | product_state | size_band | product_id | sku_snapshot | sort_order |
|------------|---------------|-----------|------------|--------------|------------|
| tiger | frozen | 40_to_45 | 75 | frozen-tiger-prawn-40-45 | 10 |
| tiger | frozen | 31_35 | 76 | frozen-tiger-prawn-30plus | 20 |
| tiger | frozen | 26_30 | 77 | frozen-tiger-prawn-25plus | 30 |
| tiger | frozen | 21_25 | 78 | frozen-tiger-prawn-20 | 40 |
| tiger | frozen | 15_plus | 79 | frozen-tiger-prawn-15plus | 50 |
| vannamei | frozen | 40_to_45 | 80 | frozen-vannamei-40-45 | 60 |
| vannamei | frozen | 31_35 | 81 | frozen-vannamei-30plus | 70 |
| vannamei | frozen | 26_30 | 82 | frozen-vannamei-25plus | 80 |
| vannamei | frozen | 21_25 | 83 | frozen-vannamei-20 | 90 |
| vannamei | frozen | 15_plus | 84 | frozen-vannamei-15plus | 100 |
| vannamei | live | 40_to_45 | 85 | live-vannamei-40-45 | 110 |
| vannamei | live | 31_35 | 86 | live-vannamei-30plus | 120 |
| vannamei | live | 26_30 | 87 | live-vannamei-25plus | 130 |
| vannamei | live | 21_25 | 88 | live-vannamei-20 | 140 |
| vannamei | live | 15_plus | 89 | live-vannamei-15plus | 150 |
| tiger | live | 40_to_45 | 90 | live-tiger-prawn-40-45 | 160 |
| tiger | live | 31_35 | 91 | live-tiger-prawn-30plus | 170 |
| tiger | live | 26_30 | 92 | live-tiger-prawn-25plus | 180 |
| tiger | live | 21_25 | 93 | live-tiger-prawn-20 | 190 |
| tiger | live | 15_plus | 94 | live-tiger-prawn-15plus | 200 |

### 18.6 Frontend display design (Batch 2B)

Replace "Ordering form coming soon" with a read-only matrix table (add-to-cart still Batch 2C):

```
| Prawn Type | Size       | Frozen/Live | Price/kg | Qty        |
|------------|------------|-------------|----------|------------|
| Tiger      | 40 to 45   | Frozen      | RM48.00  | [1kg][2kg] |
| Tiger      | 31+ / 35+  | Frozen      | RM55.00  | [1kg][2kg] |
| ...        | ...        | ...         | ...      | ...        |
```

- Group rows: **Frozen** block then **Live** block (or filter tabs: Type × State).
- Price loaded via `ProductRepository` + `PriceInfo` for current customer context (guest = base price).
- Qty buttons disabled/non-functional in Batch 2B if add-to-cart not in scope; labels shown as UI preview only.
- Mobile: card layout, one product per card.

### 18.7 Batch 2B scope recommendation

| In scope (2B) | Out of scope (2C+) |
|---------------|-------------------|
| Create `liveprawn_condo_product_map` table | Add to cart |
| Seed 20-row data patch | Checkout tagging |
| Load matrix on group-buy page | Custom kg input |
| Display price from catalog (read-only) | Bank In payment |
| Admin read-only map grid (optional) | Admin map edit UI |

### 18.8 Safe to proceed?

**Yes — safe to proceed to Batch 2B** with these guardrails:

1. Do not modify product prices or tier prices.
2. Do not enable add-to-cart until Batch 2C is explicitly approved.
3. Re-apply `dev:www-data` permissions on `generated/` after any `setup:upgrade`.
4. Display guest base price unless business confirms member pricing for condo QR flow.
5. Validate all 20 map rows resolve to enabled, in-stock products before seed patch runs.

---

*Batch 1 implemented 2026-07-03. Batch 2A inspection complete 2026-07-03. Batch 2B not yet implemented.*

---

## 19. Batch 3E hotfix — Admin order view (2026-07-05)

### Symptom

Admin → Sales → Orders → View on order **000000017** showed *"Exception occurred during order load"*. Order appeared in grid but view page failed.

### Root cause

**Not CondoGroupBuy data, Amasty Dropshipping, or missing order fields.**

Missing generated class in developer mode:

`Magento\Sales\Model\Order\Payment\TransactionFactory`

Stack trace (from `var/log/exception.log`):

- `Magento\Sales\Model\ResourceModel\Transaction\Grid\TypeList::toOptionArray()`
- → `Transaction\Repository::create()`
- → `Metadata::getNewInstance()`
- → **TransactionFactory does not exist**

Triggered during admin `sales_order_view` layout generation (payment transactions grid block arguments), before any CondoGroupBuy block renders.

### Order 000000017 data integrity

Inspected — **no repair needed**:

| Area | Status |
|------|--------|
| `sales_order` condo fields | All present (`is_condo_group_buy=1`, TEST-01, buyer, phone, etc.) |
| Billing/shipping addresses | Both exist, MY address with unit in street |
| Order item | `frozen-tiger-prawn-40-45` × 1 kg @ RM 48 |
| Payment | `banktransfer`, amount_ordered=48, amount_paid=NULL |
| Snapshot | Linked (order_id=8, quote_id=59, status=placed) |
| Invoices/shipments | 0 |

### Fix applied

1. **Regenerated** `generated/code/Magento/Sales/Model/Order/Payment/TransactionFactory.php` as `www-data` with correct `generated/` permissions.
2. **Defensive hardening** of `CondoInfo` admin block + template (try/catch, N/A fallbacks, graceful empty state) — does not fix this incident but prevents future admin breakage from missing condo fields.

### Defensive rule (admin order blocks)

All CondoGroupBuy admin order view code must:

- Use `current_order` from registry; never call `OrderRepository::get()` inside blocks
- Never assume condo fields or snapshot rows exist
- Catch missing data safely; show "N/A" or "not available"
- Never throw from admin order view blocks

### Files changed

- `app/code/LivePrawn/CondoGroupBuy/Block/Adminhtml/Order/CondoInfo.php` — defensive getters
- `app/code/LivePrawn/CondoGroupBuy/view/adminhtml/templates/order/condo_info.phtml` — empty-state message
- `generated/code/Magento/Sales/Model/Order/Payment/TransactionFactory.php` — regenerated (runtime)

### Verification

- Admin order view layout generates without exception (with admin session)
- Order/payment/invoice data unchanged
- Packing list still shows order 000000017
- Site/admin HTTP 200

---

## 20. Post-hotfix checkpoint — Order 000000017 (2026-07-05)

**Inspect-only.** No orders, invoices, payments, rewards, or prices changed.

### Checkpoint results

| # | Check | Result |
|---|-------|--------|
| 1 | Admin order grid loads | **Pass** — `/admin/sales/order/` HTTP 200 |
| 2 | Order 000000017 View loads | **Pass** — `sales_order_view` layout generates OK (post-hotfix) |
| 3 | Order status | **processing** (verified via Batch 3E payment verification) |
| 4 | Payment method | **Bank In** (`banktransfer`, amount_paid NULL) |
| 5 | Condo Group Buy info | **Pass** — Condo: Test Condo Group Buy, Unit: TEST-01, Buyer: Test Condo Buyer, Phone: +601111311660, Collection: Lobby |
| 6 | Packing list | **Pass** — 1 row for 000000017 |
| 7 | CSV export | **Pass** — row present; no cost/margin columns |
| 8 | Invoice created | **No** (0 invoices) |
| 9 | Rewards ledger | **Unchanged** (2 rows) |
| 10 | Restaurant commission | **Unchanged** (0 rows) |
| 11 | Site/admin HTTP | **200 / 200** |

### What caused the order load error

Missing generated Magento class `Magento\Sales\Model\Order\Payment\TransactionFactory` during admin order view layout build (payment transactions grid). **Not** caused by missing condo order data.

### What was fixed

| Item | Action |
|------|--------|
| `generated/code/Magento/Sales/Model/Order/Payment/TransactionFactory.php` | Regenerated + permissions fixed |
| `CondoInfo` admin block + template | Defensive coding (N/A fallbacks, no throws) |
| Order 000000017 data | **No data repair needed** — fields were already correct |

### Rule for future condo order admin blocks

> **Never let missing condo data break admin order view.**

Implementation: use registry `current_order` only; never re-load via `OrderRepository` in blocks; show "N/A" / "not available" when fields or snapshot are missing; catch all errors safely.

### Remaining blockers

1. **Developer mode generated code fragility** — missing factories can break admin pages; monitor `generated/` permissions after deploys.
2. **Quote→order condo copy** — `CopyCondoDataOnQuoteSubmit` added in Batch 3E; order 000000017 was manually repaired at placement time.
3. **Browser confirmation** — checkpoint verified via DB + layout bootstrap; Kenny should confirm View page visually in logged-in admin.
## 21. Batch 3F — Payment verification action (2026-07-05)

**Scope:** Admin "Mark Payment Verified" for condo Bank In orders only. No schema changes.

### Implementation

| Component | Purpose |
|-----------|---------|
| `Model/Order/PaymentVerificationService.php` | Eligibility checks, status→Processing, order comment, no invoice/capture |
| `Controller/Adminhtml/Packinglist/MarkVerified.php` | Admin action with confirm + redirect |
| `Block/Adminhtml/Packinglist/Report.php` | Button visibility + Payment Verification column |
| `view/adminhtml/templates/packinglist/report.phtml` | Report UI, confirm dialog, packing list status |
| `Model/Report/OrderReportProvider.php` | `payment_verification_label` on rows |
| `Controller/Adminhtml/Packinglist/Export.php` | CSV includes Payment Verification column |

### Eligibility (Mark Payment Verified)

- `is_condo_group_buy = 1`
- Payment method = `banktransfer` (Bank In)
- Status = `pending_payment` or `pending`
- State not in `canceled`, `closed`, `complete`, `holded`
- Not already verified (Processing + Bank In)

### Behavior

1. Adds comment: *"Bank In payment verified by admin."*
2. Sets status/state to **Processing** via `saveAttribute()` (no full order save)
3. **No** invoice, **no** payment capture, **no** customer email (`is_customer_notified = 0`)
4. Report/packing list/CSV show **Payment Verification: Pending / Verified**

### Payment verification inference (no schema)

| Condition | Display |
|-----------|---------|
| Bank In + `pending_payment` / `pending` | Pending |
| Bank In + `processing` | Verified |
| Other | N/A |

### Test order 000000017

Already verified in Batch 3E (`processing`, comment present). Batch 3F **did not re-verify** — button hidden, verify action rejects with notice.

### Verification results

| Check | Result |
|-------|--------|
| Condo report loads | Pass |
| 000000017 shows Verified, button hidden | Pass |
| Verify rejected without duplicate comment | Pass |
| Invoice created | No |
| Payment captured | No (`amount_paid` NULL) |
| CSV Payment Verification column | Verified |
| Admin order view | Pass |
| Normal orders | `canVerify` = false |
| Site/admin HTTP | 200 / 200 |

### Schema changed

**No** — order comment + status only.

---

## 22. Hotfix — Condo Group Buy admin grid Amasty Rolepermissions (2026-07-05)

### Symptom

Admin → **Condo Group Buy** failed with:

`Amasty\Rolepermissions\Plugin\Ui\DataProvider::afterGetSearchResult(): Argument #2 ($result) must be of type Magento\Framework\Api\SearchResultsInterface, LivePrawn\CondoGroupBuy\Model\ResourceModel\CondoGroup\Grid\Collection\Interceptor given`

### Root cause

`Model/ResourceModel/CondoGroup/Grid/Collection.php` extended `CondoGroup\Collection` (plain `AbstractCollection`) instead of Magento's UI grid `SearchResult`. Amasty Rolepermissions plugins all UI `DataProvider::getSearchResult()` and requires `SearchResultsInterface`.

### Fix (Option A — SearchResult)

Changed grid collection to extend `Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult`. Existing `etc/di.xml` `mainTable` / `resourceModel` args were already correct.

### Files changed

- `Model/ResourceModel/CondoGroup/Grid/Collection.php`

### Amasty files touched

**None** — Rolepermissions not disabled or modified.

### Verification

| Check | Result |
|-------|--------|
| Grid collection implements SearchResultsInterface | Pass |
| DataProvider getSearchResult() Amasty-compatible | Pass |
| Condo group rows load | 2 |
| Product matrix counts | Active 20 / Total 20 |
| Order 000000017 admin view | Pass |
| Site/admin/group-buy HTTP | 200 |

---

## 23. Product matrix selection fix (2026-07-05)

### Root cause

Frontend **already** loaded products via `MatrixProvider` → `liveprawn_condo_product_map` (not a full catalog collection). Active catalog has **22** enabled products; map has **20**. Non-prawn products (`premium-member-activation`, `Marketing Contract 24 months`) were never in the map table.

This batch **hardens** map-only selection and adds admin management:

- Skip inactive mappings
- Skip disabled / non-salable catalog products (do not show as rows)
- Remove frontend **Status** column
- Add **Live Prawn → Product Matrix** admin page with enable/disable per mapping

### Files changed

| File | Change |
|------|--------|
| `Model/ProductMap/MatrixProvider.php` | Map-only; hide non-salable/disabled catalog products |
| `Model/ProductMap/AdminGridProvider.php` | Admin list with catalog join |
| `Model/ProductMap/StatusToggle.php` | Enable/disable mapping status |
| `Controller/Adminhtml/ProductMatrix/*` | Index + ToggleStatus |
| `Block/Adminhtml/ProductMatrix/Grid.php` | Admin grid block |
| `view/adminhtml/templates/product_matrix/grid.phtml` | Admin UI |
| `view/frontend/templates/groupbuy/view.phtml` | Remove Status column |
| `view/adminhtml/templates/condo_group/product_map_summary.phtml` | Link to Product Matrix |
| `etc/adminhtml/menu.xml`, `etc/acl.xml` | Product Matrix menu + ACL |

### Verification

| Check | Result |
|-------|--------|
| `/group-buy/test-condo` product rows | 20 |
| Active map rows | 20 |
| Marketing Contract / Premium Member / CNY / deposit | Not shown |
| Status column | Removed |
| Site/admin HTTP | 200 |

---

## 25. Per-condo product assignment (2026-07-05)

### Summary

Replaced global matrix on frontend with **per-condo product assignments** via new table `liveprawn_condo_group_product`. Global `liveprawn_condo_product_map` (20 rows) retained for reference only — **no frontend fallback**.

### Schema

**Yes** — table `liveprawn_condo_group_product` with unique `(condo_group_id, product_id)`.

### Admin

Condo Group **Edit** page → section **Products for this Condo Group**:

- POST product search by name/SKU (`CatalogSearchProvider` + product collection)
- Add assignment with display label, custom price override (display only), min qty, qty options, sort order
- Enable/disable assignment per row

### Frontend

`/group-buy/{slug}` shows only active assignments for that condo where catalog product is enabled/saleable.

### Custom price

**Display only** this batch — shown on group-buy page if set; cart uses Magento base price.

### Test Condo Active seed

3 products: Frozen Vannamei 40-45, Live Vannamei 40-45, Frozen Tiger Prawn 40-45.

### Add to cart

Uses `assignment_id` (not global `map_id`).

---

## 24. Hotfix — Product Matrix admin 404 (2026-07-05)

### Symptom

Admin URL `liveprawn_groupbuy/productmatrix/index` returned Magento 404 after Product Matrix controllers were added.

### Root cause

**Stale `app_action_list` cache** — Magento's action router cache was built before `Controller/Adminhtml/ProductMatrix/*` existed. Route/menu/ACL/layout were already aligned; controller class was not registered in cached action list.

| Item | Value |
|------|-------|
| frontName | `liveprawn_groupbuy` |
| menu action | `liveprawn_groupbuy/productmatrix/index` |
| controller | `LivePrawn\CondoGroupBuy\Controller\Adminhtml\ProductMatrix\Index` |
| ACL | `LivePrawn_CondoGroupBuy::product_matrix` |
| layout handle | `liveprawn_groupbuy_productmatrix_index` |

No route/menu mismatch. No UI component grid (simple block/template).

### Fix

`php bin/magento cache:flush` (or `cache:clean config layout block_html full_page`) to rebuild `app_action_list`.

### Verification

| Check | Result |
|-------|--------|
| ActionList resolves productmatrix | Pass |
| Admin grid rows | 20 |
| Layout handle | Pass |
| Frontend matrix rows | 20 |
| Orders/customers unchanged | 8 / 5 |

---
