Port domain model: Scenario, Bucket, Stream entities #31

Closed
opened 2026-06-14 23:39:30 +02:00 by myrmidex · 1 comment
Owner

Rebuild the domain on Doctrine + API Platform.

  • Entities: Scenario, Bucket, Stream (+ Draw/Inflow/Outflow as needed) with migrations.
  • UUID identifiers via symfony/uid (replaces the old HasUuid trait); UUID exposed as the API id, integer PK internal.
  • Enums: BucketType (need/want/overflow), BucketAllocationType (fixed_limit/percentage/unlimited), StreamFrequency, StreamType, DistributionMode (even/priority).
  • Invariants: exactly one overflow bucket per scenario; overflow must be unlimited; non-overflow cannot be unlimited; buffer forced to 0 for non-fixed-limit.
  • Fill-order strategy setting on Scenario (folds in old #7, #8).
  • API Platform resources for each (keep money logic in services, not entities).
  • Currency stored as integer cents; percentages as basis points.

Exit: entities migrate; CRUD works via /api; invariants enforced; tests green.

Rebuild the domain on Doctrine + API Platform. - Entities: Scenario, Bucket, Stream (+ Draw/Inflow/Outflow as needed) with migrations. - UUID identifiers via `symfony/uid` (replaces the old `HasUuid` trait); UUID exposed as the API id, integer PK internal. - Enums: BucketType (need/want/overflow), BucketAllocationType (fixed_limit/percentage/unlimited), StreamFrequency, StreamType, DistributionMode (even/priority). - Invariants: exactly one overflow bucket per scenario; overflow must be unlimited; non-overflow cannot be unlimited; buffer forced to 0 for non-fixed-limit. - Fill-order strategy setting on Scenario (folds in old #7, #8). - API Platform resources for each (keep money logic in services, not entities). - Currency stored as integer cents; percentages as basis points. Exit: entities migrate; CRUD works via `/api`; invariants enforced; tests green.
myrmidex added this to the v0.3.0 milestone 2026-06-14 23:39:30 +02:00
myrmidex added the
enhancement
label 2026-06-14 23:39:30 +02:00
myrmidex self-assigned this 2026-06-14 23:39:30 +02:00
Author
Owner

Done — domain model ported to Doctrine + API Platform. 86 tests / 252 assertions green, PHPStan + CS clean. 10 commits (59807d8..bebc042).

Exit criteria

  • Entities migrate — Scenario, Bucket, Stream + migrations, all persist with round-trip tests.
  • CRUD via /apiGetCollection/Get/Post on all three, gated is_granted('ROLE_USER'), JSON-LD.
  • Invariants enforced — one overflow per scenario; overflow must be unlimited; non-overflow cannot be unlimited; buffer rule. All surface as 422 through API writes (each test asserts the violation propertyPath).
  • Tests green.

Conscious deviations from the ticket wording (decisions, not gaps):

  1. UUID-as-PK, not "uuid exposed + integer PK internal". Single UuidV7 #[ORM\Id] (no GeneratedValue), generated in constructor — matches the User entity and idiomatic Doctrine. The "int PK + exposed uuid" wording reflected the old Laravel HasUuid route-key idiom; API Platform auto-exposes /api/scenarios/{uuid} with zero wiring.
  2. Buffer is rejected (422), not coerced to 0. Silent mutation is wrong for an API (the response would lie about what it stored). The non-fixed-limit-with-buffer case is a validation error, not a quiet rewrite. Enforced via the BufferOnlyForFixedLimit validator.
  3. Invariants live in the Symfony Validator layer (custom class-level constraints), not Doctrine lifecycle callbacks — so API Platform returns 422 automatically.
  4. Fill-order strategy folds into Scenario.distribution_mode (even/priority) — no separate phantom column (old #7/#8 reduce to this).
  5. Draw/Inflow/Outflow + allocation engine deferred to #32 — they're the ledger/execution layer; no behavior in #31 (ticket said "as needed").
  6. No explicit CSRF token — JSON-LD-only content-type enforcement (415 on non-JSON write) + SameSite cookies suffice for the same-origin SPA.

Operations are read+create only (GetCollection/Get/Post); Put/Patch/Delete deliberately deferred.

Done — domain model ported to Doctrine + API Platform. 86 tests / 252 assertions green, PHPStan + CS clean. 10 commits (`59807d8`..`bebc042`). **Exit criteria** - ✅ Entities migrate — Scenario, Bucket, Stream + migrations, all persist with round-trip tests. - ✅ CRUD via `/api` — `GetCollection`/`Get`/`Post` on all three, gated `is_granted('ROLE_USER')`, JSON-LD. - ✅ Invariants enforced — one overflow per scenario; overflow must be unlimited; non-overflow cannot be unlimited; buffer rule. All surface as **422** through API writes (each test asserts the violation `propertyPath`). - ✅ Tests green. **Conscious deviations from the ticket wording (decisions, not gaps):** 1. **UUID-as-PK**, not "uuid exposed + integer PK internal". Single `UuidV7` `#[ORM\Id]` (no `GeneratedValue`), generated in constructor — matches the `User` entity and idiomatic Doctrine. The "int PK + exposed uuid" wording reflected the old Laravel `HasUuid` route-key idiom; API Platform auto-exposes `/api/scenarios/{uuid}` with zero wiring. 2. **Buffer is rejected (422), not coerced to 0.** Silent mutation is wrong for an API (the response would lie about what it stored). The non-fixed-limit-with-buffer case is a validation error, not a quiet rewrite. Enforced via the `BufferOnlyForFixedLimit` validator. 3. **Invariants live in the Symfony Validator layer** (custom class-level constraints), not Doctrine lifecycle callbacks — so API Platform returns 422 automatically. 4. **Fill-order strategy** folds into `Scenario.distribution_mode` (even/priority) — no separate phantom column (old #7/#8 reduce to this). 5. **Draw/Inflow/Outflow + allocation engine deferred to #32** — they're the ledger/execution layer; no behavior in #31 (ticket said "as needed"). 6. **No explicit CSRF token** — JSON-LD-only content-type enforcement (415 on non-JSON write) + SameSite cookies suffice for the same-origin SPA. Operations are read+create only (`GetCollection`/`Get`/`Post`); Put/Patch/Delete deliberately deferred.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lvl0/buckets#31
No description provided.