124 lines
4.1 KiB
PHP
124 lines
4.1 KiB
PHP
<?php
|
||
|
||
namespace App\Services\DecisionEvents;
|
||
|
||
class ConditionEvaluator
|
||
{
|
||
/**
|
||
* Returns true when ALL conditions pass (AND logic).
|
||
*
|
||
* Each condition: { field: string, operator: string, value: mixed }
|
||
*
|
||
* @param array<int, array{field: string, operator: string, value: mixed}> $conditions
|
||
*/
|
||
public function evaluate(array $conditions, DecisionEventContext $context): bool
|
||
{
|
||
foreach ($conditions as $condition) {
|
||
if (! $this->evaluateOne($condition, $context)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
protected function evaluateOne(array $condition, DecisionEventContext $context): bool
|
||
{
|
||
$field = $condition['field'] ?? '';
|
||
$operator = $condition['operator'] ?? '=';
|
||
$expected = $condition['value'] ?? null;
|
||
|
||
$actual = $this->resolveField($field, $context);
|
||
|
||
return $this->compare($actual, $operator, $expected);
|
||
}
|
||
|
||
protected function resolveField(string $field, DecisionEventContext $context): mixed
|
||
{
|
||
return match ($field) {
|
||
'activity.amount' => $context->activity?->amount,
|
||
'activity.note' => $context->activity?->note,
|
||
'contract.active' => $context->contract !== null ? (bool) $context->contract->active : null,
|
||
'contract.account.balance_amount' => $this->resolveAccountBalance($context),
|
||
default => null,
|
||
};
|
||
}
|
||
|
||
private function resolveAccountBalance(DecisionEventContext $context): mixed
|
||
{
|
||
if (! $context->contract) {
|
||
return null;
|
||
}
|
||
|
||
$context->contract->loadMissing('account');
|
||
|
||
return $context->contract->account?->balance_amount;
|
||
}
|
||
|
||
protected function compare(mixed $actual, string $operator, mixed $expected): bool
|
||
{
|
||
if ($actual === null) {
|
||
return false;
|
||
}
|
||
|
||
if (in_array($operator, ['>', '>=', '<', '<='], true)) {
|
||
$actual = (float) $actual;
|
||
$expected = (float) $expected;
|
||
}
|
||
|
||
return match ($operator) {
|
||
'=' => $actual == $expected,
|
||
'!=' => $actual != $expected,
|
||
'>' => $actual > $expected,
|
||
'>=' => $actual >= $expected,
|
||
'<' => $actual < $expected,
|
||
'<=' => $actual <= $expected,
|
||
'contains' => str_contains((string) $actual, (string) $expected),
|
||
default => false,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Returns available condition field definitions for the frontend.
|
||
*
|
||
* @return array<int, array{key: string, label: string, type: string}>
|
||
*/
|
||
public static function availableFields(): array
|
||
{
|
||
return [
|
||
['key' => 'activity.amount', 'label' => 'Aktivnost – znesek', 'type' => 'numeric'],
|
||
['key' => 'activity.note', 'label' => 'Aktivnost – opomba', 'type' => 'string'],
|
||
['key' => 'contract.active', 'label' => 'Pogodba – aktivna', 'type' => 'boolean'],
|
||
['key' => 'contract.account.balance_amount', 'label' => 'Račun – stanje', 'type' => 'numeric'],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Returns available operators grouped by field type.
|
||
*
|
||
* @return array<string, array<int, array{key: string, label: string}>>
|
||
*/
|
||
public static function availableOperators(): array
|
||
{
|
||
return [
|
||
'numeric' => [
|
||
['key' => '=', 'label' => 'je enako'],
|
||
['key' => '!=', 'label' => 'ni enako'],
|
||
['key' => '>', 'label' => 'je večje od'],
|
||
['key' => '>=', 'label' => 'je večje ali enako'],
|
||
['key' => '<', 'label' => 'je manjše od'],
|
||
['key' => '<=', 'label' => 'je manjše ali enako'],
|
||
],
|
||
'string' => [
|
||
['key' => '=', 'label' => 'je enako'],
|
||
['key' => '!=', 'label' => 'ni enako'],
|
||
['key' => 'contains', 'label' => 'vsebuje'],
|
||
],
|
||
'boolean' => [
|
||
['key' => '=', 'label' => 'je'],
|
||
['key' => '!=', 'label' => 'ni'],
|
||
],
|
||
];
|
||
}
|
||
}
|