Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,151 @@
|
||||
import { FormulaEndResult, FormulaOperatorType, FormulaValueType } from "../dtos/FormulaDto";
|
||||
|
||||
type BinaryOperatorFn = (leftOperand: FormulaValueType, rightOperand: FormulaValueType) => FormulaEndResult;
|
||||
|
||||
const binaryOperators: Record<FormulaOperatorType, BinaryOperatorFn> = {
|
||||
ADD: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
if (a !== null) return a as number;
|
||||
return 0;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a + b;
|
||||
} else if (typeof a === "string" || typeof b === "string") {
|
||||
return Number(a) + Number(b);
|
||||
} else if (typeof a === "boolean" && typeof b === "boolean") {
|
||||
return a || b;
|
||||
} else if (a instanceof Date && typeof b === "number") {
|
||||
const newDate = new Date(a);
|
||||
newDate.setDate(newDate.getDate() + b);
|
||||
return newDate;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
SUBTRACT: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
if (a !== null) return a as number;
|
||||
return 0;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a - b;
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
return a.replace(b, "");
|
||||
} else if (typeof a === "boolean" && typeof b === "boolean") {
|
||||
return a && !b;
|
||||
} else if (a instanceof Date && typeof b === "number") {
|
||||
const newDate = new Date(a);
|
||||
newDate.setDate(newDate.getDate() - b);
|
||||
return newDate;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
},
|
||||
MULTIPLY: (a: FormulaValueType, b: FormulaValueType) => (a !== null && b !== null ? Number(a) * Number(b) : false),
|
||||
DIVIDE: (a: FormulaValueType, b: FormulaValueType) => (a !== null && b !== null && b !== 0 ? Number(a) / Number(b) : false),
|
||||
CONCAT: (a: FormulaValueType, b: FormulaValueType) => (a?.toString() ?? "") + (b?.toString() ?? ""),
|
||||
EQUALS: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a === b;
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
return a === b;
|
||||
} else if (typeof a === "boolean" && typeof b === "boolean") {
|
||||
return a === b;
|
||||
} else if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() === b.getTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
NOT_EQUALS: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a !== b;
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
return a !== b;
|
||||
} else if (typeof a === "boolean" && typeof b === "boolean") {
|
||||
return a !== b;
|
||||
} else if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() !== b.getTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
GREATER_THAN: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a > b;
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
return a > b;
|
||||
} else if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() > b.getTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
LESS_THAN: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a < b;
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
return a < b;
|
||||
} else if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() < b.getTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
GREATER_THAN_OR_EQUAL: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a >= b;
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
return a >= b;
|
||||
} else if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() >= b.getTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
LESS_THAN_OR_EQUAL: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a === null || b === null) {
|
||||
return false;
|
||||
} else if (typeof a === "number" && typeof b === "number") {
|
||||
return a <= b;
|
||||
} else if (typeof a === "string" && typeof b === "string") {
|
||||
return a <= b;
|
||||
} else if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() <= b.getTime();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
AND: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (typeof a === "boolean" && typeof b === "boolean") {
|
||||
return a && b;
|
||||
} else {
|
||||
let aIsTrue = (a || a === "true") && a !== "false";
|
||||
let bIsTrue = (b || b === "true") && b !== "false";
|
||||
return aIsTrue && bIsTrue;
|
||||
}
|
||||
},
|
||||
OR: (a: FormulaValueType, b: FormulaValueType) => Boolean(a || b),
|
||||
NOT: (a: FormulaValueType) => !a,
|
||||
DATE_ADD_DAYS: (a: FormulaValueType, b: FormulaValueType) => {
|
||||
if (a instanceof Date || typeof a === "string") {
|
||||
const newDate = new Date(a);
|
||||
newDate.setDate(newDate.getDate() + Number(b ?? 0));
|
||||
return newDate;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
binaryOperators,
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { FormulaDto } from "../dtos/FormulaDto";
|
||||
|
||||
export function defaultFormulas(): FormulaDto[] {
|
||||
const formulas: FormulaDto[] = [
|
||||
{
|
||||
name: "Concatenates",
|
||||
description: "calculates with texts",
|
||||
resultAs: "string",
|
||||
calculationTrigger: "NEVER",
|
||||
components: [
|
||||
{ order: 1, type: "variable", value: "text1" },
|
||||
{ order: 2, type: "operator", value: "CONCAT" },
|
||||
{ order: 3, type: "variable", value: "text2" },
|
||||
{ order: 4, type: "operator", value: "CONCAT" },
|
||||
{ order: 5, type: "variable", value: "text3" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Evaluates Booleans",
|
||||
description: "calculates with booleans",
|
||||
resultAs: "boolean",
|
||||
components: [
|
||||
{ order: 1, type: "variable", value: "boolean1" },
|
||||
{ order: 2, type: "operator", value: "AND" },
|
||||
{ order: 3, type: "variable", value: "boolean2" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adds Days to Date",
|
||||
description: "calculates with dates",
|
||||
resultAs: "date",
|
||||
components: [
|
||||
{ order: 1, type: "variable", value: "date" },
|
||||
{ order: 2, type: "operator", value: "DATE_ADD_DAYS" },
|
||||
{ order: 3, type: "variable", value: "days" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Assigns Static Value",
|
||||
description: "sets static value",
|
||||
resultAs: "number",
|
||||
components: [{ order: 1, type: "variable", value: "number" }],
|
||||
},
|
||||
{
|
||||
name: "Adds Two Numbers",
|
||||
description: "calculates addition with two values",
|
||||
resultAs: "number",
|
||||
components: [
|
||||
{ order: 1, type: "variable", value: "number1" },
|
||||
{ order: 2, type: "operator", value: "ADD" },
|
||||
{ order: 3, type: "variable", value: "number2" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return formulas;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { FormulaOperatorType, FormulaDto, FormulaValueType, FormulaResultAsType, FormulaEndResult } from "../dtos/FormulaDto";
|
||||
import BinaryOperatorUtils from "./BinaryOperatorUtils";
|
||||
import FormulaHelpers from "./FormulaHelpers";
|
||||
|
||||
function calculateFormula(formula: FormulaDto, values: Record<string, FormulaValueType>): FormulaEndResult {
|
||||
const resultAs: FormulaResultAsType = FormulaHelpers.getResultAs(formula.resultAs);
|
||||
let lastComponent: string | null = null;
|
||||
let result: FormulaValueType | null = null;
|
||||
let currentOperand: FormulaValueType = null;
|
||||
let currentOperator: FormulaOperatorType | null = null;
|
||||
let openParenthesesCount = 0;
|
||||
let closeParenthesesCount = 0;
|
||||
let insideParentheses: FormulaDto | null = null;
|
||||
|
||||
formula.components.forEach((component) => {
|
||||
if (component.type === "variable") {
|
||||
lastComponent = "variable: " + component.value;
|
||||
if (insideParentheses !== null) {
|
||||
insideParentheses.components.push(component);
|
||||
} else {
|
||||
currentOperand = values[component.value];
|
||||
}
|
||||
} else if (component.type === "operator") {
|
||||
lastComponent = "operator: " + component.value;
|
||||
if (currentOperator !== null) {
|
||||
if (insideParentheses !== null) {
|
||||
insideParentheses.components.push(component);
|
||||
} else {
|
||||
result = BinaryOperatorUtils.binaryOperators[currentOperator](result, currentOperand);
|
||||
}
|
||||
} else {
|
||||
if (insideParentheses !== null) {
|
||||
throw new Error("Invalid formula: parentheses must be used in pairs");
|
||||
} else {
|
||||
result = currentOperand;
|
||||
}
|
||||
}
|
||||
// Reset the operand for the next iteration
|
||||
currentOperand = null;
|
||||
currentOperator = FormulaHelpers.getOperatorType(component.value);
|
||||
} else if (component.type === "parenthesis") {
|
||||
lastComponent = "parenthesis: " + component.value;
|
||||
const parenthesis = FormulaHelpers.getParenthesisType(component.value);
|
||||
if (parenthesis === "OPEN") {
|
||||
if (insideParentheses !== null) {
|
||||
throw new Error("Invalid formula: parentheses must be used in pairs");
|
||||
} else {
|
||||
insideParentheses = { name: "", description: "", resultAs: "number", calculationTrigger: formula.calculationTrigger, components: [] };
|
||||
openParenthesesCount++;
|
||||
}
|
||||
} else if (parenthesis === "CLOSE") {
|
||||
if (insideParentheses === null) {
|
||||
throw new Error("Invalid formula: parentheses must be used in pairs");
|
||||
} else {
|
||||
closeParenthesesCount++;
|
||||
if (closeParenthesesCount > openParenthesesCount) {
|
||||
throw new Error("Invalid formula: parentheses must be used in pairs");
|
||||
} else if (closeParenthesesCount === openParenthesesCount) {
|
||||
currentOperand = calculateFormula(insideParentheses, values);
|
||||
insideParentheses = null;
|
||||
} else {
|
||||
insideParentheses.components.push(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (component.type === "value") {
|
||||
lastComponent = "value: " + component.value;
|
||||
if (insideParentheses !== null) {
|
||||
insideParentheses.components.push(component);
|
||||
} else {
|
||||
currentOperand = component.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (formula.components.length === 1 && currentOperand) {
|
||||
result = currentOperand;
|
||||
}
|
||||
|
||||
if (currentOperand !== null && currentOperator !== null) {
|
||||
const operator = currentOperator as FormulaOperatorType;
|
||||
result = BinaryOperatorUtils.binaryOperators[operator](result, currentOperand);
|
||||
} else if (result === null) {
|
||||
if (!lastComponent) {
|
||||
throw new Error("Invalid formula");
|
||||
} else {
|
||||
throw new Error(`Invalid formula component ${lastComponent}`);
|
||||
}
|
||||
}
|
||||
|
||||
switch (resultAs) {
|
||||
case "number":
|
||||
return result !== null ? Number(result) : null;
|
||||
case "boolean":
|
||||
return result !== null ? Boolean(result) : null;
|
||||
case "date":
|
||||
return !result === null || result instanceof Date === false ? null : result;
|
||||
case "string":
|
||||
return result !== null ? String(result) : null;
|
||||
default:
|
||||
throw new Error("Invalid resultAs value in formula");
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
calculateFormula,
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
import { FormulaWithDetails } from "../db/formulas.db.server";
|
||||
import {
|
||||
FormulaResultAsType,
|
||||
FormulaOperatorType,
|
||||
FormulaParenthesisType,
|
||||
FormulaCalculationTriggerType,
|
||||
FormulaDto,
|
||||
FormulaValueType,
|
||||
FormulaComponentType,
|
||||
} from "../dtos/FormulaDto";
|
||||
|
||||
function getFormulaDto(formula: FormulaWithDetails) {
|
||||
const formulaDto: FormulaDto = {
|
||||
id: formula.id,
|
||||
createdAt: formula.createdAt,
|
||||
name: formula.name,
|
||||
description: formula.description,
|
||||
resultAs: getResultAs(formula.resultAs),
|
||||
calculationTrigger: getCalculationTrigger(formula.calculationTrigger),
|
||||
withLogs: formula.withLogs,
|
||||
components: formula.components
|
||||
.sort((a, b) => a.order - b.order)
|
||||
.map((component) => ({
|
||||
order: component.order,
|
||||
type: getComponentType(component.type),
|
||||
value: component.value ?? undefined,
|
||||
})),
|
||||
inProperties: formula.inProperties.map((x) => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
entity: {
|
||||
id: x.entity.id,
|
||||
name: x.entity.name,
|
||||
},
|
||||
})),
|
||||
};
|
||||
return formulaDto;
|
||||
}
|
||||
|
||||
function getResultAs(resultAs?: string): FormulaResultAsType {
|
||||
switch (resultAs) {
|
||||
case "number":
|
||||
return "number";
|
||||
case "string":
|
||||
return "string";
|
||||
case "boolean":
|
||||
return "boolean";
|
||||
case "date":
|
||||
return "date";
|
||||
default:
|
||||
return "string";
|
||||
}
|
||||
}
|
||||
|
||||
function getComponentType(type: string): FormulaComponentType {
|
||||
switch (type) {
|
||||
case "variable":
|
||||
return "variable";
|
||||
case "operator":
|
||||
return "operator";
|
||||
case "value":
|
||||
return "value";
|
||||
case "parenthesis":
|
||||
return "parenthesis";
|
||||
default:
|
||||
return "variable";
|
||||
}
|
||||
}
|
||||
|
||||
function getCalculationTrigger(calculationTrigger?: string): FormulaCalculationTriggerType {
|
||||
switch (calculationTrigger) {
|
||||
case "ALWAYS":
|
||||
return "ALWAYS";
|
||||
case "BEFORE_LISTED":
|
||||
return "BEFORE_LISTED";
|
||||
case "AFTER_CREATED":
|
||||
return "AFTER_CREATED";
|
||||
case "BEFORE_VIEWED":
|
||||
return "BEFORE_VIEWED";
|
||||
case "AFTER_UPDATED":
|
||||
return "AFTER_UPDATED";
|
||||
case "IF_UNSET":
|
||||
return "IF_UNSET";
|
||||
case "NEVER":
|
||||
return "NEVER";
|
||||
default:
|
||||
return "NEVER";
|
||||
}
|
||||
}
|
||||
|
||||
function getOperatorType(operator: string): FormulaOperatorType {
|
||||
switch (operator) {
|
||||
case "ADD":
|
||||
return "ADD";
|
||||
case "SUBTRACT":
|
||||
return "SUBTRACT";
|
||||
case "MULTIPLY":
|
||||
return "MULTIPLY";
|
||||
case "DIVIDE":
|
||||
return "DIVIDE";
|
||||
case "CONCAT":
|
||||
return "CONCAT";
|
||||
case "EQUALS":
|
||||
return "EQUALS";
|
||||
case "NOT_EQUALS":
|
||||
return "NOT_EQUALS";
|
||||
case "GREATER_THAN":
|
||||
return "GREATER_THAN";
|
||||
case "LESS_THAN":
|
||||
return "LESS_THAN";
|
||||
case "GREATER_THAN_OR_EQUAL":
|
||||
return "GREATER_THAN_OR_EQUAL";
|
||||
case "LESS_THAN_OR_EQUAL":
|
||||
return "LESS_THAN_OR_EQUAL";
|
||||
case "AND":
|
||||
return "AND";
|
||||
case "OR":
|
||||
return "OR";
|
||||
case "NOT":
|
||||
return "NOT";
|
||||
case "DATE_ADD_DAYS":
|
||||
return "DATE_ADD_DAYS";
|
||||
default:
|
||||
return "ADD";
|
||||
}
|
||||
}
|
||||
|
||||
function getOperatorSymbol(operator?: string): string {
|
||||
switch (getOperatorType(operator ?? "")) {
|
||||
case "ADD":
|
||||
return "+";
|
||||
case "DATE_ADD_DAYS":
|
||||
return "+";
|
||||
case "SUBTRACT":
|
||||
return "-";
|
||||
case "MULTIPLY":
|
||||
return "*";
|
||||
case "DIVIDE":
|
||||
return "/";
|
||||
case "CONCAT":
|
||||
return "&";
|
||||
case "EQUALS":
|
||||
return "=";
|
||||
case "NOT_EQUALS":
|
||||
return "!=";
|
||||
case "GREATER_THAN":
|
||||
return ">";
|
||||
case "LESS_THAN":
|
||||
return "<";
|
||||
case "GREATER_THAN_OR_EQUAL":
|
||||
return ">=";
|
||||
case "LESS_THAN_OR_EQUAL":
|
||||
return "<=";
|
||||
case "AND":
|
||||
return "AND";
|
||||
case "OR":
|
||||
return "OR";
|
||||
case "NOT":
|
||||
return "NOT";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getParenthesisType(parenthesis: string): FormulaParenthesisType {
|
||||
switch (parenthesis) {
|
||||
case "OPEN":
|
||||
return "OPEN";
|
||||
case "CLOSE":
|
||||
return "CLOSE";
|
||||
default:
|
||||
return "OPEN";
|
||||
}
|
||||
}
|
||||
|
||||
function getValueAsString(value: FormulaValueType) {
|
||||
let valueStr = "";
|
||||
if (value === null) {
|
||||
valueStr = "[null]";
|
||||
} else if (value === undefined) {
|
||||
valueStr = "[undefined]";
|
||||
} else if (typeof value === "string") {
|
||||
valueStr = `"${value}"`;
|
||||
} else if (typeof value === "boolean") {
|
||||
valueStr = `${value ? "true" : "false"}`;
|
||||
} else if (typeof value === "number") {
|
||||
valueStr = `${value}`;
|
||||
} else if (value instanceof Date) {
|
||||
valueStr = `"${value.toISOString()}"`;
|
||||
} else {
|
||||
valueStr = `"${value.toString()}"`;
|
||||
}
|
||||
return valueStr;
|
||||
}
|
||||
|
||||
export default {
|
||||
getFormulaDto,
|
||||
getResultAs,
|
||||
getComponentType,
|
||||
getCalculationTrigger,
|
||||
getOperatorType,
|
||||
getOperatorSymbol,
|
||||
getParenthesisType,
|
||||
getValueAsString,
|
||||
};
|
||||
Reference in new issue
Block a user