All files / libs/payout-calculation/statements/shared/src/lib/rule-charts statement-rule-chart.ts

88.07% Statements 133/151
86.2% Branches 25/29
60% Functions 3/5
88.07% Lines 133/151

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 1521x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 7x 7x 7x 7x 1x 1x 6x 6x 6x 6x 6x 6x 7x 1x 1x 5x 5x 7x 7x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x 4x 4x 7x 1x 1x 1x 1x 3x 3x 3x 3x 3x 7x 1x 1x 2x 2x 2x 2x 2x 7x         2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 7x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                         1x 1x     1x  
import { isNil } from 'lodash-es';
import { isAccessorNode, isFunctionNode } from 'mathjs';
 
import { commonMathJs } from '@amalia/amalia-lang/amalia-mathjs';
import {
  type AccessorNode,
  type FunctionNode,
  type MathNode,
  type SymbolNode,
} from '@amalia/amalia-lang/formula/evaluate/shared';
import { type VariableDefinition, type VariableFormatOptionsTable } from '@amalia/amalia-lang/tokens/types';
import { type Statement } from '@amalia/core/types';
import { FormatsEnum } from '@amalia/data-capture/fields/types';
import {
  isExtractedChartConfigurationSuccess,
  type CommissionTableRow,
  type ExtractedChartConfiguration,
  type ExtractedChartConfigurationSuccess,
} from '@amalia/payout-calculation/statements/types';
import { type ComputedVariable } from '@amalia/payout-calculation/types';
import { type PlanRuleChart } from '@amalia/payout-definition/plans/types';
 
const getVariableMachineNameFromAccessorNode = (node: MathNode): string | null =>
  isAccessorNode(node) && 'value' in node.index.dimensions[0] && typeof node.index.dimensions[0].value === 'string'
    ? node.index.dimensions[0].value
    : null;
 
const traverseNodes = (nodes: MathNode): MathNode[] => {
  const values: MathNode[] = [];
  nodes.traverse((node) => {
    if (!commonMathJs.isArrayNode(node)) {
      values.push(node);
    }
  });
 
  return values;
};
 
/**
 * Extract chart configuration from chart and return all values
 * @param statement
 * @param chart
 */
export const extractChartConfiguration = (
  statement: Pick<Statement, 'results'>,
  chart: PlanRuleChart,
): ExtractedChartConfiguration => {
  if (!chart.configuration.targetAchievementVariableId) {
    return { error: `Target Achievement chart ${chart.name} has no target achievement variables set up!` };
  }
 
  // Get the target achievement variable from statement results
  const targetAchievementVariable = Object.values(statement.results.definitions.variables).find(
    (v) => v.id === chart.configuration.targetAchievementVariableId,
  );
 
  if (!targetAchievementVariable) {
    return { error: `Unable to retrieve the variable for the target achievement chart ${chart.name}` };
  }
 
  // Try to get the FN node from its formula
  let fnNodeAsMathNode: MathNode | undefined = commonMathJs.parse(targetAchievementVariable.formula ?? '');
  if (!isFunctionNode(fnNodeAsMathNode) || !['LINEAR', 'TIER'].includes(fnNodeAsMathNode.fn.name)) {
    // If we don't have it directly, it's maybe later in the formula
    const nodesInFormula = traverseNodes(fnNodeAsMathNode);
    fnNodeAsMathNode = nodesInFormula.find((f) => isFunctionNode(f) && ['LINEAR', 'TIER'].includes(f.fn.name));
 
    // If we still can't find it, return an error
    if (!fnNodeAsMathNode) {
      return { error: `No tier or linear function detected in target achievement variable for chart ${chart.name}` };
    }
  }
 
  const fnNode: FunctionNode = fnNodeAsMathNode as FunctionNode;
 
  // If formula is not used correctly, return error
  if (fnNode.args.length !== 2 || fnNode.args[0].type !== 'AccessorNode' || fnNode.args[1].type !== 'AccessorNode') {
    return {
      error: `Can't parse formula of target achievement variable for chart ${chart.name}: this formula doesn't use two accessors`,
    };
  }
 
  // If formula is not using statement variables (if wrong scope OR if formula uses function in aggregation functions)
  if (
    ((fnNode.args[0] as AccessorNode).object as SymbolNode).name !== 'statement' ||
    ((fnNode.args[1] as AccessorNode).object as SymbolNode).name !== 'statement'
  ) {
    return { error: `You must use two statement variables in the target achievement variable for chart ${chart.name}` };
  }
 
  // Get the variable machineNames
  const targetVariableMachineName = getVariableMachineNameFromAccessorNode(fnNode.args[0]);
  const tableVariableMachineName = getVariableMachineNameFromAccessorNode(fnNode.args[1]);
 
  if (!targetVariableMachineName || !tableVariableMachineName) {
    return {
      error: `Can't access statement variable names from target achievement variable formula on chart ${chart.name}`,
    };
  }
 
  // Get the statement variables that correspond
  const targetVariable: VariableDefinition | undefined =
    statement.results.definitions.variables[targetVariableMachineName];
  const tableVariable: VariableDefinition | undefined =
    statement.results.definitions.variables[tableVariableMachineName];
 
  // Get the computed objects that correspond to these variables
  const computedTarget = statement.results.computedObjects.find(
    (co) => (co as ComputedVariable).variableMachineName === targetVariableMachineName,
  ) as ComputedVariable<number> | undefined;
  const computedTable = statement.results.computedObjects.find(
    (co) => (co as ComputedVariable).variableMachineName === tableVariableMachineName,
  ) as ComputedVariable<CommissionTableRow[]> | undefined;
 
  if (!computedTarget || isNil(computedTarget.value) || !computedTable) {
    return { error: `Unable to retrieve both target and table variables values from formula for chart ${chart.name}` };
  }
 
  return {
    values: {
      mode: fnNode.fn.name as 'LINEAR' | 'TIER',
      table: {
        value: computedTable.value,
        variable: tableVariable,
      },
      target: {
        value: computedTarget.value,
        variable: targetVariable,
      },
    },
  };
};
 
export const getRuleChartYAxisFormat = (extractedChartConfiguration: ExtractedChartConfiguration | null) => {
  const formatOptions =
    extractedChartConfiguration && isExtractedChartConfigurationSuccess(extractedChartConfiguration)
      ? extractedChartConfiguration.values.table.variable?.formatOptions
      : undefined;

  const tableFormat =
    formatOptions && 'columns' in formatOptions
      ? (formatOptions as VariableFormatOptionsTable).columns[2]?.format
      : undefined;

  return tableFormat === FormatsEnum.percent ? FormatsEnum.percent : FormatsEnum.number;
};
 
export const getRuleChartXAxisFormat = (extractedChartConfiguration: ExtractedChartConfigurationSuccess) =>
  extractedChartConfiguration.values.target.variable?.format === FormatsEnum.percent
    ? FormatsEnum.percent
    : FormatsEnum.number;