@llm-dev-ops/agentics-cli 1.4.95 → 1.5.1

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.
Files changed (127) hide show
  1. package/dist/agents/system-prompts.d.ts.map +1 -1
  2. package/dist/agents/system-prompts.js +25 -3
  3. package/dist/agents/system-prompts.js.map +1 -1
  4. package/dist/commands/export.d.ts.map +1 -1
  5. package/dist/commands/export.js +20 -1
  6. package/dist/commands/export.js.map +1 -1
  7. package/dist/infrastructure/db-adapter.d.ts +67 -0
  8. package/dist/infrastructure/db-adapter.d.ts.map +1 -0
  9. package/dist/infrastructure/db-adapter.js +151 -0
  10. package/dist/infrastructure/db-adapter.js.map +1 -0
  11. package/dist/infrastructure/sap-adapter.d.ts +95 -0
  12. package/dist/infrastructure/sap-adapter.d.ts.map +1 -0
  13. package/dist/infrastructure/sap-adapter.js +651 -0
  14. package/dist/infrastructure/sap-adapter.js.map +1 -0
  15. package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.d.ts.map +1 -1
  16. package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.js +56 -9
  17. package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.js.map +1 -1
  18. package/dist/routing/capability-classifier.d.ts.map +1 -1
  19. package/dist/routing/capability-classifier.js +16 -1
  20. package/dist/routing/capability-classifier.js.map +1 -1
  21. package/dist/server/routes/erpsurface.d.ts +3 -1
  22. package/dist/server/routes/erpsurface.d.ts.map +1 -1
  23. package/dist/server/routes/erpsurface.js +548 -7
  24. package/dist/server/routes/erpsurface.js.map +1 -1
  25. package/dist/synthesis/simulation-artifact-generator.d.ts.map +1 -1
  26. package/dist/synthesis/simulation-artifact-generator.js +23 -5
  27. package/dist/synthesis/simulation-artifact-generator.js.map +1 -1
  28. package/docs/ecosystem.graph.json +5 -5
  29. package/package.json +1 -1
  30. package/dist/__tests__/api_gateway.test.d.ts +0 -1
  31. package/dist/__tests__/api_gateway.test.js +0 -50
  32. package/dist/__tests__/domain_enterprise_solution.test.d.ts +0 -1
  33. package/dist/__tests__/domain_enterprise_solution.test.js +0 -50
  34. package/dist/__tests__/health.test.d.ts +0 -1
  35. package/dist/__tests__/health.test.js +0 -19
  36. package/dist/__tests__/monitoring_alerts.test.d.ts +0 -1
  37. package/dist/__tests__/monitoring_alerts.test.js +0 -50
  38. package/dist/__tests__/ongoing_regular_stakeholder.test.d.ts +0 -1
  39. package/dist/__tests__/ongoing_regular_stakeholder.test.js +0 -50
  40. package/dist/__tests__/re_evaluate_risk.test.d.ts +0 -1
  41. package/dist/__tests__/re_evaluate_risk.test.js +0 -50
  42. package/dist/__tests__/recommendation.test.d.ts +0 -1
  43. package/dist/__tests__/recommendation.test.js +0 -50
  44. package/dist/__tests__/risk_mitigation_plan.test.d.ts +0 -1
  45. package/dist/__tests__/risk_mitigation_plan.test.js +0 -50
  46. package/dist/__tests__/scoped_pilot_reduce.test.d.ts +0 -1
  47. package/dist/__tests__/scoped_pilot_reduce.test.js +0 -50
  48. package/dist/__tests__/target_enterprise_system.test.d.ts +0 -1
  49. package/dist/__tests__/target_enterprise_system.test.js +0 -50
  50. package/dist/__tests__/underwriting.test.d.ts +0 -1
  51. package/dist/__tests__/underwriting.test.js +0 -50
  52. package/dist/api-gateway/infra/api-gateway-adapter.d.ts +0 -27
  53. package/dist/api-gateway/infra/api-gateway-adapter.js +0 -54
  54. package/dist/api-gateway/ports/api-gateway.d.ts +0 -26
  55. package/dist/api-gateway/ports/api-gateway.js +0 -2
  56. package/dist/contracts/contract-validation.d.ts +0 -11
  57. package/dist/contracts/contract-validation.js +0 -21
  58. package/dist/domain-enterprise-solution/infra/api-gateway-seam-adapter.d.ts +0 -21
  59. package/dist/domain-enterprise-solution/infra/api-gateway-seam-adapter.js +0 -42
  60. package/dist/domain-enterprise-solution/infra/domain-enterprise-solution-adapter.d.ts +0 -25
  61. package/dist/domain-enterprise-solution/infra/domain-enterprise-solution-adapter.js +0 -47
  62. package/dist/domain-enterprise-solution/ports/api-gateway-seam.d.ts +0 -20
  63. package/dist/domain-enterprise-solution/ports/api-gateway-seam.js +0 -2
  64. package/dist/domain-enterprise-solution/ports/domain-enterprise-solution.d.ts +0 -24
  65. package/dist/domain-enterprise-solution/ports/domain-enterprise-solution.js +0 -2
  66. package/dist/enterprise/index.d.ts +0 -15
  67. package/dist/enterprise/index.js +0 -16
  68. package/dist/erp-client/client.d.ts +0 -42
  69. package/dist/erp-client/client.js +0 -235
  70. package/dist/erp-client/mapper.d.ts +0 -9
  71. package/dist/erp-client/mapper.js +0 -116
  72. package/dist/erp-client/retry.d.ts +0 -17
  73. package/dist/erp-client/retry.js +0 -74
  74. package/dist/erp-client/types.d.ts +0 -155
  75. package/dist/erp-client/types.js +0 -2
  76. package/dist/infra/clients.d.ts +0 -27
  77. package/dist/infra/clients.js +0 -16
  78. package/dist/infra/connection-pool.d.ts +0 -16
  79. package/dist/infra/connection-pool.js +0 -13
  80. package/dist/infra/iam-helper.d.ts +0 -1
  81. package/dist/infra/iam-helper.js +0 -138
  82. package/dist/infra/telemetry.d.ts +0 -26
  83. package/dist/infra/telemetry.js +0 -39
  84. package/dist/monitoring-alerts/infra/monitoring-alerts-adapter.d.ts +0 -25
  85. package/dist/monitoring-alerts/infra/monitoring-alerts-adapter.js +0 -47
  86. package/dist/monitoring-alerts/ports/monitoring-alerts.d.ts +0 -24
  87. package/dist/monitoring-alerts/ports/monitoring-alerts.js +0 -2
  88. package/dist/ongoing-regular-stakeholder/infra/ongoing-regular-stakeholder-adapter.d.ts +0 -25
  89. package/dist/ongoing-regular-stakeholder/infra/ongoing-regular-stakeholder-adapter.js +0 -47
  90. package/dist/ongoing-regular-stakeholder/ports/ongoing-regular-stakeholder.d.ts +0 -24
  91. package/dist/ongoing-regular-stakeholder/ports/ongoing-regular-stakeholder.js +0 -2
  92. package/dist/re-evaluate-risk/infra/re-evaluate-risk-adapter.d.ts +0 -25
  93. package/dist/re-evaluate-risk/infra/re-evaluate-risk-adapter.js +0 -47
  94. package/dist/re-evaluate-risk/ports/re-evaluate-risk.d.ts +0 -24
  95. package/dist/re-evaluate-risk/ports/re-evaluate-risk.js +0 -2
  96. package/dist/recommendation/infra/recommendation-adapter.d.ts +0 -25
  97. package/dist/recommendation/infra/recommendation-adapter.js +0 -47
  98. package/dist/recommendation/ports/recommendation.d.ts +0 -24
  99. package/dist/recommendation/ports/recommendation.js +0 -2
  100. package/dist/risk-mitigation-plan/infra/risk-mitigation-plan-adapter.d.ts +0 -25
  101. package/dist/risk-mitigation-plan/infra/risk-mitigation-plan-adapter.js +0 -47
  102. package/dist/risk-mitigation-plan/ports/risk-mitigation-plan.d.ts +0 -24
  103. package/dist/risk-mitigation-plan/ports/risk-mitigation-plan.js +0 -2
  104. package/dist/scoped-pilot-reduce/infra/scoped-pilot-reduce-adapter.d.ts +0 -25
  105. package/dist/scoped-pilot-reduce/infra/scoped-pilot-reduce-adapter.js +0 -47
  106. package/dist/scoped-pilot-reduce/ports/scoped-pilot-reduce.d.ts +0 -24
  107. package/dist/scoped-pilot-reduce/ports/scoped-pilot-reduce.js +0 -2
  108. package/dist/server/dependencies.d.ts +0 -178
  109. package/dist/server/dependencies.js +0 -321
  110. package/dist/server/health.d.ts +0 -2
  111. package/dist/server/health.js +0 -9
  112. package/dist/server/main.d.ts +0 -1
  113. package/dist/server/main.js +0 -21
  114. package/dist/server/middleware.d.ts +0 -4
  115. package/dist/server/middleware.js +0 -106
  116. package/dist/server/routes.d.ts +0 -5
  117. package/dist/server/routes.js +0 -1100
  118. package/dist/server/schemas.d.ts +0 -217
  119. package/dist/server/schemas.js +0 -185
  120. package/dist/target-enterprise-system/infra/target-enterprise-system-adapter.d.ts +0 -25
  121. package/dist/target-enterprise-system/infra/target-enterprise-system-adapter.js +0 -47
  122. package/dist/target-enterprise-system/ports/target-enterprise-system.d.ts +0 -24
  123. package/dist/target-enterprise-system/ports/target-enterprise-system.js +0 -2
  124. package/dist/underwriting/infra/underwriting-adapter.d.ts +0 -25
  125. package/dist/underwriting/infra/underwriting-adapter.js +0 -47
  126. package/dist/underwriting/ports/underwriting.d.ts +0 -24
  127. package/dist/underwriting/ports/underwriting.js +0 -2
@@ -0,0 +1,651 @@
1
+ /**
2
+ * ERP Sustainability Adapters — infrastructure layer for multi-ERP integration.
3
+ *
4
+ * Implements the Anti-Corruption Layer pattern for 10 enterprise ERP systems.
5
+ * Each adapter translates between the ERP's native wire format and the
6
+ * internal domain model (MaterialSustainability, SupplierCompliance, etc.).
7
+ *
8
+ * Supported ERP systems:
9
+ * 1. SAP S/4HANA — OData + BAPI (MARA, EKPO, LIPS, QMEL, AFKO, CHVW, LFA1)
10
+ * 2. Oracle ERP Cloud — REST (Fusion Procurement, Quality)
11
+ * 3. Oracle NetSuite — SuiteQL + SuiteTalk REST (Items, Vendors, QA)
12
+ * 4. Microsoft Dynamics — Dataverse OData (Products, Vendors, Quality Orders)
13
+ * 5. Infor CloudSuite — ION API + Data Lake (Items, Suppliers, QC)
14
+ * 6. Epicor Kinetic — REST v2 (Parts, Suppliers, Inspections)
15
+ * 7. Sage X3 — REST (Products, BPs, Quality)
16
+ * 8. IFS Cloud — OData (InventoryParts, Suppliers, QualityPlans)
17
+ * 9. Workday — SOAP/REST (Spend, Suppliers, PRISM Analytics)
18
+ * 10. PeopleSoft — Component Interface (Items, Vendors, Quality)
19
+ *
20
+ * Domain model is ERP-agnostic — only the wire-format mappers change.
21
+ */
22
+ // ============================================================================
23
+ // 1. SAP S/4HANA
24
+ // ============================================================================
25
+ function sapBool(val) { return val === 'X' || val === true; }
26
+ function sapDate(val) {
27
+ const s = String(val ?? '');
28
+ return s.length === 8 ? `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}` : s;
29
+ }
30
+ const sapAdapter = {
31
+ erpId: 'sap-s4hana',
32
+ displayName: 'SAP S/4HANA',
33
+ mapMaterial(r) {
34
+ return {
35
+ materialNumber: String(r['MATNR'] ?? ''),
36
+ recycledPercent: Number(r['ZZRECYCLED_PCT'] ?? 0),
37
+ organicCertified: sapBool(r['ZZORGANIC_FLAG']),
38
+ sustainabilityCertification: r['ZZSUSTAIN_CERT'] || null,
39
+ };
40
+ },
41
+ mapVendor(r) {
42
+ const tierMap = {
43
+ 'GOLD': 'gold', 'SILVER': 'silver', 'BRONZE': 'bronze',
44
+ };
45
+ const statusMap = {
46
+ 'COMPLIANT': 'compliant', 'CONDITIONAL': 'conditional', 'NON_COMPLIANT': 'non-compliant',
47
+ };
48
+ return {
49
+ vendorId: String(r['LIFNR'] ?? ''),
50
+ sustainabilityTier: tierMap[String(r['ZZSUSTAIN_TIER'] ?? '')] ?? 'uncertified',
51
+ lastAuditDate: sapDate(r['ZZAUDIT_DATE']),
52
+ complianceStatus: statusMap[String(r['ZZCOMPLIANCE_STATUS'] ?? '')] ?? 'non-compliant',
53
+ };
54
+ },
55
+ mapQuality(r) {
56
+ const reachMap = {
57
+ 'PASSED': 'passed', 'FAILED': 'failed', 'PENDING': 'pending',
58
+ };
59
+ const items = (r['items'] ?? []);
60
+ return {
61
+ notificationId: String(r['QMNUM'] ?? ''),
62
+ dyeCompliance: sapBool(r['ZZDYE_COMPLIANCE']),
63
+ reachStatus: reachMap[String(r['ZZREACH_STATUS'] ?? '')] ?? 'pending',
64
+ oekoTexClass: r['ZZOEKOTEX_CLASS'] || null,
65
+ testResults: items.map((item) => ({
66
+ chemicalId: String(item['ZZCHEMICAL_ID'] ?? ''),
67
+ result: Number(item['ZZTEST_RESULT'] ?? 0),
68
+ thresholdPassed: sapBool(item['ZZTHRESHOLD_PASS']),
69
+ })),
70
+ };
71
+ },
72
+ mapBatch(r) {
73
+ return {
74
+ batchId: String(r['CHARG'] ?? ''),
75
+ materialNumber: String(r['MATNR'] ?? ''),
76
+ dyeLot: String(r['ZZDYE_LOT'] ?? ''),
77
+ originCountry: String(r['ZZORIGIN_COUNTRY'] ?? ''),
78
+ fiberBlend: String(r['ZZFIBER_BLEND'] ?? ''),
79
+ carbonKg: Number(r['ZZCARBON_KG'] ?? 0),
80
+ waterLiters: Number(r['ZZWATER_LITERS'] ?? 0),
81
+ energyKwh: Number(r['ZZENERGY_KWH'] ?? 0),
82
+ };
83
+ },
84
+ };
85
+ // ============================================================================
86
+ // 2. Oracle ERP Cloud (Fusion)
87
+ // ============================================================================
88
+ const oracleErpAdapter = {
89
+ erpId: 'oracle-erp',
90
+ displayName: 'Oracle ERP Cloud',
91
+ mapMaterial(r) {
92
+ return {
93
+ materialNumber: String(r['ItemNumber'] ?? ''),
94
+ recycledPercent: Number(r['RecycledContentPct'] ?? 0),
95
+ organicCertified: r['OrganicCertified'] === true || r['OrganicCertified'] === 'Y',
96
+ sustainabilityCertification: r['SustainabilityCertCode'] || null,
97
+ };
98
+ },
99
+ mapVendor(r) {
100
+ return {
101
+ vendorId: String(r['SupplierId'] ?? ''),
102
+ sustainabilityTier: normalizeTier(r['SustainabilityRating']),
103
+ lastAuditDate: normalizeDate(r['LastAuditDate']),
104
+ complianceStatus: normalizeStatus(r['ComplianceStatus']),
105
+ };
106
+ },
107
+ mapQuality(r) {
108
+ const inspections = (r['InspectionResults'] ?? []);
109
+ return {
110
+ notificationId: String(r['QualityIssueId'] ?? ''),
111
+ dyeCompliance: r['DyeComplianceFlag'] === 'Y' || r['DyeComplianceFlag'] === true,
112
+ reachStatus: normalizeReach(r['REACHStatus']),
113
+ oekoTexClass: r['OEKOTEXClassification'] || null,
114
+ testResults: inspections.map((i) => ({
115
+ chemicalId: String(i['ChemicalSubstanceId'] ?? ''),
116
+ result: Number(i['TestResult'] ?? 0),
117
+ thresholdPassed: i['WithinThreshold'] === 'Y' || i['WithinThreshold'] === true,
118
+ })),
119
+ };
120
+ },
121
+ mapBatch(r) {
122
+ return {
123
+ batchId: String(r['LotNumber'] ?? ''),
124
+ materialNumber: String(r['ItemNumber'] ?? ''),
125
+ dyeLot: String(r['DyeLotReference'] ?? ''),
126
+ originCountry: String(r['CountryOfOrigin'] ?? ''),
127
+ fiberBlend: String(r['FiberComposition'] ?? ''),
128
+ carbonKg: Number(r['CarbonFootprintKg'] ?? 0),
129
+ waterLiters: Number(r['WaterUsageLiters'] ?? 0),
130
+ energyKwh: Number(r['EnergyConsumptionKwh'] ?? 0),
131
+ };
132
+ },
133
+ };
134
+ // ============================================================================
135
+ // 3. Oracle NetSuite
136
+ // ============================================================================
137
+ const netsuiteAdapter = {
138
+ erpId: 'oracle-netsuite',
139
+ displayName: 'Oracle NetSuite',
140
+ mapMaterial(r) {
141
+ return {
142
+ materialNumber: String(r['itemId'] ?? r['name'] ?? ''),
143
+ recycledPercent: Number(r['custitem_recycled_pct'] ?? 0),
144
+ organicCertified: r['custitem_organic'] === true || r['custitem_organic'] === 'T',
145
+ sustainabilityCertification: r['custitem_sustain_cert'] || null,
146
+ };
147
+ },
148
+ mapVendor(r) {
149
+ return {
150
+ vendorId: String(r['entityId'] ?? ''),
151
+ sustainabilityTier: normalizeTier(r['custentity_sustain_tier']),
152
+ lastAuditDate: normalizeDate(r['custentity_audit_date']),
153
+ complianceStatus: normalizeStatus(r['custentity_compliance']),
154
+ };
155
+ },
156
+ mapQuality(r) {
157
+ const lines = (r['custrecord_qa_lines'] ?? []);
158
+ return {
159
+ notificationId: String(r['custrecord_qa_id'] ?? ''),
160
+ dyeCompliance: r['custrecord_dye_pass'] === true || r['custrecord_dye_pass'] === 'T',
161
+ reachStatus: normalizeReach(r['custrecord_reach']),
162
+ oekoTexClass: r['custrecord_oekotex'] || null,
163
+ testResults: lines.map((l) => ({
164
+ chemicalId: String(l['custrecord_chemical_id'] ?? ''),
165
+ result: Number(l['custrecord_test_val'] ?? 0),
166
+ thresholdPassed: l['custrecord_pass'] === true || l['custrecord_pass'] === 'T',
167
+ })),
168
+ };
169
+ },
170
+ mapBatch(r) {
171
+ return {
172
+ batchId: String(r['inventoryNumber'] ?? ''),
173
+ materialNumber: String(r['itemId'] ?? ''),
174
+ dyeLot: String(r['custitem_dye_lot'] ?? ''),
175
+ originCountry: String(r['custitem_origin_country'] ?? ''),
176
+ fiberBlend: String(r['custitem_fiber_blend'] ?? ''),
177
+ carbonKg: Number(r['custitem_carbon_kg'] ?? 0),
178
+ waterLiters: Number(r['custitem_water_liters'] ?? 0),
179
+ energyKwh: Number(r['custitem_energy_kwh'] ?? 0),
180
+ };
181
+ },
182
+ };
183
+ // ============================================================================
184
+ // 4. Microsoft Dynamics 365
185
+ // ============================================================================
186
+ const dynamicsAdapter = {
187
+ erpId: 'microsoft-dynamics',
188
+ displayName: 'Microsoft Dynamics 365',
189
+ mapMaterial(r) {
190
+ return {
191
+ materialNumber: String(r['ProductNumber'] ?? r['msdyn_productid'] ?? ''),
192
+ recycledPercent: Number(r['cust_RecycledPercent'] ?? 0),
193
+ organicCertified: r['cust_OrganicCertified'] === true,
194
+ sustainabilityCertification: r['cust_SustainabilityCert'] || null,
195
+ };
196
+ },
197
+ mapVendor(r) {
198
+ return {
199
+ vendorId: String(r['VendorAccountNumber'] ?? ''),
200
+ sustainabilityTier: normalizeTier(r['cust_SustainabilityTier']),
201
+ lastAuditDate: normalizeDate(r['cust_LastAuditDate']),
202
+ complianceStatus: normalizeStatus(r['cust_ComplianceStatus']),
203
+ };
204
+ },
205
+ mapQuality(r) {
206
+ const lines = (r['QualityOrderLines'] ?? []);
207
+ return {
208
+ notificationId: String(r['QualityOrderId'] ?? ''),
209
+ dyeCompliance: r['cust_DyeCompliance'] === true,
210
+ reachStatus: normalizeReach(r['cust_REACHStatus']),
211
+ oekoTexClass: r['cust_OEKOTEXClass'] || null,
212
+ testResults: lines.map((l) => ({
213
+ chemicalId: String(l['cust_ChemicalId'] ?? ''),
214
+ result: Number(l['cust_TestResult'] ?? 0),
215
+ thresholdPassed: l['cust_ThresholdPassed'] === true,
216
+ })),
217
+ };
218
+ },
219
+ mapBatch(r) {
220
+ return {
221
+ batchId: String(r['InventBatchId'] ?? ''),
222
+ materialNumber: String(r['ProductNumber'] ?? ''),
223
+ dyeLot: String(r['cust_DyeLot'] ?? ''),
224
+ originCountry: String(r['cust_OriginCountry'] ?? ''),
225
+ fiberBlend: String(r['cust_FiberBlend'] ?? ''),
226
+ carbonKg: Number(r['cust_CarbonKg'] ?? 0),
227
+ waterLiters: Number(r['cust_WaterLiters'] ?? 0),
228
+ energyKwh: Number(r['cust_EnergyKwh'] ?? 0),
229
+ };
230
+ },
231
+ };
232
+ // ============================================================================
233
+ // 5. Infor CloudSuite
234
+ // ============================================================================
235
+ const inforAdapter = {
236
+ erpId: 'infor',
237
+ displayName: 'Infor CloudSuite',
238
+ mapMaterial(r) {
239
+ return {
240
+ materialNumber: String(r['ItemNumber'] ?? r['ITNO'] ?? ''),
241
+ recycledPercent: Number(r['RecycledContentPercentage'] ?? 0),
242
+ organicCertified: r['OrganicFlag'] === '1' || r['OrganicFlag'] === true,
243
+ sustainabilityCertification: r['SustainabilityCertType'] || null,
244
+ };
245
+ },
246
+ mapVendor(r) {
247
+ return {
248
+ vendorId: String(r['SupplierNumber'] ?? r['SUNO'] ?? ''),
249
+ sustainabilityTier: normalizeTier(r['SustainTier']),
250
+ lastAuditDate: normalizeDate(r['AuditDate']),
251
+ complianceStatus: normalizeStatus(r['ComplianceCode']),
252
+ };
253
+ },
254
+ mapQuality(r) {
255
+ const results = (r['TestResults'] ?? []);
256
+ return {
257
+ notificationId: String(r['QualityNotifId'] ?? ''),
258
+ dyeCompliance: r['DyeTestPassed'] === '1' || r['DyeTestPassed'] === true,
259
+ reachStatus: normalizeReach(r['REACHResult']),
260
+ oekoTexClass: r['OEKOTEXClassification'] || null,
261
+ testResults: results.map((t) => ({
262
+ chemicalId: String(t['ChemId'] ?? ''),
263
+ result: Number(t['Value'] ?? 0),
264
+ thresholdPassed: t['Pass'] === '1' || t['Pass'] === true,
265
+ })),
266
+ };
267
+ },
268
+ mapBatch(r) {
269
+ return {
270
+ batchId: String(r['LotBatchNo'] ?? r['BANO'] ?? ''),
271
+ materialNumber: String(r['ItemNumber'] ?? ''),
272
+ dyeLot: String(r['DyeLotRef'] ?? ''),
273
+ originCountry: String(r['OriginCountry'] ?? ''),
274
+ fiberBlend: String(r['FiberComposition'] ?? ''),
275
+ carbonKg: Number(r['CO2Kg'] ?? 0),
276
+ waterLiters: Number(r['WaterUseLiters'] ?? 0),
277
+ energyKwh: Number(r['EnergyKwh'] ?? 0),
278
+ };
279
+ },
280
+ };
281
+ // ============================================================================
282
+ // 6. Epicor Kinetic
283
+ // ============================================================================
284
+ const epicorAdapter = {
285
+ erpId: 'epicor',
286
+ displayName: 'Epicor Kinetic',
287
+ mapMaterial(r) {
288
+ return {
289
+ materialNumber: String(r['PartNum'] ?? ''),
290
+ recycledPercent: Number(r['RecycledContent_c'] ?? 0),
291
+ organicCertified: r['OrganicCert_c'] === true,
292
+ sustainabilityCertification: r['SustainCert_c'] || null,
293
+ };
294
+ },
295
+ mapVendor(r) {
296
+ return {
297
+ vendorId: String(r['VendorID'] ?? ''),
298
+ sustainabilityTier: normalizeTier(r['SustainTier_c']),
299
+ lastAuditDate: normalizeDate(r['AuditDate_c']),
300
+ complianceStatus: normalizeStatus(r['CompStatus_c']),
301
+ };
302
+ },
303
+ mapQuality(r) {
304
+ const lines = (r['InspResults'] ?? []);
305
+ return {
306
+ notificationId: String(r['InspPlanNum'] ?? ''),
307
+ dyeCompliance: r['DyeCompliant_c'] === true,
308
+ reachStatus: normalizeReach(r['REACHStatus_c']),
309
+ oekoTexClass: r['OEKOTEXClass_c'] || null,
310
+ testResults: lines.map((l) => ({
311
+ chemicalId: String(l['ChemicalID_c'] ?? ''),
312
+ result: Number(l['TestValue_c'] ?? 0),
313
+ thresholdPassed: l['Passed_c'] === true,
314
+ })),
315
+ };
316
+ },
317
+ mapBatch(r) {
318
+ return {
319
+ batchId: String(r['LotNum'] ?? ''),
320
+ materialNumber: String(r['PartNum'] ?? ''),
321
+ dyeLot: String(r['DyeLot_c'] ?? ''),
322
+ originCountry: String(r['CountryOfOrigin'] ?? ''),
323
+ fiberBlend: String(r['FiberBlend_c'] ?? ''),
324
+ carbonKg: Number(r['CarbonKg_c'] ?? 0),
325
+ waterLiters: Number(r['WaterLiters_c'] ?? 0),
326
+ energyKwh: Number(r['EnergyKwh_c'] ?? 0),
327
+ };
328
+ },
329
+ };
330
+ // ============================================================================
331
+ // 7. Sage X3
332
+ // ============================================================================
333
+ const sageX3Adapter = {
334
+ erpId: 'sage-x3',
335
+ displayName: 'Sage X3',
336
+ mapMaterial(r) {
337
+ return {
338
+ materialNumber: String(r['ITMREF'] ?? ''),
339
+ recycledPercent: Number(r['YRECYCLEDPCT'] ?? 0),
340
+ organicCertified: r['YORGANIC'] === 1 || r['YORGANIC'] === '1',
341
+ sustainabilityCertification: r['YSUSTAINCERT'] || null,
342
+ };
343
+ },
344
+ mapVendor(r) {
345
+ return {
346
+ vendorId: String(r['BPSNUM'] ?? ''),
347
+ sustainabilityTier: normalizeTier(r['YSUSTAINTIER']),
348
+ lastAuditDate: normalizeDate(r['YAUDITDAT']),
349
+ complianceStatus: normalizeStatus(r['YCOMPLIANCE']),
350
+ };
351
+ },
352
+ mapQuality(r) {
353
+ const lines = (r['YQALINES'] ?? []);
354
+ return {
355
+ notificationId: String(r['YQANUM'] ?? ''),
356
+ dyeCompliance: r['YDYEPASS'] === 1 || r['YDYEPASS'] === '1',
357
+ reachStatus: normalizeReach(r['YREACH']),
358
+ oekoTexClass: r['YOEKOTEX'] || null,
359
+ testResults: lines.map((l) => ({
360
+ chemicalId: String(l['YCHEMID'] ?? ''),
361
+ result: Number(l['YTESTVAL'] ?? 0),
362
+ thresholdPassed: l['YPASS'] === 1 || l['YPASS'] === '1',
363
+ })),
364
+ };
365
+ },
366
+ mapBatch(r) {
367
+ return {
368
+ batchId: String(r['LOT'] ?? ''),
369
+ materialNumber: String(r['ITMREF'] ?? ''),
370
+ dyeLot: String(r['YDYELOT'] ?? ''),
371
+ originCountry: String(r['YCOUNTRYOR'] ?? ''),
372
+ fiberBlend: String(r['YFIBERBLEND'] ?? ''),
373
+ carbonKg: Number(r['YCARBONKG'] ?? 0),
374
+ waterLiters: Number(r['YWATERL'] ?? 0),
375
+ energyKwh: Number(r['YENERGYKWH'] ?? 0),
376
+ };
377
+ },
378
+ };
379
+ // ============================================================================
380
+ // 8. IFS Cloud
381
+ // ============================================================================
382
+ const ifsAdapter = {
383
+ erpId: 'ifs',
384
+ displayName: 'IFS Cloud',
385
+ mapMaterial(r) {
386
+ return {
387
+ materialNumber: String(r['PartNo'] ?? ''),
388
+ recycledPercent: Number(r['CfRecycledPct'] ?? 0),
389
+ organicCertified: r['CfOrganicCertified'] === 'TRUE' || r['CfOrganicCertified'] === true,
390
+ sustainabilityCertification: r['CfSustainabilityCert'] || null,
391
+ };
392
+ },
393
+ mapVendor(r) {
394
+ return {
395
+ vendorId: String(r['SupplierId'] ?? ''),
396
+ sustainabilityTier: normalizeTier(r['CfSustainTier']),
397
+ lastAuditDate: normalizeDate(r['CfAuditDate']),
398
+ complianceStatus: normalizeStatus(r['CfComplianceStatus']),
399
+ };
400
+ },
401
+ mapQuality(r) {
402
+ const lines = (r['QualityResults'] ?? []);
403
+ return {
404
+ notificationId: String(r['QualityPlanNo'] ?? ''),
405
+ dyeCompliance: r['CfDyeCompliance'] === 'TRUE' || r['CfDyeCompliance'] === true,
406
+ reachStatus: normalizeReach(r['CfREACHStatus']),
407
+ oekoTexClass: r['CfOEKOTEXClass'] || null,
408
+ testResults: lines.map((l) => ({
409
+ chemicalId: String(l['CfChemicalId'] ?? ''),
410
+ result: Number(l['CfTestResult'] ?? 0),
411
+ thresholdPassed: l['CfPassed'] === 'TRUE' || l['CfPassed'] === true,
412
+ })),
413
+ };
414
+ },
415
+ mapBatch(r) {
416
+ return {
417
+ batchId: String(r['LotBatchNo'] ?? ''),
418
+ materialNumber: String(r['PartNo'] ?? ''),
419
+ dyeLot: String(r['CfDyeLot'] ?? ''),
420
+ originCountry: String(r['CfOriginCountry'] ?? ''),
421
+ fiberBlend: String(r['CfFiberBlend'] ?? ''),
422
+ carbonKg: Number(r['CfCarbonKg'] ?? 0),
423
+ waterLiters: Number(r['CfWaterLiters'] ?? 0),
424
+ energyKwh: Number(r['CfEnergyKwh'] ?? 0),
425
+ };
426
+ },
427
+ };
428
+ // ============================================================================
429
+ // 9. Workday
430
+ // ============================================================================
431
+ const workdayAdapter = {
432
+ erpId: 'workday',
433
+ displayName: 'Workday',
434
+ mapMaterial(r) {
435
+ return {
436
+ materialNumber: String(r['Spend_Item_ID'] ?? r['Item_ID'] ?? ''),
437
+ recycledPercent: Number(r['Custom_Recycled_Percent'] ?? 0),
438
+ organicCertified: r['Custom_Organic_Certified'] === true || r['Custom_Organic_Certified'] === '1',
439
+ sustainabilityCertification: r['Custom_Sustainability_Cert'] || null,
440
+ };
441
+ },
442
+ mapVendor(r) {
443
+ return {
444
+ vendorId: String(r['Supplier_ID'] ?? ''),
445
+ sustainabilityTier: normalizeTier(r['Custom_Sustainability_Tier']),
446
+ lastAuditDate: normalizeDate(r['Custom_Last_Audit_Date']),
447
+ complianceStatus: normalizeStatus(r['Custom_Compliance_Status']),
448
+ };
449
+ },
450
+ mapQuality(r) {
451
+ const checks = (r['Custom_Quality_Checks'] ?? []);
452
+ return {
453
+ notificationId: String(r['Custom_QA_Report_ID'] ?? ''),
454
+ dyeCompliance: r['Custom_Dye_Compliance'] === true || r['Custom_Dye_Compliance'] === '1',
455
+ reachStatus: normalizeReach(r['Custom_REACH_Status']),
456
+ oekoTexClass: r['Custom_OEKOTEX_Class'] || null,
457
+ testResults: checks.map((c) => ({
458
+ chemicalId: String(c['Custom_Chemical_ID'] ?? ''),
459
+ result: Number(c['Custom_Test_Result'] ?? 0),
460
+ thresholdPassed: c['Custom_Passed'] === true || c['Custom_Passed'] === '1',
461
+ })),
462
+ };
463
+ },
464
+ mapBatch(r) {
465
+ return {
466
+ batchId: String(r['Custom_Batch_ID'] ?? ''),
467
+ materialNumber: String(r['Spend_Item_ID'] ?? ''),
468
+ dyeLot: String(r['Custom_Dye_Lot'] ?? ''),
469
+ originCountry: String(r['Custom_Origin_Country'] ?? ''),
470
+ fiberBlend: String(r['Custom_Fiber_Blend'] ?? ''),
471
+ carbonKg: Number(r['Custom_Carbon_Kg'] ?? 0),
472
+ waterLiters: Number(r['Custom_Water_Liters'] ?? 0),
473
+ energyKwh: Number(r['Custom_Energy_Kwh'] ?? 0),
474
+ };
475
+ },
476
+ };
477
+ // ============================================================================
478
+ // 10. Oracle PeopleSoft
479
+ // ============================================================================
480
+ const peopleSoftAdapter = {
481
+ erpId: 'oracle-peoplesoft',
482
+ displayName: 'PeopleSoft',
483
+ mapMaterial(r) {
484
+ return {
485
+ materialNumber: String(r['INV_ITEM_ID'] ?? ''),
486
+ recycledPercent: Number(r['CUST_RECYCLED_PCT'] ?? 0),
487
+ organicCertified: r['CUST_ORGANIC_FLG'] === 'Y',
488
+ sustainabilityCertification: r['CUST_SUSTAIN_CERT'] || null,
489
+ };
490
+ },
491
+ mapVendor(r) {
492
+ return {
493
+ vendorId: String(r['VENDOR_ID'] ?? ''),
494
+ sustainabilityTier: normalizeTier(r['CUST_SUSTAIN_TIER']),
495
+ lastAuditDate: normalizeDate(r['CUST_AUDIT_DT']),
496
+ complianceStatus: normalizeStatus(r['CUST_COMPLIANCE']),
497
+ };
498
+ },
499
+ mapQuality(r) {
500
+ const lines = (r['CUST_QA_LINES'] ?? []);
501
+ return {
502
+ notificationId: String(r['QA_PLAN_ID'] ?? ''),
503
+ dyeCompliance: r['CUST_DYE_COMPLY'] === 'Y',
504
+ reachStatus: normalizeReach(r['CUST_REACH_STATUS']),
505
+ oekoTexClass: r['CUST_OEKOTEX_CLS'] || null,
506
+ testResults: lines.map((l) => ({
507
+ chemicalId: String(l['CUST_CHEM_ID'] ?? ''),
508
+ result: Number(l['CUST_TEST_VAL'] ?? 0),
509
+ thresholdPassed: l['CUST_PASS_FLG'] === 'Y',
510
+ })),
511
+ };
512
+ },
513
+ mapBatch(r) {
514
+ return {
515
+ batchId: String(r['LOT_ID'] ?? ''),
516
+ materialNumber: String(r['INV_ITEM_ID'] ?? ''),
517
+ dyeLot: String(r['CUST_DYE_LOT'] ?? ''),
518
+ originCountry: String(r['CUST_ORIGIN_CNTRY'] ?? ''),
519
+ fiberBlend: String(r['CUST_FIBER_BLEND'] ?? ''),
520
+ carbonKg: Number(r['CUST_CARBON_KG'] ?? 0),
521
+ waterLiters: Number(r['CUST_WATER_L'] ?? 0),
522
+ energyKwh: Number(r['CUST_ENERGY_KWH'] ?? 0),
523
+ };
524
+ },
525
+ };
526
+ // ============================================================================
527
+ // Adapter Registry
528
+ // ============================================================================
529
+ const ADAPTER_REGISTRY = new Map([
530
+ ['sap-s4hana', sapAdapter],
531
+ ['sap-ecc', sapAdapter], // ECC uses same wire format
532
+ ['sap-business-one', sapAdapter], // SAP B1 uses same field conventions
533
+ ['oracle-erp', oracleErpAdapter],
534
+ ['oracle-netsuite', netsuiteAdapter],
535
+ ['microsoft-dynamics', dynamicsAdapter],
536
+ ['infor', inforAdapter],
537
+ ['epicor', epicorAdapter],
538
+ ['sage-x3', sageX3Adapter],
539
+ ['ifs', ifsAdapter],
540
+ ['workday', workdayAdapter],
541
+ ['oracle-peoplesoft', peopleSoftAdapter],
542
+ ]);
543
+ /**
544
+ * Get the sustainability adapter for a given ERP system.
545
+ * Returns undefined if the ERP is not in the registry.
546
+ */
547
+ export function getERPAdapter(erpId) {
548
+ return ADAPTER_REGISTRY.get(erpId);
549
+ }
550
+ /**
551
+ * List all registered ERP adapters.
552
+ */
553
+ export function listERPAdapters() {
554
+ // Deduplicate (SAP family shares one adapter)
555
+ const seen = new Set();
556
+ const result = [];
557
+ for (const adapter of ADAPTER_REGISTRY.values()) {
558
+ if (!seen.has(adapter.erpId)) {
559
+ seen.add(adapter.erpId);
560
+ result.push(adapter);
561
+ }
562
+ }
563
+ return result;
564
+ }
565
+ /**
566
+ * Get all registered ERP IDs.
567
+ */
568
+ export function listERPAdapterIds() {
569
+ return [...ADAPTER_REGISTRY.keys()];
570
+ }
571
+ // ============================================================================
572
+ // Convenience wrappers (preserve backward-compat with SAP-only callers)
573
+ // ============================================================================
574
+ export function mapSAPMaterialToDomain(record) {
575
+ return sapAdapter.mapMaterial(record);
576
+ }
577
+ export function mapDomainMaterialToSAP(domain) {
578
+ return {
579
+ MATNR: domain.materialNumber,
580
+ ZZRECYCLED_PCT: domain.recycledPercent,
581
+ ZZORGANIC_FLAG: domain.organicCertified ? 'X' : '',
582
+ ZZSUSTAIN_CERT: domain.sustainabilityCertification ?? '',
583
+ };
584
+ }
585
+ export function mapSAPVendorToDomain(record) {
586
+ return sapAdapter.mapVendor(record);
587
+ }
588
+ export function mapSAPQualityToDomain(record) {
589
+ return sapAdapter.mapQuality(record);
590
+ }
591
+ export function mapSAPBatchToDomain(record) {
592
+ return sapAdapter.mapBatch(record);
593
+ }
594
+ // ============================================================================
595
+ // Domain Thresholds (from SPARC spec — ERP-agnostic)
596
+ // ============================================================================
597
+ /** Minimum recycled content percentage to qualify as "recycled material" */
598
+ export const THRESHOLD_RECYCLED_PCT = 5;
599
+ /** Minimum organic content percentage to qualify as "organic material" */
600
+ export const THRESHOLD_ORGANIC_PCT = 2;
601
+ /** Maximum restricted dye substance percentage (REACH compliance) */
602
+ export const THRESHOLD_DYE_PCT = 10;
603
+ export function meetsRecycledThreshold(pct) {
604
+ return pct >= THRESHOLD_RECYCLED_PCT;
605
+ }
606
+ export function meetsOrganicThreshold(pct) {
607
+ return pct >= THRESHOLD_ORGANIC_PCT;
608
+ }
609
+ export function meetsDyeThreshold(pct) {
610
+ return pct <= THRESHOLD_DYE_PCT;
611
+ }
612
+ // ============================================================================
613
+ // Shared Normalization Helpers
614
+ // ============================================================================
615
+ function normalizeTier(val) {
616
+ const s = String(val ?? '').toUpperCase();
617
+ if (s.includes('GOLD') || s === 'A' || s === '1')
618
+ return 'gold';
619
+ if (s.includes('SILVER') || s === 'B' || s === '2')
620
+ return 'silver';
621
+ if (s.includes('BRONZE') || s === 'C' || s === '3')
622
+ return 'bronze';
623
+ return 'uncertified';
624
+ }
625
+ function normalizeStatus(val) {
626
+ const s = String(val ?? '').toUpperCase();
627
+ if (s.includes('COMPLIANT') && !s.includes('NON'))
628
+ return 'compliant';
629
+ if (s.includes('CONDITIONAL') || s.includes('PARTIAL'))
630
+ return 'conditional';
631
+ return 'non-compliant';
632
+ }
633
+ function normalizeReach(val) {
634
+ const s = String(val ?? '').toUpperCase();
635
+ if (s.includes('PASS') || s === 'Y' || s === 'TRUE')
636
+ return 'passed';
637
+ if (s.includes('FAIL') || s === 'N' || s === 'FALSE')
638
+ return 'failed';
639
+ return 'pending';
640
+ }
641
+ function normalizeDate(val) {
642
+ if (val == null)
643
+ return '';
644
+ const s = String(val);
645
+ // SAP-style YYYYMMDD
646
+ if (/^\d{8}$/.test(s))
647
+ return `${s.slice(0, 4)}-${s.slice(4, 6)}-${s.slice(6, 8)}`;
648
+ // ISO or other date string — pass through
649
+ return s;
650
+ }
651
+ //# sourceMappingURL=sap-adapter.js.map