From 7481edd57f61f462b7c83bc9a936cd2fc93bd3bd Mon Sep 17 00:00:00 2001 From: Vladimir Fomichev Date: Wed, 19 Nov 2025 18:12:23 +0300 Subject: [PATCH] =?utf8?q?=D0=9F=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=BF?= =?utf8?q?=D0=BE=20=D0=B3=D1=80=D0=B5=D0=B9=D0=B4=D0=B0=D0=BC=20=D0=B8=20?= =?utf8?q?=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/actions/grade/AdminUpdateAction.php | 229 ++++++++++++++++++--- erp24/actions/grade/UpdateAction.php | 99 ++++++--- erp24/api3/core/services/ReportService.php | 2 +- erp24/commands/AdminController.php | 219 ++++++++++++++++++++ erp24/records/Admin.php | 66 +++--- erp24/views/grade/admin-update.php | 85 ++++++-- erp24/views/grade/update.php | 54 ++++- erp24/views/store-staffing/_form.php | 2 +- erp24/views/store-staffing/index.php | 2 +- 9 files changed, 651 insertions(+), 107 deletions(-) diff --git a/erp24/actions/grade/AdminUpdateAction.php b/erp24/actions/grade/AdminUpdateAction.php index 4aca3a8d..fbe84800 100644 --- a/erp24/actions/grade/AdminUpdateAction.php +++ b/erp24/actions/grade/AdminUpdateAction.php @@ -20,7 +20,8 @@ class AdminUpdateAction extends Action /** * Обновляет данные администратора, включая назначение должности и группы * - * Для специальных групп формирует group_name из EmployeePosition и смены. + * Для рабочих групп (group_id = 50 и все группы с parent_id = 50) при заполнении + * employee_position_id заполняет group_name значением из AdminGroup->name. * Для остальных групп использует текстовое поле custom_position. * * @param int $id ID администратора @@ -51,9 +52,11 @@ class AdminUpdateAction extends Action unset($attributes['storeArray']); $attributes['store_arr_guid'] = empty($attributes['storeGuidArray']) ? '' : implode(',', $attributes['storeGuidArray']); unset($attributes['storeGuidArray']); - if (!Yii::$app->user->can("updateAdminSettingsGroupId", ['group_id' => $attributes['group_id']])) { - unset($attributes['group_id']); - } + + // Сохраняем флаг, можно ли изменять group_id вручную + $originalGroupId = $attributes['group_id'] ?? $model->group_id; + $canChangeGroupId = Yii::$app->user->can("updateAdminSettingsGroupId", ['group_id' => $originalGroupId]); + if (!Yii::$app->user->can("updateAdminSettingsOnlyByHrAndAdministrator")) { unset($attributes['store_dostup_all']); unset($attributes['store_id']); @@ -105,23 +108,94 @@ class AdminUpdateAction extends Action $specialGroups[] = $workersGroup->id; } + // Определяем рабочие группы: group_id = 50 и все группы с parent_id = 50 + $workGroups = [AdminGroup::GROUP_ADMINISTRATORS]; // 50 + $childGroups = AdminGroup::find()->where(['parent_id' => AdminGroup::GROUP_ADMINISTRATORS])->all(); + foreach ($childGroups as $childGroup) { + $workGroups[] = $childGroup->id; + } + $isSpecialGroup = in_array((int)$attributes['group_id'], $specialGroups); + $isWorkGroup = in_array((int)$attributes['group_id'], $workGroups); + // Сохраняем старое значение employee_position_id для проверки изменения + $oldPositionId = $model->employee_position_id; + $oldGroupId = $model->group_id; + if ($isSpecialGroup) { - // Для специальных групп формируем group_name из employee_position + shift - if (!empty($attributes['employee_position_id'])) { - $employeePosition = EmployeePosition::findOne($attributes['employee_position_id']); - if ($employeePosition) { - $groupName = $employeePosition->name; - // Если выбрана смена, добавляем её к названию - if (!empty($attributes['shift'])) { - $groupName .= ' ' . $attributes['shift']; + if ($isWorkGroup) { + // Для рабочих групп (group_id = 50 и parent_id = 50) при заполнении employee_position_id + // заполняем group_name значением из AdminGroup->name + if (!empty($attributes['employee_position_id'])) { + $adminGroup = AdminGroup::findOne($attributes['group_id']); + if ($adminGroup) { + $attributes['group_name'] = $adminGroup->name; + } + } + } else { + // Для остальных специальных групп синхронизируем группу с грейдом + if (!empty($attributes['employee_position_id'])) { + $employeePosition = EmployeePosition::findOne($attributes['employee_position_id']); + if ($employeePosition) { + $positionName = trim($employeePosition->name); + $currentGroup = AdminGroup::findOne($attributes['group_id']); + $currentGroupName = $currentGroup ? trim($currentGroup->name) : ''; + + // Проверяем частичное совпадение группы и грейда + $hasPartialMatch = false; + if ($currentGroupName) { + // Нормализуем названия для сравнения (убираем "день", "ночь" и лишние пробелы) + $normalizedGroupName = mb_strtolower(preg_replace('/\s*(день|ночь)\s*/iu', '', $currentGroupName)); + $normalizedPositionName = mb_strtolower($positionName); + + // Проверяем частичное совпадение + if (mb_strpos($normalizedGroupName, $normalizedPositionName) !== false || + mb_strpos($normalizedPositionName, $normalizedGroupName) !== false) { + $hasPartialMatch = true; + } + } + + if ($hasPartialMatch) { + // Если группа и грейд частично совпадают - оставляем группу + $attributes['group_name'] = $positionName; + } else { + // Если не совпадают - ищем группу по названию грейда + $matchingGroup = self::findGroupByPositionName($positionName, $attributes['group_id']); + + if ($matchingGroup) { + // Найдена группа - меняем группу (только если есть права или группа не менялась вручную) + if ($canChangeGroupId || $originalGroupId == $oldGroupId) { + $attributes['group_id'] = $matchingGroup->id; + $attributes['group_name'] = $matchingGroup->name; + + if ($oldGroupId != $matchingGroup->id) { + Yii::$app->session->setFlash('info', + "Группа изменена с '{$currentGroupName}' на '{$matchingGroup->name}' в соответствии с грейдом '{$positionName}'" + ); + } + } else { + // Нет прав на изменение группы - только обновляем group_name + $attributes['group_name'] = $positionName; + Yii::$app->session->setFlash('warning', + "Грейд '{$positionName}' не соответствует группе '{$currentGroupName}'. Недостаточно прав для автоматического изменения группы." + ); + } + } else { + // Группа не найдена - оставляем текущую группу + $attributes['group_name'] = $positionName; + + if ($oldGroupId != $attributes['group_id']) { + Yii::$app->session->setFlash('warning', + "Группа для грейда '{$positionName}' не найдена. Текущая группа сохранена." + ); + } + } + } } - $attributes['group_name'] = $groupName; } } - // Очищаем shift для не-специальных групп + // Очищаем shift unset($attributes['shift']); } else { // Для остальных групп group_name берем из текстового поля @@ -134,6 +208,12 @@ class AdminUpdateAction extends Action unset($attributes['shift']); } + // Проверяем права на изменение group_id после всех автоматических изменений + if (!$canChangeGroupId) { + // Если нет прав - убираем group_id из атрибутов, чтобы не изменить его + unset($attributes['group_id']); + } + $model->setAttributes($attributes, false); if (Yii::$app->user->can("manageAvatarka", ['id' => $model->id])) { @@ -212,25 +292,114 @@ class AdminUpdateAction extends Action $companies = ArrayHelper::map(Companies::find()->all(), 'id', 'name'); - // Извлекаем смену из group_name, если она там есть - $shift = ''; - if ($model->employee_position_id && !empty($model->group_name)) { - $position = EmployeePosition::findOne($model->employee_position_id); - if ($position) { - $positionName = $position->name; - // Проверяем, что group_name начинается с названия должности и заканчивается на " день" или " ночь" - if (mb_strpos($model->group_name, $positionName) === 0) { - $remaining = mb_substr($model->group_name, mb_strlen($positionName)); - if ($remaining === ' день') { - $shift = 'день'; - } elseif ($remaining === ' ночь') { - $shift = 'ночь'; + return $this->controller->render('admin-update', compact('model', 'adminGroups', 'admins', + 'cityStores', 'adminHistoryCategories', 'companies', 'positions')); + } + + /** + * Поиск группы администраторов по названию грейда (должности) + * + * @param string $positionName Название грейда + * @param int|null $currentGroupId ID текущей группы (для приоритета) + * @return AdminGroup|null Найденная группа или null + */ + private static function findGroupByPositionName($positionName, $currentGroupId = null) { + $normalizedPositionName = mb_strtolower(trim($positionName)); + $exactMatches = []; // Точные совпадения (после нормализации) + $containsMatches = []; // Группы, содержащие полное название грейда + $partialMatches = []; // Частичные совпадения + + // Ищем все группы, соответствующие грейду + $groups = AdminGroup::find()->all(); + + foreach ($groups as $group) { + $groupName = mb_strtolower(trim($group->name)); + $normalizedGroupName = preg_replace('/\s*(день|ночь)\s*/iu', '', $groupName); + + // 1. Проверяем точное совпадение после нормализации + if ($normalizedGroupName === $normalizedPositionName) { + $exactMatches[] = $group; + continue; + } + + // 2. Проверяем, содержит ли группа полное название грейда (более специфичное совпадение) + if (mb_strpos($normalizedGroupName, $normalizedPositionName) === 0 || + mb_strpos($normalizedGroupName, ' ' . $normalizedPositionName) !== false || + mb_strpos($normalizedGroupName, $normalizedPositionName . ' ') !== false) { + $containsMatches[] = $group; + continue; + } + + // 3. Проверяем обратное - содержит ли грейд название группы (менее специфичное) + if (mb_strpos($normalizedPositionName, $normalizedGroupName) !== false) { + $partialMatches[] = $group; + } + } + + // Приоритет выбора: + // 1. Точные совпадения + if (!empty($exactMatches)) { + // Если текущая группа в точных совпадениях - оставляем её + if ($currentGroupId) { + foreach ($exactMatches as $group) { + if ($group->id == $currentGroupId) { + return $group; } } } + // Ищем базовую группу без "день"/"ночь" среди точных совпадений + foreach ($exactMatches as $group) { + $groupName = mb_strtolower(trim($group->name)); + if (mb_stripos($groupName, 'день') === false && mb_stripos($groupName, 'ночь') === false) { + return $group; + } + } + // Если все с "день"/"ночь" - возвращаем первую + return $exactMatches[0]; } - - return $this->controller->render('admin-update', compact('model', 'adminGroups', 'admins', - 'cityStores', 'adminHistoryCategories', 'companies', 'positions', 'shift')); + + // 2. Группы, содержащие полное название грейда (более специфичные) + if (!empty($containsMatches)) { + // Если текущая группа в списке - оставляем её + if ($currentGroupId) { + foreach ($containsMatches as $group) { + if ($group->id == $currentGroupId) { + return $group; + } + } + } + // Ищем базовую группу без "день"/"ночь" + foreach ($containsMatches as $group) { + $groupName = mb_strtolower(trim($group->name)); + if (mb_stripos($groupName, 'день') === false && mb_stripos($groupName, 'ночь') === false) { + return $group; + } + } + // Если все с "день"/"ночь" - возвращаем первую + return $containsMatches[0]; + } + + // 3. Частичные совпадения (менее приоритетные) + if (!empty($partialMatches)) { + // Если текущая группа в списке - оставляем её + if ($currentGroupId) { + foreach ($partialMatches as $group) { + if ($group->id == $currentGroupId) { + return $group; + } + } + } + // Ищем базовую группу без "день"/"ночь" + foreach ($partialMatches as $group) { + $groupName = mb_strtolower(trim($group->name)); + if (mb_stripos($groupName, 'день') === false && mb_stripos($groupName, 'ночь') === false) { + return $group; + } + } + // Если все с "день"/"ночь" - возвращаем первую + return $partialMatches[0]; + } + + return null; } } diff --git a/erp24/actions/grade/UpdateAction.php b/erp24/actions/grade/UpdateAction.php index 2b35edef..b5433017 100755 --- a/erp24/actions/grade/UpdateAction.php +++ b/erp24/actions/grade/UpdateAction.php @@ -7,6 +7,7 @@ use yii\base\Action; use yii\base\DynamicModel; use yii\helpers\ArrayHelper; use yii_app\records\Admin; +use yii_app\records\AdminGroup; use yii_app\records\EmployeePosition; use yii_app\records\EmployeePositionSkill; use yii_app\records\EmployeePositionStatus; @@ -21,57 +22,97 @@ class UpdateAction extends Action $positions = EmployeePosition::find()->orderBy('posit')->all(); - $modelPosition = DynamicModel::validateData(['position_id' => null, 'shift' => '', 'action' => 'updatePosition'], - [['position_id', 'integer'], ['shift', 'string'], ['action', 'string']]); + $modelPosition = DynamicModel::validateData(['position_id' => null, 'action' => 'updatePosition'], + [['position_id', 'integer'], ['action', 'string']]); if ($modelPosition->load(Yii::$app->request->post()) && $modelPosition->action == 'updatePosition') { + // Сохраняем старое значение для проверки изменения + $oldPositionId = $admin->employee_position_id; + // Гибридный подход: обновляем основное поле (источник истины) + // Важно: присваиваем значение напрямую, чтобы Yii2 отследил изменение $admin->employee_position_id = $modelPosition->position_id; - // Синхронизируем group_name с выбранной должностью и сменой (как в AdminUpdateAction) + // Явно помечаем атрибут как измененный для корректной работы afterSave + if ($oldPositionId != $admin->employee_position_id) { + $admin->markAttributeDirty('employee_position_id'); + } + + // Определяем рабочие группы: group_id = 50 и все группы с parent_id = 50 + $workGroups = [AdminGroup::GROUP_ADMINISTRATORS]; // 50 + $childGroups = AdminGroup::find()->where(['parent_id' => AdminGroup::GROUP_ADMINISTRATORS])->all(); + foreach ($childGroups as $childGroup) { + $workGroups[] = $childGroup->id; + } + + $isWorkGroup = in_array((int)$admin->group_id, $workGroups); + + // Проверяем соответствие группы и грейда (без автоматической смены группы) if ($modelPosition->position_id) { $position = EmployeePosition::findOne($modelPosition->position_id); + if ($position) { - $groupName = $position->name; - // Если выбрана смена, добавляем её к названию - if (!empty($modelPosition->shift)) { - $groupName .= ' ' . $modelPosition->shift; + $positionName = trim($position->name); + $currentGroup = AdminGroup::findOne($admin->group_id); + $currentGroupName = $currentGroup ? trim($currentGroup->name) : ''; + + // Проверяем частичное совпадение группы и грейда + $hasPartialMatch = false; + if ($currentGroupName) { + // Нормализуем названия для сравнения (убираем "день", "ночь" и лишние пробелы) + $normalizedGroupName = mb_strtolower(preg_replace('/\s*(день|ночь)\s*/iu', '', $currentGroupName)); + $normalizedPositionName = mb_strtolower($positionName); + + // Проверяем частичное совпадение + if (mb_strpos($normalizedGroupName, $normalizedPositionName) !== false || + mb_strpos($normalizedPositionName, $normalizedGroupName) !== false) { + $hasPartialMatch = true; + } + } + + // Обновляем group_name в зависимости от типа группы + if ($isWorkGroup && $currentGroup) { + $admin->group_name = $currentGroup->name; + } else { + $admin->group_name = $positionName; + } + + // Показываем уведомление, если группа не соответствует грейду + if ($oldPositionId != $modelPosition->position_id) { + $oldPosition = EmployeePosition::findOne($oldPositionId); + $oldPositionName = $oldPosition ? $oldPosition->name : 'не установлен'; + + if ($hasPartialMatch) { + Yii::$app->session->setFlash('success', + "Грейд успешно изменен с '{$oldPositionName}' на '{$positionName}'." + ); + } else { + // Группа не соответствует грейду - предупреждаем + Yii::$app->session->setFlash('warning', + "Грейд изменен с '{$oldPositionName}' на '{$positionName}'. " . + "⚠️ Внимание: текущая группа '{$currentGroupName}' не соответствует новому грейду. " . + "Пожалуйста, измените группу в разделе 'Редактирование данных пользователя'." + ); + } } - $admin->group_name = $groupName; } } // Сохраняем Admin, что автоматически вызовет afterSave // и создаст запись в EmployeePositionStatus для истории if ($admin->save(false)) { - Yii::$app->session->setFlash('success', 'Грейд успешно сохранён'); + if ($oldPositionId == $modelPosition->position_id) { + // Если ничего не изменилось - показываем стандартное сообщение + Yii::$app->session->setFlash('success', 'Данные успешно сохранены'); + } } else { - Yii::$app->session->setFlash('error', 'Ошибка при сохранении грейда'); + Yii::$app->session->setFlash('error', 'Ошибка при сохранении грейда: ' . implode(', ', $admin->getFirstErrors())); } return $this->controller->redirect(['/grade/update', 'admin_id' => $admin_id]); } else { // Показываем текущий грейд из основного поля (источник истины) $modelPosition->position_id = $admin->employee_position_id; $modelPosition->action = 'updatePosition'; - - // Извлекаем смену из group_name, если она там есть - $shift = ''; - if ($admin->employee_position_id && !empty($admin->group_name)) { - $position = EmployeePosition::findOne($admin->employee_position_id); - if ($position) { - $positionName = $position->name; - // Проверяем, что group_name начинается с названия должности и заканчивается на " день" или " ночь" - if (mb_strpos($admin->group_name, $positionName) === 0) { - $remaining = mb_substr($admin->group_name, mb_strlen($positionName)); - if ($remaining === ' день') { - $shift = 'день'; - } elseif ($remaining === ' ночь') { - $shift = 'ночь'; - } - } - } - } - $modelPosition->shift = $shift; $modelPosition->validate(); } diff --git a/erp24/api3/core/services/ReportService.php b/erp24/api3/core/services/ReportService.php index 5e4cf028..7d183959 100644 --- a/erp24/api3/core/services/ReportService.php +++ b/erp24/api3/core/services/ReportService.php @@ -27,7 +27,7 @@ class ReportService * Получает средний уровень обученности по штатному расписанию магазина * * @param int $storeId ID магазина - * @return float|null Средний уровень (посит) или null если нет данных + * @return float|null Средний уровень (posit) или null если нет данных */ private function getStoreStaffingSkillScore($storeId) { diff --git a/erp24/commands/AdminController.php b/erp24/commands/AdminController.php index 8180d1e7..508d7e76 100644 --- a/erp24/commands/AdminController.php +++ b/erp24/commands/AdminController.php @@ -5,7 +5,9 @@ namespace yii_app\commands; use yii\console\Controller; use yii\helpers\ArrayHelper; use yii_app\records\Admin; +use yii_app\records\AdminGroup; use yii_app\records\AdminStores; +use yii_app\records\EmployeePosition; use yii_app\records\ExportImportTable; class AdminController extends Controller { @@ -48,4 +50,221 @@ class AdminController extends Controller { } } } + + /** + * Очистка group_name от подстрок "ночь" и "день" для записей с заполненным employee_position_id + * + * Команда выполняет: + * 1. Выборку Admin записей с заполненным employee_position_id и наличием "ночь"/"день" в group_name + * 2. Сравнение group_name с AdminGroup->name + * 3. Очистку group_name от "ночь"/"день" при необходимости + * + * Примеры работы: + * - "Старший флорист ночь" + группа "Старший флорист" → "Старший флорист" + * - "Администратор день" + группа "Администратор" → "Администратор" + * - "Новичок ночь" + группа "Помощник флориста" → "Новичок" + */ + public function actionCleanGroupNameFromShift() { + $this->stdout("=== Начало очистки group_name от подстрок 'ночь' и 'день' ===\n", \yii\helpers\Console::FG_CYAN); + $this->stdout("Дата запуска: " . date('Y-m-d H:i:s') . "\n\n"); + + // Выборка записей с заполненным employee_position_id и наличием "ночь" или "день" в group_name + $admins = Admin::find() + ->where(['IS NOT', 'employee_position_id', null]) + ->andWhere(['!=', 'employee_position_id', 0]) + ->andWhere([ + 'OR', + ['ILIKE', 'group_name', 'ночь'], + ['ILIKE', 'group_name', 'день'] + ]) + ->all(); + + $totalCount = count($admins); + $this->stdout("Найдено записей для обработки: {$totalCount}\n\n", \yii\helpers\Console::FG_YELLOW); + + if ($totalCount === 0) { + $this->stdout("Нет записей для обработки. Завершение работы.\n", \yii\helpers\Console::FG_GREEN); + return; + } + + // Оптимизация: собираем все уникальные group_id и employee_position_id и загружаем одним запросом + $groupIds = []; + $positionIds = []; + foreach ($admins as $admin) { + if ($admin->group_id && !in_array($admin->group_id, $groupIds)) { + $groupIds[] = $admin->group_id; + } + if ($admin->employee_position_id && !in_array($admin->employee_position_id, $positionIds)) { + $positionIds[] = $admin->employee_position_id; + } + } + + // Загружаем все нужные группы одним запросом + $adminGroups = AdminGroup::find() + ->where(['id' => $groupIds]) + ->indexBy('id') + ->all(); + + // Загружаем все нужные должности одним запросом + $employeePositions = EmployeePosition::find() + ->where(['id' => $positionIds]) + ->indexBy('id') + ->all(); + + $this->stdout("Загружено групп для обработки: " . count($adminGroups) . "\n", \yii\helpers\Console::FG_CYAN); + $this->stdout("Загружено должностей для обработки: " . count($employeePositions) . "\n\n", \yii\helpers\Console::FG_CYAN); + + $processedCount = 0; + $skippedCount = 0; + $updatedCount = 0; + $errors = []; + + foreach ($admins as $index => $admin) { + $processedCount++; + $this->stdout("--- Обработка записи {$processedCount}/{$totalCount} ---\n", \yii\helpers\Console::FG_CYAN); + $this->stdout("ID: {$admin->id}\n"); + $this->stdout("Имя: {$admin->name}\n"); + $this->stdout("Текущий group_name: '{$admin->group_name}'\n"); + $this->stdout("group_id: {$admin->group_id}\n"); + $this->stdout("employee_position_id: {$admin->employee_position_id}\n"); + + // Получаем группу администратора из словаря + $adminGroup = $adminGroups[$admin->group_id] ?? null; + + if (!$adminGroup) { + $errorMsg = "Группа с ID {$admin->group_id} не найдена"; + $this->stdout("⚠ ОШИБКА: {$errorMsg}\n", \yii\helpers\Console::FG_RED); + $errors[] = [ + 'admin_id' => $admin->id, + 'admin_name' => $admin->name, + 'error' => $errorMsg + ]; + $this->stdout("\n"); + continue; + } + + $groupName = $adminGroup->name; + $this->stdout("Название группы (AdminGroup->name): '{$groupName}'\n"); + + // Сравниваем group_name и AdminGroup->name + $currentGroupName = trim($admin->group_name); + $targetGroupName = trim($groupName); + + // Если значения одинаковые - пропускаем + if ($currentGroupName === $targetGroupName) { + $this->stdout("✓ Значения идентичны, пропускаем\n", \yii\helpers\Console::FG_GREEN); + $skippedCount++; + $this->stdout("\n"); + continue; + } + + // Определяем, есть ли в текущем group_name подстроки "ночь" или "день" + $hasNight = mb_stripos($currentGroupName, 'ночь') !== false; + $hasDay = mb_stripos($currentGroupName, 'день') !== false; + + if (!$hasNight && !$hasDay) { + $this->stdout("⚠ В group_name нет подстрок 'ночь' или 'день', но значения различаются\n", \yii\helpers\Console::FG_YELLOW); + $this->stdout("Текущее: '{$currentGroupName}'\n"); + $this->stdout("Ожидаемое: '{$targetGroupName}'\n"); + $skippedCount++; + $this->stdout("\n"); + continue; + } + + // Убираем "ночь" или "день" с пробелом (в любом месте строки) + $cleanedName = $currentGroupName; + + // Убираем " ночь" или " ночь" (с пробелом до и/или после) + $cleanedName = preg_replace('/\s*ночь\s*/iu', ' ', $cleanedName); + + // Убираем " день" или " день" (с пробелом до и/или после) + $cleanedName = preg_replace('/\s*день\s*/iu', ' ', $cleanedName); + + // Убираем лишние пробелы и обрезаем + $cleanedName = trim(preg_replace('/\s+/', ' ', $cleanedName)); + + $this->stdout("Очищенное group_name: '{$cleanedName}'\n"); + + // Проверяем, совпадает ли очищенное имя с названием группы + if ($cleanedName === $targetGroupName) { + // Если совпадает - используем название группы + $newGroupName = $targetGroupName; + $this->stdout("✓ Очищенное имя совпадает с названием группы\n", \yii\helpers\Console::FG_GREEN); + } else { + // Если полное несовпадение - проверяем название грейда по employee_position_id + $employeePosition = $employeePositions[$admin->employee_position_id] ?? null; + + if ($employeePosition) { + $positionName = trim($employeePosition->name); + $this->stdout("Название грейда (EmployeePosition->name): '{$positionName}'\n"); + + // Сравниваем EmployeePosition->name с AdminGroup->name + if ($positionName === $targetGroupName) { + // Если совпадают - ставим AdminGroup->name + $newGroupName = $targetGroupName; + $this->stdout("✓ Название грейда совпадает с названием группы, используем группу\n", \yii\helpers\Console::FG_GREEN); + } else { + // Если не совпадают - ставим EmployeePosition->name (название грейда) + $newGroupName = $positionName; + $this->stdout("⚠ Название грейда не совпадает с группой, используем название грейда\n", \yii\helpers\Console::FG_YELLOW); + } + } else { + // Если должность не найдена - оставляем очищенное имя + $newGroupName = $cleanedName; + $this->stdout("⚠ Должность не найдена, оставляем очищенное имя\n", \yii\helpers\Console::FG_YELLOW); + } + } + + // Сохраняем изменения только если значение изменилось + if ($newGroupName !== $currentGroupName) { + $oldValue = $admin->group_name; + $admin->group_name = $newGroupName; + + if ($admin->save(false)) { + $this->stdout("✓ УСПЕШНО ОБНОВЛЕНО:\n", \yii\helpers\Console::FG_GREEN); + $this->stdout(" Было: '{$oldValue}'\n"); + $this->stdout(" Стало: '{$newGroupName}'\n"); + $updatedCount++; + } else { + $errorMsg = "Ошибка при сохранении: " . implode(', ', $admin->getFirstErrors()); + $this->stdout("✗ ОШИБКА СОХРАНЕНИЯ: {$errorMsg}\n", \yii\helpers\Console::FG_RED); + $errors[] = [ + 'admin_id' => $admin->id, + 'admin_name' => $admin->name, + 'old_value' => $oldValue, + 'new_value' => $newGroupName, + 'error' => $errorMsg + ]; + } + } else { + $this->stdout("⚠ Значение не изменилось, пропускаем\n", \yii\helpers\Console::FG_YELLOW); + $skippedCount++; + } + + $this->stdout("\n"); + } + + // Итоговая статистика + $this->stdout("\n=== ИТОГОВАЯ СТАТИСТИКА ===\n", \yii\helpers\Console::FG_CYAN); + $this->stdout("Всего обработано: {$processedCount}\n"); + $this->stdout("Обновлено: {$updatedCount}\n", \yii\helpers\Console::FG_GREEN); + $this->stdout("Пропущено: {$skippedCount}\n", \yii\helpers\Console::FG_YELLOW); + $this->stdout("Ошибок: " . count($errors) . "\n", \yii\helpers\Console::FG_RED); + + if (!empty($errors)) { + $this->stdout("\n=== СПИСОК ОШИБОК ===\n", \yii\helpers\Console::FG_RED); + foreach ($errors as $error) { + $this->stdout("ID: {$error['admin_id']}, Имя: {$error['admin_name']}\n"); + $this->stdout("Ошибка: {$error['error']}\n"); + if (isset($error['old_value'])) { + $this->stdout("Было: '{$error['old_value']}'\n"); + $this->stdout("Должно было стать: '{$error['new_value']}'\n"); + } + $this->stdout("\n"); + } + } + + $this->stdout("\n=== Завершение работы ===\n", \yii\helpers\Console::FG_CYAN); + $this->stdout("Дата завершения: " . date('Y-m-d H:i:s') . "\n"); + } } \ No newline at end of file diff --git a/erp24/records/Admin.php b/erp24/records/Admin.php index 157cc931..28a99f79 100755 --- a/erp24/records/Admin.php +++ b/erp24/records/Admin.php @@ -776,49 +776,59 @@ class Admin extends ActiveRecord implements IdentityInterface parent::afterSave($insert, $changedAttributes); // Проверяем, изменилось ли поле employee_position_id - if (array_key_exists('employee_position_id', $changedAttributes) || $insert) { - $oldPositionId = $changedAttributes['employee_position_id'] ?? null; - $newPositionId = $this->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 { - // Закрыть предыдущую должность, если была - if ($oldPositionId) { - EmployeePositionStatus::updateAll( - ['closed_at' => date('Y-m-d H:i:s')], - ['admin_id' => $this->id, 'closed_at' => null] - ); - } + // Закрыть ВСЕ активные записи для этого админа (на случай если есть несколько активных) + $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->save(false); + $positionStatus->closed_at = null; // Явно устанавливаем null для новой активной записи - Yii::info("История грейда обновлена для admin_id={$this->id}, position_id={$newPositionId}", 'grade-sync'); + 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(), 'grade-sync'); + Yii::error("Ошибка при обновлении истории EmployeePositionStatus для admin_id={$this->id}: " . $e->getMessage() . + " Trace: " . $e->getTraceAsString(), 'grade-sync'); // Не бросаем исключение, чтобы не прервать сохранение Admin } } + } - // === СИНХРОНИЗАЦИЯ ЗАРПЛАТЫ === - // Если должность была добавлена или изменена, и сотрудник не уволен - 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 + // === СИНХРОНИЗАЦИЯ ЗАРПЛАТЫ === + // Если должность была добавлена или изменена, и сотрудник не уволен + $oldPositionIdForSalary = $changedAttributes['employee_position_id'] ?? null; + $newPositionIdForSalary = $this->employee_position_id; + if ($newPositionIdForSalary && ($oldPositionIdForSalary != $newPositionIdForSalary || $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'); } + } catch (\Exception $e) { + Yii::error("Ошибка автоматической синхронизации оклада для admin_id={$this->id}: " . $e->getMessage(), 'salary-sync'); + // Не бросаем исключение, чтобы не прервать сохранение Admin } } } diff --git a/erp24/views/grade/admin-update.php b/erp24/views/grade/admin-update.php index f974f6bc..fcb9abd5 100644 --- a/erp24/views/grade/admin-update.php +++ b/erp24/views/grade/admin-update.php @@ -33,7 +33,7 @@ use yii_app\services\FileService;
- field($model, 'group_id')->dropDownList($adminGroups, ['onchange' => 'changeWorkRateVisibility(this);'])->label(false)) ?> + field($model, 'group_id')->dropDownList($adminGroups, ['onchange' => 'changeWorkRateVisibility(this); checkGradeGroupMatch();', 'id' => 'group-id-select'])->label(false)) ?> ;"> field($model, 'employee_position_id')->dropDownList( - ArrayHelper::map($positions, 'id', 'name'), ['prompt' => 'Выберите должность'] + ArrayHelper::map($positions, 'id', 'name'), ['prompt' => 'Выберите должность', 'id' => 'employee-position-select', 'onchange' => 'checkGradeGroupMatch();'] )->label(false)) ?> - - 'Не выбрана', - 'день' => 'День', - 'ночь' => 'Ночь' - ], ['class' => 'form-control'])) ?> + + +
@@ -260,6 +261,10 @@ use yii_app\services\FileService; .hidden { display: none; } + .has-error select { + border-color: #dc3545 !important; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important; + } \ No newline at end of file diff --git a/erp24/views/grade/update.php b/erp24/views/grade/update.php index 3db96872..6a033451 100755 --- a/erp24/views/grade/update.php +++ b/erp24/views/grade/update.php @@ -37,6 +37,51 @@ use dosamigos\datetimepicker\DateTimePicker;
+ +session->hasFlash('success')): ?> +
+
+ +
+
+ + +session->hasFlash('info')): ?> +
+
+ +
+
+ + +session->hasFlash('warning')): ?> +
+
+ +
+
+ + +session->hasFlash('error')): ?> +
+
+ +
+
+ +
@@ -52,6 +97,9 @@ use dosamigos\datetimepicker\DateTimePicker; ?> Текущий грейд: name) ?>
Установлен с: formatter->asDatetime($currentStatus->created_at, 'php:d.m.Y H:i') : 'дата неизвестна' ?> + adminGroup): ?> +
Текущая группа: adminGroup->name) ?> + ⚠️ Грейд не установлен
Пожалуйста, выберите грейд из списка ниже @@ -74,11 +122,6 @@ use dosamigos\datetimepicker\DateTimePicker; ['' => '-- Выберите грейд --'] + ArrayHelper::map($positions, 'id', 'name'), ['class' => 'form-control'] )->label(false); ?> - field($modelPosition, 'shift')->dropDownList([ - '' => 'Не выбрана', - 'день' => 'День', - 'ночь' => 'Ночь' - ], ['class' => 'form-control'])->label('Смена') ?>
@@ -161,6 +204,7 @@ use dosamigos\datetimepicker\DateTimePicker;
where(['admin_id' => $admin->id]) ->orderBy(['created_at' => SORT_DESC]) diff --git a/erp24/views/store-staffing/_form.php b/erp24/views/store-staffing/_form.php index 96fd3b3b..c0f0e6a7 100644 --- a/erp24/views/store-staffing/_form.php +++ b/erp24/views/store-staffing/_form.php @@ -25,7 +25,7 @@ use yii_app\records\EmployeePosition; ]) ?>
- Грейд (посит) должности: - + Грейд должности (цифровое значение от 0 до 5): -
field($model, 'count')->textInput(['type' => 'number', 'min' => 0.1, 'step' => 0.1]) ?> diff --git a/erp24/views/store-staffing/index.php b/erp24/views/store-staffing/index.php index baed117b..1a090ed7 100644 --- a/erp24/views/store-staffing/index.php +++ b/erp24/views/store-staffing/index.php @@ -84,7 +84,7 @@ $this->params['breadcrumbs'][] = $this->title; Магазин - Средний уровень (посит) + Средний уровень -- 2.39.5