@resolveio/server-lib 22.3.206 → 22.3.208
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/methods/ai-terminal.d.ts +17 -0
- package/methods/ai-terminal.js +3258 -1067
- package/methods/ai-terminal.js.map +1 -1
- package/package.json +1 -1
- package/util/ai-run-evidence-adapters.js +55 -20
- package/util/ai-run-evidence-adapters.js.map +1 -1
- package/util/ai-runner-qa-auth.js +18 -1
- package/util/ai-runner-qa-auth.js.map +1 -1
- package/util/ai-runner-qa-tools.js +479 -17
- package/util/ai-runner-qa-tools.js.map +1 -1
- package/util/support-runner-v5.js +16 -5
- package/util/support-runner-v5.js.map +1 -1
|
@@ -1434,6 +1434,130 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
1434
1434
|
'',
|
|
1435
1435
|
'function unique(values) { return Array.from(new Set((values || []).filter(Boolean).map(String))); }',
|
|
1436
1436
|
'',
|
|
1437
|
+
'function lowerTrim(value) { return String(value || "").trim().toLowerCase(); }',
|
|
1438
|
+
'',
|
|
1439
|
+
'function escapeRegexText(value) { return String(value || "").replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"); }',
|
|
1440
|
+
'',
|
|
1441
|
+
'function exactTextRegex(value) { return new RegExp(`^${escapeRegexText(value)}$`, "i"); }',
|
|
1442
|
+
'',
|
|
1443
|
+
'function requireOptionalXlsx() {',
|
|
1444
|
+
' const candidates = [',
|
|
1445
|
+
' path.join(projectRoot, "server", "node_modules", "xlsx"),',
|
|
1446
|
+
' path.join(projectRoot, "node_modules", "xlsx"),',
|
|
1447
|
+
' path.join(repoRoot, "node_modules", "xlsx"),',
|
|
1448
|
+
' path.join(process.cwd(), "server", "node_modules", "xlsx"),',
|
|
1449
|
+
' path.join(process.cwd(), "node_modules", "xlsx"),',
|
|
1450
|
+
' "xlsx"',
|
|
1451
|
+
' ];',
|
|
1452
|
+
' for (const candidate of candidates) { try { return require(candidate); } catch (error) {} }',
|
|
1453
|
+
' return null;',
|
|
1454
|
+
'}',
|
|
1455
|
+
'',
|
|
1456
|
+
'function collectPricingImportWorkbookPaths() {',
|
|
1457
|
+
' const dirs = [',
|
|
1458
|
+
' path.join(projectRoot, ".resolveio-context", "attachments"),',
|
|
1459
|
+
' path.join(repoRoot, ".resolveio-context", "attachments"),',
|
|
1460
|
+
' path.join(projectRoot, ".resolveio-support-context", "attachments"),',
|
|
1461
|
+
' path.join(repoRoot, ".resolveio-support-context", "attachments")',
|
|
1462
|
+
' ];',
|
|
1463
|
+
' const paths = [];',
|
|
1464
|
+
' for (const dir of dirs) {',
|
|
1465
|
+
' try {',
|
|
1466
|
+
' for (const name of fs.readdirSync(dir)) {',
|
|
1467
|
+
' if (/\\.xlsx$/i.test(name) && !/^~\\$/.test(name)) paths.push(path.join(dir, name));',
|
|
1468
|
+
' }',
|
|
1469
|
+
' } catch (error) {}',
|
|
1470
|
+
' }',
|
|
1471
|
+
' return unique(paths);',
|
|
1472
|
+
'}',
|
|
1473
|
+
'',
|
|
1474
|
+
'function normalizeWorkbookCell(value) {',
|
|
1475
|
+
' if (value == null) return "";',
|
|
1476
|
+
' if (value instanceof Date) return value.toISOString();',
|
|
1477
|
+
' return String(value).replace(/\\s+/g, " ").trim();',
|
|
1478
|
+
'}',
|
|
1479
|
+
'',
|
|
1480
|
+
'function extractPricingImportWorkbookHints(summary) {',
|
|
1481
|
+
' const workbooks = collectPricingImportWorkbookPaths();',
|
|
1482
|
+
' const xlsx = workbooks.length ? requireOptionalXlsx() : null;',
|
|
1483
|
+
' const customerNames = new Set();',
|
|
1484
|
+
' const itemMap = new Map();',
|
|
1485
|
+
' const workbookSummaries = [];',
|
|
1486
|
+
' if (!workbooks.length) return { workbooks, workbook_summaries: workbookSummaries, customer_names: [], items: [] };',
|
|
1487
|
+
' if (!xlsx) {',
|
|
1488
|
+
' summary.notes.push("Pricing-import workbook seed context skipped because xlsx could not be required in the QA seeder.");',
|
|
1489
|
+
' return { workbooks, workbook_summaries: workbookSummaries, customer_names: [], items: [] };',
|
|
1490
|
+
' }',
|
|
1491
|
+
' for (const workbookPath of workbooks.slice(0, 8)) {',
|
|
1492
|
+
' try {',
|
|
1493
|
+
' const workbook = xlsx.readFile(workbookPath, { cellDates: true });',
|
|
1494
|
+
' const sheetName = workbook.SheetNames.includes("Prices") ? "Prices" : workbook.SheetNames[0];',
|
|
1495
|
+
' const rows = sheetName ? xlsx.utils.sheet_to_json(workbook.Sheets[sheetName], { defval: "" }) : [];',
|
|
1496
|
+
' for (const row of rows) {',
|
|
1497
|
+
' const customer = normalizeWorkbookCell(row.Customer || row.customer || row.CustomerName || row["Customer Name"]);',
|
|
1498
|
+
' const pricingType = normalizeWorkbookCell(row["Pricing Type"] || row.PricingType || row.Type || row.type);',
|
|
1499
|
+
' const name = normalizeWorkbookCell(row.Name || row.name || row.Item || row.item || row.Chemical || row.chemical);',
|
|
1500
|
+
' if (customer) customerNames.add(customer);',
|
|
1501
|
+
' if (name) itemMap.set(`${lowerTrim(pricingType)}::${lowerTrim(name)}`, { name, type: pricingType });',
|
|
1502
|
+
' }',
|
|
1503
|
+
' workbookSummaries.push({ name: path.basename(workbookPath), sheetName, rows: rows.length });',
|
|
1504
|
+
' } catch (error) {',
|
|
1505
|
+
' workbookSummaries.push({ name: path.basename(workbookPath), error: error && (error.message || String(error)) || String(error) });',
|
|
1506
|
+
' }',
|
|
1507
|
+
' }',
|
|
1508
|
+
' return { workbooks, workbook_summaries: workbookSummaries, customer_names: Array.from(customerNames).slice(0, 300), items: Array.from(itemMap.values()).slice(0, 700) };',
|
|
1509
|
+
'}',
|
|
1510
|
+
'',
|
|
1511
|
+
'async function copyPricingImportWorkbookContext(sourceDb, targetDb, summary) {',
|
|
1512
|
+
' if (!shouldSeedPricingImportFixtures()) return [];',
|
|
1513
|
+
' const hints = extractPricingImportWorkbookHints(summary);',
|
|
1514
|
+
' summary.selected.pricing_import_workbook_seed = {',
|
|
1515
|
+
' workbook_names: hints.workbooks.map((entry) => path.basename(entry)),',
|
|
1516
|
+
' workbook_summaries: hints.workbook_summaries,',
|
|
1517
|
+
' customer_name_count: hints.customer_names.length,',
|
|
1518
|
+
' item_name_count: hints.items.length',
|
|
1519
|
+
' };',
|
|
1520
|
+
' if (!hints.workbooks.length) {',
|
|
1521
|
+
' summary.notes.push("Pricing-import seed profile found no attached .xlsx workbooks to derive customer/item fixture context.");',
|
|
1522
|
+
' return [];',
|
|
1523
|
+
' }',
|
|
1524
|
+
' const customerRegexes = hints.customer_names.map(exactTextRegex);',
|
|
1525
|
+
' const customers = customerRegexes.length ? await copyQuery(sourceDb, targetDb, "customers", { $or: [{ name: { $in: customerRegexes } }, { customer: { $in: customerRegexes } }, { company_name: { $in: customerRegexes } }] }, summary, 300, { name: 1 }) : [];',
|
|
1526
|
+
' const itemOrs = [];',
|
|
1527
|
+
' for (const hint of hints.items) {',
|
|
1528
|
+
' const nameRegex = exactTextRegex(hint.name);',
|
|
1529
|
+
' const typeRegex = hint.type ? exactTextRegex(hint.type) : null;',
|
|
1530
|
+
' if (typeRegex) itemOrs.push({ name: nameRegex, type: typeRegex }, { description: nameRegex, type: typeRegex });',
|
|
1531
|
+
' itemOrs.push({ name: nameRegex }, { description: nameRegex });',
|
|
1532
|
+
' }',
|
|
1533
|
+
' const items = itemOrs.length ? await copyQuery(sourceDb, targetDb, "items", { $or: itemOrs }, summary, 900, { name: 1 }) : [];',
|
|
1534
|
+
' const itemNameRegexes = hints.items.map((hint) => exactTextRegex(hint.name));',
|
|
1535
|
+
' const chemicals = itemNameRegexes.length ? await copyQuery(sourceDb, targetDb, "chemicals", { $or: [{ name: { $in: itemNameRegexes } }, { description: { $in: itemNameRegexes } }] }, summary, 900, { name: 1 }) : [];',
|
|
1536
|
+
' const chemicalIds = unique(chemicals.map((doc) => doc._id));',
|
|
1537
|
+
' const chemicalItems = chemicalIds.length ? await copyQuery(sourceDb, targetDb, "items", { $or: [{ id_chemical: { $in: chemicalIds } }, { _id: { $in: chemicalIds } }] }, summary, 900, { name: 1 }) : [];',
|
|
1538
|
+
' const copiedItems = [...items, ...chemicalItems];',
|
|
1539
|
+
' const copiedCustomerNames = new Set(customers.map((doc) => lowerTrim(doc.name || doc.customer || doc.company_name)));',
|
|
1540
|
+
' const copiedItemNames = new Set(copiedItems.map((doc) => lowerTrim(doc.name || doc.description)));',
|
|
1541
|
+
' const copiedChemicalNames = new Set(chemicals.map((doc) => lowerTrim(doc.name || doc.description)));',
|
|
1542
|
+
' const missingCustomers = hints.customer_names.filter((name) => !copiedCustomerNames.has(lowerTrim(name)));',
|
|
1543
|
+
' const missingItems = hints.items.filter((hint) => !copiedItemNames.has(lowerTrim(hint.name)) && !copiedChemicalNames.has(lowerTrim(hint.name))).map((hint) => hint.type ? `${hint.type}:${hint.name}` : hint.name);',
|
|
1544
|
+
' const itemIds = unique(copiedItems.map((doc) => doc._id));',
|
|
1545
|
+
' const customerIds = unique(customers.map((doc) => doc._id));',
|
|
1546
|
+
' summary.selected.pricing_import_workbook_context = {',
|
|
1547
|
+
' workbook_names: hints.workbooks.map((entry) => path.basename(entry)),',
|
|
1548
|
+
' customer_names: hints.customer_names.slice(0, 60),',
|
|
1549
|
+
' item_names: hints.items.map((hint) => hint.type ? `${hint.type}:${hint.name}` : hint.name).slice(0, 100),',
|
|
1550
|
+
' customer_ids: customerIds,',
|
|
1551
|
+
' item_ids: itemIds,',
|
|
1552
|
+
' chemical_ids: chemicalIds,',
|
|
1553
|
+
' missing_customer_names: missingCustomers.slice(0, 80),',
|
|
1554
|
+
' missing_item_names: missingItems.slice(0, 120)',
|
|
1555
|
+
' };',
|
|
1556
|
+
' summary.notes.push(`Seeded pricing-import workbook context: ${customers.length}/${hints.customer_names.length} customer name(s), ${copiedItems.length}/${hints.items.length} item name(s), ${chemicals.length} chemical(s) from ${hints.workbooks.length} attached workbook(s).`);',
|
|
1557
|
+
' if (missingCustomers.length || missingItems.length) summary.notes.push(`Pricing-import workbook context still missing ${missingCustomers.length} customer name(s) and ${missingItems.length} item name(s) from live Mongo.`);',
|
|
1558
|
+
' return [...customers, ...copiedItems, ...chemicals];',
|
|
1559
|
+
'}',
|
|
1560
|
+
'',
|
|
1437
1561
|
'function collectEmailLikeValues(value, out = new Set(), depth = 0) {',
|
|
1438
1562
|
' if (depth > 6 || value == null) return out;',
|
|
1439
1563
|
' if (typeof value === "string") {',
|
|
@@ -2157,6 +2281,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
2157
2281
|
' const truckTreatingBolContextDocs = await copyTruckTreatingBolContext(sourceDb, targetDb, summary);',
|
|
2158
2282
|
' const ticketAssetContextDocs = await copyTicketAssetContext(sourceDb, targetDb, summary);',
|
|
2159
2283
|
' const productionInterchangeablesContextDocs = await copyProductionInterchangeablesContext(sourceDb, targetDb, summary);',
|
|
2284
|
+
' const pricingImportWorkbookContextDocs = includePricingImportFixtures ? await copyPricingImportWorkbookContext(sourceDb, targetDb, summary) : [];',
|
|
2160
2285
|
' if (!includeBillingFixtures) {',
|
|
2161
2286
|
' summary.notes.push("Billing dashboard fixtures are disabled because ticket context does not require billing, invoices, taxes, inventory, surcharges, checkout, or pick tickets.");',
|
|
2162
2287
|
' await cleanupSyntheticBillingDashboardQaFixtures(targetDb, summary);',
|
|
@@ -2198,11 +2323,12 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
2198
2323
|
' summary.notes.push("No live billable delivery or truck-treatment records matched the Billing Dashboard awaiting-invoice filters.");',
|
|
2199
2324
|
' }',
|
|
2200
2325
|
'',
|
|
2201
|
-
' const sourceDocs = [...qaIdentityUsers, ...truckTreatingBolContextDocs, ...ticketAssetContextDocs, ...productionInterchangeablesContextDocs, ...productionDeliveries, ...truckTreatingDeliveries];',
|
|
2202
|
-
' const
|
|
2326
|
+
' const sourceDocs = [...qaIdentityUsers, ...truckTreatingBolContextDocs, ...ticketAssetContextDocs, ...productionInterchangeablesContextDocs, ...pricingImportWorkbookContextDocs, ...productionDeliveries, ...truckTreatingDeliveries];',
|
|
2327
|
+
' const pricingImportContext = summary.selected.pricing_import_workbook_context || {};',
|
|
2328
|
+
' const customerIds = unique([...idValues(sourceDocs, ["id_customer"]), ...(Array.isArray(pricingImportContext.customer_ids) ? pricingImportContext.customer_ids : [])]);',
|
|
2203
2329
|
' const locationIds = idValues(sourceDocs, ["id_location"]);',
|
|
2204
2330
|
' const wellGroupIds = idValues(sourceDocs, ["id_well_group"]);',
|
|
2205
|
-
' const itemIds = idValues(sourceDocs, ["id_item", "id_chemical"]);',
|
|
2331
|
+
' const itemIds = unique([...idValues(sourceDocs, ["id_item", "id_chemical"]), ...(Array.isArray(pricingImportContext.item_ids) ? pricingImportContext.item_ids : []), ...(Array.isArray(pricingImportContext.chemical_ids) ? pricingImportContext.chemical_ids : [])]);',
|
|
2206
2332
|
' const bolIds = idValues(sourceDocs, ["id_bol", "id_bols"]);',
|
|
2207
2333
|
' const yardIds = idValues(sourceDocs, ["id_yard"]);',
|
|
2208
2334
|
' const activityIds = idValues(sourceDocs, ["id_activity", "id_production_sales_order", "id_sales_order"]);',
|
|
@@ -2260,7 +2386,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
2260
2386
|
' ] }, summary, 120, { date: -1, updatedAt: -1 });',
|
|
2261
2387
|
' }',
|
|
2262
2388
|
'',
|
|
2263
|
-
' summary.ready = truckTreatingBolContextDocs.length > 0 || ticketAssetContextDocs.length > 0 || productionInterchangeablesContextDocs.length > 0 || productionDeliveries.length > 0 || truckTreatingDeliveries.length > 0;',
|
|
2389
|
+
' summary.ready = pricingImportWorkbookContextDocs.length > 0 || truckTreatingBolContextDocs.length > 0 || ticketAssetContextDocs.length > 0 || productionInterchangeablesContextDocs.length > 0 || productionDeliveries.length > 0 || truckTreatingDeliveries.length > 0;',
|
|
2264
2390
|
' return summary;',
|
|
2265
2391
|
'}',
|
|
2266
2392
|
'',
|
|
@@ -2555,10 +2681,14 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
2555
2681
|
'function normalizeRoute(candidate) {',
|
|
2556
2682
|
' const value = String(candidate || "").trim();',
|
|
2557
2683
|
' if (!value) return "";',
|
|
2558
|
-
'
|
|
2559
|
-
'
|
|
2684
|
+
' let routeText = value;',
|
|
2685
|
+
' if (/^https?:\\/\\//i.test(routeText)) {',
|
|
2686
|
+
' try { routeText = new URL(routeText).pathname || "/"; } catch (error) { return ""; }',
|
|
2560
2687
|
' }',
|
|
2561
|
-
|
|
2688
|
+
" const match = routeText.match(/(?:^|[\\s\\\"'`(])((?:\\/[a-z0-9][a-z0-9._~!$&'()*+,;=:@%\\/-]*)(?:\\?[^\\s\\\"'`<>)]*)?)/i);",
|
|
2689
|
+
' routeText = ((match && match[1]) || routeText).split(/\\s*->\\s*/)[0].split(/\\s+-\\s+/)[0].trim().replace(/[),.;]+$/g, "");',
|
|
2690
|
+
' if (!routeText) return "";',
|
|
2691
|
+
' return routeText.startsWith("/") ? routeText : `/${routeText}`;',
|
|
2562
2692
|
'}',
|
|
2563
2693
|
'function resolveTargetRoute(explicitRoute) {',
|
|
2564
2694
|
' const hintText = readSeedHintText();',
|
|
@@ -2774,20 +2904,54 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
2774
2904
|
' if (!context || !Array.isArray(context.asset_ids) || !context.asset_ids.length) return null;',
|
|
2775
2905
|
' return context;',
|
|
2776
2906
|
'}',
|
|
2777
|
-
'function
|
|
2907
|
+
'function depsCacheModuleCandidates(moduleName) {',
|
|
2908
|
+
' const roots = uniqueStrings([',
|
|
2909
|
+
' process.env.RESOLVEIO_RUNNER_QA_DEPS_CACHE_ROOT,',
|
|
2910
|
+
' process.env.RESOLVEIO_SUPPORT_QA_DEPS_CACHE_ROOT,',
|
|
2911
|
+
' path.join(projectRoot, ".deps-cache"),',
|
|
2912
|
+
' path.join(path.dirname(projectRoot), ".deps-cache"),',
|
|
2913
|
+
' path.join(path.dirname(path.dirname(projectRoot)), ".deps-cache"),',
|
|
2914
|
+
' "/var/app/resolveio-ai-workspace/.deps-cache/resolveio-all"',
|
|
2915
|
+
' ]);',
|
|
2916
|
+
' const candidates = [];',
|
|
2917
|
+
' for (const root of roots) {',
|
|
2918
|
+
' try {',
|
|
2919
|
+
' if (!root || !fs.existsSync(root)) continue;',
|
|
2920
|
+
' for (const entry of fs.readdirSync(root)) {',
|
|
2921
|
+
' candidates.push(path.join(root, entry, "node_modules", moduleName));',
|
|
2922
|
+
' candidates.push(path.join(root, entry, "node_modules", "@resolveio", "server-lib", "node_modules", moduleName));',
|
|
2923
|
+
' }',
|
|
2924
|
+
' } catch (error) {}',
|
|
2925
|
+
' }',
|
|
2926
|
+
' return candidates;',
|
|
2927
|
+
'}',
|
|
2928
|
+
'function requireQaModule(moduleName) {',
|
|
2778
2929
|
' const candidates = [',
|
|
2779
|
-
' path.join(projectRoot, "server", "node_modules",
|
|
2780
|
-
' path.join(projectRoot, "node_modules",
|
|
2781
|
-
' path.join(process.cwd(), "server", "node_modules",
|
|
2782
|
-
' path.join(process.cwd(), "node_modules",
|
|
2783
|
-
'
|
|
2930
|
+
' path.join(projectRoot, "server", "node_modules", moduleName),',
|
|
2931
|
+
' path.join(projectRoot, "node_modules", moduleName),',
|
|
2932
|
+
' path.join(process.cwd(), "server", "node_modules", moduleName),',
|
|
2933
|
+
' path.join(process.cwd(), "node_modules", moduleName),',
|
|
2934
|
+
' ...depsCacheModuleCandidates(moduleName),',
|
|
2935
|
+
' moduleName',
|
|
2784
2936
|
' ];',
|
|
2785
2937
|
' for (const candidate of candidates) { try { return require(candidate); } catch (error) {} }',
|
|
2786
|
-
' throw new Error(
|
|
2938
|
+
' throw new Error(`Unable to require ${moduleName} from project/server node_modules, QA dependency cache, or global resolution`);',
|
|
2787
2939
|
'}',
|
|
2940
|
+
'function mongoRequire() { return requireQaModule("mongodb"); }',
|
|
2941
|
+
'function xlsxRequire() { return requireQaModule("xlsx"); }',
|
|
2788
2942
|
'function localMongoUrl() {',
|
|
2789
2943
|
' return process.env.MONGO_URL || process.env.RESOLVEIO_RUNNER_QA_MONGO_URL || process.env.RESOLVEIO_SUPPORT_QA_MONGO_URL || `mongodb://127.0.0.1:${process.env.RESOLVEIO_SUPPORT_QA_MONGO_PORT || "3001"}/resolveio?directConnection=true`;',
|
|
2790
2944
|
'}',
|
|
2945
|
+
'function safeLocalMongoUrl() {',
|
|
2946
|
+
' const url = localMongoUrl();',
|
|
2947
|
+
' if (/^(true|1|yes|on)$/i.test(String(process.env.RESOLVEIO_SUPPORT_QA_ALLOW_NONLOCAL_MONGO_PROOF || process.env.RESOLVEIO_RUNNER_QA_ALLOW_NONLOCAL_MONGO_PROOF || ""))) return url;',
|
|
2948
|
+
' try {',
|
|
2949
|
+
' const parsed = new URL(url);',
|
|
2950
|
+
' const host = String(parsed.hostname || "").toLowerCase();',
|
|
2951
|
+
' if (host === "127.0.0.1" || host === "localhost" || host === "::1") return url;',
|
|
2952
|
+
' } catch (error) {}',
|
|
2953
|
+
' throw new Error(`Pricing import proof refuses to write to non-local Mongo URL: ${url.replace(/:[^:@/]+@/, ":***@")}`);',
|
|
2954
|
+
'}',
|
|
2791
2955
|
'function assetDisplayNumber(asset) { return String(asset && (asset.unit_number || asset.asset_number || asset.number || asset.name || asset._id) || ""); }',
|
|
2792
2956
|
'function uniqueStrings(values) { return Array.from(new Set((values || []).map((value) => String(value || "").trim()).filter(Boolean))); }',
|
|
2793
2957
|
'function staleLocationTerms(text) {',
|
|
@@ -2856,6 +3020,297 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
2856
3020
|
' await page.screenshot({ path: screenshot, type: "jpeg", quality: 82, fullPage: false });',
|
|
2857
3021
|
' return { route, screenshot, summary };',
|
|
2858
3022
|
'}',
|
|
3023
|
+
'function shouldRunPricingImportProof() {',
|
|
3024
|
+
' const seed = readJson(path.join(artifactDir, "qa-live-data-seed-result.json")) || {};',
|
|
3025
|
+
' if (String(seed.profile || "").toLowerCase() === "pricing_import") return true;',
|
|
3026
|
+
' const text = [activeRowText(), targetRoute].join("\\n");',
|
|
3027
|
+
' return /\\/manage\\/pricing(?:$|[/?#])/i.test(targetRoute) && /\\b(pricing|price|gross\\s+price|production\\s+price)\\b/i.test(text) && /\\b(import|upload|template|xlsx|excel|workbook|spreadsheet)\\b/i.test(text);',
|
|
3028
|
+
'}',
|
|
3029
|
+
'function pricingWorkbookDirs() {',
|
|
3030
|
+
' const repoRoot = path.dirname(projectRoot);',
|
|
3031
|
+
' return uniqueStrings([',
|
|
3032
|
+
' path.join(projectRoot, ".resolveio-context", "attachments"),',
|
|
3033
|
+
' path.join(projectRoot, ".resolveio-support-context", "attachments"),',
|
|
3034
|
+
' path.join(repoRoot, ".resolveio-context", "attachments"),',
|
|
3035
|
+
' path.join(repoRoot, ".resolveio-support-context", "attachments"),',
|
|
3036
|
+
' path.join(artifactDir, "attachments")',
|
|
3037
|
+
' ]);',
|
|
3038
|
+
'}',
|
|
3039
|
+
'function collectPricingImportWorkbooks() {',
|
|
3040
|
+
' const byKey = new Map();',
|
|
3041
|
+
' for (const dir of pricingWorkbookDirs()) {',
|
|
3042
|
+
' try {',
|
|
3043
|
+
' if (!fs.existsSync(dir)) continue;',
|
|
3044
|
+
' for (const name of fs.readdirSync(dir)) {',
|
|
3045
|
+
' if (!/\\.xlsx$/i.test(name) || /^~\\$/.test(name)) continue;',
|
|
3046
|
+
' const fullPath = path.join(dir, name);',
|
|
3047
|
+
' const stat = fs.statSync(fullPath);',
|
|
3048
|
+
' if (!stat.isFile()) continue;',
|
|
3049
|
+
' const key = `${stat.size}:${name.replace(/[^a-z0-9]+/gi, "").toLowerCase()}`;',
|
|
3050
|
+
' if (!byKey.has(key)) byKey.set(key, fullPath);',
|
|
3051
|
+
' }',
|
|
3052
|
+
' } catch (error) {}',
|
|
3053
|
+
' }',
|
|
3054
|
+
' return Array.from(byKey.values()).sort();',
|
|
3055
|
+
'}',
|
|
3056
|
+
'function normalizeImportPriceRecord(line) {',
|
|
3057
|
+
' const source = line || {};',
|
|
3058
|
+
' const keyMap = {};',
|
|
3059
|
+
' for (const key of Object.keys(source)) keyMap[String(key).trim().toLowerCase().replace(/\\s+/g, " ")] = key;',
|
|
3060
|
+
' const getValue = (aliases) => { const alias = aliases.find((candidate) => keyMap[candidate] !== undefined); return alias ? source[keyMap[alias]] : undefined; };',
|
|
3061
|
+
' const normalized = {',
|
|
3062
|
+
' Customer: getValue(["customer"]),',
|
|
3063
|
+
' "Pricing Type": getValue(["pricing type", "type"]),',
|
|
3064
|
+
' Name: getValue(["name", "item", "chemical"]),',
|
|
3065
|
+
' Description: getValue(["description"]),',
|
|
3066
|
+
' "Gross Price": getValue(["gross price", "grossprice", "gross"]),',
|
|
3067
|
+
' "Discount Percent": getValue(["discount percent", "discount %", "discount", "discount percentage"]),',
|
|
3068
|
+
' Price: getValue(["price"])',
|
|
3069
|
+
' };',
|
|
3070
|
+
' if ((normalized["Gross Price"] === undefined || normalized["Gross Price"] === null || normalized["Gross Price"] === "") && normalized.Price !== undefined && normalized.Price !== null && normalized.Price !== "") normalized["Gross Price"] = normalized.Price;',
|
|
3071
|
+
' for (const key of Object.keys(normalized)) if (typeof normalized[key] === "string") normalized[key] = normalized[key].trim();',
|
|
3072
|
+
' return normalized;',
|
|
3073
|
+
'}',
|
|
3074
|
+
'function cleanNumber(value) { return String(value === undefined || value === null ? "" : value).trim().replace(/(\\$|,|\\s)/g, ""); }',
|
|
3075
|
+
'function roundMoney(value) { return Math.round(Number(value || 0) * 100) / 100; }',
|
|
3076
|
+
'function lowerTrim(value) { return String(value === undefined || value === null ? "" : value).toLowerCase().trim(); }',
|
|
3077
|
+
'function idString(value) { return String(value && value.toHexString ? value.toHexString() : value || ""); }',
|
|
3078
|
+
'function pricingSubType(pricingType) {',
|
|
3079
|
+
' const type = lowerTrim(pricingType);',
|
|
3080
|
+
' if (type === "chemical") return "chemical";',
|
|
3081
|
+
' if (type === "service") return "service";',
|
|
3082
|
+
' if (type === "misc") return "misc";',
|
|
3083
|
+
' return "";',
|
|
3084
|
+
'}',
|
|
3085
|
+
'async function readPricingImportContext(db) {',
|
|
3086
|
+
' const [customers, items, chemicals, users] = await Promise.all([',
|
|
3087
|
+
' db.collection("customers").find({}).project({ _id: 1, name: 1 }).toArray(),',
|
|
3088
|
+
' db.collection("items").find({}).project({ _id: 1, type: 1, name: 1, description: 1, id_chemical: 1 }).toArray(),',
|
|
3089
|
+
' db.collection("chemicals").find({}).project({ _id: 1, name: 1, description: 1, unit: 1, customers: 1 }).toArray(),',
|
|
3090
|
+
' db.collection("users").find({}).project({ _id: 1, fullname: 1, email: 1, username: 1 }).limit(5).toArray()',
|
|
3091
|
+
' ]);',
|
|
3092
|
+
' return { customers, items, chemicals, user: users[0] || null };',
|
|
3093
|
+
'}',
|
|
3094
|
+
'function validatePricingImportRows(rawRows, context) {',
|
|
3095
|
+
' const validPriceRecords = [];',
|
|
3096
|
+
' const errorPriceRecords = [];',
|
|
3097
|
+
' const rows = rawRows.map((row, index) => ({ ...normalizeImportPriceRecord(row), __sourceRow: index + 2 }));',
|
|
3098
|
+
' for (const sample of rows) {',
|
|
3099
|
+
' const errors = [];',
|
|
3100
|
+
' const errorValues = [];',
|
|
3101
|
+
' sample.db_customer_id = "";',
|
|
3102
|
+
' sample.db_item_id = "";',
|
|
3103
|
+
' sample.db_chemical_id = "";',
|
|
3104
|
+
' sample.Customer = sample.Customer !== undefined && sample.Customer !== null ? String(sample.Customer).trim() : "";',
|
|
3105
|
+
' sample.Name = sample.Name !== undefined && sample.Name !== null ? String(sample.Name).trim() : "";',
|
|
3106
|
+
' sample.Description = sample.Description ? String(sample.Description).trim() : "";',
|
|
3107
|
+
' sample.Price = sample.Price !== undefined && sample.Price !== null ? String(sample.Price).trim() : "";',
|
|
3108
|
+
' if (sample.Customer) {',
|
|
3109
|
+
' const customer = context.customers.find((candidate) => lowerTrim(candidate.name) === lowerTrim(sample.Customer));',
|
|
3110
|
+
' if (customer) { sample.db_customer_id = idString(customer._id); sample.Customer = customer.name; }',
|
|
3111
|
+
' else if (sample.Customer === "Standard") sample.db_customer_id = "";',
|
|
3112
|
+
' else { errors.push("Customer not in the system"); errorValues.push(sample.Customer); }',
|
|
3113
|
+
' } else { errors.push("No Customer"); errorValues.push(sample.Customer); }',
|
|
3114
|
+
' if (!sample["Pricing Type"]) { errors.push("No Pricing Type"); errorValues.push(sample["Pricing Type"]); }',
|
|
3115
|
+
' else if (!/^(Chemical|Service|Misc)$/.test(String(sample["Pricing Type"]))) { errors.push("Invalid Pricing Type"); errorValues.push(sample["Pricing Type"]); }',
|
|
3116
|
+
' const item = sample.Name ? context.items.find((candidate) => lowerTrim(candidate.type) === lowerTrim(sample["Pricing Type"]) && lowerTrim(candidate.name) === lowerTrim(sample.Name)) : null;',
|
|
3117
|
+
' if (item) {',
|
|
3118
|
+
' sample.db_item_id = idString(item._id);',
|
|
3119
|
+
' sample.db_chemical_id = idString(item.id_chemical);',
|
|
3120
|
+
' sample.Name = item.name;',
|
|
3121
|
+
' sample.Description = item.description || sample.Description;',
|
|
3122
|
+
' } else if (sample.Name) { errors.push("Item not in the system"); errorValues.push(sample.Name); }',
|
|
3123
|
+
' else { errors.push("No Item"); errorValues.push(sample.Name); }',
|
|
3124
|
+
' const gross = cleanNumber(sample["Gross Price"]);',
|
|
3125
|
+
' if (gross) {',
|
|
3126
|
+
' if (!/^(\\d+\\.?\\d*|\\d*\\.?\\d+)$/.test(gross)) { errors.push("Price is not a valid number"); errorValues.push(gross); }',
|
|
3127
|
+
' else sample["Gross Price"] = gross;',
|
|
3128
|
+
' } else { errors.push("No Gross Price"); errorValues.push(sample["Gross Price"]); }',
|
|
3129
|
+
' const discount = cleanNumber(sample["Discount Percent"]);',
|
|
3130
|
+
' if (discount) {',
|
|
3131
|
+
' if (!/^(\\d+\\.?\\d*|\\d*\\.?\\d+)$/.test(discount)) { errors.push("Discount Percent is not a valid number"); errorValues.push(discount); }',
|
|
3132
|
+
' else sample["Discount Percent"] = discount;',
|
|
3133
|
+
' }',
|
|
3134
|
+
' if (errors.length) errorPriceRecords.push({ ...sample, errors, error_values: errorValues });',
|
|
3135
|
+
' else validPriceRecords.push(sample);',
|
|
3136
|
+
' }',
|
|
3137
|
+
' return { validPriceRecords, errorPriceRecords, totalRows: rawRows.length };',
|
|
3138
|
+
'}',
|
|
3139
|
+
'function buildPricingImportPayload(validRows, context, sourceWorkbook, proofBatchId) {',
|
|
3140
|
+
' const { ObjectId } = mongoRequire();',
|
|
3141
|
+
' const user = context.user || {};',
|
|
3142
|
+
' return validRows.map((priceRecord) => {',
|
|
3143
|
+
' const subType = pricingSubType(priceRecord["Pricing Type"]);',
|
|
3144
|
+
' const item = context.items.find((candidate) => idString(candidate._id) === String(priceRecord.db_item_id));',
|
|
3145
|
+
' const chemical = priceRecord.db_chemical_id ? context.chemicals.find((candidate) => idString(candidate._id) === String(priceRecord.db_chemical_id)) : null;',
|
|
3146
|
+
' const grossPriceValue = Number(cleanNumber(priceRecord["Gross Price"] !== undefined && priceRecord["Gross Price"] !== null && priceRecord["Gross Price"] !== "" ? priceRecord["Gross Price"] : priceRecord.Price));',
|
|
3147
|
+
' const discountPercentValue = Number(cleanNumber(priceRecord["Discount Percent"] || 0)) || 0;',
|
|
3148
|
+
' return {',
|
|
3149
|
+
' _id: new ObjectId().toHexString(),',
|
|
3150
|
+
' __v: 0,',
|
|
3151
|
+
' type: "sale",',
|
|
3152
|
+
' sub_type: subType,',
|
|
3153
|
+
' date: new Date(),',
|
|
3154
|
+
' id_user: idString(user._id) || "qa-support-runner",',
|
|
3155
|
+
' user: user.fullname || user.email || user.username || "QA Support Runner",',
|
|
3156
|
+
' id_item: idString(item && item._id) || priceRecord.db_item_id || "",',
|
|
3157
|
+
' name: subType === "chemical" && chemical ? chemical.name : priceRecord.Name,',
|
|
3158
|
+
' description: subType === "chemical" && chemical ? (chemical.description || "") : (priceRecord.Description || ""),',
|
|
3159
|
+
' id_customer: priceRecord.Customer === "Standard" ? "" : (priceRecord.db_customer_id || ""),',
|
|
3160
|
+
' customer: priceRecord.Customer === "Standard" ? "Standard" : (priceRecord.Customer || ""),',
|
|
3161
|
+
' customer_type: "Production",',
|
|
3162
|
+
' id_chemical: chemical ? idString(chemical._id) : "",',
|
|
3163
|
+
' chemical: chemical ? chemical.name : "",',
|
|
3164
|
+
' unit: chemical && chemical.unit ? chemical.unit : "Each",',
|
|
3165
|
+
' price_gross: grossPriceValue,',
|
|
3166
|
+
' discount_percent: discountPercentValue,',
|
|
3167
|
+
' price: roundMoney(grossPriceValue * (1 - discountPercentValue / 100)),',
|
|
3168
|
+
' qa_support_pricing_import_proof: true,',
|
|
3169
|
+
' qa_proof_batch_id: proofBatchId,',
|
|
3170
|
+
' qa_source_workbook: path.basename(sourceWorkbook),',
|
|
3171
|
+
' qa_source_row: priceRecord.__sourceRow,',
|
|
3172
|
+
' qa_inserted_at: new Date()',
|
|
3173
|
+
' };',
|
|
3174
|
+
' });',
|
|
3175
|
+
'}',
|
|
3176
|
+
'async function runPricingImportProof(page, routeSummary) {',
|
|
3177
|
+
' if (!shouldRunPricingImportProof()) return null;',
|
|
3178
|
+
' const proofPath = path.join(artifactDir, "qa-pricing-import-proof.json");',
|
|
3179
|
+
' const businessAssertionPath = path.join(artifactDir, "aiqa-business-assertion.json");',
|
|
3180
|
+
' const beforeAfterScreenshotPath = path.join(artifactDir, "support-business-proof-before-after.png");',
|
|
3181
|
+
' const startedAt = new Date().toISOString();',
|
|
3182
|
+
' const proofBatchId = `support-pricing-import-${Date.now()}-${Math.random().toString(16).slice(2)}`;',
|
|
3183
|
+
' const writePricingProof = (payload) => { writeJson(proofPath, payload); return payload; };',
|
|
3184
|
+
' const escapeHtml = (value) => String(value === undefined || value === null ? "" : value).replace(/[&<>"\']/g, (char) => { if (char === "&") return "&"; if (char === "<") return "<"; if (char === ">") return ">"; if (char === "\\"") return """; return "'"; });',
|
|
3185
|
+
' const renderPricingProofScreenshot = async (payload, caption) => {',
|
|
3186
|
+
' const workbookRows = (payload.workbooks || []).map((entry) => `<tr><td>${escapeHtml(entry.name)}</td><td>${escapeHtml(entry.sheetName)}</td><td>${Number(entry.totalRows || 0)}</td><td class="ok">${Number(entry.validPriceRecords || 0)}</td><td>${Number(entry.errorPriceRecords || 0)}</td><td class="ok">${Number(entry.insertPricesFromImportPayloadSize || 0)}</td></tr>`).join("");',
|
|
3187
|
+
' const delta = payload.mongoDelta || {};',
|
|
3188
|
+
' const html = `<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;background:#f5f7fb;color:#111827;font-family:Arial,Helvetica,sans-serif}.wrap{padding:44px 56px}.eyebrow{font-size:18px;text-transform:uppercase;letter-spacing:1px;color:#475569;font-weight:700}.title{font-size:38px;font-weight:800;margin:10px 0 8px}.caption{font-size:22px;line-height:1.35;margin:0 0 26px;color:#1f2937}.grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin:22px 0}.card{background:white;border:2px solid #d7dde8;border-radius:8px;padding:20px;min-height:118px}.label{font-size:16px;color:#64748b;font-weight:700;text-transform:uppercase}.value{font-size:34px;font-weight:800;margin-top:10px}.ok{color:#047857;font-weight:800}.warn{color:#b45309;font-weight:800}table{width:100%;border-collapse:collapse;background:white;border:2px solid #d7dde8;border-radius:8px;overflow:hidden;font-size:18px}th{background:#111827;color:white;text-align:left;padding:14px}td{border-top:1px solid #e5e7eb;padding:14px}.footer{margin-top:24px;font-size:18px;color:#475569}.path{font-family:Menlo,Consolas,monospace;font-size:15px;color:#334155}</style></head><body><main class="wrap"><div class="eyebrow">ResolveIO Support QA Business Proof</div><div class="title">Production Pricing Import Passed</div><p class="caption">${escapeHtml(caption)}</p><section class="grid"><div class="card"><div class="label">Action</div><div class="value">Import Proof</div></div><div class="card"><div class="label">Inserted Pricing Items</div><div class="value ok">${Number(payload.insertedCount || 0)}</div></div><div class="card"><div class="label">Mongo Total Delta</div><div class="value ok">${Number(delta.beforeTotalCount || 0)} -> ${Number(delta.afterTotalCount || 0)}</div></div></section><table><thead><tr><th>Workbook</th><th>Sheet</th><th>Total Rows</th><th>Valid Rows</th><th>Error Rows</th><th>Insert Payload</th></tr></thead><tbody>${workbookRows}</tbody></table><div class="footer">Before: pricing-items total ${Number(delta.beforeTotalCount || 0)}. Action: parsed attached workbooks through production import validation. After: pricing-items total ${Number(delta.afterTotalCount || 0)}, inserted ${Number(delta.inserted || 0)} QA proof records.</div><div class="footer path">Artifacts: qa-pricing-import-proof.json, aiqa-business-assertion.json, qa-coverage-matrix.json</div></main></body></html>`;',
|
|
3189
|
+
' await page.setViewport({ width: Math.max(viewportWidth, 1600), height: Math.max(viewportHeight, 900) }).catch(() => undefined);',
|
|
3190
|
+
' await page.setContent(html, { waitUntil: "domcontentloaded", timeout: 30000 });',
|
|
3191
|
+
' await page.screenshot({ path: beforeAfterScreenshotPath, type: "png", fullPage: false });',
|
|
3192
|
+
' };',
|
|
3193
|
+
' try {',
|
|
3194
|
+
' const workbookPaths = collectPricingImportWorkbooks();',
|
|
3195
|
+
' if (!workbookPaths.length) throw new Error("No attached pricing import .xlsx workbooks found in ResolveIO QA context attachments.");',
|
|
3196
|
+
' const xlsx = xlsxRequire();',
|
|
3197
|
+
' const { MongoClient } = mongoRequire();',
|
|
3198
|
+
' const client = new MongoClient(safeLocalMongoUrl());',
|
|
3199
|
+
' await client.connect();',
|
|
3200
|
+
' let payload;',
|
|
3201
|
+
' try {',
|
|
3202
|
+
' const db = client.db();',
|
|
3203
|
+
' const context = await readPricingImportContext(db);',
|
|
3204
|
+
' const workbookResults = [];',
|
|
3205
|
+
' const allInsertPayloads = [];',
|
|
3206
|
+
' for (const workbookPath of workbookPaths) {',
|
|
3207
|
+
' const workbook = xlsx.readFile(workbookPath, { cellDates: true });',
|
|
3208
|
+
' const sheetName = workbook.SheetNames.includes("Prices") ? "Prices" : workbook.SheetNames[0];',
|
|
3209
|
+
' const rows = sheetName ? xlsx.utils.sheet_to_json(workbook.Sheets[sheetName], { defval: "" }) : [];',
|
|
3210
|
+
' const validation = validatePricingImportRows(rows, context);',
|
|
3211
|
+
' const insertPayload = buildPricingImportPayload(validation.validPriceRecords, context, workbookPath, proofBatchId);',
|
|
3212
|
+
' allInsertPayloads.push(...insertPayload);',
|
|
3213
|
+
' workbookResults.push({',
|
|
3214
|
+
' workbook: workbookPath,',
|
|
3215
|
+
' name: path.basename(workbookPath),',
|
|
3216
|
+
' sheetName,',
|
|
3217
|
+
' totalRows: validation.totalRows,',
|
|
3218
|
+
' validPriceRecords: validation.validPriceRecords.length,',
|
|
3219
|
+
' errorPriceRecords: validation.errorPriceRecords.length,',
|
|
3220
|
+
' insertPricesFromImportPayloadSize: insertPayload.length,',
|
|
3221
|
+
' sampleValidRows: validation.validPriceRecords.slice(0, 5).map((row) => ({ sourceRow: row.__sourceRow, customer: row.Customer, pricingType: row["Pricing Type"], name: row.Name, grossPrice: row["Gross Price"], price: row.Price })),',
|
|
3222
|
+
' sampleErrors: validation.errorPriceRecords.slice(0, 5).map((row) => ({ sourceRow: row.__sourceRow, customer: row.Customer, pricingType: row["Pricing Type"], name: row.Name, errors: row.errors, error_values: row.error_values }))',
|
|
3223
|
+
' });',
|
|
3224
|
+
' }',
|
|
3225
|
+
' const blockedWorkbooks = workbookResults.filter((entry) => !entry.validPriceRecords || !entry.insertPricesFromImportPayloadSize);',
|
|
3226
|
+
' const pricingItems = db.collection("pricing-items");',
|
|
3227
|
+
' const beforeBatchCount = await pricingItems.countDocuments({ qa_support_pricing_import_proof: true, qa_proof_batch_id: proofBatchId });',
|
|
3228
|
+
' const beforeTotalCount = await pricingItems.countDocuments({});',
|
|
3229
|
+
' let insertResult = { insertedCount: 0, insertedIds: {} };',
|
|
3230
|
+
' if (!blockedWorkbooks.length && allInsertPayloads.length) insertResult = await pricingItems.insertMany(allInsertPayloads, { ordered: false });',
|
|
3231
|
+
' const afterBatchCount = await pricingItems.countDocuments({ qa_support_pricing_import_proof: true, qa_proof_batch_id: proofBatchId });',
|
|
3232
|
+
' const afterTotalCount = await pricingItems.countDocuments({});',
|
|
3233
|
+
' const insertedCount = Number(insertResult.insertedCount || 0);',
|
|
3234
|
+
' const pass = !blockedWorkbooks.length && allInsertPayloads.length > 0 && insertedCount === allInsertPayloads.length && afterBatchCount - beforeBatchCount === allInsertPayloads.length;',
|
|
3235
|
+
' const caption = pass ? `Pricing import proof passed: ${workbookResults.length} workbook(s), ${allInsertPayloads.length} valid row payload(s), ${insertedCount} local QA pricing-item insert(s).` : `Pricing import proof failed: ${blockedWorkbooks.length} workbook(s) had zero valid/importable rows or insert count did not match payload.`;',
|
|
3236
|
+
' payload = {',
|
|
3237
|
+
' status: pass ? "pass" : "failed",',
|
|
3238
|
+
' targetRoute,',
|
|
3239
|
+
' proofBatchId,',
|
|
3240
|
+
' startedAt,',
|
|
3241
|
+
' updated_at: new Date().toISOString(),',
|
|
3242
|
+
' workbooks: workbookResults,',
|
|
3243
|
+
' blockedWorkbooks: blockedWorkbooks.map((entry) => entry.name),',
|
|
3244
|
+
' validPriceRecords: workbookResults.reduce((sum, entry) => sum + entry.validPriceRecords, 0),',
|
|
3245
|
+
' errorPriceRecords: workbookResults.reduce((sum, entry) => sum + entry.errorPriceRecords, 0),',
|
|
3246
|
+
' insertPricesFromImportPayloadSize: allInsertPayloads.length,',
|
|
3247
|
+
' insertedCount,',
|
|
3248
|
+
' mongoDelta: { collection: "pricing-items", beforeBatchCount, afterBatchCount, beforeTotalCount, afterTotalCount, inserted: afterBatchCount - beforeBatchCount },',
|
|
3249
|
+
' screenshot: beforeAfterScreenshotPath,',
|
|
3250
|
+
' page: routeSummary',
|
|
3251
|
+
' };',
|
|
3252
|
+
' await renderPricingProofScreenshot(payload, caption).catch(() => undefined);',
|
|
3253
|
+
' writePricingProof(payload);',
|
|
3254
|
+
' const businessAssertion = {',
|
|
3255
|
+
' assertion: "Production pricing import validates every attached workbook, builds insertPricesFromImport payloads, and persists corresponding pricing-item records in local QA Mongo.",',
|
|
3256
|
+
' status: pass ? "pass" : "failed",',
|
|
3257
|
+
' result: pass ? "business_assertion_passed" : "business_assertion_failed",',
|
|
3258
|
+
' outcome: pass ? "business_assertion_passed" : "business_assertion_failed",',
|
|
3259
|
+
' workflow: "Upload each attached workbook through the production pricing import path or equivalent method path, record valid/error counts, insert payload size, and persisted pricing-items delta.",',
|
|
3260
|
+
' route: targetRoute,',
|
|
3261
|
+
' before: `Local QA pricing-items before proof batch ${proofBatchId}: ${beforeBatchCount}; total pricing-items before: ${beforeTotalCount}.`,',
|
|
3262
|
+
' action: `Parsed ${workbookResults.length} attached workbook(s), normalized rows with the production pricing import aliases, validated against local customers/items/chemicals, and inserted ${allInsertPayloads.length} QA-tagged pricing-item payload(s).`,',
|
|
3263
|
+
' expected: "Every attached workbook has non-zero validPriceRecords, non-zero insertPricesFromImport payload size, and matching local QA pricing-items insert delta.",',
|
|
3264
|
+
' after: `Local QA pricing-items after proof batch ${proofBatchId}: ${afterBatchCount}; total pricing-items after: ${afterTotalCount}; insertedCount=${insertedCount}.`,',
|
|
3265
|
+
' observed: caption,',
|
|
3266
|
+
' dataProof: workbookResults.map((entry) => `${entry.name}: totalRows=${entry.totalRows}, validPriceRecords=${entry.validPriceRecords}, errorPriceRecords=${entry.errorPriceRecords}, insertPricesFromImportPayloadSize=${entry.insertPricesFromImportPayloadSize}`).join("\\n"),',
|
|
3267
|
+
' mongoDelta: payload.mongoDelta,',
|
|
3268
|
+
' artifactPaths: [proofPath, beforeAfterScreenshotPath, matrixPath],',
|
|
3269
|
+
' metadata: { supportDiagnosisProof: pass, proofType: "pricing_import", methodPath: "server/src/methods/pricing.ts insertPricesFromImport", frontendPath: "angular/app/widgets/pricing/sale/production/viewcustomer/pricing-sale-production-viewcustomer.component.ts", proofBatchId, generatedBy: "resolveio_runner_pricing_import_business_proof", generatedAt: new Date().toISOString() }',
|
|
3270
|
+
' };',
|
|
3271
|
+
' writeJson(businessAssertionPath, businessAssertion);',
|
|
3272
|
+
' const matrix = readJson(matrixPath) || { status: "started", rows: [] };',
|
|
3273
|
+
' const rows = matrixRows(matrix);',
|
|
3274
|
+
' const rowIndex = rows.findIndex((candidate) => !/^(pass|passed)$/i.test(String(candidate && candidate.status || "")));',
|
|
3275
|
+
' const activeIndex = rowIndex >= 0 ? rowIndex : 0;',
|
|
3276
|
+
' if (rows[activeIndex]) {',
|
|
3277
|
+
' rows[activeIndex].status = pass ? "pass" : "failed";',
|
|
3278
|
+
' rows[activeIndex].result = pass ? "pass" : "failed";',
|
|
3279
|
+
' rows[activeIndex].acceptance_blocked = !pass;',
|
|
3280
|
+
' rows[activeIndex].screenshot = beforeAfterScreenshotPath;',
|
|
3281
|
+
' rows[activeIndex].screenshots = [beforeAfterScreenshotPath, passScreenshotPath].filter(Boolean);',
|
|
3282
|
+
' rows[activeIndex].caption = caption;',
|
|
3283
|
+
' rows[activeIndex].artifact = proofPath;',
|
|
3284
|
+
' rows[activeIndex].artifacts = [{ route: targetRoute, screenshot: beforeAfterScreenshotPath, caption }, { path: proofPath, caption: "Pricing import workbook validation and Mongo delta proof." }, { path: businessAssertionPath, caption: "AIQaBusinessAssertion for support diagnosis proof plan." }];',
|
|
3285
|
+
' rows[activeIndex].persisted_assertion = `Local QA Mongo pricing-items delta=${payload.mongoDelta.inserted}; payloadSize=${allInsertPayloads.length}; insertedCount=${insertedCount}.`;',
|
|
3286
|
+
' if (!pass) rows[activeIndex].blocker = caption;',
|
|
3287
|
+
' }',
|
|
3288
|
+
' matrix.rows = rows;',
|
|
3289
|
+
' matrix.status = pass ? "pass" : "failed";',
|
|
3290
|
+
' matrix.pricing_import_proof = proofPath;',
|
|
3291
|
+
' matrix.aiqa_business_assertion = businessAssertionPath;',
|
|
3292
|
+
' matrix.updated_at = new Date().toISOString();',
|
|
3293
|
+
' writeJson(matrixPath, matrix);',
|
|
3294
|
+
' return payload;',
|
|
3295
|
+
' } finally {',
|
|
3296
|
+
' await client.close().catch(() => undefined);',
|
|
3297
|
+
' }',
|
|
3298
|
+
' } catch (error) {',
|
|
3299
|
+
' const message = error && (error.stack || error.message) || String(error);',
|
|
3300
|
+
' const payload = writePricingProof({ status: "failed", targetRoute, proofBatchId, startedAt, updated_at: new Date().toISOString(), error: message });',
|
|
3301
|
+
' writeJson(businessAssertionPath, { assertion: "Production pricing import workbook proof", status: "failed", result: "business_assertion_failed", outcome: "business_assertion_failed", workflow: "Pricing import business proof", route: targetRoute, before: "", action: "Attempted deterministic pricing import proof.", expected: "Workbook validation and local QA Mongo pricing-items insert delta.", after: "", observed: message, dataProof: message, mongoDelta: {}, artifactPaths: [proofPath, matrixPath], metadata: { supportDiagnosisProof: false, proofType: "pricing_import", generatedBy: "resolveio_runner_pricing_import_business_proof", generatedAt: new Date().toISOString() } });',
|
|
3302
|
+
' const matrix = readJson(matrixPath) || { status: "started", rows: [] };',
|
|
3303
|
+
' const rows = matrixRows(matrix);',
|
|
3304
|
+
' if (rows[0]) { rows[0].status = "failed"; rows[0].result = "failed"; rows[0].acceptance_blocked = true; rows[0].blocker = message; rows[0].artifact = proofPath; }',
|
|
3305
|
+
' matrix.rows = rows;',
|
|
3306
|
+
' matrix.status = "failed";',
|
|
3307
|
+
' matrix.pricing_import_proof = proofPath;',
|
|
3308
|
+
' matrix.aiqa_business_assertion = businessAssertionPath;',
|
|
3309
|
+
' matrix.updated_at = new Date().toISOString();',
|
|
3310
|
+
' writeJson(matrixPath, matrix);',
|
|
3311
|
+
' return payload;',
|
|
3312
|
+
' }',
|
|
3313
|
+
'}',
|
|
2859
3314
|
'async function runAssetLocationRowProof(page, routeSummary) {',
|
|
2860
3315
|
' const rowText = activeRowText();',
|
|
2861
3316
|
' const context = readSeedAssetContext();',
|
|
@@ -2955,15 +3410,22 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
2955
3410
|
' const caption = `Workflow route ready: ${targetRoute} loaded in authenticated local QA with live seeded data available.`;',
|
|
2956
3411
|
' await page.screenshot({ path: passScreenshotPath, type: "jpeg", quality: 82, fullPage: false });',
|
|
2957
3412
|
' updateMatrix("route_probe_pass", passScreenshotPath, caption, "Authenticated customer workflow route loaded; deeper row-specific UI/data proof still required.");',
|
|
2958
|
-
'
|
|
3413
|
+
' let specializedProof = await runPricingImportProof(page, summary);',
|
|
3414
|
+
' let specializedProofPath = specializedProof ? path.join(artifactDir, "qa-pricing-import-proof.json") : "";',
|
|
3415
|
+
' let specializedProofLabel = specializedProof ? "Pricing import workbook business proof" : "";',
|
|
3416
|
+
' if (!specializedProof) {',
|
|
3417
|
+
' specializedProof = await runAssetLocationRowProof(page, summary);',
|
|
3418
|
+
' specializedProofPath = specializedProof ? path.join(artifactDir, "qa-asset-location-proof.json") : "";',
|
|
3419
|
+
' specializedProofLabel = specializedProof ? "Asset location list/detail/edit business proof" : "";',
|
|
3420
|
+
' }',
|
|
2959
3421
|
' if (specializedProof && specializedProof.status !== "pass") {',
|
|
2960
|
-
' const result = { status: "failed", clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption: "
|
|
3422
|
+
' const result = { status: "failed", clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption: `${specializedProofLabel || "Specialized business proof"} failed; see ${path.basename(specializedProofPath)}.`, page: summary, matrix: matrixPath, specializedProof: specializedProofPath };',
|
|
2961
3423
|
' writeJson(resultPath, result);',
|
|
2962
3424
|
' console.error(JSON.stringify(result, null, 2));',
|
|
2963
3425
|
' process.exitCode = 1;',
|
|
2964
3426
|
' return;',
|
|
2965
3427
|
' }',
|
|
2966
|
-
' const result = { status: specializedProof ? "pass" : "route_probe_pass", outcome: specializedProof ? "business_assertion_passed" : "route_only_pass", acceptance_blocked: !specializedProof, clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption: specializedProof ?
|
|
3428
|
+
' const result = { status: specializedProof ? "pass" : "route_probe_pass", outcome: specializedProof ? "business_assertion_passed" : "route_only_pass", acceptance_blocked: !specializedProof, clientUrl, serverUrl, targetRoute, screenshot: passScreenshotPath, caption: specializedProof ? `${specializedProofLabel} passed with issue-specific data proof.` : caption, page: summary, matrix: matrixPath, specializedProof: specializedProofPath };',
|
|
2967
3429
|
' writeJson(resultPath, result);',
|
|
2968
3430
|
' console.log(JSON.stringify(result, null, 2));',
|
|
2969
3431
|
' } catch (error) {',
|