Manage reusable-choice availability and shared capacity

Reusable choices can have one operational state across every Product Add-On group and product that uses them. Use central availability for options that must open or close everywhere at the same time. Enable shared capacity when completed purchases must consume one discrete pool even when the choice appears on different products.

Prerequisites

  • Enable Product Add-Ons under Settings → POM Tweaks → E-Commerce → Products.
  • Create the reusable choice and link it from at least one multiple-choice or checkbox field.
  • Confirm the WordPress timezone under Settings → General. Availability dates and weekly windows use this store timezone.
  • Decide whether unavailable choices should be hidden or kept visible with an explanation.

Configure central availability

  1. Go to Products → Product Add-Ons → Reusable choices.
  2. Open the reusable choice.
  3. Under Reusable choice details, set When this choice is unavailable to hide it or keep it visible but disabled.
  4. Under Central availability, set Manual state. Use Unavailable for an immediate operational stop.
  5. Optionally enter an Internal reason. This stays in administration and is not returned in the public product model.
  6. Optionally enter a Customer message. A disabled option displays this message, while a hidden option leaves an accessible field notice explaining why no selection is available.
  7. Optionally set Available from and Available through. Both dates are inclusive in the store timezone.
  8. Enable the required weekdays under Weekly store-time windows, then enter each start and end time. An end time before its start continues into the following day. Equal times make that weekday available for its full 24 hours.
  9. Save the draft, review its publication impact, and publish it.

A choice is selectable only when its entity is enabled, its manual state is available, the current store-local date is inside the configured range, and the current store-local time matches a weekly window when any windows exist. Shared capacity is checked last. Daylight-saving transitions follow the WordPress timezone rather than the PHP server timezone.

Product and group references cannot broaden central availability. A linked option can still be hidden locally through its existing visibility setting, but no product or group can make a centrally unavailable choice selectable. This precedence keeps the reusable choice authoritative across the catalog.

Configure shared capacity

  1. In the same reusable-choice editor, open Shared capacity.
  2. Select Limit this choice with one shared capacity across every product and group.
  3. Enter Total capacity. This is the complete pool, not the amount per product.
  4. Enter Units per selection. Capacity consumption is this value multiplied by the WooCommerce cart-line quantity. Fractional product quantities round consumption up to the next whole capacity unit.
  5. Save and publish the draft.

The editor shows current remaining capacity to authorized administrators. Public REST and Store API responses expose only available, a safe reason and message, capacity_managed, and the coarse capacity_status; they do not expose the original capacity, units sold, held quantities, or remaining units.

Changing Total capacity does not erase consumption. Turning capacity management off stops enforcing the pool but retains its operational history so turning it on again does not silently replenish stock. Refunds and cancelled or failed orders return their applicable units. A later order status change uses the quantity that remains after refunds, so it cannot restore or consume the refunded units a second time.

Understand carts, holds, and orders

Adding a selection to the cart checks current availability but does not reserve capacity. This matches normal WooCommerce behavior: abandoned carts do not block other customers. If capacity is consumed while a selection remains in a cart, cart and checkout validation explain that it is no longer available and prevent checkout until the customer changes the selection.

Checkout creates one atomic temporary hold for all managed reusable choices in the order. Holds use WooCommerce's configured stock-hold duration. Concurrent checkouts lock choices in deterministic numeric order, so only one checkout can reserve the final unit. A pending hold stops counting immediately after its expiry even if WP-Cron has not yet deleted its row.

  • A successful payment or an order entering Processing, On hold, or Completed confirms consumption.
  • A checkout exception releases its temporary holds.
  • A Failed or Cancelled order releases all of its capacity.
  • A partial or full refund returns the units represented by each refunded line quantity.
  • Reprocessing the same refund event does not return its units twice.
  • Repeating lifecycle events is safe: confirmation uses the order identity and does not consume capacity twice.
  • Editing add-ons on an editable On hold order moves confirmed capacity from the old selection to the new selection in the same database transaction. If the replacement has no capacity, both the order line and its existing allocation remain unchanged.

If a payment completes only after its temporary hold has expired, POM attempts one new atomic claim. When another checkout has already consumed the units, POM does not overcommit the pool. It adds an administrative note to the affected order so the store can review fulfilment or refund the payment.

Capacity records are scoped to the current multisite blog, while order access and status continue through WooCommerce CRUD. This keeps the lifecycle compatible with both HPOS and classic order storage.

API and storefront behavior

The complete public Product Add-Ons model includes an availability object for reusable options. Its schema_version is independent from the Product Add-Ons definition schema. The same current model is used by classic forms, REST product responses, Store API product responses, server-side cart validation, and checkout reservation.

When Hide it from every group is selected, the unavailable option is omitted from selectable controls. The surrounding field and group remain in the document, and the field exposes an accessible status message. When Keep it visible but disabled is selected, the option remains visible with its customer message and a disabled form control.

Structural product caches do not store authoritative availability. Each request evaluates schedules in store time and reads all managed choices for that product in one bounded ledger query. Reservation, confirmation, release, and refund writes bypass persistent object-cache state, so a warm cache cannot make exhausted capacity selectable.

Verify availability and capacity

  1. Publish a reusable choice with Manual state → Unavailable and Keep it visible but disabled. Confirm every linked product shows the disabled option and customer message.
  2. Change the behavior to Hide it from every group, publish, and confirm the field and group remain with an availability notice.
  3. Test a window that crosses midnight and a date around a daylight-saving change in the configured store timezone.
  4. Set total capacity to one, select the choice from two different products or sessions, and start checkout in both.
  5. Confirm the first checkout holds the unit and the second receives a clear availability error.
  6. Let a pending hold expire and confirm another checkout can reserve the unit before the cleanup event runs.
  7. Confirm one order, then test Failed, Cancelled, partial refund, and full refund transitions while reviewing remaining capacity in the reusable-choice editor.
  8. Open Tools → Site Health → POM Product Add-Ons and confirm Shared capacity ledger is Ready and Expired-hold cleanup is Scheduled.
  9. Repeat on another multisite subsite and confirm its capacity pool is independent.
  10. Partially refund one line, repeat its refund event in a controlled staging check, then move the order through another consuming or releasing status and confirm the refunded units are not counted again.

Troubleshooting and recovery

If checkout reports that shared capacity storage is unavailable, open Product Add-Ons Site Health. Capacity validation fails closed so a storage error cannot create an oversold purchase. Resolve database permissions or schema availability before accepting managed-capacity selections.

If the cleanup event is delayed, expired holds still do not count. Running WordPress cron later removes them in bounded batches; it is not required to unlock capacity.

For a non-destructive rollback, turn off capacity management on the affected choice and publish the draft. Existing order snapshots and ledger history remain intact. Permanently removing the capacity ledger is a database rollback operation and should happen only after capacity management is disabled for every reusable choice, pending checkouts have completed or expired, and a database backup has been verified.