# Feature Analysis — Per-Order-Item Print Status **Date:** 2026-05-12 (revised after dropping the "independent statuses" requirement) **Target plugin version after implementation:** 1.5.0 (minor bump — new feature, additive, non-breaking). **Status:** Analysis / planning document. No code has been written yet. --- ## 1. Requirements as received > 1. Add new status for order item called `order-item-status`: > - `pending-print` — order item is waiting for printing process — *initial* > - `in-print` — order item is currently processing for print > - `done-print` — order item was printed > - `skip-print` — order item is skipped by some custom / system reason > 2. Add to order detail to order item possibility (combo box) to change order-item-status manually. > 3. When Order status is going to change to `done`, all order items must be `!= pending-print` and `!= in-print`. > *(Previous requirement #3 — "Order status and order-item-status are independent" — was withdrawn by the stakeholder; this analysis assumes order ↔ item coupling is allowed and beneficial. See §3 for the proposed coupling rules.)* --- ## 2. Logical revisions ### 2.1 "When order status is going to change to `done`" — clarify what "done" means WooCommerce has no `wc-done` status. The plugin already adds `wc-to-print` and `wc-in-print`. The natural interpretation is the WC built-in `wc-completed`. **Proposed revision:** *"When the order status is going to change to `wc-completed`"*. ### 2.2 What about non-product line items? Orders contain product items but also fee items, shipping items, coupon items, tax items, and refund items. None of these are printed. **Proposed revision:** the completion guard inspects only **product line items** (`WC_Order_Item_Product`). Other item types are not assigned a print status and never block completion. ### 2.3 "Initial" value of `pending-print` — when is it set? The spec implies new items default to `pending-print`. Two strategies: - **Lazy (recommended):** the per-item meta is not written when the order is created. The `get_status()` helper returns `'pending-print'` when the meta is missing. Migration of existing orders is unnecessary. - **Eager:** write `pending-print` to every new item via `woocommerce_new_order_item`. Existing orders need a one-time backfill migration. **Proposed revision:** lazy default. Missing meta = `pending-print`. This avoids touching every existing order at activation time. --- ## 3. Coupling rules between order status and item statuses With the "independence" constraint dropped, the design space opens up. The cleanest model is **one-way coupling, order → items**: changing an order status can propagate to items; changing an item status never propagates back to the order. Within that, we need to decide *which* order-status transitions propagate and how. The proposed rules: | Order status transition | Effect on product items | |--------------------------|-------------------------| | → `wc-to-print` (Prepare to Printing) | No automatic change. Items typically already `pending-print` (lazy default). | | → `wc-in-print` (In Printing) | **Items in `pending-print` advance to `in-print`.** `done-print` / `skip-print` items are untouched. | | → `wc-completed` | **Gated, not propagating** — the transition is *blocked* if any product item is still `pending-print` or `in-print`. Operator (or protocol import) must advance items first. | | → `wc-cancelled` / `wc-refunded` / `wc-failed` | No automatic change. Items stay where they are (historical record preserved). | | → `wc-processing` / `wc-pending` / `wc-on-hold` | No automatic change. | | Item status changed manually | Never propagates to order. The operator drives the order status separately. | Two distinct kinds of coupling are at play: - **Propagation** (`→ in-print`): the order transition *drives* item state. The listener that handles `woocommerce_order_status_changed` runs the propagation. - **Guard** (`→ completed`): the order transition *requires* items to already be in the right state. If not, the transition is reverted. The same listener implements the guard. Both behaviours live in a single hook on `woocommerce_order_status_changed`. The protocol imports become natural consumers of these rules: - **InPrint Protocol** calls `$order->update_status('in-print', ...)`. The propagation rule advances pending items to `in-print` automatically. No special-case code in the protocol importer. - **Delivered Protocol** calls `$order->update_status('completed', ...)`. The guard would block this if items are still `pending-print` or `in-print`. The protocol importer must therefore call `advance_remaining_to_done($order)` *before* the status flip. --- ## 4. Open questions for stakeholder decision Before implementation begins, the following decisions should be confirmed: 1. **Propagation on `→ in-print`** (§3): confirm "advance `pending-print` items to `in-print`" is the desired behaviour for manual order-status changes from the admin dropdown, bulk actions, *and* the InPrint Protocol import. Alternative: leave items untouched, require explicit per-item operations. 2. **Audit trail:** should item-status changes write an entry to the order's activity feed? Recommendation: yes, with a brief note like *"Item #5 (Product Name): Pending Print → Done Print."* 3. **`skip-print` reason:** is there a free-text "reason" field, or just the status? Recommendation: status only in v1.5.0; reason field as a future enhancement. 4. **Bulk per-item operations across orders:** is there a need? Recommendation: not in v1.5.0 — single-order manual control plus protocol/propagation auto-advance covers the workflow. 5. **CSV-export filter:** should "Prepare to Printing Export" filter to only `pending-print` items, or include everything? Recommendation: include everything, add `prod_print_status` column. Filtering can happen downstream. 6. **Backwards-compatible behaviour for existing completed orders:** the completion guard applies only to *new* transitions to `wc-completed`. Already-completed orders are unaffected even if their items are `pending-print` (lazy default). Confirm this is acceptable. --- ## 5. Proposed final specification After applying §2's revisions and §3's coupling rules: 1. Each order item (specifically each `WC_Order_Item_Product`) has a print status with one of four values, persisted to per-item meta key `_print_status`. Missing meta defaults to `pending-print`. 2. The order detail screen (classic order edit) adds a **"Print Status"** column to the line-items table with a `` per line item. | | `woocommerce_process_shop_order_meta` | Read `$_POST['print_status'][$item_id]`, persist via `set_status()`. Runs after WC's own item processing. | | `woocommerce_order_status_changed` (priority 5) | Combined propagation + completion guard (see §8). | ### 7.2 Modified: `includes/class-db-manager.php` - `import_inprint_protocol()`: no functional change — propagation happens automatically via the listener when `$order->update_status('in-print', ...)` fires. Optionally surface the advanced count in the admin notice: *"3 line items advanced to In Print."* - `import_delivered_protocol()`: **before** calling `$order->update_status('completed', ...)`, call `Order_Item_Status_Manager::advance_remaining_to_done($order)` so the guard is satisfied. Surface the advanced count in the import-summary notice. - `get_orders_for_printing()`: extend the SELECT to surface item-level print status. Join `{$wpdb->prefix}woocommerce_order_items` (the item row) and `{$wpdb->prefix}woocommerce_order_itemmeta` (the `_print_status` meta) on `order_item_id`. Add `prod_print_status` to the CSV output. Note: the existing query groups by `(order, product, variation)` — multiple line items in one group would need a deterministic pick (e.g., `MIN()` again, or join via `order_products.order_item_id` if the lookup table exposes it). ### 7.3 Modified: `studiou-wc-ord-print-statuses.php` Add the new manager to `load_dependencies()` and `initialize_managers()`, identical to the existing pattern. Bump `STUDIOU_WC_OPS_VERSION` to `1.5.0`. ### 7.4 Modified: `includes/class-bulk-actions-manager.php` No code change. Bulk actions `"Set Status to Prepare to Printing"` and `"Set Status to In Printing"` already call `$order->update_status(...)` — the propagation listener handles items automatically when the transition is to `in-print`. The "Prepare to Printing" path doesn't propagate (per §3). ### 7.5 Modified: `README.md`, `CLAUDE.md`, `docs/translations.txt`, `languages/studiou-wc-ord-print-statuses-cs_CZ.po` - README: new "Order item statuses" feature section + 1.5.0 changelog entry + coupling rules table from §3. - CLAUDE.md: extend the manager-pattern list with the new manager + Recent Changes entry. - Translations: add the four status labels, "Print Status" column header, guard error message, audit-note format, and the protocol auto-advance summary strings. --- ## 8. Combined propagation + completion guard A single listener on `woocommerce_order_status_changed` handles both behaviours. Pseudocode: ```php public function on_order_status_changed($order_id, $from, $to, $order) { // Re-entrancy guard: a guard-revert below fires this hook again. Skip // the recursive invocation. if (self::$inside_self_revert) { return; } if ($to === 'in-print') { $advanced = $this->advance_pending_to_in($order); if ($advanced > 0) { UtilsLog::log("Order #{$order_id}: advanced {$advanced} item(s) to in-print on order → in-print"); } return; } if ($to === 'completed') { $check = $this->is_order_ready_for_completion($order); if ($check['ready']) { return; } self::$inside_self_revert = true; try { $order->update_status( $from, __('Cannot complete: product line items still pending print. Resolve item statuses first.', 'studiou-wc-ord-print-statuses') ); } finally { self::$inside_self_revert = false; } UtilsLog::message( sprintf( __('Order #%1$d completion blocked — %2$d product items not yet "Done Print" or "Skip Print".', 'studiou-wc-ord-print-statuses'), $order_id, count($check['blocking']) ), 'error' ); } } ``` **Caveats:** 1. `woocommerce_order_status_changed` fires *after* the change is committed. Reverting via `update_status` is post-hoc — the order shows the reverted state on the next page load. Slightly disorienting but workable, and the admin notice explains it. 2. The static `$inside_self_revert` flag prevents infinite recursion when the revert itself fires the hook. 3. For cleaner UX on the admin form path, a complementary interception in `woocommerce_admin_process_order_object` can validate **before** save. The post-hoc revert still covers programmatic / API / protocol paths. Belt-and-braces. --- ## 9. UI mockup (text) In the order edit screen's Items meta box, the table grows by one column: ``` | Item | Cost | Qty | Total | Print Status | |-----------------------|--------|-----|--------|----------------------| | Product A (variation) | 100.00 | 2 | 200.00 | [Pending Print ▼] | | Product B (variation) | 50.00 | 1 | 50.00 | [Done Print ▼] | | Shipping | — | — | 10.00 | — | | Coupon: SUMMER10 | -20.00 | — | -20.00 | — | ``` Non-product items show `—` and aren't editable. Optional v1.5.0 sweetener: a one-line summary above the table — *"Print readiness: 3 of 4 items ready (1 pending)."* --- ## 10. Edge cases & open behaviour decisions | Scenario | Proposed behaviour | |----------|---------------------| | Order has 0 product items | Completion allowed (vacuously satisfies the guard). | | Order has only refunds / shipping / fees | Completion allowed. | | Existing `wc-completed` order with items in `pending-print` | No effect — the guard only fires on the *transition* into `completed`, not on stored state. | | Manual order status flip Pending → Completed (skipping in-print) | Guard fires. Items are still `pending-print` → blocked. Operator must advance items or use protocol imports. | | Order in `wc-in-print` reverted to `wc-processing` then back to `wc-in-print` | First revert: no item change (per §3). Second forward: pending items advance again (no-op since they're already `in-print`). Done items stay done. | | Item is removed from order after being set to `done-print` | The meta is deleted along with the item. No orphan record. | | Item status changed *after* order is `completed` | Allowed. Order doesn't revert. | | Bulk action "Set Status to In Printing" on 100 orders | Each order's pending items propagate to `in-print` via the listener. | | Multiple `wc-in-print` ↔ `wc-to-print` ping-pong | Each → `in-print` advances any remaining `pending-print` items. Items previously moved stay in `in-print`. | --- ## 11. Test plan ### 11.1 Unit-style scenarios 1. **Default value:** new order item has no `_print_status` meta; `get_status()` returns `'pending-print'`. 2. **Set status:** `set_status($item, 'in-print')` writes the meta and (with `$audit=true`) appends an order note. 3. **Invalid status:** `set_status($item, 'foo')` rejects and returns `false` without writing. 4. **Completion guard ready:** all items in `done-print` or `skip-print` — `is_order_ready_for_completion()` returns `{ready:true}`. 5. **Completion guard blocked:** at least one item in `pending-print` or `in-print` — returns `{ready:false, blocking:[…]}`. 6. **Non-product items ignored:** an order with one product item (`done-print`) and one shipping item — guard returns ready. 7. **`advance_pending_to_in`:** order with items `[pending, in, done, skip]` → result has items `[in, in, done, skip]`. Count = 1. 8. **`advance_remaining_to_done`:** order with items `[pending, in, done, skip]` → result `[done, done, done, skip]`. Count = 2. ### 11.2 Integration 1. **Admin UI manual change:** open an order with three items, change one to `done-print`, click Update, reload → meta persisted, audit note added. 2. **Propagation:** change order status to `wc-in-print` via the admin dropdown → all `pending-print` items in the order are now `in-print`. Order note from `update_status` plus per-item audit notes. 3. **Completion guard via admin:** with one item still `pending-print`, change order status to `wc-completed`, click Update → order reverts to previous status (visible after reload), admin notice shows the block reason. 4. **Completion guard via programmatic call:** `wp wc shop_order update --status=completed` on an order with `pending-print` items → status reverts. 5. **InPrint Protocol import:** prepare a CSV with one `order_no`, run the import → order is in `in-print`, all the order's `pending-print` items advanced to `in-print` via the listener, `done-print` and `skip-print` items untouched. Import notice mentions advanced count. 6. **Delivered Protocol import:** prepare a CSV with one `order_no`, run the import → items advance to `done-print` first, then order flips to `completed`. Guard is satisfied. Import notice mentions advanced count. 7. **CSV export:** "Prepare to Printing Export" bulk action — output contains a `prod_print_status` column with per-item values. ### 11.3 Regression 1. The "Set Status to Prepare to Printing" bulk action still works without touching items (no propagation on `→ to-print`). 2. The "Set Status to In Printing" bulk action now also propagates items (new behaviour — expected). 3. Existing completed orders are unaffected by the new guard. 4. The Print Information panel on the order edit screen still renders. 5. The custom orders-list columns still render. 6. The order search by external reference still works. --- ## 12. Rollout ### 12.1 Versioning - New plugin version: **1.5.0** (minor — feature addition, no breaking changes). - `STUDIOU_WC_OPS_VERSION` constant updated accordingly. ### 12.2 Activation behaviour - No database migration. Lazy defaults handle existing items. - No new tables. The `woocommerce_order_itemmeta` table is already created by WC. ### 12.3 Deactivation behaviour - Same policy as today: meta keys are not cleaned up. Reactivation preserves all data. ### 12.4 Internationalisation ~12 new translatable strings: - 4 status labels: `Pending Print`, `In Print`, `Done Print`, `Skip Print`. - 1 column header: `Print Status`. - 1 placeholder for non-product items: `—` (literal, not translated). - 1 completion-guard order note: `Cannot complete: product line items still pending print. Resolve item statuses first.` - 1 admin notice: `Order #%1$d completion blocked — %2$d product items not yet "Done Print" or "Skip Print".` - 1 audit-note format: `Item #%1$d (%2$s): %3$s → %4$s.` - 2 protocol-auto-advance order notes: `%d items advanced to In Print by order-status change.`, `%d items advanced to Done Print by Delivered Protocol import.` - 1 readiness summary (optional UI sweetener): `Print readiness: %1$d of %2$d items ready (%3$d pending).` ### 12.5 Documentation - README: new "Order item statuses" feature section + coupling rules table + 1.5.0 changelog entry + Known Limitations updated (block-based screen still unsupported). - CLAUDE.md: new manager added to the manager-pattern list + Recent Changes entry. - New review doc (post-release): `docs/revise-1.5.0.md` once the feature lands. - `docs/feature-order-item-status-analysis.md` (this document) → archive or supersede after implementation. ### 12.6 Phasing (suggested) - **Phase 1 (v1.5.0):** core feature — data model, admin UI, propagation listener, completion guard, Delivered Protocol explicit advance, CSV export column. Classic order edit screen only. - **Phase 2 (v1.5.x or later):** AJAX live status changes (no full page reload), bulk-change items across orders, optional `skip-print` reason field, block-based order edit screen support. --- ## 13. Effort estimate Rough sizing for Phase 1: | Area | Effort | |------|--------| | `Order_Item_Status_Manager` class (data model + admin UI + combined propagation/guard listener) | ~6–10 hours | | `Studiou_DB_Manager` integration (Delivered Protocol advance, CSV column with new join) | ~2–3 hours | | Bootstrap + manager wiring + version bump | ~30 minutes | | README / CLAUDE.md / translations / .po | ~2 hours | | Test pass (manual scenarios in §11) | ~3–4 hours | | **Total** | **~14–20 hours** | Phase 2 items add ~6–10 hours each depending on scope. --- ## 14. Risk register | Risk | Likelihood | Impact | Mitigation | |------|------------|--------|------------| | Listener recursion (propagation → save → propagation, or guard-revert → revert → revert) | Medium | High | Static `$inside_self_revert` flag; propagation only fires on `→ in-print` not on every save. | | Existing orders break on first edit because meta is missing | Low | Low | Lazy default handles this — `get_status` returns `pending-print` for missing meta. | | Operator surprise: clicking "Set Status to In Printing" silently advances items | Low | Low | Documented in README; per-item audit notes show what happened. | | Block-based order edit screen doesn't render the new column | Medium | Low | Documented as a known limitation since 1.4.1; carried forward. | | `_print_status` meta key collides with another plugin | Very low | Medium | If collision found, fall back to a namespaced key (`_studiou_print_status`). | | Delivered Protocol "advance to done" advances items the operator intended to leave in `skip-print` | None | — | `skip-print` already satisfies the completion guard; the advance helper only touches `pending-print` / `in-print`. | --- ## 15. Recommendation Implement Phase 1 as described above, subject to stakeholder confirmation of the §4 open questions — particularly: - Open question 1 (propagation on `→ in-print`). The proposed design assumes "yes, advance pending items". This is the workflow-consistent choice, but it does change the behaviour of the existing **bulk action "Set Status to In Printing"** (previously order-only, now also touches items). If the stakeholder prefers the bulk action to remain strictly order-only, the propagation rule can be limited to specific paths (e.g., only the InPrint Protocol import) — but that re-introduces some of the awkwardness the old §2.5 grappled with. The shape of the design with requirement #3 dropped is materially cleaner than the original analysis: a single listener implements both behaviours, the protocol-import code paths are minimally changed, and the rules can be summarised in one table (§3). --- *End of analysis.*