File: docs/order-done-plan-00.md
Target version: 1.5.1
Status: Draft / not yet implemented
On the HPOS Orders list (wp-admin/admin.php?page=wc-orders), when the operator runs
the built-in WooCommerce bulk action "Change status to Completed"
(mark_completed, Czech "Změnit stav na Dokončeno"), the plugin must — before the
order status is flipped — advance every still-unresolved product line-item print status to
Done Print (done-print).
"Unresolved / unset" means any product item currently in pending-print or in-print.
Items already in done-print or skip-print are left alone. This is exactly the semantics
of the existing Order_Item_Status_Manager::advance_remaining_to_done() — we reuse it, we
do not reimplement it.
This auto-advance is proprietary to this one entry point only:
mark_completed bulk action, andwoocommerce_page_wc-orders (HPOS) screen.It must not fire for:
edit-shop_order) — out of scope; operator uses HPOS.Everywhere else, the existing completion guard behaviour is preserved: completing an order while items are still pending/in-print is blocked and reverted with an admin notice.
Today the only coupling on → wc-completed is the completion guard in
Order_Item_Status_Manager::on_order_status_changed() (hooked to
woocommerce_order_status_changed, priority 5). It does the opposite of what we want here:
if any product item is still pending-print/in-print it reverts the order back to its
previous status and shows an error notice.
So a bulk "mark completed" over orders whose items are still pending currently fails for
those orders — WooCommerce sets them to completed, our guard immediately reverts them, and
the operator sees N "completion blocked" notices. The operator's intent in this specific
bulk action is the reverse: "I'm done — mark the items done and complete the orders."
The fix is to advance the items before the status change reaches the guard, so the guard sees a ready order and lets the completion stand.
WooCommerce routes all orders-list bulk actions — including the core mark_* ones —
through the WordPress filter:
handle_bulk_actions-woocommerce_page_wc-orders ( $redirect_to, $action, $ids )
The plugin already proves this: Bulk_Actions_Manager registers its custom actions on this
exact filter at priority 10 and they work. WooCommerce core's own handler for mark_*
(which loops the selected IDs and calls $order->update_status( 'completed', ... )) is just
another callback on the same filter, also at priority 10.
Key lever: a callback registered on the same filter at a lower priority number runs first. So a callback at priority 9 runs before WooCommerce changes any status — the exact window we need to advance items.
(Priority is what orders callbacks, not registration time. Our managers register at
plugins_loaded 10; WC registers its list-table handler later in the request during screen
setup. Using priority 9 guarantees we still run first regardless of registration order.)
Add one new callback on handle_bulk_actions-woocommerce_page_wc-orders at priority 9
that:
$redirect_to untouched unless $action === 'mark_completed';$redirect_to untouched) if the user lacks edit_shop_orders — let WC's
own handler reject the request; we never mutate without the cap;array_filter(array_map('intval', (array) $ids)));wc_get_order($id), skip falsy, then call
advance_remaining_to_done($order) (reused as-is);UtilsLog::message(..., 'success') summary across the
batch (e.g. "Auto-advanced 12 items to Done Print across 4 orders before completion.");$redirect_to unchanged — it does not short-circuit. WooCommerce's
priority-10 handler then runs the actual status change, and because every item is now
done-print/skip-print, the completion guard at priority 5 sees a ready order and lets
it through silently.No change is needed to the completion guard itself. The guard stays as the safety net for every other path.
Put the new hook + handler in Order_Item_Status_Manager (not Bulk_Actions_Manager):
advance_remaining_to_done(), set_status(), get_status(), the
completion guard, and the recursion-guard flag — all the moving parts of this feature are
in one class, so the author reasons about the guard/advance interaction in one place.add_filter(...) line there is idiomatic.Constructor addition:
add_filter(
'handle_bulk_actions-woocommerce_page_wc-orders',
array($this, 'bulk_complete_advance_items'),
9, // before WooCommerce's own mark_completed handler (priority 10)
3
);
New method (sketch — final code to match house style):
/**
* Proprietary behaviour for the HPOS Orders list "Change status to Completed"
* bulk action ONLY: advance every still-pending/in-print product item to
* done-print BEFORE WooCommerce flips the order, so the completion guard is
* satisfied instead of reverting. Runs at priority 9 (before WC's priority-10
* mark_completed handler). Returns the redirect unchanged so WC still performs
* the status change.
*/
public function bulk_complete_advance_items($redirect_to, $action, $ids) {
if ($action !== 'mark_completed') {
return $redirect_to;
}
if (!current_user_can('edit_shop_orders')) {
return $redirect_to; // WC's handler will reject; we don't mutate.
}
$ids = array_filter(array_map('intval', (array) $ids));
if (empty($ids)) {
return $redirect_to;
}
$total_items = 0;
$total_orders = 0;
foreach ($ids as $id) {
$order = wc_get_order($id);
if (!$order) {
continue;
}
$advanced = $this->advance_remaining_to_done($order);
if ($advanced > 0) {
$order->add_order_note(sprintf(
_n(
'%d line item advanced to "Done Print" before bulk completion.',
'%d line items advanced to "Done Print" before bulk completion.',
$advanced,
'studiou-wc-ord-print-statuses'
),
$advanced
));
$total_items += $advanced;
$total_orders++;
}
}
if ($total_items > 0) {
UtilsLog::message(
sprintf(
/* translators: 1: item count, 2: order count */
__('Auto-advanced %1$d line item(s) to "Done Print" across %2$d order(s) before completion.', 'studiou-wc-ord-print-statuses'),
$total_items,
$total_orders
),
'success'
);
UtilsLog::log(sprintf('bulk mark_completed: advanced %d items across %d orders', $total_items, $total_orders));
}
return $redirect_to;
}
If the team would rather keep all bulk-action hooks in Bulk_Actions_Manager, the
identical logic can live there instead, with the Order_Item_Status_Manager instance
injected via the constructor. The main plugin would then instantiate
Order_Item_Status_Manager first and pass it into new Bulk_Actions_Manager($itemStatus).
Functionally equivalent; more wiring. Recommendation stays with Order_Item_Status_Manager.
Confirm during implementation:
woocommerce_order_status_changed priority 5; it fires after WC's
bulk handler calls update_status('completed'), i.e. after our priority-9 pre-advance.done-print and saves them;$order->update_status('completed');woocommerce_order_status_changed → guard (priority 5) →
is_order_ready_for_completion() now returns ready → guard returns without reverting.$inside_self_revert recursion flag is not involved (no revert happens), so no
conflict.completed before the action: update_status('completed') is a no-op (no
status_changed), so the guard never fires — harmless. Our advance still runs and is also
effectively a no-op if items are already resolved.mark_completed will move them to completed regardless (it has no terminal guard for
bulk mark-complete). Our pre-advance will advance their items too. This matches the
operator's explicit selection + explicit "complete" choice. Decision: do not special-case
terminal statuses here — note it as expected behaviour. (Contrast: the protocol imports
deliberately skip Studiou_DB_Manager::TERMINAL_STATUSES, because there the order set is
data-driven, not an explicit operator click.) If the team wants to protect these, add a
TERMINAL_STATUSES skip in the loop — flagged as Open Question Q2.edit_shop_orders); bail without mutating if
absent rather than wp_die() — WC's handler already enforces caps and we don't want to
change the failure UX of the core action.advance_remaining_to_done() already skips
anything that isn't a WC_Order_Item_Product. No change.advance_remaining_to_done() saves each changed item individually
($item->save()), consistent with the existing import path. For very large bulk sets this
is N item-saves; acceptable and matches current code. Not optimising now.If a future WooCommerce version stops routing mark_completed through
handle_bulk_actions-woocommerce_page_wc-orders, or our priority-9 callback cannot be made
to run first, fall back to context-aware guard relaxation:
on_order_status_changed(), when $to === 'completed', detect the bulk-complete
request context — is_admin() and the request carries the wc-orders bulk action, e.g.
($_REQUEST['page'] ?? '') === 'wc-orders' and the resolved bulk action
($_REQUEST['action'] / action2) is mark_completed.advance_remaining_to_done($order) and
allow completion.This is more coupled (inspects superglobals inside a domain listener) and is documented here only as a contingency. Verify Approach A on staging first (see §9); prefer it.
includes/class-order-item-status-manager.php
add_filter('handle_bulk_actions-woocommerce_page_wc-orders', ..., 9, 3) in the
constructor;bulk_complete_advance_items() method.studiou-wc-ord-print-statuses.php
Version: header and STUDIOU_WC_OPS_VERSION → 1.5.1.README.md
## 1.5.1 changelog entry.CLAUDE.md
Order_Item_Status_Manager architecture bullet with the new bulk-complete
pre-advance hook (priority 9 on handle_bulk_actions-woocommerce_page_wc-orders,
scoped to mark_completed).Add a one-line pointer to this plan in CLAUDE.md's Documentation list:
- [docs/order-done-plan-00.md](docs/order-done-plan-00.md) — Plan for auto "Done Print" on the HPOS bulk "Change status to Completed" action (1.5.1).
No new translation strings beyond the two new notice/note formats — add their English
sources and refresh cs_CZ (docs/translations.txt, .po/.mo) per the existing process.
Manual, on staging with HPOS enabled and WP_DEBUG on:
pending-print, in-print, one skip-print). On wp-admin/admin.php?page=wc-orders,
select it, run Change status to Completed. Expect: order becomes completed; the
pending/in items are now done-print; the skip-print item is untouched; one order note
"N line items advanced to Done Print before bulk completion."; one success admin notice;
no "completion blocked" notice.done-print/skip-print → completes with no advance
note (count 0).edit_shop_orders, confirm no item mutation occurs.WP_DEBUG_LOG, log the
item statuses at the top of bulk_complete_advance_items() and inside the completion
guard; confirm the advance log line precedes the guard line for the same order in one
request. This proves priority 9 runs before WC's status flip on the installed WC version.Order_Item_Status_Manager (recommended) or move
to Bulk_Actions_Manager with dependency injection? Default: Order_Item_Status_Manager.edit-shop_order screen is genuinely out of
scope (operator uses HPOS only). If it must be covered too, mirror the hook on
handle_bulk_actions-edit-shop_order.