@unifiedcommerce/core 0.1.0 → 0.2.0
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/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +8 -3
- package/dist/config/types.d.ts +3 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/interfaces/mcp/server.d.ts +3 -5
- package/dist/interfaces/mcp/server.d.ts.map +1 -1
- package/dist/interfaces/mcp/server.js +25 -510
- package/dist/interfaces/mcp/tool-builder.d.ts +120 -0
- package/dist/interfaces/mcp/tool-builder.d.ts.map +1 -0
- package/dist/interfaces/mcp/tool-builder.js +224 -0
- package/dist/interfaces/mcp/tools/analytics.d.ts +42 -0
- package/dist/interfaces/mcp/tools/analytics.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/analytics.js +70 -0
- package/dist/interfaces/mcp/tools/cart.d.ts +14 -0
- package/dist/interfaces/mcp/tools/cart.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/cart.js +47 -0
- package/dist/interfaces/mcp/tools/catalog.d.ts +53 -0
- package/dist/interfaces/mcp/tools/catalog.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/catalog.js +284 -0
- package/dist/interfaces/mcp/tools/index.d.ts +3 -0
- package/dist/interfaces/mcp/tools/index.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/index.js +20 -0
- package/dist/interfaces/mcp/tools/inventory.d.ts +27 -0
- package/dist/interfaces/mcp/tools/inventory.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/inventory.js +143 -0
- package/dist/interfaces/mcp/tools/orders.d.ts +18 -0
- package/dist/interfaces/mcp/tools/orders.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/orders.js +82 -0
- package/dist/interfaces/mcp/tools/pricing.d.ts +29 -0
- package/dist/interfaces/mcp/tools/pricing.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/pricing.js +90 -0
- package/dist/interfaces/mcp/tools/promotions.d.ts +44 -0
- package/dist/interfaces/mcp/tools/promotions.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/promotions.js +109 -0
- package/dist/interfaces/mcp/tools/registry.d.ts +32 -0
- package/dist/interfaces/mcp/tools/registry.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/registry.js +55 -0
- package/dist/interfaces/mcp/tools/search.d.ts +14 -0
- package/dist/interfaces/mcp/tools/search.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/search.js +39 -0
- package/dist/interfaces/mcp/tools/webhooks.d.ts +15 -0
- package/dist/interfaces/mcp/tools/webhooks.d.ts.map +1 -0
- package/dist/interfaces/mcp/tools/webhooks.js +48 -0
- package/dist/interfaces/mcp/transport.d.ts +17 -2
- package/dist/interfaces/mcp/transport.d.ts.map +1 -1
- package/dist/interfaces/mcp/transport.js +91 -44
- package/dist/interfaces/rest/router.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +1 -1
- package/dist/interfaces/rest/routes/promotions.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/promotions.js +3 -2
- package/dist/kernel/database/adapter.d.ts +8 -0
- package/dist/kernel/database/adapter.d.ts.map +1 -1
- package/dist/kernel/factory/repository-factory.d.ts.map +1 -1
- package/dist/kernel/factory/repository-factory.js +3 -1
- package/dist/kernel/local-api.d.ts.map +1 -1
- package/dist/kernel/local-api.js +2 -0
- package/dist/kernel/plugin/manifest.d.ts +3 -3
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +36 -7
- package/dist/runtime/kernel.d.ts +1 -2
- package/dist/runtime/kernel.d.ts.map +1 -1
- package/dist/runtime/kernel.js +16 -8
- package/dist/runtime/server.d.ts.map +1 -1
- package/dist/runtime/server.js +8 -3
- package/dist/test-utils/create-pglite-adapter.d.ts.map +1 -1
- package/dist/test-utils/create-pglite-adapter.js +7 -6
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +2 -2
- package/src/adapters/console-email.ts +0 -43
- package/src/auth/access.ts +0 -187
- package/src/auth/auth-schema.ts +0 -139
- package/src/auth/middleware.ts +0 -161
- package/src/auth/org.ts +0 -41
- package/src/auth/permissions.ts +0 -28
- package/src/auth/setup.ts +0 -169
- package/src/auth/system-actor.ts +0 -19
- package/src/auth/types.ts +0 -10
- package/src/config/defaults.ts +0 -82
- package/src/config/define-config.ts +0 -53
- package/src/config/types.ts +0 -299
- package/src/generated/plugin-capabilities.d.ts +0 -20
- package/src/generated/plugin-manifest.ts +0 -23
- package/src/generated/plugin-repositories.d.ts +0 -20
- package/src/hooks/checkout-completion.ts +0 -262
- package/src/hooks/checkout.ts +0 -677
- package/src/hooks/order-emails.ts +0 -62
- package/src/index.ts +0 -214
- package/src/interfaces/mcp/agent-prompt.ts +0 -174
- package/src/interfaces/mcp/context-enrichment.ts +0 -177
- package/src/interfaces/mcp/server.ts +0 -617
- package/src/interfaces/mcp/transport.ts +0 -68
- package/src/interfaces/rest/customer-portal.ts +0 -299
- package/src/interfaces/rest/index.ts +0 -74
- package/src/interfaces/rest/router.ts +0 -334
- package/src/interfaces/rest/routes/admin-jobs.ts +0 -58
- package/src/interfaces/rest/routes/audit.ts +0 -50
- package/src/interfaces/rest/routes/carts.ts +0 -89
- package/src/interfaces/rest/routes/catalog.ts +0 -493
- package/src/interfaces/rest/routes/checkout.ts +0 -283
- package/src/interfaces/rest/routes/inventory.ts +0 -70
- package/src/interfaces/rest/routes/media.ts +0 -86
- package/src/interfaces/rest/routes/orders.ts +0 -78
- package/src/interfaces/rest/routes/payments.ts +0 -60
- package/src/interfaces/rest/routes/pricing.ts +0 -57
- package/src/interfaces/rest/routes/promotions.ts +0 -92
- package/src/interfaces/rest/routes/search.ts +0 -71
- package/src/interfaces/rest/routes/webhooks.ts +0 -46
- package/src/interfaces/rest/schemas/admin-jobs.ts +0 -40
- package/src/interfaces/rest/schemas/audit.ts +0 -46
- package/src/interfaces/rest/schemas/carts.ts +0 -125
- package/src/interfaces/rest/schemas/catalog.ts +0 -450
- package/src/interfaces/rest/schemas/checkout.ts +0 -66
- package/src/interfaces/rest/schemas/customer-portal.ts +0 -195
- package/src/interfaces/rest/schemas/inventory.ts +0 -138
- package/src/interfaces/rest/schemas/media.ts +0 -75
- package/src/interfaces/rest/schemas/orders.ts +0 -104
- package/src/interfaces/rest/schemas/pricing.ts +0 -80
- package/src/interfaces/rest/schemas/promotions.ts +0 -110
- package/src/interfaces/rest/schemas/responses.ts +0 -85
- package/src/interfaces/rest/schemas/search.ts +0 -58
- package/src/interfaces/rest/schemas/shared.ts +0 -62
- package/src/interfaces/rest/schemas/webhooks.ts +0 -68
- package/src/interfaces/rest/utils.ts +0 -104
- package/src/interfaces/rest/webhook-router.ts +0 -50
- package/src/kernel/compensation/executor.ts +0 -61
- package/src/kernel/compensation/types.ts +0 -26
- package/src/kernel/database/adapter.ts +0 -13
- package/src/kernel/database/drizzle-db.ts +0 -56
- package/src/kernel/database/migrate.ts +0 -76
- package/src/kernel/database/plugin-types.ts +0 -34
- package/src/kernel/database/schema.ts +0 -49
- package/src/kernel/database/scoped-db.ts +0 -68
- package/src/kernel/database/tx-context.ts +0 -46
- package/src/kernel/error-mapper.ts +0 -15
- package/src/kernel/errors.ts +0 -89
- package/src/kernel/factory/repository-factory.ts +0 -242
- package/src/kernel/hooks/create-context.ts +0 -43
- package/src/kernel/hooks/executor.ts +0 -88
- package/src/kernel/hooks/registry.ts +0 -74
- package/src/kernel/hooks/types.ts +0 -52
- package/src/kernel/http-error.ts +0 -44
- package/src/kernel/jobs/adapter.ts +0 -36
- package/src/kernel/jobs/drizzle-adapter.ts +0 -58
- package/src/kernel/jobs/runner.ts +0 -153
- package/src/kernel/jobs/schema.ts +0 -46
- package/src/kernel/jobs/types.ts +0 -30
- package/src/kernel/local-api.ts +0 -185
- package/src/kernel/plugin/manifest.ts +0 -253
- package/src/kernel/query/executor.ts +0 -184
- package/src/kernel/query/registry.ts +0 -46
- package/src/kernel/result.ts +0 -33
- package/src/kernel/schema/extra-columns.ts +0 -37
- package/src/kernel/service-registry.ts +0 -76
- package/src/kernel/service-timing.ts +0 -89
- package/src/kernel/state-machine/machine.ts +0 -101
- package/src/modules/analytics/drizzle-adapter.ts +0 -426
- package/src/modules/analytics/hooks.ts +0 -11
- package/src/modules/analytics/models.ts +0 -125
- package/src/modules/analytics/repository/index.ts +0 -6
- package/src/modules/analytics/service.ts +0 -245
- package/src/modules/analytics/types.ts +0 -180
- package/src/modules/audit/hooks.ts +0 -78
- package/src/modules/audit/schema.ts +0 -33
- package/src/modules/audit/service.ts +0 -151
- package/src/modules/cart/access.ts +0 -27
- package/src/modules/cart/matcher.ts +0 -26
- package/src/modules/cart/repository/index.ts +0 -234
- package/src/modules/cart/schema.ts +0 -42
- package/src/modules/cart/schemas.ts +0 -38
- package/src/modules/cart/service.ts +0 -541
- package/src/modules/catalog/repository/index.ts +0 -772
- package/src/modules/catalog/schema.ts +0 -203
- package/src/modules/catalog/schemas.ts +0 -104
- package/src/modules/catalog/service.ts +0 -1544
- package/src/modules/customers/repository/index.ts +0 -327
- package/src/modules/customers/schema.ts +0 -64
- package/src/modules/customers/service.ts +0 -171
- package/src/modules/fulfillment/repository/index.ts +0 -426
- package/src/modules/fulfillment/schema.ts +0 -101
- package/src/modules/fulfillment/service.ts +0 -555
- package/src/modules/fulfillment/types.ts +0 -59
- package/src/modules/inventory/repository/index.ts +0 -509
- package/src/modules/inventory/schema.ts +0 -94
- package/src/modules/inventory/schemas.ts +0 -38
- package/src/modules/inventory/service.ts +0 -490
- package/src/modules/media/adapter.ts +0 -17
- package/src/modules/media/repository/index.ts +0 -274
- package/src/modules/media/schema.ts +0 -41
- package/src/modules/media/service.ts +0 -151
- package/src/modules/orders/repository/index.ts +0 -287
- package/src/modules/orders/schema.ts +0 -66
- package/src/modules/orders/service.ts +0 -619
- package/src/modules/orders/stale-order-cleanup.ts +0 -76
- package/src/modules/organization/service.ts +0 -191
- package/src/modules/payments/adapter.ts +0 -47
- package/src/modules/payments/repository/index.ts +0 -6
- package/src/modules/payments/service.ts +0 -107
- package/src/modules/pricing/repository/index.ts +0 -291
- package/src/modules/pricing/schema.ts +0 -71
- package/src/modules/pricing/schemas.ts +0 -38
- package/src/modules/pricing/service.ts +0 -494
- package/src/modules/promotions/repository/index.ts +0 -325
- package/src/modules/promotions/schema.ts +0 -62
- package/src/modules/promotions/schemas.ts +0 -38
- package/src/modules/promotions/service.ts +0 -598
- package/src/modules/search/adapter.ts +0 -57
- package/src/modules/search/hooks.ts +0 -12
- package/src/modules/search/repository/index.ts +0 -6
- package/src/modules/search/service.ts +0 -315
- package/src/modules/shipping/calculator.ts +0 -188
- package/src/modules/shipping/repository/index.ts +0 -6
- package/src/modules/shipping/service.ts +0 -51
- package/src/modules/tax/adapter.ts +0 -60
- package/src/modules/tax/repository/index.ts +0 -6
- package/src/modules/tax/service.ts +0 -53
- package/src/modules/webhooks/hook.ts +0 -34
- package/src/modules/webhooks/repository/index.ts +0 -278
- package/src/modules/webhooks/schema.ts +0 -56
- package/src/modules/webhooks/service.ts +0 -117
- package/src/modules/webhooks/signing.ts +0 -6
- package/src/modules/webhooks/ssrf-guard.ts +0 -71
- package/src/modules/webhooks/tasks.ts +0 -52
- package/src/modules/webhooks/worker.ts +0 -134
- package/src/runtime/commerce.ts +0 -145
- package/src/runtime/kernel.ts +0 -419
- package/src/runtime/logger.ts +0 -36
- package/src/runtime/server.ts +0 -349
- package/src/runtime/shutdown.ts +0 -43
- package/src/test-utils/create-pglite-adapter.ts +0 -129
- package/src/test-utils/create-plugin-test-app.ts +0 -128
- package/src/test-utils/create-repository-test-harness.ts +0 -16
- package/src/test-utils/create-test-config.ts +0 -190
- package/src/test-utils/create-test-kernel.ts +0 -7
- package/src/test-utils/create-test-plugin-context.ts +0 -75
- package/src/test-utils/rest-api-test-utils.ts +0 -265
- package/src/test-utils/test-actors.ts +0 -62
- package/src/test-utils/typed-hooks.ts +0 -54
- package/src/types/commerce-types.ts +0 -34
- package/src/utils/id.ts +0 -3
- package/src/utils/logger.ts +0 -18
- package/src/utils/pagination.ts +0 -22
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import { eq, desc, and, sql } from "drizzle-orm";
|
|
2
|
-
import type { TxContext } from "../../../kernel/database/tx-context.js";
|
|
3
|
-
import type {
|
|
4
|
-
DrizzleDatabase,
|
|
5
|
-
DbOrTx,
|
|
6
|
-
} from "../../../kernel/database/drizzle-db.js";
|
|
7
|
-
import {
|
|
8
|
-
fulfillmentRecords,
|
|
9
|
-
fulfillmentLineItems,
|
|
10
|
-
fulfillmentEvents,
|
|
11
|
-
} from "../schema.js";
|
|
12
|
-
|
|
13
|
-
// Infer types from Drizzle schema
|
|
14
|
-
export type FulfillmentRecord = typeof fulfillmentRecords.$inferSelect;
|
|
15
|
-
export type FulfillmentRecordInsert = typeof fulfillmentRecords.$inferInsert;
|
|
16
|
-
export type FulfillmentLineItem = typeof fulfillmentLineItems.$inferSelect;
|
|
17
|
-
export type FulfillmentLineItemInsert =
|
|
18
|
-
typeof fulfillmentLineItems.$inferInsert;
|
|
19
|
-
export type FulfillmentEvent = typeof fulfillmentEvents.$inferSelect;
|
|
20
|
-
export type FulfillmentEventInsert = typeof fulfillmentEvents.$inferInsert;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* FulfillmentRepository provides type-safe database operations for fulfillments.
|
|
24
|
-
*
|
|
25
|
-
* This repository manages fulfillment records, line item associations, and events.
|
|
26
|
-
* Supports physical shipments, digital deliveries, and access grants.
|
|
27
|
-
* All methods support an optional TxContext parameter for transaction participation.
|
|
28
|
-
*/
|
|
29
|
-
export class FulfillmentRepository {
|
|
30
|
-
constructor(private readonly db: DrizzleDatabase) {}
|
|
31
|
-
|
|
32
|
-
private getDb(ctx?: TxContext): DbOrTx {
|
|
33
|
-
return (ctx?.tx as DbOrTx | undefined) ?? this.db;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
-
// Fulfillment Records
|
|
38
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
async findById(
|
|
41
|
-
id: string,
|
|
42
|
-
ctx?: TxContext,
|
|
43
|
-
): Promise<FulfillmentRecord | undefined> {
|
|
44
|
-
const db = this.getDb(ctx);
|
|
45
|
-
const rows = await db
|
|
46
|
-
.select()
|
|
47
|
-
.from(fulfillmentRecords)
|
|
48
|
-
.where(eq(fulfillmentRecords.id, id));
|
|
49
|
-
return rows[0];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async findByOrderId(
|
|
53
|
-
orderId: string,
|
|
54
|
-
ctx?: TxContext,
|
|
55
|
-
): Promise<FulfillmentRecord[]> {
|
|
56
|
-
const db = this.getDb(ctx);
|
|
57
|
-
return db
|
|
58
|
-
.select()
|
|
59
|
-
.from(fulfillmentRecords)
|
|
60
|
-
.where(eq(fulfillmentRecords.orderId, orderId))
|
|
61
|
-
.orderBy(desc(fulfillmentRecords.createdAt));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async findByCustomerId(
|
|
65
|
-
customerId: string,
|
|
66
|
-
ctx?: TxContext,
|
|
67
|
-
): Promise<FulfillmentRecord[]> {
|
|
68
|
-
const db = this.getDb(ctx);
|
|
69
|
-
return db
|
|
70
|
-
.select()
|
|
71
|
-
.from(fulfillmentRecords)
|
|
72
|
-
.where(eq(fulfillmentRecords.customerId, customerId))
|
|
73
|
-
.orderBy(desc(fulfillmentRecords.createdAt));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async findByStatus(
|
|
77
|
-
status: string,
|
|
78
|
-
ctx?: TxContext,
|
|
79
|
-
): Promise<FulfillmentRecord[]> {
|
|
80
|
-
const db = this.getDb(ctx);
|
|
81
|
-
return db
|
|
82
|
-
.select()
|
|
83
|
-
.from(fulfillmentRecords)
|
|
84
|
-
.where(eq(fulfillmentRecords.status, status))
|
|
85
|
-
.orderBy(desc(fulfillmentRecords.createdAt));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async findByType(
|
|
89
|
-
type: string,
|
|
90
|
-
ctx?: TxContext,
|
|
91
|
-
): Promise<FulfillmentRecord[]> {
|
|
92
|
-
const db = this.getDb(ctx);
|
|
93
|
-
return db
|
|
94
|
-
.select()
|
|
95
|
-
.from(fulfillmentRecords)
|
|
96
|
-
.where(eq(fulfillmentRecords.type, type))
|
|
97
|
-
.orderBy(desc(fulfillmentRecords.createdAt));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async findByOrderIdAndStatus(
|
|
101
|
-
orderId: string,
|
|
102
|
-
status: string,
|
|
103
|
-
ctx?: TxContext,
|
|
104
|
-
): Promise<FulfillmentRecord[]> {
|
|
105
|
-
const db = this.getDb(ctx);
|
|
106
|
-
return db
|
|
107
|
-
.select()
|
|
108
|
-
.from(fulfillmentRecords)
|
|
109
|
-
.where(
|
|
110
|
-
and(
|
|
111
|
-
eq(fulfillmentRecords.orderId, orderId),
|
|
112
|
-
eq(fulfillmentRecords.status, status),
|
|
113
|
-
),
|
|
114
|
-
)
|
|
115
|
-
.orderBy(desc(fulfillmentRecords.createdAt));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async findActiveAccessGrants(
|
|
119
|
-
customerId: string,
|
|
120
|
-
ctx?: TxContext,
|
|
121
|
-
): Promise<FulfillmentRecord[]> {
|
|
122
|
-
const db = this.getDb(ctx);
|
|
123
|
-
return db
|
|
124
|
-
.select()
|
|
125
|
-
.from(fulfillmentRecords)
|
|
126
|
-
.where(
|
|
127
|
-
and(
|
|
128
|
-
eq(fulfillmentRecords.customerId, customerId),
|
|
129
|
-
eq(fulfillmentRecords.type, "access_grant"),
|
|
130
|
-
eq(fulfillmentRecords.isActive, true),
|
|
131
|
-
),
|
|
132
|
-
)
|
|
133
|
-
.orderBy(desc(fulfillmentRecords.grantedAt));
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async findAll(
|
|
137
|
-
options?: { limit?: number; offset?: number },
|
|
138
|
-
ctx?: TxContext,
|
|
139
|
-
): Promise<FulfillmentRecord[]> {
|
|
140
|
-
const db = this.getDb(ctx);
|
|
141
|
-
let query = db
|
|
142
|
-
.select()
|
|
143
|
-
.from(fulfillmentRecords)
|
|
144
|
-
.orderBy(desc(fulfillmentRecords.createdAt))
|
|
145
|
-
.$dynamic();
|
|
146
|
-
|
|
147
|
-
if (options?.limit !== undefined) {
|
|
148
|
-
query = query.limit(options.limit);
|
|
149
|
-
}
|
|
150
|
-
if (options?.offset !== undefined) {
|
|
151
|
-
query = query.offset(options.offset);
|
|
152
|
-
}
|
|
153
|
-
return query;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async create(
|
|
157
|
-
data: FulfillmentRecordInsert,
|
|
158
|
-
ctx?: TxContext,
|
|
159
|
-
): Promise<FulfillmentRecord> {
|
|
160
|
-
const db = this.getDb(ctx);
|
|
161
|
-
const rows = await db.insert(fulfillmentRecords).values(data).returning();
|
|
162
|
-
return rows[0]!;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async update(
|
|
166
|
-
id: string,
|
|
167
|
-
data: Partial<Omit<FulfillmentRecordInsert, "id">>,
|
|
168
|
-
ctx?: TxContext,
|
|
169
|
-
): Promise<FulfillmentRecord | undefined> {
|
|
170
|
-
const db = this.getDb(ctx);
|
|
171
|
-
const rows = await db
|
|
172
|
-
.update(fulfillmentRecords)
|
|
173
|
-
.set({ ...data, updatedAt: new Date() })
|
|
174
|
-
.where(eq(fulfillmentRecords.id, id))
|
|
175
|
-
.returning();
|
|
176
|
-
return rows[0];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async updateStatus(
|
|
180
|
-
id: string,
|
|
181
|
-
status: string,
|
|
182
|
-
ctx?: TxContext,
|
|
183
|
-
): Promise<FulfillmentRecord | undefined> {
|
|
184
|
-
const data: Partial<FulfillmentRecordInsert> = { status };
|
|
185
|
-
|
|
186
|
-
// Set timestamps based on status
|
|
187
|
-
if (status === "shipped") {
|
|
188
|
-
data.shippedAt = new Date();
|
|
189
|
-
} else if (status === "delivered") {
|
|
190
|
-
data.deliveredAt = new Date();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return this.update(id, data, ctx);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async delete(id: string, ctx?: TxContext): Promise<boolean> {
|
|
197
|
-
const db = this.getDb(ctx);
|
|
198
|
-
const result = await db
|
|
199
|
-
.delete(fulfillmentRecords)
|
|
200
|
-
.where(eq(fulfillmentRecords.id, id))
|
|
201
|
-
.returning();
|
|
202
|
-
return result.length > 0;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
206
|
-
// Digital Delivery Operations
|
|
207
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
208
|
-
|
|
209
|
-
async incrementDownloadCount(
|
|
210
|
-
id: string,
|
|
211
|
-
ctx?: TxContext,
|
|
212
|
-
): Promise<FulfillmentRecord | undefined> {
|
|
213
|
-
const db = this.getDb(ctx);
|
|
214
|
-
const rows = await db
|
|
215
|
-
.update(fulfillmentRecords)
|
|
216
|
-
.set({
|
|
217
|
-
downloadCount: sql`${fulfillmentRecords.downloadCount} + 1`,
|
|
218
|
-
updatedAt: new Date(),
|
|
219
|
-
})
|
|
220
|
-
.where(eq(fulfillmentRecords.id, id))
|
|
221
|
-
.returning();
|
|
222
|
-
return rows[0];
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async isDownloadAllowed(id: string, ctx?: TxContext): Promise<boolean> {
|
|
226
|
-
const fulfillment = await this.findById(id, ctx);
|
|
227
|
-
if (!fulfillment || fulfillment.type !== "digital") return false;
|
|
228
|
-
|
|
229
|
-
// Check expiration
|
|
230
|
-
if (
|
|
231
|
-
fulfillment.downloadExpiresAt &&
|
|
232
|
-
new Date() > fulfillment.downloadExpiresAt
|
|
233
|
-
) {
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Check download limit
|
|
238
|
-
if (
|
|
239
|
-
fulfillment.maxDownloads !== null &&
|
|
240
|
-
fulfillment.downloadCount >= fulfillment.maxDownloads
|
|
241
|
-
) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
249
|
-
// Access Grant Operations
|
|
250
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
251
|
-
|
|
252
|
-
async deactivateAccessGrant(
|
|
253
|
-
id: string,
|
|
254
|
-
ctx?: TxContext,
|
|
255
|
-
): Promise<FulfillmentRecord | undefined> {
|
|
256
|
-
return this.update(id, { isActive: false }, ctx);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async activateAccessGrant(
|
|
260
|
-
id: string,
|
|
261
|
-
ctx?: TxContext,
|
|
262
|
-
): Promise<FulfillmentRecord | undefined> {
|
|
263
|
-
return this.update(id, { isActive: true, grantedAt: new Date() }, ctx);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async isAccessGrantActive(id: string, ctx?: TxContext): Promise<boolean> {
|
|
267
|
-
const fulfillment = await this.findById(id, ctx);
|
|
268
|
-
if (!fulfillment || fulfillment.type !== "access_grant") return false;
|
|
269
|
-
|
|
270
|
-
if (!fulfillment.isActive) return false;
|
|
271
|
-
|
|
272
|
-
// Check expiration
|
|
273
|
-
if (fulfillment.expiresAt && new Date() > fulfillment.expiresAt) {
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return true;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
281
|
-
// Fulfillment Line Items
|
|
282
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
283
|
-
|
|
284
|
-
async findLineItemsByFulfillmentId(
|
|
285
|
-
fulfillmentId: string,
|
|
286
|
-
ctx?: TxContext,
|
|
287
|
-
): Promise<FulfillmentLineItem[]> {
|
|
288
|
-
const db = this.getDb(ctx);
|
|
289
|
-
return db
|
|
290
|
-
.select()
|
|
291
|
-
.from(fulfillmentLineItems)
|
|
292
|
-
.where(eq(fulfillmentLineItems.fulfillmentId, fulfillmentId));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async findLineItemsByOrderLineItemId(
|
|
296
|
-
orderLineItemId: string,
|
|
297
|
-
ctx?: TxContext,
|
|
298
|
-
): Promise<FulfillmentLineItem[]> {
|
|
299
|
-
const db = this.getDb(ctx);
|
|
300
|
-
return db
|
|
301
|
-
.select()
|
|
302
|
-
.from(fulfillmentLineItems)
|
|
303
|
-
.where(eq(fulfillmentLineItems.orderLineItemId, orderLineItemId));
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async createLineItem(
|
|
307
|
-
data: FulfillmentLineItemInsert,
|
|
308
|
-
ctx?: TxContext,
|
|
309
|
-
): Promise<FulfillmentLineItem> {
|
|
310
|
-
const db = this.getDb(ctx);
|
|
311
|
-
const rows = await db.insert(fulfillmentLineItems).values(data).returning();
|
|
312
|
-
return rows[0]!;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async createLineItems(
|
|
316
|
-
data: FulfillmentLineItemInsert[],
|
|
317
|
-
ctx?: TxContext,
|
|
318
|
-
): Promise<FulfillmentLineItem[]> {
|
|
319
|
-
if (data.length === 0) return [];
|
|
320
|
-
const db = this.getDb(ctx);
|
|
321
|
-
return db.insert(fulfillmentLineItems).values(data).returning();
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
async deleteLineItemsByFulfillmentId(
|
|
325
|
-
fulfillmentId: string,
|
|
326
|
-
ctx?: TxContext,
|
|
327
|
-
): Promise<void> {
|
|
328
|
-
const db = this.getDb(ctx);
|
|
329
|
-
await db
|
|
330
|
-
.delete(fulfillmentLineItems)
|
|
331
|
-
.where(eq(fulfillmentLineItems.fulfillmentId, fulfillmentId));
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
335
|
-
// Fulfillment Events
|
|
336
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
337
|
-
|
|
338
|
-
async findEventsByFulfillmentId(
|
|
339
|
-
fulfillmentId: string,
|
|
340
|
-
ctx?: TxContext,
|
|
341
|
-
): Promise<FulfillmentEvent[]> {
|
|
342
|
-
const db = this.getDb(ctx);
|
|
343
|
-
return db
|
|
344
|
-
.select()
|
|
345
|
-
.from(fulfillmentEvents)
|
|
346
|
-
.where(eq(fulfillmentEvents.fulfillmentId, fulfillmentId))
|
|
347
|
-
.orderBy(desc(fulfillmentEvents.occurredAt));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async createEvent(
|
|
351
|
-
data: FulfillmentEventInsert,
|
|
352
|
-
ctx?: TxContext,
|
|
353
|
-
): Promise<FulfillmentEvent> {
|
|
354
|
-
const db = this.getDb(ctx);
|
|
355
|
-
const rows = await db.insert(fulfillmentEvents).values(data).returning();
|
|
356
|
-
return rows[0]!;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async recordStatusChange(
|
|
360
|
-
fulfillmentId: string,
|
|
361
|
-
fromStatus: string,
|
|
362
|
-
toStatus: string,
|
|
363
|
-
actorId?: string,
|
|
364
|
-
description?: string,
|
|
365
|
-
ctx?: TxContext,
|
|
366
|
-
): Promise<FulfillmentEvent> {
|
|
367
|
-
return this.createEvent(
|
|
368
|
-
{
|
|
369
|
-
fulfillmentId,
|
|
370
|
-
eventType: "status_change",
|
|
371
|
-
fromStatus,
|
|
372
|
-
toStatus,
|
|
373
|
-
actorId,
|
|
374
|
-
description,
|
|
375
|
-
},
|
|
376
|
-
ctx,
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
381
|
-
// Aggregate Operations
|
|
382
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
383
|
-
|
|
384
|
-
async findWithLineItems(
|
|
385
|
-
id: string,
|
|
386
|
-
ctx?: TxContext,
|
|
387
|
-
): Promise<
|
|
388
|
-
| { fulfillment: FulfillmentRecord; lineItems: FulfillmentLineItem[] }
|
|
389
|
-
| undefined
|
|
390
|
-
> {
|
|
391
|
-
const fulfillment = await this.findById(id, ctx);
|
|
392
|
-
if (!fulfillment) return undefined;
|
|
393
|
-
const lineItems = await this.findLineItemsByFulfillmentId(id, ctx);
|
|
394
|
-
return { fulfillment, lineItems };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async findWithEvents(
|
|
398
|
-
id: string,
|
|
399
|
-
ctx?: TxContext,
|
|
400
|
-
): Promise<
|
|
401
|
-
{ fulfillment: FulfillmentRecord; events: FulfillmentEvent[] } | undefined
|
|
402
|
-
> {
|
|
403
|
-
const fulfillment = await this.findById(id, ctx);
|
|
404
|
-
if (!fulfillment) return undefined;
|
|
405
|
-
const events = await this.findEventsByFulfillmentId(id, ctx);
|
|
406
|
-
return { fulfillment, events };
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Get fulfilled quantity for an order line item.
|
|
411
|
-
* Sums all fulfillment line items linked to the order line item.
|
|
412
|
-
*/
|
|
413
|
-
async getFulfilledQuantity(
|
|
414
|
-
orderLineItemId: string,
|
|
415
|
-
ctx?: TxContext,
|
|
416
|
-
): Promise<number> {
|
|
417
|
-
const db = this.getDb(ctx);
|
|
418
|
-
const result = await db
|
|
419
|
-
.select({
|
|
420
|
-
total: sql<number>`COALESCE(SUM(${fulfillmentLineItems.quantity}), 0)::int`,
|
|
421
|
-
})
|
|
422
|
-
.from(fulfillmentLineItems)
|
|
423
|
-
.where(eq(fulfillmentLineItems.orderLineItemId, orderLineItemId));
|
|
424
|
-
return result[0]?.total ?? 0;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
integer,
|
|
3
|
-
jsonb,
|
|
4
|
-
pgTable,
|
|
5
|
-
text,
|
|
6
|
-
timestamp,
|
|
7
|
-
uuid,
|
|
8
|
-
boolean,
|
|
9
|
-
} from "drizzle-orm/pg-core";
|
|
10
|
-
import { orders, orderLineItems } from "../orders/schema.js";
|
|
11
|
-
import { customers } from "../customers/schema.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Fulfillment Records Table
|
|
15
|
-
*
|
|
16
|
-
* Tracks physical shipments, digital deliveries, and access grants.
|
|
17
|
-
* Each fulfillment can be associated with one or more order line items.
|
|
18
|
-
*/
|
|
19
|
-
export const fulfillmentRecords = pgTable("fulfillment_records", {
|
|
20
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
21
|
-
orderId: uuid("order_id")
|
|
22
|
-
.references(() => orders.id, { onDelete: "cascade" })
|
|
23
|
-
.notNull(),
|
|
24
|
-
customerId: uuid("customer_id").references(() => customers.id),
|
|
25
|
-
|
|
26
|
-
// Fulfillment type: "physical", "digital", "access_grant"
|
|
27
|
-
type: text("type").notNull(),
|
|
28
|
-
|
|
29
|
-
// Status: "pending", "processing", "shipped", "delivered", "cancelled", "failed"
|
|
30
|
-
status: text("status").notNull().default("pending"),
|
|
31
|
-
|
|
32
|
-
// Physical shipment fields
|
|
33
|
-
carrier: text("carrier"),
|
|
34
|
-
trackingNumber: text("tracking_number"),
|
|
35
|
-
trackingUrl: text("tracking_url"),
|
|
36
|
-
estimatedDelivery: timestamp("estimated_delivery", { withTimezone: true }),
|
|
37
|
-
shippedAt: timestamp("shipped_at", { withTimezone: true }),
|
|
38
|
-
deliveredAt: timestamp("delivered_at", { withTimezone: true }),
|
|
39
|
-
|
|
40
|
-
// Digital delivery fields
|
|
41
|
-
downloadUrl: text("download_url"),
|
|
42
|
-
downloadExpiresAt: timestamp("download_expires_at", { withTimezone: true }),
|
|
43
|
-
maxDownloads: integer("max_downloads"),
|
|
44
|
-
downloadCount: integer("download_count").notNull().default(0),
|
|
45
|
-
|
|
46
|
-
// Access grant fields (e.g., course access, membership)
|
|
47
|
-
entityType: text("entity_type"),
|
|
48
|
-
entityId: uuid("entity_id"),
|
|
49
|
-
grantedAt: timestamp("granted_at", { withTimezone: true }),
|
|
50
|
-
expiresAt: timestamp("expires_at", { withTimezone: true }),
|
|
51
|
-
isActive: boolean("is_active").notNull().default(true),
|
|
52
|
-
|
|
53
|
-
// Metadata for extensibility
|
|
54
|
-
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
|
|
55
|
-
|
|
56
|
-
// Timestamps
|
|
57
|
-
createdAt: timestamp("created_at", { withTimezone: true })
|
|
58
|
-
.defaultNow()
|
|
59
|
-
.notNull(),
|
|
60
|
-
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
61
|
-
.defaultNow()
|
|
62
|
-
.notNull(),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Fulfillment Line Items Junction Table
|
|
67
|
-
*
|
|
68
|
-
* Links fulfillment records to order line items (many-to-many).
|
|
69
|
-
* Allows partial fulfillments where only some items from an order are fulfilled together.
|
|
70
|
-
*/
|
|
71
|
-
export const fulfillmentLineItems = pgTable("fulfillment_line_items", {
|
|
72
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
73
|
-
fulfillmentId: uuid("fulfillment_id")
|
|
74
|
-
.references(() => fulfillmentRecords.id, { onDelete: "cascade" })
|
|
75
|
-
.notNull(),
|
|
76
|
-
orderLineItemId: uuid("order_line_item_id")
|
|
77
|
-
.references(() => orderLineItems.id, { onDelete: "cascade" })
|
|
78
|
-
.notNull(),
|
|
79
|
-
quantity: integer("quantity").notNull(),
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Fulfillment Events Table
|
|
84
|
-
*
|
|
85
|
-
* Audit trail of fulfillment status changes and events.
|
|
86
|
-
*/
|
|
87
|
-
export const fulfillmentEvents = pgTable("fulfillment_events", {
|
|
88
|
-
id: uuid("id").defaultRandom().primaryKey(),
|
|
89
|
-
fulfillmentId: uuid("fulfillment_id")
|
|
90
|
-
.references(() => fulfillmentRecords.id, { onDelete: "cascade" })
|
|
91
|
-
.notNull(),
|
|
92
|
-
eventType: text("event_type").notNull(), // "created", "shipped", "delivered", "cancelled", "download", etc.
|
|
93
|
-
fromStatus: text("from_status"),
|
|
94
|
-
toStatus: text("to_status"),
|
|
95
|
-
description: text("description"),
|
|
96
|
-
actorId: text("actor_id"), // User/system that triggered the event
|
|
97
|
-
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
|
|
98
|
-
occurredAt: timestamp("occurred_at", { withTimezone: true })
|
|
99
|
-
.defaultNow()
|
|
100
|
-
.notNull(),
|
|
101
|
-
});
|