# Validation Report: URS-061

**Title:** LOT policy defined per product with dropdown
**Date:** 2026-04-23T03:43:54.023Z
**Duration:** 122.3s
**Overall Status:** ✅ PASS

## User Requirement

> The system shall support product‑specific LOT selection in accordance with defined LOT policies.

*Source: `User_Requirement_Specifications_ZuriMED_DeviceFlow.xlsx` — the run below proves the system meets this requirement.*

## Environment

- **Inbox URL:** http://localhost:65101
- **Database:** localhost:65102/cc_repinbox_dev

## Setup

Status: ✅ PASS

## Test Steps

Each step below corresponds to one Playwright test that ran sequentially. Screenshots and video recordings provide visual evidence of the UI behaviour.

### 1. Step 1: Open Products list — ✅ PASS

**What this step proves:**

A ZuriMED admin logs in and opens the Products settings page. This confirms that the product management interface is accessible and that the products list renders for an admin user.

**Screenshots:**

![step 01 logged in](screenshots/step-01-logged-in.png)

![step 01 products list](screenshots/step-01-products-list.png)

**Video recording:**

[▶ Watch step recording](videos/step-01-open-products.webm)

---

### 2. Step 2: Open Product A — ✅ PASS

**What this step proves:**

The admin navigates to the SpeedPatch product settings page and confirms the Tracking Policy field is visible and set to "Any Lot" by default. This establishes the initial state before any policy change.

**Screenshots:**

![step 02 product a page](screenshots/step-02-product-a-page.png)

![step 02 tracking policy default](screenshots/step-02-tracking-policy-default.png)

**Video recording:**

[▶ Watch step recording](videos/step-02-open-product-a.webm)

---

### 3. Step 3: All policy options visible — ✅ PASS

**What this step proves:**

The admin opens the Tracking Policy dropdown and confirms all four policy options are available: "Any Lot", "Approved Lots Only", "No Lot", and "Use Serial Numbers". This demonstrates that the system supports multiple per-product tracking policies.

**Screenshots:**

![step 03 tracking policy options visible](screenshots/step-03-tracking-policy-options-visible.png)

**Video recording:**

[▶ Watch step recording](videos/step-03-tracking-policy-options.webm)

---

### 4. Step 4a: Approved Lots Only selected — ✅ PASS

**What this step proves:**

The "Approved Lots Only" policy is selected for Product A (SpeedPatch), changing the lot tracking mode from free-text entry to a managed dropdown of pre-approved lot values.

**Screenshots:**

![step 04 policy approved selected](screenshots/step-04-policy-approved-selected.png)

---

### 5. Step 4b: Approved policy saved — ✅ PASS

**What this step proves:**

The product form is saved and the page reloads, confirming that the "Approved Lots Only" policy is persisted for Product A. A Configured Lots section now appears so approved lot values can be added.

**Audit events generated by this step:**

*(Evidence matched by declared name — step timing not available or no events fell in window)*

| Time | Type | Action | User | Org | Performed |
|------|------|--------|------|-----|-----------|
| 2026-04-23 03:44:27Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | yes |
| 2026-04-23 03:45:14Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | yes |
| 2026-04-23 03:45:35Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | yes |
| 2026-04-23 03:45:56Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | yes |

**Screenshots:**

![step 04 policy saved](screenshots/step-04-policy-saved.png)

---

### 6. Step 5: Add approved lots — ✅ PASS

**What this step proves:**

Two approved lot values (URS061-BATCH-2025-Q1 and URS061-BATCH-2025-Q2) are added to Product A. Only these values will be available in the lot dropdown when creating an order for this product.

**Audit events generated by this step:**

*(Evidence scoped to step execution window: 2026-04-23T03:44:38.365Z → 2026-04-23T03:44:39.607Z)*

| Time | Type | Action | User | Org | Performed |
|------|------|--------|------|-----|-----------|
| 2026-04-23 03:44:39Z | decision | product.allowed_lot.add | alex.admin@zurimed.com | ZuriMED | yes |

**Screenshots:**

![step 05 first allowed lot added](screenshots/step-05-first-allowed-lot-added.png)

![step 05 both allowed lots added](screenshots/step-05-both-allowed-lots-added.png)

**Video recording:**

[▶ Watch step recording](videos/step-05-add-approved-lots.webm)

---

### 7. Step 6: Product B default policy — ✅ PASS

**What this step proves:**

Product B (FiberLocker) is confirmed to still have the default "Any Lot" policy, providing a contrast that demonstrates policies are tracked per product independently.

**Screenshots:**

![step 06 product b any lot](screenshots/step-06-product-b-any-lot.png)

**Video recording:**

[▶ Watch step recording](videos/step-06-product-b-default.webm)

---

### 8. Step 7: LOT enforcement — ✅ PASS

**What this step proves:**

A sales rep starts a bill-only order and adds both products. SpeedPatch (Approved Lots Only) renders a dropdown restricted to the two configured lot values, while FiberLocker (Any Lot) renders a free-text input. This confirms the per-product LOT policy drives the downstream UI.

**Screenshots:**

![step 07 devices used after quantity](screenshots/step-07-devices-used-after-quantity.png)

![step 07 approved lots dropdown open](screenshots/step-07-approved-lots-dropdown-open.png)

![step 07 approved lot selected](screenshots/step-07-approved-lot-selected.png)

**Video recording:**

[▶ Watch step recording](videos/step-07-order-enforcement.webm)

---

### 9. Step 8: No Lot policy suppresses lot input — ✅ PASS

**What this step proves:**

Product A is switched to the "No Lot" policy. When a sales rep adds SpeedPatch to a bill-only order, no lot or serial field is rendered on the SpeedPatch row. FiberLocker (still "Any Lot") continues to render its free-text lot input, confirming each product's policy is evaluated independently at order-entry time.

**Audit events generated by this step:**

*(Evidence scoped to step execution window: 2026-04-23T03:45:14.547Z → 2026-04-23T03:45:24.124Z)*

| Time | Type | Action | User | Org | Performed |
|------|------|--------|------|-----|-----------|
| 2026-04-23 03:45:15Z | user_log | user:login | bob.kauffman@stellartech.com | StellarTech Medical Solutions | — |

**Screenshots:**

![step 08 no lot policy saved](screenshots/step-08-no-lot-policy-saved.png)

![step 08 no lot order ui](screenshots/step-08-no-lot-order-ui.png)

---

### 10. Step 9: Serial Numbers policy shows serial input — ✅ PASS

**What this step proves:**

Product A is switched to the "Use Serial Numbers" policy. When the sales rep adds SpeedPatch to a bill-only order, the lot input is replaced with a serial-number input (placeholder "Enter serial number..."). After the evidence is captured, Product A is restored to "Approved Lots Only" so the final database validation phase matches the persisted state established in Steps 4–5.

**Audit events generated by this step:**

*(Evidence scoped to step execution window: 2026-04-23T03:45:35.219Z → 2026-04-23T03:45:56.226Z)*

| Time | Type | Action | User | Org | Performed |
|------|------|--------|------|-----|-----------|
| 2026-04-23 03:45:36Z | user_log | user:login | bob.kauffman@stellartech.com | StellarTech Medical Solutions | — |
| 2026-04-23 03:45:49Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — |
| 2026-04-23 03:45:56Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | yes |

**Screenshots:**

![step 09 serial policy saved](screenshots/step-09-serial-policy-saved.png)

![step 09 serial order ui](screenshots/step-09-serial-order-ui.png)

![step 09 approved policy restored](screenshots/step-09-approved-policy-restored.png)

---

## Database Validations

The following SQL queries ran against the application database after the Playwright scenarios completed. Each query asserts a specific condition that proves the feature under test persisted its data correctly.

### Product A (SpeedPatch) lot_policy = "approved" — ✅ PASS

**Assertion:** Row for SKU SP019N1A should have lot_policy = 'approved'

```sql
SELECT id, sku, title, lot_policy, updated_at
      FROM org_products
      WHERE organization_id = $1 AND sku = $2
```

| id | sku | title | lot_policy | updated_at |
| --- | --- | --- | --- | --- |
| 01989ca2-6a54-7834-8376-06a0251bacd8 | SP019N1A | SpeedPatch® PET | approved | 2026-04-23T03:45:56.076Z |

### Product B (FiberLocker) lot_policy = "any" — ✅ PASS

**Assertion:** Row for SKU FL516SNA should have lot_policy = 'any'

```sql
SELECT id, sku, title, lot_policy, updated_at
      FROM org_products
      WHERE organization_id = $1 AND sku = $2
```

| id | sku | title | lot_policy | updated_at |
| --- | --- | --- | --- | --- |
| 01989ca1-f2f8-7ab7-8269-7179342797cc | FL516SNA | FiberLocker® Instrument SN | any | 2026-04-23T03:43:54.075Z |

### Approved lots saved for Product A — ✅ PASS

**Assertion:** URS061-prefixed allowed-lot rows for SpeedPatch should be exactly URS061-BATCH-2025Q1, URS061-BATCH-2025Q2

```sql
SELECT pal.lot_value, pal.comment, pal.expiration
      FROM org_product_allowed_lots pal
      JOIN org_products p ON p.id = pal.org_product_id
      WHERE p.organization_id = $1 AND p.sku = $2 AND pal.lot_value LIKE 'URS061-%'
      ORDER BY pal.lot_value
```

| lot_value | comment | expiration |
| --- | --- | --- |
| URS061-BATCH-2025Q1 | NULL | NULL |
| URS061-BATCH-2025Q2 | NULL | NULL |

### Product B has no URS061- allowed lots — ✅ PASS

**Assertion:** FiberLocker should have no test-prefixed allowed-lot rows (contrast case)

```sql
SELECT pal.lot_value
      FROM org_product_allowed_lots pal
      JOIN org_products p ON p.id = pal.org_product_id
      WHERE p.organization_id = $1 AND p.sku = $2 AND pal.lot_value LIKE 'URS061-%'
```

*No rows returned*

## Audit & Email Assertion Ledger

Per-declaration outcome of every `expectedAuditActions` and `expectedEmailTemplates` entry written into the orchestrator. Missing evidence here is a real test failure, not a soft warning.

### Audit Action Assertions

Each row asserts that a declared `expectedAuditActions` entry produced a matching row in `audit_events`. A ❌ flips overall status to FAIL — the declaration is real proof, not just an annotation.

| Step | Expected Audit Action | Found |
|------|-----------------------|-------|
| Step 4b: Approved policy saved | `decision:product.lot_policy.update` | ✅ |
| Step 5: Add approved lots | `decision:product.allowed_lot.add` | ✅ |
| Step 8: No Lot policy suppresses lot input | `decision:product.lot_policy.update` | ✅ |
| Step 9: Serial Numbers policy shows serial input | `decision:product.lot_policy.update` | ✅ |

## Audit Log Events

Every row written to `audit_events` while this test was running (scoped to the demo organizations). Provides compliance evidence that user actions are traced end-to-end (URS-003).

**Capture window start:** 2026-04-23T03:43:52.101Z

<details><summary>Query used to capture events</summary>

```sql
SELECT
    ae.created_at,
    ae.event_type,
    ae.action,
    ae.user_id,
    u.email AS user_email,
    ae.organization_id,
    o.name AS organization_name,
    ae.object_id,
    ae.secondary_object_id,
    ae.payload,
    ae.route,
    ae.trace_id
  FROM audit_events ae
  LEFT JOIN users u ON u.id = ae.user_id
  LEFT JOIN organizations o ON o.id = ae.organization_id
  WHERE ae.created_at >= $1
    AND ae.organization_id = ANY($2::uuid[])
  ORDER BY ae.created_at ASC
```
</details>

18 event(s) captured:

| Time | Type | Action | User | Org | Object ID | Performed | Reason |
|------|------|--------|------|-----|-----------|-----------|--------|
| 2026-04-23 03:43:56Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:44:03Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:44:11Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:44:20Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:44:27Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | 01989ca2-6a54-7834-8376-06a0251bacd8 | yes | Product lot policy changed via product settings form |
| 2026-04-23 03:44:32Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:44:37Z | decision | product.allowed_lot.add | alex.admin@zurimed.com | ZuriMED | 01989ca2-6a54-7834-8376-06a0251bacd8 | yes | Approved lot value added to product |
| 2026-04-23 03:44:39Z | decision | product.allowed_lot.add | alex.admin@zurimed.com | ZuriMED | 01989ca2-6a54-7834-8376-06a0251bacd8 | yes | Approved lot value added to product |
| 2026-04-23 03:44:44Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:44:50Z | user_log | user:login | bob.kauffman@stellartech.com | StellarTech Medical Solutions | — | — |  |
| 2026-04-23 03:45:07Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:45:14Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | 01989ca2-6a54-7834-8376-06a0251bacd8 | yes | Product lot policy changed via product settings form |
| 2026-04-23 03:45:15Z | user_log | user:login | bob.kauffman@stellartech.com | StellarTech Medical Solutions | — | — |  |
| 2026-04-23 03:45:28Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:45:35Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | 01989ca2-6a54-7834-8376-06a0251bacd8 | yes | Product lot policy changed via product settings form |
| 2026-04-23 03:45:36Z | user_log | user:login | bob.kauffman@stellartech.com | StellarTech Medical Solutions | — | — |  |
| 2026-04-23 03:45:49Z | user_log | user:login | alex.admin@zurimed.com | ZuriMED | — | — |  |
| 2026-04-23 03:45:56Z | decision | product.lot_policy.update | alex.admin@zurimed.com | ZuriMED | 01989ca2-6a54-7834-8376-06a0251bacd8 | yes | Product lot policy changed via product settings form |

## Additional Video Evidence

The following screencast recordings were captured but could not be matched to a specific test step. Step-matched recordings appear inline in their respective step sections above.

- [videos/step-04-set-approved-policy.webm](videos/step-04-set-approved-policy.webm)
- [videos/step-08a-set-no-lot-policy.webm](videos/step-08a-set-no-lot-policy.webm)
- [videos/step-08b-no-lot-order-ui.webm](videos/step-08b-no-lot-order-ui.webm)
- [videos/step-09a-set-serial-policy.webm](videos/step-09a-set-serial-policy.webm)
- [videos/step-09b-serial-order-ui.webm](videos/step-09b-serial-order-ui.webm)
- [videos/step-09c-restore-approved-policy.webm](videos/step-09c-restore-approved-policy.webm)
