From: Vladimir Fomichev Date: Wed, 3 Dec 2025 12:12:17 +0000 (+0300) Subject: Правки по MR X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=d58f6c8d99144e69337c3148cd7e14827e5707a3;p=erp24_rep%2Fyii-erp24%2F.git Правки по MR --- diff --git a/erp24/records/Admin.php b/erp24/records/Admin.php index 28a99f79..513cf91d 100755 --- a/erp24/records/Admin.php +++ b/erp24/records/Admin.php @@ -768,63 +768,88 @@ class Admin extends ActiveRecord implements IdentityInterface } /** - * После сохранения проверяем, изменилась ли должность, и создаем запись EmployeePayment - * А также синхронизируем историю в EmployeePositionStatus (гибридный подход) + * Обработка после сохранения записи сотрудника (Admin) + * + * Метод выполняет две критически важные автоматические синхронизации: + * + * 1. **ИСТОРИЯ ГРЕЙДОВ (EmployeePositionStatus)** - Гибридный подход + * - Отслеживает изменения должности сотрудника во времени + * - При изменении employee_position_id: + * a) Закрывает все активные записи истории (closed_at = NOW) + * b) Создаёт новую запись с текущей должностью (closed_at = NULL) + * - Важно: история НЕ создаётся при первом сохранении нового сотрудника, + * так как нет предыдущей позиции для закрытия + * + * 2. **СИНХРОНИЗАЦИЯ ЗАРПЛАТЫ (EmployeePayment)** + * - Автоматически создаёт запись о зарплате через SalarySyncService + * - Срабатывает когда: + * a) Установлена должность (employee_position_id не NULL) + * b) Должность изменилась ИЛИ создаётся новая запись ($insert) + * c) Сотрудник не уволен (group_id != AdminGroup::GROUP_FIRED) + * - В отличие от истории грейдов, работает и для новых записей + * + * **Обработка ошибок:** + * - Все операции обёрнуты в try-catch + * - Ошибки логируются, но НЕ прерывают сохранение Admin + * - Это гарантирует, что основная операция (сохранение сотрудника) всегда завершится + * + * **Логирование:** + * - История грейдов: категория 'grade-sync' + * - Синхронизация зарплаты: категория 'salary-sync' + * + * @param bool $insert Флаг вставки новой записи (true) или обновления (false) + * @param array $changedAttributes Массив изменённых атрибутов в формате ['attribute' => 'oldValue'] + * @return void + * + * @see EmployeePositionStatus Модель для истории должностей + * @see SalarySyncService Сервис синхронизации зарплаты + * @see EmployeePayment Модель зарплатных данных */ public function afterSave($insert, $changedAttributes) { parent::afterSave($insert, $changedAttributes); - // Проверяем, изменилось ли поле employee_position_id - // Важно: проверяем как через changedAttributes, так и через сравнение значений + // Создаём историю только если должность реально изменилась и новая должность установлена $oldPositionId = $changedAttributes['employee_position_id'] ?? null; $newPositionId = $this->employee_position_id; - // Если это новая запись или изменилось employee_position_id - if ($insert || ($oldPositionId != $newPositionId && array_key_exists('employee_position_id', $changedAttributes))) { - // === ГИБРИДНЫЙ ПОДХОД: Ведение истории в EmployeePositionStatus === - if ($newPositionId && $oldPositionId != $newPositionId) { - try { - // Закрыть ВСЕ активные записи для этого админа (на случай если есть несколько активных) - $closedCount = EmployeePositionStatus::updateAll( - ['closed_at' => date('Y-m-d H:i:s')], - ['admin_id' => $this->id, 'closed_at' => null] - ); - - Yii::info("Закрыто активных записей для admin_id={$this->id}: {$closedCount}", 'grade-sync'); - - // Создать новую запись об истории (даже если возвращаемся к предыдущей должности) - $positionStatus = new EmployeePositionStatus(); - $positionStatus->admin_id = $this->id; - $positionStatus->position_id = $newPositionId; - $positionStatus->created_at = date('Y-m-d H:i:s'); - $positionStatus->closed_at = null; // Явно устанавливаем null для новой активной записи - - if (!$positionStatus->save(false)) { - Yii::error("Не удалось сохранить EmployeePositionStatus для admin_id={$this->id}, position_id={$newPositionId}. Ошибки: " . - implode(', ', $positionStatus->getFirstErrors()), 'grade-sync'); - } else { - Yii::info("История грейда обновлена для admin_id={$this->id}, position_id={$newPositionId} (старая: " . ($oldPositionId ?? 'null') . ")", 'grade-sync'); - } - } catch (\Exception $e) { - Yii::error("Ошибка при обновлении истории EmployeePositionStatus для admin_id={$this->id}: " . $e->getMessage() . - " Trace: " . $e->getTraceAsString(), 'grade-sync'); - // Не бросаем исключение, чтобы не прервать сохранение Admin + if ($newPositionId && $oldPositionId != $newPositionId && array_key_exists('employee_position_id', $changedAttributes)) { + try { + // Закрыть ВСЕ активные записи для этого админа (на случай если есть несколько активных) + $closedCount = EmployeePositionStatus::updateAll( + ['closed_at' => date('Y-m-d H:i:s')], + ['admin_id' => $this->id, 'closed_at' => null] + ); + + Yii::info("Закрыто активных записей для admin_id={$this->id}: {$closedCount}", 'grade-sync'); + + // Создать новую запись об истории (даже если возвращаемся к предыдущей должности) + $positionStatus = new EmployeePositionStatus(); + $positionStatus->admin_id = $this->id; + $positionStatus->position_id = $newPositionId; + $positionStatus->created_at = date('Y-m-d H:i:s'); + $positionStatus->closed_at = null; // Явно устанавливаем null для новой активной записи + + if (!$positionStatus->save()) { + Yii::error("Не удалось сохранить EmployeePositionStatus для admin_id={$this->id}, position_id={$newPositionId}. Ошибки: " . + implode(', ', $positionStatus->getFirstErrors()), 'grade-sync'); + } else { + Yii::info("История грейда обновлена для admin_id={$this->id}, position_id={$newPositionId} (старая: " . ($oldPositionId ?? 'null') . ")", 'grade-sync'); } + } catch (\Exception $e) { + Yii::error("Ошибка при обновлении истории EmployeePositionStatus для admin_id={$this->id}: " . $e->getMessage() . + " Trace: " . $e->getTraceAsString(), 'grade-sync'); + // Не бросаем исключение, чтобы не прервать сохранение Admin } } - // === СИНХРОНИЗАЦИЯ ЗАРПЛАТЫ === - // Если должность была добавлена или изменена, и сотрудник не уволен - $oldPositionIdForSalary = $changedAttributes['employee_position_id'] ?? null; - $newPositionIdForSalary = $this->employee_position_id; - if ($newPositionIdForSalary && ($oldPositionIdForSalary != $newPositionIdForSalary || $insert) && $this->group_id != AdminGroup::GROUP_FIRED) { + if ($newPositionId && ($oldPositionId != $newPositionId || $insert) && $this->group_id != AdminGroup::GROUP_FIRED) { try { $syncService = new SalarySyncService(); $result = $syncService->createPaymentFromPosition($this->id); if ($result) { - Yii::info("Автоматически создана запись EmployeePayment для admin_id={$this->id}, position_id={$newPositionIdForSalary}", 'salary-sync'); + Yii::info("Автоматически создана запись EmployeePayment для admin_id={$this->id}, position_id={$newPositionId}", 'salary-sync'); } } catch (\Exception $e) { Yii::error("Ошибка автоматической синхронизации оклада для admin_id={$this->id}: " . $e->getMessage(), 'salary-sync');