# Implementation Plan

## Validation Checklist

- [x] All specification file paths are correct and exist
- [x] Context priming section is complete
- [x] All implementation phases are defined
- [x] Each phase follows TDD: Prime → Test → Implement → Validate
- [x] Dependencies between phases are clear (no circular dependencies)
- [x] Parallel work is properly tagged with `[parallel: true]`
- [x] Activity hints provided for specialist selection `[activity: type]`
- [x] Every phase references relevant SDD sections
- [x] Every test references PRD acceptance criteria
- [x] Integration & E2E tests defined in final phase
- [x] Project commands match actual project setup
- [x] A developer could follow this plan independently

---

## Specification Compliance Guidelines

### How to Ensure Specification Adherence

1. **Before Each Phase**: Read the referenced SDD sections
2. **During Implementation**: Follow code examples from SDD exactly
3. **After Each Task**: Run `./test.sh --testsuite unit` to catch regressions
4. **Phase Completion**: Verify PRD acceptance criteria checklist items

### Deviation Protocol

If implementation cannot follow specification exactly:
1. Document the deviation and reason
2. Get approval before proceeding
3. Update SDD if the deviation is an improvement
4. Never deviate without documentation

---

## Context Priming

*GATE: You MUST fully read all files mentioned in this section before starting any implementation.*

**Specification**:
- `docs/specs/044-backstock-bulk-action-audit-logging/product-requirements.md` - Product Requirements
- `docs/specs/044-backstock-bulk-action-audit-logging/solution-design.md` - Solution Design

**Key Design Decisions**:
- ADR-1: Extend `bsActions` table with `detail` + `batchId` columns (not a new table)
- ADR-2: New action codes 10-15 for mass-specific operations (not reusing code 6)
- ADR-3: Detail stored as VARCHAR snapshot string (not FK references)
- ADR-4: UUID `batchId` directly in bsActions (no separate batch table)
- ADR-5: batchId generated in route handler (one per HTTP request)

**Implementation Context**:
- Test command: `./test.sh --testsuite unit`
- Targeted test: `cd userfrosting && ./vendor/bin/phpunit --filter "ActionCode"`
- Migration run: `php userfrosting/conductor run`
- Key files to modify:
  - `userfrosting/src/BuyerKiosk/Backstock/Action.php` (action model)
  - `userfrosting/src/BuyerKiosk/Backstock/BackstockFactory.php` (mass methods + logMassAction)
  - `userfrosting/routes/groups/backstock.php` (route handlers + per-bin history)
- New files:
  - `userfrosting/migrations/input/20260402_001_bsactions_audit_columns.json`
  - `tests/Unit/Backstock/ActionCodeTest.php`

**Action Code Reference** (from SDD):

| Code | Name | Used By |
|------|------|---------|
| 0 | empty | massEmptyBins (keep) |
| 1 | add_category | individual only (no change) |
| 2 | remove_some | individual only (no change) |
| 3 | remove_all_category | massChangeTags(remove) (keep, add detail) |
| 6 | add_items | individual only (no change, backward compat) |
| 7 | hide | massHideBins (keep, add display) |
| 8 | unhide | massUnhideBins (keep, add display) |
| 10 | mass_change_location | NEW |
| 11 | mass_change_category | NEW |
| 12 | mass_add_tags | NEW |
| 13 | mass_update_age | NEW |
| 14 | mass_add_notes | NEW |
| 15 | mass_print_labels | NEW |

---

## Implementation Risks & Gotchas

*From SDD "Implementation Gotchas" — each has a mitigation task in the plan.*

| Risk | Mitigation | Phase |
|------|------------|-------|
| `massPrintLabels()` has no `$userId` param | Add `$userId` + `$batchId` params to method signature | T3.3.8 |
| Tag names for detail: `massChangeTags` receives tag IDs not names | Resolve names from `categoriesArray` before building detail string | T3.3.4, T3.3.5 |
| `makeBinReadable()` only fetches LAST action — must add new codes | Add cases 7-15 + default in switch | T3.4 |
| PDO param reuse: `:detail` bound twice = HY093 error | Use unique param names in any query with detail | T3.2 (code review) |
| Existing code 6 entries remain as "added items" after migration | Expected behavior — no retroactive fix; document in README | N/A |

## Deferred Features (Not in This Plan)

These PRD features are **out of scope** for this implementation and will be addressed in a follow-up:

| PRD Feature | Reason Deferred |
|-------------|-----------------|
| Feature 3: UI collapse/expand grouping in activity feed | Backend batch grouping (batchId + batchSize) is delivered; frontend rendering changes are a separate UI task |
| Feature 6: Activity feed filtering by action type | Should-Have priority; requires new API query params + frontend filter UI |
| Feature 7: Activity feed filtering by user | Could-Have priority; requires new API query params + frontend user selector |
| PRD Tracking Events (bulk_action_logged, etc.) | Analytics instrumentation — add once we have an analytics pipeline |

---

## Implementation Phases

### Phase 1: Database Migration

- [ ] T1 Phase 1 — Add `detail` and `batchId` columns to `bsActions`

    - [ ] T1.1 Prime Context
        - [ ] T1.1.1 Read migration pattern `[ref: userfrosting/migrations/input/20251203_001_backstock_bin_hide_feature.json]`
        - [ ] T1.1.2 Read SDD Data Storage Changes section `[ref: solution-design.md; "Data Storage Changes"]`

    - [ ] T1.2 Implement Migration File `[activity: backend-migration]`
        - [ ] T1.2.1 Create `userfrosting/migrations/input/20260402_001_bsactions_audit_columns.json` with 3 operations:
            1. `ALTER TABLE bsActions ADD COLUMN detail VARCHAR(500) NULL AFTER categoryID` with check_query for column existence
            2. `ALTER TABLE bsActions ADD COLUMN batchId VARCHAR(36) NULL AFTER detail` with check_query for column existence
            3. `ALTER TABLE bsActions ADD INDEX idx_batchId (batchId)` with check_query for index existence
        - [ ] T1.2.2 All operations use `"database": "{{store}}"` (store-level table)
        - [ ] T1.2.3 Each has `check_query` checking `information_schema.COLUMNS` or `information_schema.STATISTICS`

    - [ ] T1.3 Validate
        - [ ] T1.3.1 Run `php userfrosting/conductor run` and verify migration applies without error `[activity: run-migration]`
        - [ ] T1.3.2 Verify columns exist: `DESCRIBE bsActions` shows `detail` and `batchId` columns
        - [ ] T1.3.3 Verify index exists: `SHOW INDEX FROM bsActions` shows `idx_batchId`
        - [ ] T1.3.4 Verify existing data unaffected: `SELECT COUNT(*) FROM bsActions` returns same count as before

---

### Phase 2: Action Model Enhancement

- [ ] T2 Phase 2 — Extend `Action` model with new properties and all action code renderings

    - [ ] T2.1 Prime Context
        - [ ] T2.1.1 Read Action model `[ref: userfrosting/src/BuyerKiosk/Backstock/Action.php; lines: 1-243]`
        - [ ] T2.1.2 Read SDD Application Data Models section `[ref: solution-design.md; "Application Data Models"]`
        - [ ] T2.1.3 Read SDD getReadableString example `[ref: solution-design.md; "Updated getReadableString"]`

    - [ ] T2.2 Write Tests `[activity: backend-test]`
        - [ ] T2.2.1 Create `tests/Unit/Backstock/ActionCodeTest.php`
        - [ ] T2.2.2 Test: `getReadableString()` returns non-empty string for EVERY action code (0, 1, 2, 3, 6, 7, 8, 10, 11, 12, 13, 14, 15) `[ref: PRD Feature 1 acceptance criteria]`
        - [ ] T2.2.3 Test: Code 7 returns string containing "hid" `[ref: PRD Feature 4]`
        - [ ] T2.2.4 Test: Code 8 returns string containing "unhid" `[ref: PRD Feature 4]`
        - [ ] T2.2.5 Test: Code 10 with detail "→ Back Room" returns string containing "moved" and "Back Room" `[ref: PRD Feature 1, Feature 2]`
        - [ ] T2.2.6 Test: Code 11 with detail "→ Electronics" returns string containing "category" and "Electronics"
        - [ ] T2.2.7 Test: Code 12 with detail "+Furniture, +Decor" returns string containing "tags"
        - [ ] T2.2.8 Test: Code 13 with detail "age → 2026-03-15" returns string containing "age"
        - [ ] T2.2.9 Test: Code 14 returns string containing "note"
        - [ ] T2.2.10 Test: Code 15 returns string containing "printed" or "label"
        - [ ] T2.2.11 Test: Unknown code (99) returns default "updated [bin]" string (not empty)
        - [ ] T2.2.12 Test: `createFromRow()` populates `detail` and `batchId` from row data
        - [ ] T2.2.13 Test: `isBulk()` returns true when batchId is set, false when null

    - [ ] T2.3 Implement Action Model Changes `[activity: backend-implementation]`
        - [ ] T2.3.1 Add public properties `$detail` and `$batchId` to `Action` class
        - [ ] T2.3.2 Update `createFromRow()` to populate `detail` and `batchId` from row
        - [ ] T2.3.3 Add `isBulk(): bool` method returning `$this->batchId !== null`
        - [ ] T2.3.4 Update `getReadableString()` switch statement:
            - Add case 7: `"{name}. hid {bin}"`
            - Add case 8: `"{name}. unhid {bin}"`
            - Add case 10: `"{name}. moved {bin}"` + append detail if set
            - Add case 11: `"{name}. changed category of {bin}"` + append detail if set
            - Add case 12: `"{name}. added tags to {bin}"` + append detail in parens if set
            - Add case 13: `"{name}. updated age of {bin}"` + append detail in parens if set
            - Add case 14: `"{name}. added note to {bin}"`
            - Add case 15: `"{name}. printed label for {bin}"`
            - Add default: `"{name}. updated {bin}"`

    - [ ] T2.4 Validate
        - [ ] T2.4.1 Run `cd userfrosting && ./vendor/bin/phpunit --filter "ActionCode"` — all tests pass `[activity: run-tests]`
        - [ ] T2.4.2 Run `./test.sh --testsuite unit` — no regressions `[activity: run-tests]`
        - [ ] T2.4.3 Verify backward compat: codes 0-3, 6 produce identical output to before `[activity: review-code]`

---

### Phase 3: BackstockFactory Mass Methods

- [ ] T3 Phase 3 — Update `logMassAction()` and all 9 mass methods with specific codes, detail, and batchId

    - [ ] T3.1 Prime Context
        - [ ] T3.1.1 Read logMassAction `[ref: userfrosting/src/BuyerKiosk/Backstock/BackstockFactory.php; lines: 713-728]`
        - [ ] T3.1.2 Read all 9 mass methods `[ref: BackstockFactory.php; lines: 782-1402]`
        - [ ] T3.1.3 Read SDD logMassAction example `[ref: solution-design.md; "Updated logMassAction"]`
        - [ ] T3.1.4 Read SDD Action Codes table `[ref: solution-design.md; "New Action Codes"]`

    - [ ] T3.2 Implement logMassAction Signature Change `[activity: backend-implementation]`
        - [ ] T3.2.1 Update `logMassAction()` signature to add `?string $detail = null, ?string $batchId = null`
        - [ ] T3.2.2 Add `:detail` and `:batchId` to INSERT SQL
        - [ ] T3.2.3 Add bindValue calls for both new params (handle null with PDO::PARAM_NULL)

    - [ ] T3.3 Implement Mass Method Updates `[activity: backend-implementation]`
        - [ ] T3.3.1 **massChangeLocation**: Change action code from 6 → 10, build detail string `"→ {locationName}"` from validated location row, accept + pass `$batchId` param
        - [ ] T3.3.2 **massEmptyBins**: Keep action code 0, accept + pass `$batchId` param (no detail needed)
        - [ ] T3.3.3 **massChangeCategory**: Change action code from 6 → 11, build detail string `"→ {categoryName}"` from categoriesArray lookup, accept + pass `$batchId` param
        - [ ] T3.3.4 **massChangeTags(add)**: Change action code from 6 → 12, build detail string from tag names (e.g., `"+Furniture, +Decor"`), accept + pass `$batchId` param
        - [ ] T3.3.5 **massChangeTags(remove)**: Keep action code 3, build detail string from tag names (e.g., `"-Furniture"`), accept + pass `$batchId` param
        - [ ] T3.3.6 **massHideBins**: Keep action code 7, accept + pass `$batchId` param (no detail needed)
        - [ ] T3.3.7 **massUnhideBins**: Keep action code 8, accept + pass `$batchId` param (no detail needed)
        - [ ] T3.3.8 **massPrintLabels**: Add `$userId` and `$batchId` params, add `logMassAction()` call per bin with action code 15 and detail `"qty: {n}"`
        - [ ] T3.3.9 **massUpdateAge**: Change action code from 6 → 13, build detail string `"age → {date}"`, accept + pass `$batchId` param
        - [ ] T3.3.10 **massAddNotes**: Change action code from 6 → 14, accept + pass `$batchId` param (no detail — note content is in the bin itself)

    - [ ] T3.4 Update makeBinReadable Action Switch `[activity: backend-implementation]`
        - [ ] T3.4.1 Read existing switch `[ref: BackstockFactory.php; lines: 429-466]`
        - [ ] T3.4.2 Add case 7: actionStringAction = "Hid"
        - [ ] T3.4.3 Add case 8: actionStringAction = "Unhid"
        - [ ] T3.4.4 Add case 10: actionStringAction = "Moved"
        - [ ] T3.4.5 Add case 11: actionStringAction = "Changed Category"
        - [ ] T3.4.6 Add case 12: actionStringAction = "Added Tags"
        - [ ] T3.4.7 Add case 13: actionStringAction = "Updated Age"
        - [ ] T3.4.8 Add case 14: actionStringAction = "Added Note"
        - [ ] T3.4.9 Add case 15: actionStringAction = "Printed Label"
        - [ ] T3.4.10 Add default: actionStringAction = "Updated"

    - [ ] T3.5 Validate
        - [ ] T3.5.1 Run `./test.sh --testsuite unit` — all tests pass including ActionCodeTest `[activity: run-tests]`
        - [ ] T3.5.2 Verify each mass method passes batchId correctly `[activity: review-code]`
        - [ ] T3.5.3 Verify detail strings are descriptive and human-readable `[activity: review-code]`

---

### Phase 4: Route Handler Updates

- [ ] T4 Phase 4 — Generate batchId in route handlers, pass to factory methods, update per-bin history endpoint

    - [ ] T4.1 Prime Context
        - [ ] T4.1.1 Read mass edit route handlers `[ref: userfrosting/routes/groups/backstock.php; lines: 765-1111]`
        - [ ] T4.1.2 Read per-bin history endpoint `[ref: backstock.php; lines: 286-345]`
        - [ ] T4.1.3 Read SDD BatchId generation example `[ref: solution-design.md; "BatchId generation"]`
        - [ ] T4.1.4 Read SDD per-bin history enrichment example `[ref: solution-design.md; "Per-bin history endpoint"]`

    - [ ] T4.2 Implement UUID Helper `[activity: backend-implementation]`
        - [ ] T4.2.1 Add `generateBatchId()` helper function near `parseMassEditRequest()` in backstock.php — uses `sprintf` with `mt_rand` to generate UUID v4 (no external dependency)

    - [ ] T4.3 Implement Route Handler Updates `[activity: backend-implementation]`
        - [ ] T4.3.1 **POST /mass/change-location**: Generate batchId, pass to `massChangeLocation()` as new param
        - [ ] T4.3.2 **POST /mass/empty**: Generate batchId, pass to `massEmptyBins()` as new param
        - [ ] T4.3.3 **POST /mass/change-category**: Generate batchId, pass to `massChangeCategory()` as new param
        - [ ] T4.3.4 **POST /mass/change-tags**: Generate batchId, pass to `massChangeTags()` as new param
        - [ ] T4.3.5 **POST /mass/hide**: Generate batchId, pass to `massHideBins()` as new param
        - [ ] T4.3.6 **POST /mass/unhide**: Generate batchId, pass to `massUnhideBins()` as new param
        - [ ] T4.3.7 **POST /mass/print-labels**: Generate batchId, add `$app->user->id` param, pass to `massPrintLabels()` as new params
        - [ ] T4.3.8 **POST /mass/update-age**: Generate batchId, pass to `massUpdateAge()` as new param
        - [ ] T4.3.9 **POST /mass/add-notes**: Generate batchId, pass to `massAddNotes()` as new param

    - [ ] T4.4 Implement Per-Bin History Endpoint Enhancement `[activity: backend-implementation]`
        - [ ] T4.4.1 Update the action switch (lines 307-322) to add cases for codes 6, 7, 8, 10, 11, 12, 13, 14, 15 with descriptive actionText strings
        - [ ] T4.4.2 Add `detail` and `batchId` fields to response objects
        - [ ] T4.4.3 Add `isBulk` derived boolean field (`!empty($row['batchId'])`)
        - [ ] T4.4.4 After collecting all actions, query batch sizes for unique batchIds and add `batchSize` to each action

    - [ ] T4.5 Write Test for BatchSize Enrichment `[activity: backend-test]`
        - [ ] T4.5.1 Test: Per-bin history response includes `detail`, `batchId`, `isBulk`, `batchSize` fields
        - [ ] T4.5.2 Test: Actions with same batchId return correct `batchSize` count
        - [ ] T4.5.3 Test: Actions with NULL batchId return `isBulk: false` and `batchSize: null`

    - [ ] T4.6 Validate
        - [ ] T4.6.1 Run `./test.sh --testsuite unit` — no regressions `[activity: run-tests]`
        - [ ] T4.6.2 Verify all 9 routes generate and pass batchId `[activity: review-code]`
        - [ ] T4.6.3 Verify per-bin history includes detail, batchId, isBulk, batchSize `[activity: review-code]`

---

### Phase 5: Integration & End-to-End Validation

- [ ] T5 Phase 5 — Full integration testing and live verification

    - [ ] T5.1 Unit Tests Passing `[activity: run-tests]`
        - [ ] T5.1.1 Run `./test.sh --testsuite unit` — ALL tests pass
        - [ ] T5.1.2 Run `cd userfrosting && ./vendor/bin/phpunit --filter "ActionCode"` — all action code tests pass
        - [ ] T5.1.3 Run `cd userfrosting && ./vendor/bin/phpunit --filter "Backstock"` — all backstock tests pass

    - [ ] T5.2 Live Smoke Tests on dev2.buyerkiosk.com `[activity: manual-test]`
        - [ ] T5.2.1 Navigate to backstock overview for pc00
        - [ ] T5.2.2 Select 3 bins → Change Location → verify action log shows "moved [bin] → [location]"
        - [ ] T5.2.3 Select 3 bins → Change Category → verify action log shows "changed category of [bin] → [category]"
        - [ ] T5.2.4 Select 2 bins → Empty → verify action log shows "emptied [bin]"
        - [ ] T5.2.5 Select 2 bins → Hide → verify action log shows "hid [bin]"
        - [ ] T5.2.6 Unhide bins → verify action log shows "unhid [bin]"
        - [ ] T5.2.7 Select 2 bins → Add Tags → verify action log shows "added tags to [bin]"
        - [ ] T5.2.8 Select 2 bins → Remove Tags → verify action log shows "removed tags from [bin]"
        - [ ] T5.2.9 Select 2 bins → Update Age → verify action log shows "updated age of [bin]"
        - [ ] T5.2.10 Select 2 bins → Add Notes → verify action log shows "added note to [bin]"
        - [ ] T5.2.11 Select 2 bins → Print Labels → verify action log shows "printed label for [bin]"

    - [ ] T5.3 Batch Grouping Verification `[activity: manual-test]`
        - [ ] T5.3.1 Perform a bulk action on 5 bins
        - [ ] T5.3.2 Open per-bin history for one of those bins
        - [ ] T5.3.3 Verify the action shows `isBulk: true` and `batchSize: 5`

    - [ ] T5.4 Global Activity Feed Verification `[activity: manual-test]`
        - [ ] T5.4.1 Load global activity feed (`GET /api/:typeNum/backstock/actions`) and verify new action codes render non-empty readable strings
        - [ ] T5.4.2 Verify actions with new codes (10-15) display correctly alongside old codes (0-3, 6)
        - [ ] T5.4.3 Verify `detail` and `batchId` are populated on new entries in the feed

    - [ ] T5.5 Backward Compatibility Verification `[activity: manual-test]`
        - [ ] T5.5.1 Verify old action log entries (before migration) still display correctly
        - [ ] T5.5.2 Verify individual (non-bulk) actions still log with `batchId: null`
        - [ ] T5.5.3 Verify global activity feed (/actions endpoint) loads without error

    - [ ] T5.6 PRD Acceptance Criteria Final Check `[ref: PRD Features 1-5]`
        - [ ] T5.6.1 Feature 1: All 9 mass action types produce unique, descriptive log entries
        - [ ] T5.6.2 Feature 2: Location, category, and tag changes show what changed in detail
        - [ ] T5.6.3 Feature 3: Bulk operations have batchId linking all actions from one operation
        - [ ] T5.6.4 Feature 4: Hide (7) and unhide (8) display correctly (not blank)
        - [ ] T5.6.5 Feature 5: Print labels generates an action log entry

    - [ ] T5.7 SDD Compliance Check
        - [ ] T5.7.1 All action codes match SDD table (0, 3, 7, 8 kept; 10-15 new)
        - [ ] T5.7.2 Detail format matches SDD examples ("→ {name}", "+tags", "age → {date}")
        - [ ] T5.7.3 batchId is UUID format, generated in route handler
        - [ ] T5.7.4 Three rendering consumers all handle all codes (Action model, makeBinReadable, per-bin history)
