agentic-dev 0.2.10 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -9
- package/bin/agentic-dev.mjs +656 -124
- package/lib/scaffold.mjs +109 -6
- package/package.json +1 -1
- package/client/admin/.dockerignore +0 -3
- package/client/admin/.env.example +0 -1
- package/client/admin/Dockerfile +0 -16
- package/client/admin/Dockerfile.dev +0 -18
- package/client/admin/README.md +0 -20
- package/client/admin/index.html +0 -12
- package/client/admin/package.json +0 -41
- package/client/admin/postcss.config.js +0 -6
- package/client/admin/scripts/ui-parity-admin-adapter.mjs +0 -65
- package/client/admin/src/api/alerts.ts +0 -33
- package/client/admin/src/api/client.ts +0 -71
- package/client/admin/src/api/orders.ts +0 -33
- package/client/admin/src/api/support.ts +0 -11
- package/client/admin/src/app/App.tsx +0 -23
- package/client/admin/src/auth/AuthProvider.tsx +0 -122
- package/client/admin/src/auth/ProtectedRoute.tsx +0 -22
- package/client/admin/src/auth/auth-client.ts +0 -38
- package/client/admin/src/auth/types.ts +0 -18
- package/client/admin/src/components/AdminNotificationsDrawer.tsx +0 -162
- package/client/admin/src/components/AdminShell.tsx +0 -76
- package/client/admin/src/components/ui/button.tsx +0 -34
- package/client/admin/src/components/ui/input.tsx +0 -21
- package/client/admin/src/lib/cn.ts +0 -6
- package/client/admin/src/lib/specRouteCatalog.json +0 -30
- package/client/admin/src/lib/specScreens.json +0 -22
- package/client/admin/src/main.tsx +0 -17
- package/client/admin/src/pages/AdminDashboardPage.tsx +0 -171
- package/client/admin/src/pages/AdminLoginPage.tsx +0 -75
- package/client/admin/src/pages/AdminQueuePage.tsx +0 -107
- package/client/admin/src/pages/AdminSupportPage.tsx +0 -61
- package/client/admin/src/styles/globals.css +0 -17
- package/client/admin/src/theme-vars.ts +0 -18
- package/client/admin/src/theme.ts +0 -25
- package/client/admin/src/vite-env.d.ts +0 -1
- package/client/admin/tailwind.config.js +0 -8
- package/client/admin/tsconfig.json +0 -25
- package/client/admin/vite.config.ts +0 -12
- package/client/landing/.dockerignore +0 -3
- package/client/landing/.env.example +0 -1
- package/client/landing/Dockerfile +0 -16
- package/client/landing/Dockerfile.dev +0 -18
- package/client/landing/README.md +0 -18
- package/client/landing/index.html +0 -12
- package/client/landing/package.json +0 -41
- package/client/landing/postcss.config.js +0 -6
- package/client/landing/scripts/ui-parity-landing-adapter.mjs +0 -65
- package/client/landing/src/App.tsx +0 -21
- package/client/landing/src/api/catalog.ts +0 -30
- package/client/landing/src/api/client.ts +0 -30
- package/client/landing/src/auth/AuthProvider.tsx +0 -122
- package/client/landing/src/auth/ProtectedRoute.tsx +0 -22
- package/client/landing/src/auth/auth-client.ts +0 -38
- package/client/landing/src/auth/types.ts +0 -18
- package/client/landing/src/components/LandingShell.tsx +0 -34
- package/client/landing/src/lib/specRouteCatalog.json +0 -23
- package/client/landing/src/lib/specScreens.json +0 -17
- package/client/landing/src/main.tsx +0 -17
- package/client/landing/src/pages/LandingHomePage.tsx +0 -215
- package/client/landing/src/pages/LandingLoginPage.tsx +0 -90
- package/client/landing/src/pages/LandingWorkspacePage.tsx +0 -126
- package/client/landing/src/styles/globals.css +0 -17
- package/client/landing/src/theme-vars.ts +0 -16
- package/client/landing/src/theme.ts +0 -21
- package/client/landing/src/vite-env.d.ts +0 -1
- package/client/landing/tailwind.config.js +0 -8
- package/client/landing/tsconfig.json +0 -25
- package/client/landing/vite.config.ts +0 -12
- package/client/mobile/.dockerignore +0 -2
- package/client/mobile/.env.example +0 -1
- package/client/mobile/Dockerfile +0 -16
- package/client/mobile/Dockerfile.dev +0 -18
- package/client/mobile/README.md +0 -19
- package/client/mobile/index.html +0 -12
- package/client/mobile/package.json +0 -42
- package/client/mobile/postcss.config.js +0 -6
- package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +0 -67
- package/client/mobile/src/App.tsx +0 -1
- package/client/mobile/src/api/client.ts +0 -62
- package/client/mobile/src/api/fulfillment.ts +0 -55
- package/client/mobile/src/api/shipping.ts +0 -56
- package/client/mobile/src/app/App.tsx +0 -23
- package/client/mobile/src/auth/AuthProvider.tsx +0 -122
- package/client/mobile/src/auth/ProtectedRoute.tsx +0 -27
- package/client/mobile/src/auth/auth-client.ts +0 -38
- package/client/mobile/src/auth/types.ts +0 -18
- package/client/mobile/src/components/InShell.tsx +0 -74
- package/client/mobile/src/components/ui/button.tsx +0 -35
- package/client/mobile/src/components/ui/card.tsx +0 -15
- package/client/mobile/src/components/ui/input.tsx +0 -21
- package/client/mobile/src/lib/cn.ts +0 -6
- package/client/mobile/src/lib/specRouteCatalog.json +0 -26
- package/client/mobile/src/lib/specScreens.json +0 -22
- package/client/mobile/src/lib/useSpeechRecognitionInput.ts +0 -271
- package/client/mobile/src/main.tsx +0 -17
- package/client/mobile/src/pages/DashboardPage.tsx +0 -172
- package/client/mobile/src/pages/FulfillmentPage.tsx +0 -138
- package/client/mobile/src/pages/LoginPage.tsx +0 -74
- package/client/mobile/src/pages/ShippingPage.tsx +0 -338
- package/client/mobile/src/styles/globals.css +0 -23
- package/client/mobile/src/theme-vars.ts +0 -16
- package/client/mobile/src/theme.ts +0 -21
- package/client/mobile/src/vite-env.d.ts +0 -1
- package/client/mobile/tailwind.config.js +0 -8
- package/client/mobile/tsconfig.json +0 -25
- package/client/mobile/vite.config.ts +0 -12
- package/client/web/.dockerignore +0 -3
- package/client/web/.env.example +0 -1
- package/client/web/Dockerfile +0 -16
- package/client/web/Dockerfile.dev +0 -18
- package/client/web/README.md +0 -47
- package/client/web/index.html +0 -12
- package/client/web/package.json +0 -42
- package/client/web/postcss.config.js +0 -6
- package/client/web/scripts/ui-parity-web-adapter.mjs +0 -66
- package/client/web/src/api/client.ts +0 -30
- package/client/web/src/api/orders.ts +0 -42
- package/client/web/src/app/App.tsx +0 -21
- package/client/web/src/auth/AuthProvider.tsx +0 -122
- package/client/web/src/auth/ProtectedRoute.tsx +0 -22
- package/client/web/src/auth/auth-client.ts +0 -38
- package/client/web/src/auth/types.ts +0 -18
- package/client/web/src/components/AppShell.tsx +0 -59
- package/client/web/src/components/ui/button.tsx +0 -35
- package/client/web/src/components/ui/card.tsx +0 -7
- package/client/web/src/components/ui/input.tsx +0 -21
- package/client/web/src/lib/cn.ts +0 -6
- package/client/web/src/lib/specRouteCatalog.json +0 -23
- package/client/web/src/lib/specScreens.json +0 -17
- package/client/web/src/main.tsx +0 -17
- package/client/web/src/pages/DashboardPage.tsx +0 -158
- package/client/web/src/pages/LoginPage.tsx +0 -72
- package/client/web/src/pages/OrdersPage.tsx +0 -123
- package/client/web/src/styles/globals.css +0 -17
- package/client/web/src/theme-vars.ts +0 -18
- package/client/web/src/theme.ts +0 -25
- package/client/web/src/vite-env.d.ts +0 -1
- package/client/web/tailwind.config.js +0 -8
- package/client/web/tsconfig.json +0 -25
- package/client/web/vite.config.ts +0 -12
- package/server/.dockerignore +0 -4
- package/server/.env.example +0 -19
- package/server/Dockerfile +0 -22
- package/server/Dockerfile.dev +0 -19
- package/server/README.md +0 -33
- package/server/__init__.py +0 -0
- package/server/api/__init__.py +0 -1
- package/server/api/http/__init__.py +0 -4
- package/server/api/http/app.py +0 -53
- package/server/api/http/router.py +0 -24
- package/server/config.py +0 -52
- package/server/contexts/__init__.py +0 -12
- package/server/contexts/alerts/__init__.py +0 -1
- package/server/contexts/alerts/application/__init__.py +0 -13
- package/server/contexts/alerts/application/services.py +0 -41
- package/server/contexts/alerts/contracts/__init__.py +0 -3
- package/server/contexts/alerts/contracts/http/__init__.py +0 -3
- package/server/contexts/alerts/contracts/http/router.py +0 -37
- package/server/contexts/alerts/domain/__init__.py +0 -15
- package/server/contexts/alerts/domain/models.py +0 -29
- package/server/contexts/alerts/infrastructure/__init__.py +0 -11
- package/server/contexts/alerts/infrastructure/repository.py +0 -41
- package/server/contexts/auth/__init__.py +0 -1
- package/server/contexts/auth/application/__init__.py +0 -3
- package/server/contexts/auth/application/ports.py +0 -10
- package/server/contexts/auth/application/services.py +0 -64
- package/server/contexts/auth/contracts/__init__.py +0 -4
- package/server/contexts/auth/contracts/http/__init__.py +0 -4
- package/server/contexts/auth/contracts/http/dependencies.py +0 -37
- package/server/contexts/auth/contracts/http/router.py +0 -19
- package/server/contexts/auth/domain/__init__.py +0 -3
- package/server/contexts/auth/domain/models.py +0 -24
- package/server/contexts/auth/infrastructure/__init__.py +0 -4
- package/server/contexts/auth/infrastructure/adapters/memory.py +0 -19
- package/server/contexts/auth/infrastructure/adapters/mongodb.py +0 -24
- package/server/contexts/auth/infrastructure/adapters/sqlalchemy.py +0 -74
- package/server/contexts/auth/infrastructure/repository.py +0 -28
- package/server/contexts/catalog/__init__.py +0 -1
- package/server/contexts/catalog/application/__init__.py +0 -28
- package/server/contexts/catalog/application/ports.py +0 -15
- package/server/contexts/catalog/application/services.py +0 -154
- package/server/contexts/catalog/contracts/__init__.py +0 -3
- package/server/contexts/catalog/contracts/http/__init__.py +0 -3
- package/server/contexts/catalog/contracts/http/router.py +0 -60
- package/server/contexts/catalog/domain/__init__.py +0 -45
- package/server/contexts/catalog/domain/models.py +0 -113
- package/server/contexts/catalog/infrastructure/__init__.py +0 -4
- package/server/contexts/catalog/infrastructure/adapters/memory.py +0 -62
- package/server/contexts/catalog/infrastructure/repository.py +0 -8
- package/server/contexts/fulfillment/__init__.py +0 -1
- package/server/contexts/fulfillment/application/__init__.py +0 -13
- package/server/contexts/fulfillment/application/ports.py +0 -20
- package/server/contexts/fulfillment/application/services.py +0 -85
- package/server/contexts/fulfillment/contracts/__init__.py +0 -3
- package/server/contexts/fulfillment/contracts/http/__init__.py +0 -3
- package/server/contexts/fulfillment/contracts/http/router.py +0 -40
- package/server/contexts/fulfillment/domain/__init__.py +0 -25
- package/server/contexts/fulfillment/domain/models.py +0 -73
- package/server/contexts/fulfillment/infrastructure/__init__.py +0 -13
- package/server/contexts/fulfillment/infrastructure/adapters/memory.py +0 -43
- package/server/contexts/fulfillment/infrastructure/repository.py +0 -97
- package/server/contexts/health/__init__.py +0 -1
- package/server/contexts/health/application/__init__.py +0 -3
- package/server/contexts/health/application/services.py +0 -2
- package/server/contexts/health/contracts/__init__.py +0 -3
- package/server/contexts/health/contracts/http/__init__.py +0 -3
- package/server/contexts/health/contracts/http/router.py +0 -10
- package/server/contexts/inventory/__init__.py +0 -1
- package/server/contexts/inventory/application/__init__.py +0 -28
- package/server/contexts/inventory/application/ports.py +0 -11
- package/server/contexts/inventory/application/services.py +0 -214
- package/server/contexts/inventory/contracts/__init__.py +0 -3
- package/server/contexts/inventory/contracts/http/__init__.py +0 -3
- package/server/contexts/inventory/contracts/http/router.py +0 -82
- package/server/contexts/inventory/domain/__init__.py +0 -33
- package/server/contexts/inventory/domain/models.py +0 -93
- package/server/contexts/inventory/infrastructure/__init__.py +0 -4
- package/server/contexts/inventory/infrastructure/adapters/memory.py +0 -24
- package/server/contexts/inventory/infrastructure/repository.py +0 -8
- package/server/contexts/orders/__init__.py +0 -1
- package/server/contexts/orders/application/__init__.py +0 -19
- package/server/contexts/orders/application/services.py +0 -127
- package/server/contexts/orders/contracts/__init__.py +0 -3
- package/server/contexts/orders/contracts/http/__init__.py +0 -3
- package/server/contexts/orders/contracts/http/router.py +0 -82
- package/server/contexts/orders/domain/__init__.py +0 -29
- package/server/contexts/orders/domain/models.py +0 -95
- package/server/contexts/orders/infrastructure/__init__.py +0 -7
- package/server/contexts/orders/infrastructure/repository.py +0 -104
- package/server/contexts/shipping/__init__.py +0 -1
- package/server/contexts/shipping/application/__init__.py +0 -13
- package/server/contexts/shipping/application/services.py +0 -92
- package/server/contexts/shipping/contracts/__init__.py +0 -3
- package/server/contexts/shipping/contracts/http/__init__.py +0 -3
- package/server/contexts/shipping/contracts/http/router.py +0 -40
- package/server/contexts/shipping/domain/__init__.py +0 -19
- package/server/contexts/shipping/domain/models.py +0 -48
- package/server/contexts/shipping/infrastructure/__init__.py +0 -9
- package/server/contexts/shipping/infrastructure/repository.py +0 -50
- package/server/contexts/support/__init__.py +0 -1
- package/server/contexts/support/application/__init__.py +0 -13
- package/server/contexts/support/application/services.py +0 -29
- package/server/contexts/support/contracts/__init__.py +0 -3
- package/server/contexts/support/contracts/http/__init__.py +0 -3
- package/server/contexts/support/contracts/http/router.py +0 -40
- package/server/contexts/support/domain/__init__.py +0 -13
- package/server/contexts/support/domain/models.py +0 -27
- package/server/contexts/support/infrastructure/__init__.py +0 -11
- package/server/contexts/support/infrastructure/repository.py +0 -70
- package/server/contexts/user/__init__.py +0 -1
- package/server/contexts/user/application/__init__.py +0 -3
- package/server/contexts/user/application/ports.py +0 -11
- package/server/contexts/user/application/services.py +0 -44
- package/server/contexts/user/contracts/__init__.py +0 -3
- package/server/contexts/user/contracts/http/__init__.py +0 -3
- package/server/contexts/user/contracts/http/router.py +0 -26
- package/server/contexts/user/domain/__init__.py +0 -3
- package/server/contexts/user/domain/models.py +0 -22
- package/server/contexts/user/infrastructure/__init__.py +0 -3
- package/server/contexts/user/infrastructure/adapters/memory.py +0 -27
- package/server/contexts/user/infrastructure/adapters/mongodb.py +0 -41
- package/server/contexts/user/infrastructure/adapters/sqlalchemy.py +0 -94
- package/server/contexts/user/infrastructure/factory.py +0 -28
- package/server/data/README.md +0 -24
- package/server/data/bootstrap/alerts.json +0 -38
- package/server/data/bootstrap/auth_accounts.json +0 -18
- package/server/data/bootstrap/catalog_products.json +0 -179
- package/server/data/bootstrap/fulfillment_events.json +0 -5
- package/server/data/bootstrap/fulfillment_notes.json +0 -5
- package/server/data/bootstrap/fulfillment_tasks.json +0 -50
- package/server/data/bootstrap/inventory_levels.json +0 -80
- package/server/data/bootstrap/orders.json +0 -62
- package/server/data/bootstrap/shipping_shipments.json +0 -50
- package/server/data/bootstrap/support_faqs.json +0 -26
- package/server/data/bootstrap/users.json +0 -20
- package/server/data/bootstrap_loader.py +0 -15
- package/server/docker-entrypoint.sh +0 -56
- package/server/main.py +0 -3
- package/server/pyproject.toml +0 -36
- package/server/shared/__init__.py +0 -1
- package/server/shared/application/__init__.py +0 -3
- package/server/shared/application/health.py +0 -2
- package/server/shared/infrastructure/__init__.py +0 -10
- package/server/shared/infrastructure/runtime.py +0 -6
- package/server/shared/infrastructure/security.py +0 -33
- package/server/tests/e2e/test_domain_feature_flows.py +0 -483
- package/server/tests/test_health.py +0 -49
- package/server/uv.lock +0 -1169
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
from pydantic import BaseModel
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class FulfillmentTaskRecord(BaseModel):
|
|
5
|
-
id: str
|
|
6
|
-
order_id: str
|
|
7
|
-
title: str
|
|
8
|
-
assignee: str
|
|
9
|
-
stage: str
|
|
10
|
-
status: str
|
|
11
|
-
priority: str
|
|
12
|
-
channel: str
|
|
13
|
-
sla_minutes: int
|
|
14
|
-
units: int
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class FulfillmentEvent(BaseModel):
|
|
18
|
-
time: str
|
|
19
|
-
title: str
|
|
20
|
-
tone: str
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class FulfillmentNote(BaseModel):
|
|
24
|
-
id: str
|
|
25
|
-
note: str
|
|
26
|
-
emphasis: str
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class FulfillmentStat(BaseModel):
|
|
30
|
-
label: str
|
|
31
|
-
value: str
|
|
32
|
-
tone: str | None = None
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class FulfillmentStageLoad(BaseModel):
|
|
36
|
-
label: str
|
|
37
|
-
value: str
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class FulfillmentOverview(BaseModel):
|
|
41
|
-
throughput_total: str
|
|
42
|
-
stats: list[FulfillmentStat]
|
|
43
|
-
timeline: list[FulfillmentEvent]
|
|
44
|
-
stage_load: list[FulfillmentStageLoad]
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class FulfillmentBoardItem(BaseModel):
|
|
48
|
-
id: str
|
|
49
|
-
order_id: str
|
|
50
|
-
title: str
|
|
51
|
-
assignee: str
|
|
52
|
-
stage: str
|
|
53
|
-
status: str
|
|
54
|
-
priority: str
|
|
55
|
-
sla: str
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class FulfillmentBoardPayload(BaseModel):
|
|
59
|
-
tasks: list[FulfillmentBoardItem]
|
|
60
|
-
notes: list[FulfillmentNote]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class FulfillmentTaskStatusTransitionCommand(BaseModel):
|
|
64
|
-
status: str
|
|
65
|
-
stage: str | None = None
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class FulfillmentTaskStatusTransition(BaseModel):
|
|
69
|
-
task_id: str
|
|
70
|
-
previous_status: str
|
|
71
|
-
status: str
|
|
72
|
-
previous_stage: str
|
|
73
|
-
stage: str
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from .repository import (
|
|
2
|
-
list_seed_fulfillment_events,
|
|
3
|
-
list_seed_fulfillment_notes,
|
|
4
|
-
list_seed_fulfillment_tasks,
|
|
5
|
-
transition_seed_fulfillment_task_status,
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
__all__ = [
|
|
9
|
-
"list_seed_fulfillment_events",
|
|
10
|
-
"list_seed_fulfillment_notes",
|
|
11
|
-
"list_seed_fulfillment_tasks",
|
|
12
|
-
"transition_seed_fulfillment_task_status",
|
|
13
|
-
]
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from contexts.fulfillment.domain import (
|
|
2
|
-
FulfillmentEvent,
|
|
3
|
-
FulfillmentNote,
|
|
4
|
-
FulfillmentTaskRecord,
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class MemoryFulfillmentRepository:
|
|
9
|
-
def __init__(self) -> None:
|
|
10
|
-
self._tasks: dict[str, FulfillmentTaskRecord] = {}
|
|
11
|
-
self._events: list[FulfillmentEvent] = []
|
|
12
|
-
self._notes: list[FulfillmentNote] = []
|
|
13
|
-
|
|
14
|
-
def initialize(self) -> None:
|
|
15
|
-
return None
|
|
16
|
-
|
|
17
|
-
def seed_tasks(self, tasks: list[FulfillmentTaskRecord]) -> None:
|
|
18
|
-
self._tasks = {task.id: task for task in tasks}
|
|
19
|
-
|
|
20
|
-
def seed_events(self, events: list[FulfillmentEvent]) -> None:
|
|
21
|
-
self._events = list(events)
|
|
22
|
-
|
|
23
|
-
def seed_notes(self, notes: list[FulfillmentNote]) -> None:
|
|
24
|
-
self._notes = list(notes)
|
|
25
|
-
|
|
26
|
-
def list_tasks(self) -> list[FulfillmentTaskRecord]:
|
|
27
|
-
return list(self._tasks.values())
|
|
28
|
-
|
|
29
|
-
def list_events(self) -> list[FulfillmentEvent]:
|
|
30
|
-
return list(self._events)
|
|
31
|
-
|
|
32
|
-
def list_notes(self) -> list[FulfillmentNote]:
|
|
33
|
-
return list(self._notes)
|
|
34
|
-
|
|
35
|
-
def update_task_status(
|
|
36
|
-
self, task_id: str, status: str
|
|
37
|
-
) -> FulfillmentTaskRecord | None:
|
|
38
|
-
task = self._tasks.get(task_id)
|
|
39
|
-
if task is None:
|
|
40
|
-
return None
|
|
41
|
-
updated = task.model_copy(update={"status": status})
|
|
42
|
-
self._tasks[task_id] = updated
|
|
43
|
-
return updated
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
|
|
3
|
-
from contexts.fulfillment.domain import (
|
|
4
|
-
FulfillmentEvent,
|
|
5
|
-
FulfillmentNote,
|
|
6
|
-
FulfillmentTaskRecord,
|
|
7
|
-
FulfillmentTaskStatusTransition,
|
|
8
|
-
FulfillmentTaskStatusTransitionCommand,
|
|
9
|
-
)
|
|
10
|
-
from data.bootstrap_loader import load_bootstrap_json
|
|
11
|
-
|
|
12
|
-
_task_store: list[FulfillmentTaskRecord] | None = None
|
|
13
|
-
_event_store: list[FulfillmentEvent] | None = None
|
|
14
|
-
_note_store: list[FulfillmentNote] | None = None
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _get_task_store() -> list[FulfillmentTaskRecord]:
|
|
18
|
-
global _task_store
|
|
19
|
-
if _task_store is None:
|
|
20
|
-
_task_store = [
|
|
21
|
-
FulfillmentTaskRecord(**entry)
|
|
22
|
-
for entry in load_bootstrap_json("fulfillment_tasks.json")
|
|
23
|
-
]
|
|
24
|
-
return _task_store
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _get_event_store() -> list[FulfillmentEvent]:
|
|
28
|
-
global _event_store
|
|
29
|
-
if _event_store is None:
|
|
30
|
-
_event_store = [
|
|
31
|
-
FulfillmentEvent(**entry)
|
|
32
|
-
for entry in load_bootstrap_json("fulfillment_events.json")
|
|
33
|
-
]
|
|
34
|
-
return _event_store
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _get_note_store() -> list[FulfillmentNote]:
|
|
38
|
-
global _note_store
|
|
39
|
-
if _note_store is None:
|
|
40
|
-
_note_store = [
|
|
41
|
-
FulfillmentNote(**entry)
|
|
42
|
-
for entry in load_bootstrap_json("fulfillment_notes.json")
|
|
43
|
-
]
|
|
44
|
-
return _note_store
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def list_seed_fulfillment_tasks() -> list[FulfillmentTaskRecord]:
|
|
48
|
-
return [task.model_copy(deep=True) for task in _get_task_store()]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def list_seed_fulfillment_events() -> list[FulfillmentEvent]:
|
|
52
|
-
return [event.model_copy(deep=True) for event in _get_event_store()]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def list_seed_fulfillment_notes() -> list[FulfillmentNote]:
|
|
56
|
-
return [note.model_copy(deep=True) for note in _get_note_store()]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def transition_seed_fulfillment_task_status(
|
|
60
|
-
task_id: str, command: FulfillmentTaskStatusTransitionCommand
|
|
61
|
-
) -> FulfillmentTaskStatusTransition:
|
|
62
|
-
tasks = _get_task_store()
|
|
63
|
-
for index, task in enumerate(tasks):
|
|
64
|
-
if task.id != task_id:
|
|
65
|
-
continue
|
|
66
|
-
|
|
67
|
-
updated_task = task.model_copy(
|
|
68
|
-
update={"status": command.status, "stage": command.stage or task.stage}
|
|
69
|
-
)
|
|
70
|
-
tasks[index] = updated_task
|
|
71
|
-
_get_event_store().insert(
|
|
72
|
-
0,
|
|
73
|
-
FulfillmentEvent(
|
|
74
|
-
time=datetime.now().strftime("%H:%M"),
|
|
75
|
-
title=f"{updated_task.order_id} {updated_task.title} -> {updated_task.status}",
|
|
76
|
-
tone=_map_event_tone(updated_task.status),
|
|
77
|
-
),
|
|
78
|
-
)
|
|
79
|
-
return FulfillmentTaskStatusTransition(
|
|
80
|
-
task_id=updated_task.id,
|
|
81
|
-
previous_status=task.status,
|
|
82
|
-
status=updated_task.status,
|
|
83
|
-
previous_stage=task.stage,
|
|
84
|
-
stage=updated_task.stage,
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
raise LookupError(f"Fulfillment task {task_id} not found")
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _map_event_tone(status: str) -> str:
|
|
91
|
-
tone_map = {
|
|
92
|
-
"Queued": "accent",
|
|
93
|
-
"In progress": "warning",
|
|
94
|
-
"Blocked": "warning",
|
|
95
|
-
"Completed": "success",
|
|
96
|
-
}
|
|
97
|
-
return tone_map.get(status, "neutral")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__all__ = ["application", "presentation"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__all__ = ["application", "contracts", "domain", "infrastructure"]
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from .services import (
|
|
2
|
-
adjust_inventory_level_or_404,
|
|
3
|
-
get_inventory_level_or_404,
|
|
4
|
-
list_inventory_levels,
|
|
5
|
-
prepare_inventory_store,
|
|
6
|
-
release_inventory_level_or_404,
|
|
7
|
-
reserve_inventory_or_404,
|
|
8
|
-
set_inventory_level_or_404,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
adjust_inventory_level = adjust_inventory_level_or_404
|
|
12
|
-
release_inventory_level = release_inventory_level_or_404
|
|
13
|
-
reserve_inventory_level = reserve_inventory_or_404
|
|
14
|
-
set_inventory_level = set_inventory_level_or_404
|
|
15
|
-
|
|
16
|
-
__all__ = [
|
|
17
|
-
"adjust_inventory_level",
|
|
18
|
-
"adjust_inventory_level_or_404",
|
|
19
|
-
"get_inventory_level_or_404",
|
|
20
|
-
"list_inventory_levels",
|
|
21
|
-
"prepare_inventory_store",
|
|
22
|
-
"release_inventory_level",
|
|
23
|
-
"release_inventory_level_or_404",
|
|
24
|
-
"reserve_inventory_level",
|
|
25
|
-
"reserve_inventory_or_404",
|
|
26
|
-
"set_inventory_level",
|
|
27
|
-
"set_inventory_level_or_404",
|
|
28
|
-
]
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from typing import Protocol
|
|
2
|
-
|
|
3
|
-
from contexts.inventory.domain import InventoryLevelRecord
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class InventoryRepository(Protocol):
|
|
7
|
-
def initialize(self) -> None: ...
|
|
8
|
-
def seed_levels(self, levels: list[InventoryLevelRecord]) -> None: ...
|
|
9
|
-
def list_levels(self) -> list[InventoryLevelRecord]: ...
|
|
10
|
-
def get_level(self, sku: str, location_id: str) -> InventoryLevelRecord | None: ...
|
|
11
|
-
def upsert_level(self, level: InventoryLevelRecord) -> InventoryLevelRecord: ...
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
from datetime import datetime, timezone
|
|
2
|
-
from functools import lru_cache
|
|
3
|
-
|
|
4
|
-
from fastapi import HTTPException, status as http_status
|
|
5
|
-
|
|
6
|
-
from contexts.inventory.domain import (
|
|
7
|
-
AdjustInventoryCommand,
|
|
8
|
-
InventoryAvailabilityStatus,
|
|
9
|
-
InventoryLevel,
|
|
10
|
-
InventoryLevelRecord,
|
|
11
|
-
InventoryMutationReceipt,
|
|
12
|
-
ReleaseInventoryCommand,
|
|
13
|
-
ReserveInventoryCommand,
|
|
14
|
-
SetInventoryLevelCommand,
|
|
15
|
-
)
|
|
16
|
-
from contexts.inventory.infrastructure import get_inventory_repository
|
|
17
|
-
from data.bootstrap_loader import load_bootstrap_json
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def list_inventory_levels(
|
|
21
|
-
sku: str | None = None,
|
|
22
|
-
product_id: str | None = None,
|
|
23
|
-
location_id: str | None = None,
|
|
24
|
-
availability_status: InventoryAvailabilityStatus | None = None,
|
|
25
|
-
) -> list[InventoryLevel]:
|
|
26
|
-
prepare_inventory_store()
|
|
27
|
-
repository = get_inventory_repository()
|
|
28
|
-
levels = repository.list_levels()
|
|
29
|
-
|
|
30
|
-
if sku is not None:
|
|
31
|
-
normalized_sku = sku.strip().casefold()
|
|
32
|
-
levels = [level for level in levels if level.sku.casefold() == normalized_sku]
|
|
33
|
-
if product_id is not None:
|
|
34
|
-
normalized_product_id = product_id.strip().casefold()
|
|
35
|
-
levels = [level for level in levels if level.product_id.casefold() == normalized_product_id]
|
|
36
|
-
if location_id is not None:
|
|
37
|
-
normalized_location_id = location_id.strip().casefold()
|
|
38
|
-
levels = [level for level in levels if level.location_id.casefold() == normalized_location_id]
|
|
39
|
-
if availability_status is not None:
|
|
40
|
-
levels = [level for level in levels if _availability_status(level) == availability_status]
|
|
41
|
-
|
|
42
|
-
return [_to_level(level) for level in levels]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def get_inventory_level_or_404(sku: str, location_id: str) -> InventoryLevel:
|
|
46
|
-
prepare_inventory_store()
|
|
47
|
-
repository = get_inventory_repository()
|
|
48
|
-
level = repository.get_level(sku, location_id)
|
|
49
|
-
if level is None:
|
|
50
|
-
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Inventory level not found")
|
|
51
|
-
return _to_level(level)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def adjust_inventory_level_or_404(
|
|
55
|
-
sku: str,
|
|
56
|
-
location_id: str,
|
|
57
|
-
command: AdjustInventoryCommand,
|
|
58
|
-
) -> InventoryMutationReceipt:
|
|
59
|
-
prepare_inventory_store()
|
|
60
|
-
repository = get_inventory_repository()
|
|
61
|
-
level = repository.get_level(sku, location_id)
|
|
62
|
-
if level is None:
|
|
63
|
-
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Inventory level not found")
|
|
64
|
-
if command.quantity_delta == 0:
|
|
65
|
-
raise HTTPException(status_code=http_status.HTTP_400_BAD_REQUEST, detail="Adjustment delta must not be zero")
|
|
66
|
-
|
|
67
|
-
next_on_hand = level.on_hand + command.quantity_delta
|
|
68
|
-
if next_on_hand < level.reserved:
|
|
69
|
-
raise HTTPException(
|
|
70
|
-
status_code=http_status.HTTP_409_CONFLICT,
|
|
71
|
-
detail="Adjustment would reduce on-hand stock below reserved stock",
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
updated = level.model_copy(update={"on_hand": next_on_hand, "updated_at": _now_iso()})
|
|
75
|
-
repository.upsert_level(updated)
|
|
76
|
-
return InventoryMutationReceipt(
|
|
77
|
-
action="adjusted",
|
|
78
|
-
sku=updated.sku,
|
|
79
|
-
location_id=updated.location_id,
|
|
80
|
-
quantity=command.quantity_delta,
|
|
81
|
-
reference_id=command.reference_id,
|
|
82
|
-
reason=command.reason,
|
|
83
|
-
level=_to_level(updated),
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def reserve_inventory_or_404(
|
|
88
|
-
sku: str,
|
|
89
|
-
location_id: str,
|
|
90
|
-
command: ReserveInventoryCommand,
|
|
91
|
-
) -> InventoryMutationReceipt:
|
|
92
|
-
prepare_inventory_store()
|
|
93
|
-
repository = get_inventory_repository()
|
|
94
|
-
level = repository.get_level(sku, location_id)
|
|
95
|
-
if level is None:
|
|
96
|
-
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Inventory level not found")
|
|
97
|
-
available_to_sell = level.on_hand - level.reserved
|
|
98
|
-
if command.quantity > available_to_sell:
|
|
99
|
-
raise HTTPException(status_code=http_status.HTTP_409_CONFLICT, detail="Not enough available stock to reserve")
|
|
100
|
-
|
|
101
|
-
updated = level.model_copy(update={"reserved": level.reserved + command.quantity, "updated_at": _now_iso()})
|
|
102
|
-
repository.upsert_level(updated)
|
|
103
|
-
return InventoryMutationReceipt(
|
|
104
|
-
action="reserved",
|
|
105
|
-
sku=updated.sku,
|
|
106
|
-
location_id=updated.location_id,
|
|
107
|
-
quantity=command.quantity,
|
|
108
|
-
reference_id=command.reference_id,
|
|
109
|
-
reason=command.channel,
|
|
110
|
-
level=_to_level(updated),
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def release_inventory_level_or_404(
|
|
115
|
-
sku: str,
|
|
116
|
-
location_id: str,
|
|
117
|
-
command: ReleaseInventoryCommand,
|
|
118
|
-
) -> InventoryMutationReceipt:
|
|
119
|
-
prepare_inventory_store()
|
|
120
|
-
repository = get_inventory_repository()
|
|
121
|
-
level = repository.get_level(sku, location_id)
|
|
122
|
-
if level is None:
|
|
123
|
-
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Inventory level not found")
|
|
124
|
-
if command.quantity > level.reserved:
|
|
125
|
-
raise HTTPException(status_code=http_status.HTTP_409_CONFLICT, detail="Release quantity exceeds reserved stock")
|
|
126
|
-
|
|
127
|
-
updated = level.model_copy(update={"reserved": level.reserved - command.quantity, "updated_at": _now_iso()})
|
|
128
|
-
repository.upsert_level(updated)
|
|
129
|
-
return InventoryMutationReceipt(
|
|
130
|
-
action="released",
|
|
131
|
-
sku=updated.sku,
|
|
132
|
-
location_id=updated.location_id,
|
|
133
|
-
quantity=command.quantity,
|
|
134
|
-
reference_id=command.reference_id,
|
|
135
|
-
reason=command.reason,
|
|
136
|
-
level=_to_level(updated),
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def set_inventory_level_or_404(
|
|
141
|
-
sku: str,
|
|
142
|
-
location_id: str,
|
|
143
|
-
command: SetInventoryLevelCommand,
|
|
144
|
-
) -> InventoryMutationReceipt:
|
|
145
|
-
prepare_inventory_store()
|
|
146
|
-
repository = get_inventory_repository()
|
|
147
|
-
current = repository.get_level(sku, location_id)
|
|
148
|
-
if current is None:
|
|
149
|
-
raise HTTPException(status_code=http_status.HTTP_404_NOT_FOUND, detail="Inventory level not found")
|
|
150
|
-
|
|
151
|
-
updated = current.model_copy(
|
|
152
|
-
update={
|
|
153
|
-
"on_hand": command.on_hand,
|
|
154
|
-
"reserved": command.reserved,
|
|
155
|
-
"safety_stock": command.safety_stock,
|
|
156
|
-
"reorder_point": command.reorder_point,
|
|
157
|
-
"updated_at": _now_iso(),
|
|
158
|
-
}
|
|
159
|
-
)
|
|
160
|
-
repository.upsert_level(updated)
|
|
161
|
-
return InventoryMutationReceipt(
|
|
162
|
-
action="set",
|
|
163
|
-
sku=updated.sku,
|
|
164
|
-
location_id=updated.location_id,
|
|
165
|
-
quantity=updated.on_hand - current.on_hand,
|
|
166
|
-
reference_id=command.reference_id,
|
|
167
|
-
reason=command.reason,
|
|
168
|
-
level=_to_level(updated),
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def prepare_inventory_store() -> None:
|
|
173
|
-
_seed_inventory_store()
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
@lru_cache
|
|
177
|
-
def _seed_inventory_store() -> None:
|
|
178
|
-
repository = get_inventory_repository()
|
|
179
|
-
repository.initialize()
|
|
180
|
-
levels = [InventoryLevelRecord(**entry) for entry in load_bootstrap_json("inventory_levels.json")]
|
|
181
|
-
repository.seed_levels(levels)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _to_level(level: InventoryLevelRecord) -> InventoryLevel:
|
|
185
|
-
available_to_sell = max(level.on_hand - level.reserved, 0)
|
|
186
|
-
return InventoryLevel(
|
|
187
|
-
sku=level.sku,
|
|
188
|
-
product_id=level.product_id,
|
|
189
|
-
product_name=level.product_name,
|
|
190
|
-
variant_name=level.variant_name,
|
|
191
|
-
location_id=level.location_id,
|
|
192
|
-
location_name=level.location_name,
|
|
193
|
-
on_hand=level.on_hand,
|
|
194
|
-
reserved=level.reserved,
|
|
195
|
-
available_to_sell=available_to_sell,
|
|
196
|
-
safety_stock=level.safety_stock,
|
|
197
|
-
reorder_point=level.reorder_point,
|
|
198
|
-
needs_reorder=available_to_sell <= level.reorder_point,
|
|
199
|
-
status=_availability_status(level),
|
|
200
|
-
updated_at=level.updated_at,
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _availability_status(level: InventoryLevelRecord) -> InventoryAvailabilityStatus:
|
|
205
|
-
available_to_sell = max(level.on_hand - level.reserved, 0)
|
|
206
|
-
if available_to_sell == 0:
|
|
207
|
-
return "out_of_stock"
|
|
208
|
-
if available_to_sell <= level.reorder_point:
|
|
209
|
-
return "low_stock"
|
|
210
|
-
return "in_stock"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def _now_iso() -> str:
|
|
214
|
-
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
from fastapi import APIRouter, Depends, Query
|
|
2
|
-
|
|
3
|
-
from contexts.auth.contracts.http.dependencies import require_admin_user
|
|
4
|
-
from contexts.inventory.application import (
|
|
5
|
-
adjust_inventory_level_or_404,
|
|
6
|
-
get_inventory_level_or_404,
|
|
7
|
-
list_inventory_levels,
|
|
8
|
-
release_inventory_level_or_404,
|
|
9
|
-
reserve_inventory_or_404,
|
|
10
|
-
set_inventory_level_or_404,
|
|
11
|
-
)
|
|
12
|
-
from contexts.inventory.domain import (
|
|
13
|
-
AdjustInventoryCommand,
|
|
14
|
-
InventoryAvailabilityStatus,
|
|
15
|
-
InventoryLevel,
|
|
16
|
-
InventoryMutationReceipt,
|
|
17
|
-
ReleaseInventoryCommand,
|
|
18
|
-
ReserveInventoryCommand,
|
|
19
|
-
SetInventoryLevelCommand,
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
router = APIRouter(
|
|
23
|
-
prefix="/inventory",
|
|
24
|
-
tags=["inventory"],
|
|
25
|
-
dependencies=[Depends(require_admin_user)],
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@router.get("/levels", response_model=list[InventoryLevel])
|
|
30
|
-
def list_levels(
|
|
31
|
-
sku: str | None = Query(default=None),
|
|
32
|
-
product_id: str | None = Query(default=None),
|
|
33
|
-
location_id: str | None = Query(default=None),
|
|
34
|
-
status: InventoryAvailabilityStatus | None = Query(default=None),
|
|
35
|
-
) -> list[InventoryLevel]:
|
|
36
|
-
return list_inventory_levels(
|
|
37
|
-
sku=sku,
|
|
38
|
-
product_id=product_id,
|
|
39
|
-
location_id=location_id,
|
|
40
|
-
availability_status=status,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@router.get("/levels/{sku}/{location_id}", response_model=InventoryLevel)
|
|
45
|
-
def get_level(sku: str, location_id: str) -> InventoryLevel:
|
|
46
|
-
return get_inventory_level_or_404(sku, location_id)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@router.post("/levels/{sku}/{location_id}/adjustments", response_model=InventoryMutationReceipt)
|
|
50
|
-
def post_adjustment(
|
|
51
|
-
sku: str,
|
|
52
|
-
location_id: str,
|
|
53
|
-
command: AdjustInventoryCommand,
|
|
54
|
-
) -> InventoryMutationReceipt:
|
|
55
|
-
return adjust_inventory_level_or_404(sku, location_id, command)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@router.post("/levels/{sku}/{location_id}/reservations", response_model=InventoryMutationReceipt)
|
|
59
|
-
def post_reservation(
|
|
60
|
-
sku: str,
|
|
61
|
-
location_id: str,
|
|
62
|
-
command: ReserveInventoryCommand,
|
|
63
|
-
) -> InventoryMutationReceipt:
|
|
64
|
-
return reserve_inventory_or_404(sku, location_id, command)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@router.post("/levels/{sku}/{location_id}/releases", response_model=InventoryMutationReceipt)
|
|
68
|
-
def post_release(
|
|
69
|
-
sku: str,
|
|
70
|
-
location_id: str,
|
|
71
|
-
command: ReleaseInventoryCommand,
|
|
72
|
-
) -> InventoryMutationReceipt:
|
|
73
|
-
return release_inventory_level_or_404(sku, location_id, command)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
@router.put("/levels/{sku}/{location_id}", response_model=InventoryMutationReceipt)
|
|
77
|
-
def put_level(
|
|
78
|
-
sku: str,
|
|
79
|
-
location_id: str,
|
|
80
|
-
command: SetInventoryLevelCommand,
|
|
81
|
-
) -> InventoryMutationReceipt:
|
|
82
|
-
return set_inventory_level_or_404(sku, location_id, command)
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from .models import (
|
|
2
|
-
AdjustInventoryCommand,
|
|
3
|
-
InventoryAdjustmentCommand,
|
|
4
|
-
InventoryAvailabilityStatus,
|
|
5
|
-
InventoryLevel,
|
|
6
|
-
InventoryLevelRecord,
|
|
7
|
-
InventoryLevelView,
|
|
8
|
-
InventoryMutationAction,
|
|
9
|
-
InventoryMutationReceipt,
|
|
10
|
-
InventoryReleaseCommand,
|
|
11
|
-
InventoryReservationCommand,
|
|
12
|
-
InventorySetLevelCommand,
|
|
13
|
-
ReleaseInventoryCommand,
|
|
14
|
-
ReserveInventoryCommand,
|
|
15
|
-
SetInventoryLevelCommand,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
__all__ = [
|
|
19
|
-
"AdjustInventoryCommand",
|
|
20
|
-
"InventoryAdjustmentCommand",
|
|
21
|
-
"InventoryAvailabilityStatus",
|
|
22
|
-
"InventoryLevel",
|
|
23
|
-
"InventoryLevelRecord",
|
|
24
|
-
"InventoryLevelView",
|
|
25
|
-
"InventoryMutationAction",
|
|
26
|
-
"InventoryMutationReceipt",
|
|
27
|
-
"InventoryReleaseCommand",
|
|
28
|
-
"InventoryReservationCommand",
|
|
29
|
-
"InventorySetLevelCommand",
|
|
30
|
-
"ReleaseInventoryCommand",
|
|
31
|
-
"ReserveInventoryCommand",
|
|
32
|
-
"SetInventoryLevelCommand",
|
|
33
|
-
]
|