/**
* Обновляет данные администратора, включая назначение должности и группы
*
- * Для специальных групп формирует group_name из EmployeePosition и смены.
+ * Для рабочих групп (group_id = 50 и все группы с parent_id = 50) при заполнении
+ * employee_position_id заполняет group_name значением из AdminGroup->name.
* Для остальных групп использует текстовое поле custom_position.
*
* @param int $id ID администратора
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']);
$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 берем из текстового поля
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])) {
$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;
}
}
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;
$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();
}
* Получает средний уровень обученности по штатному расписанию магазина
*
* @param int $storeId ID магазина
- * @return float|null Средний уровень (посит) или null если нет данных
+ * @return float|null Средний уровень (posit) или null если нет данных
*/
private function getStoreStaffingSkillScore($storeId)
{
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 {
}
}
}
+
+ /**
+ * Очистка 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
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 {
- // Ð\97акÑ\80Ñ\8bÑ\82Ñ\8c пÑ\80едÑ\8bдÑ\83Ñ\89Ñ\83Ñ\8e должноÑ\81Ñ\82Ñ\8c, еÑ\81ли бÑ\8bла
- if ($oldPositionId) {
- EmployeePositionStatus::updateAll(
- ['closed_at' => date('Y-m-d H:i:s')],
- ['admin_id' => $this->id, 'closed_at' => null]
- );
- }
+ // Ð\97акÑ\80Ñ\8bÑ\82Ñ\8c Ð\92СÐ\95 акÑ\82ивнÑ\8bе запиÑ\81и длÑ\8f Ñ\8dÑ\82ого админа (на Ñ\81лÑ\83Ñ\87ай еÑ\81ли еÑ\81Ñ\82Ñ\8c неÑ\81колÑ\8cко акÑ\82ивнÑ\8bÑ\85)
+ $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
}
}
}
<div class="admin-form">
<?php $form = ActiveForm::begin(); ?>
- <?php PrintBlockHelper::printBlock('*Должность (группа)', $form->field($model, 'group_id')->dropDownList($adminGroups, ['onchange' => 'changeWorkRateVisibility(this);'])->label(false)) ?>
+ <?php PrintBlockHelper::printBlock('*Должность (группа)', $form->field($model, 'group_id')->dropDownList($adminGroups, ['onchange' => 'changeWorkRateVisibility(this); checkGradeGroupMatch();', 'id' => 'group-id-select'])->label(false)) ?>
<?php
if (!empty($adminHistoryCategories['group'])) {
$adminHistoryCategory = $adminHistoryCategories['group'];
AdminGroup::GROUP_WORKERS, // 45
AdminGroup::GROUP_ADMINISTRATORS, // 50
AdminGroup::GROUP_FLORIST_SUPPORT_NIGHT, // 72
+ AdminGroup::GROUP_FLORIST, // 89
];
// Ищем группу "Работники магазинов" по имени
<div id="positionFieldSpecial" style="display: <?= $isSpecialGroup ? 'block' : 'none' ?>;">
<?php PrintBlockHelper::printBlock('Должность', $form->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)) ?>
-
- <?php PrintBlockHelper::printBlock('Смена', Html::dropDownList('Admin[shift]', $shift ?? '', [
- '' => 'Не выбрана',
- 'день' => 'День',
- 'ночь' => 'Ночь'
- ], ['class' => 'form-control'])) ?>
+
+ <!-- Уведомление о несовпадении группы и грейда -->
+ <div id="grade-group-mismatch-alert" class="alert alert-warning mt-2" style="display: none;">
+ <strong>⚠️ Внимание!</strong> Выбранный грейд не соответствует текущей группе.
+ Пожалуйста, выберите соответствующую группу в поле "Должность (группа)" выше.
+ </div>
</div>
<div id="positionFieldRegular" style="display: <?= !$isSpecialGroup ? 'block' : 'none' ?>;">
.hidden {
display: none;
}
+ .has-error select {
+ border-color: #dc3545 !important;
+ box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important;
+ }
</style>
<script>
$('#positionFieldRegular').show();
// Очищаем поле employee_position_id для не-специальных групп
$('select[name="Admin[employee_position_id]"]').val('');
- // Очищаем поле shift для не-специальных групп
- $('select[name="Admin[shift]"]').val('');
+ }
+ }
+
+ // Данные для проверки соответствия грейда и группы
+ var positionsData = <?= json_encode(ArrayHelper::map($positions, 'id', 'name'), JSON_UNESCAPED_UNICODE) ?>;
+ var groupsData = <?= json_encode($adminGroups, JSON_UNESCAPED_UNICODE) ?>;
+
+ /**
+ * Проверка соответствия выбранного грейда и группы
+ */
+ function checkGradeGroupMatch() {
+ var positionSelect = $('#employee-position-select');
+ var groupSelect = $('#group-id-select');
+ var alertDiv = $('#grade-group-mismatch-alert');
+
+ // Проверяем только если выбраны оба значения
+ if (!positionSelect.length || !groupSelect.length || !positionSelect.val() || !groupSelect.val()) {
+ alertDiv.hide();
+ groupSelect.closest('.form-group').removeClass('has-error');
+ return;
+ }
+
+ var selectedPositionId = positionSelect.val();
+ var selectedGroupId = groupSelect.val();
+ var positionName = positionsData[selectedPositionId] || '';
+ var groupName = groupsData[selectedGroupId] || '';
+
+ if (!positionName || !groupName) {
+ alertDiv.hide();
+ groupSelect.closest('.form-group').removeClass('has-error');
+ return;
+ }
+
+ // Нормализуем названия для сравнения (убираем "день", "ночь" и приводим к нижнему регистру)
+ var normalizedPositionName = positionName.toLowerCase().replace(/\s*(день|ночь)\s*/gi, '').trim();
+ var normalizedGroupName = groupName.toLowerCase().replace(/\s*(день|ночь)\s*/gi, '').trim();
+
+ // Проверяем частичное совпадение
+ var hasMatch = false;
+ if (normalizedGroupName.indexOf(normalizedPositionName) !== -1 ||
+ normalizedPositionName.indexOf(normalizedGroupName) !== -1) {
+ hasMatch = true;
+ }
+
+ if (!hasMatch) {
+ // Несовпадение - показываем предупреждение и выделяем поле группы
+ alertDiv.show();
+ groupSelect.closest('.form-group').addClass('has-error');
+ groupSelect.css('border-color', '#dc3545');
+ groupSelect.css('box-shadow', '0 0 0 0.2rem rgba(220, 53, 69, 0.25)');
+ } else {
+ // Совпадение - скрываем предупреждение и убираем выделение
+ alertDiv.hide();
+ groupSelect.closest('.form-group').removeClass('has-error');
+ groupSelect.css('border-color', '');
+ groupSelect.css('box-shadow', '');
}
}
var initialGroupId = $('select[name="Admin[group_id]"]').val();
changePositionFieldVisibility(initialGroupId);
- // Ð\94лÑ\8f не-Ñ\81пеÑ\86иалÑ\8cнÑ\8bÑ\85 гÑ\80Ñ\83пп оÑ\87иÑ\89аем employee_position_id и shift пÑ\80и загÑ\80Ñ\83зке
+ // Для не-специальных групп очищаем employee_position_id при загрузке
var specialGroups = <?= $specialGroupsJson ?>;
var isSpecialGroup = specialGroups.includes(parseInt(initialGroupId));
if (!isSpecialGroup) {
$('select[name="Admin[employee_position_id]"]').val('');
- $('select[name="Admin[shift]"]').val('');
}
+
+ // Проверяем соответствие при загрузке страницы
+ checkGradeGroupMatch();
});
</script>
\ No newline at end of file
</div>
</div>
+<!-- Flash сообщения -->
+<?php if (Yii::$app->session->hasFlash('success')): ?>
+ <div class="row mb-3">
+ <div class="col">
+ <div class="alert alert-success alert-dismissible fade show" role="alert">
+ <?= Yii::$app->session->getFlash('success') ?>
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+ </div>
+ </div>
+ </div>
+<?php endif; ?>
+
+<?php if (Yii::$app->session->hasFlash('info')): ?>
+ <div class="row mb-3">
+ <div class="col">
+ <div class="alert alert-info alert-dismissible fade show" role="alert">
+ <strong>ℹ️ Информация:</strong> <?= Yii::$app->session->getFlash('info') ?>
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+ </div>
+ </div>
+ </div>
+<?php endif; ?>
+
+<?php if (Yii::$app->session->hasFlash('warning')): ?>
+ <div class="row mb-3">
+ <div class="col">
+ <div class="alert alert-warning alert-dismissible fade show" role="alert">
+ <strong>⚠️ Внимание:</strong> <?= Yii::$app->session->getFlash('warning') ?>
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+ </div>
+ </div>
+ </div>
+<?php endif; ?>
+
+<?php if (Yii::$app->session->hasFlash('error')): ?>
+ <div class="row mb-3">
+ <div class="col">
+ <div class="alert alert-danger alert-dismissible fade show" role="alert">
+ <strong>❌ Ошибка:</strong> <?= Yii::$app->session->getFlash('error') ?>
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+ </div>
+ </div>
+ </div>
+<?php endif; ?>
+
<!-- Информация о текущем грейде -->
<div class="row mb-3 mt-2">
<div class="col">
?>
<strong>Текущий грейд:</strong> <?= Html::encode($currentPosition->name) ?>
<br><small>Установлен с: <?= $currentStatus ? Yii::$app->formatter->asDatetime($currentStatus->created_at, 'php:d.m.Y H:i') : 'дата неизвестна' ?></small>
+ <?php if ($admin->adminGroup): ?>
+ <br><strong>Текущая группа:</strong> <?= Html::encode($admin->adminGroup->name) ?>
+ <?php endif; ?>
<?php else: ?>
<strong style="color: #dc3545;">⚠️ Грейд не установлен</strong>
<br><small>Пожалуйста, выберите грейд из списка ниже</small>
['' => '-- Выберите грейд --'] + ArrayHelper::map($positions, 'id', 'name'),
['class' => 'form-control']
)->label(false); ?>
- <?= $gradeForm->field($modelPosition, 'shift')->dropDownList([
- '' => 'Не выбрана',
- 'день' => 'День',
- 'ночь' => 'Ночь'
- ], ['class' => 'form-control'])->label('Смена') ?>
</div>
<div class="col-2">
</div>
<?php
+// Получаем всю историю назначений грейда (включая закрытые)
$positionHistory = \yii_app\records\EmployeePositionStatus::find()
->where(['admin_id' => $admin->id])
->orderBy(['created_at' => SORT_DESC])
]) ?>
<div class="alert alert-info">
- <strong>Грейд (посит) должности:</strong> <span id="position-posit">-</span>
+ <strong>Грейд должности (цифровое значение от 0 до 5):</strong> <span id="position-posit">-</span>
</div>
<?= $form->field($model, 'count')->textInput(['type' => 'number', 'min' => 0.1, 'step' => 0.1]) ?>
<thead>
<tr>
<th>Магазин</th>
- <th>Средний уровень (посит)</th>
+ <th>Средний уровень</th>
</tr>
</thead>
<tbody>