@open-mercato/core 0.5.1-develop.3036.f02c281f23 → 0.5.1-develop.3045.b4b3320cc2
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +13 -1
- package/dist/helpers/integration/api.js +29 -16
- package/dist/helpers/integration/api.js.map +2 -2
- package/dist/helpers/integration/auth.js +11 -6
- package/dist/helpers/integration/auth.js.map +3 -3
- package/dist/modules/auth/commands/roles.js +9 -12
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/catalog/ai-agents-context.js +147 -0
- package/dist/modules/catalog/ai-agents-context.js.map +7 -0
- package/dist/modules/catalog/ai-agents.js +383 -0
- package/dist/modules/catalog/ai-agents.js.map +7 -0
- package/dist/modules/catalog/ai-tools/_shared.js +318 -0
- package/dist/modules/catalog/ai-tools/_shared.js.map +7 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js +391 -0
- package/dist/modules/catalog/ai-tools/authoring-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js +167 -0
- package/dist/modules/catalog/ai-tools/categories-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js +120 -0
- package/dist/modules/catalog/ai-tools/configuration-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js +107 -0
- package/dist/modules/catalog/ai-tools/media-tags-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js +429 -0
- package/dist/modules/catalog/ai-tools/merchandising-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js +576 -0
- package/dist/modules/catalog/ai-tools/mutation-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js +208 -0
- package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/products-pack.js +298 -0
- package/dist/modules/catalog/ai-tools/products-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js +57 -0
- package/dist/modules/catalog/ai-tools/stats-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools/types.js +10 -0
- package/dist/modules/catalog/ai-tools/types.js.map +7 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js +75 -0
- package/dist/modules/catalog/ai-tools/variants-pack.js.map +7 -0
- package/dist/modules/catalog/ai-tools.js +28 -0
- package/dist/modules/catalog/ai-tools.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +466 -0
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/products/page.js +7 -1
- package/dist/modules/catalog/backend/catalog/products/page.js.map +2 -2
- package/dist/modules/catalog/components/CatalogStatsCard.js +91 -0
- package/dist/modules/catalog/components/CatalogStatsCard.js.map +7 -0
- package/dist/modules/catalog/components/products/ProductsDataTable.js +23 -3
- package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
- package/dist/modules/catalog/events.js +7 -4
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js +59 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js +17 -0
- package/dist/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js +1 -1
- package/dist/modules/catalog/widgets/injection/product-seo/widget.client.js.map +2 -2
- package/dist/modules/catalog/widgets/injection-table.js +13 -1
- package/dist/modules/catalog/widgets/injection-table.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js +94 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js +9 -0
- package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
- package/dist/modules/customers/ai-agents-context.js +96 -0
- package/dist/modules/customers/ai-agents-context.js.map +7 -0
- package/dist/modules/customers/ai-agents.js +244 -0
- package/dist/modules/customers/ai-agents.js.map +7 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js +1015 -0
- package/dist/modules/customers/ai-tools/activities-tasks-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js +134 -0
- package/dist/modules/customers/ai-tools/addresses-tags-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/companies-pack.js +249 -0
- package/dist/modules/customers/ai-tools/companies-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/deals-pack.js +348 -0
- package/dist/modules/customers/ai-tools/deals-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/people-pack.js +261 -0
- package/dist/modules/customers/ai-tools/people-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/settings-pack.js +102 -0
- package/dist/modules/customers/ai-tools/settings-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools/types.js +10 -0
- package/dist/modules/customers/ai-tools/types.js.map +7 -0
- package/dist/modules/customers/ai-tools.js +20 -0
- package/dist/modules/customers/ai-tools.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +469 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +117 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection-table.js +26 -0
- package/dist/modules/customers/widgets/injection-table.js.map +7 -0
- package/dist/modules/inbox_ops/ai-tools.js +4 -0
- package/dist/modules/inbox_ops/ai-tools.js.map +2 -2
- package/dist/modules/inbox_ops/lib/llmProvider.js +52 -7
- package/dist/modules/inbox_ops/lib/llmProvider.js.map +2 -2
- package/dist/modules/notifications/setup.js +13 -0
- package/dist/modules/notifications/setup.js.map +7 -0
- package/jest.config.cjs +1 -0
- package/jest.setup.ts +18 -0
- package/package.json +5 -3
- package/src/helpers/integration/api.ts +38 -16
- package/src/helpers/integration/auth.ts +13 -6
- package/src/modules/auth/commands/roles.ts +10 -12
- package/src/modules/catalog/AGENTS.md +11 -0
- package/src/modules/catalog/ai-agents-context.ts +239 -0
- package/src/modules/catalog/ai-agents.ts +525 -0
- package/src/modules/catalog/ai-tools/_shared.ts +487 -0
- package/src/modules/catalog/ai-tools/authoring-pack.ts +600 -0
- package/src/modules/catalog/ai-tools/categories-pack.ts +192 -0
- package/src/modules/catalog/ai-tools/configuration-pack.ts +218 -0
- package/src/modules/catalog/ai-tools/media-tags-pack.ts +127 -0
- package/src/modules/catalog/ai-tools/merchandising-pack.ts +608 -0
- package/src/modules/catalog/ai-tools/mutation-pack.ts +761 -0
- package/src/modules/catalog/ai-tools/prices-offers-pack.ts +376 -0
- package/src/modules/catalog/ai-tools/products-pack.ts +387 -0
- package/src/modules/catalog/ai-tools/stats-pack.ts +84 -0
- package/src/modules/catalog/ai-tools/types.ts +81 -0
- package/src/modules/catalog/ai-tools/variants-pack.ts +147 -0
- package/src/modules/catalog/ai-tools.ts +78 -0
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +597 -0
- package/src/modules/catalog/backend/catalog/products/page.tsx +23 -2
- package/src/modules/catalog/components/CatalogStatsCard.tsx +118 -0
- package/src/modules/catalog/components/products/ProductsDataTable.tsx +54 -6
- package/src/modules/catalog/events.ts +7 -4
- package/src/modules/catalog/i18n/de.json +17 -0
- package/src/modules/catalog/i18n/en.json +17 -0
- package/src/modules/catalog/i18n/es.json +17 -0
- package/src/modules/catalog/i18n/pl.json +17 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.client.tsx +109 -0
- package/src/modules/catalog/widgets/injection/merchandising-assistant-trigger/widget.ts +29 -0
- package/src/modules/catalog/widgets/injection/product-seo/widget.client.tsx +1 -1
- package/src/modules/catalog/widgets/injection-table.ts +12 -0
- package/src/modules/customer_accounts/i18n/de.json +5 -0
- package/src/modules/customer_accounts/i18n/en.json +5 -0
- package/src/modules/customer_accounts/i18n/es.json +5 -0
- package/src/modules/customer_accounts/i18n/pl.json +5 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.client.tsx +136 -0
- package/src/modules/customer_accounts/widgets/injection/portal-ai-assistant-trigger/widget.ts +43 -0
- package/src/modules/customer_accounts/widgets/injection-table.ts +9 -0
- package/src/modules/customers/AGENTS.md +13 -0
- package/src/modules/customers/ai-agents-context.ts +150 -0
- package/src/modules/customers/ai-agents.ts +355 -0
- package/src/modules/customers/ai-tools/activities-tasks-pack.ts +1248 -0
- package/src/modules/customers/ai-tools/addresses-tags-pack.ts +145 -0
- package/src/modules/customers/ai-tools/companies-pack.ts +362 -0
- package/src/modules/customers/ai-tools/deals-pack.ts +505 -0
- package/src/modules/customers/ai-tools/people-pack.ts +369 -0
- package/src/modules/customers/ai-tools/settings-pack.ts +121 -0
- package/src/modules/customers/ai-tools/types.ts +76 -0
- package/src/modules/customers/ai-tools.ts +34 -0
- package/src/modules/customers/i18n/de.json +25 -0
- package/src/modules/customers/i18n/en.json +25 -0
- package/src/modules/customers/i18n/es.json +25 -0
- package/src/modules/customers/i18n/pl.json +25 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +580 -0
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.ts +36 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +191 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.ts +37 -0
- package/src/modules/customers/widgets/injection-table.ts +41 -0
- package/src/modules/inbox_ops/ai-tools.ts +4 -0
- package/src/modules/inbox_ops/lib/llmProvider.ts +83 -7
- package/src/modules/notifications/setup.ts +11 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `catalog.list_prices`, `catalog.list_price_kinds_base`, `catalog.list_offers`
|
|
3
|
+
* (Phase 1 WS-C, Step 3.10).
|
|
4
|
+
*
|
|
5
|
+
* Read-only enumeration of prices (base + offer-bound), price kinds, and
|
|
6
|
+
* offers for the caller tenant + organization. Mutation tools land in Step
|
|
7
|
+
* 5.14 under the pending-action contract.
|
|
8
|
+
*
|
|
9
|
+
* `catalog.list_price_kinds_base` uses a distinct name on purpose — Step
|
|
10
|
+
* 3.11 (D18) will own `catalog.list_price_kinds` verbatim; we keep both
|
|
11
|
+
* names available so the D18 tool can layer merchandising-specific shape
|
|
12
|
+
* over the base enumerator.
|
|
13
|
+
*
|
|
14
|
+
* Phase 3b of `.ai/specs/2026-04-27-ai-tools-api-backed-dry-refactor.md`:
|
|
15
|
+
* `catalog.list_prices` and `catalog.list_offers` are now API-backed wrappers
|
|
16
|
+
* over `GET /api/catalog/prices` and `GET /api/catalog/offers`. Tool names,
|
|
17
|
+
* schemas, requiredFeatures, and output shapes are unchanged. The offers
|
|
18
|
+
* route does not expose a `variantId` filter; the AI input is pre-resolved
|
|
19
|
+
* via `CatalogProductPrice` to the matching offer ids and threaded through
|
|
20
|
+
* the route's `id` filter (or post-filtered when more than one matches),
|
|
21
|
+
* mirroring Phase 3a's `companyId` → `ids` trick for `customers.list_people`.
|
|
22
|
+
*/
|
|
23
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
24
|
+
import { z } from 'zod'
|
|
25
|
+
import { defineApiBackedAiTool } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/api-backed-tool'
|
|
26
|
+
import type {
|
|
27
|
+
AiApiOperationRequest,
|
|
28
|
+
AiToolExecutionContext,
|
|
29
|
+
} from '@open-mercato/ai-assistant/modules/ai_assistant/lib/ai-api-operation-runner'
|
|
30
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
31
|
+
import { CatalogProductPrice } from '../data/entities'
|
|
32
|
+
import { assertTenantScope, type CatalogAiToolDefinition, type CatalogToolContext } from './types'
|
|
33
|
+
import { listPriceKindsCore } from './_shared'
|
|
34
|
+
|
|
35
|
+
function resolveEm(ctx: CatalogToolContext | AiToolExecutionContext): EntityManager {
|
|
36
|
+
return ctx.container.resolve<EntityManager>('em')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildScope(ctx: CatalogToolContext | AiToolExecutionContext, tenantId: string) {
|
|
40
|
+
return { tenantId, organizationId: ctx.organizationId }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const listPricesInput = z
|
|
44
|
+
.object({
|
|
45
|
+
productId: z.string().uuid().optional().describe('Restrict to prices attached to this product.'),
|
|
46
|
+
variantId: z.string().uuid().optional().describe('Restrict to prices attached to this variant.'),
|
|
47
|
+
priceKindId: z.string().uuid().optional().describe('Restrict to this price kind.'),
|
|
48
|
+
limit: z.number().int().min(1).max(100).optional().describe('Max rows (default 50, max 100).'),
|
|
49
|
+
offset: z.number().int().min(0).optional().describe('Rows to skip (default 0).'),
|
|
50
|
+
})
|
|
51
|
+
.passthrough()
|
|
52
|
+
|
|
53
|
+
type ListPricesInput = z.infer<typeof listPricesInput>
|
|
54
|
+
|
|
55
|
+
type ListPricesApiItem = {
|
|
56
|
+
id?: string
|
|
57
|
+
product_id?: string | null
|
|
58
|
+
productId?: string | null
|
|
59
|
+
variant_id?: string | null
|
|
60
|
+
variantId?: string | null
|
|
61
|
+
offer_id?: string | null
|
|
62
|
+
offerId?: string | null
|
|
63
|
+
price_kind_id?: string | null
|
|
64
|
+
priceKindId?: string | null
|
|
65
|
+
currency_code?: string | null
|
|
66
|
+
currencyCode?: string | null
|
|
67
|
+
kind?: string | null
|
|
68
|
+
min_quantity?: number | null
|
|
69
|
+
minQuantity?: number | null
|
|
70
|
+
max_quantity?: number | null
|
|
71
|
+
maxQuantity?: number | null
|
|
72
|
+
unit_price_net?: string | number | null
|
|
73
|
+
unitPriceNet?: string | number | null
|
|
74
|
+
unit_price_gross?: string | number | null
|
|
75
|
+
unitPriceGross?: string | number | null
|
|
76
|
+
tax_rate?: string | number | null
|
|
77
|
+
taxRate?: string | number | null
|
|
78
|
+
tax_amount?: string | number | null
|
|
79
|
+
taxAmount?: string | number | null
|
|
80
|
+
channel_id?: string | null
|
|
81
|
+
channelId?: string | null
|
|
82
|
+
user_id?: string | null
|
|
83
|
+
userId?: string | null
|
|
84
|
+
user_group_id?: string | null
|
|
85
|
+
userGroupId?: string | null
|
|
86
|
+
customer_id?: string | null
|
|
87
|
+
customerId?: string | null
|
|
88
|
+
customer_group_id?: string | null
|
|
89
|
+
customerGroupId?: string | null
|
|
90
|
+
starts_at?: string | null
|
|
91
|
+
startsAt?: string | null
|
|
92
|
+
ends_at?: string | null
|
|
93
|
+
endsAt?: string | null
|
|
94
|
+
organization_id?: string | null
|
|
95
|
+
organizationId?: string | null
|
|
96
|
+
tenant_id?: string | null
|
|
97
|
+
tenantId?: string | null
|
|
98
|
+
created_at?: string | null
|
|
99
|
+
createdAt?: string | null
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
type ListPricesApiResponse = {
|
|
103
|
+
items?: ListPricesApiItem[]
|
|
104
|
+
total?: number
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type ListPricesOutput = {
|
|
108
|
+
items: Array<Record<string, unknown>>
|
|
109
|
+
total: number
|
|
110
|
+
limit: number
|
|
111
|
+
offset: number
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const listPricesTool = defineApiBackedAiTool<
|
|
115
|
+
ListPricesInput,
|
|
116
|
+
ListPricesApiResponse,
|
|
117
|
+
ListPricesOutput
|
|
118
|
+
>({
|
|
119
|
+
name: 'catalog.list_prices',
|
|
120
|
+
displayName: 'List prices',
|
|
121
|
+
description:
|
|
122
|
+
'List catalog prices (base + offer-scoped) for the caller tenant + organization. Filters: product, variant, or price kind.',
|
|
123
|
+
inputSchema: listPricesInput,
|
|
124
|
+
requiredFeatures: ['catalog.products.view'],
|
|
125
|
+
toOperation: (input, ctx) => {
|
|
126
|
+
assertTenantScope(ctx as unknown as CatalogToolContext)
|
|
127
|
+
const limit = input.limit ?? 50
|
|
128
|
+
const offset = input.offset ?? 0
|
|
129
|
+
const page = Math.floor(offset / limit) + 1
|
|
130
|
+
|
|
131
|
+
const query: Record<string, string | number | boolean | null | undefined> = {
|
|
132
|
+
page,
|
|
133
|
+
pageSize: limit,
|
|
134
|
+
}
|
|
135
|
+
if (input.productId) query.productId = input.productId
|
|
136
|
+
if (input.variantId) query.variantId = input.variantId
|
|
137
|
+
if (input.priceKindId) query.priceKindId = input.priceKindId
|
|
138
|
+
|
|
139
|
+
const operation: AiApiOperationRequest = {
|
|
140
|
+
method: 'GET',
|
|
141
|
+
path: '/catalog/prices',
|
|
142
|
+
query,
|
|
143
|
+
}
|
|
144
|
+
return operation
|
|
145
|
+
},
|
|
146
|
+
mapResponse: (response, input) => {
|
|
147
|
+
const limit = input.limit ?? 50
|
|
148
|
+
const offset = input.offset ?? 0
|
|
149
|
+
const data = (response.data ?? {}) as ListPricesApiResponse
|
|
150
|
+
const rawItems: ListPricesApiItem[] = Array.isArray(data.items) ? data.items : []
|
|
151
|
+
return {
|
|
152
|
+
items: rawItems.map((row) => {
|
|
153
|
+
const startsAtRaw = row.starts_at ?? row.startsAt ?? null
|
|
154
|
+
const startsAt = startsAtRaw ? new Date(String(startsAtRaw)).toISOString() : null
|
|
155
|
+
const endsAtRaw = row.ends_at ?? row.endsAt ?? null
|
|
156
|
+
const endsAt = endsAtRaw ? new Date(String(endsAtRaw)).toISOString() : null
|
|
157
|
+
const createdAtRaw = row.created_at ?? row.createdAt ?? null
|
|
158
|
+
const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
|
|
159
|
+
return {
|
|
160
|
+
id: row.id,
|
|
161
|
+
priceKindId: row.price_kind_id ?? row.priceKindId ?? null,
|
|
162
|
+
productId: row.product_id ?? row.productId ?? null,
|
|
163
|
+
variantId: row.variant_id ?? row.variantId ?? null,
|
|
164
|
+
offerId: row.offer_id ?? row.offerId ?? null,
|
|
165
|
+
currencyCode: row.currency_code ?? row.currencyCode ?? null,
|
|
166
|
+
kind: row.kind ?? null,
|
|
167
|
+
minQuantity: row.min_quantity ?? row.minQuantity ?? null,
|
|
168
|
+
maxQuantity: row.max_quantity ?? row.maxQuantity ?? null,
|
|
169
|
+
unitPriceNet: row.unit_price_net ?? row.unitPriceNet ?? null,
|
|
170
|
+
unitPriceGross: row.unit_price_gross ?? row.unitPriceGross ?? null,
|
|
171
|
+
taxRate: row.tax_rate ?? row.taxRate ?? null,
|
|
172
|
+
taxAmount: row.tax_amount ?? row.taxAmount ?? null,
|
|
173
|
+
channelId: row.channel_id ?? row.channelId ?? null,
|
|
174
|
+
userId: row.user_id ?? row.userId ?? null,
|
|
175
|
+
userGroupId: row.user_group_id ?? row.userGroupId ?? null,
|
|
176
|
+
customerId: row.customer_id ?? row.customerId ?? null,
|
|
177
|
+
customerGroupId: row.customer_group_id ?? row.customerGroupId ?? null,
|
|
178
|
+
startsAt,
|
|
179
|
+
endsAt,
|
|
180
|
+
organizationId: row.organization_id ?? row.organizationId ?? null,
|
|
181
|
+
tenantId: row.tenant_id ?? row.tenantId ?? null,
|
|
182
|
+
createdAt,
|
|
183
|
+
}
|
|
184
|
+
}),
|
|
185
|
+
total: typeof data.total === 'number' ? data.total : 0,
|
|
186
|
+
limit,
|
|
187
|
+
offset,
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
}) as unknown as CatalogAiToolDefinition
|
|
191
|
+
|
|
192
|
+
const listPriceKindsInput = z
|
|
193
|
+
.object({
|
|
194
|
+
limit: z.number().int().min(1).max(100).optional().describe('Max rows (default 50, max 100).'),
|
|
195
|
+
offset: z.number().int().min(0).optional().describe('Rows to skip (default 0).'),
|
|
196
|
+
})
|
|
197
|
+
.passthrough()
|
|
198
|
+
|
|
199
|
+
const listPriceKindsTool: CatalogAiToolDefinition = {
|
|
200
|
+
name: 'catalog.list_price_kinds_base',
|
|
201
|
+
displayName: 'List price kinds (base)',
|
|
202
|
+
description:
|
|
203
|
+
'Enumerate the tenant price kinds. Base coverage tool — Step 3.11 (D18) owns `catalog.list_price_kinds` verbatim; this tool uses a distinct name to avoid collision.',
|
|
204
|
+
inputSchema: listPriceKindsInput,
|
|
205
|
+
requiredFeatures: ['catalog.settings.manage'],
|
|
206
|
+
tags: ['read', 'catalog'],
|
|
207
|
+
handler: async (rawInput, ctx) => {
|
|
208
|
+
const { tenantId } = assertTenantScope(ctx)
|
|
209
|
+
const input = listPriceKindsInput.parse(rawInput)
|
|
210
|
+
// Shared helper; Step 3.11 `catalog.list_price_kinds` uses the same core
|
|
211
|
+
// so both tools cannot drift.
|
|
212
|
+
return listPriceKindsCore(ctx, input, tenantId)
|
|
213
|
+
},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const listOffersInput = z
|
|
217
|
+
.object({
|
|
218
|
+
productId: z.string().uuid().optional().describe('Restrict to offers for this product.'),
|
|
219
|
+
variantId: z.string().uuid().optional().describe('Restrict to offers whose prices are variant-scoped.'),
|
|
220
|
+
active: z.boolean().optional().describe('When true, only active (non-archived) offers are returned.'),
|
|
221
|
+
limit: z.number().int().min(1).max(100).optional().describe('Max rows (default 50, max 100).'),
|
|
222
|
+
offset: z.number().int().min(0).optional().describe('Rows to skip (default 0).'),
|
|
223
|
+
})
|
|
224
|
+
.passthrough()
|
|
225
|
+
|
|
226
|
+
const NIL_UUID = '00000000-0000-0000-0000-000000000000'
|
|
227
|
+
|
|
228
|
+
type ListOffersInput = z.infer<typeof listOffersInput>
|
|
229
|
+
|
|
230
|
+
type ListOffersApiItem = {
|
|
231
|
+
id?: string
|
|
232
|
+
product_id?: string | null
|
|
233
|
+
productId?: string | null
|
|
234
|
+
channel_id?: string | null
|
|
235
|
+
channelId?: string | null
|
|
236
|
+
title?: string | null
|
|
237
|
+
description?: string | null
|
|
238
|
+
default_media_id?: string | null
|
|
239
|
+
defaultMediaId?: string | null
|
|
240
|
+
default_media_url?: string | null
|
|
241
|
+
defaultMediaUrl?: string | null
|
|
242
|
+
is_active?: boolean | null
|
|
243
|
+
isActive?: boolean | null
|
|
244
|
+
organization_id?: string | null
|
|
245
|
+
organizationId?: string | null
|
|
246
|
+
tenant_id?: string | null
|
|
247
|
+
tenantId?: string | null
|
|
248
|
+
created_at?: string | null
|
|
249
|
+
createdAt?: string | null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
type ListOffersApiResponse = {
|
|
253
|
+
items?: ListOffersApiItem[]
|
|
254
|
+
total?: number
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
type ListOffersOutput = {
|
|
258
|
+
items: Array<Record<string, unknown>>
|
|
259
|
+
total: number
|
|
260
|
+
limit: number
|
|
261
|
+
offset: number
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function resolveOfferIdsForVariant(
|
|
265
|
+
ctx: AiToolExecutionContext | CatalogToolContext,
|
|
266
|
+
tenantId: string,
|
|
267
|
+
variantId: string,
|
|
268
|
+
): Promise<string[]> {
|
|
269
|
+
const em = resolveEm(ctx)
|
|
270
|
+
const priceWhere: Record<string, unknown> = { tenantId, variant: variantId }
|
|
271
|
+
if (ctx.organizationId) priceWhere.organizationId = ctx.organizationId
|
|
272
|
+
const prices = await findWithDecryption<CatalogProductPrice>(
|
|
273
|
+
em,
|
|
274
|
+
CatalogProductPrice,
|
|
275
|
+
priceWhere as any,
|
|
276
|
+
undefined,
|
|
277
|
+
buildScope(ctx, tenantId),
|
|
278
|
+
)
|
|
279
|
+
const offerIds = prices
|
|
280
|
+
.map((price) => (price as any).offer)
|
|
281
|
+
.map((offer) => (offer && typeof offer === 'object' ? offer.id : offer))
|
|
282
|
+
.filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)
|
|
283
|
+
return Array.from(new Set(offerIds))
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const listOffersTool = defineApiBackedAiTool<
|
|
287
|
+
ListOffersInput,
|
|
288
|
+
ListOffersApiResponse,
|
|
289
|
+
ListOffersOutput
|
|
290
|
+
>({
|
|
291
|
+
name: 'catalog.list_offers',
|
|
292
|
+
displayName: 'List offers',
|
|
293
|
+
description:
|
|
294
|
+
'List catalog offers for the caller tenant + organization, optionally narrowed to a product (or a variant via its prices).',
|
|
295
|
+
inputSchema: listOffersInput,
|
|
296
|
+
requiredFeatures: ['catalog.products.view'],
|
|
297
|
+
toOperation: async (input, ctx) => {
|
|
298
|
+
const { tenantId } = assertTenantScope(ctx as unknown as CatalogToolContext)
|
|
299
|
+
const limit = input.limit ?? 50
|
|
300
|
+
const offset = input.offset ?? 0
|
|
301
|
+
const page = Math.floor(offset / limit) + 1
|
|
302
|
+
|
|
303
|
+
const query: Record<string, string | number | boolean | null | undefined> = {
|
|
304
|
+
page,
|
|
305
|
+
pageSize: limit,
|
|
306
|
+
}
|
|
307
|
+
if (input.productId) query.productId = input.productId
|
|
308
|
+
if (input.active === true) query.isActive = 'true'
|
|
309
|
+
|
|
310
|
+
if (input.variantId) {
|
|
311
|
+
const offerIds = await resolveOfferIdsForVariant(ctx, tenantId, input.variantId)
|
|
312
|
+
if (offerIds.length === 0) {
|
|
313
|
+
// Empty match — feed a non-existent uuid so the route returns an
|
|
314
|
+
// empty page without us bypassing the API.
|
|
315
|
+
query.id = NIL_UUID
|
|
316
|
+
} else if (offerIds.length === 1) {
|
|
317
|
+
query.id = offerIds[0]
|
|
318
|
+
}
|
|
319
|
+
// For >1 offer ids the route's single-id filter cannot narrow; the
|
|
320
|
+
// mapper post-filters the unfiltered response by the resolved ids.
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const operation: AiApiOperationRequest = {
|
|
324
|
+
method: 'GET',
|
|
325
|
+
path: '/catalog/offers',
|
|
326
|
+
query,
|
|
327
|
+
}
|
|
328
|
+
return operation
|
|
329
|
+
},
|
|
330
|
+
mapResponse: async (response, input, ctx) => {
|
|
331
|
+
const limit = input.limit ?? 50
|
|
332
|
+
const offset = input.offset ?? 0
|
|
333
|
+
const data = (response.data ?? {}) as ListOffersApiResponse
|
|
334
|
+
let rawItems: ListOffersApiItem[] = Array.isArray(data.items) ? data.items : []
|
|
335
|
+
let total = typeof data.total === 'number' ? data.total : 0
|
|
336
|
+
|
|
337
|
+
if (input.variantId) {
|
|
338
|
+
const { tenantId } = assertTenantScope(ctx as unknown as CatalogToolContext)
|
|
339
|
+
const offerIds = await resolveOfferIdsForVariant(ctx, tenantId, input.variantId)
|
|
340
|
+
const offerIdSet = new Set(offerIds)
|
|
341
|
+
rawItems = rawItems.filter((row) => typeof row.id === 'string' && offerIdSet.has(row.id))
|
|
342
|
+
total = rawItems.length
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
items: rawItems.map((row) => {
|
|
347
|
+
const createdAtRaw = row.created_at ?? row.createdAt ?? null
|
|
348
|
+
const createdAt = createdAtRaw ? new Date(String(createdAtRaw)).toISOString() : null
|
|
349
|
+
return {
|
|
350
|
+
id: row.id,
|
|
351
|
+
title: row.title ?? '',
|
|
352
|
+
description: row.description ?? null,
|
|
353
|
+
channelId: row.channel_id ?? row.channelId ?? null,
|
|
354
|
+
productId: row.product_id ?? row.productId ?? null,
|
|
355
|
+
defaultMediaId: row.default_media_id ?? row.defaultMediaId ?? null,
|
|
356
|
+
defaultMediaUrl: row.default_media_url ?? row.defaultMediaUrl ?? null,
|
|
357
|
+
isActive: !!(row.is_active ?? row.isActive),
|
|
358
|
+
organizationId: row.organization_id ?? row.organizationId ?? null,
|
|
359
|
+
tenantId: row.tenant_id ?? row.tenantId ?? null,
|
|
360
|
+
createdAt,
|
|
361
|
+
}
|
|
362
|
+
}),
|
|
363
|
+
total,
|
|
364
|
+
limit,
|
|
365
|
+
offset,
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
}) as unknown as CatalogAiToolDefinition
|
|
369
|
+
|
|
370
|
+
export const pricesOffersAiTools: CatalogAiToolDefinition[] = [
|
|
371
|
+
listPricesTool,
|
|
372
|
+
listPriceKindsTool,
|
|
373
|
+
listOffersTool,
|
|
374
|
+
]
|
|
375
|
+
|
|
376
|
+
export default pricesOffersAiTools
|