Adds the workshop-side counterpart to Stage 11B's intake form: a tracking workspace filtered by fulfillment stage, an auditable timeline of stage changes, and inventory deduction on completion. Closes audit finding P2.2; Stage 11 is complete. Domain: FulfillmentEvent (append-only transition record with operator notes and a malformed-line-tolerant wire format), MaterialUsage, MaterialConsumptionPort + MaterialDeductionResult. Order gains fulfillmentHistory, materialUsage, derived stage accessors, and withFulfillmentEvent(). All existing fields, defaults, and copyWith semantics preserved. Data: advanceFulfillmentStatus() records an event with an optional note and injectable clock; recordMaterialUsage() added. WooCommerce round-trips both through _kc_ meta keys, skipping malformed lines and never blanking remote values. Application/presentation: AdvanceFulfillment, CompleteCustomOrder, CustomOrderTrackingController, CustomOrderTrackingPage, FulfillmentTimeline, showAdvanceStageDialog. Illegal transitions surface the domain rejection reason verbatim. Architecture: inventory deduction crosses the feature boundary through an outbound port, not a package dependency. feature_orders still has no dependency on feature_inventory; each app supplies InventoryMaterialConsumptionAdapter. Completion advances first and deducts second, so a rejected transition can never decrement stock, and a deduction failure warns rather than failing the commission. Tests: 329 new (293 feature_orders, 22 kell_mobile, 14 kell_web). 1085/1085 feature_orders, 109/109 kell_mobile, 81/81 kell_web. dart analyze --fatal-infos clean. |
||
|---|---|---|
| .. | ||
| build | ||
| lib | ||
| test | ||
| .gitignore | ||
| .metadata | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
| analysis_options.yaml | ||
| pubspec.yaml | ||
README.md
Order management for the Kell Creations operations platform. Covers both
standard catalogue orders and made-to-order custom commissions behind a single
Order aggregate and one OrdersRepository contract.
Features
- Domain model —
Order,OrderItem,OrderStatus,OrderSource,OrderType,PaymentMethod,PaymentResult. - Custom orders —
CustomOrderSpec(material, colour, size, dimensions, reference images, notes, estimated completion) andDepositInfo(deposit and total amounts in integer cents, deposit/settlement dates, derived balance). - Fulfillment pipeline —
FulfillmentStatuswith enforced transitions:quoted → depositReceived → inProduction → qualityCheck → readyForPickup → completed, pluscancelledfrom any non-terminal stage and aqualityCheck → inProductionrework path. Illegal jumps are rejected by the domain, not silently applied. - Fulfillment tracking —
FulfillmentEventrecords every accepted transition (stage from, stage to, timestamp, optional operator note) on an append-onlyOrder.fulfillmentHistory. - Material consumption —
MaterialUsageitemises what a commission consumes;MaterialConsumptionPortis the outbound port the app's composition layer implements to decrement stock on completion. - Repository contract —
OrdersRepositorywith read, status, note, and create operations pluscreateCustomOrder,updateCustomOrderSpec,recordDeposit,advanceFulfillmentStatus, andrecordMaterialUsage. - Implementations —
FakeOrdersRepository(seeded standard and custom orders across every pipeline stage) andWooCommerceOrdersRepository(REST API v3, custom order state round-tripped through_kc_-prefixed order meta-data). - Application layer —
OrdersController,CheckoutController,CustomOrderFormController,CustomOrderTrackingController, and theGetOrders/UpdateOrderStatus/AddOrderNote/CreateOrder/CreateCustomOrder/RecordDeposit/AdvanceFulfillment/CompleteCustomOrderuse cases. - Presentation —
OrdersPage,OrderCard,OrderDetailPanel,OrderStatusChip,CustomOrderFormPage,CustomOrderConfirmationPage,CustomOrderTrackingPage,CustomOrderTrackingCard,CustomOrderTrackingDetail,FulfillmentStatusChip,FulfillmentTimeline,showReferenceImageDialog,showAdvanceStageDialog.
Getting started
Add the package as a path dependency and import the barrel:
dependencies:
feature_orders:
path: ../feature_orders
import 'package:feature_orders/feature_orders.dart';
Usage
final repository = FakeOrdersRepository(logger: observability.logger);
final result = await repository.createCustomOrder(
order,
spec: CustomOrderSpec(
material: 'Organic cotton',
color: 'Sage green',
size: 'Large',
),
deposit: const DepositInfo.quoted(
totalAmountCents: 7200,
depositAmountCents: 2400,
),
);
// Advance through the pipeline — illegal jumps are rejected.
await repository.recordDeposit(
order.id,
amountCents: 2400,
paidAt: DateTime.now(),
);
await repository.advanceFulfillmentStatus(order.id, FulfillmentStatus.inProduction);
Custom order intake UI
CustomOrderFormPage captures an entire commission in one screen — customer
contact, description, specification, reference images, pricing and deposit, and
the estimated completion date. It owns a CustomOrderFormController unless one
is injected, so a route or a shell can mount it with only a repository:
CustomOrderFormPage(
repository: services.ordersRepository,
onComplete: (order) => Navigator.of(context).pop(order),
);
Money is typed as free text and converted to integer cents by
CustomOrderFormController.parseMoneyToCents before it reaches the domain, so
no double arithmetic ever decides a price. On success the page swaps to
CustomOrderConfirmationPage, which renders the deposit receipt.
Custom order tracking
CustomOrderTrackingPage is the workshop-side counterpart to the intake form.
It lists commissions filtered by fulfillment stage (not the commercial
OrderStatus, which OrdersPage already covers), and shows the full timeline
of recorded stage changes for the selected commission:
CustomOrderTrackingPage(
repository: services.ordersRepository,
materialPort: services.materialConsumptionPort,
onCreateCustomOrder: () => openIntakeForm(),
);
Advancing a commission opens showAdvanceStageDialog, which offers only the
stages the domain permits from the current one. The repository re-validates
regardless, and an illegal transition surfaces the domain's own
FulfillmentTransition.rejectionReason verbatim — the UI never invents its own
wording for a rule it does not own.
Inventory deduction on completion
feature_orders must not depend on feature_inventory; the two are sibling
features. Instead the orders layer declares a narrow outbound port:
abstract class MaterialConsumptionPort {
Future<MaterialDeductionResult> deductMaterials(
String orderId,
List<MaterialUsage> usages,
);
}
Each app's composition layer supplies an adapter backed by its own
InventoryRepository (InventoryMaterialConsumptionAdapter in kell_web and
kell_mobile). CompleteCustomOrder advances the order first and deducts
only after the pipeline accepted the transition, so a rejected completion can
never decrement stock.
Deduction never blocks completion. A missing SKU, a rejected adjustment, or an
unwired port yields a per-SKU entry in MaterialDeductionResult.failures and a
warning for the operator — an operator handing over a finished piece must not
be stopped by a stock discrepancy.
Observability and privacy
Repository implementations accept an optional KcLogger from core. Failed
and rejected write operations are logged with the operation name, order ID, and
status names only. Customer identity, addresses, money amounts, and deposit
payment identifiers are never logged, and every context map is additionally
passed through KcRedactor. There are no silent catch (_) {} blocks.
The WooCommerce meta-data mapping deliberately excludes the deposit
transaction ID — it lives on PaymentResult alone. See the class-level
documentation on WooCommerceOrderMapper for the complete key table.
Additional information
Part of the kell_creations_apps monorepo. Run the package tests with
flutter test from this directory, or the whole suite with
./tools/run_all_tests.sh --analyze from kell_creations_apps/.