jupiter-dynamic-forms 1.20.2 → 1.20.4

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 (33) hide show
  1. package/dist/core/concept-tree.d.ts.map +1 -1
  2. package/dist/core/dynamic-form.d.ts +49 -1
  3. package/dist/core/dynamic-form.d.ts.map +1 -1
  4. package/dist/core/form-field.d.ts.map +1 -1
  5. package/dist/core/form-section.d.ts +4 -0
  6. package/dist/core/form-section.d.ts.map +1 -1
  7. package/dist/core/formula-validation-dialog.d.ts +32 -0
  8. package/dist/core/formula-validation-dialog.d.ts.map +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +484 -183
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +1846 -282
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/schema/types.d.ts +33 -0
  16. package/dist/schema/types.d.ts.map +1 -1
  17. package/dist/schema/xbrl-types.d.ts +67 -0
  18. package/dist/schema/xbrl-types.d.ts.map +1 -1
  19. package/dist/utils/formula-constants.d.ts +7 -0
  20. package/dist/utils/formula-constants.d.ts.map +1 -0
  21. package/dist/utils/formula-expression-evaluator.d.ts +64 -0
  22. package/dist/utils/formula-expression-evaluator.d.ts.map +1 -0
  23. package/dist/utils/formula-precondition-evaluator.d.ts +33 -0
  24. package/dist/utils/formula-precondition-evaluator.d.ts.map +1 -0
  25. package/dist/utils/formula-resolution-context.d.ts +53 -0
  26. package/dist/utils/formula-resolution-context.d.ts.map +1 -0
  27. package/dist/utils/formula-variable-resolver.d.ts +48 -0
  28. package/dist/utils/formula-variable-resolver.d.ts.map +1 -0
  29. package/dist/utils/total-group-resolver.d.ts +68 -0
  30. package/dist/utils/total-group-resolver.d.ts.map +1 -0
  31. package/dist/utils/total-label.d.ts +9 -0
  32. package/dist/utils/total-label.d.ts.map +1 -0
  33. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1668,7 +1668,8 @@ const form$1 = {
1668
1668
  download: "Download",
1669
1669
  cancelValidation: "Cancel Validation",
1670
1670
  lastValidationResults: "Last Validation Results",
1671
- validationInProgress: "Validation In Progress"
1671
+ validationInProgress: "Validation In Progress",
1672
+ quickValidate: "Quick Validate"
1672
1673
  };
1673
1674
  const filter$1 = {
1674
1675
  selectRoles: "Select Roles",
@@ -1817,6 +1818,15 @@ const calculationWarning$1 = {
1817
1818
  added: "Added",
1818
1819
  subtracted: "Subtracted (opposite balance)"
1819
1820
  };
1821
+ const formulaValidation$1 = {
1822
+ dialogTitle: "Formula check results",
1823
+ allPassed: "All formula checks passed",
1824
+ summaryCount: "{{failed}} of {{total}} checks failed",
1825
+ summaryCountBreakdown: "{{warnings}} warnings, {{errors}} errors",
1826
+ severityWarning: "Warning",
1827
+ severityError: "Error",
1828
+ close: "Close"
1829
+ };
1820
1830
  const enTranslations = {
1821
1831
  conceptInfo: conceptInfo$1,
1822
1832
  form: form$1,
@@ -1828,7 +1838,8 @@ const enTranslations = {
1828
1838
  validation: validation$1,
1829
1839
  xbrlValidation: xbrlValidation$1,
1830
1840
  error: error$1,
1831
- calculationWarning: calculationWarning$1
1841
+ calculationWarning: calculationWarning$1,
1842
+ formulaValidation: formulaValidation$1
1832
1843
  };
1833
1844
  const conceptInfo = {
1834
1845
  title: "Conceptinformatie",
@@ -1863,7 +1874,8 @@ const form = {
1863
1874
  download: "Downloaden",
1864
1875
  cancelValidation: "Validatie annuleren",
1865
1876
  lastValidationResults: "Laatste validatieresultaten",
1866
- validationInProgress: "Validatie bezig"
1877
+ validationInProgress: "Validatie bezig",
1878
+ quickValidate: "Snel valideren"
1867
1879
  };
1868
1880
  const filter = {
1869
1881
  selectRoles: "Rollen selecteren",
@@ -2012,6 +2024,15 @@ const calculationWarning = {
2012
2024
  added: "Opgeteld",
2013
2025
  subtracted: "Afgetrokken (tegengestelde balans)"
2014
2026
  };
2027
+ const formulaValidation = {
2028
+ dialogTitle: "Resultaten formulecontrole",
2029
+ allPassed: "Alle formulecontroles geslaagd",
2030
+ summaryCount: "{{failed}} van {{total}} controles mislukt",
2031
+ summaryCountBreakdown: "{{warnings}} waarschuwingen, {{errors}} fouten",
2032
+ severityWarning: "Waarschuwing",
2033
+ severityError: "Fout",
2034
+ close: "Sluiten"
2035
+ };
2015
2036
  const nlTranslations = {
2016
2037
  conceptInfo,
2017
2038
  form,
@@ -2023,7 +2044,8 @@ const nlTranslations = {
2023
2044
  validation,
2024
2045
  xbrlValidation,
2025
2046
  error,
2026
- calculationWarning
2047
+ calculationWarning,
2048
+ formulaValidation
2027
2049
  };
2028
2050
  const translations = {
2029
2051
  en: enTranslations,
@@ -2102,6 +2124,9 @@ class I18n {
2102
2124
  }
2103
2125
  }
2104
2126
  I18n.currentLanguage = "en";
2127
+ function isTotalLabel$1(preferredLabel) {
2128
+ return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("totallabel"));
2129
+ }
2105
2130
  class DraftStorageService {
2106
2131
  constructor(useSessionStorage = false) {
2107
2132
  this.DRAFT_DATA_KEY = "jupiter-form-draft-data";
@@ -2261,7 +2286,7 @@ class DraftStorageService {
2261
2286
  /**
2262
2287
  * Create metadata snapshot from current form state
2263
2288
  */
2264
- createMetadataSnapshot(periodStartDate, periodEndDate, language, selectedRoleIds, allSections, typedMemberData, periodPreferences, periodData, unitData, reportingLanguage = "en", repeatCounts, decimalsData, roleCompletedStates, hiddenColumns) {
2289
+ createMetadataSnapshot(periodStartDate, periodEndDate, language, selectedRoleIds, allSections, typedMemberData, periodPreferences, periodData, unitData, reportingLanguage = "en", repeatCounts, decimalsData, roleCompletedStates, hiddenColumns, totalManuallyEditedData) {
2265
2290
  return {
2266
2291
  periodStartDate,
2267
2292
  periodEndDate,
@@ -2278,6 +2303,7 @@ class DraftStorageService {
2278
2303
  repeatCounts,
2279
2304
  roleCompletedStates,
2280
2305
  hiddenColumns,
2306
+ totalManuallyEditedData,
2281
2307
  schemaVersion: this.STORAGE_VERSION
2282
2308
  };
2283
2309
  }
@@ -2300,6 +2326,882 @@ class DraftStorageService {
2300
2326
  return { compatible, warnings };
2301
2327
  }
2302
2328
  }
2329
+ const CALCULATION_TOLERANCE = 0.01;
2330
+ class FormulaExpressionError extends Error {
2331
+ constructor(message, test, reason = "unsupported-syntax") {
2332
+ super(message);
2333
+ this.test = test;
2334
+ this.reason = reason;
2335
+ this.name = "FormulaExpressionError";
2336
+ }
2337
+ }
2338
+ const TOKEN_PATTERN = /\s*(?:(\$[A-Za-z_][\w-]*)|(-?\d+(?:\.\d+)?)|([A-Za-z][\w:-]*)|('[^']*')|(\()|(\))|(\+)|(-)|(=))/y;
2339
+ function tokenize(test) {
2340
+ const tokens = [];
2341
+ TOKEN_PATTERN.lastIndex = 0;
2342
+ let index = 0;
2343
+ while (index < test.length) {
2344
+ TOKEN_PATTERN.lastIndex = index;
2345
+ const match = TOKEN_PATTERN.exec(test);
2346
+ if (!match || match.index !== index) {
2347
+ if (/^\s*$/.test(test.slice(index)))
2348
+ break;
2349
+ throw new FormulaExpressionError(`Unrecognized token at position ${index}: "${test.slice(index, index + 10)}..."`, test, "unsupported-syntax");
2350
+ }
2351
+ const [full, variable, number, ident, string, lparen, rparen, plus, minus, equals] = match;
2352
+ if (variable)
2353
+ tokens.push({ type: "VARIABLE", value: variable.slice(1) });
2354
+ else if (number)
2355
+ tokens.push({ type: "NUMBER", value: number });
2356
+ else if (ident)
2357
+ tokens.push({ type: "IDENT", value: ident });
2358
+ else if (string)
2359
+ tokens.push({ type: "STRING", value: string.slice(1, -1) });
2360
+ else if (lparen)
2361
+ tokens.push({ type: "LPAREN", value: lparen });
2362
+ else if (rparen)
2363
+ tokens.push({ type: "RPAREN", value: rparen });
2364
+ else if (plus)
2365
+ tokens.push({ type: "PLUS", value: plus });
2366
+ else if (minus)
2367
+ tokens.push({ type: "MINUS", value: minus });
2368
+ else if (equals)
2369
+ tokens.push({ type: "EQUALS", value: equals });
2370
+ index += full.length;
2371
+ }
2372
+ tokens.push({ type: "EOF", value: "" });
2373
+ return tokens;
2374
+ }
2375
+ class Parser {
2376
+ constructor(tokens, test) {
2377
+ this.tokens = tokens;
2378
+ this.test = test;
2379
+ this.pos = 0;
2380
+ }
2381
+ peek() {
2382
+ return this.tokens[this.pos];
2383
+ }
2384
+ next() {
2385
+ return this.tokens[this.pos++];
2386
+ }
2387
+ expect(type) {
2388
+ const token = this.next();
2389
+ if (token.type !== type) {
2390
+ throw new FormulaExpressionError(
2391
+ `Expected ${type} but got ${token.type} ("${token.value}")`,
2392
+ this.test,
2393
+ "parse-error"
2394
+ );
2395
+ }
2396
+ return token;
2397
+ }
2398
+ parseBoolExpr() {
2399
+ const node = this.parseOrExpr();
2400
+ if (this.peek().type !== "EOF") {
2401
+ throw new FormulaExpressionError(
2402
+ `Unexpected trailing token "${this.peek().value}"`,
2403
+ this.test,
2404
+ "unsupported-syntax"
2405
+ );
2406
+ }
2407
+ return node;
2408
+ }
2409
+ parseOrExpr() {
2410
+ const terms = [this.parseAndExpr()];
2411
+ while (this.peek().type === "IDENT" && this.peek().value === "or") {
2412
+ this.next();
2413
+ terms.push(this.parseAndExpr());
2414
+ }
2415
+ return terms.length === 1 ? terms[0] : { kind: "or", terms };
2416
+ }
2417
+ parseAndExpr() {
2418
+ const terms = [this.parseBoolTerm()];
2419
+ while (this.peek().type === "IDENT" && this.peek().value === "and") {
2420
+ this.next();
2421
+ terms.push(this.parseBoolTerm());
2422
+ }
2423
+ return terms.length === 1 ? terms[0] : { kind: "and", terms };
2424
+ }
2425
+ parseBoolTerm() {
2426
+ const token = this.peek();
2427
+ if (token.type === "IDENT" && token.value === "not") {
2428
+ this.next();
2429
+ this.expect("LPAREN");
2430
+ const arg = this.parseOrExpr();
2431
+ this.expect("RPAREN");
2432
+ return { kind: "not", arg };
2433
+ }
2434
+ if (token.type === "IDENT" && token.value === "empty") {
2435
+ this.next();
2436
+ this.expect("LPAREN");
2437
+ const variable = this.expect("VARIABLE").value;
2438
+ this.expect("RPAREN");
2439
+ return { kind: "empty", variable };
2440
+ }
2441
+ if (token.type === "IDENT" && token.value === "xff:has-fallback-value") {
2442
+ this.next();
2443
+ this.expect("LPAREN");
2444
+ const inner = this.next();
2445
+ if (inner.type !== "IDENT" || inner.value !== "xs:QName") {
2446
+ throw new FormulaExpressionError(
2447
+ `Expected xs:QName(...) inside xff:has-fallback-value(...), got "${inner.value}"`,
2448
+ this.test,
2449
+ "unsupported-syntax"
2450
+ );
2451
+ }
2452
+ this.expect("LPAREN");
2453
+ const variableName = this.expect("STRING").value;
2454
+ this.expect("RPAREN");
2455
+ this.expect("RPAREN");
2456
+ return { kind: "hasFallbackValue", variableName };
2457
+ }
2458
+ const left = this.parseValueExpr();
2459
+ const opToken = this.peek();
2460
+ if (opToken.type === "EQUALS") {
2461
+ this.next();
2462
+ return { kind: "comparison", op: "=", left, right: this.parseValueExpr() };
2463
+ }
2464
+ if (opToken.type === "IDENT" && opToken.value === "eq") {
2465
+ this.next();
2466
+ return { kind: "comparison", op: "eq", left, right: this.parseValueExpr() };
2467
+ }
2468
+ throw new FormulaExpressionError(
2469
+ `Expected a comparison operator ("=" or "eq") after value expression, got "${opToken.value}"`,
2470
+ this.test,
2471
+ "unsupported-syntax"
2472
+ );
2473
+ }
2474
+ parseValueExpr() {
2475
+ const terms = [];
2476
+ let sign = 1;
2477
+ if (this.peek().type === "PLUS") {
2478
+ this.next();
2479
+ } else if (this.peek().type === "MINUS") {
2480
+ this.next();
2481
+ sign = -1;
2482
+ }
2483
+ terms.push({ sign, term: this.parseValueTerm() });
2484
+ while (this.peek().type === "PLUS" || this.peek().type === "MINUS") {
2485
+ const opSign = this.next().type === "PLUS" ? 1 : -1;
2486
+ terms.push({ sign: opSign, term: this.parseValueTerm() });
2487
+ }
2488
+ return terms.length === 1 && terms[0].sign === 1 ? terms[0].term : { kind: "additive", terms };
2489
+ }
2490
+ parseValueTerm() {
2491
+ const token = this.peek();
2492
+ if (token.type === "NUMBER") {
2493
+ this.next();
2494
+ return { kind: "number", value: Number(token.value) };
2495
+ }
2496
+ if (token.type === "VARIABLE") {
2497
+ this.next();
2498
+ return { kind: "variable", name: token.value };
2499
+ }
2500
+ if (token.type === "IDENT" && token.value === "sum") {
2501
+ this.next();
2502
+ this.expect("LPAREN");
2503
+ const variable = this.expect("VARIABLE").value;
2504
+ this.expect("RPAREN");
2505
+ return { kind: "sum", variable };
2506
+ }
2507
+ if (token.type === "LPAREN") {
2508
+ this.next();
2509
+ const inner = this.parseValueExpr();
2510
+ this.expect("RPAREN");
2511
+ return inner;
2512
+ }
2513
+ throw new FormulaExpressionError(
2514
+ `Expected a number, variable, sum(...), or parenthesized expression, got "${token.value}"`,
2515
+ this.test,
2516
+ "unsupported-syntax"
2517
+ );
2518
+ }
2519
+ }
2520
+ function lookup(bindings, name) {
2521
+ return bindings.get(name);
2522
+ }
2523
+ function evalSum(variable, bindings) {
2524
+ const binding = lookup(bindings, variable);
2525
+ if (binding === void 0)
2526
+ return 0;
2527
+ if (Array.isArray(binding))
2528
+ return binding.reduce((total, value) => total + value, 0);
2529
+ return binding;
2530
+ }
2531
+ function evalValue(node, bindings, test) {
2532
+ switch (node.kind) {
2533
+ case "number":
2534
+ return node.value;
2535
+ case "sum":
2536
+ return evalSum(node.variable, bindings);
2537
+ case "variable": {
2538
+ const binding = lookup(bindings, node.name);
2539
+ if (binding === void 0) {
2540
+ throw new FormulaExpressionError(
2541
+ `Variable "$${node.name}" has no bound fact and cannot be evaluated directly (only sum()/empty() tolerate an unbound variable)`,
2542
+ test,
2543
+ "unbound-variable"
2544
+ );
2545
+ }
2546
+ if (Array.isArray(binding)) {
2547
+ throw new FormulaExpressionError(
2548
+ `Variable "$${node.name}" is a sequence and must be wrapped in sum(...) to be used in arithmetic`,
2549
+ test,
2550
+ "unsupported-syntax"
2551
+ );
2552
+ }
2553
+ return binding;
2554
+ }
2555
+ case "additive":
2556
+ return node.terms.reduce((total, { sign, term }) => total + sign * evalValue(term, bindings, test), 0);
2557
+ }
2558
+ }
2559
+ function evalBool(node, bindings, test, fallbackUsage) {
2560
+ switch (node.kind) {
2561
+ case "or":
2562
+ return node.terms.some((term) => evalBool(term, bindings, test, fallbackUsage));
2563
+ case "and":
2564
+ return node.terms.every((term) => evalBool(term, bindings, test, fallbackUsage));
2565
+ case "not":
2566
+ return !evalBool(node.arg, bindings, test, fallbackUsage);
2567
+ case "empty": {
2568
+ const binding = lookup(bindings, node.variable);
2569
+ return binding === void 0 || Array.isArray(binding) && binding.length === 0;
2570
+ }
2571
+ case "hasFallbackValue": {
2572
+ const usedFallback = fallbackUsage.get(node.variableName);
2573
+ if (usedFallback === void 0) {
2574
+ throw new FormulaExpressionError(
2575
+ `xff:has-fallback-value(xs:QName('${node.variableName}')) could not be evaluated — no fallback-usage tracked for this variable (either not one of this assertion's FACT_VARIABLEs, or it matched zero facts with no fallbackValue declared)`,
2576
+ test,
2577
+ "unbound-variable"
2578
+ );
2579
+ }
2580
+ return usedFallback;
2581
+ }
2582
+ case "comparison": {
2583
+ const left = evalValue(node.left, bindings, test);
2584
+ const right = evalValue(node.right, bindings, test);
2585
+ return Math.abs(left - right) < CALCULATION_TOLERANCE;
2586
+ }
2587
+ }
2588
+ }
2589
+ function evaluateFormulaTest(test, bindings, fallbackUsage = /* @__PURE__ */ new Map()) {
2590
+ const tokens = tokenize(test);
2591
+ const ast = new Parser(tokens, test).parseBoolExpr();
2592
+ return evalBool(ast, bindings, test, fallbackUsage);
2593
+ }
2594
+ const EXISTENCE_TEST_PATTERN = /^\.\s*(eq|ne|gt|lt|ge|le)\s+(-?\d+(?:\.\d+)?)$/;
2595
+ function compareExistence(op, count, threshold) {
2596
+ switch (op) {
2597
+ case "eq":
2598
+ return count === threshold;
2599
+ case "ne":
2600
+ return count !== threshold;
2601
+ case "gt":
2602
+ return count > threshold;
2603
+ case "lt":
2604
+ return count < threshold;
2605
+ case "ge":
2606
+ return count >= threshold;
2607
+ case "le":
2608
+ return count <= threshold;
2609
+ }
2610
+ }
2611
+ function evaluateExistenceTest(test, matchedFactCount) {
2612
+ const trimmed = (test ?? "").trim();
2613
+ if (trimmed === "") {
2614
+ return matchedFactCount > 0;
2615
+ }
2616
+ const clauses = trimmed.split(/\bor\b/);
2617
+ return clauses.some((clause) => {
2618
+ const match = EXISTENCE_TEST_PATTERN.exec(clause.trim());
2619
+ if (!match) {
2620
+ throw new FormulaExpressionError(`Unsupported existence-assertion test shape: "${clause.trim()}"`, trimmed, "unsupported-syntax");
2621
+ }
2622
+ const [, op, thresholdText] = match;
2623
+ return compareExistence(op, matchedFactCount, Number(thresholdText));
2624
+ });
2625
+ }
2626
+ function isTotalLabel(preferredLabel) {
2627
+ return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("totallabel"));
2628
+ }
2629
+ function isNegatedTotalLabel(preferredLabel) {
2630
+ return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("negatedtotallabel"));
2631
+ }
2632
+ function resolveValueNode(node, accessors, depth = 0) {
2633
+ if (!accessors.isAbstract(node))
2634
+ return node;
2635
+ if (depth > 5)
2636
+ return null;
2637
+ const children = accessors.getChildren(node);
2638
+ const nestedTotal = children.find((child) => isTotalLabel(accessors.getPreferredLabel(child)));
2639
+ if (!nestedTotal)
2640
+ return null;
2641
+ return resolveValueNode(nestedTotal, accessors, depth + 1);
2642
+ }
2643
+ function resolveTotalGroupMembers(nodes, i2, lastTotalIdx, rawSiblings, accessors) {
2644
+ if (lastTotalIdx < 0)
2645
+ return rawSiblings;
2646
+ const node = nodes[i2];
2647
+ if (isNegatedTotalLabel(accessors.getPreferredLabel(node))) {
2648
+ return rawSiblings.length > 0 ? rawSiblings : [nodes[lastTotalIdx]];
2649
+ }
2650
+ if (rawSiblings.length > 0) {
2651
+ return [nodes[lastTotalIdx], ...rawSiblings];
2652
+ }
2653
+ if (isNegatedTotalLabel(accessors.getPreferredLabel(nodes[lastTotalIdx]))) {
2654
+ let grandTotalIdx = -1;
2655
+ for (let k = lastTotalIdx - 1; k >= 0; k--) {
2656
+ if (isTotalLabel(accessors.getPreferredLabel(nodes[k]))) {
2657
+ grandTotalIdx = k;
2658
+ break;
2659
+ }
2660
+ }
2661
+ return grandTotalIdx >= 0 ? [nodes[grandTotalIdx], nodes[lastTotalIdx]] : [nodes[lastTotalIdx]];
2662
+ }
2663
+ return [nodes[lastTotalIdx]];
2664
+ }
2665
+ function buildTotalGroupChildrenIndex(roots, accessors) {
2666
+ const index = /* @__PURE__ */ new Map();
2667
+ function registerGroup(totalNode, members) {
2668
+ const resolvedIds = [];
2669
+ for (const member of members) {
2670
+ const resolved = resolveValueNode(member, accessors);
2671
+ if (resolved)
2672
+ resolvedIds.push(accessors.getId(resolved));
2673
+ }
2674
+ if (resolvedIds.length === 0)
2675
+ return;
2676
+ index.set(accessors.getId(totalNode), resolvedIds);
2677
+ }
2678
+ function traverse(nodes) {
2679
+ for (let i2 = 0; i2 < nodes.length; i2++) {
2680
+ const node = nodes[i2];
2681
+ if (isTotalLabel(accessors.getPreferredLabel(node))) {
2682
+ const children2 = accessors.getChildren(node);
2683
+ if (children2.length === 0) {
2684
+ let lastTotalIdx = -1;
2685
+ for (let j = i2 - 1; j >= 0; j--) {
2686
+ if (isTotalLabel(accessors.getPreferredLabel(nodes[j]))) {
2687
+ lastTotalIdx = j;
2688
+ break;
2689
+ }
2690
+ }
2691
+ const rawSiblings = nodes.slice(lastTotalIdx + 1, i2);
2692
+ const members = resolveTotalGroupMembers(nodes, i2, lastTotalIdx, rawSiblings, accessors);
2693
+ if (members.length > 0)
2694
+ registerGroup(node, members);
2695
+ }
2696
+ }
2697
+ const children = accessors.getChildren(node);
2698
+ if (children.length > 0)
2699
+ traverse(children);
2700
+ }
2701
+ }
2702
+ traverse(roots);
2703
+ return index;
2704
+ }
2705
+ function isNonEmptyRawValue(rawValue) {
2706
+ return rawValue !== void 0 && rawValue !== null && rawValue !== "";
2707
+ }
2708
+ function collectAllCandidates(formData) {
2709
+ const candidates = [];
2710
+ for (const conceptId of Object.keys(formData)) {
2711
+ const columnValues = formData[conceptId] || {};
2712
+ for (const columnId of Object.keys(columnValues)) {
2713
+ if (isNonEmptyRawValue(columnValues[columnId])) {
2714
+ candidates.push({ conceptId, columnId });
2715
+ }
2716
+ }
2717
+ }
2718
+ return candidates;
2719
+ }
2720
+ function indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex) {
2721
+ const info = {
2722
+ conceptId: concept.id,
2723
+ qname: concept.name,
2724
+ balance: concept.balance
2725
+ };
2726
+ conceptById.set(concept.id, info);
2727
+ (conceptByQName.get(concept.name) ?? conceptByQName.set(concept.name, []).get(concept.name)).push(info);
2728
+ const children = concept.children || [];
2729
+ const hasOwnChildren = children.length > 0;
2730
+ const alreadyHasChildren = conceptIdByQNameHasChildren.get(concept.name) === true;
2731
+ if (!conceptIdByQName.has(concept.name) || hasOwnChildren && !alreadyHasChildren) {
2732
+ conceptIdByQName.set(concept.name, concept.id);
2733
+ conceptIdByQNameHasChildren.set(concept.name, hasOwnChildren);
2734
+ }
2735
+ childrenIndex.set(
2736
+ concept.id,
2737
+ children.map((child) => child.id)
2738
+ );
2739
+ children.forEach((child) => indexConceptTree(child, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex));
2740
+ }
2741
+ function indexMemberTree(member, memberQNameToId) {
2742
+ if (!memberQNameToId.has(member.conceptName))
2743
+ memberQNameToId.set(member.conceptName, member.id);
2744
+ (member.children || []).forEach((child) => indexMemberTree(child, memberQNameToId));
2745
+ }
2746
+ const PRESENTATION_TOTAL_GROUP_ACCESSORS = {
2747
+ getId: (concept) => concept.id,
2748
+ getPreferredLabel: (concept) => concept.preferredLabel,
2749
+ isAbstract: (concept) => concept.abstract === true,
2750
+ getChildren: (concept) => concept.children || []
2751
+ };
2752
+ function buildFormulaResolutionContext(formData, columns, sections, hypercubeRoles, periodStartDate, periodEndDate) {
2753
+ const conceptByQName = /* @__PURE__ */ new Map();
2754
+ const conceptById = /* @__PURE__ */ new Map();
2755
+ const conceptIdByQNamePerRole = /* @__PURE__ */ new Map();
2756
+ const childrenByRole = /* @__PURE__ */ new Map();
2757
+ const totalGroupChildrenByRole = /* @__PURE__ */ new Map();
2758
+ sections.forEach((section2) => {
2759
+ const childrenIndex = /* @__PURE__ */ new Map();
2760
+ const conceptIdByQName = /* @__PURE__ */ new Map();
2761
+ const conceptIdByQNameHasChildren = /* @__PURE__ */ new Map();
2762
+ section2.concepts.forEach((concept) => indexConceptTree(concept, conceptByQName, conceptById, conceptIdByQName, conceptIdByQNameHasChildren, childrenIndex));
2763
+ childrenByRole.set(section2.roleURI, childrenIndex);
2764
+ conceptIdByQNamePerRole.set(section2.roleURI, conceptIdByQName);
2765
+ totalGroupChildrenByRole.set(section2.roleURI, buildTotalGroupChildrenIndex(section2.concepts, PRESENTATION_TOTAL_GROUP_ACCESSORS));
2766
+ });
2767
+ const dimensionQNameToId = /* @__PURE__ */ new Map();
2768
+ const memberQNameToId = /* @__PURE__ */ new Map();
2769
+ (hypercubeRoles || []).forEach((role) => {
2770
+ role.items.forEach((item) => {
2771
+ item.dimensions.forEach((dimension) => {
2772
+ if (!dimensionQNameToId.has(dimension.conceptName)) {
2773
+ dimensionQNameToId.set(dimension.conceptName, dimension.id);
2774
+ }
2775
+ (dimension.members || []).forEach((member) => indexMemberTree(member, memberQNameToId));
2776
+ });
2777
+ });
2778
+ });
2779
+ return {
2780
+ formData,
2781
+ columns,
2782
+ periodStartDate,
2783
+ periodEndDate,
2784
+ conceptByQName,
2785
+ conceptById,
2786
+ conceptIdByQNamePerRole,
2787
+ childrenByRole,
2788
+ totalGroupChildrenByRole,
2789
+ dimensionQNameToId,
2790
+ memberQNameToId,
2791
+ allCandidates: collectAllCandidates(formData)
2792
+ };
2793
+ }
2794
+ function collectDescendantConceptIds(childrenIndex, rootConceptId) {
2795
+ const result = [];
2796
+ const stack = [...childrenIndex.get(rootConceptId) || []];
2797
+ const seen = /* @__PURE__ */ new Set();
2798
+ while (stack.length > 0) {
2799
+ const conceptId = stack.pop();
2800
+ if (seen.has(conceptId))
2801
+ continue;
2802
+ seen.add(conceptId);
2803
+ result.push(conceptId);
2804
+ stack.push(...childrenIndex.get(conceptId) || []);
2805
+ }
2806
+ return result;
2807
+ }
2808
+ function candidateKey(candidate) {
2809
+ return `${candidate.conceptId}::${candidate.columnId}`;
2810
+ }
2811
+ function resolveMatchedCandidates(variable, ctx) {
2812
+ return intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, ctx.allCandidates, ctx)));
2813
+ }
2814
+ function getColumn(ctx, columnId) {
2815
+ return ctx.columns.find((column2) => column2.id === columnId);
2816
+ }
2817
+ function columnMatchesExplicitDimension(column2, axisId, memberId) {
2818
+ if (!(column2 == null ? void 0 : column2.dimensionData))
2819
+ return false;
2820
+ const primaryMatch = column2.dimensionData.axisId === axisId && (memberId === void 0 || column2.dimensionData.memberId === memberId);
2821
+ if (primaryMatch)
2822
+ return true;
2823
+ return (column2.dimensionData.combinations || []).some(
2824
+ (combo) => combo.axisId === axisId && (memberId === void 0 || combo.memberId === memberId)
2825
+ );
2826
+ }
2827
+ function columnMatchesTypedDimension(column2, axisId) {
2828
+ if (!(column2 == null ? void 0 : column2.dimensionData))
2829
+ return false;
2830
+ if (column2.dimensionData.typedMemberId !== void 0 && column2.dimensionData.axisId === axisId)
2831
+ return true;
2832
+ return (column2.dimensionData.typedMembers || []).some((typedMember) => typedMember.axisId === axisId);
2833
+ }
2834
+ function paramTokenName(rawDate) {
2835
+ if (!rawDate)
2836
+ return void 0;
2837
+ return rawDate.startsWith("$") ? rawDate.slice(1) : rawDate;
2838
+ }
2839
+ function resolveParamDate(paramToken, ctx) {
2840
+ const isStart = paramToken.includes("StartDate");
2841
+ const isEnd = paramToken.includes("EndDate") || paramToken === "FinancialReportingPeriodCurrentEndDateParam";
2842
+ if (paramToken.startsWith("FinancialReportingPeriodCurrent")) {
2843
+ return isStart ? { start: ctx.periodStartDate } : isEnd ? { end: ctx.periodEndDate } : void 0;
2844
+ }
2845
+ if (paramToken.startsWith("FinancialReportingPeriodPrePrevious") || paramToken.startsWith("FinancialReportingPeriodPrevious")) {
2846
+ const previousYearColumn = findPreviousYearColumn(ctx);
2847
+ if (!previousYearColumn)
2848
+ return void 0;
2849
+ return isStart ? { start: previousYearColumn.periodStartDate } : isEnd ? { end: previousYearColumn.periodEndDate } : void 0;
2850
+ }
2851
+ return void 0;
2852
+ }
2853
+ function findPreviousYearColumn(ctx) {
2854
+ return ctx.columns.find((column2) => column2.periodEndDate !== void 0 && column2.periodEndDate < ctx.periodEndDate);
2855
+ }
2856
+ function columnEffectivePeriod(column2, ctx) {
2857
+ return {
2858
+ start: (column2 == null ? void 0 : column2.periodStartDate) ?? ctx.periodStartDate,
2859
+ end: (column2 == null ? void 0 : column2.periodEndDate) ?? ctx.periodEndDate
2860
+ };
2861
+ }
2862
+ function columnMatchesPeriod(column2, dateAttribute, ctx) {
2863
+ const paramToken = paramTokenName(dateAttribute);
2864
+ if (!paramToken)
2865
+ return false;
2866
+ const resolved = resolveParamDate(paramToken, ctx);
2867
+ if (!resolved)
2868
+ return false;
2869
+ const period = columnEffectivePeriod(column2, ctx);
2870
+ const targetDate = resolved.end ?? resolved.start;
2871
+ return period.end === targetDate;
2872
+ }
2873
+ function unionCandidates(lists) {
2874
+ const merged = /* @__PURE__ */ new Map();
2875
+ lists.forEach((list) => list.forEach((candidate) => merged.set(candidateKey(candidate), candidate)));
2876
+ return Array.from(merged.values());
2877
+ }
2878
+ function matchFilter(filter2, universe, ctx) {
2879
+ var _a, _b;
2880
+ switch (filter2.type) {
2881
+ case "CONCEPT_NAME": {
2882
+ const qname = filter2.attributes["concept.qname"];
2883
+ const infos = qname ? ctx.conceptByQName.get(qname) : void 0;
2884
+ if (!infos || infos.length === 0)
2885
+ return [];
2886
+ const conceptIds = new Set(infos.map((info) => info.conceptId));
2887
+ return universe.filter((candidate) => conceptIds.has(candidate.conceptId));
2888
+ }
2889
+ case "CONCEPT_BALANCE": {
2890
+ const balance = filter2.attributes["balance"];
2891
+ return universe.filter((candidate) => {
2892
+ var _a2;
2893
+ return ((_a2 = ctx.conceptById.get(candidate.conceptId)) == null ? void 0 : _a2.balance) === balance;
2894
+ });
2895
+ }
2896
+ case "CONCEPT_RELATION": {
2897
+ const anchorQName = filter2.attributes["qname"];
2898
+ const linkrole = filter2.attributes["linkrole"];
2899
+ const axis = filter2.attributes["axis"];
2900
+ const anchorConceptId = anchorQName && linkrole ? (_a = ctx.conceptIdByQNamePerRole.get(linkrole)) == null ? void 0 : _a.get(anchorQName) : void 0;
2901
+ const childrenIndex = linkrole ? ctx.childrenByRole.get(linkrole) : void 0;
2902
+ if (!anchorConceptId || !childrenIndex)
2903
+ return [];
2904
+ let relatedConceptIds;
2905
+ if (axis === "descendant") {
2906
+ relatedConceptIds = collectDescendantConceptIds(childrenIndex, anchorConceptId);
2907
+ } else {
2908
+ const nestedChildren = childrenIndex.get(anchorConceptId) || [];
2909
+ relatedConceptIds = nestedChildren.length > 0 ? nestedChildren : ((_b = ctx.totalGroupChildrenByRole.get(linkrole ?? "")) == null ? void 0 : _b.get(anchorConceptId)) || [];
2910
+ }
2911
+ const relatedSet = new Set(relatedConceptIds);
2912
+ return universe.filter((candidate) => relatedSet.has(candidate.conceptId));
2913
+ }
2914
+ case "EXPLICIT_DIMENSION": {
2915
+ const dimensionQName = filter2.attributes["dimension.qname"];
2916
+ const memberQName = filter2.attributes["member.qname"];
2917
+ const axisId = dimensionQName ? ctx.dimensionQNameToId.get(dimensionQName) : void 0;
2918
+ const memberId = memberQName ? ctx.memberQNameToId.get(memberQName) : void 0;
2919
+ if (!axisId)
2920
+ return [];
2921
+ return universe.filter((candidate) => columnMatchesExplicitDimension(getColumn(ctx, candidate.columnId), axisId, memberId));
2922
+ }
2923
+ case "TYPED_DIMENSION": {
2924
+ const dimensionQName = filter2.attributes["dimension.qname"];
2925
+ const axisId = dimensionQName ? ctx.dimensionQNameToId.get(dimensionQName) : void 0;
2926
+ if (!axisId)
2927
+ return [];
2928
+ return universe.filter((candidate) => columnMatchesTypedDimension(getColumn(ctx, candidate.columnId), axisId));
2929
+ }
2930
+ case "PERIOD_INSTANT":
2931
+ case "PERIOD_END": {
2932
+ const dateAttribute = filter2.attributes["date"];
2933
+ return universe.filter((candidate) => columnMatchesPeriod(getColumn(ctx, candidate.columnId), dateAttribute, ctx));
2934
+ }
2935
+ case "OR_FILTER":
2936
+ return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx)));
2937
+ case "ASPECT_COVER":
2938
+ return universe;
2939
+ default:
2940
+ return [];
2941
+ }
2942
+ }
2943
+ function resolveFilter(filter2, universe, ctx) {
2944
+ const matched = matchFilter(filter2, universe, ctx);
2945
+ if (!filter2.complement)
2946
+ return matched;
2947
+ const matchedKeys = new Set(matched.map(candidateKey));
2948
+ return universe.filter((candidate) => !matchedKeys.has(candidateKey(candidate)));
2949
+ }
2950
+ function intersectCandidates(sets) {
2951
+ if (sets.length === 0)
2952
+ return [];
2953
+ return sets.reduce((acc, set) => {
2954
+ const setKeys = new Set(set.map(candidateKey));
2955
+ return acc.filter((candidate) => setKeys.has(candidateKey(candidate)));
2956
+ });
2957
+ }
2958
+ function resolveFormulaVariable(variable, ctx) {
2959
+ const matched = resolveMatchedCandidates(variable, ctx);
2960
+ const values = matched.map((candidate) => {
2961
+ var _a;
2962
+ return parseFloat((_a = ctx.formData[candidate.conceptId]) == null ? void 0 : _a[candidate.columnId]);
2963
+ }).filter((value) => !Number.isNaN(value));
2964
+ if (variable.bindAsSequence) {
2965
+ if (values.length === 0 && variable.fallbackValue === "()")
2966
+ return [];
2967
+ if (values.length === 0)
2968
+ return void 0;
2969
+ return values;
2970
+ }
2971
+ if (values.length === 0)
2972
+ return void 0;
2973
+ if (values.length > 1) {
2974
+ console.warn(
2975
+ `[formula-variable-resolver] Variable "${variable.name}" is scalar (bindAsSequence: false) but matched ${values.length} facts; using the first match by column order. This can legitimately happen when filters under-constrain the candidate set.`
2976
+ );
2977
+ }
2978
+ return values[0];
2979
+ }
2980
+ function resolveFormulaVariableMatchCount(variable, ctx) {
2981
+ return resolveMatchedCandidates(variable, ctx).length;
2982
+ }
2983
+ function resolveFormulaVariableUsedFallback(variable, ctx) {
2984
+ const matchCount = resolveFormulaVariableMatchCount(variable, ctx);
2985
+ if (matchCount > 0)
2986
+ return false;
2987
+ if (variable.fallbackValue !== void 0)
2988
+ return true;
2989
+ return void 0;
2990
+ }
2991
+ function collectConceptNameQNames(filters) {
2992
+ const qnames = [];
2993
+ for (const filter2 of filters) {
2994
+ if (filter2.type === "CONCEPT_NAME") {
2995
+ const qname = filter2.attributes["concept.qname"];
2996
+ if (qname)
2997
+ qnames.push(qname);
2998
+ } else if (filter2.type === "OR_FILTER") {
2999
+ qnames.push(...collectConceptNameQNames(filter2.children));
3000
+ }
3001
+ }
3002
+ return qnames;
3003
+ }
3004
+ function resolveVariableConceptQNames(variable) {
3005
+ return collectConceptNameQNames(variable.filters);
3006
+ }
3007
+ function resolveVariableColumnsForConcept(variable, targetConceptId, ctx) {
3008
+ const universe = ctx.columns.map((column2) => ({ conceptId: targetConceptId, columnId: column2.id }));
3009
+ const resolved = intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, universe, ctx)));
3010
+ return resolved.map((candidate) => candidate.columnId);
3011
+ }
3012
+ function buildAssertionBindings(assertion, ctx) {
3013
+ const bindings = /* @__PURE__ */ new Map();
3014
+ for (const variable of assertion.variables) {
3015
+ bindings.set(variable.name, resolveFormulaVariable(variable, ctx));
3016
+ }
3017
+ return bindings;
3018
+ }
3019
+ function buildAssertionFallbackUsage(assertion, ctx) {
3020
+ var _a;
3021
+ const usesHasFallbackValue = ((_a = assertion.test) == null ? void 0 : _a.includes("has-fallback-value")) || assertion.preconditions.some((precondition) => precondition.test.includes("has-fallback-value"));
3022
+ if (!usesHasFallbackValue)
3023
+ return /* @__PURE__ */ new Map();
3024
+ const fallbackUsage = /* @__PURE__ */ new Map();
3025
+ for (const variable of assertion.variables) {
3026
+ const usedFallback = resolveFormulaVariableUsedFallback(variable, ctx);
3027
+ if (usedFallback !== void 0)
3028
+ fallbackUsage.set(variable.name, usedFallback);
3029
+ }
3030
+ return fallbackUsage;
3031
+ }
3032
+ function evaluateAssertionPreconditions(preconditions, bindings, fallbackUsage = /* @__PURE__ */ new Map()) {
3033
+ if (preconditions.length === 0)
3034
+ return { satisfied: true };
3035
+ for (const precondition of preconditions) {
3036
+ let result;
3037
+ try {
3038
+ result = evaluateFormulaTest(precondition.test, bindings, fallbackUsage);
3039
+ } catch (error2) {
3040
+ if (error2 instanceof FormulaExpressionError) {
3041
+ return { satisfied: false, skipReason: "unresolvable" };
3042
+ }
3043
+ throw error2;
3044
+ }
3045
+ if (!result) {
3046
+ return { satisfied: false, skipReason: "precondition" };
3047
+ }
3048
+ }
3049
+ return { satisfied: true };
3050
+ }
3051
+ class FormulaValidationService {
3052
+ /**
3053
+ * Evaluates every assertion in `xbrlInput.formula[0].roles` (only the first entrypoint,
3054
+ * consistent with how xbrl-form-builder.ts uses presentation/hypercube data) against the
3055
+ * given resolution context. One bad assertion never aborts the other 783 — resolution/
3056
+ * evaluation errors are caught per-assertion and recorded as skipped, not thrown.
3057
+ */
3058
+ evaluate(xbrlInput, ctx, language) {
3059
+ var _a;
3060
+ const formulaData = (_a = xbrlInput.formula) == null ? void 0 : _a[0];
3061
+ if (!formulaData)
3062
+ return [];
3063
+ const results = [];
3064
+ for (const role of formulaData.roles) {
3065
+ for (const assertion of role.assertions) {
3066
+ results.push(this._evaluateAssertion(assertion, role, ctx, language));
3067
+ }
3068
+ }
3069
+ return results;
3070
+ }
3071
+ summarize(results) {
3072
+ const summary = {
3073
+ totalAssertions: results.length,
3074
+ evaluated: 0,
3075
+ skipped: 0,
3076
+ passed: 0,
3077
+ failed: 0,
3078
+ failedWarnings: 0,
3079
+ failedErrors: 0
3080
+ };
3081
+ for (const result of results) {
3082
+ if (result.skipped) {
3083
+ summary.skipped += 1;
3084
+ continue;
3085
+ }
3086
+ summary.evaluated += 1;
3087
+ if (result.satisfied) {
3088
+ summary.passed += 1;
3089
+ } else {
3090
+ summary.failed += 1;
3091
+ if (result.severity === "ERROR")
3092
+ summary.failedErrors += 1;
3093
+ else
3094
+ summary.failedWarnings += 1;
3095
+ }
3096
+ }
3097
+ return summary;
3098
+ }
3099
+ _evaluateAssertion(assertion, role, ctx, language) {
3100
+ try {
3101
+ const bindings = buildAssertionBindings(assertion, ctx);
3102
+ const fallbackUsage = buildAssertionFallbackUsage(assertion, ctx);
3103
+ const precondition = evaluateAssertionPreconditions(assertion.preconditions, bindings, fallbackUsage);
3104
+ if (!precondition.satisfied) {
3105
+ return this._skippedResult(assertion, role, precondition.skipReason ?? "unresolvable");
3106
+ }
3107
+ if (assertion.type === "EXISTENCE_ASSERTION") {
3108
+ const matchedFactCount = resolveFormulaVariableMatchCount(assertion.variables[0], ctx);
3109
+ const satisfied2 = evaluateExistenceTest(assertion.test, matchedFactCount);
3110
+ return this._evaluatedResult(assertion, role, ctx, satisfied2, language, matchedFactCount);
3111
+ }
3112
+ const satisfied = evaluateFormulaTest(assertion.test, bindings, fallbackUsage);
3113
+ return this._evaluatedResult(assertion, role, ctx, satisfied, language);
3114
+ } catch (error2) {
3115
+ if (error2 instanceof FormulaExpressionError) {
3116
+ console.warn(`[FormulaValidationService] Assertion "${assertion.id}" could not be evaluated (${error2.reason}): ${error2.message}`);
3117
+ return this._skippedResult(assertion, role, "unresolvable");
3118
+ }
3119
+ throw error2;
3120
+ }
3121
+ }
3122
+ _skippedResult(assertion, role, skipReason) {
3123
+ return {
3124
+ assertionId: assertion.id,
3125
+ roleURI: role.roleURI,
3126
+ roleLabel: role.role,
3127
+ type: assertion.type,
3128
+ severity: assertion.severity,
3129
+ satisfied: false,
3130
+ skipped: true,
3131
+ skipReason
3132
+ };
3133
+ }
3134
+ _evaluatedResult(assertion, role, ctx, satisfied, language, matchedFactCount) {
3135
+ const message = satisfied ? void 0 : this._pickMessage(assertion, language);
3136
+ return {
3137
+ assertionId: assertion.id,
3138
+ roleURI: role.roleURI,
3139
+ roleLabel: role.role,
3140
+ type: assertion.type,
3141
+ severity: assertion.severity,
3142
+ satisfied,
3143
+ skipped: false,
3144
+ message,
3145
+ matchedFactCount,
3146
+ conceptLinks: message ? this._buildConceptLinks(assertion, role.roleURI, ctx, message) : void 0
3147
+ };
3148
+ }
3149
+ _pickMessage(assertion, language) {
3150
+ const messages = assertion.messages;
3151
+ if (messages.length === 0)
3152
+ return void 0;
3153
+ const message = messages.find((m) => m.xmlLang === language) ?? messages.find((m) => m.xmlLang === "en") ?? messages[0];
3154
+ return this._stripTechnicalDetail(message.text);
3155
+ }
3156
+ /**
3157
+ * Taxonomy-authored messages append raw XBRL Formula/XPath debug expressions after the
3158
+ * human-readable sentence, delimited by " | {...}" (e.g. `xff:has-fallback-value(...)`,
3159
+ * `varArc_...` variable names) — see the fixture's `*_formula.json` message text. That debug
3160
+ * payload is meaningless to a non-technical filer, so only the sentence preceding it is shown.
3161
+ */
3162
+ _stripTechnicalDetail(text) {
3163
+ const braceIndex = text.indexOf("{");
3164
+ if (braceIndex === -1)
3165
+ return text.trim();
3166
+ return text.slice(0, braceIndex).replace(/\|\s*$/, "").trim();
3167
+ }
3168
+ /**
3169
+ * JDF-059: resolves every concept the assertion's variables name directly (JDF-045's
3170
+ * `resolveVariableConceptQNames`) to a clickable fact location — but only the ones the
3171
+ * (already-stripped) message text actually mentions by backtick-quoted local name, e.g.
3172
+ * `` `Equity` `` — a concept a variable references internally (say, as a dimensional
3173
+ * exclusion) but the message never calls out by name has nothing for the user to click on, so
3174
+ * it's left out rather than guessed at. Column resolution reuses the assertion's own filters
3175
+ * (`resolveVariableColumnsForConcept`), so the link always points at the exact fact this
3176
+ * specific assertion run cared about — never just "some field for this concept" — including an
3177
+ * empty one for a failed "MUST exist" assertion, so the user can navigate straight to where the
3178
+ * missing value belongs.
3179
+ */
3180
+ _buildConceptLinks(assertion, roleURI, ctx, message) {
3181
+ var _a, _b, _c;
3182
+ const links = [];
3183
+ const seenLabels = /* @__PURE__ */ new Set();
3184
+ for (const variable of assertion.variables) {
3185
+ for (const qname of resolveVariableConceptQNames(variable)) {
3186
+ const conceptId = ((_a = ctx.conceptIdByQNamePerRole.get(roleURI)) == null ? void 0 : _a.get(qname)) ?? ((_c = (_b = ctx.conceptByQName.get(qname)) == null ? void 0 : _b[0]) == null ? void 0 : _c.conceptId);
3187
+ if (!conceptId)
3188
+ continue;
3189
+ const label = qname.includes(":") ? qname.split(":").pop() : qname;
3190
+ if (seenLabels.has(label))
3191
+ continue;
3192
+ if (!new RegExp("`" + this._escapeRegExp(label) + "`").test(message))
3193
+ continue;
3194
+ const columns = resolveVariableColumnsForConcept(variable, conceptId, ctx);
3195
+ links.push({ label, conceptQName: qname, conceptId, columnId: columns[0] });
3196
+ seenLabels.add(label);
3197
+ }
3198
+ }
3199
+ return links;
3200
+ }
3201
+ _escapeRegExp(text) {
3202
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3203
+ }
3204
+ }
2303
3205
  class FactMatcher {
2304
3206
  /**
2305
3207
  * Find a matching fact for a given cell context
@@ -3153,15 +4055,15 @@ function determineInputTypeFromBaseChain(baseTypeChain, datatypes) {
3153
4055
  placeholder: I18n.t("field.enterValue")
3154
4056
  };
3155
4057
  }
3156
- var __defProp$6 = Object.defineProperty;
3157
- var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
3158
- var __decorateClass$6 = (decorators, target, key, kind) => {
3159
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
4058
+ var __defProp$7 = Object.defineProperty;
4059
+ var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
4060
+ var __decorateClass$7 = (decorators, target, key, kind) => {
4061
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
3160
4062
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
3161
4063
  if (decorator = decorators[i2])
3162
4064
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
3163
4065
  if (kind && result)
3164
- __defProp$6(target, key, result);
4066
+ __defProp$7(target, key, result);
3165
4067
  return result;
3166
4068
  };
3167
4069
  let JupiterFormField = class extends LitElement {
@@ -4178,6 +5080,7 @@ let JupiterFormField = class extends LitElement {
4178
5080
  <button
4179
5081
  class="period-icon-btn"
4180
5082
  type="button"
5083
+ tabindex="-1"
4181
5084
  @click="${this._handlePeriodIconClick}"
4182
5085
  title="Edit period"
4183
5086
  >
@@ -4674,99 +5577,99 @@ JupiterFormField.styles = css`
4674
5577
  animation: concept-highlight-pulse 10s ease-out forwards;
4675
5578
  }
4676
5579
  `;
4677
- __decorateClass$6([
5580
+ __decorateClass$7([
4678
5581
  n2({ type: Object })
4679
5582
  ], JupiterFormField.prototype, "field", 2);
4680
- __decorateClass$6([
5583
+ __decorateClass$7([
4681
5584
  n2({ type: String })
4682
5585
  ], JupiterFormField.prototype, "conceptId", 2);
4683
- __decorateClass$6([
5586
+ __decorateClass$7([
4684
5587
  n2({ type: String })
4685
5588
  ], JupiterFormField.prototype, "conceptType", 2);
4686
- __decorateClass$6([
5589
+ __decorateClass$7([
4687
5590
  n2({ type: Array })
4688
5591
  ], JupiterFormField.prototype, "datatypes", 2);
4689
- __decorateClass$6([
5592
+ __decorateClass$7([
4690
5593
  n2({ type: Array })
4691
5594
  ], JupiterFormField.prototype, "defaultUnits", 2);
4692
- __decorateClass$6([
5595
+ __decorateClass$7([
4693
5596
  n2({ type: String })
4694
5597
  ], JupiterFormField.prototype, "columnId", 2);
4695
- __decorateClass$6([
5598
+ __decorateClass$7([
4696
5599
  n2()
4697
5600
  ], JupiterFormField.prototype, "value", 2);
4698
- __decorateClass$6([
5601
+ __decorateClass$7([
4699
5602
  n2({ type: Boolean })
4700
5603
  ], JupiterFormField.prototype, "disabled", 2);
4701
- __decorateClass$6([
5604
+ __decorateClass$7([
4702
5605
  n2({ type: String })
4703
5606
  ], JupiterFormField.prototype, "locale", 2);
4704
- __decorateClass$6([
5607
+ __decorateClass$7([
4705
5608
  n2({ type: Boolean })
4706
5609
  ], JupiterFormField.prototype, "hideLabel", 2);
4707
- __decorateClass$6([
5610
+ __decorateClass$7([
4708
5611
  n2({ type: String })
4709
5612
  ], JupiterFormField.prototype, "mode", 2);
4710
- __decorateClass$6([
5613
+ __decorateClass$7([
4711
5614
  n2({ type: Object })
4712
5615
  ], JupiterFormField.prototype, "masterData", 2);
4713
- __decorateClass$6([
5616
+ __decorateClass$7([
4714
5617
  n2({ type: Array })
4715
5618
  ], JupiterFormField.prototype, "facts", 2);
4716
- __decorateClass$6([
5619
+ __decorateClass$7([
4717
5620
  n2({ type: Object })
4718
5621
  ], JupiterFormField.prototype, "column", 2);
4719
- __decorateClass$6([
5622
+ __decorateClass$7([
4720
5623
  n2({ type: String })
4721
5624
  ], JupiterFormField.prototype, "periodStartDate", 2);
4722
- __decorateClass$6([
5625
+ __decorateClass$7([
4723
5626
  n2({ type: String })
4724
5627
  ], JupiterFormField.prototype, "periodEndDate", 2);
4725
- __decorateClass$6([
5628
+ __decorateClass$7([
4726
5629
  n2({ type: String })
4727
5630
  ], JupiterFormField.prototype, "periodInstantDate", 2);
4728
- __decorateClass$6([
5631
+ __decorateClass$7([
4729
5632
  n2({ type: String })
4730
5633
  ], JupiterFormField.prototype, "unit", 2);
4731
- __decorateClass$6([
5634
+ __decorateClass$7([
4732
5635
  n2({ type: String })
4733
5636
  ], JupiterFormField.prototype, "decimals", 2);
4734
- __decorateClass$6([
5637
+ __decorateClass$7([
4735
5638
  n2({ type: String })
4736
5639
  ], JupiterFormField.prototype, "globalDecimals", 2);
4737
- __decorateClass$6([
5640
+ __decorateClass$7([
4738
5641
  n2({ type: Object })
4739
5642
  ], JupiterFormField.prototype, "typedMemberValues", 2);
4740
- __decorateClass$6([
5643
+ __decorateClass$7([
4741
5644
  r()
4742
5645
  ], JupiterFormField.prototype, "_errors", 2);
4743
- __decorateClass$6([
5646
+ __decorateClass$7([
4744
5647
  r()
4745
5648
  ], JupiterFormField.prototype, "_xbrlErrors", 2);
4746
- __decorateClass$6([
5649
+ __decorateClass$7([
4747
5650
  r()
4748
5651
  ], JupiterFormField.prototype, "_touched", 2);
4749
- __decorateClass$6([
5652
+ __decorateClass$7([
4750
5653
  r()
4751
5654
  ], JupiterFormField.prototype, "_showPeriodPopup", 2);
4752
- __decorateClass$6([
5655
+ __decorateClass$7([
4753
5656
  r()
4754
5657
  ], JupiterFormField.prototype, "_availableUnits", 2);
4755
- __decorateClass$6([
5658
+ __decorateClass$7([
4756
5659
  r()
4757
5660
  ], JupiterFormField.prototype, "_numericDraftValue", 2);
4758
- JupiterFormField = __decorateClass$6([
5661
+ JupiterFormField = __decorateClass$7([
4759
5662
  t$1("jupiter-form-field")
4760
5663
  ], JupiterFormField);
4761
- var __defProp$5 = Object.defineProperty;
4762
- var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
4763
- var __decorateClass$5 = (decorators, target, key, kind) => {
4764
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
5664
+ var __defProp$6 = Object.defineProperty;
5665
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
5666
+ var __decorateClass$6 = (decorators, target, key, kind) => {
5667
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
4765
5668
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
4766
5669
  if (decorator = decorators[i2])
4767
5670
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
4768
5671
  if (kind && result)
4769
- __defProp$5(target, key, result);
5672
+ __defProp$6(target, key, result);
4770
5673
  return result;
4771
5674
  };
4772
5675
  let JupiterConceptTree = class extends LitElement {
@@ -5036,6 +5939,7 @@ let JupiterConceptTree = class extends LitElement {
5036
5939
  const hasChildren = this.concept.children && this.concept.children.length > 0;
5037
5940
  const level = this.concept.level || 0;
5038
5941
  const isAbstract = this.concept.abstract || false;
5942
+ const isTotal = isTotalLabel$1(this.concept.preferredLabel);
5039
5943
  return html`
5040
5944
  <!-- Concept Name Cell (Left Column) -->
5041
5945
  <td class="concept-name-cell ${isAbstract ? "abstract" : hasChildren ? "" : "leaf"} ${this.rowFocused ? "row-focused" : ""}">
@@ -5045,7 +5949,7 @@ let JupiterConceptTree = class extends LitElement {
5045
5949
  @click="${this._toggleExpanded}">
5046
5950
  ${hasChildren ? "▶" : ""}
5047
5951
  </div>
5048
- <div class="concept-label"
5952
+ <div class="concept-label ${isTotal ? "total-concept-label" : ""}"
5049
5953
  @click="${this._toggleExpanded}"
5050
5954
  title="${this.concept.id}${this.concept.description ? " - " + this.concept.description : ""}">
5051
5955
  ${this.concept.label}
@@ -5053,14 +5957,14 @@ let JupiterConceptTree = class extends LitElement {
5053
5957
  ${this.concept.balance ? html`
5054
5958
  <div class="concept-balance ${this.concept.balance}">${this.concept.balance}</div>
5055
5959
  ` : ""}
5056
- <button class="concept-info-btn" type="button" title="${I18n.t("conceptInfo.title")}"
5960
+ <button class="concept-info-btn" type="button" tabindex="-1" title="${I18n.t("conceptInfo.title")}"
5057
5961
  @click="${this._openInfoDialog}">ℹ</button>
5058
5962
  ${this.showAddButton ? html`
5059
- <button class="repeat-btn" type="button" title="Add row"
5963
+ <button class="repeat-btn" type="button" tabindex="-1" title="Add row"
5060
5964
  @click="${this._handleAddRepeat}">+</button>
5061
5965
  ` : ""}
5062
5966
  ${this.showRemoveButton ? html`
5063
- <button class="repeat-btn remove" type="button" title="Remove row"
5967
+ <button class="repeat-btn remove" type="button" tabindex="-1" title="Remove row"
5064
5968
  @click="${this._handleRemoveRepeat}">−</button>
5065
5969
  ` : ""}
5066
5970
  </div>
@@ -5078,11 +5982,12 @@ let JupiterConceptTree = class extends LitElement {
5078
5982
  const storedDecimals = (_d = (_c = this.decimalsData) == null ? void 0 : _c[this.concept.id]) == null ? void 0 : _d[column2.id];
5079
5983
  const calcMismatch = this.mode !== "readonly" ? (_e = this.calculationMismatches) == null ? void 0 : _e.get(`${this.concept.id}__${column2.id}`) : void 0;
5080
5984
  return html`
5081
- <td class="field-cell ${!shouldShowField ? "empty" : ""} ${isAbstract ? "abstract-row" : ""} ${this.highlightType && column2.id === this.highlightColumnId ? "highlight-" + this.highlightType : ""} ${calcMismatch ? "calc-warning" : ""} ${this.rowFocused ? "row-focused" : ""}">
5985
+ <td class="field-cell ${!shouldShowField ? "empty" : ""} ${isAbstract ? "abstract-row" : ""} ${isTotal ? "total-row" : ""} ${this.highlightType && column2.id === this.highlightColumnId ? "highlight-" + this.highlightType : ""} ${calcMismatch ? "calc-warning" : ""} ${this.rowFocused ? "row-focused" : ""}">
5082
5986
  ${calcMismatch ? html`
5083
5987
  <button
5084
5988
  class="calc-warning-badge"
5085
5989
  type="button"
5990
+ tabindex="-1"
5086
5991
  title="${calcMismatch.message}"
5087
5992
  @click="${(e2) => this._openCalculationWarningDialog(e2, calcMismatch)}"
5088
5993
  >!</button>
@@ -5199,6 +6104,10 @@ JupiterConceptTree.styles = css`
5199
6104
  min-width: 0; /* Allows flex item to shrink below content size */
5200
6105
  }
5201
6106
 
6107
+ .concept-label.total-concept-label {
6108
+ font-weight: 700;
6109
+ }
6110
+
5202
6111
  .concept-balance {
5203
6112
  margin-left: 8px;
5204
6113
  font-size: 11px;
@@ -5290,6 +6199,10 @@ JupiterConceptTree.styles = css`
5290
6199
  color: #fff;
5291
6200
  }
5292
6201
 
6202
+ .field-cell.total-row {
6203
+ background: var(--jupiter-total-cell-background, rgba(0, 0, 0, 0.045));
6204
+ }
6205
+
5293
6206
  .field-cell.highlight-total {
5294
6207
  background: rgba(25, 118, 210, 0.15);
5295
6208
  }
@@ -5395,90 +6308,90 @@ JupiterConceptTree.styles = css`
5395
6308
  }
5396
6309
 
5397
6310
  `;
5398
- __decorateClass$5([
6311
+ __decorateClass$6([
5399
6312
  n2({ type: Object })
5400
6313
  ], JupiterConceptTree.prototype, "concept", 2);
5401
- __decorateClass$5([
6314
+ __decorateClass$6([
5402
6315
  n2({ type: Array })
5403
6316
  ], JupiterConceptTree.prototype, "columns", 2);
5404
- __decorateClass$5([
6317
+ __decorateClass$6([
5405
6318
  n2({ type: Object })
5406
6319
  ], JupiterConceptTree.prototype, "formData", 2);
5407
- __decorateClass$5([
6320
+ __decorateClass$6([
5408
6321
  n2({ type: Object })
5409
6322
  ], JupiterConceptTree.prototype, "periodData", 2);
5410
- __decorateClass$5([
6323
+ __decorateClass$6([
5411
6324
  n2({ type: Object })
5412
6325
  ], JupiterConceptTree.prototype, "unitData", 2);
5413
- __decorateClass$5([
6326
+ __decorateClass$6([
5414
6327
  n2({ type: Object })
5415
6328
  ], JupiterConceptTree.prototype, "decimalsData", 2);
5416
- __decorateClass$5([
6329
+ __decorateClass$6([
5417
6330
  n2({ type: String })
5418
6331
  ], JupiterConceptTree.prototype, "globalDecimals", 2);
5419
- __decorateClass$5([
6332
+ __decorateClass$6([
5420
6333
  n2({ type: Array })
5421
6334
  ], JupiterConceptTree.prototype, "defaultUnits", 2);
5422
- __decorateClass$5([
6335
+ __decorateClass$6([
5423
6336
  n2({ type: Boolean })
5424
6337
  ], JupiterConceptTree.prototype, "disabled", 2);
5425
- __decorateClass$5([
6338
+ __decorateClass$6([
5426
6339
  n2({ type: String })
5427
6340
  ], JupiterConceptTree.prototype, "locale", 2);
5428
- __decorateClass$5([
6341
+ __decorateClass$6([
5429
6342
  n2({ type: Set })
5430
6343
  ], JupiterConceptTree.prototype, "expandedConcepts", 2);
5431
- __decorateClass$5([
6344
+ __decorateClass$6([
5432
6345
  n2({ type: Array })
5433
6346
  ], JupiterConceptTree.prototype, "datatypes", 2);
5434
- __decorateClass$5([
6347
+ __decorateClass$6([
5435
6348
  n2({ type: String })
5436
6349
  ], JupiterConceptTree.prototype, "mode", 2);
5437
- __decorateClass$5([
6350
+ __decorateClass$6([
5438
6351
  n2({ type: Object })
5439
6352
  ], JupiterConceptTree.prototype, "masterData", 2);
5440
- __decorateClass$5([
6353
+ __decorateClass$6([
5441
6354
  n2({ type: Array })
5442
6355
  ], JupiterConceptTree.prototype, "facts", 2);
5443
- __decorateClass$5([
6356
+ __decorateClass$6([
5444
6357
  n2({ type: Object })
5445
6358
  ], JupiterConceptTree.prototype, "typedMemberData", 2);
5446
- __decorateClass$5([
6359
+ __decorateClass$6([
5447
6360
  n2({ type: Boolean })
5448
6361
  ], JupiterConceptTree.prototype, "showAddButton", 2);
5449
- __decorateClass$5([
6362
+ __decorateClass$6([
5450
6363
  n2({ type: Boolean })
5451
6364
  ], JupiterConceptTree.prototype, "showRemoveButton", 2);
5452
- __decorateClass$5([
6365
+ __decorateClass$6([
5453
6366
  n2({ type: String })
5454
6367
  ], JupiterConceptTree.prototype, "highlightType", 2);
5455
- __decorateClass$5([
6368
+ __decorateClass$6([
5456
6369
  n2({ type: String })
5457
6370
  ], JupiterConceptTree.prototype, "highlightColumnId", 2);
5458
- __decorateClass$5([
6371
+ __decorateClass$6([
5459
6372
  n2({ type: Object })
5460
6373
  ], JupiterConceptTree.prototype, "calculationMismatches", 2);
5461
- __decorateClass$5([
6374
+ __decorateClass$6([
5462
6375
  n2({ type: String })
5463
6376
  ], JupiterConceptTree.prototype, "language", 2);
5464
- __decorateClass$5([
6377
+ __decorateClass$6([
5465
6378
  n2({ type: Boolean })
5466
6379
  ], JupiterConceptTree.prototype, "rowFocused", 2);
5467
- __decorateClass$5([
6380
+ __decorateClass$6([
5468
6381
  r()
5469
6382
  ], JupiterConceptTree.prototype, "_expanded", 2);
5470
- JupiterConceptTree = __decorateClass$5([
6383
+ JupiterConceptTree = __decorateClass$6([
5471
6384
  t$1("jupiter-concept-tree")
5472
6385
  ], JupiterConceptTree);
5473
- var __defProp$4 = Object.defineProperty;
5474
- var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
5475
- var __decorateClass$4 = (decorators, target, key, kind) => {
5476
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
6386
+ var __defProp$5 = Object.defineProperty;
6387
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
6388
+ var __decorateClass$5 = (decorators, target, key, kind) => {
6389
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
5477
6390
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
5478
6391
  if (decorator = decorators[i2])
5479
6392
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
5480
6393
  if (kind && result)
5481
- __defProp$4(target, key, result);
6394
+ __defProp$5(target, key, result);
5482
6395
  return result;
5483
6396
  };
5484
6397
  let JupiterAddColumnDialog = class extends LitElement {
@@ -6087,48 +7000,48 @@ JupiterAddColumnDialog.styles = css`
6087
7000
  line-height: 1.4;
6088
7001
  }
6089
7002
  `;
6090
- __decorateClass$4([
7003
+ __decorateClass$5([
6091
7004
  n2({ type: String })
6092
7005
  ], JupiterAddColumnDialog.prototype, "periodType", 2);
6093
- __decorateClass$4([
7006
+ __decorateClass$5([
6094
7007
  n2({ type: Boolean })
6095
7008
  ], JupiterAddColumnDialog.prototype, "open", 2);
6096
- __decorateClass$4([
7009
+ __decorateClass$5([
6097
7010
  n2({ type: Array })
6098
7011
  ], JupiterAddColumnDialog.prototype, "availableDimensions", 2);
6099
- __decorateClass$4([
7012
+ __decorateClass$5([
6100
7013
  n2({ type: String })
6101
7014
  ], JupiterAddColumnDialog.prototype, "periodStartDate", 2);
6102
- __decorateClass$4([
7015
+ __decorateClass$5([
6103
7016
  n2({ type: String })
6104
7017
  ], JupiterAddColumnDialog.prototype, "periodEndDate", 2);
6105
- __decorateClass$4([
7018
+ __decorateClass$5([
6106
7019
  r()
6107
7020
  ], JupiterAddColumnDialog.prototype, "_startDate", 2);
6108
- __decorateClass$4([
7021
+ __decorateClass$5([
6109
7022
  r()
6110
7023
  ], JupiterAddColumnDialog.prototype, "_endDate", 2);
6111
- __decorateClass$4([
7024
+ __decorateClass$5([
6112
7025
  r()
6113
7026
  ], JupiterAddColumnDialog.prototype, "_instantDate", 2);
6114
- __decorateClass$4([
7027
+ __decorateClass$5([
6115
7028
  r()
6116
7029
  ], JupiterAddColumnDialog.prototype, "_selectedType", 2);
6117
- __decorateClass$4([
7030
+ __decorateClass$5([
6118
7031
  r()
6119
7032
  ], JupiterAddColumnDialog.prototype, "_selectedDimensions", 2);
6120
- JupiterAddColumnDialog = __decorateClass$4([
7033
+ JupiterAddColumnDialog = __decorateClass$5([
6121
7034
  t$1("jupiter-add-column-dialog")
6122
7035
  ], JupiterAddColumnDialog);
6123
- var __defProp$3 = Object.defineProperty;
6124
- var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
6125
- var __decorateClass$3 = (decorators, target, key, kind) => {
6126
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
7036
+ var __defProp$4 = Object.defineProperty;
7037
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
7038
+ var __decorateClass$4 = (decorators, target, key, kind) => {
7039
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
6127
7040
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
6128
7041
  if (decorator = decorators[i2])
6129
7042
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
6130
7043
  if (kind && result)
6131
- __defProp$3(target, key, result);
7044
+ __defProp$4(target, key, result);
6132
7045
  return result;
6133
7046
  };
6134
7047
  let JupiterFormSection = class extends LitElement {
@@ -6155,6 +7068,7 @@ let JupiterFormSection = class extends LitElement {
6155
7068
  this.periodEndDate = "";
6156
7069
  this.language = "en";
6157
7070
  this.calculationEnabled = false;
7071
+ this.totalManuallyEditedData = {};
6158
7072
  this._expanded = true;
6159
7073
  this._showAddColumnDialog = false;
6160
7074
  this._sectionPeriodType = "duration";
@@ -6179,6 +7093,7 @@ let JupiterFormSection = class extends LitElement {
6179
7093
  this._memberParentMap = /* @__PURE__ */ new Map();
6180
7094
  this._totalConceptMap = /* @__PURE__ */ new Map();
6181
7095
  this._totalChildConceptsMap = /* @__PURE__ */ new Map();
7096
+ this._sessionManuallyEditedTotals = /* @__PURE__ */ new Set();
6182
7097
  }
6183
7098
  connectedCallback() {
6184
7099
  super.connectedCallback();
@@ -6206,6 +7121,9 @@ let JupiterFormSection = class extends LitElement {
6206
7121
  this._clearCalculationState();
6207
7122
  }
6208
7123
  }
7124
+ if (changedProperties.has("totalManuallyEditedData")) {
7125
+ this._sessionManuallyEditedTotals.clear();
7126
+ }
6209
7127
  }
6210
7128
  _checkAndExpandFirstSection() {
6211
7129
  if (this.isFirstSection && this.section && this.section.concepts) {
@@ -6355,6 +7273,15 @@ let JupiterFormSection = class extends LitElement {
6355
7273
  }
6356
7274
  _handleFieldChange(event) {
6357
7275
  event.stopPropagation();
7276
+ const { conceptId, columnId } = event.detail || {};
7277
+ if (conceptId && columnId && this._totalConceptMap.has(conceptId)) {
7278
+ this._sessionManuallyEditedTotals.add(`${conceptId}__${columnId}`);
7279
+ this.dispatchEvent(new CustomEvent("total-manually-edited", {
7280
+ detail: { conceptId, columnId },
7281
+ bubbles: true,
7282
+ composed: true
7283
+ }));
7284
+ }
6358
7285
  this.dispatchEvent(new CustomEvent("field-change", {
6359
7286
  detail: event.detail,
6360
7287
  bubbles: true,
@@ -6383,6 +7310,13 @@ let JupiterFormSection = class extends LitElement {
6383
7310
  }
6384
7311
  return result;
6385
7312
  }
7313
+ _isTotalManuallyEdited(totalConceptId, columnId) {
7314
+ var _a, _b;
7315
+ const key = `${totalConceptId}__${columnId}`;
7316
+ if (this._sessionManuallyEditedTotals.has(key))
7317
+ return true;
7318
+ return ((_b = (_a = this.totalManuallyEditedData) == null ? void 0 : _a[totalConceptId]) == null ? void 0 : _b[columnId]) === true;
7319
+ }
6386
7320
  /**
6387
7321
  * Returns true when every column cell for a concept has no value.
6388
7322
  * Used in readonly mode to hide fully-blank rows.
@@ -6463,7 +7397,7 @@ let JupiterFormSection = class extends LitElement {
6463
7397
  * matching is the single source of truth for "is this row a total" everywhere in this file.
6464
7398
  */
6465
7399
  static _isTotalLabel(preferredLabel) {
6466
- return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("totallabel"));
7400
+ return isTotalLabel$1(preferredLabel);
6467
7401
  }
6468
7402
  /**
6469
7403
  * JDF-041: distinguishes a self-contained "deduction block" subtotal (negatedTotalLabel, e.g.
@@ -6584,10 +7518,29 @@ let JupiterFormSection = class extends LitElement {
6584
7518
  }
6585
7519
  }
6586
7520
  _evaluateTotal(totalConceptId, columnId) {
6587
- var _a;
7521
+ var _a, _b;
6588
7522
  const totalConcept = this._totalConceptMap.get(totalConceptId);
6589
- const totalRaw = (_a = this.formData[totalConceptId]) == null ? void 0 : _a[columnId];
6590
7523
  const key = `${totalConceptId}__${columnId}`;
7524
+ const children = this._totalChildConceptsMap.get(totalConceptId) || [];
7525
+ const { calculatedSum, hasAnyValue, breakdown } = this._buildCalculationBreakdown(
7526
+ children,
7527
+ this.formData,
7528
+ columnId,
7529
+ totalConcept.balance
7530
+ );
7531
+ if (!this._isTotalManuallyEdited(totalConceptId, columnId)) {
7532
+ if (hasAnyValue) {
7533
+ const currentRaw = (_a = this.formData[totalConceptId]) == null ? void 0 : _a[columnId];
7534
+ const currentValue = parseFloat(currentRaw);
7535
+ const isBlank = currentRaw === null || currentRaw === void 0 || currentRaw === "";
7536
+ if (isBlank || isNaN(currentValue) || !JupiterFormSection._checkTotal(currentValue, calculatedSum)) {
7537
+ this._dispatchCalculationTotalAutoPopulated(totalConceptId, columnId, calculatedSum);
7538
+ }
7539
+ }
7540
+ this._clearCalculationMismatch(key);
7541
+ return;
7542
+ }
7543
+ const totalRaw = (_b = this.formData[totalConceptId]) == null ? void 0 : _b[columnId];
6591
7544
  if (totalRaw === null || totalRaw === void 0 || totalRaw === "") {
6592
7545
  this._clearCalculationMismatch(key);
6593
7546
  return;
@@ -6597,13 +7550,6 @@ let JupiterFormSection = class extends LitElement {
6597
7550
  this._clearCalculationMismatch(key);
6598
7551
  return;
6599
7552
  }
6600
- const children = this._totalChildConceptsMap.get(totalConceptId) || [];
6601
- const { calculatedSum, hasAnyValue, breakdown } = this._buildCalculationBreakdown(
6602
- children,
6603
- this.formData,
6604
- columnId,
6605
- totalConcept.balance
6606
- );
6607
7553
  if (!hasAnyValue || JupiterFormSection._checkTotal(totalValue, calculatedSum)) {
6608
7554
  this._clearCalculationMismatch(key);
6609
7555
  return;
@@ -6626,6 +7572,13 @@ let JupiterFormSection = class extends LitElement {
6626
7572
  this._calculationMismatches = next;
6627
7573
  this._dispatchCalculationMismatchChanged(key, detail);
6628
7574
  }
7575
+ _dispatchCalculationTotalAutoPopulated(conceptId, columnId, value) {
7576
+ this.dispatchEvent(new CustomEvent("calculation-total-auto-populated", {
7577
+ detail: { conceptId, columnId, value, sectionId: this.section.id },
7578
+ bubbles: true,
7579
+ composed: true
7580
+ }));
7581
+ }
6629
7582
  _clearCalculationMismatch(key) {
6630
7583
  if (!this._calculationMismatches.has(key))
6631
7584
  return;
@@ -6689,7 +7642,7 @@ let JupiterFormSection = class extends LitElement {
6689
7642
  return { calculatedSum, hasAnyValue, breakdown };
6690
7643
  }
6691
7644
  static _checkTotal(totalValue, calculatedSum) {
6692
- return Math.abs(calculatedSum - totalValue) < 0.01;
7645
+ return Math.abs(calculatedSum - totalValue) < CALCULATION_TOLERANCE;
6693
7646
  }
6694
7647
  static _formatNumber(n3) {
6695
7648
  return Number.isInteger(n3) ? String(n3) : n3.toFixed(2);
@@ -7275,123 +8228,126 @@ JupiterFormSection.styles = css`
7275
8228
  font-style: italic;
7276
8229
  }
7277
8230
  `;
7278
- __decorateClass$3([
8231
+ __decorateClass$4([
7279
8232
  n2({ type: Object })
7280
8233
  ], JupiterFormSection.prototype, "section", 2);
7281
- __decorateClass$3([
8234
+ __decorateClass$4([
7282
8235
  n2({ type: Array })
7283
8236
  ], JupiterFormSection.prototype, "columns", 2);
7284
- __decorateClass$3([
8237
+ __decorateClass$4([
7285
8238
  n2({ type: Array })
7286
8239
  ], JupiterFormSection.prototype, "datatypes", 2);
7287
- __decorateClass$3([
8240
+ __decorateClass$4([
7288
8241
  n2({ type: Object })
7289
8242
  ], JupiterFormSection.prototype, "formData", 2);
7290
- __decorateClass$3([
8243
+ __decorateClass$4([
7291
8244
  n2({ type: Object })
7292
8245
  ], JupiterFormSection.prototype, "periodData", 2);
7293
- __decorateClass$3([
8246
+ __decorateClass$4([
7294
8247
  n2({ type: Object })
7295
8248
  ], JupiterFormSection.prototype, "unitData", 2);
7296
- __decorateClass$3([
8249
+ __decorateClass$4([
7297
8250
  n2({ type: Object })
7298
8251
  ], JupiterFormSection.prototype, "decimalsData", 2);
7299
- __decorateClass$3([
8252
+ __decorateClass$4([
7300
8253
  n2({ type: String })
7301
8254
  ], JupiterFormSection.prototype, "globalDecimals", 2);
7302
- __decorateClass$3([
8255
+ __decorateClass$4([
7303
8256
  n2({ type: Object })
7304
8257
  ], JupiterFormSection.prototype, "typedMemberData", 2);
7305
- __decorateClass$3([
8258
+ __decorateClass$4([
7306
8259
  n2({ type: Object })
7307
8260
  ], JupiterFormSection.prototype, "repeatCounts", 2);
7308
- __decorateClass$3([
8261
+ __decorateClass$4([
7309
8262
  n2({ type: Array })
7310
8263
  ], JupiterFormSection.prototype, "defaultUnits", 2);
7311
- __decorateClass$3([
8264
+ __decorateClass$4([
7312
8265
  n2({ type: Boolean })
7313
8266
  ], JupiterFormSection.prototype, "disabled", 2);
7314
- __decorateClass$3([
8267
+ __decorateClass$4([
7315
8268
  n2({ type: Boolean })
7316
8269
  ], JupiterFormSection.prototype, "collapsible", 2);
7317
- __decorateClass$3([
8270
+ __decorateClass$4([
7318
8271
  n2({ type: String })
7319
8272
  ], JupiterFormSection.prototype, "locale", 2);
7320
- __decorateClass$3([
8273
+ __decorateClass$4([
7321
8274
  n2({ type: Boolean })
7322
8275
  ], JupiterFormSection.prototype, "isFirstSection", 2);
7323
- __decorateClass$3([
8276
+ __decorateClass$4([
7324
8277
  n2({ type: Array })
7325
8278
  ], JupiterFormSection.prototype, "availableDimensions", 2);
7326
- __decorateClass$3([
8279
+ __decorateClass$4([
7327
8280
  n2({ type: Boolean })
7328
8281
  ], JupiterFormSection.prototype, "hideHeader", 2);
7329
- __decorateClass$3([
8282
+ __decorateClass$4([
7330
8283
  n2({ type: String })
7331
8284
  ], JupiterFormSection.prototype, "mode", 2);
7332
- __decorateClass$3([
8285
+ __decorateClass$4([
7333
8286
  n2({ type: Boolean })
7334
8287
  ], JupiterFormSection.prototype, "showFactsOnly", 2);
7335
- __decorateClass$3([
8288
+ __decorateClass$4([
7336
8289
  n2({ type: Object })
7337
8290
  ], JupiterFormSection.prototype, "conceptMatchIds", 2);
7338
- __decorateClass$3([
8291
+ __decorateClass$4([
7339
8292
  n2({ type: Object })
7340
8293
  ], JupiterFormSection.prototype, "masterData", 2);
7341
- __decorateClass$3([
8294
+ __decorateClass$4([
7342
8295
  n2({ type: String })
7343
8296
  ], JupiterFormSection.prototype, "periodStartDate", 2);
7344
- __decorateClass$3([
8297
+ __decorateClass$4([
7345
8298
  n2({ type: String })
7346
8299
  ], JupiterFormSection.prototype, "periodEndDate", 2);
7347
- __decorateClass$3([
8300
+ __decorateClass$4([
7348
8301
  n2({ type: String })
7349
8302
  ], JupiterFormSection.prototype, "language", 2);
7350
- __decorateClass$3([
8303
+ __decorateClass$4([
7351
8304
  n2({ type: Boolean })
7352
8305
  ], JupiterFormSection.prototype, "calculationEnabled", 2);
7353
- __decorateClass$3([
8306
+ __decorateClass$4([
8307
+ n2({ type: Object })
8308
+ ], JupiterFormSection.prototype, "totalManuallyEditedData", 2);
8309
+ __decorateClass$4([
7354
8310
  r()
7355
8311
  ], JupiterFormSection.prototype, "_expanded", 2);
7356
- __decorateClass$3([
8312
+ __decorateClass$4([
7357
8313
  r()
7358
8314
  ], JupiterFormSection.prototype, "_showAddColumnDialog", 2);
7359
- __decorateClass$3([
8315
+ __decorateClass$4([
7360
8316
  r()
7361
8317
  ], JupiterFormSection.prototype, "_sectionPeriodType", 2);
7362
- __decorateClass$3([
8318
+ __decorateClass$4([
7363
8319
  r()
7364
8320
  ], JupiterFormSection.prototype, "_openMenuColumnId", 2);
7365
- __decorateClass$3([
8321
+ __decorateClass$4([
7366
8322
  r()
7367
8323
  ], JupiterFormSection.prototype, "_insertAfterColumnId", 2);
7368
- __decorateClass$3([
8324
+ __decorateClass$4([
7369
8325
  r()
7370
8326
  ], JupiterFormSection.prototype, "_expandedConcepts", 2);
7371
- __decorateClass$3([
8327
+ __decorateClass$4([
7372
8328
  r()
7373
8329
  ], JupiterFormSection.prototype, "_allTreeExpanded", 2);
7374
- __decorateClass$3([
8330
+ __decorateClass$4([
7375
8331
  r()
7376
8332
  ], JupiterFormSection.prototype, "_highlightMap", 2);
7377
- __decorateClass$3([
8333
+ __decorateClass$4([
7378
8334
  r()
7379
8335
  ], JupiterFormSection.prototype, "_focusedConceptId", 2);
7380
- __decorateClass$3([
8336
+ __decorateClass$4([
7381
8337
  r()
7382
8338
  ], JupiterFormSection.prototype, "_calculationMismatches", 2);
7383
- JupiterFormSection = __decorateClass$3([
8339
+ JupiterFormSection = __decorateClass$4([
7384
8340
  t$1("jupiter-form-section")
7385
8341
  ], JupiterFormSection);
7386
- var __defProp$2 = Object.defineProperty;
7387
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
7388
- var __decorateClass$2 = (decorators, target, key, kind) => {
7389
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
8342
+ var __defProp$3 = Object.defineProperty;
8343
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
8344
+ var __decorateClass$3 = (decorators, target, key, kind) => {
8345
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
7390
8346
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
7391
8347
  if (decorator = decorators[i2])
7392
8348
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
7393
8349
  if (kind && result)
7394
- __defProp$2(target, key, result);
8350
+ __defProp$3(target, key, result);
7395
8351
  return result;
7396
8352
  };
7397
8353
  let JupiterAdvancedFilter = class extends LitElement {
@@ -7580,27 +8536,27 @@ JupiterAdvancedFilter.styles = css`
7580
8536
  color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
7581
8537
  }
7582
8538
  `;
7583
- __decorateClass$2([
8539
+ __decorateClass$3([
7584
8540
  n2({ type: Boolean })
7585
8541
  ], JupiterAdvancedFilter.prototype, "showFactsOnly", 2);
7586
- __decorateClass$2([
8542
+ __decorateClass$3([
7587
8543
  n2({ type: String })
7588
8544
  ], JupiterAdvancedFilter.prototype, "conceptSearchText", 2);
7589
- __decorateClass$2([
8545
+ __decorateClass$3([
7590
8546
  r()
7591
8547
  ], JupiterAdvancedFilter.prototype, "_localConceptSearchText", 2);
7592
- JupiterAdvancedFilter = __decorateClass$2([
8548
+ JupiterAdvancedFilter = __decorateClass$3([
7593
8549
  t$1("jupiter-advanced-filter")
7594
8550
  ], JupiterAdvancedFilter);
7595
- var __defProp$1 = Object.defineProperty;
7596
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
7597
- var __decorateClass$1 = (decorators, target, key, kind) => {
7598
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
8551
+ var __defProp$2 = Object.defineProperty;
8552
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
8553
+ var __decorateClass$2 = (decorators, target, key, kind) => {
8554
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
7599
8555
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
7600
8556
  if (decorator = decorators[i2])
7601
8557
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
7602
8558
  if (kind && result)
7603
- __defProp$1(target, key, result);
8559
+ __defProp$2(target, key, result);
7604
8560
  return result;
7605
8561
  };
7606
8562
  let _lastViewWasAdvancedFilter = false;
@@ -9217,162 +10173,539 @@ JupiterFilterRolesDialog.styles = css`
9217
10173
  border-color: var(--jupiter-primary-color, #1976d2);
9218
10174
  }
9219
10175
 
9220
- .picklist-item.selected {
9221
- background: var(--jupiter-primary-color, #1976d2);
9222
- color: white;
9223
- border-color: var(--jupiter-primary-color, #1976d2);
10176
+ .picklist-item.selected {
10177
+ background: var(--jupiter-primary-color, #1976d2);
10178
+ color: white;
10179
+ border-color: var(--jupiter-primary-color, #1976d2);
10180
+ }
10181
+
10182
+ .picklist-item.dragging {
10183
+ opacity: 0.5;
10184
+ cursor: move;
10185
+ }
10186
+
10187
+ .picklist-item.drag-over {
10188
+ border-top: 3px solid var(--jupiter-primary-color, #1976d2);
10189
+ margin-top: 3px;
10190
+ }
10191
+
10192
+ .picklist-item[draggable="true"] {
10193
+ cursor: move;
10194
+ }
10195
+
10196
+ .picklist-item-label {
10197
+ font-weight: 500;
10198
+ font-size: 14px;
10199
+ margin-bottom: 4px;
10200
+ }
10201
+
10202
+ .picklist-item-value {
10203
+ font-size: 11px;
10204
+ opacity: 0.8;
10205
+ word-break: break-all;
10206
+ }
10207
+
10208
+ .picklist-controls {
10209
+ display: flex;
10210
+ flex-direction: column;
10211
+ justify-content: center;
10212
+ gap: 8px;
10213
+ padding: 0 8px;
10214
+ }
10215
+
10216
+ .picklist-button {
10217
+ background: var(--jupiter-primary-color, #1976d2);
10218
+ color: white;
10219
+ border: none;
10220
+ padding: 8px 16px;
10221
+ border-radius: 4px;
10222
+ cursor: pointer;
10223
+ font-size: 14px;
10224
+ transition: background-color 0.2s ease;
10225
+ }
10226
+
10227
+ .picklist-button:hover:not(:disabled) {
10228
+ background: var(--jupiter-primary-color-dark, #1565c0);
10229
+ }
10230
+
10231
+ .picklist-button:disabled {
10232
+ opacity: 0.4;
10233
+ cursor: not-allowed;
10234
+ }
10235
+
10236
+ .picklist-count {
10237
+ padding: 8px 12px;
10238
+ font-size: 12px;
10239
+ color: var(--jupiter-text-secondary, #666);
10240
+ background: var(--jupiter-background-light, #f8f9fa);
10241
+ border-top: 1px solid var(--jupiter-border-color, #ddd);
10242
+ text-align: center;
10243
+ }
10244
+
10245
+ .btn-text {
10246
+ background: none;
10247
+ border: none;
10248
+ color: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
10249
+ padding: 10px 8px;
10250
+ font-size: 14px;
10251
+ font-weight: 500;
10252
+ font-family: inherit;
10253
+ cursor: pointer;
10254
+ margin-right: auto;
10255
+ border-radius: 4px;
10256
+ transition: background-color 0.2s ease;
10257
+ }
10258
+
10259
+ .btn-text:hover {
10260
+ background: var(--menuBgColorLighter, var(--jupiter-hover-background, #f5f5f5));
10261
+ }
10262
+ `;
10263
+ __decorateClass$2([
10264
+ n2({ type: Boolean, reflect: true })
10265
+ ], JupiterFilterRolesDialog.prototype, "open", 2);
10266
+ __decorateClass$2([
10267
+ n2({ type: Array })
10268
+ ], JupiterFilterRolesDialog.prototype, "availableRoles", 2);
10269
+ __decorateClass$2([
10270
+ n2({ type: Array })
10271
+ ], JupiterFilterRolesDialog.prototype, "selectedRoleIds", 2);
10272
+ __decorateClass$2([
10273
+ n2({ type: Object })
10274
+ ], JupiterFilterRolesDialog.prototype, "periodPreferences", 2);
10275
+ __decorateClass$2([
10276
+ n2({ type: String })
10277
+ ], JupiterFilterRolesDialog.prototype, "mode", 2);
10278
+ __decorateClass$2([
10279
+ n2({ type: Object })
10280
+ ], JupiterFilterRolesDialog.prototype, "hypercubeData", 2);
10281
+ __decorateClass$2([
10282
+ n2({ type: Boolean })
10283
+ ], JupiterFilterRolesDialog.prototype, "showFactsOnly", 2);
10284
+ __decorateClass$2([
10285
+ n2({ type: String })
10286
+ ], JupiterFilterRolesDialog.prototype, "conceptSearchText", 2);
10287
+ __decorateClass$2([
10288
+ r()
10289
+ ], JupiterFilterRolesDialog.prototype, "_tempSelectedRoles", 2);
10290
+ __decorateClass$2([
10291
+ r()
10292
+ ], JupiterFilterRolesDialog.prototype, "_searchQuery", 2);
10293
+ __decorateClass$2([
10294
+ r()
10295
+ ], JupiterFilterRolesDialog.prototype, "_filteredRoles", 2);
10296
+ __decorateClass$2([
10297
+ r()
10298
+ ], JupiterFilterRolesDialog.prototype, "_tempPeriodPreferences", 2);
10299
+ __decorateClass$2([
10300
+ r()
10301
+ ], JupiterFilterRolesDialog.prototype, "_selectedAvailableRole", 2);
10302
+ __decorateClass$2([
10303
+ r()
10304
+ ], JupiterFilterRolesDialog.prototype, "_selectedChosenRole", 2);
10305
+ __decorateClass$2([
10306
+ r()
10307
+ ], JupiterFilterRolesDialog.prototype, "_chosenSearchQuery", 2);
10308
+ __decorateClass$2([
10309
+ r()
10310
+ ], JupiterFilterRolesDialog.prototype, "_draggedRoleId", 2);
10311
+ __decorateClass$2([
10312
+ r()
10313
+ ], JupiterFilterRolesDialog.prototype, "_dragOverRoleId", 2);
10314
+ __decorateClass$2([
10315
+ r()
10316
+ ], JupiterFilterRolesDialog.prototype, "_chosenRoleOrder", 2);
10317
+ __decorateClass$2([
10318
+ r()
10319
+ ], JupiterFilterRolesDialog.prototype, "_showAdvancedFilter", 2);
10320
+ __decorateClass$2([
10321
+ r()
10322
+ ], JupiterFilterRolesDialog.prototype, "_showFactsOnly", 2);
10323
+ __decorateClass$2([
10324
+ r()
10325
+ ], JupiterFilterRolesDialog.prototype, "_conceptSearchText", 2);
10326
+ __decorateClass$2([
10327
+ r()
10328
+ ], JupiterFilterRolesDialog.prototype, "_collapsedRoles", 2);
10329
+ JupiterFilterRolesDialog = __decorateClass$2([
10330
+ t$1("jupiter-filter-roles-dialog")
10331
+ ], JupiterFilterRolesDialog);
10332
+ var __defProp$1 = Object.defineProperty;
10333
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
10334
+ var __decorateClass$1 = (decorators, target, key, kind) => {
10335
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
10336
+ for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
10337
+ if (decorator = decorators[i2])
10338
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
10339
+ if (kind && result)
10340
+ __defProp$1(target, key, result);
10341
+ return result;
10342
+ };
10343
+ let JupiterFormulaValidationDialog = class extends LitElement {
10344
+ constructor() {
10345
+ super(...arguments);
10346
+ this.results = [];
10347
+ this.open = false;
10348
+ this._collapsedGroups = /* @__PURE__ */ new Set();
10349
+ this._boundHandleKeydown = this._handleKeydown.bind(this);
10350
+ }
10351
+ connectedCallback() {
10352
+ super.connectedCallback();
10353
+ window.addEventListener("keydown", this._boundHandleKeydown);
10354
+ }
10355
+ disconnectedCallback() {
10356
+ window.removeEventListener("keydown", this._boundHandleKeydown);
10357
+ super.disconnectedCallback();
10358
+ }
10359
+ _handleKeydown(event) {
10360
+ if (this.open && event.key === "Escape") {
10361
+ this._handleClose();
10362
+ }
10363
+ }
10364
+ _handleClose() {
10365
+ this.open = false;
10366
+ this.dispatchEvent(new CustomEvent("dialog-cancel", { bubbles: true }));
10367
+ }
10368
+ _toggleGroup(roleLabel) {
10369
+ if (this._collapsedGroups.has(roleLabel)) {
10370
+ this._collapsedGroups.delete(roleLabel);
10371
+ } else {
10372
+ this._collapsedGroups.add(roleLabel);
10373
+ }
10374
+ this.requestUpdate();
10375
+ }
10376
+ // JDF-059: navigates the host form to the exact fact this assertion's variable resolved to
10377
+ // (or its empty target cell, for a "must exist" failure) and closes this dialog, mirroring the
10378
+ // existing "click a validation error, land on its field" flow this feature deliberately reuses
10379
+ // rather than building a second focus mechanism.
10380
+ _handleConceptLinkClick(link) {
10381
+ this.dispatchEvent(new CustomEvent("formula-concept-click", {
10382
+ detail: { conceptId: link.conceptId, conceptQName: link.conceptQName, columnId: link.columnId },
10383
+ bubbles: true,
10384
+ composed: true
10385
+ }));
10386
+ }
10387
+ // Splits a message on its backtick-quoted concept mentions and renders the ones this
10388
+ // assertion resolved a fact location for (`result.conceptLinks`) as click-to-focus buttons;
10389
+ // any other backtick mention (e.g. an enumerated value like `Na`, not a concept) renders as
10390
+ // plain text with the backticks dropped, since there is nothing to link it to.
10391
+ _renderMessage(result) {
10392
+ const message = result.message ?? "";
10393
+ const linkByLabel = new Map((result.conceptLinks ?? []).map((link) => [link.label, link]));
10394
+ const parts = message.split(/(`[^`]+`)/g);
10395
+ return html`${parts.map((part) => {
10396
+ const match = /^`([^`]+)`$/.exec(part);
10397
+ if (!match)
10398
+ return part;
10399
+ const link = linkByLabel.get(match[1]);
10400
+ if (!link)
10401
+ return match[1];
10402
+ return html`<button type="button" class="concept-link" @click="${() => this._handleConceptLinkClick(link)}">${match[1]}</button>`;
10403
+ })}`;
10404
+ }
10405
+ _groupByRole(failures) {
10406
+ const grouped = failures.reduce((groups, result) => {
10407
+ var _a;
10408
+ (groups[_a = result.roleLabel] ?? (groups[_a] = [])).push(result);
10409
+ return groups;
10410
+ }, {});
10411
+ return Object.entries(grouped);
10412
+ }
10413
+ render() {
10414
+ if (!this.open)
10415
+ return html``;
10416
+ const failures = this.results.filter((result) => !result.skipped && !result.satisfied);
10417
+ const groups = this._groupByRole(failures);
10418
+ const summary = this.summary;
10419
+ return html`
10420
+ <div class="dialog" @click="${(e2) => e2.stopPropagation()}">
10421
+ <div class="dialog-header">
10422
+ <h2 class="dialog-title">${I18n.t("formulaValidation.dialogTitle")}</h2>
10423
+ ${summary ? html`
10424
+ <p class="dialog-summary">
10425
+ ${I18n.t("formulaValidation.summaryCount", { failed: summary.failed, total: summary.totalAssertions })}
10426
+ ${summary.failed > 0 ? html`— ${I18n.t("formulaValidation.summaryCountBreakdown", { warnings: summary.failedWarnings, errors: summary.failedErrors })}` : ""}
10427
+ </p>
10428
+ ` : ""}
10429
+ </div>
10430
+
10431
+ <div class="dialog-content">
10432
+ ${failures.length === 0 ? html`
10433
+ <div class="all-passed">
10434
+ <div class="all-passed-icon">✓</div>
10435
+ <div class="all-passed-text">${I18n.t("formulaValidation.allPassed")}</div>
10436
+ </div>
10437
+ ` : groups.map(([roleLabel, rows]) => {
10438
+ const collapsed = this._collapsedGroups.has(roleLabel);
10439
+ return html`
10440
+ <div class="role-group">
10441
+ <div class="role-group-header" @click="${() => this._toggleGroup(roleLabel)}">
10442
+ <span class="role-group-title">${roleLabel}</span>
10443
+ <span class="role-group-count">${rows.length}</span>
10444
+ <button class="role-group-toggle" type="button">${collapsed ? "▸" : "▾"}</button>
10445
+ </div>
10446
+ ${collapsed ? "" : html`
10447
+ <div class="role-group-rows">
10448
+ ${rows.map((result) => html`
10449
+ <div class="assertion-row">
10450
+ <span class="severity-badge ${result.severity === "ERROR" ? "error" : "warning"}">
10451
+ ${result.severity === "ERROR" ? I18n.t("formulaValidation.severityError") : I18n.t("formulaValidation.severityWarning")}
10452
+ </span>
10453
+ <span class="assertion-message">${this._renderMessage(result)}</span>
10454
+ </div>
10455
+ `)}
10456
+ </div>
10457
+ `}
10458
+ </div>
10459
+ `;
10460
+ })}
10461
+ </div>
10462
+
10463
+ <div class="dialog-actions">
10464
+ <button class="btn btn-primary" @click="${this._handleClose}">
10465
+ ${I18n.t("formulaValidation.close")}
10466
+ </button>
10467
+ </div>
10468
+ </div>
10469
+ `;
10470
+ }
10471
+ };
10472
+ JupiterFormulaValidationDialog.styles = css`
10473
+ :host {
10474
+ position: fixed;
10475
+ top: 0;
10476
+ left: 0;
10477
+ width: 100%;
10478
+ height: 100%;
10479
+ background: rgba(0, 0, 0, 0.5);
10480
+ z-index: 1100;
10481
+ display: flex;
10482
+ align-items: center;
10483
+ justify-content: center;
10484
+ opacity: 0;
10485
+ visibility: hidden;
10486
+ transition: opacity 0.3s ease, visibility 0.3s ease;
10487
+ }
10488
+
10489
+ :host([open]) {
10490
+ opacity: 1;
10491
+ visibility: visible;
10492
+ }
10493
+
10494
+ .dialog {
10495
+ background: var(--bg-color-2, var(--jupiter-card-background, #fff));
10496
+ border-radius: 8px;
10497
+ padding: 24px;
10498
+ min-width: 420px;
10499
+ max-width: 640px;
10500
+ width: 90vw;
10501
+ max-height: 90vh;
10502
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
10503
+ transform: scale(0.9);
10504
+ transition: transform 0.3s ease;
10505
+ display: flex;
10506
+ flex-direction: column;
10507
+ }
10508
+
10509
+ :host([open]) .dialog {
10510
+ transform: scale(1);
10511
+ }
10512
+
10513
+ .dialog-header {
10514
+ margin-bottom: 16px;
10515
+ flex-shrink: 0;
10516
+ }
10517
+
10518
+ .dialog-title {
10519
+ font-size: 20px;
10520
+ font-weight: 600;
10521
+ color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
10522
+ margin: 0 0 8px 0;
10523
+ }
10524
+
10525
+ .dialog-summary {
10526
+ font-size: 14px;
10527
+ color: var(--jupiter-text-secondary, #666);
10528
+ margin: 0;
10529
+ }
10530
+
10531
+ /* JDF-035 dialog-overflow fix: min-height: 0 is required on a flex-column scrollable child,
10532
+ otherwise the browser lets it grow past the dialog's max-height instead of scrolling. */
10533
+ .dialog-content {
10534
+ margin-bottom: 20px;
10535
+ flex: 1;
10536
+ overflow-y: auto;
10537
+ min-height: 0;
10538
+ }
10539
+
10540
+ .all-passed {
10541
+ display: flex;
10542
+ flex-direction: column;
10543
+ align-items: center;
10544
+ gap: 12px;
10545
+ padding: 32px 16px;
10546
+ color: var(--jupiter-text-primary, #333);
10547
+ }
10548
+
10549
+ .all-passed-icon {
10550
+ width: 40px;
10551
+ height: 40px;
10552
+ border-radius: 50%;
10553
+ background: var(--jupiter-success-color, #4caf50);
10554
+ color: #fff;
10555
+ display: flex;
10556
+ align-items: center;
10557
+ justify-content: center;
10558
+ font-size: 22px;
10559
+ font-weight: 700;
10560
+ }
10561
+
10562
+ .all-passed-text {
10563
+ font-size: 15px;
10564
+ font-weight: 500;
10565
+ }
10566
+
10567
+ .role-group {
10568
+ border: 1px solid var(--jupiter-border-color, #ddd);
10569
+ border-radius: 4px;
10570
+ margin-bottom: 12px;
10571
+ overflow: hidden;
10572
+ }
10573
+
10574
+ .role-group-header {
10575
+ display: flex;
10576
+ align-items: center;
10577
+ justify-content: space-between;
10578
+ gap: 8px;
10579
+ padding: 10px 12px;
10580
+ background: var(--jupiter-card-background, #fafafa);
10581
+ cursor: pointer;
10582
+ user-select: none;
10583
+ }
10584
+
10585
+ .role-group-title {
10586
+ font-weight: 600;
10587
+ font-size: 14px;
10588
+ color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
10589
+ }
10590
+
10591
+ .role-group-count {
10592
+ font-size: 12px;
10593
+ color: var(--jupiter-text-secondary, #666);
10594
+ }
10595
+
10596
+ .role-group-toggle {
10597
+ background: none;
10598
+ border: none;
10599
+ cursor: pointer;
10600
+ font-size: 12px;
10601
+ color: var(--jupiter-text-secondary, #666);
10602
+ padding: 0 4px;
9224
10603
  }
9225
10604
 
9226
- .picklist-item.dragging {
9227
- opacity: 0.5;
9228
- cursor: move;
10605
+ .role-group-rows {
10606
+ padding: 4px 12px 8px 12px;
9229
10607
  }
9230
10608
 
9231
- .picklist-item.drag-over {
9232
- border-top: 3px solid var(--jupiter-primary-color, #1976d2);
9233
- margin-top: 3px;
10609
+ .assertion-row {
10610
+ display: flex;
10611
+ align-items: flex-start;
10612
+ gap: 8px;
10613
+ padding: 8px 0;
10614
+ border-top: 1px solid var(--jupiter-border-color, #eee);
9234
10615
  }
9235
10616
 
9236
- .picklist-item[draggable="true"] {
9237
- cursor: move;
10617
+ .assertion-row:first-child {
10618
+ border-top: none;
9238
10619
  }
9239
10620
 
9240
- .picklist-item-label {
9241
- font-weight: 500;
9242
- font-size: 14px;
9243
- margin-bottom: 4px;
10621
+ .severity-badge {
10622
+ flex-shrink: 0;
10623
+ display: inline-flex;
10624
+ align-items: center;
10625
+ gap: 4px;
10626
+ padding: 2px 8px;
10627
+ border-radius: 10px;
10628
+ font-size: 11px;
10629
+ font-weight: 700;
10630
+ color: #fff;
10631
+ white-space: nowrap;
9244
10632
  }
9245
10633
 
9246
- .picklist-item-value {
9247
- font-size: 11px;
9248
- opacity: 0.8;
9249
- word-break: break-all;
10634
+ .severity-badge.warning {
10635
+ background: var(--jupiter-warning-color, #ff9800);
9250
10636
  }
9251
10637
 
9252
- .picklist-controls {
9253
- display: flex;
9254
- flex-direction: column;
9255
- justify-content: center;
9256
- gap: 8px;
9257
- padding: 0 8px;
10638
+ .severity-badge.error {
10639
+ background: var(--jupiter-error-color, #d32f2f);
9258
10640
  }
9259
10641
 
9260
- .picklist-button {
9261
- background: var(--jupiter-primary-color, #1976d2);
9262
- color: white;
9263
- border: none;
9264
- padding: 8px 16px;
9265
- border-radius: 4px;
9266
- cursor: pointer;
9267
- font-size: 14px;
9268
- transition: background-color 0.2s ease;
10642
+ .assertion-message {
10643
+ font-size: 13px;
10644
+ color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
10645
+ line-height: 1.4;
9269
10646
  }
9270
10647
 
9271
- .picklist-button:hover:not(:disabled) {
9272
- background: var(--jupiter-primary-color-dark, #1565c0);
10648
+ .concept-link {
10649
+ font: inherit;
10650
+ font-weight: 600;
10651
+ color: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
10652
+ background: none;
10653
+ border: none;
10654
+ padding: 0;
10655
+ margin: 0;
10656
+ cursor: pointer;
10657
+ text-decoration: underline;
10658
+ text-underline-offset: 2px;
9273
10659
  }
9274
10660
 
9275
- .picklist-button:disabled {
9276
- opacity: 0.4;
9277
- cursor: not-allowed;
10661
+ .concept-link:hover {
10662
+ opacity: 0.8;
9278
10663
  }
9279
10664
 
9280
- .picklist-count {
9281
- padding: 8px 12px;
9282
- font-size: 12px;
9283
- color: var(--jupiter-text-secondary, #666);
9284
- background: var(--jupiter-background-light, #f8f9fa);
10665
+ .dialog-actions {
10666
+ display: flex;
10667
+ gap: 12px;
10668
+ justify-content: flex-end;
10669
+ flex-shrink: 0;
9285
10670
  border-top: 1px solid var(--jupiter-border-color, #ddd);
9286
- text-align: center;
10671
+ padding-top: 16px;
10672
+ margin-top: 16px;
9287
10673
  }
9288
10674
 
9289
- .btn-text {
9290
- background: none;
10675
+ .btn {
10676
+ padding: 10px 20px;
9291
10677
  border: none;
9292
- color: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
9293
- padding: 10px 8px;
10678
+ border-radius: 4px;
9294
10679
  font-size: 14px;
9295
10680
  font-weight: 500;
9296
- font-family: inherit;
9297
10681
  cursor: pointer;
9298
- margin-right: auto;
9299
- border-radius: 4px;
9300
10682
  transition: background-color 0.2s ease;
9301
10683
  }
9302
10684
 
9303
- .btn-text:hover {
9304
- background: var(--menuBgColorLighter, var(--jupiter-hover-background, #f5f5f5));
10685
+ .btn-primary {
10686
+ background: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
10687
+ color: var(--buttonTextColor, white);
10688
+ }
10689
+
10690
+ .btn-primary:hover {
10691
+ opacity: 0.9;
9305
10692
  }
9306
10693
  `;
9307
- __decorateClass$1([
9308
- n2({ type: Boolean, reflect: true })
9309
- ], JupiterFilterRolesDialog.prototype, "open", 2);
9310
- __decorateClass$1([
9311
- n2({ type: Array })
9312
- ], JupiterFilterRolesDialog.prototype, "availableRoles", 2);
9313
10694
  __decorateClass$1([
9314
10695
  n2({ type: Array })
9315
- ], JupiterFilterRolesDialog.prototype, "selectedRoleIds", 2);
9316
- __decorateClass$1([
9317
- n2({ type: Object })
9318
- ], JupiterFilterRolesDialog.prototype, "periodPreferences", 2);
9319
- __decorateClass$1([
9320
- n2({ type: String })
9321
- ], JupiterFilterRolesDialog.prototype, "mode", 2);
10696
+ ], JupiterFormulaValidationDialog.prototype, "results", 2);
9322
10697
  __decorateClass$1([
9323
10698
  n2({ type: Object })
9324
- ], JupiterFilterRolesDialog.prototype, "hypercubeData", 2);
9325
- __decorateClass$1([
9326
- n2({ type: Boolean })
9327
- ], JupiterFilterRolesDialog.prototype, "showFactsOnly", 2);
9328
- __decorateClass$1([
9329
- n2({ type: String })
9330
- ], JupiterFilterRolesDialog.prototype, "conceptSearchText", 2);
9331
- __decorateClass$1([
9332
- r()
9333
- ], JupiterFilterRolesDialog.prototype, "_tempSelectedRoles", 2);
9334
- __decorateClass$1([
9335
- r()
9336
- ], JupiterFilterRolesDialog.prototype, "_searchQuery", 2);
9337
- __decorateClass$1([
9338
- r()
9339
- ], JupiterFilterRolesDialog.prototype, "_filteredRoles", 2);
9340
- __decorateClass$1([
9341
- r()
9342
- ], JupiterFilterRolesDialog.prototype, "_tempPeriodPreferences", 2);
9343
- __decorateClass$1([
9344
- r()
9345
- ], JupiterFilterRolesDialog.prototype, "_selectedAvailableRole", 2);
9346
- __decorateClass$1([
9347
- r()
9348
- ], JupiterFilterRolesDialog.prototype, "_selectedChosenRole", 2);
9349
- __decorateClass$1([
9350
- r()
9351
- ], JupiterFilterRolesDialog.prototype, "_chosenSearchQuery", 2);
9352
- __decorateClass$1([
9353
- r()
9354
- ], JupiterFilterRolesDialog.prototype, "_draggedRoleId", 2);
9355
- __decorateClass$1([
9356
- r()
9357
- ], JupiterFilterRolesDialog.prototype, "_dragOverRoleId", 2);
9358
- __decorateClass$1([
9359
- r()
9360
- ], JupiterFilterRolesDialog.prototype, "_chosenRoleOrder", 2);
9361
- __decorateClass$1([
9362
- r()
9363
- ], JupiterFilterRolesDialog.prototype, "_showAdvancedFilter", 2);
9364
- __decorateClass$1([
9365
- r()
9366
- ], JupiterFilterRolesDialog.prototype, "_showFactsOnly", 2);
10699
+ ], JupiterFormulaValidationDialog.prototype, "summary", 2);
9367
10700
  __decorateClass$1([
9368
- r()
9369
- ], JupiterFilterRolesDialog.prototype, "_conceptSearchText", 2);
10701
+ n2({ type: Boolean, reflect: true })
10702
+ ], JupiterFormulaValidationDialog.prototype, "open", 2);
9370
10703
  __decorateClass$1([
9371
10704
  r()
9372
- ], JupiterFilterRolesDialog.prototype, "_collapsedRoles", 2);
9373
- JupiterFilterRolesDialog = __decorateClass$1([
9374
- t$1("jupiter-filter-roles-dialog")
9375
- ], JupiterFilterRolesDialog);
10705
+ ], JupiterFormulaValidationDialog.prototype, "_collapsedGroups", 2);
10706
+ JupiterFormulaValidationDialog = __decorateClass$1([
10707
+ t$1("jupiter-formula-validation-dialog")
10708
+ ], JupiterFormulaValidationDialog);
9376
10709
  var __defProp = Object.defineProperty;
9377
10710
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9378
10711
  var __decorateClass = (decorators, target, key, kind) => {
@@ -9392,6 +10725,7 @@ let JupiterDynamicForm = class extends LitElement {
9392
10725
  this.disabled = false;
9393
10726
  this.readonly = false;
9394
10727
  this.calculationEnabled = false;
10728
+ this.formulaEnabled = false;
9395
10729
  this.periodStartDate = "2025-01-01";
9396
10730
  this.periodEndDate = "2025-12-31";
9397
10731
  this.language = "en";
@@ -9411,6 +10745,7 @@ let JupiterDynamicForm = class extends LitElement {
9411
10745
  this._unitData = {};
9412
10746
  this._preservedUnitData = {};
9413
10747
  this._decimalsData = {};
10748
+ this._totalManuallyEditedData = {};
9414
10749
  this._effectiveMasterData = void 0;
9415
10750
  this._typedMemberData = {};
9416
10751
  this._preservedTypedMemberData = {};
@@ -9443,6 +10778,9 @@ let JupiterDynamicForm = class extends LitElement {
9443
10778
  this._contextMenuY = 0;
9444
10779
  this._contextMenuRoleId = null;
9445
10780
  this._validationStatus = "idle";
10781
+ this._formulaValidationResults = [];
10782
+ this._showFormulaValidationDialog = false;
10783
+ this._formulaValidationService = new FormulaValidationService();
9446
10784
  this._skipDraftLoading = false;
9447
10785
  this._skipPeriodPreferencesRestore = false;
9448
10786
  this._autoSaveTimer = null;
@@ -9474,6 +10812,24 @@ let JupiterDynamicForm = class extends LitElement {
9474
10812
  this.addEventListener("calculation-mismatch-changed", (e2) => {
9475
10813
  this._handleCalculationMismatchChanged(e2);
9476
10814
  });
10815
+ this.addEventListener("total-manually-edited", (e2) => {
10816
+ const { conceptId, columnId } = e2.detail;
10817
+ const updated = { ...this._totalManuallyEditedData };
10818
+ updated[conceptId] = { ...updated[conceptId], [columnId]: true };
10819
+ this._totalManuallyEditedData = updated;
10820
+ });
10821
+ this.addEventListener("calculation-total-auto-populated", (e2) => {
10822
+ const { conceptId, columnId, value } = e2.detail;
10823
+ const oldValue = this._writeFormDataValue(conceptId, columnId, value);
10824
+ this._touched.add(`${conceptId}-${columnId}`);
10825
+ this._dirty = true;
10826
+ this._validateForm();
10827
+ this.requestUpdate();
10828
+ this.dispatchEvent(new CustomEvent("field-change", {
10829
+ detail: { fieldId: conceptId, conceptId, columnId, value, oldValue, source: "auto-calculated" },
10830
+ bubbles: true
10831
+ }));
10832
+ });
9477
10833
  document.addEventListener("click", () => {
9478
10834
  if (this._showRoleContextMenu) {
9479
10835
  this._showRoleContextMenu = false;
@@ -10148,6 +11504,20 @@ let JupiterDynamicForm = class extends LitElement {
10148
11504
  const roleCount = this._getRoleIdsArray().length;
10149
11505
  console.log(`🚫 Filter dialog cancelled. Current selection: ${roleCount}/${this._allSections.length}`);
10150
11506
  }
11507
+ // JDF-050: closes the formula-validation results dialog opened by JDF-049's pre-flight.
11508
+ // Purely a visibility toggle — submit already proceeded on the click that opened it
11509
+ // (non-blocking, per the corrected JDF-043 Q4 decision), so there is nothing to resume here.
11510
+ _handleFormulaValidationDialogCancel() {
11511
+ this._showFormulaValidationDialog = false;
11512
+ }
11513
+ // JDF-059: a concept name inside a formula-validation message was clicked — close the popup
11514
+ // (mirrors the existing "click a validation error, land on its field" UX from the host-rendered
11515
+ // show-validation-results flow) and jump straight to the exact fact the assertion's own variable
11516
+ // resolution identified, via `columnId` rather than re-deriving it from dimensions.
11517
+ _handleFormulaConceptClick(event) {
11518
+ this._showFormulaValidationDialog = false;
11519
+ this.scrollToConcept(event.detail.conceptQName, void 0, void 0, event.detail.columnId);
11520
+ }
10151
11521
  _handleRoleFilterApply(event) {
10152
11522
  var _a, _b;
10153
11523
  const { selectedRoleIds, periodPreferences } = event.detail;
@@ -10175,7 +11545,8 @@ let JupiterDynamicForm = class extends LitElement {
10175
11545
  this._repeatCounts,
10176
11546
  this._decimalsData,
10177
11547
  void 0,
10178
- this._getHiddenColumnsForMetadata()
11548
+ this._getHiddenColumnsForMetadata(),
11549
+ this._totalManuallyEditedData
10179
11550
  );
10180
11551
  this._draftStorageService.saveDraft(currentFormData, currentMetadata);
10181
11552
  console.log("✅ Current form data saved to draft storage with NEW preferences");
@@ -10284,9 +11655,13 @@ let JupiterDynamicForm = class extends LitElement {
10284
11655
  this._calculationWarnings = next;
10285
11656
  this._validateForm();
10286
11657
  }
10287
- _handleFieldChange(event) {
10288
- const { fieldId, conceptId, columnId, value } = event.detail;
10289
- console.log(`📝 Field change: conceptId=${conceptId}, columnId=${columnId}, value=${value}, fieldId=${fieldId}`);
11658
+ /**
11659
+ * JDF-058: single source of truth for writing a value into `_formData` with the immutable-update
11660
+ * pattern Lit reactivity needs shared by a real user keystroke (`_handleFieldChange`) and the
11661
+ * auto-populate-totals `calculation-total-auto-populated` handler, so the update logic isn't
11662
+ * duplicated. Returns the value that was previously stored, for event `oldValue` fields.
11663
+ */
11664
+ _writeFormDataValue(conceptId, columnId, value) {
10290
11665
  const updatedFormData = { ...this._formData };
10291
11666
  if (!updatedFormData[conceptId]) {
10292
11667
  updatedFormData[conceptId] = {};
@@ -10294,6 +11669,12 @@ let JupiterDynamicForm = class extends LitElement {
10294
11669
  const oldValue = updatedFormData[conceptId][columnId];
10295
11670
  updatedFormData[conceptId] = { ...updatedFormData[conceptId], [columnId]: value };
10296
11671
  this._formData = updatedFormData;
11672
+ return oldValue;
11673
+ }
11674
+ _handleFieldChange(event) {
11675
+ const { fieldId, conceptId, columnId, value } = event.detail;
11676
+ console.log(`📝 Field change: conceptId=${conceptId}, columnId=${columnId}, value=${value}, fieldId=${fieldId}`);
11677
+ const oldValue = this._writeFormDataValue(conceptId, columnId, value);
10297
11678
  console.log(`💾 Updated formData[${conceptId}]:`, this._formData[conceptId]);
10298
11679
  this._touched.add(`${conceptId}-${columnId}`);
10299
11680
  this._dirty = true;
@@ -11296,6 +12677,89 @@ let JupiterDynamicForm = class extends LitElement {
11296
12677
  this._submitDisabled = false;
11297
12678
  }, 1e3);
11298
12679
  }
12680
+ /**
12681
+ * Handler for the standalone "Quick Validate" button (visible only when `formulaEnabled` is
12682
+ * true). Runs the formula-assertion pre-flight in isolation from the original Validate/submit
12683
+ * button, which no longer triggers formula validation as a side effect.
12684
+ */
12685
+ _handleQuickValidate() {
12686
+ if (this.mode === "admin")
12687
+ return;
12688
+ this._runFormulaValidationPreflight();
12689
+ }
12690
+ /**
12691
+ * JDF-049: runs FormulaValidationService against the live form state and stores the results
12692
+ * for the results dialog (JDF-050). Graceful no-op — per JDF-048's gating and this ticket's
12693
+ * "no Validate-formulas affordance/effect when formula data is absent" requirement — when
12694
+ * `formulaEnabled` is off or `xbrlInput.formula` has no roles. Never blocks the caller; always
12695
+ * returns before any resolution work is attempted when gated off (JDF-048's "zero evaluation
12696
+ * cost when off" requirement).
12697
+ */
12698
+ _runFormulaValidationPreflight() {
12699
+ var _a, _b, _c, _d, _e, _f, _g, _h;
12700
+ if (!this.formulaEnabled)
12701
+ return;
12702
+ const formulaRoles = (_c = (_b = (_a = this.xbrlInput) == null ? void 0 : _a.formula) == null ? void 0 : _b[0]) == null ? void 0 : _c.roles;
12703
+ if (!formulaRoles || formulaRoles.length === 0)
12704
+ return;
12705
+ const scopeSections = ((_e = (_d = this._currentSchema) == null ? void 0 : _d.sections) == null ? void 0 : _e.length) ? this._currentSchema.sections : this._allSections;
12706
+ if (!scopeSections || scopeSections.length === 0)
12707
+ return;
12708
+ const visibleRoleURIs = new Set(scopeSections.map((section2) => {
12709
+ var _a2;
12710
+ return ((_a2 = section2.metadata) == null ? void 0 : _a2.roleURI) || section2.id;
12711
+ }));
12712
+ const scopedFormulaRoles = formulaRoles.filter((role) => visibleRoleURIs.has(role.roleURI));
12713
+ if (scopedFormulaRoles.length === 0)
12714
+ return;
12715
+ const conceptSections = scopeSections.map((section2) => {
12716
+ var _a2;
12717
+ return {
12718
+ roleURI: ((_a2 = section2.metadata) == null ? void 0 : _a2.roleURI) || section2.id,
12719
+ concepts: section2.concepts
12720
+ };
12721
+ });
12722
+ const ctx = buildFormulaResolutionContext(
12723
+ this._formData,
12724
+ this._mergeAllSectionColumns(),
12725
+ conceptSections,
12726
+ (_h = (_g = (_f = this.xbrlInput) == null ? void 0 : _f.hypercubes) == null ? void 0 : _g[0]) == null ? void 0 : _h.roles,
12727
+ this.periodStartDate,
12728
+ this.periodEndDate
12729
+ );
12730
+ const scopedXbrlInput = {
12731
+ ...this.xbrlInput,
12732
+ formula: [{ ...this.xbrlInput.formula[0], roles: scopedFormulaRoles }]
12733
+ };
12734
+ const results = this._formulaValidationService.evaluate(scopedXbrlInput, ctx, this.language);
12735
+ const summary = this._formulaValidationService.summarize(results);
12736
+ this._formulaValidationResults = results;
12737
+ this._formulaValidationSummary = summary;
12738
+ const hasFailures = results.some((result) => !result.skipped && !result.satisfied);
12739
+ if (hasFailures) {
12740
+ this._showFormulaValidationDialog = true;
12741
+ }
12742
+ this.dispatchEvent(new CustomEvent("formula-validation-complete", {
12743
+ detail: { results, summary },
12744
+ bubbles: true,
12745
+ composed: true
12746
+ }));
12747
+ }
12748
+ /**
12749
+ * Formula assertions can reference concepts/dimensions from any disclosure role, but
12750
+ * `FormColumn`s (especially dimension columns) are tracked per-section (`section.columns`),
12751
+ * with `this._columns` as the shared/default fallback (see the pattern at L1398/L2814/L5284).
12752
+ * `_formData` itself is already global (keyed by conceptId/columnId, no section nesting), so a
12753
+ * single merged, deduplicated-by-id column list is the correct match for it.
12754
+ */
12755
+ _mergeAllSectionColumns() {
12756
+ const merged = /* @__PURE__ */ new Map();
12757
+ this._columns.forEach((column2) => merged.set(column2.id, column2));
12758
+ this._allSections.forEach((section2) => {
12759
+ (section2.columns || []).forEach((column2) => merged.set(column2.id, column2));
12760
+ });
12761
+ return Array.from(merged.values());
12762
+ }
11299
12763
  _handleSaveDraft(source = "manual") {
11300
12764
  console.log(`🔵 [Save Draft] Checking for errors...`);
11301
12765
  console.log(`🔵 [Save Draft] _xbrlFormErrors.length: ${this._xbrlFormErrors.length}`);
@@ -11333,7 +12797,8 @@ let JupiterDynamicForm = class extends LitElement {
11333
12797
  this._repeatCounts,
11334
12798
  this._decimalsData,
11335
12799
  roleCompletedStates,
11336
- this._getHiddenColumnsForMetadata()
12800
+ this._getHiddenColumnsForMetadata(),
12801
+ this._totalManuallyEditedData
11337
12802
  );
11338
12803
  const draftPayloadSnapshot = JSON.stringify({
11339
12804
  draftData,
@@ -11475,6 +12940,10 @@ let JupiterDynamicForm = class extends LitElement {
11475
12940
  );
11476
12941
  console.log("🔄 Restored hidden columns for", this._hiddenColumnIds.size, "sections");
11477
12942
  }
12943
+ if (metadata.totalManuallyEditedData) {
12944
+ this._totalManuallyEditedData = metadata.totalManuallyEditedData;
12945
+ console.log("🔄 Restored total manually-edited data:", Object.keys(this._totalManuallyEditedData).length, "concepts");
12946
+ }
11478
12947
  if (metadata.periodPreferences) {
11479
12948
  if (this._skipPeriodPreferencesRestore) {
11480
12949
  console.log("⏭️ Skipping period preferences restoration - using new filter selections");
@@ -11554,6 +13023,7 @@ let JupiterDynamicForm = class extends LitElement {
11554
13023
  });
11555
13024
  this._formData = { ...this._formData, ...restoredFormData };
11556
13025
  console.log(`🔄 Restored ${Object.keys(restoredFormData).length} concepts with data`);
13026
+ this._seedLegacyManuallyEditedTotals(restoredFormData);
11557
13027
  this._initializeGlobalDecimalsFromRoundingData();
11558
13028
  this._periodData = {
11559
13029
  ...this._periodData,
@@ -11687,6 +13157,47 @@ let JupiterDynamicForm = class extends LitElement {
11687
13157
  });
11688
13158
  console.log(`✅ Field population complete: ${populatedCount} populated, ${notFoundCount} not found`);
11689
13159
  }
13160
+ /**
13161
+ * JDF-058: seed the manual-edit dirty flag for any total whose value predates this feature, so
13162
+ * upgrading never silently recomputes a customer's pre-existing, already-filed totals. Walks
13163
+ * `_allSections` (the full pool, including currently-hidden/filtered-out sections — a hidden
13164
+ * total's pre-existing value must stay protected the moment its section becomes visible again),
13165
+ * not just the currently-selected roles. Idempotent: only seeds a cell that has no explicit
13166
+ * `totalManuallyEditedData` entry already (i.e. `metadata.totalManuallyEditedData` didn't cover
13167
+ * it), so re-running on an already-seeded draft is a no-op. Does not touch `restoredFormData`.
13168
+ */
13169
+ _seedLegacyManuallyEditedTotals(restoredFormData) {
13170
+ const seeded = { ...this._totalManuallyEditedData };
13171
+ let changed = false;
13172
+ const walk = (concepts) => {
13173
+ var _a;
13174
+ for (const concept of concepts) {
13175
+ if (isTotalLabel$1(concept.preferredLabel)) {
13176
+ const values = restoredFormData[concept.id];
13177
+ if (values) {
13178
+ for (const columnId of Object.keys(values)) {
13179
+ const value = values[columnId];
13180
+ const isBlank = value === null || value === void 0 || value === "";
13181
+ if (!isBlank && ((_a = seeded[concept.id]) == null ? void 0 : _a[columnId]) === void 0) {
13182
+ seeded[concept.id] = { ...seeded[concept.id] || {}, [columnId]: true };
13183
+ changed = true;
13184
+ }
13185
+ }
13186
+ }
13187
+ }
13188
+ if (concept.children && concept.children.length > 0) {
13189
+ walk(concept.children);
13190
+ }
13191
+ }
13192
+ };
13193
+ for (const section2 of this._allSections) {
13194
+ walk(section2.concepts || []);
13195
+ }
13196
+ if (changed) {
13197
+ this._totalManuallyEditedData = seeded;
13198
+ console.log("🔒 [JDF-058] Seeded manual-edit protection for pre-existing total values in legacy draft");
13199
+ }
13200
+ }
11690
13201
  _restoreCustomColumns(customColumns) {
11691
13202
  const columnsBySection = /* @__PURE__ */ new Map();
11692
13203
  customColumns.forEach((col) => {
@@ -13136,6 +14647,7 @@ let JupiterDynamicForm = class extends LitElement {
13136
14647
  .periodStartDate="${this.periodStartDate}"
13137
14648
  .periodEndDate="${this.periodEndDate}"
13138
14649
  .calculationEnabled="${this.calculationEnabled}"
14650
+ .totalManuallyEditedData="${this._totalManuallyEditedData}"
13139
14651
  @field-change="${this._handleFieldChange}"
13140
14652
  @period-change="${this._handlePeriodChange}"
13141
14653
  @typed-member-change="${this._handleTypedMemberChange}"
@@ -13284,6 +14796,7 @@ let JupiterDynamicForm = class extends LitElement {
13284
14796
  .periodStartDate="${this.periodStartDate}"
13285
14797
  .periodEndDate="${this.periodEndDate}"
13286
14798
  .calculationEnabled="${this.calculationEnabled}"
14799
+ .totalManuallyEditedData="${this._totalManuallyEditedData}"
13287
14800
  @field-change="${this._handleFieldChange}"
13288
14801
  @typed-member-change="${this._handleTypedMemberChange}"
13289
14802
  @add-concept-repeat="${this._handleAddConceptRepeat}"
@@ -13382,9 +14895,9 @@ let JupiterDynamicForm = class extends LitElement {
13382
14895
  return dims.length === 1 && this._normalizeAxisId(col.dimensionData.axisId ?? "") === this._normalizeAxisId(dims[0].axis) && this._normalizeMemberId(col.dimensionData.memberId ?? "") === this._normalizeMemberId(dims[0].member);
13383
14896
  })) == null ? void 0 : _a.id;
13384
14897
  }
13385
- async scrollToConcept(conceptName, dimensions, match) {
14898
+ async scrollToConcept(conceptName, dimensions, match, columnId) {
13386
14899
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
13387
- console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} dims=${JSON.stringify(dimensions)}`);
14900
+ console.log(`[scrollToConcept] ▶ START conceptName=${conceptName} value=${match == null ? void 0 : match.value} dims=${JSON.stringify(dimensions)} columnId=${columnId}`);
13388
14901
  let targetSection = null;
13389
14902
  let targetConcept = null;
13390
14903
  const sectionsToSearch = [
@@ -13398,7 +14911,19 @@ let JupiterDynamicForm = class extends LitElement {
13398
14911
  for (const section2 of sectionsToSearch) {
13399
14912
  const found = this._findConceptByName(section2.concepts, conceptName);
13400
14913
  if (found) {
13401
- if (dimensions == null ? void 0 : dimensions.length) {
14914
+ if (columnId) {
14915
+ const cols = section2.columns ?? this._columns;
14916
+ const hasColumn = cols.some((c2) => c2.id === columnId);
14917
+ console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | columnId="${columnId}" present=${hasColumn}`);
14918
+ if (hasColumn) {
14919
+ targetSection = section2;
14920
+ targetConcept = found;
14921
+ break;
14922
+ } else if (!targetSection) {
14923
+ targetSection = section2;
14924
+ targetConcept = found;
14925
+ }
14926
+ } else if (dimensions == null ? void 0 : dimensions.length) {
13402
14927
  const cols = section2.columns ?? this._columns;
13403
14928
  const colId = this._findColumnByDimensions(cols, dimensions);
13404
14929
  console.log(`[scrollToConcept] Section "${section2.id}" has concept "${found.id}" | colMatch=${colId ?? "null"} | cols=${cols.map((c2) => c2.id).join(",")}`);
@@ -13431,7 +14956,9 @@ let JupiterDynamicForm = class extends LitElement {
13431
14956
  let targetColumnId = null;
13432
14957
  const hasValueMatch = (match == null ? void 0 : match.value) !== void 0 && (match == null ? void 0 : match.value) !== null;
13433
14958
  const targetValue = hasValueMatch ? String(match.value) : null;
13434
- if (dimensions == null ? void 0 : dimensions.length) {
14959
+ if (columnId) {
14960
+ targetColumnId = columnId;
14961
+ } else if (dimensions == null ? void 0 : dimensions.length) {
13435
14962
  targetColumnId = this._findColumnByDimensions(columns, dimensions) ?? null;
13436
14963
  } else if (!hasValueMatch) {
13437
14964
  targetColumnId = ((_b = columns[0]) == null ? void 0 : _b.id) ?? null;
@@ -13677,6 +15204,15 @@ let JupiterDynamicForm = class extends LitElement {
13677
15204
  </button>
13678
15205
  ` : ""}
13679
15206
 
15207
+ ${this.formulaEnabled && this.mode !== "admin" ? html`
15208
+ <button
15209
+ class="btn-secondary btn-quick-validate"
15210
+ @click="${this._handleQuickValidate}"
15211
+ ?disabled="${this.disabled || this.readonly || this._validationStatus === "inProgress"}"
15212
+ >
15213
+ ${I18n.t("form.quickValidate")}
15214
+ </button>
15215
+ ` : ""}
13680
15216
 
13681
15217
  <button
13682
15218
  class="btn-primary"
@@ -13706,7 +15242,19 @@ let JupiterDynamicForm = class extends LitElement {
13706
15242
  @click="${this._handleFilterDialogCancel}"
13707
15243
  ></jupiter-filter-roles-dialog>
13708
15244
  ` : ""}
13709
-
15245
+
15246
+ <!-- Formula Validation Results Dialog (JDF-050) -->
15247
+ ${this._showFormulaValidationDialog ? html`
15248
+ <jupiter-formula-validation-dialog
15249
+ ?open="${this._showFormulaValidationDialog}"
15250
+ .results="${this._formulaValidationResults}"
15251
+ .summary="${this._formulaValidationSummary}"
15252
+ @dialog-cancel="${this._handleFormulaValidationDialogCancel}"
15253
+ @formula-concept-click="${this._handleFormulaConceptClick}"
15254
+ @click="${this._handleFormulaValidationDialogCancel}"
15255
+ ></jupiter-formula-validation-dialog>
15256
+ ` : ""}
15257
+
13710
15258
  <!-- Validation Error Popup -->
13711
15259
  ${this._showErrorPopup ? html`
13712
15260
  <div class="error-popup-overlay" @click="${() => {
@@ -14623,6 +16171,9 @@ __decorateClass([
14623
16171
  __decorateClass([
14624
16172
  n2({ type: Boolean, attribute: "calculation-enabled" })
14625
16173
  ], JupiterDynamicForm.prototype, "calculationEnabled", 2);
16174
+ __decorateClass([
16175
+ n2({ type: Boolean, attribute: "formula-enabled" })
16176
+ ], JupiterDynamicForm.prototype, "formulaEnabled", 2);
14626
16177
  __decorateClass([
14627
16178
  n2({ type: String })
14628
16179
  ], JupiterDynamicForm.prototype, "periodStartDate", 2);
@@ -14695,6 +16246,9 @@ __decorateClass([
14695
16246
  __decorateClass([
14696
16247
  r()
14697
16248
  ], JupiterDynamicForm.prototype, "_decimalsData", 2);
16249
+ __decorateClass([
16250
+ r()
16251
+ ], JupiterDynamicForm.prototype, "_totalManuallyEditedData", 2);
14698
16252
  __decorateClass([
14699
16253
  r()
14700
16254
  ], JupiterDynamicForm.prototype, "_effectiveMasterData", 2);
@@ -14794,6 +16348,15 @@ __decorateClass([
14794
16348
  __decorateClass([
14795
16349
  r()
14796
16350
  ], JupiterDynamicForm.prototype, "_validationStatus", 2);
16351
+ __decorateClass([
16352
+ r()
16353
+ ], JupiterDynamicForm.prototype, "_formulaValidationResults", 2);
16354
+ __decorateClass([
16355
+ r()
16356
+ ], JupiterDynamicForm.prototype, "_formulaValidationSummary", 2);
16357
+ __decorateClass([
16358
+ r()
16359
+ ], JupiterDynamicForm.prototype, "_showFormulaValidationDialog", 2);
14797
16360
  JupiterDynamicForm = __decorateClass([
14798
16361
  t$1("jupiter-dynamic-form")
14799
16362
  ], JupiterDynamicForm);
@@ -14808,6 +16371,7 @@ export {
14808
16371
  JupiterFilterRolesDialog,
14809
16372
  JupiterFormField,
14810
16373
  JupiterFormSection,
16374
+ JupiterFormulaValidationDialog,
14811
16375
  TYPE_INPUT_MAP,
14812
16376
  XBRLValidator,
14813
16377
  collectEnumerationsFromChain,