A seat-based product (meta.seat_based) sold at quantity N provisions N assignable LicenseSeat rows instead of granting the buyer. Assigning a seat runs the product's existing ProductAction grants against the seat holder via an explicit `grantee` (resolution: grantee ?? purchaser ?? subscription->user), so a seat confers exactly the product's access with zero duplicated grant logic. - LicenseSeat model: assign / reassign / reclaim / retire / refreshGrant - SeatService: idempotent provisioning for one-time purchases and subscriptions (sizes the pool to quantity; renewal extends expiry + re-grants held seats) - SeatAssigned / SeatReassigned / SeatRevoked events - Product::isSeatBased(); ProductPurchase + Subscription provision on completion / lifecycle hooks - license_seats migration + factory; config `shop.seats` block - 15 new tests (purchase, subscription, renewal, retire, delegation, gating) Backward compatible: with no `grantee` the buyer grant is unchanged, and seat behavior is gated behind the per-product meta flag + config `shop.seats.enabled`. Full package suite green: 1425 tests / 3812 assertions / 0 failures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| Feature | ||
| Unit | ||
| TestCase.php | ||
| bootstrap.php | ||