use yii\helpers\ArrayHelper;
use yii\web\IdentityInterface;
use yii_app\api3\core\validators\PhoneValidator;
-use yii_app\services\SalarySyncService;
/**
* Class Admin
* 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
* @return void
*
* @see EmployeePositionStatus Модель для истории должностей
- * @see SalarySyncService Сервис синхронизации зарплаты
* @see EmployeePayment Модель зарплатных данных
*/
public function afterSave($insert, $changedAttributes)
// Не бросаем исключение, чтобы не прервать сохранение Admin
}
}
- //TODO: разобраться с синхронизацией окладов по должностям
- 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={$newPositionId}", 'salary-sync');
- }
- } catch (\Exception $e) {
- Yii::error("Ошибка автоматической синхронизации оклада для admin_id={$this->id}: " . $e->getMessage(), 'salary-sync');
- // Не бросаем исключение, чтобы не прервать сохранение Admin
- }
- }
}
}
+++ /dev/null
-<?php
-
-namespace yii_app\services;
-
-use Yii;
-use yii_app\records\Admin;
-use yii_app\records\AdminGroup;
-use yii_app\records\EmployeePayment;
-use yii_app\records\EmployeePosition;
-
-/**
- * Сервис для синхронизации окладов между EmployeePosition и EmployeePayment
- */
-class SalarySyncService
-{
- /**
- * Синхронизирует оклады для всех сотрудников с заполненным employee_position_id
- * Создает записи EmployeePayment на основе окладов из EmployeePosition
- *
- * @param int|null $creatorId ID пользователя, выполняющего синхронизацию
- * @return array ['success' => bool, 'message' => string, 'created' => int, 'skipped' => int]
- */
- public function syncAllEmployeesFromPositions($creatorId = null): array
- {
- if ($creatorId === null) {
- $creatorId = Yii::$app->user->id ?? null;
- }
-
- if (!$creatorId) {
- return [
- 'success' => false,
- 'message' => 'Не указан ID пользователя для создания записей',
- 'created' => 0,
- 'skipped' => 0
- ];
- }
-
- $created = 0;
- $skipped = 0;
- $errors = [];
-
- // Получаем всех сотрудников с заполненным employee_position_id, не уволенных
- $admins = Admin::find()
- ->where(['IS NOT', 'employee_position_id', null])
- ->andWhere(['!=', 'group_id', AdminGroup::GROUP_FIRED])
- ->all();
-
- foreach ($admins as $admin) {
- $position = EmployeePosition::findOne($admin->employee_position_id);
-
- if (!$position) {
- $skipped++;
- continue;
- }
-
- // Проверяем, заполнены ли оклады для должности
- if ($position->monthly_salary === null || $position->daily_payment === null) {
- $skipped++;
- continue;
- }
-
- // Проверяем, есть ли уже запись EmployeePayment для этого сотрудника
- $existingPayment = EmployeePayment::find()
- ->where(['admin_id' => $admin->id])
- ->orderBy(['date' => SORT_DESC])
- ->one();
-
- // Если запись уже есть, создаем новую с датой следующего месяца
- $date = date('Y-m-01', strtotime('+1 month'));
-
- // Проверяем, нет ли уже записи на эту дату
- $paymentOnDate = EmployeePayment::find()
- ->where(['admin_id' => $admin->id, 'date' => $date])
- ->one();
-
- if ($paymentOnDate) {
- $skipped++;
- continue;
- }
-
- // Создаем новую запись
- $payment = new EmployeePayment();
- $payment->admin_id = $admin->id;
- $payment->admin_group_id = $admin->group_id;
- $payment->employee_position_id = $position->id;
- $payment->monthly_salary = $position->monthly_salary;
- $payment->daily_payment = $position->daily_payment;
- $payment->date = $date;
- $payment->creator_id = $creatorId;
-
- if ($payment->save()) {
- $created++;
- } else {
- $skipped++;
- $errors[] = "Ошибка для сотрудника {$admin->name} (ID: {$admin->id}): " . implode(', ', $payment->getFirstErrors());
- }
- }
-
- $message = "Синхронизация завершена. Создано записей: {$created}, пропущено: {$skipped}";
- if (!empty($errors)) {
- $message .= ". Ошибки: " . implode('; ', array_slice($errors, 0, 5));
- }
-
- return [
- 'success' => true,
- 'message' => $message,
- 'created' => $created,
- 'skipped' => $skipped,
- 'errors' => $errors
- ];
- }
-
- /**
- * Создает или обновляет запись EmployeePayment для сотрудника на основе его должности
- * Дата устанавливается первым числом текущего месяца
- * Если запись за этот месяц уже существует, она обновляется
- *
- * @param int $adminId ID сотрудника
- * @param int|null $creatorId ID пользователя, создающего запись
- * @param string|null $date Дата записи (по умолчанию первое число текущего месяца)
- * @return EmployeePayment|null Созданная или обновленная запись или null в случае ошибки
- */
- public function createPaymentFromPosition($adminId, $creatorId = null, $date = null): ?EmployeePayment
- {
- if ($creatorId === null) {
- $creatorId = Yii::$app->user->id ?? null;
- }
-
- if (!$creatorId) {
- return null;
- }
- // Первое число следующего месяца
- $date = date('Y-m-01', strtotime('+1 month'));
-
- $admin = Admin::findOne($adminId);
- if (!$admin || !$admin->employee_position_id) {
- return null;
- }
-
- $position = EmployeePosition::findOne($admin->employee_position_id);
- if (!$position || $position->monthly_salary === null || $position->daily_payment === null) {
- return null;
- }
-
- // Проверяем, есть ли уже запись за этот месяц и год
- $year = date('Y', strtotime($date));
- $month = date('m', strtotime($date));
-
- $existingPayment = EmployeePayment::find()
- ->where(['admin_id' => $adminId])
- ->andWhere(['EXTRACT(YEAR FROM date)' => $year])
- ->andWhere(['EXTRACT(MONTH FROM date)' => $month])
- ->one();
-
- if ($existingPayment) {
- // Обновляем существующую запись
- $existingPayment->admin_group_id = $admin->group_id;
- $existingPayment->employee_position_id = $position->id;
- $existingPayment->monthly_salary = $position->monthly_salary;
- $existingPayment->daily_payment = $position->daily_payment;
- $existingPayment->date = $date;
- $existingPayment->creator_id = $creatorId;
-
- if ($existingPayment->save()) {
- return $existingPayment;
- }
- return null;
- }
-
- // Создаем новую запись
- $payment = new EmployeePayment();
- $payment->admin_id = $adminId;
- $payment->admin_group_id = $admin->group_id;
- $payment->employee_position_id = $position->id;
- $payment->monthly_salary = $position->monthly_salary;
- $payment->daily_payment = $position->daily_payment;
- $payment->date = $date;
- $payment->creator_id = $creatorId;
-
- if ($payment->save()) {
- return $payment;
- }
-
- return null;
- }
-
- /**
- * Синхронизирует оклады для всех сотрудников с указанной должностью
- * Используется при изменении оклада должности
- * Дата устанавливается первым числом текущего месяца
- * Если запись за этот месяц уже существует, она обновляется
- *
- * @param int $positionId ID должности
- * @param int|null $creatorId ID пользователя, изменившего оклад
- * @return array ['success' => bool, 'message' => string, 'created' => int, 'updated' => int]
- */
- public function syncEmployeesByPosition($positionId, $creatorId = null): array
- {
- if ($creatorId === null) {
- $creatorId = Yii::$app->user->id ?? null;
- }
-
- if (!$creatorId) {
- return [
- 'success' => false,
- 'message' => 'Не указан ID пользователя для создания записей',
- 'created' => 0,
- 'updated' => 0
- ];
- }
-
- $position = EmployeePosition::findOne($positionId);
- if (!$position || $position->monthly_salary === null || $position->daily_payment === null) {
- return [
- 'success' => false,
- 'message' => 'Должность не найдена или оклады не заполнены',
- 'created' => 0,
- 'updated' => 0
- ];
- }
-
- $created = 0;
- $updated = 0;
- $date = date('Y-m-01', strtotime('+1 month')); // Первое число следующего месяца
- $year = date('Y');
- $month = date('m');
-
- // Получаем всех сотрудников с этой должностью, не уволенных
- $admins = Admin::find()
- ->where(['employee_position_id' => $positionId])
- ->andWhere(['!=', 'group_id', AdminGroup::GROUP_FIRED])
- ->all();
-
- foreach ($admins as $admin) {
- // Проверяем, есть ли уже запись за этот месяц и год
- $existingPayment = EmployeePayment::find()
- ->where(['admin_id' => $admin->id])
- ->andWhere(['EXTRACT(YEAR FROM date)' => $year])
- ->andWhere(['EXTRACT(MONTH FROM date)' => $month])
- ->one();
-
- if ($existingPayment) {
- // Обновляем существующую запись
- $existingPayment->admin_group_id = $admin->group_id;
- $existingPayment->employee_position_id = $position->id;
- $existingPayment->monthly_salary = $position->monthly_salary;
- $existingPayment->daily_payment = $position->daily_payment;
- $existingPayment->date = $date;
- $existingPayment->creator_id = $creatorId;
-
- if ($existingPayment->save()) {
- $updated++;
- }
- } else {
- // Создаем новую запись
- $payment = new EmployeePayment();
- $payment->admin_id = $admin->id;
- $payment->admin_group_id = $admin->group_id;
- $payment->employee_position_id = $position->id;
- $payment->monthly_salary = $position->monthly_salary;
- $payment->daily_payment = $position->daily_payment;
- $payment->date = $date;
- $payment->creator_id = $creatorId;
-
- if ($payment->save()) {
- $created++;
- }
- }
- }
-
- $message = "Синхронизация завершена. Создано записей: {$created}, обновлено: {$updated}";
- return [
- 'success' => true,
- 'message' => $message,
- 'created' => $created,
- 'updated' => $updated
- ];
- }
-}
-