| Server IP : 37.9.174.138 / Your IP : 216.73.216.117 Web Server : Apache System : Linux vps6.backend.sk 6.12.100+deb13-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.100-1 (2026-07-30) x86_64 User : ftpuser ( 1001) PHP Version : 8.4.24 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/html/backend-accounting.sk/app/modules/worksheets/code/ |
Upload File : |
<?php
namespace modules\worksheets\code;
use app\classes\PDOCollection;
use app\classes\PDODatabase;
use app\constants\State;
use modules\gDriveWatchDog\code\EvidenceVehicleUse;
use modules\gDriveWatchDog\code\repo\EvidenceVehicleUseRepository;
use modules\users\code\repo\ConsultantUserRepository;
use modules\worksheets\code\enums\WorksheetInitialData;
use modules\worksheets\code\enums\WorksheetStatus;
class WorksheetCellHistoryService {
/**
* @return list<array{
* id: int,
* created: string,
* created_label: string,
* created_by: string,
* user_label: string,
* user_full_name: string,
* user_color: string,
* old_val: string,
* new_val: string,
* old_val_raw: string|null,
* new_val_raw: string|null,
* old_val_badge: string|null,
* new_val_badge: string|null
* }>
*/
public static function getCellHistory(AbstractWorksheetTable $table, int $rowId, string $field): array {
if ($rowId <= 0 || $field === '') {
return [];
}
return self::getLoggerHistory(
$table->getDbTableName(),
$rowId,
$field,
static function ($raw) use ($table, $field) {
return self::formatHistoryValueDetails($table, $field, $raw);
}
);
}
/**
* History for worsheet_main_control preview cells.
*
* @return list<array<string, mixed>>
*/
public static function getControlCellHistory(int $rowId, string $field): array {
if ($rowId <= 0 || !in_array($field, WorksheetMainControl::EDITABLE_FIELDS, true)) {
return [];
}
return self::getLoggerHistory(
WorksheetMainControl::$dbTable,
$rowId,
$field,
static function ($raw) use ($field) {
return self::formatSideTableHistoryValue(
$field,
$raw,
WorksheetMainControl::CHECKBOX_FIELDS,
$field === 'submission_date'
);
}
);
}
/**
* History for worsheet_main_dppo preview cells.
*
* @return list<array<string, mixed>>
*/
public static function getDppoCellHistory(int $rowId, string $field): array {
if ($rowId <= 0 || !in_array($field, WorksheetMainDppo::EDITABLE_FIELDS, true)) {
return [];
}
return self::getLoggerHistory(
WorksheetMainDppo::$dbTable,
$rowId,
$field,
static function ($raw) use ($field) {
return self::formatSideTableHistoryValue(
$field,
$raw,
WorksheetMainDppo::CHECKBOX_FIELDS,
false
);
}
);
}
/**
* History for Motorové vozidlá "Použitie MV v tomto mesiaci" checkbox
* (`evidence_vehicles_use.used`).
*
* @return list<array<string, mixed>>
*/
public static function getVehicleUseCellHistory(int $vehicleId, int $period): array {
if ($vehicleId <= 0 || !EvidenceVehicleUseRepository::isValidPeriod($period)) {
return [];
}
$use = (new EvidenceVehicleUseRepository())->findActiveByVehicleAndPeriod($vehicleId, $period);
if ($use === null) {
return [];
}
$rowId = (int) $use->getId();
if ($rowId <= 0) {
return [];
}
return self::getLoggerHistory(
EvidenceVehicleUse::$dbTable,
$rowId,
'used',
static function ($raw) {
return self::formatCheckboxHistoryValue($raw);
}
);
}
/**
* @param callable(mixed): array{label: string, badge: string|null} $formatter
* @return list<array<string, mixed>>
*/
private static function getLoggerHistory(string $dbTable, int $rowId, string $field, callable $formatter): array {
$collection = new PDOCollection(
'db_updates_logger',
'id',
'SELECT id, created, created_by, column_name, old_val, new_val'
. ' FROM db_updates_logger'
. ' WHERE table_name = :TABLE_NAME'
. ' AND row_id_column_name = :ROW_ID_COLUMN'
. ' AND row_id = :ROW_ID'
. ' AND column_name = :COLUMN_NAME'
. ' AND state = :ACTIVE'
. ' ORDER BY created DESC, id DESC',
[
'TABLE_NAME' => $dbTable,
'ROW_ID_COLUMN' => 'id',
'ROW_ID' => (string) $rowId,
'COLUMN_NAME' => $field,
'ACTIVE' => State::ACTIVE,
]
);
$items = [];
foreach ($collection->toArray() as $row) {
$createdBy = trim((string) ($row['created_by'] ?? ''));
$user = self::resolveUserByCreatedBy($createdBy);
$created = (string) ($row['created'] ?? '');
$oldRaw = array_key_exists('old_val', $row) ? $row['old_val'] : null;
$newRaw = array_key_exists('new_val', $row) ? $row['new_val'] : null;
$oldFormatted = $formatter($oldRaw);
$newFormatted = $formatter($newRaw);
$items[] = [
'id' => (int) ($row['id'] ?? 0),
'created' => $created,
'created_label' => self::formatDateTime($created),
'created_by' => $createdBy,
'user_label' => self::formatUserLabel($user, $createdBy),
'user_full_name' => self::formatUserFullName($user, $createdBy),
'user_color' => self::formatUserColor($user),
'old_val' => $oldFormatted['label'],
'new_val' => $newFormatted['label'],
'old_val_raw' => $oldRaw === null ? null : (string) $oldRaw,
'new_val_raw' => $newRaw === null ? null : (string) $newRaw,
'old_val_badge' => $oldFormatted['badge'],
'new_val_badge' => $newFormatted['badge'],
];
}
return $items;
}
/**
* @param list<string> $checkboxFields
* @param mixed $raw
* @return array{label: string, badge: string|null}
*/
private static function formatSideTableHistoryValue(
string $field,
$raw,
array $checkboxFields,
bool $isDate
): array {
if ($raw === null || $raw === '') {
return ['label' => '—', 'badge' => null];
}
if (in_array($field, $checkboxFields, true)) {
return self::formatCheckboxHistoryValue($raw);
}
if ($isDate) {
return self::formatDateHistoryValue($raw);
}
return ['label' => (string) $raw, 'badge' => null];
}
/**
* Resolve actor from logger created_by email, including soft-deleted users
* whose email was rewritten to original_email_<unixTimestamp>.
*
* @return array<string, mixed>|null
*/
public static function resolveUserByCreatedBy(string $createdBy): ?array {
$createdBy = trim($createdBy);
if ($createdBy === '') {
return null;
}
$db = PDODatabase::getInstance();
$exact = $db->getRow(
'users',
0,
'id',
false,
['email' => $createdBy],
'SELECT id, email, first_name, last_name, personalno, state, color'
. ' FROM users WHERE email = :email'
);
if (is_array($exact) && !empty($exact['id'])) {
return $exact;
}
$collection = new PDOCollection(
'users',
'id',
'SELECT id, email, first_name, last_name, personalno, state, color'
. ' FROM users'
. ' WHERE email LIKE :PREFIX'
. ' ORDER BY id DESC',
['PREFIX' => $createdBy . '_%']
);
$prefix = $createdBy . '_';
foreach ($collection->toArray() as $candidate) {
$email = (string) ($candidate['email'] ?? '');
if (strpos($email, $prefix) !== 0) {
continue;
}
$suffix = substr($email, strlen($prefix));
if ($suffix !== '' && ctype_digit($suffix)) {
return $candidate;
}
}
return null;
}
/**
* @param array<string, mixed>|null $user
*/
private static function formatUserLabel(?array $user, string $fallbackEmail): string {
if ($user !== null) {
$full = ConsultantUserRepository::formatFullLabel($user);
if ($full !== '') {
return self::appendDeletedMarker($full, $user);
}
}
return $fallbackEmail !== '' ? $fallbackEmail : '—';
}
/**
* @param array<string, mixed>|null $user
*/
private static function formatUserFullName(?array $user, string $fallbackEmail): string {
if ($user !== null) {
$full = ConsultantUserRepository::formatFullLabel($user);
if ($full !== '') {
return self::appendDeletedMarker($full, $user);
}
}
return $fallbackEmail;
}
/**
* @param array<string, mixed>|null $user
*/
private static function formatUserColor(?array $user): string {
if ($user === null) {
return '';
}
$color = trim((string) ($user['color'] ?? ''));
if ($color === '') {
return '';
}
if ($color[0] !== '#') {
$color = '#' . $color;
}
if (!preg_match('/^#[0-9A-Fa-f]{3}([0-9A-Fa-f]{3})?$/', $color)) {
return '';
}
return $color;
}
/**
* @param array<string, mixed> $user
*/
private static function appendDeletedMarker(string $label, array $user): string {
if (($user['state'] ?? '') === State::DELETED) {
return $label . ' (zmazaný)';
}
return $label;
}
/**
* @param mixed $raw
* @return array{label: string, badge: string|null}
*/
private static function formatHistoryValueDetails(AbstractWorksheetTable $table, string $field, $raw): array {
if ($raw === null || $raw === '') {
return ['label' => '—', 'badge' => null];
}
$controller = $table->getColumnController($field);
if ($controller === null) {
return ['label' => self::formatScalarHistoryValue($raw), 'badge' => null];
}
$clientValue = $controller->normalizeForClient($raw);
if ($clientValue === null || $clientValue === '') {
return ['label' => '—', 'badge' => null];
}
$formatter = self::resolveHistoryFormatterMethod($field, $controller->getTypeKey());
if ($formatter !== null) {
return self::$formatter($clientValue);
}
if (is_bool($clientValue)) {
return ['label' => ($clientValue ? 'Áno' : 'Nie'), 'badge' => null];
}
if (is_array($clientValue) || is_object($clientValue)) {
return [
'label' => (string) json_encode($clientValue, JSON_UNESCAPED_UNICODE),
'badge' => null,
];
}
return ['label' => self::formatScalarHistoryValue($clientValue), 'badge' => null];
}
/**
* Resolve formatter by column field first, then by column type.
* Example: status -> formatStatusHistoryValue, worksheet_status -> formatWorksheetStatusHistoryValue.
*/
private static function resolveHistoryFormatterMethod(string $field, string $typeKey): ?string {
foreach ([$field, $typeKey] as $name) {
$name = trim((string) $name);
if ($name === '') {
continue;
}
$method = self::historyFormatterMethodName($name);
if (method_exists(self::class, $method)) {
return $method;
}
}
return null;
}
private static function historyFormatterMethodName(string $name): string {
$studly = str_replace(' ', '', ucwords(str_replace('_', ' ', strtolower($name))));
return 'format' . $studly . 'HistoryValue';
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatStatusHistoryValue($value): array {
return self::formatWorksheetStatusHistoryValue($value);
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatWorksheetStatusHistoryValue($value): array {
$state = (string) $value;
$label = WorksheetStatus::getStateMessage($state);
if ($label === false || $label === '') {
$label = $state;
}
return [
'label' => (string) $label,
'badge' => 'worksheet-status-badge ' . WorksheetStatus::getBadgeClass($state),
];
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatInitialDataHistoryValue($value): array {
return self::formatWorksheetInitialDataHistoryValue($value);
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatWorksheetInitialDataHistoryValue($value): array {
$state = (string) $value;
$label = WorksheetInitialData::getStateMessage($state);
if ($label === false || $label === '') {
$label = $state;
}
$badgeClass = WorksheetInitialData::getBadgeClass($state);
return [
'label' => (string) $label,
'badge' => $badgeClass !== ''
? ('worksheet-initial-data-badge ' . $badgeClass)
: null,
];
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatCheckboxHistoryValue($value): array {
return [
'label' => ((int) $value === 1 || $value === true || $value === '1') ? 'Áno' : 'Nie',
'badge' => null,
];
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatVatCheckboxHistoryValue($value): array {
return self::formatCheckboxHistoryValue($value);
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatDateHistoryValue($value): array {
$formattedDate = self::formatDateValue($value);
return [
'label' => $formattedDate !== null ? $formattedDate : (string) $value,
'badge' => null,
];
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatPeriodDateHistoryValue($value): array {
return self::formatDateHistoryValue($value);
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatSeniorUserHistoryValue($value): array {
$userId = (int) $value;
if ($userId <= 0) {
return ['label' => '—', 'badge' => null];
}
$user = PDODatabase::getInstance()->getRow(
'users',
0,
'id',
false,
['id' => $userId],
'SELECT id, email, first_name, last_name, personalno, state, color'
. ' FROM users WHERE id = :id'
);
if (is_array($user)) {
$label = ConsultantUserRepository::formatShortLabel($user);
if ($label !== '') {
return ['label' => $label, 'badge' => null];
}
}
return ['label' => (string) $value, 'badge' => null];
}
/**
* @param mixed $value
* @return array{label: string, badge: string|null}
*/
private static function formatMediorUserHistoryValue($value): array {
return self::formatSeniorUserHistoryValue($value);
}
/**
* @param mixed $value
*/
private static function formatScalarHistoryValue($value): string {
$formattedDate = self::formatDateValue($value);
if ($formattedDate !== null) {
return $formattedDate;
}
return (string) $value;
}
/**
* @param mixed $value
*/
private static function formatDateValue($value): ?string {
if (!is_string($value) && !is_numeric($value)) {
return null;
}
$value = trim((string) $value);
if ($value === '' || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') {
return null;
}
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
$ts = strtotime($value . ' 00:00:00');
return $ts === false ? null : date('d.m.Y', $ts);
}
if (preg_match('/^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(:\d{2})?/', $value)) {
$ts = strtotime($value);
return $ts === false ? null : date('d.m.Y H:i:s', $ts);
}
return null;
}
private static function formatDateTime(string $created): string {
$created = trim($created);
if ($created === '' || $created === '0000-00-00 00:00:00') {
return '—';
}
$ts = strtotime($created);
if ($ts === false) {
return $created;
}
return date('d.m.Y H:i:s', $ts);
}
}