Skip to content

feat: enforce order status transitions, restock/reverse on cancel - #82

Merged
lordlabakdas merged 1 commit into
masterfrom
fix/order-status-state-machine-and-cancel
Oct 4, 2026
Merged

lordlabakdas merged 1 commit into
masterfrom
fix/order-status-state-machine-and-cancel

Conversation

@lordlabakdas

Copy link
Copy Markdown
Owner

Summary

Two gaps surfaced while diffing the "Orders module" flow (Notion) against the actual create_order() / update_order_status() code:

  • No status state machine. PUT /order/{order_id}/status only validated that the target string was a recognized status, not that the current status could legally move there — a store could jump confirmed → delivered directly, or "revive" a delivered/cancelled order. Added ORDER_STATUS_TRANSITIONS (api/validators/order_validation.py) matching the diagram's confirmed → ready → delivered path; cancellation is reachable any time before delivery, delivered/cancelled are terminal. Self-transitions (same status twice) are a no-op, not an error.
  • Cancelling an order had no side effects. Stock decremented at order-create time was never restocked, and loyalty points earned/redeemed on the order were never reversed. Added OrderService._cancel_order(), invoked automatically when update_order_status() transitions to "cancelled" — restocks every OrderItem's inventory, reverses the order's earned/redeemed LoyaltyTransaction effects (new "adjusted"-type transactions for audit trail), and frees up one coupon use if a coupon was applied.

PUT /{order_id}/status now returns 400 for an illegal transition (previously only 404-not-found and 400-unrecognized-status-string existed).

Related: the Notion "Orders module" flowchart's delivery-dispatch step has also been updated to make clear that's a manual store action ("Request Delivery"), not automatic — per SPEC_DELIVERY_DISPATCH.md §3.4.

Test plan

  • New unit test: update_order_status still 200s on a legal transition (tests/unit/test_store_orders_service_extra.py)
  • New integration tests (tests/integration/test_order_promo_extra.py::TestUpdateOrderStatus):
    • confirmed → delivered direct jump rejected (400)
    • Cancelling a confirmed order fully restocks inventory and reverses loyalty points/total_earned
    • cancelled is terminal — cancelled → confirmed rejected (400)
  • pytest tests/ -v — 610/610 pass

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1eBFvEm5tiPyqQiQ3DNM

Two gaps found while diffing the Orders module flow (Notion) against
create_order()/update_order_status():

1. No status state machine. PUT /{order_id}/status only checked that
   the target string was one of VALID_STATUSES, with no check on
   whether the *current* status could legally move there — a store
   could jump straight from "confirmed" to "delivered", or "unmark"
   a delivered/cancelled order. Added ORDER_STATUS_TRANSITIONS
   (order_validation.py) matching the diagram's confirmed -> ready ->
   delivered path, with cancellation reachable any time before
   delivery and delivered/cancelled as terminal states.

2. Cancelling an order had no side effects beyond flipping the status
   string: stock decremented at order-create time was never restocked,
   and loyalty points earned/redeemed on the order were never
   reversed. Added OrderService._cancel_order(), invoked automatically
   when update_order_status() transitions to "cancelled" — restocks
   each OrderItem's inventory, reverses the order's earned/redeemed
   LoyaltyTransaction effects (logged as new "adjusted" transactions
   for audit trail), and frees up one coupon use if a coupon was
   applied.

api/order_api.py's PUT /{order_id}/status now surfaces invalid
transitions as 400 (previously only 404-for-not-found and
400-for-unrecognized-status-string existed).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1eBFvEm5tiPyqQiQ3DNM
@lordlabakdas
lordlabakdas merged commit c9d400a into master Oct 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant