Both per-booker policies are asked at the hold as soon as the order names a booker, and there they count only the places already written. The places in the request are invisible to them, so the check is short by exactly what is being asked for.
Confirmed by probe tests against unmodified 1.x. With the per-slot limit set to a maximum of two, a booker already holding two places, and the booker named on the order, a click for one more place is allowed and the order ends up holding three. Raising the stored total to three first, so the stored rows alone break the rule, makes the same click refuse. That shows the policy is asked at the hold and that only the arithmetic is wrong.
Affected:
slot_per_booker_limitsums stored quantities for the slot and compares that sum, atmodules/yoyaku_order/src/Plugin/ConstraintPolicy/SlotPerBookerLimit.php:139through:152.cross_resource_limitcounts stored distinct slots, atmodules/yoyaku_order/src/Plugin/ConstraintPolicy/CrossResourceLimit.php:159through:180. A slot appearing only in the request is not among them, so a second resource may be held under a maximum of one.
transaction_quantity_limit is unaffected: it adds up the lines it is handed and never queries.
The cause is that BookingManager::holdGroup() runs the group policies before any line of the group is saved, both above the slot locks and again under them, as the comment on the second pass says. A policy that answers with a query therefore sees the basket without the request.
Steps to reproduce
Attach the per-slot per-booker limit to a resource, with a maximum of two. Hold two places of one slot into an order. Name the booker on that order. Hold one more place of the same slot into the same order.
Expected: the second hold is refused, because the booker would then hold three of a maximum of two.
Actual: the second hold succeeds and the order holds three places. The refusal arrives only at confirmation.
Why this has not been seen
Every test in OrderConstraintTest names the booker after holding, so the booker fact is false at the hold in all of them and these policies are only ever reached at confirmation, where every line is written and the arithmetic is correct. The constraint policies documentation describes the same order of operations, saying a per-booker rule first refuses where the booker is named, which for a visitor filling in a form is the registration step rather than the click. That ordering stops holding as soon as a site requires an account, and also whenever anything is added to a cart after the booker attributes have been captured.
Impact
No order settles over the allowance, because confirmation still refuses. What happens instead is that a basket is allowed past the allowance, capacity is held out of sale by a booker who cannot complete, and the refusal lands at checkout rather than on the click that caused it.
Proposed resolution
Count the request as well as the stored rows. For the per-slot limit, add the quantities of the covered lines carrying no saved booking to the queried sum. For the cross-resource limit, add the distinct slots of those lines to the counted set. Both belong in BookerPolicyBase, since both subclasses need the same thing and a third would need it too.
At confirmation every line is written, so nothing is added and the behavior there is unchanged. That is what keeps the existing tests meaningful rather than merely green.
The summed axis proposed in #3614828: Add a per-booker limit that sums units across everything it reaches inherits this query shape and is hurt more by it, since over a season the places being clicked for are most of what the rule counts. This should land first.
Issue fork yoyaku-3614839
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
mably commentedComment #5
mably commented