@open-mercato/core 0.6.7-develop.6706.1.b3a4c759bb → 0.6.7-develop.6725.1.07a748cfc2
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/dist/modules/auth/api/admin/nav.js +14 -4
- package/dist/modules/auth/api/admin/nav.js.map +2 -2
- package/dist/modules/auth/api/users/route.js +8 -19
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/lib/backendChrome.js +31 -14
- package/dist/modules/auth/lib/backendChrome.js.map +2 -2
- package/dist/modules/customers/api/utils.js +12 -18
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/customers/cli.js +11 -4
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +4 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +14 -10
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/batch/route.js +4 -2
- package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/route.js +20 -2
- package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
- package/dist/modules/dashboards/lib/aggregations.js +76 -40
- package/dist/modules/dashboards/lib/aggregations.js.map +2 -2
- package/dist/modules/dashboards/lib/exactDecimal.js +69 -0
- package/dist/modules/dashboards/lib/exactDecimal.js.map +7 -0
- package/dist/modules/dashboards/services/widgetDataService.js +228 -5
- package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
- package/dist/modules/feature_toggles/components/FeatureGuard.js +3 -2
- package/dist/modules/feature_toggles/components/FeatureGuard.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/staleTime.js +5 -0
- package/dist/modules/feature_toggles/components/hooks/staleTime.js.map +7 -0
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagJson.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagNumber.js.map +2 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js +5 -2
- package/dist/modules/feature_toggles/components/hooks/useFeatureFlagString.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/route.js +16 -1
- package/dist/modules/inbox_ops/api/proposals/route.js.map +2 -2
- package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js +32 -0
- package/dist/modules/inbox_ops/subscribers/search-reindex-proposal-created.js.map +7 -0
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js +0 -5
- package/dist/modules/integrations/widgets/injection/external-ids/widget.client.js.map +2 -2
- package/dist/modules/integrations/widgets/injection/external-ids/widget.js +14 -0
- package/dist/modules/integrations/widgets/injection/external-ids/widget.js.map +7 -0
- package/dist/modules/messages/lib/searchLookup.js +8 -16
- package/dist/modules/messages/lib/searchLookup.js.map +2 -2
- package/dist/modules/query_index/api/openapi.js +13 -1
- package/dist/modules/query_index/api/openapi.js.map +2 -2
- package/dist/modules/query_index/api/status.js +165 -105
- package/dist/modules/query_index/api/status.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +128 -41
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/query_index/lib/batch.js +11 -4
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/document.js +12 -5
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +36 -4
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +1 -1
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +7 -4
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +14 -3
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +22 -1
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/useSalesChannelsEnabled.js +2 -35
- package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +2 -2
- package/dist/modules/sales/data/validators.js +25 -4
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/admin/nav.ts +24 -4
- package/src/modules/auth/api/users/route.ts +8 -28
- package/src/modules/auth/lib/backendChrome.tsx +51 -13
- package/src/modules/customers/api/utils.ts +12 -31
- package/src/modules/customers/cli.ts +11 -4
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +4 -0
- package/src/modules/customers/components/formConfig.tsx +12 -8
- package/src/modules/dashboards/api/widgets/data/batch/route.ts +4 -0
- package/src/modules/dashboards/api/widgets/data/route.ts +21 -0
- package/src/modules/dashboards/lib/aggregations.ts +122 -43
- package/src/modules/dashboards/lib/exactDecimal.ts +96 -0
- package/src/modules/dashboards/services/widgetDataService.ts +342 -4
- package/src/modules/feature_toggles/components/FeatureGuard.tsx +9 -2
- package/src/modules/feature_toggles/components/hooks/staleTime.ts +5 -0
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagBoolean.ts +13 -1
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagJson.ts +12 -3
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagNumber.ts +10 -1
- package/src/modules/feature_toggles/components/hooks/useFeatureFlagString.ts +10 -1
- package/src/modules/inbox_ops/api/proposals/route.ts +20 -1
- package/src/modules/inbox_ops/subscribers/search-reindex-proposal-created.ts +39 -0
- package/src/modules/integrations/widgets/injection/external-ids/widget.client.tsx +0 -5
- package/src/modules/integrations/widgets/injection/external-ids/widget.ts +13 -0
- package/src/modules/messages/lib/searchLookup.ts +11 -34
- package/src/modules/query_index/api/openapi.ts +12 -0
- package/src/modules/query_index/api/status.ts +238 -136
- package/src/modules/query_index/components/QueryIndexesTable.tsx +192 -46
- package/src/modules/query_index/i18n/de.json +5 -3
- package/src/modules/query_index/i18n/en.json +5 -3
- package/src/modules/query_index/i18n/es.json +5 -3
- package/src/modules/query_index/i18n/pl.json +5 -3
- package/src/modules/query_index/lib/batch.ts +11 -4
- package/src/modules/query_index/lib/document.ts +35 -4
- package/src/modules/query_index/lib/engine.ts +53 -5
- package/src/modules/query_index/lib/indexer.ts +1 -1
- package/src/modules/query_index/lib/search-tokens.ts +13 -4
- package/src/modules/sales/api/documents/factory.ts +28 -3
- package/src/modules/sales/commands/documents.ts +26 -2
- package/src/modules/sales/components/useSalesChannelsEnabled.ts +2 -48
- package/src/modules/sales/data/validators.ts +50 -5
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { resolveSearchConfig, type SearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
23
23
|
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
24
24
|
import { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from '@open-mercato/shared/lib/query/query-extension-runner'
|
|
25
|
+
import { warnOnCiphertextLikeFallback } from '@open-mercato/shared/lib/query/ciphertext-search-warning'
|
|
25
26
|
import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from '@open-mercato/shared/lib/query/encrypted-sort'
|
|
26
27
|
import { mapWithConcurrency } from '@open-mercato/shared/lib/query/bounded-decrypt'
|
|
27
28
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
@@ -415,12 +416,16 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
415
416
|
const searchSources: SearchTokenSource[] = indexSources
|
|
416
417
|
.map((src) => ({ entity: String(src.entityId), recordIdColumn: src.recordIdColumn }))
|
|
417
418
|
.filter((src) => src.recordIdColumn && src.entity)
|
|
418
|
-
const
|
|
419
|
+
const searchFilters = normalizedFilters.filter((filter) => filter.op === 'like' || filter.op === 'ilike')
|
|
420
|
+
// Probe `search_tokens` only when this query actually searches. Every consumer of
|
|
421
|
+
// `searchRuntime.enabled` already sits behind a like/ilike guard, so on a plain list load the
|
|
422
|
+
// answer is unused — and the probe is a `LIMIT 1` the planner can resolve as a seq scan over a
|
|
423
|
+
// large `search_tokens`. The join path below already probes lazily for the same reason.
|
|
424
|
+
const hasSearchTokens = searchEnabled && searchSources.length && searchFilters.length
|
|
419
425
|
? await this.searchSourcesHaveTokens(searchSources, opts.tenantId ?? null, orgScope)
|
|
420
426
|
: false
|
|
421
427
|
const searchRuntime: SearchRuntime = { ...searchRuntimeBase, searchSources, enabled: searchEnabled && hasSearchTokens }
|
|
422
428
|
const joinSearchAvailability = new Map<string, boolean>()
|
|
423
|
-
const searchFilters = normalizeFilters(opts.filters).filter((filter) => filter.op === 'like' || filter.op === 'ilike')
|
|
424
429
|
if (searchFilters.length) {
|
|
425
430
|
this.logSearchDebug('search:init', {
|
|
426
431
|
entity,
|
|
@@ -446,6 +451,47 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
446
451
|
organizationScope: orgScope,
|
|
447
452
|
searchSources,
|
|
448
453
|
})
|
|
454
|
+
const baseSearchFilters = [...baseFilters, ...cfFilters]
|
|
455
|
+
.filter((filter) => filter.op === 'like' || filter.op === 'ilike')
|
|
456
|
+
const fallbackFields = baseSearchFilters
|
|
457
|
+
.filter((filter) => !searchRuntime.enabled || typeof filter.value !== 'string' || tokenizeText(filter.value, searchConfig).hashes.length === 0)
|
|
458
|
+
.map((filter) => String(filter.field))
|
|
459
|
+
if (fallbackFields.length) {
|
|
460
|
+
await warnOnCiphertextLikeFallback({
|
|
461
|
+
entity: String(entity),
|
|
462
|
+
fields: fallbackFields,
|
|
463
|
+
tenantId: opts.tenantId ?? null,
|
|
464
|
+
// `searchEnabled` also folds in the missing-table case, which is
|
|
465
|
+
// "no usable tokens" rather than "the operator switched search off".
|
|
466
|
+
reason: searchRuntime.enabled
|
|
467
|
+
? 'no-indexable-tokens'
|
|
468
|
+
: searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',
|
|
469
|
+
service: this.getEncryptionService(),
|
|
470
|
+
})
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (const [alias, joinedFilters] of joinFilters) {
|
|
474
|
+
const filters = joinedFilters.filter((entry) => entry.op === 'like' || entry.op === 'ilike')
|
|
475
|
+
if (!filters.length) continue
|
|
476
|
+
const join = joinMap.get(alias)
|
|
477
|
+
if (!join?.entityId) continue
|
|
478
|
+
const hasJoinedTokens = searchEnabled
|
|
479
|
+
? await this.hasSearchTokens(String(join.entityId), opts.tenantId ?? null, orgScope)
|
|
480
|
+
: false
|
|
481
|
+
joinSearchAvailability.set(String(join.entityId), hasJoinedTokens)
|
|
482
|
+
const fallbackFields = filters
|
|
483
|
+
.filter((filter) => !hasJoinedTokens || typeof filter.value !== 'string' || tokenizeText(filter.value, searchConfig).hashes.length === 0)
|
|
484
|
+
.map((filter) => filter.column)
|
|
485
|
+
if (!fallbackFields.length) continue
|
|
486
|
+
await warnOnCiphertextLikeFallback({
|
|
487
|
+
entity: String(join.entityId),
|
|
488
|
+
fields: fallbackFields,
|
|
489
|
+
tenantId: opts.tenantId ?? null,
|
|
490
|
+
reason: hasJoinedTokens
|
|
491
|
+
? 'no-indexable-tokens'
|
|
492
|
+
: searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',
|
|
493
|
+
service: this.getEncryptionService(),
|
|
494
|
+
})
|
|
449
495
|
}
|
|
450
496
|
const hasNonBaseSearchSource = searchSources.some(
|
|
451
497
|
(src) => src.entity !== String(entity) || src.recordIdColumn !== 'b.id'
|
|
@@ -1636,9 +1682,13 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1636
1682
|
const orgScope = this.resolveOrganizationScope(opts)
|
|
1637
1683
|
if (!opts.tenantId) throw new Error('QueryEngine: tenantId is required')
|
|
1638
1684
|
|
|
1685
|
+
const normalizedFilters = normalizeFilters(opts.filters)
|
|
1686
|
+
const hasSearchFilter = normalizedFilters.some((filter) => filter.op === 'like' || filter.op === 'ilike')
|
|
1687
|
+
|
|
1639
1688
|
const searchConfig = resolveSearchConfig()
|
|
1640
1689
|
const searchEnabled = searchConfig.enabled && await this.tableExists('search_tokens')
|
|
1641
|
-
|
|
1690
|
+
// Same gate as `query()`: without a like/ilike filter the probe's answer is never read.
|
|
1691
|
+
const hasSearchTokens = searchEnabled && hasSearchFilter
|
|
1642
1692
|
? await this.hasSearchTokens(entity, opts.tenantId ?? null, orgScope)
|
|
1643
1693
|
: false
|
|
1644
1694
|
const searchRuntime: SearchRuntime = {
|
|
@@ -1649,8 +1699,6 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1649
1699
|
mintAlias: createSearchAliasMinter(),
|
|
1650
1700
|
}
|
|
1651
1701
|
|
|
1652
|
-
const normalizedFilters = normalizeFilters(opts.filters)
|
|
1653
|
-
|
|
1654
1702
|
const applyScope = (q: AnyBuilder): AnyBuilder => {
|
|
1655
1703
|
let next = q
|
|
1656
1704
|
.where(`${alias}.entity_type`, '=', entity)
|
|
@@ -127,7 +127,7 @@ export async function buildIndexDoc(em: EntityManager, params: BuildDocParams):
|
|
|
127
127
|
// Kept outside the guard below: a failure while building the aggregate search field is a
|
|
128
128
|
// bug in the aggregation or its configuration, and must surface instead of being mistaken
|
|
129
129
|
// for an encryption failure and silently skipping encryption.
|
|
130
|
-
doc = attachAggregateSearchField(doc)
|
|
130
|
+
doc = attachAggregateSearchField(doc, { entityType: params.entityType })
|
|
131
131
|
|
|
132
132
|
try {
|
|
133
133
|
const encryption = resolveTenantEncryptionService(em as any)
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type Kysely, sql } from 'kysely'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isSearchFieldBlocklisted,
|
|
4
|
+
resolveSearchConfig,
|
|
5
|
+
type SearchConfig,
|
|
6
|
+
} from '@open-mercato/shared/lib/search/config'
|
|
3
7
|
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
4
8
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
5
9
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
@@ -62,13 +66,18 @@ function collectTextValues(value: unknown): string[] {
|
|
|
62
66
|
return []
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
function shouldIndexField(
|
|
69
|
+
function shouldIndexField(
|
|
70
|
+
field: string,
|
|
71
|
+
value: unknown,
|
|
72
|
+
config: SearchConfig,
|
|
73
|
+
entityType: string | null,
|
|
74
|
+
): boolean {
|
|
66
75
|
if (typeof value !== 'string' && !Array.isArray(value)) return false
|
|
67
76
|
const lower = field.toLowerCase()
|
|
68
77
|
if (lower === 'id' || lower.endsWith('_id') || lower.endsWith('.id')) return false
|
|
69
78
|
if (lower.endsWith('_at')) return false
|
|
70
79
|
if (['created_at', 'updated_at', 'deleted_at', 'tenant_id', 'organization_id'].includes(lower)) return false
|
|
71
|
-
if (
|
|
80
|
+
if (isSearchFieldBlocklisted(field, entityType, config)) return false
|
|
72
81
|
const values = collectTextValues(value)
|
|
73
82
|
if (!values.length) return false
|
|
74
83
|
return values.some((text) => tokenizeText(text, config).tokens.length > 0)
|
|
@@ -87,7 +96,7 @@ export function buildSearchTokenRows(params: BuildTokenOptions): SearchTokenRow[
|
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
for (const [field, rawValue] of Object.entries(params.doc)) {
|
|
90
|
-
if (!shouldIndexField(field, rawValue, config)) continue
|
|
99
|
+
if (!shouldIndexField(field, rawValue, config, params.entityType)) continue
|
|
91
100
|
const values = collectTextValues(rawValue)
|
|
92
101
|
const seen = new Set<string>()
|
|
93
102
|
for (const text of values) {
|
|
@@ -7,8 +7,11 @@ import { E } from '#generated/entities.ids.generated'
|
|
|
7
7
|
import type { SalesOrder, SalesQuote } from '../../data/entities'
|
|
8
8
|
import { SalesDocumentTagAssignment } from '../../data/entities'
|
|
9
9
|
import {
|
|
10
|
+
ORDER_PAYMENT_LEDGER_FIELDS,
|
|
11
|
+
ORDER_PAYMENT_LEDGER_WARNING_CODE,
|
|
10
12
|
orderCreateSchema,
|
|
11
13
|
quoteCreateSchema,
|
|
14
|
+
type OrderPaymentLedgerWarning,
|
|
12
15
|
} from '../../data/validators'
|
|
13
16
|
import {
|
|
14
17
|
createPagedListResponseSchema,
|
|
@@ -37,6 +40,13 @@ type DocumentBinding = {
|
|
|
37
40
|
viewFeature: string
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
type DocumentCreateResult = {
|
|
44
|
+
orderId?: string
|
|
45
|
+
quoteId?: string
|
|
46
|
+
id?: string
|
|
47
|
+
warnings?: OrderPaymentLedgerWarning[]
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
const rawBodySchema = z.object({}).passthrough()
|
|
41
51
|
|
|
42
52
|
const normalizeJsonRecord = (value: unknown): Record<string, unknown> | null => {
|
|
@@ -472,7 +482,12 @@ export function buildDocumentCrudOptions(binding: DocumentBinding) {
|
|
|
472
482
|
)
|
|
473
483
|
return parsed
|
|
474
484
|
},
|
|
475
|
-
response: ({ result }: { result
|
|
485
|
+
response: ({ result }: { result?: DocumentCreateResult | null }) => ({
|
|
486
|
+
id: result?.orderId ?? result?.quoteId ?? result?.id ?? null,
|
|
487
|
+
...(binding.kind === 'order' && Array.isArray(result?.warnings)
|
|
488
|
+
? { warnings: result.warnings }
|
|
489
|
+
: {}),
|
|
490
|
+
}),
|
|
476
491
|
status: 201,
|
|
477
492
|
},
|
|
478
493
|
update: {
|
|
@@ -616,8 +631,18 @@ export function buildDocumentOpenApi(binding: DocumentBinding) {
|
|
|
616
631
|
listResponseSchema,
|
|
617
632
|
create: {
|
|
618
633
|
schema: createSchema,
|
|
619
|
-
responseSchema:
|
|
620
|
-
|
|
634
|
+
responseSchema: binding.kind === 'order'
|
|
635
|
+
? z.object({
|
|
636
|
+
id: z.string().uuid().nullable(),
|
|
637
|
+
warnings: z.array(z.object({
|
|
638
|
+
code: z.literal(ORDER_PAYMENT_LEDGER_WARNING_CODE),
|
|
639
|
+
fields: z.array(z.enum(ORDER_PAYMENT_LEDGER_FIELDS)),
|
|
640
|
+
})).optional(),
|
|
641
|
+
})
|
|
642
|
+
: z.object({ id: z.string().uuid().nullable() }),
|
|
643
|
+
description: binding.kind === 'order'
|
|
644
|
+
? 'Creates a new sales order. paidTotalAmount, refundedTotalAmount, and outstandingAmount are deprecated compatibility inputs: supplied values are ignored and reported in warnings. Record payments through sales.payments.create or POST /api/sales/payments.'
|
|
645
|
+
: 'Creates a new sales quote.',
|
|
621
646
|
},
|
|
622
647
|
del: {
|
|
623
648
|
schema: defaultDeleteRequestSchema,
|
|
@@ -76,6 +76,8 @@ import {
|
|
|
76
76
|
quoteLineCreateSchema,
|
|
77
77
|
quoteAdjustmentCreateSchema,
|
|
78
78
|
orderCreateSchema,
|
|
79
|
+
ORDER_PAYMENT_LEDGER_WARNING_CODE,
|
|
80
|
+
resolveSuppliedOrderPaymentLedgerFields,
|
|
79
81
|
orderLineCreateSchema,
|
|
80
82
|
orderAdjustmentCreateSchema,
|
|
81
83
|
invoiceCreateSchema,
|
|
@@ -87,6 +89,7 @@ import {
|
|
|
87
89
|
type QuoteLineCreateInput,
|
|
88
90
|
type QuoteAdjustmentCreateInput,
|
|
89
91
|
type OrderCreateInput,
|
|
92
|
+
type OrderPaymentLedgerWarning,
|
|
90
93
|
type OrderLineCreateInput,
|
|
91
94
|
type OrderAdjustmentCreateInput,
|
|
92
95
|
type InvoiceCreateInput,
|
|
@@ -143,6 +146,7 @@ import type { TranslateWithFallbackFn } from "@open-mercato/shared/lib/i18n/tran
|
|
|
143
146
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
144
147
|
|
|
145
148
|
const logger = createLogger('sales')
|
|
149
|
+
let warnedDeprecatedOrderPaymentLedgerInput = false
|
|
146
150
|
|
|
147
151
|
// CRUD events configuration for workflow triggers
|
|
148
152
|
const orderCrudEvents: CrudEventsConfig<SalesOrder> = {
|
|
@@ -5505,14 +5509,22 @@ const updateOrderCommand: CommandHandler<
|
|
|
5505
5509
|
|
|
5506
5510
|
const createOrderCommand: CommandHandler<
|
|
5507
5511
|
OrderCreateInput,
|
|
5508
|
-
{ orderId: string }
|
|
5512
|
+
{ orderId: string; warnings?: OrderPaymentLedgerWarning[] }
|
|
5509
5513
|
> = {
|
|
5510
5514
|
id: "sales.orders.create",
|
|
5511
5515
|
async execute(rawInput, ctx) {
|
|
5512
5516
|
const generator = ctx.container.resolve(
|
|
5513
5517
|
"salesDocumentNumberGenerator",
|
|
5514
5518
|
) as SalesDocumentNumberGenerator;
|
|
5519
|
+
const deprecatedPaymentLedgerFields = resolveSuppliedOrderPaymentLedgerFields(rawInput)
|
|
5515
5520
|
const initial = orderCreateSchema.parse(rawInput ?? {});
|
|
5521
|
+
if (deprecatedPaymentLedgerFields.length && !warnedDeprecatedOrderPaymentLedgerInput) {
|
|
5522
|
+
warnedDeprecatedOrderPaymentLedgerInput = true
|
|
5523
|
+
logger.warn("sales.orders.create ignored deprecated payment ledger input", {
|
|
5524
|
+
code: ORDER_PAYMENT_LEDGER_WARNING_CODE,
|
|
5525
|
+
fields: deprecatedPaymentLedgerFields,
|
|
5526
|
+
})
|
|
5527
|
+
}
|
|
5516
5528
|
const orderNumber =
|
|
5517
5529
|
typeof initial.orderNumber === "string" &&
|
|
5518
5530
|
initial.orderNumber.trim().length
|
|
@@ -5871,7 +5883,19 @@ const createOrderCommand: CommandHandler<
|
|
|
5871
5883
|
previousStatus: null,
|
|
5872
5884
|
});
|
|
5873
5885
|
|
|
5874
|
-
return {
|
|
5886
|
+
return {
|
|
5887
|
+
orderId: order.id,
|
|
5888
|
+
...(deprecatedPaymentLedgerFields.length
|
|
5889
|
+
? {
|
|
5890
|
+
warnings: [
|
|
5891
|
+
{
|
|
5892
|
+
code: ORDER_PAYMENT_LEDGER_WARNING_CODE,
|
|
5893
|
+
fields: deprecatedPaymentLedgerFields,
|
|
5894
|
+
},
|
|
5895
|
+
],
|
|
5896
|
+
}
|
|
5897
|
+
: {}),
|
|
5898
|
+
};
|
|
5875
5899
|
},
|
|
5876
5900
|
captureAfter: async (_input, result, ctx) => {
|
|
5877
5901
|
const em = (ctx.container.resolve("em") as EntityManager).fork();
|
|
@@ -1,59 +1,13 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
3
|
+
import { useFeatureFlagBoolean } from '@open-mercato/core/modules/feature_toggles/components/hooks/useFeatureFlagBoolean'
|
|
5
4
|
import { SALES_CHANNELS_TOGGLE_ID } from '../lib/salesChannelsToggleId'
|
|
6
5
|
|
|
7
6
|
export { SALES_CHANNELS_TOGGLE_ID }
|
|
8
7
|
|
|
9
|
-
const CACHE_TTL_MS = 60_000
|
|
10
|
-
|
|
11
|
-
type FeatureToggleCheckResponse = {
|
|
12
|
-
ok?: boolean
|
|
13
|
-
value?: unknown
|
|
14
|
-
}
|
|
15
|
-
|
|
16
8
|
// Fails open: channels stay visible unless the toggle explicitly resolves to
|
|
17
9
|
// false. A missing toggle definition (404) or an absent feature_toggles module
|
|
18
10
|
// must not hide sales channel UI.
|
|
19
|
-
async function fetchSalesChannelsEnabled(): Promise<boolean> {
|
|
20
|
-
try {
|
|
21
|
-
const call = await apiCall<FeatureToggleCheckResponse>(
|
|
22
|
-
`/api/feature_toggles/check/boolean?identifier=${SALES_CHANNELS_TOGGLE_ID}`,
|
|
23
|
-
)
|
|
24
|
-
if (!call.ok) return true
|
|
25
|
-
return !(call.result?.ok === true && call.result.value === false)
|
|
26
|
-
} catch {
|
|
27
|
-
return true
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
let cache: { value: Promise<boolean>; at: number } | null = null
|
|
32
|
-
|
|
33
|
-
function getSalesChannelsEnabled(): Promise<boolean> {
|
|
34
|
-
const now = Date.now()
|
|
35
|
-
if (!cache || now - cache.at > CACHE_TTL_MS) {
|
|
36
|
-
cache = { value: fetchSalesChannelsEnabled(), at: now }
|
|
37
|
-
}
|
|
38
|
-
return cache.value
|
|
39
|
-
}
|
|
40
|
-
|
|
41
11
|
export function useSalesChannelsEnabled(): { enabled: boolean; isLoading: boolean } {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
React.useEffect(() => {
|
|
45
|
-
let active = true
|
|
46
|
-
getSalesChannelsEnabled()
|
|
47
|
-
.then((value) => {
|
|
48
|
-
if (active) setResolved(value)
|
|
49
|
-
})
|
|
50
|
-
.catch(() => {
|
|
51
|
-
if (active) setResolved(true)
|
|
52
|
-
})
|
|
53
|
-
return () => {
|
|
54
|
-
active = false
|
|
55
|
-
}
|
|
56
|
-
}, [])
|
|
57
|
-
|
|
58
|
-
return { enabled: resolved !== false, isLoading: resolved === null }
|
|
12
|
+
return useFeatureFlagBoolean({ id: SALES_CHANNELS_TOGGLE_ID, defaultValue: true })
|
|
59
13
|
}
|
|
@@ -624,6 +624,38 @@ export const quoteAdjustmentUpdateSchema = z
|
|
|
624
624
|
})
|
|
625
625
|
.merge(quoteAdjustmentCreateSchema.partial())
|
|
626
626
|
|
|
627
|
+
export const ORDER_PAYMENT_LEDGER_FIELDS = [
|
|
628
|
+
'paidTotalAmount',
|
|
629
|
+
'refundedTotalAmount',
|
|
630
|
+
'outstandingAmount',
|
|
631
|
+
] as const
|
|
632
|
+
|
|
633
|
+
export type OrderPaymentLedgerField = (typeof ORDER_PAYMENT_LEDGER_FIELDS)[number]
|
|
634
|
+
|
|
635
|
+
export const ORDER_PAYMENT_LEDGER_WARNING_CODE =
|
|
636
|
+
'sales.order.payment_ledger_input_deprecated' as const
|
|
637
|
+
|
|
638
|
+
export type OrderPaymentLedgerWarning = {
|
|
639
|
+
code: typeof ORDER_PAYMENT_LEDGER_WARNING_CODE
|
|
640
|
+
fields: OrderPaymentLedgerField[]
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
export function resolveSuppliedOrderPaymentLedgerFields(value: unknown): OrderPaymentLedgerField[] {
|
|
644
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return []
|
|
645
|
+
return ORDER_PAYMENT_LEDGER_FIELDS.filter((field) =>
|
|
646
|
+
Object.prototype.hasOwnProperty.call(value, field),
|
|
647
|
+
)
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const orderPaymentLedgerShape = {
|
|
651
|
+
/** @deprecated Derived from recorded payments. Use sales.payments.create or POST /api/sales/payments. */
|
|
652
|
+
paidTotalAmount: decimal({ min: 0 }).optional(),
|
|
653
|
+
/** @deprecated Derived from recorded payments. Use sales.payments.create or POST /api/sales/payments. */
|
|
654
|
+
refundedTotalAmount: decimal({ min: 0 }).optional(),
|
|
655
|
+
/** @deprecated Derived from recorded payments. Use sales.payments.create or POST /api/sales/payments. */
|
|
656
|
+
outstandingAmount: decimal().optional(),
|
|
657
|
+
}
|
|
658
|
+
|
|
627
659
|
const orderTotalsSchema = z.object({
|
|
628
660
|
subtotalNetAmount: decimal({ min: 0 }).optional(),
|
|
629
661
|
subtotalGrossAmount: decimal({ min: 0 }).optional(),
|
|
@@ -634,9 +666,6 @@ const orderTotalsSchema = z.object({
|
|
|
634
666
|
surchargeTotalAmount: decimal({ min: 0 }).optional(),
|
|
635
667
|
grandTotalNetAmount: decimal({ min: 0 }).optional(),
|
|
636
668
|
grandTotalGrossAmount: decimal({ min: 0 }).optional(),
|
|
637
|
-
paidTotalAmount: decimal({ min: 0 }).optional(),
|
|
638
|
-
refundedTotalAmount: decimal({ min: 0 }).optional(),
|
|
639
|
-
outstandingAmount: decimal().optional(),
|
|
640
669
|
lineItemCount: z.coerce.number().int().min(0).optional(),
|
|
641
670
|
})
|
|
642
671
|
|
|
@@ -690,6 +719,7 @@ export const orderCreateSchema = scoped.extend({
|
|
|
690
719
|
adjustments: z.array(orderAdjustmentCreateSchema.omit({ organizationId: true, tenantId: true, orderId: true })).optional(),
|
|
691
720
|
tags: z.array(uuid()).optional(),
|
|
692
721
|
...orderTotalsSchema.shape,
|
|
722
|
+
...orderPaymentLedgerShape,
|
|
693
723
|
})
|
|
694
724
|
|
|
695
725
|
export const orderUpdateSchema = z
|
|
@@ -1046,8 +1076,23 @@ export type PaymentMethodCreateInput = z.infer<typeof paymentMethodCreateSchema>
|
|
|
1046
1076
|
export type PaymentMethodUpdateInput = z.infer<typeof paymentMethodUpdateSchema>
|
|
1047
1077
|
export type TaxRateCreateInput = z.infer<typeof taxRateCreateSchema>
|
|
1048
1078
|
export type TaxRateUpdateInput = z.infer<typeof taxRateUpdateSchema>
|
|
1049
|
-
export type
|
|
1050
|
-
|
|
1079
|
+
export type DeprecatedOrderPaymentLedgerInput = {
|
|
1080
|
+
/** @deprecated Derived from recorded payments. Use sales.payments.create or POST /api/sales/payments. */
|
|
1081
|
+
paidTotalAmount?: number
|
|
1082
|
+
/** @deprecated Derived from recorded payments. Use sales.payments.create or POST /api/sales/payments. */
|
|
1083
|
+
refundedTotalAmount?: number
|
|
1084
|
+
/** @deprecated Derived from recorded payments. Use sales.payments.create or POST /api/sales/payments. */
|
|
1085
|
+
outstandingAmount?: number
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
export type OrderCreateInput = Omit<
|
|
1089
|
+
z.infer<typeof orderCreateSchema>,
|
|
1090
|
+
OrderPaymentLedgerField
|
|
1091
|
+
> & DeprecatedOrderPaymentLedgerInput
|
|
1092
|
+
export type OrderUpdateInput = Omit<
|
|
1093
|
+
z.infer<typeof orderUpdateSchema>,
|
|
1094
|
+
OrderPaymentLedgerField
|
|
1095
|
+
> & DeprecatedOrderPaymentLedgerInput
|
|
1051
1096
|
export type OrderLineCreateInput = z.infer<typeof orderLineCreateSchema>
|
|
1052
1097
|
export type OrderLineUpdateInput = z.infer<typeof orderLineUpdateSchema>
|
|
1053
1098
|
export type OrderAdjustmentCreateInput = z.infer<typeof orderAdjustmentCreateSchema>
|