fields as $field) { $values[$field->code] = self::evaluateSingleField($field, $record, $values); } return $values; } protected static function evaluateSingleField($field, Model $record, array $evaluatedValues): mixed { $config = $field->source_config ?? []; return match ($field->source_type) { 'db_column' => self::getDbColumnValue($record, $config['column'] ?? null), 'db_relation' => self::getRelationValue($record, $config), 'formula' => self::evaluateFormula($config['expression'] ?? '', $evaluatedValues), 'input' => $config['default'] ?? '', 'static' => $config['value'] ?? '', default => '', }; } protected static function getDbColumnValue(Model $record, ?string $column): mixed { if (! $column) return ''; return $record->{$column} ?? ''; } protected static function getRelationValue(Model $record, array $config): mixed { $relation = $config['relation'] ?? null; $column = $config['column'] ?? null; $index = $config['index'] ?? null; if (! $relation || ! $column) return ''; $related = $record->{$relation}; if (is_null($related)) return ''; if ($related instanceof \Illuminate\Database\Eloquent\Collection) { if ($index !== null) { $item = $related->skip($index)->first(); return $item?->{$column} ?? ''; } return $related->pluck($column)->implode(', '); } return $related->{$column} ?? ''; } protected static function evaluateFormula(string $expression, array $values): float { if (empty($expression)) return 0; // Thay thế tên biến bằng giá trị $evalExpression = $expression; foreach ($values as $key => $value) { if (is_numeric($value)) { $evalExpression = str_replace($key, (float) $value, $evalExpression); } } // Chỉ cho phép số và các phép toán cơ bản $evalExpression = preg_replace('/[^0-9.\+\-\*\/\(\)\s]/', '', $evalExpression); if (empty($evalExpression)) return 0; try { // Eval an toàn với chỉ phép toán $result = self::safeEval($evalExpression); return (float) $result; } catch (\Throwable $e) { return 0; } } protected static function safeEval(string $expression): float { // Loại bỏ các hàm nguy hiểm, chỉ giữ phép toán $expression = preg_replace('/[^0-9.\+\-\*\/\(\)\s]/', '', $expression); if (empty($expression) || preg_match('/[a-zA-Z]/', $expression)) { throw new \InvalidArgumentException('Invalid expression'); } // Dùng bc math nếu có, hoặc eval đơn giản return (float) eval('return ' . $expression . ';'); } /** * Format value theo kiểu field. */ public static function formatValue(mixed $value, string $format, int $decimals = 0): string { return match ($format) { 'number' => number_format((float) $value, $decimals, ',', '.'), 'currency' => number_format((float) $value, 0, ',', '.') . ' VNĐ', 'percent' => number_format((float) $value, $decimals, ',', '.') . '%', 'date' => $value ? \Carbon\Carbon::parse($value)->format('d/m/Y') : '', default => (string) $value, }; } /** * Render template with evaluated values. */ public static function render(FormTemplate $template, Model $record): array { $rawValues = self::evaluateFields($template, $record); $formattedValues = []; foreach ($template->fields as $field) { $code = $field->code; $rawValue = $rawValues[$code] ?? ''; $formattedValues[$code] = self::formatValue( $rawValue, $field->format, $field->decimal_places ); } $html = $template->html_template; foreach ($formattedValues as $code => $value) { $html = str_replace('{{' . $code . '}}', (string) $value, $html); } return [ 'html' => $html, 'raw_values' => $rawValues, 'formatted_values' => $formattedValues, ]; } /** * Save print log with snapshot. */ public static function savePrintLog(FormTemplate $template, Model $record, array $renderResult, int $userId): FormPrintLog { return FormPrintLog::create([ 'template_id' => $template->id, 'target_model' => get_class($record), 'target_id' => $record->id, 'target_number' => $record->contract_number ?? $record->code ?? null, 'snapshot_data' => [ 'raw_values' => $renderResult['raw_values'], 'formatted_values' => $renderResult['formatted_values'], ], 'rendered_html' => $renderResult['html'], 'printed_by' => $userId, 'printed_at' => now(), ]); } }