Orders & fulfilment
Draft orders and order editing
Indicative price, not an offer: €29–79/month
Generated from docs/plan/paid/draft-orders-and-order-editing/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
Allows merchants to create draft orders on behalf of customers (e.g., wholesale, B2B). Drafts can be edited, sent as invoices for customer to pay, or converted to live orders. Supports order editing after placement for customer service use cases.
The problem
I cannot create orders for wholesale customers manually or edit orders after placement. Every order is final; if a customer requests a change, I must cancel and re-create. No draft workflow.
What it does
- Draft order entity: draft_order_id, customer_id, status (draft, sent, converted, expired), expires_at, line_items (product, qty, price_override), shipping_method, billing_address, shipping_address, discount, notes
- Draft creation: staff create order on behalf of customer with custom line items, prices, discounts
- Edit draft: add/remove/edit line items, change addresses, adjust discount before sending
- Send draft as quote/invoice: generate PDF invoice with ‘Accept’ link; email to customer
- Customer accept/reject: customer clicks link, reviews order, approves (or rejects with reason); order is converted to live order
- Payment on acceptance: if draft has payment method stored, auto-charge on acceptance (or send payment link)
- Draft expiration: drafts older than 30 days auto-expire; customer cannot accept expired draft
- Order editing: after order creation, staff can edit line items (add/remove), addresses, discount for 24h (before fulfillment)
- Edit webhook: order.edited with changes (old vs new)
- Bulk draft creation: import CSV with customer email, product IDs, quantities; batch-create drafts
What it deliberately does NOT do
Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.
- Recurring draft orders (subscriptions): out of scope—one-time drafts only. Reason: subscription logic is deferred.
- Automatic re-send of expired drafts: out of scope—staff must manually re-send. Reason: automation deferred to workflow engine.
- Price negotiation workflow: out of scope—staff sets price, customer accepts as-is. Reason: negotiation is rare; handled via customer service.
- Customer counter-offer: out of scope. Reason: outside scope; handled via email back-and-forth.
- Approval chains (manager approval before customer sees): out of scope. Reason: workflow engine is separate.
- Draft duplication (create new draft from existing): out of scope—staff manually recreates. Reason: minor UX; handled later.
Data model
Migration: new DraftOrder table/collection (draft_order_id, customer_id, status, expires_at, created_at, converted_to_order_id). DraftOrderLineItem table (id, draft_order_id, product_id, quantity, price_minor, discount_percent). Add order.edited_at, order.original_order_id (for auditing edits), order.edited_by_staff_id.
API
- POST /draft-orders (staff, body: {customer_id, line_items: [{product_id, quantity, price_minor?}], shipping_method, billing_address_id, shipping_address_id, discount_percent?, notes?}) → {draft_order_id}
- GET /draft-orders (staff, query: ?status=draft) → [{draft_order_id, customer_email, status, line_items, expires_at}]
- GET /draft-orders/:id (staff or customer with token) → {draft_order_id, customer, line_items, total, accept_url}
- PUT /draft-orders/:id (staff, body: {line_items?, addresses?, discount_percent?}) → {draft_order_id}
- POST /draft-orders/:id/send (staff, body: {send_email: true}) → {draft_order_id, email_sent_at}
- POST /draft-orders/:id/accept (customer, body: {payment_method_id?}) → {order_id, status: ‘pending’}
- POST /draft-orders/:id/reject (customer, body: {reason}) → {draft_order_id, status: ‘rejected’, reason}
- PUT /orders/:id/edit (staff, body: {line_items?, addresses?, discount_percent?}, edit_window_hours=24) → {order_id, edited_at}
- GET /orders/:id/edit-history → [{change_id, edited_at, edited_by, changes: {old, new}}]
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Draft Orders section: list drafts with customer email, status, expires_at, total. Create Draft button opens form for customer selection, line items, addresses. Send Draft button emails accept link to customer. Order detail shows ‘Edit Order’ button (if within 24h); edit form allows line item changes and address updates. Edit history shows changes with dates and staff names.
The seam — why this is paid
Core owns draft entity, creation, editing, and conversion. Paid pack owns: approval workflows, price negotiation, automatic re-send, subscription drafts. Why: core provides honest manual order creation and editing for staff; workflows and negotiations are paid.
B2B and customer service capability. Support commitment for order state consistency.
Dependencies
- orders (order entity must exist; draft converts to order)
- customers (customer_id must link to customer)
- address-book-with-billing-shipping-split (draft must store billing and shipping addresses)
- email-layer (send draft as invoice)
Acceptance checks
Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.
- Staff create draft: select customer, add product (qty 2), override price to 50.00, total = 100.00; draft_order_id returned
- GET /draft-orders returns list with draft_order_id, customer_email, status=‘draft’, expires_at set to 30 days out
- Staff send draft; email sent to customer with ‘Accept Order’ link and PDF invoice
- Customer clicks link, reviews order, clicks ‘Accept’; order_id created, status=‘pending’, draft_order_id marked ‘converted’
- Draft expires after 30 days; customer cannot accept expired draft; returns 410 with ‘draft_order.expired’
- Staff edit order (24h after creation): add line item, remove another, change discount; edited_at set, order.edited webhook fired
- Edit history shows: ‘Removed product 123 (qty 2)’ and ‘Added product 456 (qty 1)’ with timestamps and staff name
- Edit after 24h rejected with ‘order.edit_window_closed’; customer service staff must cancel and recreate
- Draft with payment_method_id: on acceptance, charge customer automatically; order marked paid
- Draft without payment_method_id: on acceptance, order created with status=‘pending_payment’; customer sent payment link
Risks
Price override: if staff accidentally uses wrong price, order is created at loss; no validation. Edit window: 24h window might be too short for customer service edge cases; balance with inventory volatility. Expiration: if draft expires and customer’s order is urgent, staff must recreate; no auto-extend. Payment: if auto-charge fails on acceptance, order is created unpaid; staff must manually collect. Audit: edit history must show all changes (not just line items) or audit trail is incomplete.
Commercial context
| Suggested price | €29–79/month |
| Rival anchor | Magento Open Source: free; Adobe Commerce: advanced order management paid. |
The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.
Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full
catalogue and ../../AI-GUIDE.md for how to work on this repository.