@open-mercato/core 0.10.1-develop.7826.1.5e82b7c668 → 0.10.1-develop.7833.1.d6cbf0e3ab

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.
@@ -23,17 +23,18 @@ function appendLine(lines, label, value) {
23
23
  function resolveClaimId(record) {
24
24
  return readString(record, "id", "id");
25
25
  }
26
- function resolvePresenter(record) {
26
+ async function resolvePresenter(record) {
27
+ const { t } = await resolveTranslations();
27
28
  const claimNumber = readString(record, "claim_number", "claimNumber") ?? "Warranty claim";
28
29
  const customerName = readString(record, "customer_name", "customerName");
29
30
  const claimType = readString(record, "claim_type", "claimType");
30
31
  const status = readString(record, "status", "status");
31
- const subtitle = [customerName, claimType].filter((value) => Boolean(value)).join(" \u2014 ");
32
+ const statusLabel = status ? t(`warranty_claims.status.${status}`, status) : null;
33
+ const subtitle = [customerName, claimType, statusLabel].filter((value) => Boolean(value)).join(" \u2014 ");
32
34
  return {
33
35
  title: claimNumber,
34
36
  subtitle: subtitle || void 0,
35
- icon: "shield-check",
36
- badge: status ?? void 0
37
+ icon: "shield-check"
37
38
  };
38
39
  }
39
40
  async function loadClaimLineRows(ctx, claimId) {
@@ -113,7 +114,7 @@ const searchConfig = {
113
114
  const lineRows = claimId ? await loadClaimLineRows(ctx, claimId) : [];
114
115
  appendLineRows(lines, lineRows);
115
116
  if (!lines.length) return null;
116
- const presenter = resolvePresenter(record);
117
+ const presenter = await resolvePresenter(record);
117
118
  const url = buildClaimUrl(record);
118
119
  const links = url ? [{ href: url, label: presenter.title, kind: "primary" }] : [];
119
120
  return {
@@ -127,7 +128,7 @@ const searchConfig = {
127
128
  }
128
129
  };
129
130
  },
130
- formatResult: (ctx) => {
131
+ formatResult: async (ctx) => {
131
132
  assertTenantContext(ctx);
132
133
  return resolvePresenter(ctx.record);
133
134
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/warranty_claims/search.ts"],
4
- "sourcesContent": ["import { SortDir, type QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type {\n SearchBuildContext,\n SearchIndexSource,\n SearchModuleConfig,\n SearchResultLink,\n SearchResultPresenter,\n} from '@open-mercato/shared/modules/search'\nimport { E } from '#generated/entities.ids.generated'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nconst logger = createLogger('warranty_claims')\n\ntype SearchContext = SearchBuildContext & {\n tenantId: string\n queryEngine?: QueryEngine\n}\n\nfunction assertTenantContext(ctx: SearchBuildContext): asserts ctx is SearchContext {\n if (typeof ctx.tenantId !== 'string' || ctx.tenantId.length === 0) {\n throw new Error('[internal] [search.warranty_claims] Missing tenantId in search build context')\n }\n}\n\nfunction readString(record: Record<string, unknown>, snakeKey: string, camelKey?: string): string | null {\n const value = record[snakeKey] ?? (camelKey ? record[camelKey] : undefined)\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\nfunction appendLine(lines: string[], label: string, value: unknown): void {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed.length) return\n lines.push(`${label}: ${trimmed}`)\n}\n\nfunction resolveClaimId(record: Record<string, unknown>): string | null {\n return readString(record, 'id', 'id')\n}\n\nfunction resolvePresenter(record: Record<string, unknown>): SearchResultPresenter {\n const claimNumber = readString(record, 'claim_number', 'claimNumber') ?? 'Warranty claim'\n const customerName = readString(record, 'customer_name', 'customerName')\n const claimType = readString(record, 'claim_type', 'claimType')\n const status = readString(record, 'status', 'status')\n const subtitle = [customerName, claimType].filter((value): value is string => Boolean(value)).join(' \u2014 ')\n return {\n title: claimNumber,\n subtitle: subtitle || undefined,\n icon: 'shield-check',\n badge: status ?? undefined,\n }\n}\n\nasync function loadClaimLineRows(ctx: SearchContext, claimId: string): Promise<Array<Record<string, unknown>>> {\n if (!ctx.queryEngine) return []\n try {\n const rows: Array<Record<string, unknown>> = []\n const pageSize = 100\n for (let page = 1; ; page += 1) {\n const result = await ctx.queryEngine.query<Record<string, unknown>>(E.warranty_claims.warranty_claim_line, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? undefined,\n filters: { claim_id: { $eq: claimId } },\n fields: [\n 'id',\n 'line_no',\n 'sku',\n 'product_name',\n 'serial_number',\n 'lot_number',\n 'fault_code',\n 'warranty_status',\n 'disposition',\n 'line_status',\n ],\n page: { page, pageSize },\n sort: [\n { field: 'line_no', dir: SortDir.Asc },\n { field: 'id', dir: SortDir.Asc },\n ],\n })\n rows.push(...result.items)\n if (result.items.length < pageSize || rows.length >= result.total) break\n }\n return rows\n } catch (err) {\n logger.warn('[search.warranty_claims] Failed to load claim lines', {\n claimId,\n error: err instanceof Error ? err.message : err,\n })\n return []\n }\n}\n\nfunction appendLineRows(lines: string[], lineRows: Array<Record<string, unknown>>): void {\n for (const row of lineRows) {\n appendLine(lines, 'Line SKU', readString(row, 'sku', 'sku') ?? '')\n appendLine(lines, 'Line product', readString(row, 'product_name', 'productName') ?? '')\n appendLine(lines, 'Serial number', readString(row, 'serial_number', 'serialNumber') ?? '')\n appendLine(lines, 'Lot number', readString(row, 'lot_number', 'lotNumber') ?? '')\n appendLine(lines, 'Fault code', readString(row, 'fault_code', 'faultCode') ?? '')\n appendLine(lines, 'Warranty status', readString(row, 'warranty_status', 'warrantyStatus') ?? '')\n appendLine(lines, 'Disposition', readString(row, 'disposition', 'disposition') ?? '')\n appendLine(lines, 'Line status', readString(row, 'line_status', 'lineStatus') ?? '')\n }\n}\n\nfunction buildClaimUrl(record: Record<string, unknown>): string | null {\n const claimId = resolveClaimId(record)\n return claimId ? `/backend/warranty_claims/${claimId}` : null\n}\n\nexport const searchConfig: SearchModuleConfig = {\n entities: [\n {\n entityId: E.warranty_claims.warranty_claim,\n enabled: true,\n priority: 20,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const record = ctx.record\n const lines: string[] = []\n appendLine(lines, 'Claim number', readString(record, 'claim_number', 'claimNumber') ?? '')\n appendLine(lines, 'Customer', readString(record, 'customer_name', 'customerName') ?? '')\n appendLine(lines, 'Order number', readString(record, 'order_number', 'orderNumber') ?? '')\n appendLine(lines, 'Claim type', readString(record, 'claim_type', 'claimType') ?? '')\n appendLine(lines, 'Status', readString(record, 'status', 'status') ?? '')\n appendLine(lines, 'Vendor', readString(record, 'vendor_name', 'vendorName') ?? '')\n appendLine(lines, 'Vendor reference', readString(record, 'vendor_ref', 'vendorRef') ?? '')\n\n const claimId = resolveClaimId(record)\n const lineRows = claimId ? await loadClaimLineRows(ctx, claimId) : []\n appendLineRows(lines, lineRows)\n if (!lines.length) return null\n\n const presenter = resolvePresenter(record)\n const url = buildClaimUrl(record)\n const links: SearchResultLink[] = url ? [{ href: url, label: presenter.title, kind: 'primary' }] : []\n return {\n text: lines,\n presenter,\n links,\n checksumSource: {\n record,\n customFields: ctx.customFields,\n lineRows,\n },\n }\n },\n\n formatResult: (ctx: SearchBuildContext): SearchResultPresenter | null => {\n assertTenantContext(ctx)\n return resolvePresenter(ctx.record)\n },\n\n resolveUrl: (ctx: SearchBuildContext): string | null => buildClaimUrl(ctx.record),\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const url = buildClaimUrl(ctx.record)\n if (!url) return null\n const { t } = await resolveTranslations()\n return [{ href: url, label: t('warranty_claims.search.openClaim', 'Open claim'), kind: 'secondary' }]\n },\n\n fieldPolicy: {\n searchable: [\n 'claim_number',\n 'claim_type',\n 'status',\n 'priority',\n 'customer_name',\n 'vendor_name',\n 'vendor_ref',\n ],\n hashOnly: ['customer_id', 'order_id', 'sales_return_id', 'replacement_order_id', 'source_claim_id'],\n excluded: [\n 'tenant_id',\n 'organization_id',\n 'assignee_user_id',\n 'deleted_at',\n 'notes',\n 'resolution_summary',\n 'contact_email',\n 'fault_description',\n 'inspection_notes',\n 'rejection_reason_code',\n ],\n },\n aclFeatures: ['warranty_claims.claim.view'],\n },\n ],\n}\n\nexport default searchConfig\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { SortDir, type QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type {\n SearchBuildContext,\n SearchIndexSource,\n SearchModuleConfig,\n SearchResultLink,\n SearchResultPresenter,\n} from '@open-mercato/shared/modules/search'\nimport { E } from '#generated/entities.ids.generated'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nconst logger = createLogger('warranty_claims')\n\ntype SearchContext = SearchBuildContext & {\n tenantId: string\n queryEngine?: QueryEngine\n}\n\nfunction assertTenantContext(ctx: SearchBuildContext): asserts ctx is SearchContext {\n if (typeof ctx.tenantId !== 'string' || ctx.tenantId.length === 0) {\n throw new Error('[internal] [search.warranty_claims] Missing tenantId in search build context')\n }\n}\n\nfunction readString(record: Record<string, unknown>, snakeKey: string, camelKey?: string): string | null {\n const value = record[snakeKey] ?? (camelKey ? record[camelKey] : undefined)\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\nfunction appendLine(lines: string[], label: string, value: unknown): void {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed.length) return\n lines.push(`${label}: ${trimmed}`)\n}\n\nfunction resolveClaimId(record: Record<string, unknown>): string | null {\n return readString(record, 'id', 'id')\n}\n\nasync function resolvePresenter(record: Record<string, unknown>): Promise<SearchResultPresenter> {\n const { t } = await resolveTranslations()\n const claimNumber = readString(record, 'claim_number', 'claimNumber') ?? 'Warranty claim'\n const customerName = readString(record, 'customer_name', 'customerName')\n const claimType = readString(record, 'claim_type', 'claimType')\n const status = readString(record, 'status', 'status')\n const statusLabel = status ? t(`warranty_claims.status.${status}`, status) : null\n const subtitle = [customerName, claimType, statusLabel]\n .filter((value): value is string => Boolean(value))\n .join(' \u2014 ')\n return {\n title: claimNumber,\n subtitle: subtitle || undefined,\n icon: 'shield-check',\n }\n}\n\nasync function loadClaimLineRows(ctx: SearchContext, claimId: string): Promise<Array<Record<string, unknown>>> {\n if (!ctx.queryEngine) return []\n try {\n const rows: Array<Record<string, unknown>> = []\n const pageSize = 100\n for (let page = 1; ; page += 1) {\n const result = await ctx.queryEngine.query<Record<string, unknown>>(E.warranty_claims.warranty_claim_line, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? undefined,\n filters: { claim_id: { $eq: claimId } },\n fields: [\n 'id',\n 'line_no',\n 'sku',\n 'product_name',\n 'serial_number',\n 'lot_number',\n 'fault_code',\n 'warranty_status',\n 'disposition',\n 'line_status',\n ],\n page: { page, pageSize },\n sort: [\n { field: 'line_no', dir: SortDir.Asc },\n { field: 'id', dir: SortDir.Asc },\n ],\n })\n rows.push(...result.items)\n if (result.items.length < pageSize || rows.length >= result.total) break\n }\n return rows\n } catch (err) {\n logger.warn('[search.warranty_claims] Failed to load claim lines', {\n claimId,\n error: err instanceof Error ? err.message : err,\n })\n return []\n }\n}\n\nfunction appendLineRows(lines: string[], lineRows: Array<Record<string, unknown>>): void {\n for (const row of lineRows) {\n appendLine(lines, 'Line SKU', readString(row, 'sku', 'sku') ?? '')\n appendLine(lines, 'Line product', readString(row, 'product_name', 'productName') ?? '')\n appendLine(lines, 'Serial number', readString(row, 'serial_number', 'serialNumber') ?? '')\n appendLine(lines, 'Lot number', readString(row, 'lot_number', 'lotNumber') ?? '')\n appendLine(lines, 'Fault code', readString(row, 'fault_code', 'faultCode') ?? '')\n appendLine(lines, 'Warranty status', readString(row, 'warranty_status', 'warrantyStatus') ?? '')\n appendLine(lines, 'Disposition', readString(row, 'disposition', 'disposition') ?? '')\n appendLine(lines, 'Line status', readString(row, 'line_status', 'lineStatus') ?? '')\n }\n}\n\nfunction buildClaimUrl(record: Record<string, unknown>): string | null {\n const claimId = resolveClaimId(record)\n return claimId ? `/backend/warranty_claims/${claimId}` : null\n}\n\nexport const searchConfig: SearchModuleConfig = {\n entities: [\n {\n entityId: E.warranty_claims.warranty_claim,\n enabled: true,\n priority: 20,\n\n buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {\n assertTenantContext(ctx)\n const record = ctx.record\n const lines: string[] = []\n appendLine(lines, 'Claim number', readString(record, 'claim_number', 'claimNumber') ?? '')\n appendLine(lines, 'Customer', readString(record, 'customer_name', 'customerName') ?? '')\n appendLine(lines, 'Order number', readString(record, 'order_number', 'orderNumber') ?? '')\n appendLine(lines, 'Claim type', readString(record, 'claim_type', 'claimType') ?? '')\n appendLine(lines, 'Status', readString(record, 'status', 'status') ?? '')\n appendLine(lines, 'Vendor', readString(record, 'vendor_name', 'vendorName') ?? '')\n appendLine(lines, 'Vendor reference', readString(record, 'vendor_ref', 'vendorRef') ?? '')\n\n const claimId = resolveClaimId(record)\n const lineRows = claimId ? await loadClaimLineRows(ctx, claimId) : []\n appendLineRows(lines, lineRows)\n if (!lines.length) return null\n\n const presenter = await resolvePresenter(record)\n const url = buildClaimUrl(record)\n const links: SearchResultLink[] = url ? [{ href: url, label: presenter.title, kind: 'primary' }] : []\n return {\n text: lines,\n presenter,\n links,\n checksumSource: {\n record,\n customFields: ctx.customFields,\n lineRows,\n },\n }\n },\n\n formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {\n assertTenantContext(ctx)\n return resolvePresenter(ctx.record)\n },\n\n resolveUrl: (ctx: SearchBuildContext): string | null => buildClaimUrl(ctx.record),\n\n resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {\n const url = buildClaimUrl(ctx.record)\n if (!url) return null\n const { t } = await resolveTranslations()\n return [{ href: url, label: t('warranty_claims.search.openClaim', 'Open claim'), kind: 'secondary' }]\n },\n\n fieldPolicy: {\n searchable: [\n 'claim_number',\n 'claim_type',\n 'status',\n 'priority',\n 'customer_name',\n 'vendor_name',\n 'vendor_ref',\n ],\n hashOnly: ['customer_id', 'order_id', 'sales_return_id', 'replacement_order_id', 'source_claim_id'],\n excluded: [\n 'tenant_id',\n 'organization_id',\n 'assignee_user_id',\n 'deleted_at',\n 'notes',\n 'resolution_summary',\n 'contact_email',\n 'fault_description',\n 'inspection_notes',\n 'rejection_reason_code',\n ],\n },\n aclFeatures: ['warranty_claims.claim.view'],\n },\n ],\n}\n\nexport default searchConfig\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.10.1-develop.7826.1.5e82b7c668",
3
+ "version": "0.10.1-develop.7833.1.d6cbf0e3ab",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -266,16 +266,16 @@
266
266
  "zod": "^4.6.6"
267
267
  },
268
268
  "peerDependencies": {
269
- "@open-mercato/ai-assistant": "0.10.1-develop.7826.1.5e82b7c668",
270
- "@open-mercato/shared": "0.10.1-develop.7826.1.5e82b7c668",
271
- "@open-mercato/ui": "0.10.1-develop.7826.1.5e82b7c668",
269
+ "@open-mercato/ai-assistant": "0.10.1-develop.7833.1.d6cbf0e3ab",
270
+ "@open-mercato/shared": "0.10.1-develop.7833.1.d6cbf0e3ab",
271
+ "@open-mercato/ui": "0.10.1-develop.7833.1.d6cbf0e3ab",
272
272
  "react": "^19.0.0",
273
273
  "react-dom": "^19.0.0"
274
274
  },
275
275
  "devDependencies": {
276
- "@open-mercato/ai-assistant": "0.10.1-develop.7826.1.5e82b7c668",
277
- "@open-mercato/shared": "0.10.1-develop.7826.1.5e82b7c668",
278
- "@open-mercato/ui": "0.10.1-develop.7826.1.5e82b7c668",
276
+ "@open-mercato/ai-assistant": "0.10.1-develop.7833.1.d6cbf0e3ab",
277
+ "@open-mercato/shared": "0.10.1-develop.7833.1.d6cbf0e3ab",
278
+ "@open-mercato/ui": "0.10.1-develop.7833.1.d6cbf0e3ab",
279
279
  "@testing-library/dom": "^10.4.1",
280
280
  "@testing-library/jest-dom": "^7.0.1",
281
281
  "@testing-library/react": "^16.3.3",
@@ -0,0 +1,26 @@
1
+ import { createRequire } from 'node:module'
2
+ import path from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+
5
+ /**
6
+ * Imports a package module from the same copy the app under test runs on.
7
+ *
8
+ * A spec that bootstraps the app in-process (`bootstrapFromAppRoot`) and then calls into its
9
+ * runtime — the command bus, `withAtomicFlush`, transaction lifetime — must load every one of those
10
+ * modules from a single copy. Against a standalone app (`OM_TEST_APP_ROOT`) the app's generated
11
+ * registries import `node_modules/@open-mercato/*` inside the app, while the spec's own static
12
+ * imports resolve to the monorepo's packages, so module-local state such as the command registry
13
+ * splits in two: handlers register in one copy and the bus resolves them in the other.
14
+ *
15
+ * Without `OM_TEST_APP_ROOT` this is a plain dynamic import, so the monorepo lane is unchanged.
16
+ */
17
+ export async function importFromAppRuntime<TModule>(specifier: string): Promise<TModule> {
18
+ const resolvedPath = resolveAppRuntimeModulePath(specifier)
19
+ return (await import(resolvedPath ? pathToFileURL(resolvedPath).href : specifier)) as TModule
20
+ }
21
+
22
+ export function resolveAppRuntimeModulePath(specifier: string, appRoot = process.env.OM_TEST_APP_ROOT): string | null {
23
+ const standaloneRoot = appRoot?.trim()
24
+ if (!standaloneRoot) return null
25
+ return createRequire(path.join(path.resolve(standaloneRoot), 'package.json')).resolve(specifier)
26
+ }
@@ -58,7 +58,7 @@ import {
58
58
  import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
59
59
  import { buildContainmentPatterns } from '@open-mercato/shared/lib/search/containment'
60
60
  import { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from '@open-mercato/shared/lib/query/query-extension-runner'
61
- import { warnOnCiphertextLikeFallback } from '@open-mercato/shared/lib/query/ciphertext-search-warning'
61
+ import { warnOnCiphertextLikeFallback, type CiphertextSearchWarning } from '@open-mercato/shared/lib/query/ciphertext-search-warning'
62
62
  import {
63
63
  matchEncryptedSortFields,
64
64
  resolveEncryptedFieldNames,
@@ -452,12 +452,17 @@ export class HybridQueryEngine implements QueryEngine {
452
452
  const applyAfterExtensions = async <R>(queryResult: QueryResult<R>): Promise<QueryResult<R>> => {
453
453
  if (!ext || !hybridExtCtx) return queryResult
454
454
  const diCtx = ext.resolve ? { resolve: ext.resolve } : noopDi
455
- return await runAfterQueryPipeline(
455
+ const ciphertextSearchWarnings = queryResult.meta?.ciphertextSearchWarnings
456
+ const result = await runAfterQueryPipeline(
456
457
  queryResult as QueryResult<Record<string, unknown>>,
457
458
  opts,
458
459
  hybridExtCtx,
459
460
  diCtx,
460
461
  ) as QueryResult<R>
462
+ if (ciphertextSearchWarnings?.length) {
463
+ result.meta = { ...result.meta, ciphertextSearchWarnings }
464
+ }
465
+ return result
461
466
  }
462
467
 
463
468
  try {
@@ -727,6 +732,7 @@ export class HybridQueryEngine implements QueryEngine {
727
732
  searchRuntime.encryptedFields = null
728
733
  }
729
734
  }
735
+ const ciphertextSearchWarnings: CiphertextSearchWarning[] = []
730
736
  if (searchFilters.length) {
731
737
  this.logSearchDebug('search:init', {
732
738
  entity,
@@ -768,6 +774,7 @@ export class HybridQueryEngine implements QueryEngine {
768
774
  ? 'no-indexable-tokens'
769
775
  : searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',
770
776
  service: this.getEncryptionService(),
777
+ onDiagnostic: (warning) => ciphertextSearchWarnings.push(warning),
771
778
  })
772
779
  }
773
780
  }
@@ -791,6 +798,7 @@ export class HybridQueryEngine implements QueryEngine {
791
798
  ? 'no-indexable-tokens'
792
799
  : searchConfig.enabled ? 'no-search-tokens' : 'search-disabled',
793
800
  service: this.getEncryptionService(),
801
+ onDiagnostic: (warning) => ciphertextSearchWarnings.push(warning),
794
802
  })
795
803
  }
796
804
  const hasNonBaseSearchSource = searchSources.some(
@@ -1597,6 +1605,9 @@ export class HybridQueryEngine implements QueryEngine {
1597
1605
  }
1598
1606
 
1599
1607
  result = await applyAfterExtensions(result)
1608
+ if (ciphertextSearchWarnings.length) {
1609
+ result.meta = { ...result.meta, ciphertextSearchWarnings }
1610
+ }
1600
1611
  finishProfile({
1601
1612
  result: 'ok', total, page, pageSize,
1602
1613
  itemCount: Array.isArray(items) ? items.length : undefined,
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "notifications.categories.warranty_claims": "Garantieclaims",
3
+ "search.entityType.warranty_claims.warranty_claim": "Garantieclaim",
3
4
  "warranty_claims.ai.apply": "Anwenden",
4
5
  "warranty_claims.ai.assess.attachment": "Anhang",
5
6
  "warranty_claims.ai.assess.attachmentsError": "Positionsanhänge konnten nicht geladen werden.",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "notifications.categories.warranty_claims": "Warranty claims",
3
+ "search.entityType.warranty_claims.warranty_claim": "Warranty Claim",
3
4
  "warranty_claims.ai.apply": "Apply",
4
5
  "warranty_claims.ai.assess.attachment": "Attachment",
5
6
  "warranty_claims.ai.assess.attachmentsError": "Failed to load line attachments.",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "notifications.categories.warranty_claims": "Reclamos de garantía",
3
+ "search.entityType.warranty_claims.warranty_claim": "Reclamo de garantía",
3
4
  "warranty_claims.ai.apply": "Aplicar",
4
5
  "warranty_claims.ai.assess.attachment": "Adjunto",
5
6
  "warranty_claims.ai.assess.attachmentsError": "No se pudieron cargar los adjuntos de la línea.",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "notifications.categories.warranty_claims": "보증 클레임",
3
+ "search.entityType.warranty_claims.warranty_claim": "보증 클레임",
3
4
  "warranty_claims.ai.apply": "적용",
4
5
  "warranty_claims.ai.assess.attachment": "첨부파일",
5
6
  "warranty_claims.ai.assess.attachmentsError": "라인 첨부파일을 불러오지 못했습니다.",
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "notifications.categories.warranty_claims": "Reklamacje gwarancyjne",
3
+ "search.entityType.warranty_claims.warranty_claim": "Reklamacja gwarancyjna",
3
4
  "warranty_claims.ai.apply": "Zastosuj",
4
5
  "warranty_claims.ai.assess.attachment": "Załącznik",
5
6
  "warranty_claims.ai.assess.attachmentsError": "Nie udało się załadować załączników pozycji.",
@@ -41,17 +41,20 @@ function resolveClaimId(record: Record<string, unknown>): string | null {
41
41
  return readString(record, 'id', 'id')
42
42
  }
43
43
 
44
- function resolvePresenter(record: Record<string, unknown>): SearchResultPresenter {
44
+ async function resolvePresenter(record: Record<string, unknown>): Promise<SearchResultPresenter> {
45
+ const { t } = await resolveTranslations()
45
46
  const claimNumber = readString(record, 'claim_number', 'claimNumber') ?? 'Warranty claim'
46
47
  const customerName = readString(record, 'customer_name', 'customerName')
47
48
  const claimType = readString(record, 'claim_type', 'claimType')
48
49
  const status = readString(record, 'status', 'status')
49
- const subtitle = [customerName, claimType].filter((value): value is string => Boolean(value)).join(' — ')
50
+ const statusLabel = status ? t(`warranty_claims.status.${status}`, status) : null
51
+ const subtitle = [customerName, claimType, statusLabel]
52
+ .filter((value): value is string => Boolean(value))
53
+ .join(' — ')
50
54
  return {
51
55
  title: claimNumber,
52
56
  subtitle: subtitle || undefined,
53
57
  icon: 'shield-check',
54
- badge: status ?? undefined,
55
58
  }
56
59
  }
57
60
 
@@ -138,7 +141,7 @@ export const searchConfig: SearchModuleConfig = {
138
141
  appendLineRows(lines, lineRows)
139
142
  if (!lines.length) return null
140
143
 
141
- const presenter = resolvePresenter(record)
144
+ const presenter = await resolvePresenter(record)
142
145
  const url = buildClaimUrl(record)
143
146
  const links: SearchResultLink[] = url ? [{ href: url, label: presenter.title, kind: 'primary' }] : []
144
147
  return {
@@ -153,7 +156,7 @@ export const searchConfig: SearchModuleConfig = {
153
156
  }
154
157
  },
155
158
 
156
- formatResult: (ctx: SearchBuildContext): SearchResultPresenter | null => {
159
+ formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
157
160
  assertTenantContext(ctx)
158
161
  return resolvePresenter(ctx.record)
159
162
  },