@open-mercato/core 0.8.1-develop.7263.1.bbc6db3437 → 0.8.1-develop.7267.1.2e95af80a6
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 +2 -2
- package/dist/generated/entities.ids.generated.js +0 -4
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +0 -21
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -0
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +13 -0
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/email-mime.js +19 -1
- package/dist/modules/communication_channels/lib/email-mime.js.map +2 -2
- package/dist/modules/customers/lib/link-channel-message-handler.js +17 -7
- package/dist/modules/customers/lib/link-channel-message-handler.js.map +3 -3
- package/dist/modules/messages/api/route.js +1 -0
- package/dist/modules/messages/api/route.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +3 -1
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/data/validators.js +10 -2
- package/dist/modules/messages/data/validators.js.map +2 -2
- package/dist/modules/wms/di.js +0 -3
- package/dist/modules/wms/di.js.map +2 -2
- package/generated/entities.ids.generated.ts +0 -4
- package/generated/entity-fields-registry.ts +0 -21
- package/package.json +7 -7
- package/src/modules/auth/i18n/de.json +0 -3
- package/src/modules/auth/i18n/en.json +0 -3
- package/src/modules/auth/i18n/es.json +0 -3
- package/src/modules/auth/i18n/ko.json +0 -3
- package/src/modules/auth/i18n/pl.json +0 -3
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +8 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +16 -0
- package/src/modules/communication_channels/lib/email-mime.ts +52 -2
- package/src/modules/customers/lib/link-channel-message-handler.ts +48 -5
- package/src/modules/messages/api/route.ts +6 -3
- package/src/modules/messages/commands/messages.ts +3 -1
- package/src/modules/messages/data/validators.ts +21 -12
- package/src/modules/wms/di.ts +0 -6
- package/dist/generated/entities/availability_policy/index.js +0 -41
- package/dist/generated/entities/availability_policy/index.js.map +0 -7
- package/dist/modules/availability/acl.js +0 -16
- package/dist/modules/availability/acl.js.map +0 -7
- package/dist/modules/availability/api/check/route.js +0 -95
- package/dist/modules/availability/api/check/route.js.map +0 -7
- package/dist/modules/availability/api/openapi.js +0 -27
- package/dist/modules/availability/api/openapi.js.map +0 -7
- package/dist/modules/availability/api/policies/resolve-preview/route.js +0 -58
- package/dist/modules/availability/api/policies/resolve-preview/route.js.map +0 -7
- package/dist/modules/availability/api/policies/route.js +0 -188
- package/dist/modules/availability/api/policies/route.js.map +0 -7
- package/dist/modules/availability/backend/availability/check/page.js +0 -185
- package/dist/modules/availability/backend/availability/check/page.js.map +0 -7
- package/dist/modules/availability/backend/availability/check/page.meta.js +0 -16
- package/dist/modules/availability/backend/availability/check/page.meta.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js +0 -79
- package/dist/modules/availability/backend/availability/policies/ResolutionPreviewPanel.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/ScopeFields.js +0 -53
- package/dist/modules/availability/backend/availability/policies/ScopeFields.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/[id]/page.js +0 -152
- package/dist/modules/availability/backend/availability/policies/[id]/page.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js +0 -12
- package/dist/modules/availability/backend/availability/policies/[id]/page.meta.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/create/page.js +0 -94
- package/dist/modules/availability/backend/availability/policies/create/page.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/create/page.meta.js +0 -12
- package/dist/modules/availability/backend/availability/policies/create/page.meta.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/formGroups.js +0 -75
- package/dist/modules/availability/backend/availability/policies/formGroups.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/page.js +0 -176
- package/dist/modules/availability/backend/availability/policies/page.js.map +0 -7
- package/dist/modules/availability/backend/availability/policies/page.meta.js +0 -16
- package/dist/modules/availability/backend/availability/policies/page.meta.js.map +0 -7
- package/dist/modules/availability/commands/policies.js +0 -312
- package/dist/modules/availability/commands/policies.js.map +0 -7
- package/dist/modules/availability/commands/scope.js +0 -19
- package/dist/modules/availability/commands/scope.js.map +0 -7
- package/dist/modules/availability/data/entities.js +0 -95
- package/dist/modules/availability/data/entities.js.map +0 -7
- package/dist/modules/availability/data/validators.js +0 -53
- package/dist/modules/availability/data/validators.js.map +0 -7
- package/dist/modules/availability/di.js +0 -41
- package/dist/modules/availability/di.js.map +0 -7
- package/dist/modules/availability/events.js +0 -18
- package/dist/modules/availability/events.js.map +0 -7
- package/dist/modules/availability/index.js +0 -15
- package/dist/modules/availability/index.js.map +0 -7
- package/dist/modules/availability/lib/policyResolution.js +0 -155
- package/dist/modules/availability/lib/policyResolution.js.map +0 -7
- package/dist/modules/availability/lib/tryResolve.js +0 -11
- package/dist/modules/availability/lib/tryResolve.js.map +0 -7
- package/dist/modules/availability/migrations/Migration20260922095844_availability.js +0 -16
- package/dist/modules/availability/migrations/Migration20260922095844_availability.js.map +0 -7
- package/dist/modules/availability/setup.js +0 -12
- package/dist/modules/availability/setup.js.map +0 -7
- package/dist/modules/wms/lib/availabilityCache.js +0 -85
- package/dist/modules/wms/lib/availabilityCache.js.map +0 -7
- package/dist/modules/wms/lib/availabilityCalculation.js +0 -178
- package/dist/modules/wms/lib/availabilityCalculation.js.map +0 -7
- package/dist/modules/wms/lib/availabilityProvider.js +0 -14
- package/dist/modules/wms/lib/availabilityProvider.js.map +0 -7
- package/dist/modules/wms/lib/sqlInClause.js +0 -9
- package/dist/modules/wms/lib/sqlInClause.js.map +0 -7
- package/dist/modules/wms/lib/tryResolve.js +0 -11
- package/dist/modules/wms/lib/tryResolve.js.map +0 -7
- package/dist/modules/wms/subscribers/invalidate-availability-cache.js +0 -16
- package/dist/modules/wms/subscribers/invalidate-availability-cache.js.map +0 -7
- package/generated/entities/availability_policy/index.ts +0 -19
- package/src/modules/availability/acl.ts +0 -12
- package/src/modules/availability/api/check/route.ts +0 -114
- package/src/modules/availability/api/openapi.ts +0 -28
- package/src/modules/availability/api/policies/resolve-preview/route.ts +0 -67
- package/src/modules/availability/api/policies/route.ts +0 -224
- package/src/modules/availability/backend/availability/check/page.meta.ts +0 -12
- package/src/modules/availability/backend/availability/check/page.tsx +0 -223
- package/src/modules/availability/backend/availability/policies/ResolutionPreviewPanel.tsx +0 -106
- package/src/modules/availability/backend/availability/policies/ScopeFields.tsx +0 -55
- package/src/modules/availability/backend/availability/policies/[id]/page.meta.ts +0 -8
- package/src/modules/availability/backend/availability/policies/[id]/page.tsx +0 -182
- package/src/modules/availability/backend/availability/policies/create/page.meta.ts +0 -8
- package/src/modules/availability/backend/availability/policies/create/page.tsx +0 -105
- package/src/modules/availability/backend/availability/policies/formGroups.ts +0 -77
- package/src/modules/availability/backend/availability/policies/page.meta.ts +0 -12
- package/src/modules/availability/backend/availability/policies/page.tsx +0 -196
- package/src/modules/availability/commands/policies.ts +0 -358
- package/src/modules/availability/commands/scope.ts +0 -26
- package/src/modules/availability/data/entities.ts +0 -92
- package/src/modules/availability/data/validators.ts +0 -70
- package/src/modules/availability/di.ts +0 -54
- package/src/modules/availability/events.ts +0 -17
- package/src/modules/availability/i18n/de.json +0 -81
- package/src/modules/availability/i18n/en.json +0 -81
- package/src/modules/availability/i18n/es.json +0 -81
- package/src/modules/availability/i18n/ko.json +0 -81
- package/src/modules/availability/i18n/pl.json +0 -81
- package/src/modules/availability/index.ts +0 -13
- package/src/modules/availability/lib/policyResolution.ts +0 -264
- package/src/modules/availability/lib/tryResolve.ts +0 -8
- package/src/modules/availability/migrations/.snapshot-open-mercato.json +0 -375
- package/src/modules/availability/migrations/Migration20260922095844_availability.ts +0 -13
- package/src/modules/availability/setup.ts +0 -10
- package/src/modules/wms/lib/availabilityCache.ts +0 -136
- package/src/modules/wms/lib/availabilityCalculation.ts +0 -289
- package/src/modules/wms/lib/availabilityProvider.ts +0 -29
- package/src/modules/wms/lib/sqlInClause.ts +0 -21
- package/src/modules/wms/lib/tryResolve.ts +0 -8
- package/src/modules/wms/subscribers/invalidate-availability-cache.ts +0 -30
|
@@ -62,9 +62,6 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
-
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
-
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
-
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
68
65
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
69
66
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
70
67
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,9 +62,6 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
-
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
-
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
-
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
68
65
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
69
66
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
70
67
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,9 +62,6 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
-
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
-
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
-
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
68
65
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
69
66
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
70
67
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,9 +62,6 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Delete users",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edit users",
|
|
64
64
|
"auth.acl.features.auth.users.list": "List users",
|
|
65
|
-
"auth.acl.features.availability.check": "Run availability checks",
|
|
66
|
-
"auth.acl.features.availability.policies.manage": "Manage availability policies",
|
|
67
|
-
"auth.acl.features.availability.policies.view": "View availability policies",
|
|
68
65
|
"auth.acl.features.business_rules.execute": "Execute business rules manually",
|
|
69
66
|
"auth.acl.features.business_rules.manage": "Create, edit, and delete business rules",
|
|
70
67
|
"auth.acl.features.business_rules.manage_sets": "Manage rule sets",
|
|
@@ -62,9 +62,6 @@
|
|
|
62
62
|
"auth.acl.features.auth.users.delete": "Usuwaj użytkowników",
|
|
63
63
|
"auth.acl.features.auth.users.edit": "Edytuj użytkowników",
|
|
64
64
|
"auth.acl.features.auth.users.list": "Wyświetl listę użytkowników",
|
|
65
|
-
"auth.acl.features.availability.check": "Uruchamiaj sprawdzenia dostępności",
|
|
66
|
-
"auth.acl.features.availability.policies.manage": "Zarządzaj politykami dostępności",
|
|
67
|
-
"auth.acl.features.availability.policies.view": "Wyświetl polityki dostępności",
|
|
68
65
|
"auth.acl.features.business_rules.execute": "Uruchamiaj reguły biznesowe ręcznie",
|
|
69
66
|
"auth.acl.features.business_rules.manage": "Twórz, edytuj i usuwaj reguły biznesowe",
|
|
70
67
|
"auth.acl.features.business_rules.manage_sets": "Zarządzaj zestawami reguł",
|
|
@@ -530,6 +530,14 @@ const deliverOutboundMessageCommand: CommandHandler<
|
|
|
530
530
|
providerKey: channel.providerKey,
|
|
531
531
|
channelType: channel.channelType,
|
|
532
532
|
direction: 'outbound',
|
|
533
|
+
// #6095: same contract as the inbound path — the send time travels on
|
|
534
|
+
// the event so consumers (the customers timeline) date their rows
|
|
535
|
+
// from it instead of reading the ExternalMessage row across the
|
|
536
|
+
// module boundary. ISO string because this event is persistent.
|
|
537
|
+
providerTimestamp:
|
|
538
|
+
externalMessage.providerTimestamp instanceof Date
|
|
539
|
+
? externalMessage.providerTimestamp.toISOString()
|
|
540
|
+
: null,
|
|
533
541
|
tenantId: input.scope.tenantId,
|
|
534
542
|
organizationId: input.scope.organizationId ?? null,
|
|
535
543
|
},
|
|
@@ -417,6 +417,12 @@ const ingestInboundMessageCommand: CommandHandler<IngestInboundMessageInput, Ing
|
|
|
417
417
|
// when the matcher returned null (no token / JWZ / subject hit).
|
|
418
418
|
parentMessageId: threadMatch?.messageThreadId ?? mapping?.messageThreadId,
|
|
419
419
|
isDraft: false,
|
|
420
|
+
// #6095: the platform message is dated when the provider received the
|
|
421
|
+
// mail, not when this worker ran. Otherwise a history import lands every
|
|
422
|
+
// message on the import minute and the inbox (sorted on `sent_at`) shows
|
|
423
|
+
// a 90-day mailbox as one block in import order. Adapters without a
|
|
424
|
+
// timestamp fall through to compose's own `new Date()`.
|
|
425
|
+
sentAt: m.timestamp ?? undefined,
|
|
420
426
|
// Stable dedup key so a retried ingest (after a transient failure between
|
|
421
427
|
// compose and the ExternalMessage anchor insert) reuses the message
|
|
422
428
|
// composed by the first attempt instead of duplicating it. Mirrors the
|
|
@@ -576,6 +582,16 @@ const ingestInboundMessageCommand: CommandHandler<IngestInboundMessageInput, Ing
|
|
|
576
582
|
providerKey: input.providerKey,
|
|
577
583
|
channelType: input.channelType,
|
|
578
584
|
direction: 'inbound',
|
|
585
|
+
// #6095: subscribers that date their own rows from this message (the
|
|
586
|
+
// customers timeline) need the provider's receive time, not the moment
|
|
587
|
+
// this worker ran. Carried here so no consumer has to read the
|
|
588
|
+
// ExternalMessage row across the module boundary. ISO string because a
|
|
589
|
+
// persistent event is serialized onto the queue; null when the adapter
|
|
590
|
+
// supplied no timestamp, which leaves the consumer's own fallback.
|
|
591
|
+
providerTimestamp:
|
|
592
|
+
externalMessage.providerTimestamp instanceof Date
|
|
593
|
+
? externalMessage.providerTimestamp.toISOString()
|
|
594
|
+
: null,
|
|
579
595
|
tenantId: input.scope.tenantId,
|
|
580
596
|
organizationId: input.scope.organizationId ?? null,
|
|
581
597
|
},
|
|
@@ -414,7 +414,19 @@ export interface NormalizeMimeInboundOptions {
|
|
|
414
414
|
fallbackMessageId: string
|
|
415
415
|
/** Compute the conversation grouping id from the resolved message id + references. */
|
|
416
416
|
resolveConversationId: (context: { messageId: string; references: string[] }) => string
|
|
417
|
-
/**
|
|
417
|
+
/**
|
|
418
|
+
* When the provider received the message (Gmail `internalDate`, IMAP
|
|
419
|
+
* `INTERNALDATE`). Preferred over the MIME `Date` header for the message
|
|
420
|
+
* timestamp: the header is written by the sender's client and can be forged
|
|
421
|
+
* or skewed, and since #6095 the timestamp also dates the platform message
|
|
422
|
+
* and the CRM interaction, so a bad header would pin a message in the future
|
|
423
|
+
* or hide it in the past.
|
|
424
|
+
*/
|
|
425
|
+
receivedAt?: Date
|
|
426
|
+
/**
|
|
427
|
+
* Fallback timestamp when neither `receivedAt` nor a usable Date header is
|
|
428
|
+
* available.
|
|
429
|
+
*/
|
|
418
430
|
fallbackDate?: Date
|
|
419
431
|
/** Provider-specific fields merged into `channelMetadata`. */
|
|
420
432
|
channelMetadata?: (parsed: ParsedMail) => Record<string, unknown>
|
|
@@ -422,6 +434,40 @@ export interface NormalizeMimeInboundOptions {
|
|
|
422
434
|
channelPayload?: (parsed: ParsedMail) => Record<string, unknown>
|
|
423
435
|
}
|
|
424
436
|
|
|
437
|
+
/**
|
|
438
|
+
* A MIME `Date` header this far ahead of the clock is treated as bogus. Clock
|
|
439
|
+
* skew between real mail servers is seconds to minutes; a day of tolerance
|
|
440
|
+
* keeps legitimate mail while stopping a forged header from pinning a message
|
|
441
|
+
* to the top of a `sent_at`-sorted inbox for weeks.
|
|
442
|
+
*/
|
|
443
|
+
const MAX_MIME_DATE_FUTURE_SKEW_MS = 24 * 60 * 60 * 1000
|
|
444
|
+
|
|
445
|
+
function isValidDate(value: unknown): value is Date {
|
|
446
|
+
return value instanceof Date && !Number.isNaN(value.getTime())
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Pick the timestamp for an inbound message.
|
|
451
|
+
*
|
|
452
|
+
* Order: the provider's receipt time; then the sender's MIME `Date` header,
|
|
453
|
+
* unless it is unparsable or more than a day in the future; then the caller's
|
|
454
|
+
* fallback; then now. Exported for tests.
|
|
455
|
+
*/
|
|
456
|
+
export function resolveInboundTimestamp(input: {
|
|
457
|
+
receivedAt?: Date
|
|
458
|
+
mimeDate?: Date
|
|
459
|
+
fallbackDate?: Date
|
|
460
|
+
now?: Date
|
|
461
|
+
}): Date {
|
|
462
|
+
if (isValidDate(input.receivedAt)) return input.receivedAt
|
|
463
|
+
const now = input.now ?? new Date()
|
|
464
|
+
if (isValidDate(input.mimeDate) && input.mimeDate.getTime() <= now.getTime() + MAX_MIME_DATE_FUTURE_SKEW_MS) {
|
|
465
|
+
return input.mimeDate
|
|
466
|
+
}
|
|
467
|
+
if (isValidDate(input.fallbackDate)) return input.fallbackDate
|
|
468
|
+
return now
|
|
469
|
+
}
|
|
470
|
+
|
|
425
471
|
/**
|
|
426
472
|
* Build the hub's canonical `NormalizedInboundMessage` from a parsed MIME
|
|
427
473
|
* message. Providers supply the bits that genuinely differ (message-id
|
|
@@ -474,7 +520,11 @@ export function normalizeMimeInbound(options: NormalizeMimeInboundOptions): Norm
|
|
|
474
520
|
body,
|
|
475
521
|
bodyFormat,
|
|
476
522
|
attachments,
|
|
477
|
-
timestamp:
|
|
523
|
+
timestamp: resolveInboundTimestamp({
|
|
524
|
+
receivedAt: options.receivedAt,
|
|
525
|
+
mimeDate: parsed.date ? new Date(parsed.date) : undefined,
|
|
526
|
+
fallbackDate: options.fallbackDate,
|
|
527
|
+
}),
|
|
478
528
|
replyToExternalId: inReplyTo ?? undefined,
|
|
479
529
|
channelPayload,
|
|
480
530
|
channelContentType: 'email/mime',
|
|
@@ -34,6 +34,13 @@ type LinkChannelMessagePayload = {
|
|
|
34
34
|
organizationId?: string | null
|
|
35
35
|
providerKey?: string | null
|
|
36
36
|
direction?: 'inbound' | 'outbound' | null
|
|
37
|
+
/**
|
|
38
|
+
* The provider's own receive/send time, carried by the hub on the event
|
|
39
|
+
* (#6095). An ISO string when the event rode the queue, a Date for an
|
|
40
|
+
* in-process emit. Absent on events enqueued before the field existed and on
|
|
41
|
+
* adapters that supply no timestamp.
|
|
42
|
+
*/
|
|
43
|
+
providerTimestamp?: string | Date | null
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
type SubscriberContext = {
|
|
@@ -88,6 +95,17 @@ export default async function handler(
|
|
|
88
95
|
const metaJson = (link.channelMetadata ?? null) as Record<string, unknown> | null
|
|
89
96
|
const payloadJson = (link.channelPayload ?? null) as Record<string, unknown> | null
|
|
90
97
|
|
|
98
|
+
// ── (1b) Resolve when the email actually happened ─────────────────────
|
|
99
|
+
//
|
|
100
|
+
// `link.createdAt` is when the hub ingested the message, which for a history
|
|
101
|
+
// import is the import minute, not the day the mail arrived (#6095). The hub
|
|
102
|
+
// carries the provider's own timestamp on the event payload, so take it from
|
|
103
|
+
// there: reading the communication_channels ExternalMessage row here would
|
|
104
|
+
// cross the storage boundary (Cross-Module Coupling in AGENTS.md) and add a
|
|
105
|
+
// peer-table query per event. Resolved once and threaded through every branch
|
|
106
|
+
// below so the address-match and threading-inheritance paths agree.
|
|
107
|
+
const occurredAt = resolveOccurredAt(payload, link)
|
|
108
|
+
|
|
91
109
|
// ── (2) Resolve the channel to get its owner userId ───────────────────
|
|
92
110
|
//
|
|
93
111
|
// The channel.userId is needed for two purposes:
|
|
@@ -172,7 +190,7 @@ export default async function handler(
|
|
|
172
190
|
// Early exit: no addresses AND no hint → nothing to link.
|
|
173
191
|
if (normalized.length === 0 && !crmPersonId) {
|
|
174
192
|
// Before giving up, try threading-inheritance (TC-CRM-EMAIL-005).
|
|
175
|
-
await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
|
|
193
|
+
await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson, occurredAt)
|
|
176
194
|
return
|
|
177
195
|
}
|
|
178
196
|
|
|
@@ -183,7 +201,7 @@ export default async function handler(
|
|
|
183
201
|
|
|
184
202
|
if (personIdSet.size === 0) {
|
|
185
203
|
// Try threading-inheritance before giving up.
|
|
186
|
-
await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson)
|
|
204
|
+
await handleThreadingInheritance(em, link, linkId, tenantId, organizationId, channelUserId, metaJson, payloadJson, occurredAt)
|
|
187
205
|
return
|
|
188
206
|
}
|
|
189
207
|
|
|
@@ -211,7 +229,6 @@ export default async function handler(
|
|
|
211
229
|
? (payloadJson!.text as string)
|
|
212
230
|
: null
|
|
213
231
|
|
|
214
|
-
const occurredAt = link.createdAt instanceof Date ? link.createdAt : new Date()
|
|
215
232
|
const providerKey =
|
|
216
233
|
typeof link.providerKey === 'string' ? (link.providerKey as string) : null
|
|
217
234
|
|
|
@@ -257,6 +274,7 @@ async function handleThreadingInheritance(
|
|
|
257
274
|
channelUserId: string | null,
|
|
258
275
|
metaJson: Record<string, unknown> | null,
|
|
259
276
|
payloadJson: Record<string, unknown> | null,
|
|
277
|
+
occurredAt: Date,
|
|
260
278
|
): Promise<void> {
|
|
261
279
|
// ── Primary: inherit Person(s) from the hub's authoritative thread ──────
|
|
262
280
|
//
|
|
@@ -325,7 +343,6 @@ async function handleThreadingInheritance(
|
|
|
325
343
|
: typeof metaJson?.bodyText === 'string'
|
|
326
344
|
? (metaJson.bodyText as string)
|
|
327
345
|
: null
|
|
328
|
-
const occurredAt = _link.createdAt instanceof Date ? (_link.createdAt as Date) : new Date()
|
|
329
346
|
const providerKey = typeof _link.providerKey === 'string' ? (_link.providerKey as string) : null
|
|
330
347
|
await persistInteractions(em, threadPersonIds, {
|
|
331
348
|
linkId,
|
|
@@ -420,7 +437,6 @@ async function handleThreadingInheritance(
|
|
|
420
437
|
const inheritedMeta = (link.channelMetadata ?? null) as Record<string, unknown> | null
|
|
421
438
|
const subject =
|
|
422
439
|
typeof inheritedMeta?.subject === 'string' ? (inheritedMeta.subject as string) : null
|
|
423
|
-
const occurredAt = link.createdAt instanceof Date ? (link.createdAt as Date) : new Date()
|
|
424
440
|
const providerKey =
|
|
425
441
|
typeof link.providerKey === 'string' ? (link.providerKey as string) : null
|
|
426
442
|
|
|
@@ -444,6 +460,33 @@ async function handleThreadingInheritance(
|
|
|
444
460
|
|
|
445
461
|
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
446
462
|
|
|
463
|
+
/**
|
|
464
|
+
* When the linked email happened, for `CustomerInteraction.occurredAt` (#6095).
|
|
465
|
+
*
|
|
466
|
+
* Preference order:
|
|
467
|
+
* 1. `payload.providerTimestamp` — the provider's own receive/send time, put
|
|
468
|
+
* on the hub event by ingest / delivery;
|
|
469
|
+
* 2. `MessageChannelLink.createdAt` — the ingest time, the only date this
|
|
470
|
+
* handler knew before #6095, and the compatibility path for events that
|
|
471
|
+
* were enqueued before the field existed;
|
|
472
|
+
* 3. now, for a link row with no usable `createdAt` (test stubs).
|
|
473
|
+
*/
|
|
474
|
+
function resolveOccurredAt(
|
|
475
|
+
payload: LinkChannelMessagePayload,
|
|
476
|
+
link: Record<string, unknown>,
|
|
477
|
+
): Date {
|
|
478
|
+
const ingestedAt = link.createdAt instanceof Date ? link.createdAt : new Date()
|
|
479
|
+
const carried = payload.providerTimestamp
|
|
480
|
+
if (carried instanceof Date) {
|
|
481
|
+
return Number.isNaN(carried.getTime()) ? ingestedAt : carried
|
|
482
|
+
}
|
|
483
|
+
if (typeof carried === 'string' && carried) {
|
|
484
|
+
const parsed = new Date(carried)
|
|
485
|
+
if (!Number.isNaN(parsed.getTime())) return parsed
|
|
486
|
+
}
|
|
487
|
+
return ingestedAt
|
|
488
|
+
}
|
|
489
|
+
|
|
447
490
|
interface InteractionData {
|
|
448
491
|
linkId: string
|
|
449
492
|
tenantId: string
|
|
@@ -470,12 +470,15 @@ export async function POST(req: Request) {
|
|
|
470
470
|
sourceHint.data,
|
|
471
471
|
)
|
|
472
472
|
: undefined
|
|
473
|
-
// `inboundFromChannel`
|
|
474
|
-
//
|
|
475
|
-
//
|
|
473
|
+
// `inboundFromChannel` and `sentAt` are likewise server-only (#6093, #6095):
|
|
474
|
+
// channel ingest sets them so an inbound message can be addressed to the
|
|
475
|
+
// conversation's assignee and stamped with the provider's receive time, but
|
|
476
|
+
// a request body must not be able to waive the recipients rule or backdate
|
|
477
|
+
// a message with them.
|
|
476
478
|
const {
|
|
477
479
|
sourceChannelType: _clientSuppliedChannelType,
|
|
478
480
|
inboundFromChannel: _clientSuppliedInboundFlag,
|
|
481
|
+
sentAt: _clientSuppliedSentAt,
|
|
479
482
|
...clientBody
|
|
480
483
|
} = (body ?? {}) as Record<string, unknown>
|
|
481
484
|
const input = composeMessageSchema.parse({
|
|
@@ -333,7 +333,9 @@ const composeMessageCommand: CommandHandler<unknown, { id: string; threadId: str
|
|
|
333
333
|
priority: input.priority,
|
|
334
334
|
status: input.isDraft ? 'draft' : 'sent',
|
|
335
335
|
isDraft: input.isDraft ?? false,
|
|
336
|
-
|
|
336
|
+
// #6095: channel ingest passes the provider's timestamp so an imported
|
|
337
|
+
// mailbox sorts by when the mail was received, not when it was imported.
|
|
338
|
+
sentAt: input.isDraft ? null : input.sentAt ?? new Date(),
|
|
337
339
|
actionData: input.actionData as MessageActionData | undefined,
|
|
338
340
|
sendViaEmail,
|
|
339
341
|
idempotencyKey: input.idempotencyKey ?? null,
|
|
@@ -138,12 +138,20 @@ const composeMessageBaseSchema = z.object({
|
|
|
138
138
|
sendViaEmail: z.boolean().optional().default(false),
|
|
139
139
|
parentMessageId: z.string().uuid().optional(),
|
|
140
140
|
isDraft: z.boolean().optional().default(false),
|
|
141
|
+
/**
|
|
142
|
+
* When the message was actually sent, if the caller knows better than "now"
|
|
143
|
+
* (#6095). Channel ingest sets it to the provider's timestamp so an imported
|
|
144
|
+
* mailbox keeps its real chronology instead of collapsing onto the import
|
|
145
|
+
* minute. Server-only: the HTTP route strips any client-sent value, so a
|
|
146
|
+
* browser caller cannot backdate a message. Ignored for drafts.
|
|
147
|
+
*/
|
|
148
|
+
sentAt: z.coerce.date().optional(),
|
|
141
149
|
})
|
|
142
150
|
|
|
143
151
|
type ComposeMessageRefinementValue = Omit<
|
|
144
152
|
z.infer<typeof composeMessageBaseSchema>,
|
|
145
|
-
'sourceChannelType'
|
|
146
|
-
> & { sourceChannelType?: string }
|
|
153
|
+
'sourceChannelType' | 'sentAt'
|
|
154
|
+
> & { sourceChannelType?: string; sentAt?: Date }
|
|
147
155
|
|
|
148
156
|
function refineComposeMessage(value: ComposeMessageRefinementValue, ctx: z.RefinementCtx): void {
|
|
149
157
|
const isDraft = value.isDraft ?? false
|
|
@@ -213,23 +221,24 @@ function refineComposeMessage(value: ComposeMessageRefinementValue, ctx: z.Refin
|
|
|
213
221
|
}
|
|
214
222
|
|
|
215
223
|
/**
|
|
216
|
-
* Full compose contract, including the server-resolved `sourceChannelType
|
|
217
|
-
* the ingest-only `inboundFromChannel
|
|
218
|
-
* command and by the HTTP route AFTER it
|
|
224
|
+
* Full compose contract, including the server-resolved `sourceChannelType`,
|
|
225
|
+
* the ingest-only `inboundFromChannel`, and the server-supplied `sentAt`. Used
|
|
226
|
+
* by the `messages.messages.compose` command and by the HTTP route AFTER it
|
|
227
|
+
* has resolved the channel type itself.
|
|
219
228
|
*/
|
|
220
229
|
export const composeMessageSchema = composeMessageBaseSchema.superRefine(refineComposeMessage)
|
|
221
230
|
|
|
222
231
|
/**
|
|
223
232
|
* Client-facing compose contract — the same rules minus the server-only fields:
|
|
224
|
-
* `sourceChannelType` is never accepted from a request body (#4975)
|
|
225
|
-
*
|
|
226
|
-
* request shape matches what `POST /api/messages` actually reads:
|
|
227
|
-
* discards any client-sent value for these, resolves the real
|
|
228
|
-
* the referenced conversation or parent message, and only
|
|
229
|
-
* against {@link composeMessageSchema}.
|
|
233
|
+
* `sourceChannelType` is never accepted from a request body (#4975), neither is
|
|
234
|
+
* `inboundFromChannel` (#6093) nor `sentAt` (#6095). Published in OpenAPI so the
|
|
235
|
+
* documented request shape matches what `POST /api/messages` actually reads:
|
|
236
|
+
* the route discards any client-sent value for these, resolves the real
|
|
237
|
+
* channel type from the referenced conversation or parent message, and only
|
|
238
|
+
* then validates against {@link composeMessageSchema}.
|
|
230
239
|
*/
|
|
231
240
|
export const composeMessageRequestSchema = composeMessageBaseSchema
|
|
232
|
-
.omit({ sourceChannelType: true, inboundFromChannel: true })
|
|
241
|
+
.omit({ sourceChannelType: true, inboundFromChannel: true, sentAt: true })
|
|
233
242
|
.superRefine(refineComposeMessage)
|
|
234
243
|
|
|
235
244
|
export const updateDraftSchema = z.object({
|
package/src/modules/wms/di.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { asValue } from 'awilix'
|
|
2
2
|
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
-
import { availabilityProviderRegistry } from '@open-mercato/shared/lib/availability'
|
|
4
3
|
import {
|
|
5
4
|
InventoryBalance,
|
|
6
5
|
InventoryLot,
|
|
@@ -11,7 +10,6 @@ import {
|
|
|
11
10
|
WarehouseLocation,
|
|
12
11
|
WarehouseZone,
|
|
13
12
|
} from './data/entities'
|
|
14
|
-
import { createWmsAvailabilityProvider } from './lib/availabilityProvider'
|
|
15
13
|
|
|
16
14
|
export function register(container: AppContainer) {
|
|
17
15
|
container.register({
|
|
@@ -24,8 +22,4 @@ export function register(container: AppContainer) {
|
|
|
24
22
|
InventoryReservation: asValue(InventoryReservation),
|
|
25
23
|
InventoryMovement: asValue(InventoryMovement),
|
|
26
24
|
})
|
|
27
|
-
|
|
28
|
-
// Explicit, idempotent (replace-by-id) registration into the shared,
|
|
29
|
-
// dependency-free registry — never "same DI key, load order wins" (§3.1).
|
|
30
|
-
availabilityProviderRegistry.register(createWmsAvailabilityProvider(container))
|
|
31
25
|
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const id = "id";
|
|
2
|
-
const organization_id = "organization_id";
|
|
3
|
-
const tenant_id = "tenant_id";
|
|
4
|
-
const store_id = "store_id";
|
|
5
|
-
const product_id = "product_id";
|
|
6
|
-
const variant_id = "variant_id";
|
|
7
|
-
const is_stock_managed = "is_stock_managed";
|
|
8
|
-
const allow_backorder = "allow_backorder";
|
|
9
|
-
const backorder_lead_time_days = "backorder_lead_time_days";
|
|
10
|
-
const preorder_release_at = "preorder_release_at";
|
|
11
|
-
const low_stock_threshold = "low_stock_threshold";
|
|
12
|
-
const min_order_quantity = "min_order_quantity";
|
|
13
|
-
const max_order_quantity = "max_order_quantity";
|
|
14
|
-
const quantity_increment = "quantity_increment";
|
|
15
|
-
const hide_when_out_of_stock = "hide_when_out_of_stock";
|
|
16
|
-
const is_active = "is_active";
|
|
17
|
-
const created_at = "created_at";
|
|
18
|
-
const updated_at = "updated_at";
|
|
19
|
-
const deleted_at = "deleted_at";
|
|
20
|
-
export {
|
|
21
|
-
allow_backorder,
|
|
22
|
-
backorder_lead_time_days,
|
|
23
|
-
created_at,
|
|
24
|
-
deleted_at,
|
|
25
|
-
hide_when_out_of_stock,
|
|
26
|
-
id,
|
|
27
|
-
is_active,
|
|
28
|
-
is_stock_managed,
|
|
29
|
-
low_stock_threshold,
|
|
30
|
-
max_order_quantity,
|
|
31
|
-
min_order_quantity,
|
|
32
|
-
organization_id,
|
|
33
|
-
preorder_release_at,
|
|
34
|
-
product_id,
|
|
35
|
-
quantity_increment,
|
|
36
|
-
store_id,
|
|
37
|
-
tenant_id,
|
|
38
|
-
updated_at,
|
|
39
|
-
variant_id
|
|
40
|
-
};
|
|
41
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../generated/entities/availability_policy/index.ts"],
|
|
4
|
-
"sourcesContent": ["export const id = \"id\";\nexport const organization_id = \"organization_id\";\nexport const tenant_id = \"tenant_id\";\nexport const store_id = \"store_id\";\nexport const product_id = \"product_id\";\nexport const variant_id = \"variant_id\";\nexport const is_stock_managed = \"is_stock_managed\";\nexport const allow_backorder = \"allow_backorder\";\nexport const backorder_lead_time_days = \"backorder_lead_time_days\";\nexport const preorder_release_at = \"preorder_release_at\";\nexport const low_stock_threshold = \"low_stock_threshold\";\nexport const min_order_quantity = \"min_order_quantity\";\nexport const max_order_quantity = \"max_order_quantity\";\nexport const quantity_increment = \"quantity_increment\";\nexport const hide_when_out_of_stock = \"hide_when_out_of_stock\";\nexport const is_active = \"is_active\";\nexport const created_at = \"created_at\";\nexport const updated_at = \"updated_at\";\nexport const deleted_at = \"deleted_at\";\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,KAAK;AACX,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAClB,MAAM,WAAW;AACjB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,mBAAmB;AACzB,MAAM,kBAAkB;AACxB,MAAM,2BAA2B;AACjC,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;AAC5B,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,qBAAqB;AAC3B,MAAM,yBAAyB;AAC/B,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,aAAa;AACnB,MAAM,aAAa;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const features = [
|
|
2
|
-
{ id: "availability.policies.view", title: "View availability policies", module: "availability" },
|
|
3
|
-
{
|
|
4
|
-
id: "availability.policies.manage",
|
|
5
|
-
title: "Manage availability policies",
|
|
6
|
-
module: "availability",
|
|
7
|
-
dependsOn: ["availability.policies.view"]
|
|
8
|
-
},
|
|
9
|
-
{ id: "availability.check", title: "Run availability checks", module: "availability" }
|
|
10
|
-
];
|
|
11
|
-
var acl_default = features;
|
|
12
|
-
export {
|
|
13
|
-
acl_default as default,
|
|
14
|
-
features
|
|
15
|
-
};
|
|
16
|
-
//# sourceMappingURL=acl.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/modules/availability/acl.ts"],
|
|
4
|
-
"sourcesContent": ["export const features = [\n { id: 'availability.policies.view', title: 'View availability policies', module: 'availability' },\n {\n id: 'availability.policies.manage',\n title: 'Manage availability policies',\n module: 'availability',\n dependsOn: ['availability.policies.view'],\n },\n { id: 'availability.check', title: 'Run availability checks', module: 'availability' },\n]\n\nexport default features\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,8BAA8B,OAAO,8BAA8B,QAAQ,eAAe;AAAA,EAChG;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,4BAA4B;AAAA,EAC1C;AAAA,EACA,EAAE,IAAI,sBAAsB,OAAO,2BAA2B,QAAQ,eAAe;AACvF;AAEA,IAAO,cAAQ;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
4
|
-
import { resolveActiveOrganizationId, organizationScopeRequiredResponse } from "@open-mercato/shared/lib/auth/organizationScope";
|
|
5
|
-
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
6
|
-
import { resolveAvailability, availabilityItemKey } from "@open-mercato/shared/lib/availability";
|
|
7
|
-
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
8
|
-
import { tryResolve } from "../../lib/tryResolve.js";
|
|
9
|
-
const routeMetadata = {
|
|
10
|
-
POST: { requireAuth: true, requireFeatures: ["availability.check"] }
|
|
11
|
-
};
|
|
12
|
-
const metadata = routeMetadata;
|
|
13
|
-
const openApi = {
|
|
14
|
-
tags: ["Availability"],
|
|
15
|
-
summary: "Reproduce the availability state and policy trace a buyer would see for one item.",
|
|
16
|
-
description: "Admin/debug tool. Mirrors resolveAvailability() and includes the per-field policySourceId trace."
|
|
17
|
-
};
|
|
18
|
-
const checkSchema = z.object({
|
|
19
|
-
productId: z.uuid(),
|
|
20
|
-
variantId: z.uuid().nullable().optional(),
|
|
21
|
-
quantity: z.coerce.number().int().min(1).default(1),
|
|
22
|
-
storeId: z.uuid().nullable().optional()
|
|
23
|
-
});
|
|
24
|
-
async function POST(req) {
|
|
25
|
-
const { translate } = await resolveTranslations();
|
|
26
|
-
const auth = await getAuthFromRequest(req);
|
|
27
|
-
if (!auth || !auth.tenantId) {
|
|
28
|
-
return NextResponse.json({ error: translate("availability.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
29
|
-
}
|
|
30
|
-
const organizationId = resolveActiveOrganizationId(auth);
|
|
31
|
-
if (!organizationId) return organizationScopeRequiredResponse();
|
|
32
|
-
const body = await req.json().catch(() => ({}));
|
|
33
|
-
const parsed = checkSchema.safeParse(body);
|
|
34
|
-
if (!parsed.success) {
|
|
35
|
-
return NextResponse.json(
|
|
36
|
-
{ error: translate("availability.errors.invalidCheckRequest", "Invalid request") },
|
|
37
|
-
{ status: 400 }
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
const { productId, variantId, quantity, storeId } = parsed.data;
|
|
41
|
-
const tenantId = auth.tenantId;
|
|
42
|
-
const container = await createRequestContainer();
|
|
43
|
-
const em = container.resolve("em");
|
|
44
|
-
const CatalogProduct = tryResolve(container, "CatalogProduct");
|
|
45
|
-
if (CatalogProduct) {
|
|
46
|
-
const product = await em.findOne(CatalogProduct, { id: productId, organizationId, tenantId, deletedAt: null });
|
|
47
|
-
if (!product) {
|
|
48
|
-
return NextResponse.json(
|
|
49
|
-
{ error: translate("availability.errors.productNotFound", "No such product") },
|
|
50
|
-
{ status: 404 }
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
if (variantId) {
|
|
54
|
-
const CatalogProductVariant = tryResolve(container, "CatalogProductVariant");
|
|
55
|
-
if (CatalogProductVariant) {
|
|
56
|
-
const variant = await em.findOne(CatalogProductVariant, {
|
|
57
|
-
id: variantId,
|
|
58
|
-
organizationId,
|
|
59
|
-
tenantId,
|
|
60
|
-
deletedAt: null
|
|
61
|
-
});
|
|
62
|
-
if (!variant) {
|
|
63
|
-
return NextResponse.json(
|
|
64
|
-
{ error: translate("availability.errors.variantNotFound", "No such variant") },
|
|
65
|
-
{ status: 404 }
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const query = {
|
|
72
|
-
tenantId,
|
|
73
|
-
organizationId,
|
|
74
|
-
storeId: storeId ?? null,
|
|
75
|
-
items: [{ catalogProductId: productId, catalogVariantId: variantId ?? null, quantity }]
|
|
76
|
-
};
|
|
77
|
-
const moduleConfig = tryResolve(container, "moduleConfigService");
|
|
78
|
-
const result = await resolveAvailability(query, moduleConfig ? { moduleConfig } : void 0);
|
|
79
|
-
const item = result.byItem[availabilityItemKey({ catalogProductId: productId, catalogVariantId: variantId ?? null })] ?? null;
|
|
80
|
-
const policyResolutionService = tryResolve(container, "policyResolutionService");
|
|
81
|
-
const policyTrace = policyResolutionService ? await policyResolutionService.resolve(em, {
|
|
82
|
-
tenantId,
|
|
83
|
-
organizationId,
|
|
84
|
-
storeId: storeId ?? null,
|
|
85
|
-
productId,
|
|
86
|
-
variantId: variantId ?? null
|
|
87
|
-
}) : null;
|
|
88
|
-
return NextResponse.json({ availability: item, policyTrace });
|
|
89
|
-
}
|
|
90
|
-
export {
|
|
91
|
-
POST,
|
|
92
|
-
metadata,
|
|
93
|
-
openApi
|
|
94
|
-
};
|
|
95
|
-
//# sourceMappingURL=route.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../../src/modules/availability/api/check/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveActiveOrganizationId, organizationScopeRequiredResponse } from '@open-mercato/shared/lib/auth/organizationScope'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveAvailability, availabilityItemKey } from '@open-mercato/shared/lib/availability'\nimport type { AvailabilityModuleConfigReader, AvailabilityQuery } from '@open-mercato/shared/lib/availability'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { tryResolve } from '../../lib/tryResolve'\nimport type { PolicyResolutionService } from '../../lib/policyResolution'\n\n/**\n * `POST /api/availability/check` \u2014 admin/debug reproduction of what a buyer saw,\n * including the per-field `policySourceId` trace. No public storefront endpoints\n * ship in this module (\u00A78).\n */\nconst routeMetadata = {\n POST: { requireAuth: true, requireFeatures: ['availability.check'] },\n}\n\nexport const metadata = routeMetadata\n\nexport const openApi = {\n tags: ['Availability'],\n summary: 'Reproduce the availability state and policy trace a buyer would see for one item.',\n description: 'Admin/debug tool. Mirrors resolveAvailability() and includes the per-field policySourceId trace.',\n}\n\nconst checkSchema = z.object({\n productId: z.uuid(),\n variantId: z.uuid().nullable().optional(),\n quantity: z.coerce.number().int().min(1).default(1),\n storeId: z.uuid().nullable().optional(),\n})\n\nexport async function POST(req: Request) {\n const { translate } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: translate('availability.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n\n const organizationId = resolveActiveOrganizationId(auth)\n if (!organizationId) return organizationScopeRequiredResponse()\n\n const body = await req.json().catch(() => ({}))\n const parsed = checkSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json(\n { error: translate('availability.errors.invalidCheckRequest', 'Invalid request') },\n { status: 400 },\n )\n }\n const { productId, variantId, quantity, storeId } = parsed.data\n const tenantId = auth.tenantId\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n // Soft-resolved product/variant existence check \u2014 degrades gracefully when\n // `catalog` is ejected (Phase 1 gate: coherent behaviour without it).\n const CatalogProduct = tryResolve<new () => unknown>(container, 'CatalogProduct')\n if (CatalogProduct) {\n const product = await em.findOne(CatalogProduct as any, { id: productId, organizationId, tenantId, deletedAt: null })\n if (!product) {\n return NextResponse.json(\n { error: translate('availability.errors.productNotFound', 'No such product') },\n { status: 404 },\n )\n }\n if (variantId) {\n const CatalogProductVariant = tryResolve<new () => unknown>(container, 'CatalogProductVariant')\n if (CatalogProductVariant) {\n const variant = await em.findOne(CatalogProductVariant as any, {\n id: variantId,\n organizationId,\n tenantId,\n deletedAt: null,\n })\n if (!variant) {\n return NextResponse.json(\n { error: translate('availability.errors.variantNotFound', 'No such variant') },\n { status: 404 },\n )\n }\n }\n }\n }\n\n const query: AvailabilityQuery = {\n tenantId,\n organizationId,\n storeId: storeId ?? null,\n items: [{ catalogProductId: productId, catalogVariantId: variantId ?? null, quantity }],\n }\n\n const moduleConfig = tryResolve<AvailabilityModuleConfigReader>(container, 'moduleConfigService')\n const result = await resolveAvailability(query, moduleConfig ? { moduleConfig } : undefined)\n const item = result.byItem[availabilityItemKey({ catalogProductId: productId, catalogVariantId: variantId ?? null })] ?? null\n\n const policyResolutionService = tryResolve<PolicyResolutionService>(container, 'policyResolutionService')\n const policyTrace = policyResolutionService\n ? await policyResolutionService.resolve(em, {\n tenantId,\n organizationId,\n storeId: storeId ?? null,\n productId,\n variantId: variantId ?? null,\n })\n : null\n\n return NextResponse.json({ availability: item, policyTrace })\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createCrudOpenApiFactory,
|
|
3
|
-
createPagedListResponseSchema as createSharedPagedListResponseSchema,
|
|
4
|
-
defaultCreateResponseSchema as sharedDefaultCreateResponseSchema,
|
|
5
|
-
defaultOkResponseSchema as sharedDefaultOkResponseSchema
|
|
6
|
-
} from "@open-mercato/shared/lib/openapi/crud";
|
|
7
|
-
const defaultCreateResponseSchema = sharedDefaultCreateResponseSchema;
|
|
8
|
-
const defaultOkResponseSchema = sharedDefaultOkResponseSchema;
|
|
9
|
-
function createPagedListResponseSchema(itemSchema) {
|
|
10
|
-
return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true });
|
|
11
|
-
}
|
|
12
|
-
const buildAvailabilityCrudOpenApi = createCrudOpenApiFactory({
|
|
13
|
-
defaultTag: "Availability",
|
|
14
|
-
defaultCreateResponseSchema,
|
|
15
|
-
defaultOkResponseSchema,
|
|
16
|
-
makeListDescription: ({ pluralLower }) => `Returns a paginated collection of ${pluralLower} scoped to the authenticated organization.`
|
|
17
|
-
});
|
|
18
|
-
function createAvailabilityCrudOpenApi(options) {
|
|
19
|
-
return buildAvailabilityCrudOpenApi(options);
|
|
20
|
-
}
|
|
21
|
-
export {
|
|
22
|
-
createAvailabilityCrudOpenApi,
|
|
23
|
-
createPagedListResponseSchema,
|
|
24
|
-
defaultCreateResponseSchema,
|
|
25
|
-
defaultOkResponseSchema
|
|
26
|
-
};
|
|
27
|
-
//# sourceMappingURL=openapi.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/modules/availability/api/openapi.ts"],
|
|
4
|
-
"sourcesContent": ["import { type ZodTypeAny } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n createCrudOpenApiFactory,\n createPagedListResponseSchema as createSharedPagedListResponseSchema,\n defaultCreateResponseSchema as sharedDefaultCreateResponseSchema,\n defaultOkResponseSchema as sharedDefaultOkResponseSchema,\n type CrudOpenApiOptions,\n} from '@open-mercato/shared/lib/openapi/crud'\n\nexport const defaultCreateResponseSchema = sharedDefaultCreateResponseSchema\nexport const defaultOkResponseSchema = sharedDefaultOkResponseSchema\n\nexport function createPagedListResponseSchema(itemSchema: ZodTypeAny) {\n return createSharedPagedListResponseSchema(itemSchema, { paginationMetaOptional: true })\n}\n\nconst buildAvailabilityCrudOpenApi = createCrudOpenApiFactory({\n defaultTag: 'Availability',\n defaultCreateResponseSchema,\n defaultOkResponseSchema,\n makeListDescription: ({ pluralLower }) =>\n `Returns a paginated collection of ${pluralLower} scoped to the authenticated organization.`,\n})\n\nexport function createAvailabilityCrudOpenApi(options: CrudOpenApiOptions): OpenApiRouteDoc {\n return buildAvailabilityCrudOpenApi(options)\n}\n"],
|
|
5
|
-
"mappings": "AAEA;AAAA,EACE;AAAA,EACA,iCAAiC;AAAA,EACjC,+BAA+B;AAAA,EAC/B,2BAA2B;AAAA,OAEtB;AAEA,MAAM,8BAA8B;AACpC,MAAM,0BAA0B;AAEhC,SAAS,8BAA8B,YAAwB;AACpE,SAAO,oCAAoC,YAAY,EAAE,wBAAwB,KAAK,CAAC;AACzF;AAEA,MAAM,+BAA+B,yBAAyB;AAAA,EAC5D,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,qBAAqB,CAAC,EAAE,YAAY,MAClC,qCAAqC,WAAW;AACpD,CAAC;AAEM,SAAS,8BAA8B,SAA8C;AAC1F,SAAO,6BAA6B,OAAO;AAC7C;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|