Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
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,
};