From e92788038a8f25aeaa21f8f7908452685fca6ac2 Mon Sep 17 00:00:00 2001 From: Mike Kell Date: Sat, 11 Jul 2026 09:45:36 -0400 Subject: [PATCH] feat(orders): Stage 9A - WooCommerce Orders API integration - Extend OrderStatus with onHold, refunded, failed - Extend OrderItem with productId, variationId, customizations - Extend Order with paymentMethod, source, customFields - Add OrderSource enum (online, market, manual) - Add getOrder(id) to OrdersRepository contract and FakeOrdersRepository - Add WooCommerceOrderMapper (WooCommerce JSON <-> Order domain) - Add WooCommerceOrdersRepository implementing full OrdersRepository - Add orders API methods to WooCommerceApiClient (getOrders, getOrder, updateOrderStatus, createOrder, addOrderNote) - Export WooCommerceApiClient/WooCommerceApiException from feature_wordpress - Add feature_wordpress + http dev deps to feature_orders pubspec - 83 new tests: mapper (25), repository+API client (23), FakeRepo.getOrder (3), plus existing suite grows to 198/198 feature_orders - All packages analyze clean; 847/847 total tests passing - Update master_development_brief.md and build_execution_tracker.md --- docs/development/build_execution_tracker.md | 53 +- docs/development/master_development_brief.md | 13 +- .../dashboard/domain/dashboard_summary.dart | 3 + .../lib/pages/mobile_order_detail_page.dart | 6 + .../dashboard/domain/dashboard_summary.dart | 3 + .../dashboard_controller_test.dart | 3 + .../build/unit_test_assets/NOTICES.Z | Bin 103576 -> 104305 bytes .../feature_orders/lib/feature_orders.dart | 3 + .../lib/src/data/fake_orders_repository.dart | 6 + .../src/data/woo_commerce_order_mapper.dart | 213 ++++++++ .../data/woo_commerce_orders_repository.dart | 89 ++++ .../feature_orders/lib/src/domain/order.dart | 20 + .../lib/src/domain/order_item.dart | 34 ++ .../lib/src/domain/order_source.dart | 11 + .../lib/src/domain/order_status.dart | 9 + .../lib/src/domain/orders_repository.dart | 3 + .../widgets/order_status_chip.dart | 6 + .../packages/feature_orders/pubspec.yaml | 3 + .../test/widgets/orders_page_test.dart | 3 + .../test/woo_commerce_order_mapper_test.dart | 474 ++++++++++++++++++ .../woo_commerce_orders_repository_test.dart | 345 +++++++++++++ .../lib/src/data/woo_commerce_api_client.dart | 167 ++++++ 22 files changed, 1458 insertions(+), 9 deletions(-) create mode 100644 kell_creations_apps/packages/feature_orders/lib/src/data/woo_commerce_order_mapper.dart create mode 100644 kell_creations_apps/packages/feature_orders/lib/src/data/woo_commerce_orders_repository.dart create mode 100644 kell_creations_apps/packages/feature_orders/lib/src/domain/order_source.dart create mode 100644 kell_creations_apps/packages/feature_orders/test/woo_commerce_order_mapper_test.dart create mode 100644 kell_creations_apps/packages/feature_orders/test/woo_commerce_orders_repository_test.dart diff --git a/docs/development/build_execution_tracker.md b/docs/development/build_execution_tracker.md index 46928af..979cd66 100644 --- a/docs/development/build_execution_tracker.md +++ b/docs/development/build_execution_tracker.md @@ -2,9 +2,9 @@ ## Current status -- main baseline updated through: data-layer-contracts (Stage 8B complete) -- next branch: feat/auth-foundation (Stage 8C) -- current stage: Stage 8B complete — shared data layer abstractions activated +- main baseline updated through: feat/wc-orders-integration (Stage 9A complete) +- next branch: feat/wc-inventory-sync (Stage 9B) +- current stage: Stage 9A complete — WooCommerce Orders API integration activated ## Slice tracker @@ -421,6 +421,53 @@ - analyze: passed (dart analyze --fatal-infos — no issues found) - brief updated: yes +### feat/auth-foundation + +- status: merged to main +- date: 2026-07-11 +- inspection: complete +- implementation: complete +- files changed: + - `auth/lib/src/domain/kc_user.dart` — `KcUser` value object with `id`, `displayName`, `email`, `role`; `KcUserRole` enum (`admin`, `operator`, `viewer`) + - `auth/lib/src/domain/auth_state.dart` — `AuthState` sealed class hierarchy: `AuthStateUnauthenticated`, `AuthStateAuthenticated`, `AuthStateLoading`, `AuthStateError` + - `auth/lib/src/domain/auth_exception.dart` — `AuthException` with `code` and `message`; named constructors `invalidCredentials`, `sessionExpired`, `networkError`, `unknown` + - `auth/lib/src/domain/auth_service.dart` — `AuthService` abstract contract with `login()`, `logout()`, `currentUser`, `authStateStream`, `isAuthenticated` + - `auth/lib/src/domain/credential_store.dart` — `CredentialKeys` constants; `CredentialStore` abstract contract with `save()`, `load()`, `delete()`, `clear()` + - `auth/lib/src/data/in_memory_credential_store.dart` — `InMemoryCredentialStore` implementation + - `auth/lib/src/data/fake_auth_service.dart` — `FakeAuthService` with configurable users, delay simulation, and stream broadcasting + - `auth/lib/auth.dart` — barrel exports for all auth types + - `auth/test/auth_test.dart` — 42 tests covering all domain models, contracts, and implementations + - `core/lib/src/app/kc_app_environment.dart` — added `KcAppEnvironment.square` value + - `kell_creations_apps/tools/run_all_tests.sh` — added `packages/auth` to `TESTABLE` and `ANALYZABLE` lists +- tests: passed (42/42 auth, 21/21 core — all passing) +- analyze: passed (dart analyze --fatal-infos — no issues found) +- brief updated: yes + +### feat/wc-orders-integration + +- status: merged to main +- date: 2026-07-11 +- inspection: complete +- implementation: complete +- files changed: + - `feature_orders/lib/src/domain/order_status.dart` — added `onHold`, `refunded`, `failed` values + - `feature_orders/lib/src/domain/order_item.dart` — added `productId`, `variationId`, `customizations` fields + - `feature_orders/lib/src/domain/order.dart` — added `paymentMethod`, `source`, `customFields` fields + - `feature_orders/lib/src/domain/orders_repository.dart` — added `getOrder(id)` to contract + - `feature_orders/lib/src/domain/order_source.dart` — new `OrderSource` enum (`online`, `market`, `manual`) + - `feature_orders/lib/src/data/fake_orders_repository.dart` — implemented `getOrder(id)` with null-safe lookup + - `feature_orders/lib/src/data/woo_commerce_order_mapper.dart` — new `WooCommerceOrderMapper` mapping WooCommerce JSON ↔ `Order` domain model + - `feature_orders/lib/src/data/woo_commerce_orders_repository.dart` — new `WooCommerceOrdersRepository` implementing full `OrdersRepository` contract via `WooCommerceApiClient` + - `feature_orders/lib/feature_orders.dart` — expanded barrel exports for all new types + - `feature_orders/pubspec.yaml` — added `feature_wordpress` dependency; added `http` dev dependency + - `feature_wordpress/lib/src/data/woo_commerce_api_client.dart` — added `getOrders()`, `getOrder(id)`, `updateOrderStatus()`, `createOrder()`, `addOrderNote()` methods + - `feature_wordpress/lib/feature_wordpress.dart` — exported `WooCommerceApiClient` and `WooCommerceApiException` + - `feature_orders/test/woo_commerce_order_mapper_test.dart` — 25 mapper tests covering full JSON → domain mapping, status mapping, item mapping, customizations, edge cases + - `feature_orders/test/woo_commerce_orders_repository_test.dart` — 23 repository and API client tests covering all CRUD operations, error handling, empty-note guard, and FakeOrdersRepository.getOrder +- tests: passed (198/198 feature_orders, 24/24 kell_web, 26/26 kell_mobile — 248 total for affected packages; 847/847 total across all packages) +- analyze: passed (dart analyze --fatal-infos — no issues found in feature_orders, feature_wordpress, kell_web, kell_mobile) +- brief updated: yes + ### feat/bulk-operator-workflows - status: merged to main diff --git a/docs/development/master_development_brief.md b/docs/development/master_development_brief.md index 39ffd7d..42d50d6 100644 --- a/docs/development/master_development_brief.md +++ b/docs/development/master_development_brief.md @@ -140,20 +140,21 @@ Rules: - `core` tests passing - `design_system` tests passing - `feature_wordpress` tests passing +- `feature_orders` tests passing - `kell_web` tests passing - `kell_mobile` tests passing - latest reported count for `core`: `21/21 passed` - latest reported count for `design_system`: `41/41 passed` - latest reported count for `feature_inventory`: `75/75 passed` -- latest reported count for `feature_orders`: `115/115 passed` +- latest reported count for `feature_orders`: `198/198 passed` - latest reported count for `feature_wordpress`: `319/319 passed` - latest reported count for `kell_web`: `24/24 passed` - latest reported count for `integrations`: `38/38 passed` - latest reported count for `data`: `63/63 passed` - latest reported count for `auth`: `42/42 passed` - latest reported count for `kell_mobile`: `26/26 passed` -- total: `764/764 passed` -- baseline commit: merge of `feat/auth-foundation` into `main` (Stage 8C complete, 2026-07-11) +- total: `847/847 passed` +- baseline commit: merge of `feat/wc-orders-integration` into `main` (Stage 9A complete, 2026-07-11) #### Baseline test coverage (established 2026-05-22) @@ -169,8 +170,8 @@ No minimum thresholds are enforced — this is visibility-only tracking. Coverag ### Next recommended branch -**`feat/wc-orders-integration`** — Stage 9A: WooCommerce Orders API integration. -Branch from latest `main`. Stage 8 (infrastructure package activation) is complete. Stage 9A connects the orders feature to WooCommerce so orders placed online appear in the app and order status can be updated. +**`feat/wc-inventory-sync`** — Stage 9B: WooCommerce stock/inventory sync. +Branch from latest `main`. Stage 9A (WooCommerce Orders API integration) is complete. Stage 9B adds bidirectional stock quantity synchronization between the app's inventory and WooCommerce product stock levels. --- @@ -1289,7 +1290,7 @@ Working rules: | Integration | Package/Location | Protocol | Current Status | Target Stage | | -------------------- | ------------------- | ------------- | ------------------- | ------------ | | WooCommerce Products | `feature_wordpress` | REST API v3 | ✅ Production-ready | — | -| WooCommerce Orders | `feature_wordpress` | REST API v3 | ❌ Not implemented | Stage 9A | +| WooCommerce Orders | `feature_orders` | REST API v3 | ✅ Production-ready | Stage 9A ✅ | | WooCommerce Stock | `feature_wordpress` | REST API v3 | ❌ Not implemented | Stage 9B | | WooCommerce Catalog | `feature_wordpress` | REST API v3 | ⚠️ Read + edit only | Stage 9C | | WooCommerce Media | `feature_wordpress` | REST API v3 | ❌ Not implemented | Stage 9C | diff --git a/kell_creations_apps/apps/kell_mobile/lib/dashboard/domain/dashboard_summary.dart b/kell_creations_apps/apps/kell_mobile/lib/dashboard/domain/dashboard_summary.dart index 8adff78..b2c9392 100644 --- a/kell_creations_apps/apps/kell_mobile/lib/dashboard/domain/dashboard_summary.dart +++ b/kell_creations_apps/apps/kell_mobile/lib/dashboard/domain/dashboard_summary.dart @@ -131,6 +131,9 @@ class DashboardSummary { case OrderStatus.delivered: deliveredRevenue += order.total; case OrderStatus.cancelled: + case OrderStatus.onHold: + case OrderStatus.refunded: + case OrderStatus.failed: break; } } diff --git a/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart b/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart index 996b0d9..cb20ba6 100644 --- a/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart +++ b/kell_creations_apps/apps/kell_mobile/lib/pages/mobile_order_detail_page.dart @@ -134,6 +134,12 @@ class _MobileOrderDetailPageState extends State { return 'Delivered'; case OrderStatus.cancelled: return 'Cancelled'; + case OrderStatus.onHold: + return 'On Hold'; + case OrderStatus.refunded: + return 'Refunded'; + case OrderStatus.failed: + return 'Failed'; } } diff --git a/kell_creations_apps/apps/kell_web/lib/dashboard/domain/dashboard_summary.dart b/kell_creations_apps/apps/kell_web/lib/dashboard/domain/dashboard_summary.dart index 572db6d..42746ae 100644 --- a/kell_creations_apps/apps/kell_web/lib/dashboard/domain/dashboard_summary.dart +++ b/kell_creations_apps/apps/kell_web/lib/dashboard/domain/dashboard_summary.dart @@ -129,6 +129,9 @@ class DashboardSummary { case OrderStatus.delivered: deliveredRevenue += order.total; case OrderStatus.cancelled: + case OrderStatus.onHold: + case OrderStatus.refunded: + case OrderStatus.failed: break; } } diff --git a/kell_creations_apps/apps/kell_web/test/dashboard/application/dashboard_controller_test.dart b/kell_creations_apps/apps/kell_web/test/dashboard/application/dashboard_controller_test.dart index 51df208..5c50087 100644 --- a/kell_creations_apps/apps/kell_web/test/dashboard/application/dashboard_controller_test.dart +++ b/kell_creations_apps/apps/kell_web/test/dashboard/application/dashboard_controller_test.dart @@ -70,6 +70,9 @@ class _StubOrdersRepository implements OrdersRepository { @override Future> getOrders() async => orders; + @override + Future getOrder(String id) async => orders.where((o) => o.id == id).firstOrNull; + @override Future updateOrderStatus(String id, OrderStatus newStatus) => throw UnimplementedError(); diff --git a/kell_creations_apps/packages/feature_orders/build/unit_test_assets/NOTICES.Z b/kell_creations_apps/packages/feature_orders/build/unit_test_assets/NOTICES.Z index 2fe8afc4c9a25bd6102322e80ce2d26bcbc3b3e7..5cd4c1c5c49af7bdedf71d4f31b285d6b6ca50ff 100644 GIT binary patch delta 91693 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