jupiter-dynamic-forms 1.20.1 → 1.20.3

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 +42 -0
  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 +50 -1
  6. package/dist/core/form-section.d.ts.map +1 -1
  7. package/dist/core/formula-validation-dialog.d.ts +30 -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 +351 -75
  12. package/dist/index.js.map +1 -1
  13. package/dist/index.mjs +1784 -306
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/schema/types.d.ts +26 -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 +45 -0
  26. package/dist/utils/formula-resolution-context.d.ts.map +1 -0
  27. package/dist/utils/formula-variable-resolver.d.ts +30 -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
@@ -1817,6 +1817,15 @@ const calculationWarning$1 = {
1817
1817
  added: "Added",
1818
1818
  subtracted: "Subtracted (opposite balance)"
1819
1819
  };
1820
+ const formulaValidation$1 = {
1821
+ dialogTitle: "Formula check results",
1822
+ allPassed: "All formula checks passed",
1823
+ summaryCount: "{{failed}} of {{total}} checks failed",
1824
+ summaryCountBreakdown: "{{warnings}} warnings, {{errors}} errors",
1825
+ severityWarning: "Warning",
1826
+ severityError: "Error",
1827
+ close: "Close"
1828
+ };
1820
1829
  const enTranslations = {
1821
1830
  conceptInfo: conceptInfo$1,
1822
1831
  form: form$1,
@@ -1828,7 +1837,8 @@ const enTranslations = {
1828
1837
  validation: validation$1,
1829
1838
  xbrlValidation: xbrlValidation$1,
1830
1839
  error: error$1,
1831
- calculationWarning: calculationWarning$1
1840
+ calculationWarning: calculationWarning$1,
1841
+ formulaValidation: formulaValidation$1
1832
1842
  };
1833
1843
  const conceptInfo = {
1834
1844
  title: "Conceptinformatie",
@@ -2012,6 +2022,15 @@ const calculationWarning = {
2012
2022
  added: "Opgeteld",
2013
2023
  subtracted: "Afgetrokken (tegengestelde balans)"
2014
2024
  };
2025
+ const formulaValidation = {
2026
+ dialogTitle: "Resultaten formulecontrole",
2027
+ allPassed: "Alle formulecontroles geslaagd",
2028
+ summaryCount: "{{failed}} van {{total}} controles mislukt",
2029
+ summaryCountBreakdown: "{{warnings}} waarschuwingen, {{errors}} fouten",
2030
+ severityWarning: "Waarschuwing",
2031
+ severityError: "Fout",
2032
+ close: "Sluiten"
2033
+ };
2015
2034
  const nlTranslations = {
2016
2035
  conceptInfo,
2017
2036
  form,
@@ -2023,7 +2042,8 @@ const nlTranslations = {
2023
2042
  validation,
2024
2043
  xbrlValidation,
2025
2044
  error,
2026
- calculationWarning
2045
+ calculationWarning,
2046
+ formulaValidation
2027
2047
  };
2028
2048
  const translations = {
2029
2049
  en: enTranslations,
@@ -2102,6 +2122,9 @@ class I18n {
2102
2122
  }
2103
2123
  }
2104
2124
  I18n.currentLanguage = "en";
2125
+ function isTotalLabel$1(preferredLabel) {
2126
+ return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("totallabel"));
2127
+ }
2105
2128
  class DraftStorageService {
2106
2129
  constructor(useSessionStorage = false) {
2107
2130
  this.DRAFT_DATA_KEY = "jupiter-form-draft-data";
@@ -2261,7 +2284,7 @@ class DraftStorageService {
2261
2284
  /**
2262
2285
  * Create metadata snapshot from current form state
2263
2286
  */
2264
- createMetadataSnapshot(periodStartDate, periodEndDate, language, selectedRoleIds, allSections, typedMemberData, periodPreferences, periodData, unitData, reportingLanguage = "en", repeatCounts, decimalsData, roleCompletedStates, hiddenColumns) {
2287
+ createMetadataSnapshot(periodStartDate, periodEndDate, language, selectedRoleIds, allSections, typedMemberData, periodPreferences, periodData, unitData, reportingLanguage = "en", repeatCounts, decimalsData, roleCompletedStates, hiddenColumns, totalManuallyEditedData) {
2265
2288
  return {
2266
2289
  periodStartDate,
2267
2290
  periodEndDate,
@@ -2278,6 +2301,7 @@ class DraftStorageService {
2278
2301
  repeatCounts,
2279
2302
  roleCompletedStates,
2280
2303
  hiddenColumns,
2304
+ totalManuallyEditedData,
2281
2305
  schemaVersion: this.STORAGE_VERSION
2282
2306
  };
2283
2307
  }
@@ -2300,6 +2324,801 @@ class DraftStorageService {
2300
2324
  return { compatible, warnings };
2301
2325
  }
2302
2326
  }
2327
+ const CALCULATION_TOLERANCE = 0.01;
2328
+ class FormulaExpressionError extends Error {
2329
+ constructor(message, test, reason = "unsupported-syntax") {
2330
+ super(message);
2331
+ this.test = test;
2332
+ this.reason = reason;
2333
+ this.name = "FormulaExpressionError";
2334
+ }
2335
+ }
2336
+ const TOKEN_PATTERN = /\s*(?:(\$[A-Za-z_][\w-]*)|(-?\d+(?:\.\d+)?)|([A-Za-z][\w:-]*)|('[^']*')|(\()|(\))|(\+)|(-)|(=))/y;
2337
+ function tokenize(test) {
2338
+ const tokens = [];
2339
+ TOKEN_PATTERN.lastIndex = 0;
2340
+ let index = 0;
2341
+ while (index < test.length) {
2342
+ TOKEN_PATTERN.lastIndex = index;
2343
+ const match = TOKEN_PATTERN.exec(test);
2344
+ if (!match || match.index !== index) {
2345
+ if (/^\s*$/.test(test.slice(index)))
2346
+ break;
2347
+ throw new FormulaExpressionError(`Unrecognized token at position ${index}: "${test.slice(index, index + 10)}..."`, test, "unsupported-syntax");
2348
+ }
2349
+ const [full, variable, number, ident, string, lparen, rparen, plus, minus, equals] = match;
2350
+ if (variable)
2351
+ tokens.push({ type: "VARIABLE", value: variable.slice(1) });
2352
+ else if (number)
2353
+ tokens.push({ type: "NUMBER", value: number });
2354
+ else if (ident)
2355
+ tokens.push({ type: "IDENT", value: ident });
2356
+ else if (string)
2357
+ tokens.push({ type: "STRING", value: string.slice(1, -1) });
2358
+ else if (lparen)
2359
+ tokens.push({ type: "LPAREN", value: lparen });
2360
+ else if (rparen)
2361
+ tokens.push({ type: "RPAREN", value: rparen });
2362
+ else if (plus)
2363
+ tokens.push({ type: "PLUS", value: plus });
2364
+ else if (minus)
2365
+ tokens.push({ type: "MINUS", value: minus });
2366
+ else if (equals)
2367
+ tokens.push({ type: "EQUALS", value: equals });
2368
+ index += full.length;
2369
+ }
2370
+ tokens.push({ type: "EOF", value: "" });
2371
+ return tokens;
2372
+ }
2373
+ class Parser {
2374
+ constructor(tokens, test) {
2375
+ this.tokens = tokens;
2376
+ this.test = test;
2377
+ this.pos = 0;
2378
+ }
2379
+ peek() {
2380
+ return this.tokens[this.pos];
2381
+ }
2382
+ next() {
2383
+ return this.tokens[this.pos++];
2384
+ }
2385
+ expect(type) {
2386
+ const token = this.next();
2387
+ if (token.type !== type) {
2388
+ throw new FormulaExpressionError(
2389
+ `Expected ${type} but got ${token.type} ("${token.value}")`,
2390
+ this.test,
2391
+ "parse-error"
2392
+ );
2393
+ }
2394
+ return token;
2395
+ }
2396
+ parseBoolExpr() {
2397
+ const node = this.parseOrExpr();
2398
+ if (this.peek().type !== "EOF") {
2399
+ throw new FormulaExpressionError(
2400
+ `Unexpected trailing token "${this.peek().value}"`,
2401
+ this.test,
2402
+ "unsupported-syntax"
2403
+ );
2404
+ }
2405
+ return node;
2406
+ }
2407
+ parseOrExpr() {
2408
+ const terms = [this.parseAndExpr()];
2409
+ while (this.peek().type === "IDENT" && this.peek().value === "or") {
2410
+ this.next();
2411
+ terms.push(this.parseAndExpr());
2412
+ }
2413
+ return terms.length === 1 ? terms[0] : { kind: "or", terms };
2414
+ }
2415
+ parseAndExpr() {
2416
+ const terms = [this.parseBoolTerm()];
2417
+ while (this.peek().type === "IDENT" && this.peek().value === "and") {
2418
+ this.next();
2419
+ terms.push(this.parseBoolTerm());
2420
+ }
2421
+ return terms.length === 1 ? terms[0] : { kind: "and", terms };
2422
+ }
2423
+ parseBoolTerm() {
2424
+ const token = this.peek();
2425
+ if (token.type === "IDENT" && token.value === "not") {
2426
+ this.next();
2427
+ this.expect("LPAREN");
2428
+ const arg = this.parseOrExpr();
2429
+ this.expect("RPAREN");
2430
+ return { kind: "not", arg };
2431
+ }
2432
+ if (token.type === "IDENT" && token.value === "empty") {
2433
+ this.next();
2434
+ this.expect("LPAREN");
2435
+ const variable = this.expect("VARIABLE").value;
2436
+ this.expect("RPAREN");
2437
+ return { kind: "empty", variable };
2438
+ }
2439
+ if (token.type === "IDENT" && token.value === "xff:has-fallback-value") {
2440
+ this.next();
2441
+ this.expect("LPAREN");
2442
+ const inner = this.next();
2443
+ if (inner.type !== "IDENT" || inner.value !== "xs:QName") {
2444
+ throw new FormulaExpressionError(
2445
+ `Expected xs:QName(...) inside xff:has-fallback-value(...), got "${inner.value}"`,
2446
+ this.test,
2447
+ "unsupported-syntax"
2448
+ );
2449
+ }
2450
+ this.expect("LPAREN");
2451
+ const variableName = this.expect("STRING").value;
2452
+ this.expect("RPAREN");
2453
+ this.expect("RPAREN");
2454
+ return { kind: "hasFallbackValue", variableName };
2455
+ }
2456
+ const left = this.parseValueExpr();
2457
+ const opToken = this.peek();
2458
+ if (opToken.type === "EQUALS") {
2459
+ this.next();
2460
+ return { kind: "comparison", op: "=", left, right: this.parseValueExpr() };
2461
+ }
2462
+ if (opToken.type === "IDENT" && opToken.value === "eq") {
2463
+ this.next();
2464
+ return { kind: "comparison", op: "eq", left, right: this.parseValueExpr() };
2465
+ }
2466
+ throw new FormulaExpressionError(
2467
+ `Expected a comparison operator ("=" or "eq") after value expression, got "${opToken.value}"`,
2468
+ this.test,
2469
+ "unsupported-syntax"
2470
+ );
2471
+ }
2472
+ parseValueExpr() {
2473
+ const terms = [];
2474
+ let sign = 1;
2475
+ if (this.peek().type === "PLUS") {
2476
+ this.next();
2477
+ } else if (this.peek().type === "MINUS") {
2478
+ this.next();
2479
+ sign = -1;
2480
+ }
2481
+ terms.push({ sign, term: this.parseValueTerm() });
2482
+ while (this.peek().type === "PLUS" || this.peek().type === "MINUS") {
2483
+ const opSign = this.next().type === "PLUS" ? 1 : -1;
2484
+ terms.push({ sign: opSign, term: this.parseValueTerm() });
2485
+ }
2486
+ return terms.length === 1 && terms[0].sign === 1 ? terms[0].term : { kind: "additive", terms };
2487
+ }
2488
+ parseValueTerm() {
2489
+ const token = this.peek();
2490
+ if (token.type === "NUMBER") {
2491
+ this.next();
2492
+ return { kind: "number", value: Number(token.value) };
2493
+ }
2494
+ if (token.type === "VARIABLE") {
2495
+ this.next();
2496
+ return { kind: "variable", name: token.value };
2497
+ }
2498
+ if (token.type === "IDENT" && token.value === "sum") {
2499
+ this.next();
2500
+ this.expect("LPAREN");
2501
+ const variable = this.expect("VARIABLE").value;
2502
+ this.expect("RPAREN");
2503
+ return { kind: "sum", variable };
2504
+ }
2505
+ if (token.type === "LPAREN") {
2506
+ this.next();
2507
+ const inner = this.parseValueExpr();
2508
+ this.expect("RPAREN");
2509
+ return inner;
2510
+ }
2511
+ throw new FormulaExpressionError(
2512
+ `Expected a number, variable, sum(...), or parenthesized expression, got "${token.value}"`,
2513
+ this.test,
2514
+ "unsupported-syntax"
2515
+ );
2516
+ }
2517
+ }
2518
+ function lookup(bindings, name) {
2519
+ return bindings.get(name);
2520
+ }
2521
+ function evalSum(variable, bindings) {
2522
+ const binding = lookup(bindings, variable);
2523
+ if (binding === void 0)
2524
+ return 0;
2525
+ if (Array.isArray(binding))
2526
+ return binding.reduce((total, value) => total + value, 0);
2527
+ return binding;
2528
+ }
2529
+ function evalValue(node, bindings, test) {
2530
+ switch (node.kind) {
2531
+ case "number":
2532
+ return node.value;
2533
+ case "sum":
2534
+ return evalSum(node.variable, bindings);
2535
+ case "variable": {
2536
+ const binding = lookup(bindings, node.name);
2537
+ if (binding === void 0) {
2538
+ throw new FormulaExpressionError(
2539
+ `Variable "$${node.name}" has no bound fact and cannot be evaluated directly (only sum()/empty() tolerate an unbound variable)`,
2540
+ test,
2541
+ "unbound-variable"
2542
+ );
2543
+ }
2544
+ if (Array.isArray(binding)) {
2545
+ throw new FormulaExpressionError(
2546
+ `Variable "$${node.name}" is a sequence and must be wrapped in sum(...) to be used in arithmetic`,
2547
+ test,
2548
+ "unsupported-syntax"
2549
+ );
2550
+ }
2551
+ return binding;
2552
+ }
2553
+ case "additive":
2554
+ return node.terms.reduce((total, { sign, term }) => total + sign * evalValue(term, bindings, test), 0);
2555
+ }
2556
+ }
2557
+ function evalBool(node, bindings, test, fallbackUsage) {
2558
+ switch (node.kind) {
2559
+ case "or":
2560
+ return node.terms.some((term) => evalBool(term, bindings, test, fallbackUsage));
2561
+ case "and":
2562
+ return node.terms.every((term) => evalBool(term, bindings, test, fallbackUsage));
2563
+ case "not":
2564
+ return !evalBool(node.arg, bindings, test, fallbackUsage);
2565
+ case "empty": {
2566
+ const binding = lookup(bindings, node.variable);
2567
+ return binding === void 0 || Array.isArray(binding) && binding.length === 0;
2568
+ }
2569
+ case "hasFallbackValue": {
2570
+ const usedFallback = fallbackUsage.get(node.variableName);
2571
+ if (usedFallback === void 0) {
2572
+ throw new FormulaExpressionError(
2573
+ `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)`,
2574
+ test,
2575
+ "unbound-variable"
2576
+ );
2577
+ }
2578
+ return usedFallback;
2579
+ }
2580
+ case "comparison": {
2581
+ const left = evalValue(node.left, bindings, test);
2582
+ const right = evalValue(node.right, bindings, test);
2583
+ return Math.abs(left - right) < CALCULATION_TOLERANCE;
2584
+ }
2585
+ }
2586
+ }
2587
+ function evaluateFormulaTest(test, bindings, fallbackUsage = /* @__PURE__ */ new Map()) {
2588
+ const tokens = tokenize(test);
2589
+ const ast = new Parser(tokens, test).parseBoolExpr();
2590
+ return evalBool(ast, bindings, test, fallbackUsage);
2591
+ }
2592
+ const EXISTENCE_TEST_PATTERN = /^\.\s*(eq|ne|gt|lt|ge|le)\s+(-?\d+(?:\.\d+)?)$/;
2593
+ function compareExistence(op, count, threshold) {
2594
+ switch (op) {
2595
+ case "eq":
2596
+ return count === threshold;
2597
+ case "ne":
2598
+ return count !== threshold;
2599
+ case "gt":
2600
+ return count > threshold;
2601
+ case "lt":
2602
+ return count < threshold;
2603
+ case "ge":
2604
+ return count >= threshold;
2605
+ case "le":
2606
+ return count <= threshold;
2607
+ }
2608
+ }
2609
+ function evaluateExistenceTest(test, matchedFactCount) {
2610
+ const trimmed = (test ?? "").trim();
2611
+ if (trimmed === "") {
2612
+ return matchedFactCount > 0;
2613
+ }
2614
+ const clauses = trimmed.split(/\bor\b/);
2615
+ return clauses.some((clause) => {
2616
+ const match = EXISTENCE_TEST_PATTERN.exec(clause.trim());
2617
+ if (!match) {
2618
+ throw new FormulaExpressionError(`Unsupported existence-assertion test shape: "${clause.trim()}"`, trimmed, "unsupported-syntax");
2619
+ }
2620
+ const [, op, thresholdText] = match;
2621
+ return compareExistence(op, matchedFactCount, Number(thresholdText));
2622
+ });
2623
+ }
2624
+ function isTotalLabel(preferredLabel) {
2625
+ return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("totallabel"));
2626
+ }
2627
+ function isNegatedTotalLabel(preferredLabel) {
2628
+ return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("negatedtotallabel"));
2629
+ }
2630
+ function resolveValueNode(node, accessors, depth = 0) {
2631
+ if (!accessors.isAbstract(node))
2632
+ return node;
2633
+ if (depth > 5)
2634
+ return null;
2635
+ const children = accessors.getChildren(node);
2636
+ const nestedTotal = children.find((child) => isTotalLabel(accessors.getPreferredLabel(child)));
2637
+ if (!nestedTotal)
2638
+ return null;
2639
+ return resolveValueNode(nestedTotal, accessors, depth + 1);
2640
+ }
2641
+ function resolveTotalGroupMembers(nodes, i2, lastTotalIdx, rawSiblings, accessors) {
2642
+ if (lastTotalIdx < 0)
2643
+ return rawSiblings;
2644
+ const node = nodes[i2];
2645
+ if (isNegatedTotalLabel(accessors.getPreferredLabel(node))) {
2646
+ return rawSiblings.length > 0 ? rawSiblings : [nodes[lastTotalIdx]];
2647
+ }
2648
+ if (rawSiblings.length > 0) {
2649
+ return [nodes[lastTotalIdx], ...rawSiblings];
2650
+ }
2651
+ if (isNegatedTotalLabel(accessors.getPreferredLabel(nodes[lastTotalIdx]))) {
2652
+ let grandTotalIdx = -1;
2653
+ for (let k = lastTotalIdx - 1; k >= 0; k--) {
2654
+ if (isTotalLabel(accessors.getPreferredLabel(nodes[k]))) {
2655
+ grandTotalIdx = k;
2656
+ break;
2657
+ }
2658
+ }
2659
+ return grandTotalIdx >= 0 ? [nodes[grandTotalIdx], nodes[lastTotalIdx]] : [nodes[lastTotalIdx]];
2660
+ }
2661
+ return [nodes[lastTotalIdx]];
2662
+ }
2663
+ function buildTotalGroupChildrenIndex(roots, accessors) {
2664
+ const index = /* @__PURE__ */ new Map();
2665
+ function registerGroup(totalNode, members) {
2666
+ const resolvedIds = [];
2667
+ for (const member of members) {
2668
+ const resolved = resolveValueNode(member, accessors);
2669
+ if (resolved)
2670
+ resolvedIds.push(accessors.getId(resolved));
2671
+ }
2672
+ if (resolvedIds.length === 0)
2673
+ return;
2674
+ index.set(accessors.getId(totalNode), resolvedIds);
2675
+ }
2676
+ function traverse(nodes) {
2677
+ for (let i2 = 0; i2 < nodes.length; i2++) {
2678
+ const node = nodes[i2];
2679
+ if (isTotalLabel(accessors.getPreferredLabel(node))) {
2680
+ const children2 = accessors.getChildren(node);
2681
+ if (children2.length === 0) {
2682
+ let lastTotalIdx = -1;
2683
+ for (let j = i2 - 1; j >= 0; j--) {
2684
+ if (isTotalLabel(accessors.getPreferredLabel(nodes[j]))) {
2685
+ lastTotalIdx = j;
2686
+ break;
2687
+ }
2688
+ }
2689
+ const rawSiblings = nodes.slice(lastTotalIdx + 1, i2);
2690
+ const members = resolveTotalGroupMembers(nodes, i2, lastTotalIdx, rawSiblings, accessors);
2691
+ if (members.length > 0)
2692
+ registerGroup(node, members);
2693
+ }
2694
+ }
2695
+ const children = accessors.getChildren(node);
2696
+ if (children.length > 0)
2697
+ traverse(children);
2698
+ }
2699
+ }
2700
+ traverse(roots);
2701
+ return index;
2702
+ }
2703
+ function isNonEmptyRawValue(rawValue) {
2704
+ return rawValue !== void 0 && rawValue !== null && rawValue !== "";
2705
+ }
2706
+ function collectAllCandidates(formData) {
2707
+ const candidates = [];
2708
+ for (const conceptId of Object.keys(formData)) {
2709
+ const columnValues = formData[conceptId] || {};
2710
+ for (const columnId of Object.keys(columnValues)) {
2711
+ if (isNonEmptyRawValue(columnValues[columnId])) {
2712
+ candidates.push({ conceptId, columnId });
2713
+ }
2714
+ }
2715
+ }
2716
+ return candidates;
2717
+ }
2718
+ function indexConceptTree(concept, conceptByQName, conceptById, childrenIndex) {
2719
+ const info = {
2720
+ conceptId: concept.id,
2721
+ qname: concept.conceptName,
2722
+ balance: concept.balance
2723
+ };
2724
+ if (!conceptById.has(concept.id))
2725
+ conceptById.set(concept.id, info);
2726
+ if (!conceptByQName.has(concept.conceptName))
2727
+ conceptByQName.set(concept.conceptName, info);
2728
+ const children = concept.children || [];
2729
+ childrenIndex.set(
2730
+ concept.id,
2731
+ children.map((child) => child.id)
2732
+ );
2733
+ children.forEach((child) => indexConceptTree(child, conceptByQName, conceptById, childrenIndex));
2734
+ }
2735
+ function indexMemberTree(member, memberQNameToId) {
2736
+ if (!memberQNameToId.has(member.conceptName))
2737
+ memberQNameToId.set(member.conceptName, member.id);
2738
+ (member.children || []).forEach((child) => indexMemberTree(child, memberQNameToId));
2739
+ }
2740
+ const PRESENTATION_TOTAL_GROUP_ACCESSORS = {
2741
+ getId: (concept) => concept.id,
2742
+ getPreferredLabel: (concept) => concept.preferredLabel,
2743
+ isAbstract: (concept) => concept.elementAbstract,
2744
+ getChildren: (concept) => concept.children || []
2745
+ };
2746
+ function buildFormulaResolutionContext(formData, columns, presentationRoles, hypercubeRoles, periodStartDate, periodEndDate) {
2747
+ const conceptByQName = /* @__PURE__ */ new Map();
2748
+ const conceptById = /* @__PURE__ */ new Map();
2749
+ const childrenByRole = /* @__PURE__ */ new Map();
2750
+ const totalGroupChildrenByRole = /* @__PURE__ */ new Map();
2751
+ presentationRoles.forEach((role) => {
2752
+ var _a;
2753
+ const concepts = ((_a = role.presentationLinkbase) == null ? void 0 : _a.concepts) || [];
2754
+ const childrenIndex = /* @__PURE__ */ new Map();
2755
+ concepts.forEach((concept) => indexConceptTree(concept, conceptByQName, conceptById, childrenIndex));
2756
+ childrenByRole.set(role.roleURI, childrenIndex);
2757
+ totalGroupChildrenByRole.set(role.roleURI, buildTotalGroupChildrenIndex(concepts, PRESENTATION_TOTAL_GROUP_ACCESSORS));
2758
+ });
2759
+ const dimensionQNameToId = /* @__PURE__ */ new Map();
2760
+ const memberQNameToId = /* @__PURE__ */ new Map();
2761
+ (hypercubeRoles || []).forEach((role) => {
2762
+ role.items.forEach((item) => {
2763
+ item.dimensions.forEach((dimension) => {
2764
+ if (!dimensionQNameToId.has(dimension.conceptName)) {
2765
+ dimensionQNameToId.set(dimension.conceptName, dimension.id);
2766
+ }
2767
+ (dimension.members || []).forEach((member) => indexMemberTree(member, memberQNameToId));
2768
+ });
2769
+ });
2770
+ });
2771
+ return {
2772
+ formData,
2773
+ columns,
2774
+ periodStartDate,
2775
+ periodEndDate,
2776
+ conceptByQName,
2777
+ conceptById,
2778
+ childrenByRole,
2779
+ totalGroupChildrenByRole,
2780
+ dimensionQNameToId,
2781
+ memberQNameToId,
2782
+ allCandidates: collectAllCandidates(formData)
2783
+ };
2784
+ }
2785
+ function collectDescendantConceptIds(childrenIndex, rootConceptId) {
2786
+ const result = [];
2787
+ const stack = [...childrenIndex.get(rootConceptId) || []];
2788
+ const seen = /* @__PURE__ */ new Set();
2789
+ while (stack.length > 0) {
2790
+ const conceptId = stack.pop();
2791
+ if (seen.has(conceptId))
2792
+ continue;
2793
+ seen.add(conceptId);
2794
+ result.push(conceptId);
2795
+ stack.push(...childrenIndex.get(conceptId) || []);
2796
+ }
2797
+ return result;
2798
+ }
2799
+ function candidateKey(candidate) {
2800
+ return `${candidate.conceptId}::${candidate.columnId}`;
2801
+ }
2802
+ function resolveMatchedCandidates(variable, ctx) {
2803
+ return intersectCandidates(variable.filters.map((filter2) => resolveFilter(filter2, ctx.allCandidates, ctx)));
2804
+ }
2805
+ function getColumn(ctx, columnId) {
2806
+ return ctx.columns.find((column2) => column2.id === columnId);
2807
+ }
2808
+ function columnMatchesExplicitDimension(column2, axisId, memberId) {
2809
+ if (!(column2 == null ? void 0 : column2.dimensionData))
2810
+ return false;
2811
+ const primaryMatch = column2.dimensionData.axisId === axisId && (memberId === void 0 || column2.dimensionData.memberId === memberId);
2812
+ if (primaryMatch)
2813
+ return true;
2814
+ return (column2.dimensionData.combinations || []).some(
2815
+ (combo) => combo.axisId === axisId && (memberId === void 0 || combo.memberId === memberId)
2816
+ );
2817
+ }
2818
+ function columnMatchesTypedDimension(column2, axisId) {
2819
+ if (!(column2 == null ? void 0 : column2.dimensionData))
2820
+ return false;
2821
+ if (column2.dimensionData.typedMemberId !== void 0 && column2.dimensionData.axisId === axisId)
2822
+ return true;
2823
+ return (column2.dimensionData.typedMembers || []).some((typedMember) => typedMember.axisId === axisId);
2824
+ }
2825
+ function paramTokenName(rawDate) {
2826
+ if (!rawDate)
2827
+ return void 0;
2828
+ return rawDate.startsWith("$") ? rawDate.slice(1) : rawDate;
2829
+ }
2830
+ function resolveParamDate(paramToken, ctx) {
2831
+ const isStart = paramToken.includes("StartDate");
2832
+ const isEnd = paramToken.includes("EndDate") || paramToken === "FinancialReportingPeriodCurrentEndDateParam";
2833
+ if (paramToken.startsWith("FinancialReportingPeriodCurrent")) {
2834
+ return isStart ? { start: ctx.periodStartDate } : isEnd ? { end: ctx.periodEndDate } : void 0;
2835
+ }
2836
+ if (paramToken.startsWith("FinancialReportingPeriodPrePrevious") || paramToken.startsWith("FinancialReportingPeriodPrevious")) {
2837
+ const previousYearColumn = findPreviousYearColumn(ctx);
2838
+ if (!previousYearColumn)
2839
+ return void 0;
2840
+ return isStart ? { start: previousYearColumn.periodStartDate } : isEnd ? { end: previousYearColumn.periodEndDate } : void 0;
2841
+ }
2842
+ return void 0;
2843
+ }
2844
+ function findPreviousYearColumn(ctx) {
2845
+ return ctx.columns.find((column2) => column2.periodEndDate !== void 0 && column2.periodEndDate < ctx.periodEndDate);
2846
+ }
2847
+ function columnEffectivePeriod(column2, ctx) {
2848
+ return {
2849
+ start: (column2 == null ? void 0 : column2.periodStartDate) ?? ctx.periodStartDate,
2850
+ end: (column2 == null ? void 0 : column2.periodEndDate) ?? ctx.periodEndDate
2851
+ };
2852
+ }
2853
+ function columnMatchesPeriod(column2, dateAttribute, ctx) {
2854
+ const paramToken = paramTokenName(dateAttribute);
2855
+ if (!paramToken)
2856
+ return false;
2857
+ const resolved = resolveParamDate(paramToken, ctx);
2858
+ if (!resolved)
2859
+ return false;
2860
+ const period = columnEffectivePeriod(column2, ctx);
2861
+ const targetDate = resolved.end ?? resolved.start;
2862
+ return period.end === targetDate;
2863
+ }
2864
+ function unionCandidates(lists) {
2865
+ const merged = /* @__PURE__ */ new Map();
2866
+ lists.forEach((list) => list.forEach((candidate) => merged.set(candidateKey(candidate), candidate)));
2867
+ return Array.from(merged.values());
2868
+ }
2869
+ function matchFilter(filter2, universe, ctx) {
2870
+ var _a;
2871
+ switch (filter2.type) {
2872
+ case "CONCEPT_NAME": {
2873
+ const qname = filter2.attributes["concept.qname"];
2874
+ const info = qname ? ctx.conceptByQName.get(qname) : void 0;
2875
+ if (!info)
2876
+ return [];
2877
+ return universe.filter((candidate) => candidate.conceptId === info.conceptId);
2878
+ }
2879
+ case "CONCEPT_BALANCE": {
2880
+ const balance = filter2.attributes["balance"];
2881
+ return universe.filter((candidate) => {
2882
+ var _a2;
2883
+ return ((_a2 = ctx.conceptById.get(candidate.conceptId)) == null ? void 0 : _a2.balance) === balance;
2884
+ });
2885
+ }
2886
+ case "CONCEPT_RELATION": {
2887
+ const anchorQName = filter2.attributes["qname"];
2888
+ const linkrole = filter2.attributes["linkrole"];
2889
+ const axis = filter2.attributes["axis"];
2890
+ const anchorInfo = anchorQName ? ctx.conceptByQName.get(anchorQName) : void 0;
2891
+ const childrenIndex = linkrole ? ctx.childrenByRole.get(linkrole) : void 0;
2892
+ if (!anchorInfo || !childrenIndex)
2893
+ return [];
2894
+ let relatedConceptIds;
2895
+ if (axis === "descendant") {
2896
+ relatedConceptIds = collectDescendantConceptIds(childrenIndex, anchorInfo.conceptId);
2897
+ } else {
2898
+ const nestedChildren = childrenIndex.get(anchorInfo.conceptId) || [];
2899
+ relatedConceptIds = nestedChildren.length > 0 ? nestedChildren : ((_a = ctx.totalGroupChildrenByRole.get(linkrole ?? "")) == null ? void 0 : _a.get(anchorInfo.conceptId)) || [];
2900
+ }
2901
+ const relatedSet = new Set(relatedConceptIds);
2902
+ return universe.filter((candidate) => relatedSet.has(candidate.conceptId));
2903
+ }
2904
+ case "EXPLICIT_DIMENSION": {
2905
+ const dimensionQName = filter2.attributes["dimension.qname"];
2906
+ const memberQName = filter2.attributes["member.qname"];
2907
+ const axisId = dimensionQName ? ctx.dimensionQNameToId.get(dimensionQName) : void 0;
2908
+ const memberId = memberQName ? ctx.memberQNameToId.get(memberQName) : void 0;
2909
+ if (!axisId)
2910
+ return [];
2911
+ return universe.filter((candidate) => columnMatchesExplicitDimension(getColumn(ctx, candidate.columnId), axisId, memberId));
2912
+ }
2913
+ case "TYPED_DIMENSION": {
2914
+ const dimensionQName = filter2.attributes["dimension.qname"];
2915
+ const axisId = dimensionQName ? ctx.dimensionQNameToId.get(dimensionQName) : void 0;
2916
+ if (!axisId)
2917
+ return [];
2918
+ return universe.filter((candidate) => columnMatchesTypedDimension(getColumn(ctx, candidate.columnId), axisId));
2919
+ }
2920
+ case "PERIOD_INSTANT":
2921
+ case "PERIOD_END": {
2922
+ const dateAttribute = filter2.attributes["date"];
2923
+ return universe.filter((candidate) => columnMatchesPeriod(getColumn(ctx, candidate.columnId), dateAttribute, ctx));
2924
+ }
2925
+ case "OR_FILTER":
2926
+ return unionCandidates(filter2.children.map((child) => resolveFilter(child, universe, ctx)));
2927
+ default:
2928
+ return [];
2929
+ }
2930
+ }
2931
+ function resolveFilter(filter2, universe, ctx) {
2932
+ const matched = matchFilter(filter2, universe, ctx);
2933
+ if (!filter2.complement)
2934
+ return matched;
2935
+ const matchedKeys = new Set(matched.map(candidateKey));
2936
+ return universe.filter((candidate) => !matchedKeys.has(candidateKey(candidate)));
2937
+ }
2938
+ function intersectCandidates(sets) {
2939
+ if (sets.length === 0)
2940
+ return [];
2941
+ return sets.reduce((acc, set) => {
2942
+ const setKeys = new Set(set.map(candidateKey));
2943
+ return acc.filter((candidate) => setKeys.has(candidateKey(candidate)));
2944
+ });
2945
+ }
2946
+ function resolveFormulaVariable(variable, ctx) {
2947
+ const matched = resolveMatchedCandidates(variable, ctx);
2948
+ const values = matched.map((candidate) => {
2949
+ var _a;
2950
+ return parseFloat((_a = ctx.formData[candidate.conceptId]) == null ? void 0 : _a[candidate.columnId]);
2951
+ }).filter((value) => !Number.isNaN(value));
2952
+ if (variable.bindAsSequence) {
2953
+ if (values.length === 0 && variable.fallbackValue === "()")
2954
+ return [];
2955
+ if (values.length === 0)
2956
+ return void 0;
2957
+ return values;
2958
+ }
2959
+ if (values.length === 0)
2960
+ return void 0;
2961
+ if (values.length > 1) {
2962
+ console.warn(
2963
+ `[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.`
2964
+ );
2965
+ }
2966
+ return values[0];
2967
+ }
2968
+ function resolveFormulaVariableMatchCount(variable, ctx) {
2969
+ return resolveMatchedCandidates(variable, ctx).length;
2970
+ }
2971
+ function resolveFormulaVariableUsedFallback(variable, ctx) {
2972
+ const matchCount = resolveFormulaVariableMatchCount(variable, ctx);
2973
+ if (matchCount > 0)
2974
+ return false;
2975
+ if (variable.fallbackValue !== void 0)
2976
+ return true;
2977
+ return void 0;
2978
+ }
2979
+ function buildAssertionBindings(assertion, ctx) {
2980
+ const bindings = /* @__PURE__ */ new Map();
2981
+ for (const variable of assertion.variables) {
2982
+ bindings.set(variable.name, resolveFormulaVariable(variable, ctx));
2983
+ }
2984
+ return bindings;
2985
+ }
2986
+ function buildAssertionFallbackUsage(assertion, ctx) {
2987
+ var _a;
2988
+ const usesHasFallbackValue = ((_a = assertion.test) == null ? void 0 : _a.includes("has-fallback-value")) || assertion.preconditions.some((precondition) => precondition.test.includes("has-fallback-value"));
2989
+ if (!usesHasFallbackValue)
2990
+ return /* @__PURE__ */ new Map();
2991
+ const fallbackUsage = /* @__PURE__ */ new Map();
2992
+ for (const variable of assertion.variables) {
2993
+ const usedFallback = resolveFormulaVariableUsedFallback(variable, ctx);
2994
+ if (usedFallback !== void 0)
2995
+ fallbackUsage.set(variable.name, usedFallback);
2996
+ }
2997
+ return fallbackUsage;
2998
+ }
2999
+ function evaluateAssertionPreconditions(preconditions, bindings, fallbackUsage = /* @__PURE__ */ new Map()) {
3000
+ if (preconditions.length === 0)
3001
+ return { satisfied: true };
3002
+ for (const precondition of preconditions) {
3003
+ let result;
3004
+ try {
3005
+ result = evaluateFormulaTest(precondition.test, bindings, fallbackUsage);
3006
+ } catch (error2) {
3007
+ if (error2 instanceof FormulaExpressionError) {
3008
+ return { satisfied: false, skipReason: "unresolvable" };
3009
+ }
3010
+ throw error2;
3011
+ }
3012
+ if (!result) {
3013
+ return { satisfied: false, skipReason: "precondition" };
3014
+ }
3015
+ }
3016
+ return { satisfied: true };
3017
+ }
3018
+ class FormulaValidationService {
3019
+ /**
3020
+ * Evaluates every assertion in `xbrlInput.formula[0].roles` (only the first entrypoint,
3021
+ * consistent with how xbrl-form-builder.ts uses presentation/hypercube data) against the
3022
+ * given resolution context. One bad assertion never aborts the other 783 — resolution/
3023
+ * evaluation errors are caught per-assertion and recorded as skipped, not thrown.
3024
+ */
3025
+ evaluate(xbrlInput, ctx, language) {
3026
+ var _a;
3027
+ const formulaData = (_a = xbrlInput.formula) == null ? void 0 : _a[0];
3028
+ if (!formulaData)
3029
+ return [];
3030
+ const results = [];
3031
+ for (const role of formulaData.roles) {
3032
+ for (const assertion of role.assertions) {
3033
+ results.push(this._evaluateAssertion(assertion, role, ctx, language));
3034
+ }
3035
+ }
3036
+ return results;
3037
+ }
3038
+ summarize(results) {
3039
+ const summary = {
3040
+ totalAssertions: results.length,
3041
+ evaluated: 0,
3042
+ skipped: 0,
3043
+ passed: 0,
3044
+ failed: 0,
3045
+ failedWarnings: 0,
3046
+ failedErrors: 0
3047
+ };
3048
+ for (const result of results) {
3049
+ if (result.skipped) {
3050
+ summary.skipped += 1;
3051
+ continue;
3052
+ }
3053
+ summary.evaluated += 1;
3054
+ if (result.satisfied) {
3055
+ summary.passed += 1;
3056
+ } else {
3057
+ summary.failed += 1;
3058
+ if (result.severity === "ERROR")
3059
+ summary.failedErrors += 1;
3060
+ else
3061
+ summary.failedWarnings += 1;
3062
+ }
3063
+ }
3064
+ return summary;
3065
+ }
3066
+ _evaluateAssertion(assertion, role, ctx, language) {
3067
+ try {
3068
+ const bindings = buildAssertionBindings(assertion, ctx);
3069
+ const fallbackUsage = buildAssertionFallbackUsage(assertion, ctx);
3070
+ const precondition = evaluateAssertionPreconditions(assertion.preconditions, bindings, fallbackUsage);
3071
+ if (!precondition.satisfied) {
3072
+ return this._skippedResult(assertion, role, precondition.skipReason ?? "unresolvable");
3073
+ }
3074
+ if (assertion.type === "EXISTENCE_ASSERTION") {
3075
+ const matchedFactCount = resolveFormulaVariableMatchCount(assertion.variables[0], ctx);
3076
+ const satisfied2 = evaluateExistenceTest(assertion.test, matchedFactCount);
3077
+ return this._evaluatedResult(assertion, role, satisfied2, language, matchedFactCount);
3078
+ }
3079
+ const satisfied = evaluateFormulaTest(assertion.test, bindings, fallbackUsage);
3080
+ return this._evaluatedResult(assertion, role, satisfied, language);
3081
+ } catch (error2) {
3082
+ if (error2 instanceof FormulaExpressionError) {
3083
+ console.warn(`[FormulaValidationService] Assertion "${assertion.id}" could not be evaluated (${error2.reason}): ${error2.message}`);
3084
+ return this._skippedResult(assertion, role, "unresolvable");
3085
+ }
3086
+ throw error2;
3087
+ }
3088
+ }
3089
+ _skippedResult(assertion, role, skipReason) {
3090
+ return {
3091
+ assertionId: assertion.id,
3092
+ roleURI: role.roleURI,
3093
+ roleLabel: role.role,
3094
+ type: assertion.type,
3095
+ severity: assertion.severity,
3096
+ satisfied: false,
3097
+ skipped: true,
3098
+ skipReason
3099
+ };
3100
+ }
3101
+ _evaluatedResult(assertion, role, satisfied, language, matchedFactCount) {
3102
+ return {
3103
+ assertionId: assertion.id,
3104
+ roleURI: role.roleURI,
3105
+ roleLabel: role.role,
3106
+ type: assertion.type,
3107
+ severity: assertion.severity,
3108
+ satisfied,
3109
+ skipped: false,
3110
+ message: satisfied ? void 0 : this._pickMessage(assertion, language),
3111
+ matchedFactCount
3112
+ };
3113
+ }
3114
+ _pickMessage(assertion, language) {
3115
+ const messages = assertion.messages;
3116
+ if (messages.length === 0)
3117
+ return void 0;
3118
+ const message = messages.find((m) => m.xmlLang === language) ?? messages.find((m) => m.xmlLang === "en") ?? messages[0];
3119
+ return message.text;
3120
+ }
3121
+ }
2303
3122
  class FactMatcher {
2304
3123
  /**
2305
3124
  * Find a matching fact for a given cell context
@@ -3153,15 +3972,15 @@ function determineInputTypeFromBaseChain(baseTypeChain, datatypes) {
3153
3972
  placeholder: I18n.t("field.enterValue")
3154
3973
  };
3155
3974
  }
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;
3975
+ var __defProp$7 = Object.defineProperty;
3976
+ var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
3977
+ var __decorateClass$7 = (decorators, target, key, kind) => {
3978
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
3160
3979
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
3161
3980
  if (decorator = decorators[i2])
3162
3981
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
3163
3982
  if (kind && result)
3164
- __defProp$6(target, key, result);
3983
+ __defProp$7(target, key, result);
3165
3984
  return result;
3166
3985
  };
3167
3986
  let JupiterFormField = class extends LitElement {
@@ -4178,6 +4997,7 @@ let JupiterFormField = class extends LitElement {
4178
4997
  <button
4179
4998
  class="period-icon-btn"
4180
4999
  type="button"
5000
+ tabindex="-1"
4181
5001
  @click="${this._handlePeriodIconClick}"
4182
5002
  title="Edit period"
4183
5003
  >
@@ -4674,99 +5494,99 @@ JupiterFormField.styles = css`
4674
5494
  animation: concept-highlight-pulse 10s ease-out forwards;
4675
5495
  }
4676
5496
  `;
4677
- __decorateClass$6([
5497
+ __decorateClass$7([
4678
5498
  n2({ type: Object })
4679
5499
  ], JupiterFormField.prototype, "field", 2);
4680
- __decorateClass$6([
5500
+ __decorateClass$7([
4681
5501
  n2({ type: String })
4682
5502
  ], JupiterFormField.prototype, "conceptId", 2);
4683
- __decorateClass$6([
5503
+ __decorateClass$7([
4684
5504
  n2({ type: String })
4685
5505
  ], JupiterFormField.prototype, "conceptType", 2);
4686
- __decorateClass$6([
5506
+ __decorateClass$7([
4687
5507
  n2({ type: Array })
4688
5508
  ], JupiterFormField.prototype, "datatypes", 2);
4689
- __decorateClass$6([
5509
+ __decorateClass$7([
4690
5510
  n2({ type: Array })
4691
5511
  ], JupiterFormField.prototype, "defaultUnits", 2);
4692
- __decorateClass$6([
5512
+ __decorateClass$7([
4693
5513
  n2({ type: String })
4694
5514
  ], JupiterFormField.prototype, "columnId", 2);
4695
- __decorateClass$6([
5515
+ __decorateClass$7([
4696
5516
  n2()
4697
5517
  ], JupiterFormField.prototype, "value", 2);
4698
- __decorateClass$6([
5518
+ __decorateClass$7([
4699
5519
  n2({ type: Boolean })
4700
5520
  ], JupiterFormField.prototype, "disabled", 2);
4701
- __decorateClass$6([
5521
+ __decorateClass$7([
4702
5522
  n2({ type: String })
4703
5523
  ], JupiterFormField.prototype, "locale", 2);
4704
- __decorateClass$6([
5524
+ __decorateClass$7([
4705
5525
  n2({ type: Boolean })
4706
5526
  ], JupiterFormField.prototype, "hideLabel", 2);
4707
- __decorateClass$6([
5527
+ __decorateClass$7([
4708
5528
  n2({ type: String })
4709
5529
  ], JupiterFormField.prototype, "mode", 2);
4710
- __decorateClass$6([
5530
+ __decorateClass$7([
4711
5531
  n2({ type: Object })
4712
5532
  ], JupiterFormField.prototype, "masterData", 2);
4713
- __decorateClass$6([
5533
+ __decorateClass$7([
4714
5534
  n2({ type: Array })
4715
5535
  ], JupiterFormField.prototype, "facts", 2);
4716
- __decorateClass$6([
5536
+ __decorateClass$7([
4717
5537
  n2({ type: Object })
4718
5538
  ], JupiterFormField.prototype, "column", 2);
4719
- __decorateClass$6([
5539
+ __decorateClass$7([
4720
5540
  n2({ type: String })
4721
5541
  ], JupiterFormField.prototype, "periodStartDate", 2);
4722
- __decorateClass$6([
5542
+ __decorateClass$7([
4723
5543
  n2({ type: String })
4724
5544
  ], JupiterFormField.prototype, "periodEndDate", 2);
4725
- __decorateClass$6([
5545
+ __decorateClass$7([
4726
5546
  n2({ type: String })
4727
5547
  ], JupiterFormField.prototype, "periodInstantDate", 2);
4728
- __decorateClass$6([
5548
+ __decorateClass$7([
4729
5549
  n2({ type: String })
4730
5550
  ], JupiterFormField.prototype, "unit", 2);
4731
- __decorateClass$6([
5551
+ __decorateClass$7([
4732
5552
  n2({ type: String })
4733
5553
  ], JupiterFormField.prototype, "decimals", 2);
4734
- __decorateClass$6([
5554
+ __decorateClass$7([
4735
5555
  n2({ type: String })
4736
5556
  ], JupiterFormField.prototype, "globalDecimals", 2);
4737
- __decorateClass$6([
5557
+ __decorateClass$7([
4738
5558
  n2({ type: Object })
4739
5559
  ], JupiterFormField.prototype, "typedMemberValues", 2);
4740
- __decorateClass$6([
5560
+ __decorateClass$7([
4741
5561
  r()
4742
5562
  ], JupiterFormField.prototype, "_errors", 2);
4743
- __decorateClass$6([
5563
+ __decorateClass$7([
4744
5564
  r()
4745
5565
  ], JupiterFormField.prototype, "_xbrlErrors", 2);
4746
- __decorateClass$6([
5566
+ __decorateClass$7([
4747
5567
  r()
4748
5568
  ], JupiterFormField.prototype, "_touched", 2);
4749
- __decorateClass$6([
5569
+ __decorateClass$7([
4750
5570
  r()
4751
5571
  ], JupiterFormField.prototype, "_showPeriodPopup", 2);
4752
- __decorateClass$6([
5572
+ __decorateClass$7([
4753
5573
  r()
4754
5574
  ], JupiterFormField.prototype, "_availableUnits", 2);
4755
- __decorateClass$6([
5575
+ __decorateClass$7([
4756
5576
  r()
4757
5577
  ], JupiterFormField.prototype, "_numericDraftValue", 2);
4758
- JupiterFormField = __decorateClass$6([
5578
+ JupiterFormField = __decorateClass$7([
4759
5579
  t$1("jupiter-form-field")
4760
5580
  ], 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;
5581
+ var __defProp$6 = Object.defineProperty;
5582
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
5583
+ var __decorateClass$6 = (decorators, target, key, kind) => {
5584
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
4765
5585
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
4766
5586
  if (decorator = decorators[i2])
4767
5587
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
4768
5588
  if (kind && result)
4769
- __defProp$5(target, key, result);
5589
+ __defProp$6(target, key, result);
4770
5590
  return result;
4771
5591
  };
4772
5592
  let JupiterConceptTree = class extends LitElement {
@@ -5036,6 +5856,7 @@ let JupiterConceptTree = class extends LitElement {
5036
5856
  const hasChildren = this.concept.children && this.concept.children.length > 0;
5037
5857
  const level = this.concept.level || 0;
5038
5858
  const isAbstract = this.concept.abstract || false;
5859
+ const isTotal = isTotalLabel$1(this.concept.preferredLabel);
5039
5860
  return html`
5040
5861
  <!-- Concept Name Cell (Left Column) -->
5041
5862
  <td class="concept-name-cell ${isAbstract ? "abstract" : hasChildren ? "" : "leaf"} ${this.rowFocused ? "row-focused" : ""}">
@@ -5045,7 +5866,7 @@ let JupiterConceptTree = class extends LitElement {
5045
5866
  @click="${this._toggleExpanded}">
5046
5867
  ${hasChildren ? "▶" : ""}
5047
5868
  </div>
5048
- <div class="concept-label"
5869
+ <div class="concept-label ${isTotal ? "total-concept-label" : ""}"
5049
5870
  @click="${this._toggleExpanded}"
5050
5871
  title="${this.concept.id}${this.concept.description ? " - " + this.concept.description : ""}">
5051
5872
  ${this.concept.label}
@@ -5053,14 +5874,14 @@ let JupiterConceptTree = class extends LitElement {
5053
5874
  ${this.concept.balance ? html`
5054
5875
  <div class="concept-balance ${this.concept.balance}">${this.concept.balance}</div>
5055
5876
  ` : ""}
5056
- <button class="concept-info-btn" type="button" title="${I18n.t("conceptInfo.title")}"
5877
+ <button class="concept-info-btn" type="button" tabindex="-1" title="${I18n.t("conceptInfo.title")}"
5057
5878
  @click="${this._openInfoDialog}">ℹ</button>
5058
5879
  ${this.showAddButton ? html`
5059
- <button class="repeat-btn" type="button" title="Add row"
5880
+ <button class="repeat-btn" type="button" tabindex="-1" title="Add row"
5060
5881
  @click="${this._handleAddRepeat}">+</button>
5061
5882
  ` : ""}
5062
5883
  ${this.showRemoveButton ? html`
5063
- <button class="repeat-btn remove" type="button" title="Remove row"
5884
+ <button class="repeat-btn remove" type="button" tabindex="-1" title="Remove row"
5064
5885
  @click="${this._handleRemoveRepeat}">−</button>
5065
5886
  ` : ""}
5066
5887
  </div>
@@ -5078,11 +5899,12 @@ let JupiterConceptTree = class extends LitElement {
5078
5899
  const storedDecimals = (_d = (_c = this.decimalsData) == null ? void 0 : _c[this.concept.id]) == null ? void 0 : _d[column2.id];
5079
5900
  const calcMismatch = this.mode !== "readonly" ? (_e = this.calculationMismatches) == null ? void 0 : _e.get(`${this.concept.id}__${column2.id}`) : void 0;
5080
5901
  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" : ""}">
5902
+ <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
5903
  ${calcMismatch ? html`
5083
5904
  <button
5084
5905
  class="calc-warning-badge"
5085
5906
  type="button"
5907
+ tabindex="-1"
5086
5908
  title="${calcMismatch.message}"
5087
5909
  @click="${(e2) => this._openCalculationWarningDialog(e2, calcMismatch)}"
5088
5910
  >!</button>
@@ -5199,6 +6021,10 @@ JupiterConceptTree.styles = css`
5199
6021
  min-width: 0; /* Allows flex item to shrink below content size */
5200
6022
  }
5201
6023
 
6024
+ .concept-label.total-concept-label {
6025
+ font-weight: 700;
6026
+ }
6027
+
5202
6028
  .concept-balance {
5203
6029
  margin-left: 8px;
5204
6030
  font-size: 11px;
@@ -5290,6 +6116,10 @@ JupiterConceptTree.styles = css`
5290
6116
  color: #fff;
5291
6117
  }
5292
6118
 
6119
+ .field-cell.total-row {
6120
+ background: var(--jupiter-total-cell-background, rgba(0, 0, 0, 0.045));
6121
+ }
6122
+
5293
6123
  .field-cell.highlight-total {
5294
6124
  background: rgba(25, 118, 210, 0.15);
5295
6125
  }
@@ -5395,90 +6225,90 @@ JupiterConceptTree.styles = css`
5395
6225
  }
5396
6226
 
5397
6227
  `;
5398
- __decorateClass$5([
6228
+ __decorateClass$6([
5399
6229
  n2({ type: Object })
5400
6230
  ], JupiterConceptTree.prototype, "concept", 2);
5401
- __decorateClass$5([
6231
+ __decorateClass$6([
5402
6232
  n2({ type: Array })
5403
6233
  ], JupiterConceptTree.prototype, "columns", 2);
5404
- __decorateClass$5([
6234
+ __decorateClass$6([
5405
6235
  n2({ type: Object })
5406
6236
  ], JupiterConceptTree.prototype, "formData", 2);
5407
- __decorateClass$5([
6237
+ __decorateClass$6([
5408
6238
  n2({ type: Object })
5409
6239
  ], JupiterConceptTree.prototype, "periodData", 2);
5410
- __decorateClass$5([
6240
+ __decorateClass$6([
5411
6241
  n2({ type: Object })
5412
6242
  ], JupiterConceptTree.prototype, "unitData", 2);
5413
- __decorateClass$5([
6243
+ __decorateClass$6([
5414
6244
  n2({ type: Object })
5415
6245
  ], JupiterConceptTree.prototype, "decimalsData", 2);
5416
- __decorateClass$5([
6246
+ __decorateClass$6([
5417
6247
  n2({ type: String })
5418
6248
  ], JupiterConceptTree.prototype, "globalDecimals", 2);
5419
- __decorateClass$5([
6249
+ __decorateClass$6([
5420
6250
  n2({ type: Array })
5421
6251
  ], JupiterConceptTree.prototype, "defaultUnits", 2);
5422
- __decorateClass$5([
6252
+ __decorateClass$6([
5423
6253
  n2({ type: Boolean })
5424
6254
  ], JupiterConceptTree.prototype, "disabled", 2);
5425
- __decorateClass$5([
6255
+ __decorateClass$6([
5426
6256
  n2({ type: String })
5427
6257
  ], JupiterConceptTree.prototype, "locale", 2);
5428
- __decorateClass$5([
6258
+ __decorateClass$6([
5429
6259
  n2({ type: Set })
5430
6260
  ], JupiterConceptTree.prototype, "expandedConcepts", 2);
5431
- __decorateClass$5([
6261
+ __decorateClass$6([
5432
6262
  n2({ type: Array })
5433
6263
  ], JupiterConceptTree.prototype, "datatypes", 2);
5434
- __decorateClass$5([
6264
+ __decorateClass$6([
5435
6265
  n2({ type: String })
5436
6266
  ], JupiterConceptTree.prototype, "mode", 2);
5437
- __decorateClass$5([
6267
+ __decorateClass$6([
5438
6268
  n2({ type: Object })
5439
6269
  ], JupiterConceptTree.prototype, "masterData", 2);
5440
- __decorateClass$5([
6270
+ __decorateClass$6([
5441
6271
  n2({ type: Array })
5442
6272
  ], JupiterConceptTree.prototype, "facts", 2);
5443
- __decorateClass$5([
6273
+ __decorateClass$6([
5444
6274
  n2({ type: Object })
5445
6275
  ], JupiterConceptTree.prototype, "typedMemberData", 2);
5446
- __decorateClass$5([
6276
+ __decorateClass$6([
5447
6277
  n2({ type: Boolean })
5448
6278
  ], JupiterConceptTree.prototype, "showAddButton", 2);
5449
- __decorateClass$5([
6279
+ __decorateClass$6([
5450
6280
  n2({ type: Boolean })
5451
6281
  ], JupiterConceptTree.prototype, "showRemoveButton", 2);
5452
- __decorateClass$5([
6282
+ __decorateClass$6([
5453
6283
  n2({ type: String })
5454
6284
  ], JupiterConceptTree.prototype, "highlightType", 2);
5455
- __decorateClass$5([
6285
+ __decorateClass$6([
5456
6286
  n2({ type: String })
5457
6287
  ], JupiterConceptTree.prototype, "highlightColumnId", 2);
5458
- __decorateClass$5([
6288
+ __decorateClass$6([
5459
6289
  n2({ type: Object })
5460
6290
  ], JupiterConceptTree.prototype, "calculationMismatches", 2);
5461
- __decorateClass$5([
6291
+ __decorateClass$6([
5462
6292
  n2({ type: String })
5463
6293
  ], JupiterConceptTree.prototype, "language", 2);
5464
- __decorateClass$5([
6294
+ __decorateClass$6([
5465
6295
  n2({ type: Boolean })
5466
6296
  ], JupiterConceptTree.prototype, "rowFocused", 2);
5467
- __decorateClass$5([
6297
+ __decorateClass$6([
5468
6298
  r()
5469
6299
  ], JupiterConceptTree.prototype, "_expanded", 2);
5470
- JupiterConceptTree = __decorateClass$5([
6300
+ JupiterConceptTree = __decorateClass$6([
5471
6301
  t$1("jupiter-concept-tree")
5472
6302
  ], 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;
6303
+ var __defProp$5 = Object.defineProperty;
6304
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
6305
+ var __decorateClass$5 = (decorators, target, key, kind) => {
6306
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
5477
6307
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
5478
6308
  if (decorator = decorators[i2])
5479
6309
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
5480
6310
  if (kind && result)
5481
- __defProp$4(target, key, result);
6311
+ __defProp$5(target, key, result);
5482
6312
  return result;
5483
6313
  };
5484
6314
  let JupiterAddColumnDialog = class extends LitElement {
@@ -6087,48 +6917,48 @@ JupiterAddColumnDialog.styles = css`
6087
6917
  line-height: 1.4;
6088
6918
  }
6089
6919
  `;
6090
- __decorateClass$4([
6920
+ __decorateClass$5([
6091
6921
  n2({ type: String })
6092
6922
  ], JupiterAddColumnDialog.prototype, "periodType", 2);
6093
- __decorateClass$4([
6923
+ __decorateClass$5([
6094
6924
  n2({ type: Boolean })
6095
6925
  ], JupiterAddColumnDialog.prototype, "open", 2);
6096
- __decorateClass$4([
6926
+ __decorateClass$5([
6097
6927
  n2({ type: Array })
6098
6928
  ], JupiterAddColumnDialog.prototype, "availableDimensions", 2);
6099
- __decorateClass$4([
6929
+ __decorateClass$5([
6100
6930
  n2({ type: String })
6101
6931
  ], JupiterAddColumnDialog.prototype, "periodStartDate", 2);
6102
- __decorateClass$4([
6932
+ __decorateClass$5([
6103
6933
  n2({ type: String })
6104
6934
  ], JupiterAddColumnDialog.prototype, "periodEndDate", 2);
6105
- __decorateClass$4([
6935
+ __decorateClass$5([
6106
6936
  r()
6107
6937
  ], JupiterAddColumnDialog.prototype, "_startDate", 2);
6108
- __decorateClass$4([
6938
+ __decorateClass$5([
6109
6939
  r()
6110
6940
  ], JupiterAddColumnDialog.prototype, "_endDate", 2);
6111
- __decorateClass$4([
6941
+ __decorateClass$5([
6112
6942
  r()
6113
6943
  ], JupiterAddColumnDialog.prototype, "_instantDate", 2);
6114
- __decorateClass$4([
6944
+ __decorateClass$5([
6115
6945
  r()
6116
6946
  ], JupiterAddColumnDialog.prototype, "_selectedType", 2);
6117
- __decorateClass$4([
6947
+ __decorateClass$5([
6118
6948
  r()
6119
6949
  ], JupiterAddColumnDialog.prototype, "_selectedDimensions", 2);
6120
- JupiterAddColumnDialog = __decorateClass$4([
6950
+ JupiterAddColumnDialog = __decorateClass$5([
6121
6951
  t$1("jupiter-add-column-dialog")
6122
6952
  ], 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;
6953
+ var __defProp$4 = Object.defineProperty;
6954
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
6955
+ var __decorateClass$4 = (decorators, target, key, kind) => {
6956
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
6127
6957
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
6128
6958
  if (decorator = decorators[i2])
6129
6959
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
6130
6960
  if (kind && result)
6131
- __defProp$3(target, key, result);
6961
+ __defProp$4(target, key, result);
6132
6962
  return result;
6133
6963
  };
6134
6964
  let JupiterFormSection = class extends LitElement {
@@ -6154,6 +6984,8 @@ let JupiterFormSection = class extends LitElement {
6154
6984
  this.periodStartDate = "";
6155
6985
  this.periodEndDate = "";
6156
6986
  this.language = "en";
6987
+ this.calculationEnabled = false;
6988
+ this.totalManuallyEditedData = {};
6157
6989
  this._expanded = true;
6158
6990
  this._showAddColumnDialog = false;
6159
6991
  this._sectionPeriodType = "duration";
@@ -6162,7 +6994,7 @@ let JupiterFormSection = class extends LitElement {
6162
6994
  this._boundFieldBlur = (e2) => {
6163
6995
  this._clearHighlights();
6164
6996
  this._focusedConceptId = null;
6165
- if (this.mode === "readonly")
6997
+ if (this.mode === "readonly" || !this.calculationEnabled)
6166
6998
  return;
6167
6999
  const detail = e2.detail;
6168
7000
  if ((detail == null ? void 0 : detail.conceptId) && (detail == null ? void 0 : detail.columnId)) {
@@ -6178,6 +7010,7 @@ let JupiterFormSection = class extends LitElement {
6178
7010
  this._memberParentMap = /* @__PURE__ */ new Map();
6179
7011
  this._totalConceptMap = /* @__PURE__ */ new Map();
6180
7012
  this._totalChildConceptsMap = /* @__PURE__ */ new Map();
7013
+ this._sessionManuallyEditedTotals = /* @__PURE__ */ new Set();
6181
7014
  }
6182
7015
  connectedCallback() {
6183
7016
  super.connectedCallback();
@@ -6198,8 +7031,15 @@ let JupiterFormSection = class extends LitElement {
6198
7031
  if (changedProperties.has("isFirstSection") || changedProperties.has("section")) {
6199
7032
  this._checkAndExpandFirstSection();
6200
7033
  }
6201
- if (changedProperties.has("section") && ((_a = this.section) == null ? void 0 : _a.concepts)) {
6202
- this._buildTotalMaps(this.section.concepts);
7034
+ if (changedProperties.has("section") || changedProperties.has("calculationEnabled")) {
7035
+ if (this.calculationEnabled && ((_a = this.section) == null ? void 0 : _a.concepts)) {
7036
+ this._buildTotalMaps(this.section.concepts);
7037
+ } else {
7038
+ this._clearCalculationState();
7039
+ }
7040
+ }
7041
+ if (changedProperties.has("totalManuallyEditedData")) {
7042
+ this._sessionManuallyEditedTotals.clear();
6203
7043
  }
6204
7044
  }
6205
7045
  _checkAndExpandFirstSection() {
@@ -6350,6 +7190,15 @@ let JupiterFormSection = class extends LitElement {
6350
7190
  }
6351
7191
  _handleFieldChange(event) {
6352
7192
  event.stopPropagation();
7193
+ const { conceptId, columnId } = event.detail || {};
7194
+ if (conceptId && columnId && this._totalConceptMap.has(conceptId)) {
7195
+ this._sessionManuallyEditedTotals.add(`${conceptId}__${columnId}`);
7196
+ this.dispatchEvent(new CustomEvent("total-manually-edited", {
7197
+ detail: { conceptId, columnId },
7198
+ bubbles: true,
7199
+ composed: true
7200
+ }));
7201
+ }
6353
7202
  this.dispatchEvent(new CustomEvent("field-change", {
6354
7203
  detail: event.detail,
6355
7204
  bubbles: true,
@@ -6378,6 +7227,13 @@ let JupiterFormSection = class extends LitElement {
6378
7227
  }
6379
7228
  return result;
6380
7229
  }
7230
+ _isTotalManuallyEdited(totalConceptId, columnId) {
7231
+ var _a, _b;
7232
+ const key = `${totalConceptId}__${columnId}`;
7233
+ if (this._sessionManuallyEditedTotals.has(key))
7234
+ return true;
7235
+ return ((_b = (_a = this.totalManuallyEditedData) == null ? void 0 : _a[totalConceptId]) == null ? void 0 : _b[columnId]) === true;
7236
+ }
6381
7237
  /**
6382
7238
  * Returns true when every column cell for a concept has no value.
6383
7239
  * Used in readonly mode to hide fully-blank rows.
@@ -6435,6 +7291,21 @@ let JupiterFormSection = class extends LitElement {
6435
7291
  this._totalChildConceptsMap.clear();
6436
7292
  this._traverseTotalGroups(concepts);
6437
7293
  }
7294
+ /**
7295
+ * JDF-041: drops all calculation-warning state — used when the feature is disabled/toggled off.
7296
+ * Clears each existing mismatch through `_clearCalculationMismatch` (rather than resetting the
7297
+ * map directly) so the parent's validation summary, which tracks mismatches via the dispatched
7298
+ * `calculation-mismatch-changed` events, is told about each removal instead of being left stale.
7299
+ */
7300
+ _clearCalculationState() {
7301
+ this._totalChildrenMap.clear();
7302
+ this._memberParentMap.clear();
7303
+ this._totalConceptMap.clear();
7304
+ this._totalChildConceptsMap.clear();
7305
+ for (const key of Array.from(this._calculationMismatches.keys())) {
7306
+ this._clearCalculationMismatch(key);
7307
+ }
7308
+ }
6438
7309
  /**
6439
7310
  * JDF-038: XBRL uses more than one preferredLabel role to mark a "total" row — the plain
6440
7311
  * `.../role/totalLabel` and the sign-flipped `.../role/negatedTotalLabel` (used for subtotals of
@@ -6443,7 +7314,16 @@ let JupiterFormSection = class extends LitElement {
6443
7314
  * matching is the single source of truth for "is this row a total" everywhere in this file.
6444
7315
  */
6445
7316
  static _isTotalLabel(preferredLabel) {
6446
- return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("totallabel"));
7317
+ return isTotalLabel$1(preferredLabel);
7318
+ }
7319
+ /**
7320
+ * JDF-041: distinguishes a self-contained "deduction block" subtotal (negatedTotalLabel, e.g.
7321
+ * "Total of sum of expenses") from a plain cascading/running total. Only the latter is treated as
7322
+ * additively built on the total before it — a negatedTotalLabel row summarizes just the raw items
7323
+ * directly before it and must not also absorb an unrelated earlier total into its own check.
7324
+ */
7325
+ static _isNegatedTotalLabel(preferredLabel) {
7326
+ return !!(preferredLabel == null ? void 0 : preferredLabel.toLowerCase().includes("negatedtotallabel"));
6447
7327
  }
6448
7328
  /**
6449
7329
  * A member of a total's sibling/child group can itself be a presentation-only abstract wrapper
@@ -6475,6 +7355,47 @@ let JupiterFormSection = class extends LitElement {
6475
7355
  this._memberParentMap.set(memberId, totalConcept.id);
6476
7356
  }
6477
7357
  }
7358
+ /**
7359
+ * JDF-041: computes the JDF-030 sibling-fallback member set for a total at `concepts[i]`, given the
7360
+ * nearest preceding total's index (`lastTotalIdx`, -1 if none) and the raw siblings between them.
7361
+ *
7362
+ * - No preceding total → members are just the raw siblings (unchanged, e.g. the very first total
7363
+ * in the role).
7364
+ * - This total is itself a negatedTotalLabel (a self-contained deduction-block subtotal, e.g.
7365
+ * "Total of sum of expenses") → the preceding total stays a scan boundary only, never a member —
7366
+ * it summarizes just its own raw siblings, same as before JDF-041.
7367
+ * - Otherwise (a plain totalLabel, cascading/running total) with raw siblings before it → the
7368
+ * preceding total is included as a real member alongside them, since this total is built
7369
+ * additively on top of it.
7370
+ * - Otherwise (a plain totalLabel adjacent to the preceding total, zero raw siblings between them):
7371
+ * if that preceding total is itself a negatedTotalLabel subtotal, it doesn't carry the running
7372
+ * total from before it forward (per the rule above) — reach one more hop back to also include the
7373
+ * total the subtotal was deducted from (e.g. "Net operating result" needs both "Gross operating
7374
+ * result" and "Total of sum of expenses", not just the latter). Otherwise, the single preceding
7375
+ * total is the member, as it was for JDF-038's original adjacent-boundary case.
7376
+ */
7377
+ _resolveTotalGroupMembers(concepts, i2, lastTotalIdx, siblings) {
7378
+ if (lastTotalIdx < 0)
7379
+ return siblings;
7380
+ const concept = concepts[i2];
7381
+ if (JupiterFormSection._isNegatedTotalLabel(concept.preferredLabel)) {
7382
+ return siblings.length > 0 ? siblings : [concepts[lastTotalIdx]];
7383
+ }
7384
+ if (siblings.length > 0) {
7385
+ return [concepts[lastTotalIdx], ...siblings];
7386
+ }
7387
+ if (JupiterFormSection._isNegatedTotalLabel(concepts[lastTotalIdx].preferredLabel)) {
7388
+ let grandTotalIdx = -1;
7389
+ for (let k = lastTotalIdx - 1; k >= 0; k--) {
7390
+ if (JupiterFormSection._isTotalLabel(concepts[k].preferredLabel)) {
7391
+ grandTotalIdx = k;
7392
+ break;
7393
+ }
7394
+ }
7395
+ return grandTotalIdx >= 0 ? [concepts[grandTotalIdx], concepts[lastTotalIdx]] : [concepts[lastTotalIdx]];
7396
+ }
7397
+ return [concepts[lastTotalIdx]];
7398
+ }
6478
7399
  _traverseTotalGroups(concepts) {
6479
7400
  var _a, _b;
6480
7401
  for (let i2 = 0; i2 < concepts.length; i2++) {
@@ -6491,10 +7412,9 @@ let JupiterFormSection = class extends LitElement {
6491
7412
  }
6492
7413
  }
6493
7414
  const siblings = concepts.slice(lastTotalIdx + 1, i2);
6494
- if (siblings.length > 0) {
6495
- this._registerTotalGroup(concept, siblings);
6496
- } else if (lastTotalIdx >= 0) {
6497
- this._registerTotalGroup(concept, [concepts[lastTotalIdx]]);
7415
+ const members = this._resolveTotalGroupMembers(concepts, i2, lastTotalIdx, siblings);
7416
+ if (members.length > 0) {
7417
+ this._registerTotalGroup(concept, members);
6498
7418
  }
6499
7419
  }
6500
7420
  }
@@ -6515,10 +7435,29 @@ let JupiterFormSection = class extends LitElement {
6515
7435
  }
6516
7436
  }
6517
7437
  _evaluateTotal(totalConceptId, columnId) {
6518
- var _a;
7438
+ var _a, _b;
6519
7439
  const totalConcept = this._totalConceptMap.get(totalConceptId);
6520
- const totalRaw = (_a = this.formData[totalConceptId]) == null ? void 0 : _a[columnId];
6521
7440
  const key = `${totalConceptId}__${columnId}`;
7441
+ const children = this._totalChildConceptsMap.get(totalConceptId) || [];
7442
+ const { calculatedSum, hasAnyValue, breakdown } = this._buildCalculationBreakdown(
7443
+ children,
7444
+ this.formData,
7445
+ columnId,
7446
+ totalConcept.balance
7447
+ );
7448
+ if (!this._isTotalManuallyEdited(totalConceptId, columnId)) {
7449
+ if (hasAnyValue) {
7450
+ const currentRaw = (_a = this.formData[totalConceptId]) == null ? void 0 : _a[columnId];
7451
+ const currentValue = parseFloat(currentRaw);
7452
+ const isBlank = currentRaw === null || currentRaw === void 0 || currentRaw === "";
7453
+ if (isBlank || isNaN(currentValue) || !JupiterFormSection._checkTotal(currentValue, calculatedSum)) {
7454
+ this._dispatchCalculationTotalAutoPopulated(totalConceptId, columnId, calculatedSum);
7455
+ }
7456
+ }
7457
+ this._clearCalculationMismatch(key);
7458
+ return;
7459
+ }
7460
+ const totalRaw = (_b = this.formData[totalConceptId]) == null ? void 0 : _b[columnId];
6522
7461
  if (totalRaw === null || totalRaw === void 0 || totalRaw === "") {
6523
7462
  this._clearCalculationMismatch(key);
6524
7463
  return;
@@ -6528,13 +7467,6 @@ let JupiterFormSection = class extends LitElement {
6528
7467
  this._clearCalculationMismatch(key);
6529
7468
  return;
6530
7469
  }
6531
- const children = this._totalChildConceptsMap.get(totalConceptId) || [];
6532
- const { calculatedSum, hasAnyValue, breakdown } = JupiterFormSection._buildCalculationBreakdown(
6533
- children,
6534
- this.formData,
6535
- columnId,
6536
- totalConcept.balance
6537
- );
6538
7470
  if (!hasAnyValue || JupiterFormSection._checkTotal(totalValue, calculatedSum)) {
6539
7471
  this._clearCalculationMismatch(key);
6540
7472
  return;
@@ -6557,6 +7489,13 @@ let JupiterFormSection = class extends LitElement {
6557
7489
  this._calculationMismatches = next;
6558
7490
  this._dispatchCalculationMismatchChanged(key, detail);
6559
7491
  }
7492
+ _dispatchCalculationTotalAutoPopulated(conceptId, columnId, value) {
7493
+ this.dispatchEvent(new CustomEvent("calculation-total-auto-populated", {
7494
+ detail: { conceptId, columnId, value, sectionId: this.section.id },
7495
+ bubbles: true,
7496
+ composed: true
7497
+ }));
7498
+ }
6560
7499
  _clearCalculationMismatch(key) {
6561
7500
  if (!this._calculationMismatches.has(key))
6562
7501
  return;
@@ -6576,24 +7515,51 @@ let JupiterFormSection = class extends LitElement {
6576
7515
  static _resolveWeight(totalBalance, childBalance) {
6577
7516
  return !totalBalance || !childBalance || totalBalance === childBalance ? 1 : -1;
6578
7517
  }
6579
- static _buildCalculationBreakdown(children, formData, columnId, totalBalance) {
7518
+ /**
7519
+ * JDF-041: a group member can itself be a registered cascading total with no entered value (e.g. a
7520
+ * customer who fills in "Operating income" directly without ever populating the "Net operating
7521
+ * result" running-subtotal row it's built on). Treating a blank member as a zero contribution
7522
+ * silently drops whatever it's actually built from and produces a false-positive mismatch. Instead,
7523
+ * look through a blank total member into its own registered members, recursively, so the check
7524
+ * reaches the real entered values wherever they are in the chain. `visited`/`depth` guard against
7525
+ * cycles and unbounded recursion (mirrors `_resolveValueConcept`'s depth cutoff).
7526
+ */
7527
+ _resolveMemberContributions(member, weight, formData, columnId, visited, depth = 0) {
6580
7528
  var _a;
7529
+ const val = parseFloat((_a = formData[member.id]) == null ? void 0 : _a[columnId]);
7530
+ if (!isNaN(val)) {
7531
+ return [{ conceptId: member.id, label: member.label, value: val, balance: member.balance, weight }];
7532
+ }
7533
+ if (depth >= 5 || visited.has(member.id))
7534
+ return [];
7535
+ const nestedMembers = this._totalChildConceptsMap.get(member.id);
7536
+ if (!nestedMembers)
7537
+ return [];
7538
+ const nextVisited = new Set(visited).add(member.id);
7539
+ const contributions = [];
7540
+ for (const nested of nestedMembers) {
7541
+ const nestedWeight = weight * JupiterFormSection._resolveWeight(member.balance, nested.balance);
7542
+ contributions.push(...this._resolveMemberContributions(nested, nestedWeight, formData, columnId, nextVisited, depth + 1));
7543
+ }
7544
+ return contributions;
7545
+ }
7546
+ _buildCalculationBreakdown(children, formData, columnId, totalBalance) {
6581
7547
  let calculatedSum = 0;
6582
7548
  let hasAnyValue = false;
6583
7549
  const breakdown = [];
6584
7550
  for (const child of children) {
6585
- const val = parseFloat((_a = formData[child.id]) == null ? void 0 : _a[columnId]);
6586
- if (isNaN(val))
6587
- continue;
6588
- hasAnyValue = true;
6589
7551
  const weight = JupiterFormSection._resolveWeight(totalBalance, child.balance);
6590
- calculatedSum += weight * val;
6591
- breakdown.push({ conceptId: child.id, label: child.label, value: val, balance: child.balance, weight });
7552
+ const contributions = this._resolveMemberContributions(child, weight, formData, columnId, /* @__PURE__ */ new Set());
7553
+ for (const contribution of contributions) {
7554
+ calculatedSum += contribution.weight * contribution.value;
7555
+ hasAnyValue = true;
7556
+ breakdown.push(contribution);
7557
+ }
6592
7558
  }
6593
7559
  return { calculatedSum, hasAnyValue, breakdown };
6594
7560
  }
6595
7561
  static _checkTotal(totalValue, calculatedSum) {
6596
- return Math.abs(calculatedSum - totalValue) < 0.01;
7562
+ return Math.abs(calculatedSum - totalValue) < CALCULATION_TOLERANCE;
6597
7563
  }
6598
7564
  static _formatNumber(n3) {
6599
7565
  return Number.isInteger(n3) ? String(n3) : n3.toFixed(2);
@@ -7179,120 +8145,126 @@ JupiterFormSection.styles = css`
7179
8145
  font-style: italic;
7180
8146
  }
7181
8147
  `;
7182
- __decorateClass$3([
8148
+ __decorateClass$4([
7183
8149
  n2({ type: Object })
7184
8150
  ], JupiterFormSection.prototype, "section", 2);
7185
- __decorateClass$3([
8151
+ __decorateClass$4([
7186
8152
  n2({ type: Array })
7187
8153
  ], JupiterFormSection.prototype, "columns", 2);
7188
- __decorateClass$3([
8154
+ __decorateClass$4([
7189
8155
  n2({ type: Array })
7190
8156
  ], JupiterFormSection.prototype, "datatypes", 2);
7191
- __decorateClass$3([
8157
+ __decorateClass$4([
7192
8158
  n2({ type: Object })
7193
8159
  ], JupiterFormSection.prototype, "formData", 2);
7194
- __decorateClass$3([
8160
+ __decorateClass$4([
7195
8161
  n2({ type: Object })
7196
8162
  ], JupiterFormSection.prototype, "periodData", 2);
7197
- __decorateClass$3([
8163
+ __decorateClass$4([
7198
8164
  n2({ type: Object })
7199
8165
  ], JupiterFormSection.prototype, "unitData", 2);
7200
- __decorateClass$3([
8166
+ __decorateClass$4([
7201
8167
  n2({ type: Object })
7202
8168
  ], JupiterFormSection.prototype, "decimalsData", 2);
7203
- __decorateClass$3([
8169
+ __decorateClass$4([
7204
8170
  n2({ type: String })
7205
8171
  ], JupiterFormSection.prototype, "globalDecimals", 2);
7206
- __decorateClass$3([
8172
+ __decorateClass$4([
7207
8173
  n2({ type: Object })
7208
8174
  ], JupiterFormSection.prototype, "typedMemberData", 2);
7209
- __decorateClass$3([
8175
+ __decorateClass$4([
7210
8176
  n2({ type: Object })
7211
8177
  ], JupiterFormSection.prototype, "repeatCounts", 2);
7212
- __decorateClass$3([
8178
+ __decorateClass$4([
7213
8179
  n2({ type: Array })
7214
8180
  ], JupiterFormSection.prototype, "defaultUnits", 2);
7215
- __decorateClass$3([
8181
+ __decorateClass$4([
7216
8182
  n2({ type: Boolean })
7217
8183
  ], JupiterFormSection.prototype, "disabled", 2);
7218
- __decorateClass$3([
8184
+ __decorateClass$4([
7219
8185
  n2({ type: Boolean })
7220
8186
  ], JupiterFormSection.prototype, "collapsible", 2);
7221
- __decorateClass$3([
8187
+ __decorateClass$4([
7222
8188
  n2({ type: String })
7223
8189
  ], JupiterFormSection.prototype, "locale", 2);
7224
- __decorateClass$3([
8190
+ __decorateClass$4([
7225
8191
  n2({ type: Boolean })
7226
8192
  ], JupiterFormSection.prototype, "isFirstSection", 2);
7227
- __decorateClass$3([
8193
+ __decorateClass$4([
7228
8194
  n2({ type: Array })
7229
8195
  ], JupiterFormSection.prototype, "availableDimensions", 2);
7230
- __decorateClass$3([
8196
+ __decorateClass$4([
7231
8197
  n2({ type: Boolean })
7232
8198
  ], JupiterFormSection.prototype, "hideHeader", 2);
7233
- __decorateClass$3([
8199
+ __decorateClass$4([
7234
8200
  n2({ type: String })
7235
8201
  ], JupiterFormSection.prototype, "mode", 2);
7236
- __decorateClass$3([
8202
+ __decorateClass$4([
7237
8203
  n2({ type: Boolean })
7238
8204
  ], JupiterFormSection.prototype, "showFactsOnly", 2);
7239
- __decorateClass$3([
8205
+ __decorateClass$4([
7240
8206
  n2({ type: Object })
7241
8207
  ], JupiterFormSection.prototype, "conceptMatchIds", 2);
7242
- __decorateClass$3([
8208
+ __decorateClass$4([
7243
8209
  n2({ type: Object })
7244
8210
  ], JupiterFormSection.prototype, "masterData", 2);
7245
- __decorateClass$3([
8211
+ __decorateClass$4([
7246
8212
  n2({ type: String })
7247
8213
  ], JupiterFormSection.prototype, "periodStartDate", 2);
7248
- __decorateClass$3([
8214
+ __decorateClass$4([
7249
8215
  n2({ type: String })
7250
8216
  ], JupiterFormSection.prototype, "periodEndDate", 2);
7251
- __decorateClass$3([
8217
+ __decorateClass$4([
7252
8218
  n2({ type: String })
7253
8219
  ], JupiterFormSection.prototype, "language", 2);
7254
- __decorateClass$3([
8220
+ __decorateClass$4([
8221
+ n2({ type: Boolean })
8222
+ ], JupiterFormSection.prototype, "calculationEnabled", 2);
8223
+ __decorateClass$4([
8224
+ n2({ type: Object })
8225
+ ], JupiterFormSection.prototype, "totalManuallyEditedData", 2);
8226
+ __decorateClass$4([
7255
8227
  r()
7256
8228
  ], JupiterFormSection.prototype, "_expanded", 2);
7257
- __decorateClass$3([
8229
+ __decorateClass$4([
7258
8230
  r()
7259
8231
  ], JupiterFormSection.prototype, "_showAddColumnDialog", 2);
7260
- __decorateClass$3([
8232
+ __decorateClass$4([
7261
8233
  r()
7262
8234
  ], JupiterFormSection.prototype, "_sectionPeriodType", 2);
7263
- __decorateClass$3([
8235
+ __decorateClass$4([
7264
8236
  r()
7265
8237
  ], JupiterFormSection.prototype, "_openMenuColumnId", 2);
7266
- __decorateClass$3([
8238
+ __decorateClass$4([
7267
8239
  r()
7268
8240
  ], JupiterFormSection.prototype, "_insertAfterColumnId", 2);
7269
- __decorateClass$3([
8241
+ __decorateClass$4([
7270
8242
  r()
7271
8243
  ], JupiterFormSection.prototype, "_expandedConcepts", 2);
7272
- __decorateClass$3([
8244
+ __decorateClass$4([
7273
8245
  r()
7274
8246
  ], JupiterFormSection.prototype, "_allTreeExpanded", 2);
7275
- __decorateClass$3([
8247
+ __decorateClass$4([
7276
8248
  r()
7277
8249
  ], JupiterFormSection.prototype, "_highlightMap", 2);
7278
- __decorateClass$3([
8250
+ __decorateClass$4([
7279
8251
  r()
7280
8252
  ], JupiterFormSection.prototype, "_focusedConceptId", 2);
7281
- __decorateClass$3([
8253
+ __decorateClass$4([
7282
8254
  r()
7283
8255
  ], JupiterFormSection.prototype, "_calculationMismatches", 2);
7284
- JupiterFormSection = __decorateClass$3([
8256
+ JupiterFormSection = __decorateClass$4([
7285
8257
  t$1("jupiter-form-section")
7286
8258
  ], JupiterFormSection);
7287
- var __defProp$2 = Object.defineProperty;
7288
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
7289
- var __decorateClass$2 = (decorators, target, key, kind) => {
7290
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
8259
+ var __defProp$3 = Object.defineProperty;
8260
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
8261
+ var __decorateClass$3 = (decorators, target, key, kind) => {
8262
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
7291
8263
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
7292
8264
  if (decorator = decorators[i2])
7293
8265
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
7294
8266
  if (kind && result)
7295
- __defProp$2(target, key, result);
8267
+ __defProp$3(target, key, result);
7296
8268
  return result;
7297
8269
  };
7298
8270
  let JupiterAdvancedFilter = class extends LitElement {
@@ -7481,27 +8453,27 @@ JupiterAdvancedFilter.styles = css`
7481
8453
  color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
7482
8454
  }
7483
8455
  `;
7484
- __decorateClass$2([
8456
+ __decorateClass$3([
7485
8457
  n2({ type: Boolean })
7486
8458
  ], JupiterAdvancedFilter.prototype, "showFactsOnly", 2);
7487
- __decorateClass$2([
8459
+ __decorateClass$3([
7488
8460
  n2({ type: String })
7489
8461
  ], JupiterAdvancedFilter.prototype, "conceptSearchText", 2);
7490
- __decorateClass$2([
8462
+ __decorateClass$3([
7491
8463
  r()
7492
8464
  ], JupiterAdvancedFilter.prototype, "_localConceptSearchText", 2);
7493
- JupiterAdvancedFilter = __decorateClass$2([
8465
+ JupiterAdvancedFilter = __decorateClass$3([
7494
8466
  t$1("jupiter-advanced-filter")
7495
8467
  ], JupiterAdvancedFilter);
7496
- var __defProp$1 = Object.defineProperty;
7497
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
7498
- var __decorateClass$1 = (decorators, target, key, kind) => {
7499
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
8468
+ var __defProp$2 = Object.defineProperty;
8469
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
8470
+ var __decorateClass$2 = (decorators, target, key, kind) => {
8471
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
7500
8472
  for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
7501
8473
  if (decorator = decorators[i2])
7502
8474
  result = (kind ? decorator(target, key, result) : decorator(result)) || result;
7503
8475
  if (kind && result)
7504
- __defProp$1(target, key, result);
8476
+ __defProp$2(target, key, result);
7505
8477
  return result;
7506
8478
  };
7507
8479
  let _lastViewWasAdvancedFilter = false;
@@ -9092,188 +10064,519 @@ JupiterFilterRolesDialog.styles = css`
9092
10064
  font-size: 14px;
9093
10065
  }
9094
10066
 
9095
- .picklist-search {
9096
- padding: 12px;
9097
- border-bottom: 1px solid var(--jupiter-border-color, #ddd);
10067
+ .picklist-search {
10068
+ padding: 12px;
10069
+ border-bottom: 1px solid var(--jupiter-border-color, #ddd);
10070
+ }
10071
+
10072
+ .picklist-list {
10073
+ flex: 1;
10074
+ overflow-y: auto;
10075
+ padding: 8px;
10076
+ }
10077
+
10078
+ .picklist-item {
10079
+ padding: 10px 12px;
10080
+ margin-bottom: 4px;
10081
+ border: 1px solid var(--jupiter-border-color, #ddd);
10082
+ border-radius: 4px;
10083
+ cursor: pointer;
10084
+ transition: all 0.2s ease;
10085
+ background: var(--jupiter-background, #fff);
10086
+ }
10087
+
10088
+ .picklist-item:hover {
10089
+ background: var(--jupiter-hover-background, #f5f5f5);
10090
+ border-color: var(--jupiter-primary-color, #1976d2);
10091
+ }
10092
+
10093
+ .picklist-item.selected {
10094
+ background: var(--jupiter-primary-color, #1976d2);
10095
+ color: white;
10096
+ border-color: var(--jupiter-primary-color, #1976d2);
10097
+ }
10098
+
10099
+ .picklist-item.dragging {
10100
+ opacity: 0.5;
10101
+ cursor: move;
10102
+ }
10103
+
10104
+ .picklist-item.drag-over {
10105
+ border-top: 3px solid var(--jupiter-primary-color, #1976d2);
10106
+ margin-top: 3px;
10107
+ }
10108
+
10109
+ .picklist-item[draggable="true"] {
10110
+ cursor: move;
10111
+ }
10112
+
10113
+ .picklist-item-label {
10114
+ font-weight: 500;
10115
+ font-size: 14px;
10116
+ margin-bottom: 4px;
10117
+ }
10118
+
10119
+ .picklist-item-value {
10120
+ font-size: 11px;
10121
+ opacity: 0.8;
10122
+ word-break: break-all;
10123
+ }
10124
+
10125
+ .picklist-controls {
10126
+ display: flex;
10127
+ flex-direction: column;
10128
+ justify-content: center;
10129
+ gap: 8px;
10130
+ padding: 0 8px;
10131
+ }
10132
+
10133
+ .picklist-button {
10134
+ background: var(--jupiter-primary-color, #1976d2);
10135
+ color: white;
10136
+ border: none;
10137
+ padding: 8px 16px;
10138
+ border-radius: 4px;
10139
+ cursor: pointer;
10140
+ font-size: 14px;
10141
+ transition: background-color 0.2s ease;
10142
+ }
10143
+
10144
+ .picklist-button:hover:not(:disabled) {
10145
+ background: var(--jupiter-primary-color-dark, #1565c0);
10146
+ }
10147
+
10148
+ .picklist-button:disabled {
10149
+ opacity: 0.4;
10150
+ cursor: not-allowed;
10151
+ }
10152
+
10153
+ .picklist-count {
10154
+ padding: 8px 12px;
10155
+ font-size: 12px;
10156
+ color: var(--jupiter-text-secondary, #666);
10157
+ background: var(--jupiter-background-light, #f8f9fa);
10158
+ border-top: 1px solid var(--jupiter-border-color, #ddd);
10159
+ text-align: center;
10160
+ }
10161
+
10162
+ .btn-text {
10163
+ background: none;
10164
+ border: none;
10165
+ color: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
10166
+ padding: 10px 8px;
10167
+ font-size: 14px;
10168
+ font-weight: 500;
10169
+ font-family: inherit;
10170
+ cursor: pointer;
10171
+ margin-right: auto;
10172
+ border-radius: 4px;
10173
+ transition: background-color 0.2s ease;
10174
+ }
10175
+
10176
+ .btn-text:hover {
10177
+ background: var(--menuBgColorLighter, var(--jupiter-hover-background, #f5f5f5));
10178
+ }
10179
+ `;
10180
+ __decorateClass$2([
10181
+ n2({ type: Boolean, reflect: true })
10182
+ ], JupiterFilterRolesDialog.prototype, "open", 2);
10183
+ __decorateClass$2([
10184
+ n2({ type: Array })
10185
+ ], JupiterFilterRolesDialog.prototype, "availableRoles", 2);
10186
+ __decorateClass$2([
10187
+ n2({ type: Array })
10188
+ ], JupiterFilterRolesDialog.prototype, "selectedRoleIds", 2);
10189
+ __decorateClass$2([
10190
+ n2({ type: Object })
10191
+ ], JupiterFilterRolesDialog.prototype, "periodPreferences", 2);
10192
+ __decorateClass$2([
10193
+ n2({ type: String })
10194
+ ], JupiterFilterRolesDialog.prototype, "mode", 2);
10195
+ __decorateClass$2([
10196
+ n2({ type: Object })
10197
+ ], JupiterFilterRolesDialog.prototype, "hypercubeData", 2);
10198
+ __decorateClass$2([
10199
+ n2({ type: Boolean })
10200
+ ], JupiterFilterRolesDialog.prototype, "showFactsOnly", 2);
10201
+ __decorateClass$2([
10202
+ n2({ type: String })
10203
+ ], JupiterFilterRolesDialog.prototype, "conceptSearchText", 2);
10204
+ __decorateClass$2([
10205
+ r()
10206
+ ], JupiterFilterRolesDialog.prototype, "_tempSelectedRoles", 2);
10207
+ __decorateClass$2([
10208
+ r()
10209
+ ], JupiterFilterRolesDialog.prototype, "_searchQuery", 2);
10210
+ __decorateClass$2([
10211
+ r()
10212
+ ], JupiterFilterRolesDialog.prototype, "_filteredRoles", 2);
10213
+ __decorateClass$2([
10214
+ r()
10215
+ ], JupiterFilterRolesDialog.prototype, "_tempPeriodPreferences", 2);
10216
+ __decorateClass$2([
10217
+ r()
10218
+ ], JupiterFilterRolesDialog.prototype, "_selectedAvailableRole", 2);
10219
+ __decorateClass$2([
10220
+ r()
10221
+ ], JupiterFilterRolesDialog.prototype, "_selectedChosenRole", 2);
10222
+ __decorateClass$2([
10223
+ r()
10224
+ ], JupiterFilterRolesDialog.prototype, "_chosenSearchQuery", 2);
10225
+ __decorateClass$2([
10226
+ r()
10227
+ ], JupiterFilterRolesDialog.prototype, "_draggedRoleId", 2);
10228
+ __decorateClass$2([
10229
+ r()
10230
+ ], JupiterFilterRolesDialog.prototype, "_dragOverRoleId", 2);
10231
+ __decorateClass$2([
10232
+ r()
10233
+ ], JupiterFilterRolesDialog.prototype, "_chosenRoleOrder", 2);
10234
+ __decorateClass$2([
10235
+ r()
10236
+ ], JupiterFilterRolesDialog.prototype, "_showAdvancedFilter", 2);
10237
+ __decorateClass$2([
10238
+ r()
10239
+ ], JupiterFilterRolesDialog.prototype, "_showFactsOnly", 2);
10240
+ __decorateClass$2([
10241
+ r()
10242
+ ], JupiterFilterRolesDialog.prototype, "_conceptSearchText", 2);
10243
+ __decorateClass$2([
10244
+ r()
10245
+ ], JupiterFilterRolesDialog.prototype, "_collapsedRoles", 2);
10246
+ JupiterFilterRolesDialog = __decorateClass$2([
10247
+ t$1("jupiter-filter-roles-dialog")
10248
+ ], JupiterFilterRolesDialog);
10249
+ var __defProp$1 = Object.defineProperty;
10250
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
10251
+ var __decorateClass$1 = (decorators, target, key, kind) => {
10252
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
10253
+ for (var i2 = decorators.length - 1, decorator; i2 >= 0; i2--)
10254
+ if (decorator = decorators[i2])
10255
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
10256
+ if (kind && result)
10257
+ __defProp$1(target, key, result);
10258
+ return result;
10259
+ };
10260
+ let JupiterFormulaValidationDialog = class extends LitElement {
10261
+ constructor() {
10262
+ super(...arguments);
10263
+ this.results = [];
10264
+ this.open = false;
10265
+ this._collapsedGroups = /* @__PURE__ */ new Set();
10266
+ this._boundHandleKeydown = this._handleKeydown.bind(this);
10267
+ }
10268
+ connectedCallback() {
10269
+ super.connectedCallback();
10270
+ window.addEventListener("keydown", this._boundHandleKeydown);
10271
+ }
10272
+ disconnectedCallback() {
10273
+ window.removeEventListener("keydown", this._boundHandleKeydown);
10274
+ super.disconnectedCallback();
10275
+ }
10276
+ _handleKeydown(event) {
10277
+ if (this.open && event.key === "Escape") {
10278
+ this._handleClose();
10279
+ }
10280
+ }
10281
+ _handleClose() {
10282
+ this.open = false;
10283
+ this.dispatchEvent(new CustomEvent("dialog-cancel", { bubbles: true }));
10284
+ }
10285
+ _toggleGroup(roleLabel) {
10286
+ if (this._collapsedGroups.has(roleLabel)) {
10287
+ this._collapsedGroups.delete(roleLabel);
10288
+ } else {
10289
+ this._collapsedGroups.add(roleLabel);
10290
+ }
10291
+ this.requestUpdate();
10292
+ }
10293
+ _groupByRole(failures) {
10294
+ const grouped = failures.reduce((groups, result) => {
10295
+ var _a;
10296
+ (groups[_a = result.roleLabel] ?? (groups[_a] = [])).push(result);
10297
+ return groups;
10298
+ }, {});
10299
+ return Object.entries(grouped);
10300
+ }
10301
+ render() {
10302
+ if (!this.open)
10303
+ return html``;
10304
+ const failures = this.results.filter((result) => !result.skipped && !result.satisfied);
10305
+ const groups = this._groupByRole(failures);
10306
+ const summary = this.summary;
10307
+ return html`
10308
+ <div class="dialog" @click="${(e2) => e2.stopPropagation()}">
10309
+ <div class="dialog-header">
10310
+ <h2 class="dialog-title">${I18n.t("formulaValidation.dialogTitle")}</h2>
10311
+ ${summary ? html`
10312
+ <p class="dialog-summary">
10313
+ ${I18n.t("formulaValidation.summaryCount", { failed: summary.failed, total: summary.totalAssertions })}
10314
+ ${summary.failed > 0 ? html`— ${I18n.t("formulaValidation.summaryCountBreakdown", { warnings: summary.failedWarnings, errors: summary.failedErrors })}` : ""}
10315
+ </p>
10316
+ ` : ""}
10317
+ </div>
10318
+
10319
+ <div class="dialog-content">
10320
+ ${failures.length === 0 ? html`
10321
+ <div class="all-passed">
10322
+ <div class="all-passed-icon">✓</div>
10323
+ <div class="all-passed-text">${I18n.t("formulaValidation.allPassed")}</div>
10324
+ </div>
10325
+ ` : groups.map(([roleLabel, rows]) => {
10326
+ const collapsed = this._collapsedGroups.has(roleLabel);
10327
+ return html`
10328
+ <div class="role-group">
10329
+ <div class="role-group-header" @click="${() => this._toggleGroup(roleLabel)}">
10330
+ <span class="role-group-title">${roleLabel}</span>
10331
+ <span class="role-group-count">${rows.length}</span>
10332
+ <button class="role-group-toggle" type="button">${collapsed ? "▸" : "▾"}</button>
10333
+ </div>
10334
+ ${collapsed ? "" : html`
10335
+ <div class="role-group-rows">
10336
+ ${rows.map((result) => html`
10337
+ <div class="assertion-row">
10338
+ <span class="severity-badge ${result.severity === "ERROR" ? "error" : "warning"}">
10339
+ ${result.severity === "ERROR" ? I18n.t("formulaValidation.severityError") : I18n.t("formulaValidation.severityWarning")}
10340
+ </span>
10341
+ <span class="assertion-message">${result.message}</span>
10342
+ </div>
10343
+ `)}
10344
+ </div>
10345
+ `}
10346
+ </div>
10347
+ `;
10348
+ })}
10349
+ </div>
10350
+
10351
+ <div class="dialog-actions">
10352
+ <button class="btn btn-primary" @click="${this._handleClose}">
10353
+ ${I18n.t("formulaValidation.close")}
10354
+ </button>
10355
+ </div>
10356
+ </div>
10357
+ `;
10358
+ }
10359
+ };
10360
+ JupiterFormulaValidationDialog.styles = css`
10361
+ :host {
10362
+ position: fixed;
10363
+ top: 0;
10364
+ left: 0;
10365
+ width: 100%;
10366
+ height: 100%;
10367
+ background: rgba(0, 0, 0, 0.5);
10368
+ z-index: 1100;
10369
+ display: flex;
10370
+ align-items: center;
10371
+ justify-content: center;
10372
+ opacity: 0;
10373
+ visibility: hidden;
10374
+ transition: opacity 0.3s ease, visibility 0.3s ease;
10375
+ }
10376
+
10377
+ :host([open]) {
10378
+ opacity: 1;
10379
+ visibility: visible;
10380
+ }
10381
+
10382
+ .dialog {
10383
+ background: var(--bg-color-2, var(--jupiter-card-background, #fff));
10384
+ border-radius: 8px;
10385
+ padding: 24px;
10386
+ min-width: 420px;
10387
+ max-width: 640px;
10388
+ width: 90vw;
10389
+ max-height: 90vh;
10390
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
10391
+ transform: scale(0.9);
10392
+ transition: transform 0.3s ease;
10393
+ display: flex;
10394
+ flex-direction: column;
10395
+ }
10396
+
10397
+ :host([open]) .dialog {
10398
+ transform: scale(1);
10399
+ }
10400
+
10401
+ .dialog-header {
10402
+ margin-bottom: 16px;
10403
+ flex-shrink: 0;
10404
+ }
10405
+
10406
+ .dialog-title {
10407
+ font-size: 20px;
10408
+ font-weight: 600;
10409
+ color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
10410
+ margin: 0 0 8px 0;
10411
+ }
10412
+
10413
+ .dialog-summary {
10414
+ font-size: 14px;
10415
+ color: var(--jupiter-text-secondary, #666);
10416
+ margin: 0;
10417
+ }
10418
+
10419
+ /* JDF-035 dialog-overflow fix: min-height: 0 is required on a flex-column scrollable child,
10420
+ otherwise the browser lets it grow past the dialog's max-height instead of scrolling. */
10421
+ .dialog-content {
10422
+ margin-bottom: 20px;
10423
+ flex: 1;
10424
+ overflow-y: auto;
10425
+ min-height: 0;
10426
+ }
10427
+
10428
+ .all-passed {
10429
+ display: flex;
10430
+ flex-direction: column;
10431
+ align-items: center;
10432
+ gap: 12px;
10433
+ padding: 32px 16px;
10434
+ color: var(--jupiter-text-primary, #333);
10435
+ }
10436
+
10437
+ .all-passed-icon {
10438
+ width: 40px;
10439
+ height: 40px;
10440
+ border-radius: 50%;
10441
+ background: var(--jupiter-success-color, #4caf50);
10442
+ color: #fff;
10443
+ display: flex;
10444
+ align-items: center;
10445
+ justify-content: center;
10446
+ font-size: 22px;
10447
+ font-weight: 700;
10448
+ }
10449
+
10450
+ .all-passed-text {
10451
+ font-size: 15px;
10452
+ font-weight: 500;
9098
10453
  }
9099
10454
 
9100
- .picklist-list {
9101
- flex: 1;
9102
- overflow-y: auto;
9103
- padding: 8px;
10455
+ .role-group {
10456
+ border: 1px solid var(--jupiter-border-color, #ddd);
10457
+ border-radius: 4px;
10458
+ margin-bottom: 12px;
10459
+ overflow: hidden;
9104
10460
  }
9105
10461
 
9106
- .picklist-item {
10462
+ .role-group-header {
10463
+ display: flex;
10464
+ align-items: center;
10465
+ justify-content: space-between;
10466
+ gap: 8px;
9107
10467
  padding: 10px 12px;
9108
- margin-bottom: 4px;
9109
- border: 1px solid var(--jupiter-border-color, #ddd);
9110
- border-radius: 4px;
10468
+ background: var(--jupiter-card-background, #fafafa);
9111
10469
  cursor: pointer;
9112
- transition: all 0.2s ease;
9113
- background: var(--jupiter-background, #fff);
10470
+ user-select: none;
9114
10471
  }
9115
10472
 
9116
- .picklist-item:hover {
9117
- background: var(--jupiter-hover-background, #f5f5f5);
9118
- border-color: var(--jupiter-primary-color, #1976d2);
10473
+ .role-group-title {
10474
+ font-weight: 600;
10475
+ font-size: 14px;
10476
+ color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
9119
10477
  }
9120
10478
 
9121
- .picklist-item.selected {
9122
- background: var(--jupiter-primary-color, #1976d2);
9123
- color: white;
9124
- border-color: var(--jupiter-primary-color, #1976d2);
10479
+ .role-group-count {
10480
+ font-size: 12px;
10481
+ color: var(--jupiter-text-secondary, #666);
9125
10482
  }
9126
10483
 
9127
- .picklist-item.dragging {
9128
- opacity: 0.5;
9129
- cursor: move;
10484
+ .role-group-toggle {
10485
+ background: none;
10486
+ border: none;
10487
+ cursor: pointer;
10488
+ font-size: 12px;
10489
+ color: var(--jupiter-text-secondary, #666);
10490
+ padding: 0 4px;
9130
10491
  }
9131
10492
 
9132
- .picklist-item.drag-over {
9133
- border-top: 3px solid var(--jupiter-primary-color, #1976d2);
9134
- margin-top: 3px;
10493
+ .role-group-rows {
10494
+ padding: 4px 12px 8px 12px;
9135
10495
  }
9136
10496
 
9137
- .picklist-item[draggable="true"] {
9138
- cursor: move;
10497
+ .assertion-row {
10498
+ display: flex;
10499
+ align-items: flex-start;
10500
+ gap: 8px;
10501
+ padding: 8px 0;
10502
+ border-top: 1px solid var(--jupiter-border-color, #eee);
9139
10503
  }
9140
10504
 
9141
- .picklist-item-label {
9142
- font-weight: 500;
9143
- font-size: 14px;
9144
- margin-bottom: 4px;
10505
+ .assertion-row:first-child {
10506
+ border-top: none;
9145
10507
  }
9146
10508
 
9147
- .picklist-item-value {
10509
+ .severity-badge {
10510
+ flex-shrink: 0;
10511
+ display: inline-flex;
10512
+ align-items: center;
10513
+ gap: 4px;
10514
+ padding: 2px 8px;
10515
+ border-radius: 10px;
9148
10516
  font-size: 11px;
9149
- opacity: 0.8;
9150
- word-break: break-all;
9151
- }
9152
-
9153
- .picklist-controls {
9154
- display: flex;
9155
- flex-direction: column;
9156
- justify-content: center;
9157
- gap: 8px;
9158
- padding: 0 8px;
10517
+ font-weight: 700;
10518
+ color: #fff;
10519
+ white-space: nowrap;
9159
10520
  }
9160
10521
 
9161
- .picklist-button {
9162
- background: var(--jupiter-primary-color, #1976d2);
9163
- color: white;
9164
- border: none;
9165
- padding: 8px 16px;
9166
- border-radius: 4px;
9167
- cursor: pointer;
9168
- font-size: 14px;
9169
- transition: background-color 0.2s ease;
10522
+ .severity-badge.warning {
10523
+ background: var(--jupiter-warning-color, #ff9800);
9170
10524
  }
9171
10525
 
9172
- .picklist-button:hover:not(:disabled) {
9173
- background: var(--jupiter-primary-color-dark, #1565c0);
10526
+ .severity-badge.error {
10527
+ background: var(--jupiter-error-color, #d32f2f);
9174
10528
  }
9175
10529
 
9176
- .picklist-button:disabled {
9177
- opacity: 0.4;
9178
- cursor: not-allowed;
10530
+ .assertion-message {
10531
+ font-size: 13px;
10532
+ color: var(--primaryTextColor, var(--jupiter-text-primary, #333));
10533
+ line-height: 1.4;
9179
10534
  }
9180
10535
 
9181
- .picklist-count {
9182
- padding: 8px 12px;
9183
- font-size: 12px;
9184
- color: var(--jupiter-text-secondary, #666);
9185
- background: var(--jupiter-background-light, #f8f9fa);
10536
+ .dialog-actions {
10537
+ display: flex;
10538
+ gap: 12px;
10539
+ justify-content: flex-end;
10540
+ flex-shrink: 0;
9186
10541
  border-top: 1px solid var(--jupiter-border-color, #ddd);
9187
- text-align: center;
10542
+ padding-top: 16px;
10543
+ margin-top: 16px;
9188
10544
  }
9189
10545
 
9190
- .btn-text {
9191
- background: none;
10546
+ .btn {
10547
+ padding: 10px 20px;
9192
10548
  border: none;
9193
- color: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
9194
- padding: 10px 8px;
10549
+ border-radius: 4px;
9195
10550
  font-size: 14px;
9196
10551
  font-weight: 500;
9197
- font-family: inherit;
9198
10552
  cursor: pointer;
9199
- margin-right: auto;
9200
- border-radius: 4px;
9201
10553
  transition: background-color 0.2s ease;
9202
10554
  }
9203
10555
 
9204
- .btn-text:hover {
9205
- background: var(--menuBgColorLighter, var(--jupiter-hover-background, #f5f5f5));
10556
+ .btn-primary {
10557
+ background: var(--buttonBgColor, var(--jupiter-primary-color, #1976d2));
10558
+ color: var(--buttonTextColor, white);
10559
+ }
10560
+
10561
+ .btn-primary:hover {
10562
+ opacity: 0.9;
9206
10563
  }
9207
10564
  `;
9208
- __decorateClass$1([
9209
- n2({ type: Boolean, reflect: true })
9210
- ], JupiterFilterRolesDialog.prototype, "open", 2);
9211
10565
  __decorateClass$1([
9212
10566
  n2({ type: Array })
9213
- ], JupiterFilterRolesDialog.prototype, "availableRoles", 2);
9214
- __decorateClass$1([
9215
- n2({ type: Array })
9216
- ], JupiterFilterRolesDialog.prototype, "selectedRoleIds", 2);
10567
+ ], JupiterFormulaValidationDialog.prototype, "results", 2);
9217
10568
  __decorateClass$1([
9218
10569
  n2({ type: Object })
9219
- ], JupiterFilterRolesDialog.prototype, "periodPreferences", 2);
9220
- __decorateClass$1([
9221
- n2({ type: String })
9222
- ], JupiterFilterRolesDialog.prototype, "mode", 2);
9223
- __decorateClass$1([
9224
- n2({ type: Object })
9225
- ], JupiterFilterRolesDialog.prototype, "hypercubeData", 2);
9226
- __decorateClass$1([
9227
- n2({ type: Boolean })
9228
- ], JupiterFilterRolesDialog.prototype, "showFactsOnly", 2);
9229
- __decorateClass$1([
9230
- n2({ type: String })
9231
- ], JupiterFilterRolesDialog.prototype, "conceptSearchText", 2);
9232
- __decorateClass$1([
9233
- r()
9234
- ], JupiterFilterRolesDialog.prototype, "_tempSelectedRoles", 2);
9235
- __decorateClass$1([
9236
- r()
9237
- ], JupiterFilterRolesDialog.prototype, "_searchQuery", 2);
9238
- __decorateClass$1([
9239
- r()
9240
- ], JupiterFilterRolesDialog.prototype, "_filteredRoles", 2);
9241
- __decorateClass$1([
9242
- r()
9243
- ], JupiterFilterRolesDialog.prototype, "_tempPeriodPreferences", 2);
9244
- __decorateClass$1([
9245
- r()
9246
- ], JupiterFilterRolesDialog.prototype, "_selectedAvailableRole", 2);
9247
- __decorateClass$1([
9248
- r()
9249
- ], JupiterFilterRolesDialog.prototype, "_selectedChosenRole", 2);
9250
- __decorateClass$1([
9251
- r()
9252
- ], JupiterFilterRolesDialog.prototype, "_chosenSearchQuery", 2);
9253
- __decorateClass$1([
9254
- r()
9255
- ], JupiterFilterRolesDialog.prototype, "_draggedRoleId", 2);
9256
- __decorateClass$1([
9257
- r()
9258
- ], JupiterFilterRolesDialog.prototype, "_dragOverRoleId", 2);
10570
+ ], JupiterFormulaValidationDialog.prototype, "summary", 2);
9259
10571
  __decorateClass$1([
9260
- r()
9261
- ], JupiterFilterRolesDialog.prototype, "_chosenRoleOrder", 2);
9262
- __decorateClass$1([
9263
- r()
9264
- ], JupiterFilterRolesDialog.prototype, "_showAdvancedFilter", 2);
9265
- __decorateClass$1([
9266
- r()
9267
- ], JupiterFilterRolesDialog.prototype, "_showFactsOnly", 2);
9268
- __decorateClass$1([
9269
- r()
9270
- ], JupiterFilterRolesDialog.prototype, "_conceptSearchText", 2);
10572
+ n2({ type: Boolean, reflect: true })
10573
+ ], JupiterFormulaValidationDialog.prototype, "open", 2);
9271
10574
  __decorateClass$1([
9272
10575
  r()
9273
- ], JupiterFilterRolesDialog.prototype, "_collapsedRoles", 2);
9274
- JupiterFilterRolesDialog = __decorateClass$1([
9275
- t$1("jupiter-filter-roles-dialog")
9276
- ], JupiterFilterRolesDialog);
10576
+ ], JupiterFormulaValidationDialog.prototype, "_collapsedGroups", 2);
10577
+ JupiterFormulaValidationDialog = __decorateClass$1([
10578
+ t$1("jupiter-formula-validation-dialog")
10579
+ ], JupiterFormulaValidationDialog);
9277
10580
  var __defProp = Object.defineProperty;
9278
10581
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9279
10582
  var __decorateClass = (decorators, target, key, kind) => {
@@ -9292,6 +10595,8 @@ let JupiterDynamicForm = class extends LitElement {
9292
10595
  this.initialData = {};
9293
10596
  this.disabled = false;
9294
10597
  this.readonly = false;
10598
+ this.calculationEnabled = false;
10599
+ this.formulaEnabled = false;
9295
10600
  this.periodStartDate = "2025-01-01";
9296
10601
  this.periodEndDate = "2025-12-31";
9297
10602
  this.language = "en";
@@ -9311,6 +10616,7 @@ let JupiterDynamicForm = class extends LitElement {
9311
10616
  this._unitData = {};
9312
10617
  this._preservedUnitData = {};
9313
10618
  this._decimalsData = {};
10619
+ this._totalManuallyEditedData = {};
9314
10620
  this._effectiveMasterData = void 0;
9315
10621
  this._typedMemberData = {};
9316
10622
  this._preservedTypedMemberData = {};
@@ -9343,6 +10649,9 @@ let JupiterDynamicForm = class extends LitElement {
9343
10649
  this._contextMenuY = 0;
9344
10650
  this._contextMenuRoleId = null;
9345
10651
  this._validationStatus = "idle";
10652
+ this._formulaValidationResults = [];
10653
+ this._showFormulaValidationDialog = false;
10654
+ this._formulaValidationService = new FormulaValidationService();
9346
10655
  this._skipDraftLoading = false;
9347
10656
  this._skipPeriodPreferencesRestore = false;
9348
10657
  this._autoSaveTimer = null;
@@ -9374,6 +10683,24 @@ let JupiterDynamicForm = class extends LitElement {
9374
10683
  this.addEventListener("calculation-mismatch-changed", (e2) => {
9375
10684
  this._handleCalculationMismatchChanged(e2);
9376
10685
  });
10686
+ this.addEventListener("total-manually-edited", (e2) => {
10687
+ const { conceptId, columnId } = e2.detail;
10688
+ const updated = { ...this._totalManuallyEditedData };
10689
+ updated[conceptId] = { ...updated[conceptId], [columnId]: true };
10690
+ this._totalManuallyEditedData = updated;
10691
+ });
10692
+ this.addEventListener("calculation-total-auto-populated", (e2) => {
10693
+ const { conceptId, columnId, value } = e2.detail;
10694
+ const oldValue = this._writeFormDataValue(conceptId, columnId, value);
10695
+ this._touched.add(`${conceptId}-${columnId}`);
10696
+ this._dirty = true;
10697
+ this._validateForm();
10698
+ this.requestUpdate();
10699
+ this.dispatchEvent(new CustomEvent("field-change", {
10700
+ detail: { fieldId: conceptId, conceptId, columnId, value, oldValue, source: "auto-calculated" },
10701
+ bubbles: true
10702
+ }));
10703
+ });
9377
10704
  document.addEventListener("click", () => {
9378
10705
  if (this._showRoleContextMenu) {
9379
10706
  this._showRoleContextMenu = false;
@@ -10048,6 +11375,12 @@ let JupiterDynamicForm = class extends LitElement {
10048
11375
  const roleCount = this._getRoleIdsArray().length;
10049
11376
  console.log(`🚫 Filter dialog cancelled. Current selection: ${roleCount}/${this._allSections.length}`);
10050
11377
  }
11378
+ // JDF-050: closes the formula-validation results dialog opened by JDF-049's pre-flight.
11379
+ // Purely a visibility toggle — submit already proceeded on the click that opened it
11380
+ // (non-blocking, per the corrected JDF-043 Q4 decision), so there is nothing to resume here.
11381
+ _handleFormulaValidationDialogCancel() {
11382
+ this._showFormulaValidationDialog = false;
11383
+ }
10051
11384
  _handleRoleFilterApply(event) {
10052
11385
  var _a, _b;
10053
11386
  const { selectedRoleIds, periodPreferences } = event.detail;
@@ -10075,7 +11408,8 @@ let JupiterDynamicForm = class extends LitElement {
10075
11408
  this._repeatCounts,
10076
11409
  this._decimalsData,
10077
11410
  void 0,
10078
- this._getHiddenColumnsForMetadata()
11411
+ this._getHiddenColumnsForMetadata(),
11412
+ this._totalManuallyEditedData
10079
11413
  );
10080
11414
  this._draftStorageService.saveDraft(currentFormData, currentMetadata);
10081
11415
  console.log("✅ Current form data saved to draft storage with NEW preferences");
@@ -10184,9 +11518,13 @@ let JupiterDynamicForm = class extends LitElement {
10184
11518
  this._calculationWarnings = next;
10185
11519
  this._validateForm();
10186
11520
  }
10187
- _handleFieldChange(event) {
10188
- const { fieldId, conceptId, columnId, value } = event.detail;
10189
- console.log(`📝 Field change: conceptId=${conceptId}, columnId=${columnId}, value=${value}, fieldId=${fieldId}`);
11521
+ /**
11522
+ * JDF-058: single source of truth for writing a value into `_formData` with the immutable-update
11523
+ * pattern Lit reactivity needs shared by a real user keystroke (`_handleFieldChange`) and the
11524
+ * auto-populate-totals `calculation-total-auto-populated` handler, so the update logic isn't
11525
+ * duplicated. Returns the value that was previously stored, for event `oldValue` fields.
11526
+ */
11527
+ _writeFormDataValue(conceptId, columnId, value) {
10190
11528
  const updatedFormData = { ...this._formData };
10191
11529
  if (!updatedFormData[conceptId]) {
10192
11530
  updatedFormData[conceptId] = {};
@@ -10194,6 +11532,12 @@ let JupiterDynamicForm = class extends LitElement {
10194
11532
  const oldValue = updatedFormData[conceptId][columnId];
10195
11533
  updatedFormData[conceptId] = { ...updatedFormData[conceptId], [columnId]: value };
10196
11534
  this._formData = updatedFormData;
11535
+ return oldValue;
11536
+ }
11537
+ _handleFieldChange(event) {
11538
+ const { fieldId, conceptId, columnId, value } = event.detail;
11539
+ console.log(`📝 Field change: conceptId=${conceptId}, columnId=${columnId}, value=${value}, fieldId=${fieldId}`);
11540
+ const oldValue = this._writeFormDataValue(conceptId, columnId, value);
10197
11541
  console.log(`💾 Updated formData[${conceptId}]:`, this._formData[conceptId]);
10198
11542
  this._touched.add(`${conceptId}-${columnId}`);
10199
11543
  this._dirty = true;
@@ -11175,6 +12519,7 @@ let JupiterDynamicForm = class extends LitElement {
11175
12519
  return;
11176
12520
  }
11177
12521
  this._correctLegacyPeriodStartInstantDates();
12522
+ this._runFormulaValidationPreflight();
11178
12523
  const submissionData = this._generateSubmissionData();
11179
12524
  console.log("📊 Form Submission Data:", JSON.stringify(submissionData, null, 2));
11180
12525
  console.log("📊 Submission Data Summary:");
@@ -11196,6 +12541,58 @@ let JupiterDynamicForm = class extends LitElement {
11196
12541
  this._submitDisabled = false;
11197
12542
  }, 1e3);
11198
12543
  }
12544
+ /**
12545
+ * JDF-049: runs FormulaValidationService against the live form state and stores the results
12546
+ * for the results dialog (JDF-050). Graceful no-op — per JDF-048's gating and this ticket's
12547
+ * "no Validate-formulas affordance/effect when formula data is absent" requirement — when
12548
+ * `formulaEnabled` is off or `xbrlInput.formula` has no roles. Never blocks the caller; always
12549
+ * returns before any resolution work is attempted when gated off (JDF-048's "zero evaluation
12550
+ * cost when off" requirement).
12551
+ */
12552
+ _runFormulaValidationPreflight() {
12553
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
12554
+ if (!this.formulaEnabled)
12555
+ return;
12556
+ const formulaRoles = (_c = (_b = (_a = this.xbrlInput) == null ? void 0 : _a.formula) == null ? void 0 : _b[0]) == null ? void 0 : _c.roles;
12557
+ if (!formulaRoles || formulaRoles.length === 0)
12558
+ return;
12559
+ const ctx = buildFormulaResolutionContext(
12560
+ this._formData,
12561
+ this._mergeAllSectionColumns(),
12562
+ ((_f = (_e = (_d = this.xbrlInput) == null ? void 0 : _d.presentation) == null ? void 0 : _e[0]) == null ? void 0 : _f.roles) || [],
12563
+ (_i = (_h = (_g = this.xbrlInput) == null ? void 0 : _g.hypercubes) == null ? void 0 : _h[0]) == null ? void 0 : _i.roles,
12564
+ this.periodStartDate,
12565
+ this.periodEndDate
12566
+ );
12567
+ const results = this._formulaValidationService.evaluate(this.xbrlInput, ctx, this.language);
12568
+ const summary = this._formulaValidationService.summarize(results);
12569
+ this._formulaValidationResults = results;
12570
+ this._formulaValidationSummary = summary;
12571
+ const hasFailures = results.some((result) => !result.skipped && !result.satisfied);
12572
+ if (hasFailures) {
12573
+ this._showFormulaValidationDialog = true;
12574
+ }
12575
+ this.dispatchEvent(new CustomEvent("formula-validation-complete", {
12576
+ detail: { results, summary },
12577
+ bubbles: true,
12578
+ composed: true
12579
+ }));
12580
+ }
12581
+ /**
12582
+ * Formula assertions can reference concepts/dimensions from any disclosure role, but
12583
+ * `FormColumn`s (especially dimension columns) are tracked per-section (`section.columns`),
12584
+ * with `this._columns` as the shared/default fallback (see the pattern at L1398/L2814/L5284).
12585
+ * `_formData` itself is already global (keyed by conceptId/columnId, no section nesting), so a
12586
+ * single merged, deduplicated-by-id column list is the correct match for it.
12587
+ */
12588
+ _mergeAllSectionColumns() {
12589
+ const merged = /* @__PURE__ */ new Map();
12590
+ this._columns.forEach((column2) => merged.set(column2.id, column2));
12591
+ this._allSections.forEach((section2) => {
12592
+ (section2.columns || []).forEach((column2) => merged.set(column2.id, column2));
12593
+ });
12594
+ return Array.from(merged.values());
12595
+ }
11199
12596
  _handleSaveDraft(source = "manual") {
11200
12597
  console.log(`🔵 [Save Draft] Checking for errors...`);
11201
12598
  console.log(`🔵 [Save Draft] _xbrlFormErrors.length: ${this._xbrlFormErrors.length}`);
@@ -11233,7 +12630,8 @@ let JupiterDynamicForm = class extends LitElement {
11233
12630
  this._repeatCounts,
11234
12631
  this._decimalsData,
11235
12632
  roleCompletedStates,
11236
- this._getHiddenColumnsForMetadata()
12633
+ this._getHiddenColumnsForMetadata(),
12634
+ this._totalManuallyEditedData
11237
12635
  );
11238
12636
  const draftPayloadSnapshot = JSON.stringify({
11239
12637
  draftData,
@@ -11375,6 +12773,10 @@ let JupiterDynamicForm = class extends LitElement {
11375
12773
  );
11376
12774
  console.log("🔄 Restored hidden columns for", this._hiddenColumnIds.size, "sections");
11377
12775
  }
12776
+ if (metadata.totalManuallyEditedData) {
12777
+ this._totalManuallyEditedData = metadata.totalManuallyEditedData;
12778
+ console.log("🔄 Restored total manually-edited data:", Object.keys(this._totalManuallyEditedData).length, "concepts");
12779
+ }
11378
12780
  if (metadata.periodPreferences) {
11379
12781
  if (this._skipPeriodPreferencesRestore) {
11380
12782
  console.log("⏭️ Skipping period preferences restoration - using new filter selections");
@@ -11454,6 +12856,7 @@ let JupiterDynamicForm = class extends LitElement {
11454
12856
  });
11455
12857
  this._formData = { ...this._formData, ...restoredFormData };
11456
12858
  console.log(`🔄 Restored ${Object.keys(restoredFormData).length} concepts with data`);
12859
+ this._seedLegacyManuallyEditedTotals(restoredFormData);
11457
12860
  this._initializeGlobalDecimalsFromRoundingData();
11458
12861
  this._periodData = {
11459
12862
  ...this._periodData,
@@ -11587,6 +12990,47 @@ let JupiterDynamicForm = class extends LitElement {
11587
12990
  });
11588
12991
  console.log(`✅ Field population complete: ${populatedCount} populated, ${notFoundCount} not found`);
11589
12992
  }
12993
+ /**
12994
+ * JDF-058: seed the manual-edit dirty flag for any total whose value predates this feature, so
12995
+ * upgrading never silently recomputes a customer's pre-existing, already-filed totals. Walks
12996
+ * `_allSections` (the full pool, including currently-hidden/filtered-out sections — a hidden
12997
+ * total's pre-existing value must stay protected the moment its section becomes visible again),
12998
+ * not just the currently-selected roles. Idempotent: only seeds a cell that has no explicit
12999
+ * `totalManuallyEditedData` entry already (i.e. `metadata.totalManuallyEditedData` didn't cover
13000
+ * it), so re-running on an already-seeded draft is a no-op. Does not touch `restoredFormData`.
13001
+ */
13002
+ _seedLegacyManuallyEditedTotals(restoredFormData) {
13003
+ const seeded = { ...this._totalManuallyEditedData };
13004
+ let changed = false;
13005
+ const walk = (concepts) => {
13006
+ var _a;
13007
+ for (const concept of concepts) {
13008
+ if (isTotalLabel$1(concept.preferredLabel)) {
13009
+ const values = restoredFormData[concept.id];
13010
+ if (values) {
13011
+ for (const columnId of Object.keys(values)) {
13012
+ const value = values[columnId];
13013
+ const isBlank = value === null || value === void 0 || value === "";
13014
+ if (!isBlank && ((_a = seeded[concept.id]) == null ? void 0 : _a[columnId]) === void 0) {
13015
+ seeded[concept.id] = { ...seeded[concept.id] || {}, [columnId]: true };
13016
+ changed = true;
13017
+ }
13018
+ }
13019
+ }
13020
+ }
13021
+ if (concept.children && concept.children.length > 0) {
13022
+ walk(concept.children);
13023
+ }
13024
+ }
13025
+ };
13026
+ for (const section2 of this._allSections) {
13027
+ walk(section2.concepts || []);
13028
+ }
13029
+ if (changed) {
13030
+ this._totalManuallyEditedData = seeded;
13031
+ console.log("🔒 [JDF-058] Seeded manual-edit protection for pre-existing total values in legacy draft");
13032
+ }
13033
+ }
11590
13034
  _restoreCustomColumns(customColumns) {
11591
13035
  const columnsBySection = /* @__PURE__ */ new Map();
11592
13036
  customColumns.forEach((col) => {
@@ -13035,6 +14479,8 @@ let JupiterDynamicForm = class extends LitElement {
13035
14479
  .masterData="${this._effectiveMasterData}"
13036
14480
  .periodStartDate="${this.periodStartDate}"
13037
14481
  .periodEndDate="${this.periodEndDate}"
14482
+ .calculationEnabled="${this.calculationEnabled}"
14483
+ .totalManuallyEditedData="${this._totalManuallyEditedData}"
13038
14484
  @field-change="${this._handleFieldChange}"
13039
14485
  @period-change="${this._handlePeriodChange}"
13040
14486
  @typed-member-change="${this._handleTypedMemberChange}"
@@ -13182,6 +14628,8 @@ let JupiterDynamicForm = class extends LitElement {
13182
14628
  .masterData="${this._effectiveMasterData}"
13183
14629
  .periodStartDate="${this.periodStartDate}"
13184
14630
  .periodEndDate="${this.periodEndDate}"
14631
+ .calculationEnabled="${this.calculationEnabled}"
14632
+ .totalManuallyEditedData="${this._totalManuallyEditedData}"
13185
14633
  @field-change="${this._handleFieldChange}"
13186
14634
  @typed-member-change="${this._handleTypedMemberChange}"
13187
14635
  @add-concept-repeat="${this._handleAddConceptRepeat}"
@@ -13604,7 +15052,18 @@ let JupiterDynamicForm = class extends LitElement {
13604
15052
  @click="${this._handleFilterDialogCancel}"
13605
15053
  ></jupiter-filter-roles-dialog>
13606
15054
  ` : ""}
13607
-
15055
+
15056
+ <!-- Formula Validation Results Dialog (JDF-050) -->
15057
+ ${this._showFormulaValidationDialog ? html`
15058
+ <jupiter-formula-validation-dialog
15059
+ ?open="${this._showFormulaValidationDialog}"
15060
+ .results="${this._formulaValidationResults}"
15061
+ .summary="${this._formulaValidationSummary}"
15062
+ @dialog-cancel="${this._handleFormulaValidationDialogCancel}"
15063
+ @click="${this._handleFormulaValidationDialogCancel}"
15064
+ ></jupiter-formula-validation-dialog>
15065
+ ` : ""}
15066
+
13608
15067
  <!-- Validation Error Popup -->
13609
15068
  ${this._showErrorPopup ? html`
13610
15069
  <div class="error-popup-overlay" @click="${() => {
@@ -14518,6 +15977,12 @@ __decorateClass([
14518
15977
  __decorateClass([
14519
15978
  n2({ type: Boolean })
14520
15979
  ], JupiterDynamicForm.prototype, "readonly", 2);
15980
+ __decorateClass([
15981
+ n2({ type: Boolean, attribute: "calculation-enabled" })
15982
+ ], JupiterDynamicForm.prototype, "calculationEnabled", 2);
15983
+ __decorateClass([
15984
+ n2({ type: Boolean, attribute: "formula-enabled" })
15985
+ ], JupiterDynamicForm.prototype, "formulaEnabled", 2);
14521
15986
  __decorateClass([
14522
15987
  n2({ type: String })
14523
15988
  ], JupiterDynamicForm.prototype, "periodStartDate", 2);
@@ -14590,6 +16055,9 @@ __decorateClass([
14590
16055
  __decorateClass([
14591
16056
  r()
14592
16057
  ], JupiterDynamicForm.prototype, "_decimalsData", 2);
16058
+ __decorateClass([
16059
+ r()
16060
+ ], JupiterDynamicForm.prototype, "_totalManuallyEditedData", 2);
14593
16061
  __decorateClass([
14594
16062
  r()
14595
16063
  ], JupiterDynamicForm.prototype, "_effectiveMasterData", 2);
@@ -14689,6 +16157,15 @@ __decorateClass([
14689
16157
  __decorateClass([
14690
16158
  r()
14691
16159
  ], JupiterDynamicForm.prototype, "_validationStatus", 2);
16160
+ __decorateClass([
16161
+ r()
16162
+ ], JupiterDynamicForm.prototype, "_formulaValidationResults", 2);
16163
+ __decorateClass([
16164
+ r()
16165
+ ], JupiterDynamicForm.prototype, "_formulaValidationSummary", 2);
16166
+ __decorateClass([
16167
+ r()
16168
+ ], JupiterDynamicForm.prototype, "_showFormulaValidationDialog", 2);
14692
16169
  JupiterDynamicForm = __decorateClass([
14693
16170
  t$1("jupiter-dynamic-form")
14694
16171
  ], JupiterDynamicForm);
@@ -14703,6 +16180,7 @@ export {
14703
16180
  JupiterFilterRolesDialog,
14704
16181
  JupiterFormField,
14705
16182
  JupiterFormSection,
16183
+ JupiterFormulaValidationDialog,
14706
16184
  TYPE_INPUT_MAP,
14707
16185
  XBRLValidator,
14708
16186
  collectEnumerationsFromChain,