ruleDefModel = new RuleDefModel(); $this->ruleActionModel = new RuleActionModel(); $this->expr = new RuleExpressionService(); } /** * Run rules for an event. * * Expected context keys for ORDER_CREATED: * - order: array (must include InternalOID) * - tests: array (patres rows, optional) */ public function run(string $eventCode, array $context = []): void { $order = $context['order'] ?? null; $testSiteID = $context['testSiteID'] ?? null; if (is_array($order) && isset($order['TestSiteID']) && $testSiteID === null) { $testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null; } $rules = $this->ruleDefModel->getActiveByEvent($eventCode, $testSiteID); if (empty($rules)) { return; } $ruleIDs = array_values(array_filter(array_map(static fn ($r) => $r['RuleID'] ?? null, $rules))); $actions = $this->ruleActionModel->getActiveByRuleIDs($ruleIDs); $actionsByRule = []; foreach ($actions as $action) { $rid = $action['RuleID'] ?? null; if (!$rid) { continue; } $actionsByRule[$rid][] = $action; } foreach ($rules as $rule) { $rid = (int) ($rule['RuleID'] ?? 0); if ($rid === 0) { continue; } try { // Check for compiled expression first $compiled = null; if (!empty($rule['ConditionExprCompiled'])) { $compiled = json_decode($rule['ConditionExprCompiled'], true); } if (!empty($compiled) && is_array($compiled)) { // Compiled rule: evaluate condition from compiled structure $conditionExpr = $compiled['conditionExpr'] ?? 'true'; $matches = $this->expr->evaluateBoolean($conditionExpr, $context); if (!$matches) { continue; } // Use compiled valueExpr for SET_RESULT action if (!empty($compiled['valueExpr'])) { $this->executeCompiledSetResult($rid, $compiled['valueExpr'], $context); } } else { // Legacy rule: evaluate raw ConditionExpr and execute stored actions $matches = $this->expr->evaluateBoolean($rule['ConditionExpr'] ?? null, $context); if (!$matches) { continue; } foreach ($actionsByRule[$rid] ?? [] as $action) { $this->executeAction($action, $context); } } } catch (\Throwable $e) { log_message('error', 'Rule engine error (RuleID=' . $rid . '): ' . $e->getMessage()); continue; } } } /** * Execute SET_RESULT action using compiled valueExpr. * Automatically creates the test result if it doesn't exist. */ protected function executeCompiledSetResult(int $ruleID, string $valueExpr, array $context): void { $order = $context['order'] ?? null; if (!is_array($order) || empty($order['InternalOID'])) { throw new \Exception('SET_RESULT requires context.order.InternalOID'); } $internalOID = (int) $order['InternalOID']; $testSiteID = $context['testSiteID'] ?? null; if ($testSiteID === null && isset($order['TestSiteID'])) { $testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null; } if ($testSiteID === null) { // Try to get testSiteID from context tests $tests = $context['tests'] ?? []; if (!empty($tests) && is_array($tests)) { $testSiteID = (int) ($tests[0]['TestSiteID'] ?? null); } } if ($testSiteID === null) { throw new \Exception('SET_RESULT requires testSiteID'); } // Evaluate the value expression $value = $this->expr->evaluate($valueExpr, $context); $db = \Config\Database::connect(); // Check if patres row exists $patres = $db->table('patres') ->where('OrderID', $internalOID) ->where('TestSiteID', $testSiteID) ->where('DelDate', null) ->get() ->getRowArray(); if ($patres) { // Update existing result $ok = $db->table('patres') ->where('OrderID', $internalOID) ->where('TestSiteID', $testSiteID) ->where('DelDate', null) ->update(['Result' => $value]); } else { // Insert new result row $ok = $db->table('patres')->insert([ 'OrderID' => $internalOID, 'TestSiteID' => $testSiteID, 'Result' => $value, 'CreateDate' => date('Y-m-d H:i:s'), ]); } if ($ok === false) { throw new \Exception('SET_RESULT update/insert failed'); } } protected function executeAction(array $action, array $context): void { $type = strtoupper((string) ($action['ActionType'] ?? '')); if ($type === 'SET_RESULT') { $this->executeSetResult($action, $context); return; } // Unknown action type: ignore } /** * SET_RESULT action params (JSON): * - testSiteID (int) OR testSiteCode (string) * - value (scalar) OR valueExpr (ExpressionLanguage string) */ protected function executeSetResult(array $action, array $context): void { $paramsRaw = (string) ($action['ActionParams'] ?? ''); $params = []; if (trim($paramsRaw) !== '') { $decoded = json_decode($paramsRaw, true); if (is_array($decoded)) { $params = $decoded; } } $order = $context['order'] ?? null; if (!is_array($order) || empty($order['InternalOID'])) { throw new \Exception('SET_RESULT requires context.order.InternalOID'); } $internalOID = (int) $order['InternalOID']; $testSiteID = isset($params['testSiteID']) && is_numeric($params['testSiteID']) ? (int) $params['testSiteID'] : null; if ($testSiteID === null && !empty($params['testSiteCode'])) { $testSiteCode = (string) $params['testSiteCode']; $testDefSiteModel = new TestDefSiteModel(); $row = $testDefSiteModel->where('TestSiteCode', $testSiteCode)->where('EndDate', null)->first(); $testSiteID = isset($row['TestSiteID']) ? (int) $row['TestSiteID'] : null; } if ($testSiteID === null) { throw new \Exception('SET_RESULT requires testSiteID or testSiteCode'); } if (array_key_exists('valueExpr', $params) && is_string($params['valueExpr'])) { $value = $this->expr->evaluate($params['valueExpr'], $context); } else { $value = $params['value'] ?? null; } $db = \Config\Database::connect(); $ok = $db->table('patres') ->where('OrderID', $internalOID) ->where('TestSiteID', $testSiteID) ->where('DelDate', null) ->update(['Result' => $value]); if ($ok === false) { throw new \Exception('SET_RESULT update failed'); } } }