@open-mercato/core 0.6.8-develop.7054.1.3c75040822 → 0.6.8-develop.7057.1.61440fc3bc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/customers/api/people/check-phone/route.js +51 -9
- package/dist/modules/customers/api/people/check-phone/route.js.map +2 -2
- package/dist/modules/customers/lib/findPeopleByAddresses.js +1 -0
- package/dist/modules/customers/lib/findPeopleByAddresses.js.map +2 -2
- package/dist/modules/eudr/commands/statements.js +0 -3
- package/dist/modules/eudr/commands/statements.js.map +2 -2
- package/dist/modules/eudr/data/validators.js +4 -1
- package/dist/modules/eudr/data/validators.js.map +2 -2
- package/dist/modules/warranty_claims/api/assignees/route.js +3 -3
- package/dist/modules/warranty_claims/api/assignees/route.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/claims/route.js +12 -9
- package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/api/people/check-phone/route.ts +75 -10
- package/src/modules/customers/lib/findPeopleByAddresses.ts +8 -6
- package/src/modules/eudr/commands/statements.ts +0 -3
- package/src/modules/eudr/data/validators.ts +16 -1
- package/src/modules/warranty_claims/api/assignees/route.ts +3 -3
- package/src/modules/warranty_claims/api/portal/claims/route.ts +12 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/eudr/data/validators.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\n\nimport { EUDR_RISK_CRITERIA_KEYS } from '../lib/reference-data'\n\nexport const EUDR_COMMODITIES = ['cattle', 'cocoa', 'coffee', 'oil_palm', 'rubber', 'soya', 'wood'] as const\nexport type EudrCommodity = (typeof EUDR_COMMODITIES)[number]\n\nexport const EUDR_SUBMISSION_STATUSES = ['draft', 'submitted', 'verified', 'rejected'] as const\nexport type EudrSubmissionStatus = (typeof EUDR_SUBMISSION_STATUSES)[number]\n\nexport const EUDR_STATEMENT_STATUSES = ['draft', 'submitted', 'available', 'withdrawn', 'archived'] as const\nexport type EudrStatementStatus = (typeof EUDR_STATEMENT_STATUSES)[number]\n\nexport const EUDR_PLOT_TYPES = ['point', 'polygon'] as const\nexport type EudrPlotType = (typeof EUDR_PLOT_TYPES)[number]\n\nexport const EUDR_RISK_TIERS = ['low', 'standard', 'high', 'mixed', 'unknown'] as const\nexport type EudrRiskTier = (typeof EUDR_RISK_TIERS)[number]\n\nexport const EUDR_RISK_CONCLUSIONS = ['negligible', 'non_negligible'] as const\nexport type EudrRiskConclusion = (typeof EUDR_RISK_CONCLUSIONS)[number]\n\nexport const EUDR_CRITERIA_ANSWERS = ['no_concern', 'concern', 'not_applicable'] as const\nexport type EudrCriteriaAnswer = (typeof EUDR_CRITERIA_ANSWERS)[number]\n\nexport const EUDR_MITIGATION_TYPES = [\n 'request_documents',\n 'supplier_audit',\n 'satellite_verification',\n 'certification_check',\n 'switch_sourcing',\n 'other',\n] as const\nexport type EudrMitigationType = (typeof EUDR_MITIGATION_TYPES)[number]\n\nexport const EUDR_MITIGATION_STATUSES = ['planned', 'in_progress', 'completed', 'cancelled'] as const\nexport type EudrMitigationStatus = (typeof EUDR_MITIGATION_STATUSES)[number]\n\nexport const EUDR_ACTIVITY_TYPES = ['import', 'export', 'domestic_production', 'trade'] as const\nexport type EudrActivityType = (typeof EUDR_ACTIVITY_TYPES)[number]\n\nexport const EUDR_ACTOR_ROLES = ['operator', 'non_sme_trader', 'sme_trader'] as const\nexport type EudrActorRole = (typeof EUDR_ACTOR_ROLES)[number]\n\nexport const GEOJSON_TYPES = ['Feature', 'FeatureCollection', 'Point', 'Polygon', 'MultiPolygon'] as const\n\nconst uuid = () => z.string().uuid()\nconst geoJsonSizeLimit = 1_048_576\nconst serverComputedSubmissionFields = ['completenessScore', 'missingFields'] as const\nconst serverComputedPlotFields = ['validationWarnings', 'computedArea'] as const\nconst serverComputedRiskAssessmentFields = ['countryRisks', 'overallTier', 'isSimplified', 'assessedByName'] as const\nconst serverComputedMitigationActionFields = ['completedAt'] as const\nconst serverComputedStatementFields = ['submittedAt'] as const\nconst riskCriteriaKeySet = new Set(EUDR_RISK_CRITERIA_KEYS)\n\nconst productSnapshotSchema = z.object({\n name: z.string().max(500).optional().nullable(),\n sku: z.string().max(255).optional().nullable(),\n})\nconst supplierSnapshotSchema = z.object({\n displayName: z.string().max(500).optional().nullable(),\n})\nconst orderSnapshotSchema = z.object({\n orderNumber: z.string().max(255).optional().nullable(),\n})\n\nfunction rejectServerComputedFields(fields: readonly string[]): z.ZodType<unknown> {\n return z.unknown().superRefine((input, context) => {\n if (input === null || typeof input !== 'object' || Array.isArray(input)) return\n const record = input as Record<string, unknown>\n for (const field of fields) {\n if (!(field in record)) continue\n context.addIssue({\n code: z.ZodIssueCode.custom,\n path: [field],\n message: 'eudr.errors.serverComputedField',\n })\n }\n })\n}\n\nfunction requiredUnknown(message: string): z.ZodType<unknown> {\n return z.unknown().refine((value) => value !== undefined, { message })\n}\n\nfunction isIsoDate(value: string): boolean {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value)\n if (match === null) return false\n const year = Number(match[1])\n const month = Number(match[2])\n const day = Number(match[3])\n const date = new Date(Date.UTC(year, month - 1, day))\n return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day\n}\n\n// Route mapInput parses these schemas first (string -> Date) and commands parse\n// the mapped input again, so both schemas must also accept Date instances.\nconst isoDateSchema = () => z.union([\n z.date(),\n z\n .string()\n .refine(isIsoDate, { message: 'eudr.errors.invalidDate' })\n .transform((value) => new Date(`${value}T00:00:00.000Z`)),\n])\n\nconst isoDateTimeSchema = () => z.union([\n z.date(),\n z\n .string()\n .datetime({ message: 'eudr.errors.invalidDateTime' })\n .transform((value) => new Date(value)),\n])\n\nconst pastOrNowIsoDateTimeSchema = () => isoDateTimeSchema().refine((value) => value.getTime() <= Date.now(), {\n message: 'eudr.errors.assessedAtInFuture',\n})\n\nconst isoDateOrDateTimeSchema = () => z.union([isoDateSchema(), isoDateTimeSchema()])\n\n// Country codes are validated through ICU rather than the shared country helper.\n// That helper imports language-subtag-registry's JSON, and the build strips the\n// import attribute Node's ESM loader requires, so importing it from server-side\n// code (CLI, workers) fails with ERR_IMPORT_ATTRIBUTE_MISSING. UI components can\n// keep using the helper because bundlers resolve the JSON themselves.\nconst countryDisplayNames = typeof Intl !== 'undefined' && typeof Intl.DisplayNames !== 'undefined'\n ? new Intl.DisplayNames(['en'], { type: 'region' })\n : null\n\nfunction isIsoCountryCode(value: string): boolean {\n // Without ICU data there is nothing to check against, so accept the shape.\n if (!countryDisplayNames) return true\n try {\n // Intl echoes the input back when it cannot resolve the region.\n const label = countryDisplayNames.of(value)\n return typeof label === 'string' && label !== value\n } catch {\n return false\n }\n}\n\nconst countryCodeSchema = () => z\n .string()\n .regex(/^[A-Za-z]{2}$/)\n .transform((value) => value.toUpperCase())\n .refine(isIsoCountryCode, { message: 'eudr.errors.invalidCountry' })\n\nconst referencedStatementCodeSchema = (maxLength: number) => z\n .string()\n .trim()\n .min(1)\n .max(maxLength)\n .regex(/^[A-Za-z0-9._/-]+$/, { message: 'eudr.errors.invalidReferenceIdentifier' })\n .transform((value) => value.toUpperCase())\n\nexport const referencedStatementSchema = z.object({\n referenceNumber: referencedStatementCodeSchema(32),\n verificationNumber: referencedStatementCodeSchema(16).optional(),\n})\n\nexport const geoJsonSchema = z\n .object({\n type: z.enum(GEOJSON_TYPES),\n })\n .passthrough()\n .superRefine((value, context) => {\n try {\n const serialized = JSON.stringify(value)\n if (serialized.length <= geoJsonSizeLimit) return\n } catch {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'eudr.errors.geolocationTooLarge',\n })\n return\n }\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'eudr.errors.geolocationTooLarge',\n })\n })\n\nexport const productMappingCreateSchema = z.object({\n productId: uuid(),\n productSnapshot: productSnapshotSchema.optional().nullable(),\n commodity: z.enum(EUDR_COMMODITIES),\n hsCode: z.string().max(20).optional().nullable(),\n speciesScientificName: z.string().trim().max(256).optional().nullable(),\n speciesCommonName: z.string().trim().max(256).optional().nullable(),\n isInScope: z.boolean().optional(),\n notes: z.string().max(5000).optional().nullable(),\n})\n\nexport const productMappingUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(productMappingCreateSchema.partial())\n\nconst plotBaseSchema = z.object({\n supplierEntityId: uuid(),\n supplierSnapshot: supplierSnapshotSchema.optional().nullable(),\n name: z.string().min(1).max(200),\n externalId: z.string().max(100).optional().nullable(),\n description: z.string().max(2000).optional().nullable(),\n originCountry: countryCodeSchema(),\n geometry: requiredUnknown('eudr.errors.geometryRequired'),\n areaHa: z.coerce.number().positive().max(100_000).optional().nullable(),\n producerName: z.string().max(500).optional().nullable(),\n isActive: z.boolean().optional(),\n})\n\nexport const plotCreateSchema = rejectServerComputedFields(serverComputedPlotFields).pipe(plotBaseSchema)\n\nexport const plotUpdateSchema = rejectServerComputedFields(serverComputedPlotFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(plotBaseSchema.partial()),\n)\n\nconst evidenceSubmissionBaseSchema = z.object({\n supplierEntityId: uuid(),\n supplierSnapshot: supplierSnapshotSchema.optional().nullable(),\n commodity: z.enum(EUDR_COMMODITIES),\n productMappingId: uuid().optional().nullable(),\n statementId: uuid().optional().nullable(),\n originCountry: countryCodeSchema().optional().nullable(),\n geolocation: geoJsonSchema.optional().nullable(),\n quantityKg: z.coerce.number().positive().optional().nullable(),\n batchNumber: z.string().max(255).optional().nullable(),\n harvestFrom: z.coerce.date().optional().nullable(),\n harvestTo: z.coerce.date().optional().nullable(),\n producerName: z.string().max(500).optional().nullable(),\n attachmentIds: z.array(uuid()).max(100).optional(),\n plotIds: z.array(uuid()).max(200).optional(),\n status: z.enum(EUDR_SUBMISSION_STATUSES).optional(),\n notes: z.string().max(5000).optional().nullable(),\n})\n\nfunction validateHarvestRange(input: { harvestFrom?: Date | null; harvestTo?: Date | null }, context: z.RefinementCtx): void {\n if (!input.harvestFrom || !input.harvestTo || input.harvestFrom <= input.harvestTo) return\n context.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['harvestTo'],\n message: 'eudr.errors.harvestRange',\n })\n}\n\nexport const evidenceSubmissionCreateSchema = rejectServerComputedFields(serverComputedSubmissionFields).pipe(\n evidenceSubmissionBaseSchema.superRefine(validateHarvestRange),\n)\n\nexport const evidenceSubmissionUpdateSchema = rejectServerComputedFields(serverComputedSubmissionFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(evidenceSubmissionBaseSchema.partial())\n .superRefine(validateHarvestRange),\n)\n\nconst riskCriteriaEntrySchema = z.object({\n answer: z.enum(EUDR_CRITERIA_ANSWERS),\n note: z.string().max(2000).optional().nullable(),\n})\n\nconst riskCriteriaSchema = z.record(z.string(), riskCriteriaEntrySchema).superRefine((criteria, context) => {\n for (const key of Object.keys(criteria)) {\n if (riskCriteriaKeySet.has(key)) continue\n context.addIssue({\n code: z.ZodIssueCode.custom,\n path: [key],\n message: 'eudr.errors.invalidRiskCriterion',\n })\n }\n})\n\nconst riskAssessmentBaseSchema = z.object({\n statementId: uuid(),\n criteria: riskCriteriaSchema,\n conclusion: z.enum(EUDR_RISK_CONCLUSIONS),\n reviewDueAt: isoDateOrDateTimeSchema().optional().nullable(),\n assessedAt: pastOrNowIsoDateTimeSchema().optional(),\n notes: z.string().max(4000).optional().nullable(),\n})\n\nexport const riskAssessmentCreateSchema = rejectServerComputedFields(serverComputedRiskAssessmentFields).pipe(riskAssessmentBaseSchema)\n\nexport const riskAssessmentUpdateSchema = rejectServerComputedFields(serverComputedRiskAssessmentFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(riskAssessmentBaseSchema.partial()),\n)\n\nconst mitigationActionBaseSchema = z.object({\n riskAssessmentId: uuid(),\n actionType: z.enum(EUDR_MITIGATION_TYPES).optional(),\n title: z.string().min(1).max(200),\n description: z.string().max(2000).optional().nullable(),\n status: z.enum(EUDR_MITIGATION_STATUSES).optional(),\n dueDate: isoDateSchema().optional().nullable(),\n notes: z.string().max(2000).optional().nullable(),\n})\n\nexport const mitigationActionCreateSchema = rejectServerComputedFields(serverComputedMitigationActionFields).pipe(mitigationActionBaseSchema)\n\nexport const mitigationActionUpdateSchema = rejectServerComputedFields(serverComputedMitigationActionFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(mitigationActionBaseSchema.partial()),\n)\n\nconst statementBaseSchema = z.object({\n title: z.string().min(1).max(500),\n commodity: z.enum(EUDR_COMMODITIES),\n referenceNumber: z.string().max(255).optional().nullable(),\n verificationNumber: z.string().max(255).optional().nullable(),\n status: z.enum(EUDR_STATEMENT_STATUSES).optional(),\n activityType: z.enum(EUDR_ACTIVITY_TYPES).optional().nullable(),\n actorRole: z.enum(EUDR_ACTOR_ROLES).optional().nullable(),\n referencedStatements: z.array(referencedStatementSchema).max(100).optional(),\n quantityKg: z.coerce.number().positive().optional().nullable(),\n supplementaryUnit: z.string().max(16).optional().nullable(),\n supplementaryQuantity: z.coerce.number().positive().optional().nullable(),\n orderId: uuid().optional().nullable(),\n referenceIssuedAt: isoDateTimeSchema().optional().nullable(),\n orderSnapshot: orderSnapshotSchema.optional().nullable(),\n notes: z.string().max(5000).optional().nullable(),\n})\n\nexport const statementCreateSchema = rejectServerComputedFields(serverComputedStatementFields).pipe(statementBaseSchema)\n\nexport const statementUpdateSchema = rejectServerComputedFields(serverComputedStatementFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(statementBaseSchema.partial()),\n)\n\nexport type ProductMappingCreateInput = z.infer<typeof productMappingCreateSchema>\nexport type ProductMappingUpdateInput = z.infer<typeof productMappingUpdateSchema>\nexport type PlotCreateInput = z.infer<typeof plotCreateSchema>\nexport type PlotUpdateInput = z.infer<typeof plotUpdateSchema>\nexport type EvidenceSubmissionCreateInput = z.infer<typeof evidenceSubmissionCreateSchema>\nexport type EvidenceSubmissionUpdateInput = z.infer<typeof evidenceSubmissionUpdateSchema>\nexport type RiskAssessmentCreateInput = z.infer<typeof riskAssessmentCreateSchema>\nexport type RiskAssessmentUpdateInput = z.infer<typeof riskAssessmentUpdateSchema>\nexport type MitigationActionCreateInput = z.infer<typeof mitigationActionCreateSchema>\nexport type MitigationActionUpdateInput = z.infer<typeof mitigationActionUpdateSchema>\nexport type StatementCreateInput = z.infer<typeof statementCreateSchema>\nexport type StatementUpdateInput = z.infer<typeof statementUpdateSchema>\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,+BAA+B;AAEjC,MAAM,mBAAmB,CAAC,UAAU,SAAS,UAAU,YAAY,UAAU,QAAQ,MAAM;AAG3F,MAAM,2BAA2B,CAAC,SAAS,aAAa,YAAY,UAAU;AAG9E,MAAM,0BAA0B,CAAC,SAAS,aAAa,aAAa,aAAa,UAAU;AAG3F,MAAM,kBAAkB,CAAC,SAAS,SAAS;AAG3C,MAAM,kBAAkB,CAAC,OAAO,YAAY,QAAQ,SAAS,SAAS;AAGtE,MAAM,wBAAwB,CAAC,cAAc,gBAAgB;AAG7D,MAAM,wBAAwB,CAAC,cAAc,WAAW,gBAAgB;AAGxE,MAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,MAAM,2BAA2B,CAAC,WAAW,eAAe,aAAa,WAAW;AAGpF,MAAM,sBAAsB,CAAC,UAAU,UAAU,uBAAuB,OAAO;AAG/E,MAAM,mBAAmB,CAAC,YAAY,kBAAkB,YAAY;AAGpE,MAAM,gBAAgB,CAAC,WAAW,qBAAqB,SAAS,WAAW,cAAc;AAEhG,MAAM,OAAO,MAAM,EAAE,OAAO,EAAE,KAAK;AACnC,MAAM,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\n\nimport { EUDR_RISK_CRITERIA_KEYS } from '../lib/reference-data'\n\nexport const EUDR_COMMODITIES = ['cattle', 'cocoa', 'coffee', 'oil_palm', 'rubber', 'soya', 'wood'] as const\nexport type EudrCommodity = (typeof EUDR_COMMODITIES)[number]\n\nexport const EUDR_SUBMISSION_STATUSES = ['draft', 'submitted', 'verified', 'rejected'] as const\nexport type EudrSubmissionStatus = (typeof EUDR_SUBMISSION_STATUSES)[number]\n\nexport const EUDR_STATEMENT_STATUSES = ['draft', 'submitted', 'available', 'withdrawn', 'archived'] as const\nexport type EudrStatementStatus = (typeof EUDR_STATEMENT_STATUSES)[number]\n\nexport const EUDR_PLOT_TYPES = ['point', 'polygon'] as const\nexport type EudrPlotType = (typeof EUDR_PLOT_TYPES)[number]\n\nexport const EUDR_RISK_TIERS = ['low', 'standard', 'high', 'mixed', 'unknown'] as const\nexport type EudrRiskTier = (typeof EUDR_RISK_TIERS)[number]\n\nexport const EUDR_RISK_CONCLUSIONS = ['negligible', 'non_negligible'] as const\nexport type EudrRiskConclusion = (typeof EUDR_RISK_CONCLUSIONS)[number]\n\nexport const EUDR_CRITERIA_ANSWERS = ['no_concern', 'concern', 'not_applicable'] as const\nexport type EudrCriteriaAnswer = (typeof EUDR_CRITERIA_ANSWERS)[number]\n\nexport const EUDR_MITIGATION_TYPES = [\n 'request_documents',\n 'supplier_audit',\n 'satellite_verification',\n 'certification_check',\n 'switch_sourcing',\n 'other',\n] as const\nexport type EudrMitigationType = (typeof EUDR_MITIGATION_TYPES)[number]\n\nexport const EUDR_MITIGATION_STATUSES = ['planned', 'in_progress', 'completed', 'cancelled'] as const\nexport type EudrMitigationStatus = (typeof EUDR_MITIGATION_STATUSES)[number]\n\nexport const EUDR_ACTIVITY_TYPES = ['import', 'export', 'domestic_production', 'trade'] as const\nexport type EudrActivityType = (typeof EUDR_ACTIVITY_TYPES)[number]\n\nexport const EUDR_ACTOR_ROLES = ['operator', 'non_sme_trader', 'sme_trader'] as const\nexport type EudrActorRole = (typeof EUDR_ACTOR_ROLES)[number]\n\nexport const GEOJSON_TYPES = ['Feature', 'FeatureCollection', 'Point', 'Polygon', 'MultiPolygon'] as const\n\nconst uuid = () => z.string().uuid()\nconst geoJsonSizeLimit = 1_048_576\n// Two conventions for server-managed fields on these entities: fields listed below\n// are never advertised as create inputs, so a client sending a real value is rejected\n// (`rejectServerComputedFields` tolerates a null/undefined no-op echo). A field that\n// WAS advertised in the create schema \u2014 statements' `referenceIssuedAt` \u2014 is instead\n// dropped from the create schema, so it is stripped rather than rejected (#5508).\nconst serverComputedSubmissionFields = ['completenessScore', 'missingFields'] as const\nconst serverComputedPlotFields = ['validationWarnings', 'computedArea'] as const\nconst serverComputedRiskAssessmentFields = ['countryRisks', 'overallTier', 'isSimplified', 'assessedByName'] as const\nconst serverComputedMitigationActionFields = ['completedAt'] as const\nconst serverComputedStatementFields = ['submittedAt'] as const\nconst riskCriteriaKeySet = new Set(EUDR_RISK_CRITERIA_KEYS)\n\nconst productSnapshotSchema = z.object({\n name: z.string().max(500).optional().nullable(),\n sku: z.string().max(255).optional().nullable(),\n})\nconst supplierSnapshotSchema = z.object({\n displayName: z.string().max(500).optional().nullable(),\n})\nconst orderSnapshotSchema = z.object({\n orderNumber: z.string().max(255).optional().nullable(),\n})\n\n// A whole-object POST echoes server-computed fields back with their no-op value\n// (a fresh draft's `submittedAt` is null), so tolerate a null/undefined echo and\n// reject only a meaningful attempt to set server-owned state (#5508).\nfunction rejectServerComputedFields(fields: readonly string[]): z.ZodType<unknown> {\n return z.unknown().superRefine((input, context) => {\n if (input === null || typeof input !== 'object' || Array.isArray(input)) return\n const record = input as Record<string, unknown>\n for (const field of fields) {\n if (!(field in record)) continue\n if (record[field] === null || record[field] === undefined) continue\n context.addIssue({\n code: z.ZodIssueCode.custom,\n path: [field],\n message: 'eudr.errors.serverComputedField',\n })\n }\n })\n}\n\nfunction requiredUnknown(message: string): z.ZodType<unknown> {\n return z.unknown().refine((value) => value !== undefined, { message })\n}\n\nfunction isIsoDate(value: string): boolean {\n const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(value)\n if (match === null) return false\n const year = Number(match[1])\n const month = Number(match[2])\n const day = Number(match[3])\n const date = new Date(Date.UTC(year, month - 1, day))\n return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day\n}\n\n// Route mapInput parses these schemas first (string -> Date) and commands parse\n// the mapped input again, so both schemas must also accept Date instances.\nconst isoDateSchema = () => z.union([\n z.date(),\n z\n .string()\n .refine(isIsoDate, { message: 'eudr.errors.invalidDate' })\n .transform((value) => new Date(`${value}T00:00:00.000Z`)),\n])\n\nconst isoDateTimeSchema = () => z.union([\n z.date(),\n z\n .string()\n .datetime({ message: 'eudr.errors.invalidDateTime' })\n .transform((value) => new Date(value)),\n])\n\nconst pastOrNowIsoDateTimeSchema = () => isoDateTimeSchema().refine((value) => value.getTime() <= Date.now(), {\n message: 'eudr.errors.assessedAtInFuture',\n})\n\nconst isoDateOrDateTimeSchema = () => z.union([isoDateSchema(), isoDateTimeSchema()])\n\n// Country codes are validated through ICU rather than the shared country helper.\n// That helper imports language-subtag-registry's JSON, and the build strips the\n// import attribute Node's ESM loader requires, so importing it from server-side\n// code (CLI, workers) fails with ERR_IMPORT_ATTRIBUTE_MISSING. UI components can\n// keep using the helper because bundlers resolve the JSON themselves.\nconst countryDisplayNames = typeof Intl !== 'undefined' && typeof Intl.DisplayNames !== 'undefined'\n ? new Intl.DisplayNames(['en'], { type: 'region' })\n : null\n\nfunction isIsoCountryCode(value: string): boolean {\n // Without ICU data there is nothing to check against, so accept the shape.\n if (!countryDisplayNames) return true\n try {\n // Intl echoes the input back when it cannot resolve the region.\n const label = countryDisplayNames.of(value)\n return typeof label === 'string' && label !== value\n } catch {\n return false\n }\n}\n\nconst countryCodeSchema = () => z\n .string()\n .regex(/^[A-Za-z]{2}$/)\n .transform((value) => value.toUpperCase())\n .refine(isIsoCountryCode, { message: 'eudr.errors.invalidCountry' })\n\nconst referencedStatementCodeSchema = (maxLength: number) => z\n .string()\n .trim()\n .min(1)\n .max(maxLength)\n .regex(/^[A-Za-z0-9._/-]+$/, { message: 'eudr.errors.invalidReferenceIdentifier' })\n .transform((value) => value.toUpperCase())\n\nexport const referencedStatementSchema = z.object({\n referenceNumber: referencedStatementCodeSchema(32),\n verificationNumber: referencedStatementCodeSchema(16).optional(),\n})\n\nexport const geoJsonSchema = z\n .object({\n type: z.enum(GEOJSON_TYPES),\n })\n .passthrough()\n .superRefine((value, context) => {\n try {\n const serialized = JSON.stringify(value)\n if (serialized.length <= geoJsonSizeLimit) return\n } catch {\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'eudr.errors.geolocationTooLarge',\n })\n return\n }\n context.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'eudr.errors.geolocationTooLarge',\n })\n })\n\nexport const productMappingCreateSchema = z.object({\n productId: uuid(),\n productSnapshot: productSnapshotSchema.optional().nullable(),\n commodity: z.enum(EUDR_COMMODITIES),\n hsCode: z.string().max(20).optional().nullable(),\n speciesScientificName: z.string().trim().max(256).optional().nullable(),\n speciesCommonName: z.string().trim().max(256).optional().nullable(),\n isInScope: z.boolean().optional(),\n notes: z.string().max(5000).optional().nullable(),\n})\n\nexport const productMappingUpdateSchema = z\n .object({\n id: uuid(),\n })\n .merge(productMappingCreateSchema.partial())\n\nconst plotBaseSchema = z.object({\n supplierEntityId: uuid(),\n supplierSnapshot: supplierSnapshotSchema.optional().nullable(),\n name: z.string().min(1).max(200),\n externalId: z.string().max(100).optional().nullable(),\n description: z.string().max(2000).optional().nullable(),\n originCountry: countryCodeSchema(),\n geometry: requiredUnknown('eudr.errors.geometryRequired'),\n areaHa: z.coerce.number().positive().max(100_000).optional().nullable(),\n producerName: z.string().max(500).optional().nullable(),\n isActive: z.boolean().optional(),\n})\n\nexport const plotCreateSchema = rejectServerComputedFields(serverComputedPlotFields).pipe(plotBaseSchema)\n\nexport const plotUpdateSchema = rejectServerComputedFields(serverComputedPlotFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(plotBaseSchema.partial()),\n)\n\nconst evidenceSubmissionBaseSchema = z.object({\n supplierEntityId: uuid(),\n supplierSnapshot: supplierSnapshotSchema.optional().nullable(),\n commodity: z.enum(EUDR_COMMODITIES),\n productMappingId: uuid().optional().nullable(),\n statementId: uuid().optional().nullable(),\n originCountry: countryCodeSchema().optional().nullable(),\n geolocation: geoJsonSchema.optional().nullable(),\n quantityKg: z.coerce.number().positive().optional().nullable(),\n batchNumber: z.string().max(255).optional().nullable(),\n harvestFrom: z.coerce.date().optional().nullable(),\n harvestTo: z.coerce.date().optional().nullable(),\n producerName: z.string().max(500).optional().nullable(),\n attachmentIds: z.array(uuid()).max(100).optional(),\n plotIds: z.array(uuid()).max(200).optional(),\n status: z.enum(EUDR_SUBMISSION_STATUSES).optional(),\n notes: z.string().max(5000).optional().nullable(),\n})\n\nfunction validateHarvestRange(input: { harvestFrom?: Date | null; harvestTo?: Date | null }, context: z.RefinementCtx): void {\n if (!input.harvestFrom || !input.harvestTo || input.harvestFrom <= input.harvestTo) return\n context.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['harvestTo'],\n message: 'eudr.errors.harvestRange',\n })\n}\n\nexport const evidenceSubmissionCreateSchema = rejectServerComputedFields(serverComputedSubmissionFields).pipe(\n evidenceSubmissionBaseSchema.superRefine(validateHarvestRange),\n)\n\nexport const evidenceSubmissionUpdateSchema = rejectServerComputedFields(serverComputedSubmissionFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(evidenceSubmissionBaseSchema.partial())\n .superRefine(validateHarvestRange),\n)\n\nconst riskCriteriaEntrySchema = z.object({\n answer: z.enum(EUDR_CRITERIA_ANSWERS),\n note: z.string().max(2000).optional().nullable(),\n})\n\nconst riskCriteriaSchema = z.record(z.string(), riskCriteriaEntrySchema).superRefine((criteria, context) => {\n for (const key of Object.keys(criteria)) {\n if (riskCriteriaKeySet.has(key)) continue\n context.addIssue({\n code: z.ZodIssueCode.custom,\n path: [key],\n message: 'eudr.errors.invalidRiskCriterion',\n })\n }\n})\n\nconst riskAssessmentBaseSchema = z.object({\n statementId: uuid(),\n criteria: riskCriteriaSchema,\n conclusion: z.enum(EUDR_RISK_CONCLUSIONS),\n reviewDueAt: isoDateOrDateTimeSchema().optional().nullable(),\n assessedAt: pastOrNowIsoDateTimeSchema().optional(),\n notes: z.string().max(4000).optional().nullable(),\n})\n\nexport const riskAssessmentCreateSchema = rejectServerComputedFields(serverComputedRiskAssessmentFields).pipe(riskAssessmentBaseSchema)\n\nexport const riskAssessmentUpdateSchema = rejectServerComputedFields(serverComputedRiskAssessmentFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(riskAssessmentBaseSchema.partial()),\n)\n\nconst mitigationActionBaseSchema = z.object({\n riskAssessmentId: uuid(),\n actionType: z.enum(EUDR_MITIGATION_TYPES).optional(),\n title: z.string().min(1).max(200),\n description: z.string().max(2000).optional().nullable(),\n status: z.enum(EUDR_MITIGATION_STATUSES).optional(),\n dueDate: isoDateSchema().optional().nullable(),\n notes: z.string().max(2000).optional().nullable(),\n})\n\nexport const mitigationActionCreateSchema = rejectServerComputedFields(serverComputedMitigationActionFields).pipe(mitigationActionBaseSchema)\n\nexport const mitigationActionUpdateSchema = rejectServerComputedFields(serverComputedMitigationActionFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(mitigationActionBaseSchema.partial()),\n)\n\nconst statementBaseSchema = z.object({\n title: z.string().min(1).max(500),\n commodity: z.enum(EUDR_COMMODITIES),\n referenceNumber: z.string().max(255).optional().nullable(),\n verificationNumber: z.string().max(255).optional().nullable(),\n status: z.enum(EUDR_STATEMENT_STATUSES).optional(),\n activityType: z.enum(EUDR_ACTIVITY_TYPES).optional().nullable(),\n actorRole: z.enum(EUDR_ACTOR_ROLES).optional().nullable(),\n referencedStatements: z.array(referencedStatementSchema).max(100).optional(),\n quantityKg: z.coerce.number().positive().optional().nullable(),\n supplementaryUnit: z.string().max(16).optional().nullable(),\n supplementaryQuantity: z.coerce.number().positive().optional().nullable(),\n orderId: uuid().optional().nullable(),\n referenceIssuedAt: isoDateTimeSchema().optional().nullable(),\n orderSnapshot: orderSnapshotSchema.optional().nullable(),\n notes: z.string().max(5000).optional().nullable(),\n})\n\n// referenceIssuedAt is server-managed (only set at the submitted->available\n// transition) and is never a create input, so it is dropped from the create\n// schema. A whole-object POST that echoes it (null or a value) is accepted and\n// the field ignored, instead of a self-contradictory 400 (#5508).\nexport const statementCreateSchema = rejectServerComputedFields(serverComputedStatementFields).pipe(\n statementBaseSchema.omit({ referenceIssuedAt: true }),\n)\n\nexport const statementUpdateSchema = rejectServerComputedFields(serverComputedStatementFields).pipe(\n z\n .object({\n id: uuid(),\n })\n .merge(statementBaseSchema.partial()),\n)\n\nexport type ProductMappingCreateInput = z.infer<typeof productMappingCreateSchema>\nexport type ProductMappingUpdateInput = z.infer<typeof productMappingUpdateSchema>\nexport type PlotCreateInput = z.infer<typeof plotCreateSchema>\nexport type PlotUpdateInput = z.infer<typeof plotUpdateSchema>\nexport type EvidenceSubmissionCreateInput = z.infer<typeof evidenceSubmissionCreateSchema>\nexport type EvidenceSubmissionUpdateInput = z.infer<typeof evidenceSubmissionUpdateSchema>\nexport type RiskAssessmentCreateInput = z.infer<typeof riskAssessmentCreateSchema>\nexport type RiskAssessmentUpdateInput = z.infer<typeof riskAssessmentUpdateSchema>\nexport type MitigationActionCreateInput = z.infer<typeof mitigationActionCreateSchema>\nexport type MitigationActionUpdateInput = z.infer<typeof mitigationActionUpdateSchema>\nexport type StatementCreateInput = z.infer<typeof statementCreateSchema>\nexport type StatementUpdateInput = z.infer<typeof statementUpdateSchema>\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -31,12 +31,12 @@ function parseRequestedIds(raw) {
|
|
|
31
31
|
return [...deduped].slice(0, MAX_ASSIGNEE_LOOKUP_IDS);
|
|
32
32
|
}
|
|
33
33
|
async function GET(req) {
|
|
34
|
+
const { translate } = await resolveTranslations();
|
|
34
35
|
try {
|
|
35
36
|
const url = new URL(req.url);
|
|
36
37
|
const parsedQuery = querySchema.parse(Object.fromEntries(url.searchParams));
|
|
37
38
|
const container = await createRequestContainer();
|
|
38
39
|
const auth = await getAuthFromRequest(req);
|
|
39
|
-
const { translate } = await resolveTranslations();
|
|
40
40
|
if (!auth || !auth.tenantId) {
|
|
41
41
|
throw new CrudHttpError(401, { error: translate("warranty_claims.errors.unauthorized", "Unauthorized") });
|
|
42
42
|
}
|
|
@@ -63,10 +63,10 @@ async function GET(req) {
|
|
|
63
63
|
return NextResponse.json(error.body, { status: error.status });
|
|
64
64
|
}
|
|
65
65
|
if (error instanceof z.ZodError) {
|
|
66
|
-
return NextResponse.json({ error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
66
|
+
return NextResponse.json({ error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
67
67
|
}
|
|
68
68
|
logger.error("[warranty_claims] Failed to resolve assignee display names", { error });
|
|
69
|
-
return NextResponse.json({ error: "warranty_claims.errors.load_failed" }, { status: 500 });
|
|
69
|
+
return NextResponse.json({ error: translate("warranty_claims.errors.load_failed", "Failed to load warranty claim data.") }, { status: 500 });
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
const openApi = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/api/assignees/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveAssigneeDisplayNames } from '../../lib/assigneeNames'\n\nconst logger = createLogger('warranty_claims')\n\nexport const MAX_ASSIGNEE_LOOKUP_IDS = 100\n\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nconst querySchema = z.object({\n ids: z.string().min(1),\n}).strict()\n\nconst responseSchema = z.object({\n items: z.array(z.object({\n id: z.string(),\n name: z.string().nullable(),\n })),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n}\n\nexport function parseRequestedIds(raw: string): string[] {\n const deduped = new Set<string>()\n for (const value of parseCommaSeparatedList(raw)) {\n if (UUID_REGEX.test(value)) deduped.add(value)\n }\n return [...deduped].slice(0, MAX_ASSIGNEE_LOOKUP_IDS)\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const parsedQuery = querySchema.parse(Object.fromEntries(url.searchParams))\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAExC,SAAS,oBAAoB;AAC7B,SAAS,0CAA0C;AACnD,SAAS,mCAAmC;AAE5C,MAAM,SAAS,aAAa,iBAAiB;AAEtC,MAAM,0BAA0B;AAEvC,MAAM,aAAa;AAEnB,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC;AACvB,CAAC,EAAE,OAAO;AAEV,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,OAAO,EAAE,MAAM,EAAE,OAAO;AAAA,IACtB,IAAI,EAAE,OAAO;AAAA,IACb,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,CAAC,CAAC;AACJ,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,4BAA4B,EAAE;AAC5E;AAEO,SAAS,kBAAkB,KAAuB;AACvD,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,SAAS,wBAAwB,GAAG,GAAG;AAChD,QAAI,WAAW,KAAK,KAAK,EAAG,SAAQ,IAAI,KAAK;AAAA,EAC/C;AACA,SAAO,CAAC,GAAG,OAAO,EAAE,MAAM,GAAG,uBAAuB;AACtD;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,cAAc,YAAY,MAAM,OAAO,YAAY,IAAI,YAAY,CAAC;AAC1E,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveAssigneeDisplayNames } from '../../lib/assigneeNames'\n\nconst logger = createLogger('warranty_claims')\n\nexport const MAX_ASSIGNEE_LOOKUP_IDS = 100\n\nconst UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nconst querySchema = z.object({\n ids: z.string().min(1),\n}).strict()\n\nconst responseSchema = z.object({\n items: z.array(z.object({\n id: z.string(),\n name: z.string().nullable(),\n })),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['warranty_claims.claim.view'] },\n}\n\nexport function parseRequestedIds(raw: string): string[] {\n const deduped = new Set<string>()\n for (const value of parseCommaSeparatedList(raw)) {\n if (UUID_REGEX.test(value)) deduped.add(value)\n }\n return [...deduped].slice(0, MAX_ASSIGNEE_LOOKUP_IDS)\n}\n\nexport async function GET(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const url = new URL(req.url)\n const parsedQuery = querySchema.parse(Object.fromEntries(url.searchParams))\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope.tenantId ?? auth.tenantId\n if (!tenantId) {\n throw new CrudHttpError(401, { error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') })\n }\n\n const requestedIds = parseRequestedIds(parsedQuery.ids)\n if (!requestedIds.length) return NextResponse.json({ items: [] })\n\n const displayNames = await resolveAssigneeDisplayNames({\n container,\n tenantId,\n organizationId: scope.selectedId,\n organizationIds: scope.filterIds,\n }, requestedIds)\n const items = requestedIds.flatMap((id) => {\n const name = displayNames.get(id)\n return name ? [{ id, name }] : []\n })\n return NextResponse.json({ items })\n } catch (error) {\n if (isCrudHttpError(error)) {\n return NextResponse.json(error.body, { status: error.status })\n }\n if (error instanceof z.ZodError) {\n return NextResponse.json({ error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n logger.error('[warranty_claims] Failed to resolve assignee display names', { error })\n return NextResponse.json({ error: translate('warranty_claims.errors.load_failed', 'Failed to load warranty claim data.') }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims',\n summary: 'Warranty claim assignee display names',\n methods: {\n GET: {\n summary: 'Resolve assignee user ids to display names',\n description: `Resolves up to ${MAX_ASSIGNEE_LOOKUP_IDS} explicitly supplied user ids to their display name, scoped to the caller's tenant and selected or visible organizations. Requires \\`ids\\`; it cannot enumerate the user directory, and it returns neither role assignments nor organization membership.`,\n query: querySchema,\n responses: [\n {\n status: 200,\n description: 'Assignee display names',\n schema: responseSchema,\n },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -99,12 +99,13 @@ function serializePortalClaim(claim, lines) {
|
|
|
99
99
|
};
|
|
100
100
|
}
|
|
101
101
|
async function resolvePortalContext(req) {
|
|
102
|
+
const { translate } = await resolveTranslations();
|
|
102
103
|
const auth = await getCustomerAuthFromRequest(req);
|
|
103
104
|
if (!auth) {
|
|
104
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.unauthorized" }, { status: 401 });
|
|
105
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.unauthorized", "Unauthorized") }, { status: 401 });
|
|
105
106
|
}
|
|
106
107
|
if (!auth.customerEntityId) {
|
|
107
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.customerAccountNotLinked" }, { status: 403 });
|
|
108
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.customerAccountNotLinked", "Customer account is not linked to a customer record") }, { status: 403 });
|
|
108
109
|
}
|
|
109
110
|
const container = await createRequestContainer();
|
|
110
111
|
const em = container.resolve("em");
|
|
@@ -161,6 +162,7 @@ async function loadOwnedOrderLines(context, orderLineIds) {
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
async function validatePortalOrderOwnership(context, input) {
|
|
165
|
+
const { translate } = await resolveTranslations();
|
|
164
166
|
const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])));
|
|
165
167
|
const orderLinesById = await loadOwnedOrderLines(context, orderLineIds);
|
|
166
168
|
const orderIds = /* @__PURE__ */ new Set();
|
|
@@ -170,7 +172,7 @@ async function validatePortalOrderOwnership(context, input) {
|
|
|
170
172
|
let resolvedOrder = input.orderId ? ordersById.get(input.orderId) ?? null : null;
|
|
171
173
|
if (input.orderId) {
|
|
172
174
|
if (!resolvedOrder) {
|
|
173
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.orderNotOwned" }, { status: 404 });
|
|
175
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.orderNotOwned", "Order not found.") }, { status: 404 });
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
for (const line of input.lines) {
|
|
@@ -178,10 +180,9 @@ async function validatePortalOrderOwnership(context, input) {
|
|
|
178
180
|
const resolvedLine = orderLinesById.get(line.orderLineId);
|
|
179
181
|
const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null;
|
|
180
182
|
if (!resolvedLine || !lineOrder) {
|
|
181
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.orderLineMismatch" }, { status: 404 });
|
|
183
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.orderLineMismatch", "Order line does not belong to the selected order") }, { status: 404 });
|
|
182
184
|
}
|
|
183
185
|
if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {
|
|
184
|
-
const { translate } = await resolveTranslations();
|
|
185
186
|
return NextResponse.json(
|
|
186
187
|
{ ok: false, error: translate("warranty_claims.errors.orderLineMismatch", "Order line does not belong to the selected order") },
|
|
187
188
|
{ status: 400 }
|
|
@@ -226,7 +227,8 @@ async function GET(req) {
|
|
|
226
227
|
serialNumber: url.searchParams.get("serialNumber") ?? void 0
|
|
227
228
|
});
|
|
228
229
|
if (!query.success) {
|
|
229
|
-
|
|
230
|
+
const { translate } = await resolveTranslations();
|
|
231
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
230
232
|
}
|
|
231
233
|
const { page, pageSize, search, status, stateGroup, serialNumber } = query.data;
|
|
232
234
|
const where = buildPortalOwnedClaimWhere(context);
|
|
@@ -301,15 +303,16 @@ async function POST(req) {
|
|
|
301
303
|
const contextOrResponse = await resolvePortalContext(req);
|
|
302
304
|
if (contextOrResponse instanceof Response) return contextOrResponse;
|
|
303
305
|
const context = contextOrResponse;
|
|
306
|
+
const { translate } = await resolveTranslations();
|
|
304
307
|
let body;
|
|
305
308
|
try {
|
|
306
309
|
body = await req.json();
|
|
307
310
|
} catch {
|
|
308
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
311
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
309
312
|
}
|
|
310
313
|
const parsed = portalIntakeInputSchema.safeParse(body);
|
|
311
314
|
if (!parsed.success) {
|
|
312
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.invalidInput" }, { status: 400 });
|
|
315
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.invalidInput", "Invalid input") }, { status: 400 });
|
|
313
316
|
}
|
|
314
317
|
const ownership = await validatePortalOrderOwnership(context, parsed.data);
|
|
315
318
|
if (ownership instanceof Response) return ownership;
|
|
@@ -368,7 +371,7 @@ async function POST(req) {
|
|
|
368
371
|
);
|
|
369
372
|
const claimId = createResult.result?.claimId;
|
|
370
373
|
if (typeof claimId !== "string") {
|
|
371
|
-
return NextResponse.json({ ok: false, error: "warranty_claims.errors.save_failed" }, { status: 500 });
|
|
374
|
+
return NextResponse.json({ ok: false, error: translate("warranty_claims.errors.save_failed", "Failed to save warranty claim.") }, { status: 500 });
|
|
372
375
|
}
|
|
373
376
|
try {
|
|
374
377
|
await commandBus.execute(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/warranty_claims/api/portal/claims/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'\nimport {\n claimStatusSchema,\n portalIntakeInputSchema,\n type ClaimCreateInput,\n type PortalIntakeInput,\n} from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { portalClaimStatusesForStateGroup } from '../../../lib/listSegments'\nimport { buildPortalOwnedClaimWhere } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst logger = createLogger('warranty_claims')\n\nconst emptyQueryValueToUndefined = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nconst optionalTrimmedQueryString = (max: number) =>\n z.preprocess(emptyQueryValueToUndefined, z.string().trim().min(1).max(max).optional())\n\nconst listQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n search: optionalTrimmedQueryString(190),\n status: z.preprocess(emptyQueryValueToUndefined, claimStatusSchema.optional()),\n stateGroup: z.enum(['open', 'resolved']).optional(),\n serialNumber: optionalTrimmedQueryString(191),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\ntype LineSummary = {\n count: number\n creditTotal: string\n statuses: Record<string, number>\n}\n\ntype OrderValidationResult = {\n orderId: string | null\n currencyCode: string | null\n}\n\ntype PortalOrderLookup = {\n id: string\n currencyCode: string | null\n}\n\ntype PortalOrderLineLookup = {\n id: string\n orderId: string\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction stringField(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' ? value : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nconst CREDIT_SCALE = 10_000n\n\nfunction decimalUnits(value: unknown): bigint {\n const normalized = typeof value === 'number'\n ? (Number.isFinite(value) ? value.toFixed(4) : '')\n : typeof value === 'string' ? value.trim() : ''\n const match = /^([+-]?)(\\d+)(?:\\.(\\d+))?$/.exec(normalized)\n if (!match) return 0n\n const [, sign, whole, fraction = ''] = match\n let units = BigInt(whole) * CREDIT_SCALE + BigInt(fraction.slice(0, 4).padEnd(4, '0'))\n if (fraction.length > 4 && fraction[4] >= '5') units += 1n\n return sign === '-' ? -units : units\n}\n\nfunction decimalString(value: bigint): string {\n const sign = value < 0n ? '-' : ''\n const absolute = value < 0n ? -value : value\n const whole = absolute / CREDIT_SCALE\n const fraction = String(absolute % CREDIT_SCALE).padStart(4, '0').replace(/0+$/, '')\n return `${sign}${whole}${fraction ? `.${fraction}` : ''}`\n}\n\nfunction summarizeLines(lines: WarrantyClaimLine[]): LineSummary {\n let creditTotal = 0n\n const statuses: Record<string, number> = {}\n for (const line of lines) {\n creditTotal += decimalUnits(line.creditAmount)\n statuses[line.lineStatus] = (statuses[line.lineStatus] ?? 0) + 1\n }\n return {\n count: lines.length,\n creditTotal: decimalString(creditTotal),\n statuses,\n }\n}\n\nfunction serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: summarizeLines(lines),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.unauthorized' }, { status: 401 })\n }\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.customerAccountNotLinked' }, { status: 403 })\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\ntype PortalSalesDb = {\n sales_orders: {\n id: string\n currency_code: string\n customer_entity_id: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n sales_order_lines: {\n id: string\n order_id: string\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n}\n\nfunction isMissingSalesTableError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false\n const candidate = err as { code?: unknown; message?: unknown }\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string'\n && candidate.message.includes('does not exist')\n && (candidate.message.includes('sales_orders') || candidate.message.includes('sales_order_lines')))\n}\n\nasync function loadOwnedOrders(\n context: PortalContext,\n orderIds: string[],\n): Promise<Map<string, PortalOrderLookup>> {\n if (orderIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_orders')\n .select(['id', 'currency_code'])\n .where('id', 'in', orderIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('customer_entity_id', '=', context.customerId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, currencyCode: row.currency_code ?? null }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function loadOwnedOrderLines(\n context: PortalContext,\n orderLineIds: string[],\n): Promise<Map<string, PortalOrderLineLookup>> {\n if (orderLineIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_order_lines')\n .select(['id', 'order_id'])\n .where('id', 'in', orderLineIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, orderId: row.order_id }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function validatePortalOrderOwnership(\n context: PortalContext,\n input: PortalIntakeInput,\n): Promise<OrderValidationResult | Response> {\n const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])))\n const orderLinesById = await loadOwnedOrderLines(context, orderLineIds)\n const orderIds = new Set<string>()\n if (input.orderId) orderIds.add(input.orderId)\n for (const orderLine of orderLinesById.values()) orderIds.add(orderLine.orderId)\n const ordersById = await loadOwnedOrders(context, Array.from(orderIds))\n let resolvedOrder = input.orderId ? (ordersById.get(input.orderId) ?? null) : null\n if (input.orderId) {\n if (!resolvedOrder) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.orderNotOwned' }, { status: 404 })\n }\n }\n\n for (const line of input.lines) {\n if (!line.orderLineId) continue\n const resolvedLine = orderLinesById.get(line.orderLineId)\n const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null\n if (!resolvedLine || !lineOrder) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.orderLineMismatch' }, { status: 404 })\n }\n if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') },\n { status: 400 },\n )\n }\n if (!resolvedOrder) resolvedOrder = lineOrder\n }\n\n return {\n orderId: resolvedOrder?.id ?? input.orderId ?? null,\n currencyCode: resolvedOrder?.currencyCode ?? null,\n }\n}\n\nasync function runPortalCreateGuard(\n req: Request,\n context: PortalContext,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: null,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const query = listQuerySchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n stateGroup: url.searchParams.get('stateGroup') ?? undefined,\n serialNumber: url.searchParams.get('serialNumber') ?? undefined,\n })\n if (!query.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const { page, pageSize, search, status, stateGroup, serialNumber } = query.data\n const where = buildPortalOwnedClaimWhere(context)\n if (status) where.status = status\n else if (stateGroup) where.status = { $in: portalClaimStatusesForStateGroup(stateGroup) }\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n where.$or = [\n { claimNumber: { $ilike: pattern } },\n { orderNumber: { $ilike: pattern } },\n ]\n }\n if (serialNumber) {\n const matchingLines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n serialNumber,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n claim: buildPortalOwnedClaimWhere(context),\n },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const matchingClaimIds = Array.from(new Set(\n matchingLines.map((line) => relationId(line.claim)).filter((id): id is string => Boolean(id)),\n ))\n if (matchingClaimIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 1 })\n }\n where.id = { $in: matchingClaimIds }\n }\n const total = await context.em.count(WarrantyClaim, where)\n const claims = await findWithDecryption(\n context.em,\n WarrantyClaim,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: pageSize,\n offset: (page - 1) * pageSize,\n },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const claimIds = claims.map((claim) => claim.id)\n const lines = claimIds.length\n ? await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: { $in: claimIds }, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n : []\n const linesByClaim = new Map<string, WarrantyClaimLine[]>()\n for (const line of lines) {\n const claimId = relationId(line.claim)\n if (!claimId) continue\n const bucket = linesByClaim.get(claimId) ?? []\n bucket.push(line)\n linesByClaim.set(claimId, bucket)\n }\n return NextResponse.json({\n items: claims.map((claim) => serializePortalClaim(claim, linesByClaim.get(claim.id) ?? [])),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const parsed = portalIntakeInputSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.invalidInput' }, { status: 400 })\n }\n const ownership = await validatePortalOrderOwnership(context, parsed.data)\n if (ownership instanceof Response) return ownership\n\n const createInput: ClaimCreateInput = {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n claimType: 'warranty',\n channel: 'portal',\n priority: 'normal',\n customerId: context.customerId,\n customerName: context.auth.displayName || null,\n orderId: ownership.orderId,\n // A resolved order supplies its own number; otherwise persist the typed reference (WQA-002).\n orderNumber: ownership.orderId ? null : (parsed.data.orderReference ?? null),\n reasonCode: parsed.data.reasonCode,\n notes: parsed.data.notes ?? null,\n currencyCode: ownership.currencyCode,\n lines: parsed.data.lines.map((line, index) => ({\n lineNo: index + 1,\n orderLineId: line.orderLineId ?? null,\n productId: line.productId ?? null,\n productName: line.productName ?? null,\n sku: line.sku ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription,\n qtyClaimed: line.qtyClaimed ?? 1,\n // Carry warranty metadata so order-backed portal lines keep their entitlement basis\n // instead of persisting warrantyStatus='unknown' (WQA-004 / LINE-06).\n purchaseDate: line.purchaseDate ?? null,\n warrantyMonths: line.warrantyMonths ?? null,\n })),\n }\n\n const guarded = await runPortalCreateGuard(req, context, { ...createInput })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: ClaimCreateInput = guarded.modifiedPayload\n ? {\n ...createInput,\n ...guarded.modifiedPayload,\n tenantId: createInput.tenantId,\n organizationId: createInput.organizationId,\n customerId: createInput.customerId,\n channel: createInput.channel,\n claimType: createInput.claimType,\n orderId: createInput.orderId,\n orderNumber: createInput.orderNumber,\n currencyCode: createInput.currencyCode,\n lines: createInput.lines,\n }\n : createInput\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n const createResult = await commandBus.execute<ClaimCreateInput, { claimId: string }>(\n 'warranty_claims.claim.create',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n const claimId = createResult.result?.claimId\n if (typeof claimId !== 'string') {\n return NextResponse.json({ ok: false, error: 'warranty_claims.errors.save_failed' }, { status: 500 })\n }\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claimId, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (submitError) {\n try {\n await commandBus.execute<{ id: string }, { claimId: string }>(\n 'warranty_claims.claim.delete',\n { input: { id: claimId }, ctx: context.commandCtx },\n )\n } catch (compensationError) {\n logger.error('warranty_claims.portal.claim.submit compensation failed \u2014 orphaned draft claim', {\n err: compensationError,\n claimId,\n customerId: context.customerId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n throw submitError\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n}\n\nconst lineSummarySchema = z.object({\n count: z.number().int().nonnegative(),\n creditTotal: z.string(),\n statuses: z.record(z.string(), z.number().int().nonnegative()),\n})\n\nconst portalClaimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: lineSummarySchema,\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claims',\n methods: {\n GET: {\n summary: 'List the authenticated customer account claims',\n query: listQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer claims',\n schema: z.object({\n items: z.array(portalClaimSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n POST: {\n summary: 'Submit a customer portal claim intake',\n requestBody: { contentType: 'application/json', schema: portalIntakeInputSchema },\n responses: [\n {\n status: 201,\n description: 'Claim created and submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { runRouteMutationGuards, type RouteMutationGuardResult } from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getCustomerAuthFromRequest, type CustomerAuthContext } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { WarrantyClaim, WarrantyClaimLine } from '../../../data/entities'\nimport {\n claimStatusSchema,\n portalIntakeInputSchema,\n type ClaimCreateInput,\n type PortalIntakeInput,\n} from '../../../data/validators'\nimport { WARRANTY_CLAIM_RESOURCE_KIND } from '../../../commands/shared'\nimport { portalClaimStatusesForStateGroup } from '../../../lib/listSegments'\nimport { buildPortalOwnedClaimWhere } from '../../../lib/portalClaimAccess'\n\nexport const metadata = {\n GET: { requireAuth: false },\n POST: { requireAuth: false },\n}\n\nconst logger = createLogger('warranty_claims')\n\nconst emptyQueryValueToUndefined = (value: unknown): unknown => {\n if (typeof value !== 'string') return value\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nconst optionalTrimmedQueryString = (max: number) =>\n z.preprocess(emptyQueryValueToUndefined, z.string().trim().min(1).max(max).optional())\n\nconst listQuerySchema = z.object({\n page: z.coerce.number().int().min(1).default(1),\n pageSize: z.coerce.number().int().min(1).max(100).default(20),\n search: optionalTrimmedQueryString(190),\n status: z.preprocess(emptyQueryValueToUndefined, claimStatusSchema.optional()),\n stateGroup: z.enum(['open', 'resolved']).optional(),\n serialNumber: optionalTrimmedQueryString(191),\n})\n\ntype PortalContext = {\n auth: CustomerAuthContext\n customerId: string\n tenantId: string\n organizationId: string\n em: EntityManager\n commandCtx: CommandRuntimeContext\n}\n\ntype LineSummary = {\n count: number\n creditTotal: string\n statuses: Record<string, number>\n}\n\ntype OrderValidationResult = {\n orderId: string | null\n currencyCode: string | null\n}\n\ntype PortalOrderLookup = {\n id: string\n currencyCode: string | null\n}\n\ntype PortalOrderLineLookup = {\n id: string\n orderId: string\n}\n\nfunction toRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n}\n\nfunction stringField(record: Record<string, unknown>, key: string): string | null {\n const value = record[key]\n return typeof value === 'string' ? value : null\n}\n\nfunction toIso(value: Date | string | null | undefined): string | null {\n if (!value) return null\n if (value instanceof Date) return value.toISOString()\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toISOString()\n}\n\nfunction relationId(value: unknown): string | null {\n if (typeof value === 'string') return value\n const record = toRecord(value)\n return typeof record.id === 'string' ? record.id : null\n}\n\nconst CREDIT_SCALE = 10_000n\n\nfunction decimalUnits(value: unknown): bigint {\n const normalized = typeof value === 'number'\n ? (Number.isFinite(value) ? value.toFixed(4) : '')\n : typeof value === 'string' ? value.trim() : ''\n const match = /^([+-]?)(\\d+)(?:\\.(\\d+))?$/.exec(normalized)\n if (!match) return 0n\n const [, sign, whole, fraction = ''] = match\n let units = BigInt(whole) * CREDIT_SCALE + BigInt(fraction.slice(0, 4).padEnd(4, '0'))\n if (fraction.length > 4 && fraction[4] >= '5') units += 1n\n return sign === '-' ? -units : units\n}\n\nfunction decimalString(value: bigint): string {\n const sign = value < 0n ? '-' : ''\n const absolute = value < 0n ? -value : value\n const whole = absolute / CREDIT_SCALE\n const fraction = String(absolute % CREDIT_SCALE).padStart(4, '0').replace(/0+$/, '')\n return `${sign}${whole}${fraction ? `.${fraction}` : ''}`\n}\n\nfunction summarizeLines(lines: WarrantyClaimLine[]): LineSummary {\n let creditTotal = 0n\n const statuses: Record<string, number> = {}\n for (const line of lines) {\n creditTotal += decimalUnits(line.creditAmount)\n statuses[line.lineStatus] = (statuses[line.lineStatus] ?? 0) + 1\n }\n return {\n count: lines.length,\n creditTotal: decimalString(creditTotal),\n statuses,\n }\n}\n\nfunction serializePortalClaim(claim: WarrantyClaim, lines: WarrantyClaimLine[]) {\n return {\n id: claim.id,\n claimNumber: claim.claimNumber,\n claimType: claim.claimType,\n status: claim.status,\n orderId: claim.orderId ?? null,\n orderNumber: claim.orderNumber ?? null,\n reasonCode: claim.reasonCode ?? null,\n resolutionSummary: claim.resolutionSummary ?? null,\n createdAt: toIso(claim.createdAt),\n updatedAt: toIso(claim.updatedAt),\n lines: summarizeLines(lines),\n }\n}\n\nasync function resolvePortalContext(req: Request): Promise<PortalContext | Response> {\n const { translate } = await resolveTranslations()\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n }\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.customerAccountNotLinked', 'Customer account is not linked to a customer record') }, { status: 403 })\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const commandAuth: NonNullable<AuthContext> = {\n sub: auth.sub,\n sid: auth.sid,\n tenantId: auth.tenantId,\n orgId: auth.orgId,\n email: auth.email,\n customerEntityId: auth.customerEntityId ?? null,\n personEntityId: auth.personEntityId ?? null,\n }\n return {\n auth,\n customerId: auth.customerEntityId,\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n em,\n commandCtx: {\n container,\n auth: commandAuth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId,\n organizationIds: [auth.orgId],\n request: req,\n },\n }\n}\n\ntype PortalSalesDb = {\n sales_orders: {\n id: string\n currency_code: string\n customer_entity_id: string | null\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n sales_order_lines: {\n id: string\n order_id: string\n tenant_id: string | null\n organization_id: string | null\n deleted_at: Date | null\n }\n}\n\nfunction isMissingSalesTableError(err: unknown): boolean {\n if (typeof err !== 'object' || err === null) return false\n const candidate = err as { code?: unknown; message?: unknown }\n return candidate.code === '42P01'\n || (typeof candidate.message === 'string'\n && candidate.message.includes('does not exist')\n && (candidate.message.includes('sales_orders') || candidate.message.includes('sales_order_lines')))\n}\n\nasync function loadOwnedOrders(\n context: PortalContext,\n orderIds: string[],\n): Promise<Map<string, PortalOrderLookup>> {\n if (orderIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_orders')\n .select(['id', 'currency_code'])\n .where('id', 'in', orderIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('customer_entity_id', '=', context.customerId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, currencyCode: row.currency_code ?? null }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function loadOwnedOrderLines(\n context: PortalContext,\n orderLineIds: string[],\n): Promise<Map<string, PortalOrderLineLookup>> {\n if (orderLineIds.length === 0) return new Map()\n try {\n const db = context.em.fork().getKysely<PortalSalesDb>()\n const rows = await db\n .selectFrom('sales_order_lines')\n .select(['id', 'order_id'])\n .where('id', 'in', orderLineIds)\n .where('tenant_id', '=', context.tenantId)\n .where('organization_id', '=', context.organizationId)\n .where('deleted_at', 'is', null)\n .execute()\n return new Map(rows.map((row) => [row.id, { id: row.id, orderId: row.order_id }]))\n } catch (err) {\n if (isMissingSalesTableError(err)) return new Map()\n throw err\n }\n}\n\nasync function validatePortalOrderOwnership(\n context: PortalContext,\n input: PortalIntakeInput,\n): Promise<OrderValidationResult | Response> {\n const { translate } = await resolveTranslations()\n const orderLineIds = Array.from(new Set(input.lines.flatMap((line) => line.orderLineId ? [line.orderLineId] : [])))\n const orderLinesById = await loadOwnedOrderLines(context, orderLineIds)\n const orderIds = new Set<string>()\n if (input.orderId) orderIds.add(input.orderId)\n for (const orderLine of orderLinesById.values()) orderIds.add(orderLine.orderId)\n const ordersById = await loadOwnedOrders(context, Array.from(orderIds))\n let resolvedOrder = input.orderId ? (ordersById.get(input.orderId) ?? null) : null\n if (input.orderId) {\n if (!resolvedOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderNotOwned', 'Order not found.') }, { status: 404 })\n }\n }\n\n for (const line of input.lines) {\n if (!line.orderLineId) continue\n const resolvedLine = orderLinesById.get(line.orderLineId)\n const lineOrder = resolvedLine ? ordersById.get(resolvedLine.orderId) : null\n if (!resolvedLine || !lineOrder) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') }, { status: 404 })\n }\n if (resolvedOrder && lineOrder.id !== resolvedOrder.id) {\n return NextResponse.json(\n { ok: false, error: translate('warranty_claims.errors.orderLineMismatch', 'Order line does not belong to the selected order') },\n { status: 400 },\n )\n }\n if (!resolvedOrder) resolvedOrder = lineOrder\n }\n\n return {\n orderId: resolvedOrder?.id ?? input.orderId ?? null,\n currencyCode: resolvedOrder?.currencyCode ?? null,\n }\n}\n\nasync function runPortalCreateGuard(\n req: Request,\n context: PortalContext,\n mutationPayload: Record<string, unknown>,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: context.commandCtx.container,\n req,\n auth: {\n userId: context.auth.sub,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userFeatures: [],\n },\n input: {\n resourceKind: WARRANTY_CLAIM_RESOURCE_KIND,\n resourceId: null,\n operation: 'create',\n mutationPayload,\n },\n })\n}\n\nexport async function GET(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const url = new URL(req.url)\n const query = listQuerySchema.safeParse({\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n status: url.searchParams.get('status') ?? undefined,\n stateGroup: url.searchParams.get('stateGroup') ?? undefined,\n serialNumber: url.searchParams.get('serialNumber') ?? undefined,\n })\n if (!query.success) {\n const { translate } = await resolveTranslations()\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const { page, pageSize, search, status, stateGroup, serialNumber } = query.data\n const where = buildPortalOwnedClaimWhere(context)\n if (status) where.status = status\n else if (stateGroup) where.status = { $in: portalClaimStatusesForStateGroup(stateGroup) }\n if (search) {\n const pattern = `%${escapeLikePattern(search)}%`\n where.$or = [\n { claimNumber: { $ilike: pattern } },\n { orderNumber: { $ilike: pattern } },\n ]\n }\n if (serialNumber) {\n const matchingLines = await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n {\n serialNumber,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n deletedAt: null,\n claim: buildPortalOwnedClaimWhere(context),\n },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const matchingClaimIds = Array.from(new Set(\n matchingLines.map((line) => relationId(line.claim)).filter((id): id is string => Boolean(id)),\n ))\n if (matchingClaimIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, page, pageSize, totalPages: 1 })\n }\n where.id = { $in: matchingClaimIds }\n }\n const total = await context.em.count(WarrantyClaim, where)\n const claims = await findWithDecryption(\n context.em,\n WarrantyClaim,\n where,\n {\n orderBy: { createdAt: 'DESC' },\n limit: pageSize,\n offset: (page - 1) * pageSize,\n },\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n const claimIds = claims.map((claim) => claim.id)\n const lines = claimIds.length\n ? await findWithDecryption(\n context.em,\n WarrantyClaimLine,\n { claim: { $in: claimIds }, tenantId: context.tenantId, organizationId: context.organizationId, deletedAt: null },\n {},\n { tenantId: context.tenantId, organizationId: context.organizationId },\n )\n : []\n const linesByClaim = new Map<string, WarrantyClaimLine[]>()\n for (const line of lines) {\n const claimId = relationId(line.claim)\n if (!claimId) continue\n const bucket = linesByClaim.get(claimId) ?? []\n bucket.push(line)\n linesByClaim.set(claimId, bucket)\n }\n return NextResponse.json({\n items: claims.map((claim) => serializePortalClaim(claim, linesByClaim.get(claim.id) ?? [])),\n total,\n page,\n pageSize,\n totalPages: Math.max(1, Math.ceil(total / pageSize)),\n })\n}\n\nexport async function POST(req: Request) {\n const contextOrResponse = await resolvePortalContext(req)\n if (contextOrResponse instanceof Response) return contextOrResponse\n const context = contextOrResponse\n const { translate } = await resolveTranslations()\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const parsed = portalIntakeInputSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.invalidInput', 'Invalid input') }, { status: 400 })\n }\n const ownership = await validatePortalOrderOwnership(context, parsed.data)\n if (ownership instanceof Response) return ownership\n\n const createInput: ClaimCreateInput = {\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n claimType: 'warranty',\n channel: 'portal',\n priority: 'normal',\n customerId: context.customerId,\n customerName: context.auth.displayName || null,\n orderId: ownership.orderId,\n // A resolved order supplies its own number; otherwise persist the typed reference (WQA-002).\n orderNumber: ownership.orderId ? null : (parsed.data.orderReference ?? null),\n reasonCode: parsed.data.reasonCode,\n notes: parsed.data.notes ?? null,\n currencyCode: ownership.currencyCode,\n lines: parsed.data.lines.map((line, index) => ({\n lineNo: index + 1,\n orderLineId: line.orderLineId ?? null,\n productId: line.productId ?? null,\n productName: line.productName ?? null,\n sku: line.sku ?? null,\n serialNumber: line.serialNumber ?? null,\n faultCode: line.faultCode ?? null,\n faultDescription: line.faultDescription,\n qtyClaimed: line.qtyClaimed ?? 1,\n // Carry warranty metadata so order-backed portal lines keep their entitlement basis\n // instead of persisting warrantyStatus='unknown' (WQA-004 / LINE-06).\n purchaseDate: line.purchaseDate ?? null,\n warrantyMonths: line.warrantyMonths ?? null,\n })),\n }\n\n const guarded = await runPortalCreateGuard(req, context, { ...createInput })\n if (!guarded.ok) {\n return guarded.response\n }\n const effectiveInput: ClaimCreateInput = guarded.modifiedPayload\n ? {\n ...createInput,\n ...guarded.modifiedPayload,\n tenantId: createInput.tenantId,\n organizationId: createInput.organizationId,\n customerId: createInput.customerId,\n channel: createInput.channel,\n claimType: createInput.claimType,\n orderId: createInput.orderId,\n orderNumber: createInput.orderNumber,\n currencyCode: createInput.currencyCode,\n lines: createInput.lines,\n }\n : createInput\n\n const commandBus = context.commandCtx.container.resolve('commandBus') as CommandBus\n try {\n const createResult = await commandBus.execute<ClaimCreateInput, { claimId: string }>(\n 'warranty_claims.claim.create',\n { input: effectiveInput, ctx: context.commandCtx },\n )\n const claimId = createResult.result?.claimId\n if (typeof claimId !== 'string') {\n return NextResponse.json({ ok: false, error: translate('warranty_claims.errors.save_failed', 'Failed to save warranty claim.') }, { status: 500 })\n }\n try {\n await commandBus.execute<{ id: string; actorCustomerId: string }, { claimId: string }>(\n 'warranty_claims.claim.submit',\n { input: { id: claimId, actorCustomerId: context.customerId }, ctx: context.commandCtx },\n )\n } catch (submitError) {\n try {\n await commandBus.execute<{ id: string }, { claimId: string }>(\n 'warranty_claims.claim.delete',\n { input: { id: claimId }, ctx: context.commandCtx },\n )\n } catch (compensationError) {\n logger.error('warranty_claims.portal.claim.submit compensation failed \u2014 orphaned draft claim', {\n err: compensationError,\n claimId,\n customerId: context.customerId,\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n }\n throw submitError\n }\n\n await guarded.runAfterSuccess()\n\n return NextResponse.json({ ok: true, claimId }, { status: 201 })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n}\n\nconst lineSummarySchema = z.object({\n count: z.number().int().nonnegative(),\n creditTotal: z.string(),\n statuses: z.record(z.string(), z.number().int().nonnegative()),\n})\n\nconst portalClaimSchema = z.object({\n id: z.string().uuid(),\n claimNumber: z.string(),\n claimType: z.string(),\n status: z.string(),\n orderId: z.string().uuid().nullable(),\n orderNumber: z.string().nullable(),\n reasonCode: z.string().nullable(),\n resolutionSummary: z.string().nullable(),\n createdAt: z.string().nullable(),\n updatedAt: z.string().nullable(),\n lines: lineSummarySchema,\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Warranty Claims Portal',\n summary: 'Customer portal warranty claims',\n methods: {\n GET: {\n summary: 'List the authenticated customer account claims',\n query: listQuerySchema,\n responses: [\n {\n status: 200,\n description: 'Customer claims',\n schema: z.object({\n items: z.array(portalClaimSchema),\n total: z.number().int().nonnegative(),\n page: z.number().int().min(1),\n pageSize: z.number().int().min(1),\n totalPages: z.number().int().min(1),\n }),\n },\n ],\n },\n POST: {\n summary: 'Submit a customer portal claim intake',\n requestBody: { contentType: 'application/json', schema: portalIntakeInputSchema },\n responses: [\n {\n status: 201,\n description: 'Claim created and submitted',\n schema: z.object({ ok: z.boolean(), claimId: z.string().uuid() }),\n },\n ],\n },\n },\n}\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.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7057.1.61440fc3bc",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -252,16 +252,16 @@
|
|
|
252
252
|
"zod": "^4.4.3"
|
|
253
253
|
},
|
|
254
254
|
"peerDependencies": {
|
|
255
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
256
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
257
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
255
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7057.1.61440fc3bc",
|
|
256
|
+
"@open-mercato/shared": "0.6.8-develop.7057.1.61440fc3bc",
|
|
257
|
+
"@open-mercato/ui": "0.6.8-develop.7057.1.61440fc3bc",
|
|
258
258
|
"react": "^19.0.0",
|
|
259
259
|
"react-dom": "^19.0.0"
|
|
260
260
|
},
|
|
261
261
|
"devDependencies": {
|
|
262
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
263
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
264
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
262
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7057.1.61440fc3bc",
|
|
263
|
+
"@open-mercato/shared": "0.6.8-develop.7057.1.61440fc3bc",
|
|
264
|
+
"@open-mercato/ui": "0.6.8-develop.7057.1.61440fc3bc",
|
|
265
265
|
"@testing-library/dom": "^10.4.1",
|
|
266
266
|
"@testing-library/jest-dom": "^7.0.0",
|
|
267
267
|
"@testing-library/react": "^16.3.1",
|
|
@@ -3,9 +3,12 @@ import { z } from 'zod'
|
|
|
3
3
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
4
4
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
5
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
6
|
-
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
6
|
+
import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
|
|
7
7
|
import { CustomerEntity } from '../../../data/entities'
|
|
8
8
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
9
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
10
|
+
import { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'
|
|
11
|
+
import { MATCH_CANDIDATE_LIMIT } from '../../../lib/findPeopleByAddresses'
|
|
9
12
|
|
|
10
13
|
const querySchema = z.object({
|
|
11
14
|
digits: z
|
|
@@ -18,6 +21,10 @@ export const metadata = {
|
|
|
18
21
|
GET: { requireAuth: true, requireFeatures: ['customers.people.view'] },
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
function normalizePhoneDigits(value: string | null | undefined): string {
|
|
25
|
+
return typeof value === 'string' ? value.replace(/\D/g, '') : ''
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
export async function GET(req: Request) {
|
|
22
29
|
const url = new URL(req.url)
|
|
23
30
|
const rawQuery: Record<string, string | null> = { digits: url.searchParams.get('digits') }
|
|
@@ -45,18 +52,76 @@ export async function GET(req: Request) {
|
|
|
45
52
|
return NextResponse.json({ match: null })
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
// SQL fast path, gated on the encryption toggle: it can only match stored
|
|
56
|
+
// plaintext digits, so running it while tenant data encryption is enabled
|
|
57
|
+
// (the default) would pay a per-row scan that provably returns nothing.
|
|
58
|
+
// When encryption is off it serves the pre-encryption single-query behavior,
|
|
59
|
+
// including legacy plaintext rows. Encrypted rows written before the toggle
|
|
60
|
+
// was turned off are not resolvable by either path by design; plaintext
|
|
61
|
+
// legacy rows under an enabled toggle still resolve through the decrypted
|
|
62
|
+
// scan below, where decryption no-ops on non-ciphertext values.
|
|
63
|
+
if (!isTenantDataEncryptionEnabled()) {
|
|
64
|
+
const qb = em.createQueryBuilder(CustomerEntity, 'person')
|
|
65
|
+
qb.select(['person.id', 'person.displayName'])
|
|
66
|
+
qb.where({ kind: 'person', deletedAt: null })
|
|
67
|
+
qb.andWhere('person.primary_phone is not null')
|
|
68
|
+
qb.andWhere("regexp_replace(person.primary_phone, '\\D', '', 'g') = ?", [parse.data.digits])
|
|
69
|
+
if (auth.tenantId) {
|
|
70
|
+
qb.andWhere({ tenantId: auth.tenantId })
|
|
71
|
+
}
|
|
72
|
+
qb.andWhere({ organizationId: { $in: Array.from(allowedOrgIds) } })
|
|
73
|
+
qb.limit(1)
|
|
74
|
+
|
|
75
|
+
const fastMatch = await qb.getSingleResult()
|
|
76
|
+
if (fastMatch) {
|
|
77
|
+
return NextResponse.json({
|
|
78
|
+
match: {
|
|
79
|
+
id: fastMatch.id,
|
|
80
|
+
displayName: fastMatch.displayName,
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Encryption-on path: primary_phone holds random-IV ciphertext, so digit
|
|
87
|
+
// normalization must run in application code over decrypted rows instead of
|
|
88
|
+
// SQL against the stored column (issue #3840).
|
|
89
|
+
// Encryption-on path: primary_phone holds random-IV ciphertext, so digit
|
|
90
|
+
// normalization must run in application code over decrypted rows instead of
|
|
91
|
+
// SQL against the stored column (issue #3840). The scan is bounded newest-
|
|
92
|
+
// first and projected to the columns the route uses plus tenantId/
|
|
93
|
+
// organizationId, which the decryption path reads per row to resolve each
|
|
94
|
+
// row's own key (a request-level fallback cannot cover multi-org scans or
|
|
95
|
+
// null-tenant sessions). A primary_phone blind index is the exact O(1)
|
|
96
|
+
// follow-up (#5515).
|
|
97
|
+
const where: FilterQuery<CustomerEntity> = {
|
|
98
|
+
kind: 'person',
|
|
99
|
+
deletedAt: null,
|
|
100
|
+
primaryPhone: { $ne: null },
|
|
101
|
+
organizationId: { $in: Array.from(allowedOrgIds) },
|
|
102
|
+
}
|
|
53
103
|
if (auth.tenantId) {
|
|
54
|
-
|
|
104
|
+
where.tenantId = auth.tenantId
|
|
55
105
|
}
|
|
56
|
-
qb.andWhere({ organizationId: { $in: Array.from(allowedOrgIds) } })
|
|
57
|
-
qb.limit(1)
|
|
58
106
|
|
|
59
|
-
const
|
|
107
|
+
const candidates = await findWithDecryption(
|
|
108
|
+
em,
|
|
109
|
+
CustomerEntity,
|
|
110
|
+
where,
|
|
111
|
+
{
|
|
112
|
+
limit: MATCH_CANDIDATE_LIMIT,
|
|
113
|
+
orderBy: { createdAt: 'DESC' },
|
|
114
|
+
fields: ['id', 'displayName', 'primaryPhone', 'tenantId', 'organizationId'],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
tenantId: auth.tenantId ?? null,
|
|
118
|
+
organizationId: scope?.selectedId ?? auth.orgId ?? null,
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const match = candidates.find(
|
|
123
|
+
(candidate) => normalizePhoneDigits(candidate.primaryPhone) === parse.data.digits,
|
|
124
|
+
)
|
|
60
125
|
if (!match) {
|
|
61
126
|
return NextResponse.json({ match: null })
|
|
62
127
|
}
|
|
@@ -32,13 +32,15 @@ export interface MatchedPerson {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Max
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
35
|
+
* Max rows scanned by in-memory fallback matches over encrypted contact
|
|
36
|
+
* columns (`primary_email` here, `primary_phone` in the check-phone route).
|
|
37
|
+
* These columns are GDPR-encrypted with a random IV (see
|
|
38
|
+
* `customers/encryption.ts`), so they cannot be filtered by value in SQL when
|
|
39
|
+
* tenant data encryption is on — fallbacks decrypt recent rows and compare in
|
|
40
|
+
* memory instead. Bounded to keep interactive lookups cheap; a blind-index
|
|
41
|
+
* (hash) column per field is the follow-up if tenants outgrow it (#5515).
|
|
40
42
|
*/
|
|
41
|
-
const MATCH_CANDIDATE_LIMIT = 500
|
|
43
|
+
export const MATCH_CANDIDATE_LIMIT = 500
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* Batch lookup of CustomerEntity rows (kind='person') whose `primaryEmail`
|