From: Vladimir Fomichev Date: Thu, 4 Dec 2025 12:07:33 +0000 (+0300) Subject: Merge branch 'refs/heads/develop' into feature_fomichev_erp-497_add_count_on_shift_to... X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=ecb6d2cfe44de1d18bc533f17140e4209ee5d214;p=erp24_rep%2Fyii-erp24%2F.git Merge branch 'refs/heads/develop' into feature_fomichev_erp-497_add_count_on_shift_to_report # Conflicts: # erp24/actions/grade/AdminUpdateAction.php # erp24/api3/core/services/ReportService.php --- ecb6d2cfe44de1d18bc533f17140e4209ee5d214 diff --cc erp24/actions/grade/AdminUpdateAction.php index 8a581d0b,70ae6133..ada50c09 --- a/erp24/actions/grade/AdminUpdateAction.php +++ b/erp24/actions/grade/AdminUpdateAction.php @@@ -31,6 -32,10 +32,10 @@@ class AdminUpdateAction extends Actio return "Нет доступа"; } $model = Admin::findOne($id); - ++ + // Определяем специальные группы работников (используется в POST обработке и при рендеринге) + $workersGroup = AdminGroup::getWorkersGroups(); - ++ if (Yii::$app->user->can("updateAdminSettings", ['id' => $model->id])) { if (Yii::$app->request->isPost) { $attributes = Yii::$app->request->post()['Admin']; @@@ -51,9 -56,21 +56,20 @@@ 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 + // Если group_id не передан в POST, используем текущий из модели + $newGroupId = $attributes['group_id'] ?? $model->group_id; - ++ + // Проверяем права только если group_id действительно меняется + if ($newGroupId != $model->group_id) { + $canChangeGroupId = Yii::$app->user->can("updateAdminSettingsGroupId", ['group_id' => $newGroupId]); - ++ + // Если нет прав на изменение group_id - используем текущий group_id из модели + if (!$canChangeGroupId) { + $attributes['group_id'] = $model->group_id; + } } - if (!Yii::$app->user->can("updateAdminSettingsOnlyByHrAndAdministrator")) { unset($attributes['store_dostup_all']); unset($attributes['store_id']); @@@ -89,52 -106,26 +105,26 @@@ Yii::$app->cache->set("dirtyAuthSettings", true); } - // Определяем специальные группы с parent_id = 50 - $specialGroups = [ - AdminGroup::GROUP_FLORIST_DAY, // 30 - AdminGroup::GROUP_FLORIST_NIGHT, // 35 - AdminGroup::GROUP_FLORIST_SUPPORT_DAY, // 40 - AdminGroup::GROUP_WORKERS, // 45 - AdminGroup::GROUP_ADMINISTRATORS, // 50 - AdminGroup::GROUP_FLORIST_SUPPORT_NIGHT, // 72 - AdminGroup::GROUP_FLORIST, // 89 - ]; - - // Ищем группу "Работники магазинов" по имени - $workersGroup = AdminGroup::find()->where(['name' => AdminGroup::GROUP_STORE_WORKERS_NAME])->one(); - if ($workersGroup) { - $specialGroups[] = $workersGroup->id; - } + // Получаем группу один раз для всех последующих операций + $currentGroup = AdminGroup::findOne($attributes['group_id']); + $isSpecialGroup = in_array((int)$attributes['group_id'], $workersGroup); - + - $isSpecialGroup = in_array((int)$attributes['group_id'], $specialGroups); - - 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']; - } - $attributes['group_name'] = $groupName; - } - } + // Устанавливаем group_name из AdminGroup->name + if ($currentGroup) { + $attributes['group_name'] = $currentGroup->name; + } else if (isset($attributes['custom_position'])) { + // Если группа не найдена, используем текстовое поле как fallback + $attributes['group_name'] = $attributes['custom_position']; + } - + - // Очищаем shift для не-специальных групп - unset($attributes['shift']); - } else { - // Для остальных групп group_name берем из текстового поля - if (isset($attributes['custom_position'])) { - $attributes['group_name'] = $attributes['custom_position']; - unset($attributes['custom_position']); - } - // Очищаем employee_position_id и shift для не-специальных групп + // Для не-специальных групп очищаем employee_position_id + if (!$isSpecialGroup) { $attributes['employee_position_id'] = null; - unset($attributes['shift']); } - + + // Очищаем custom_position в любом случае + unset($attributes['custom_position']); + $model->setAttributes($attributes, false); if (Yii::$app->user->can("manageAvatarka", ['id' => $model->id])) { @@@ -195,17 -186,18 +185,18 @@@ } } - $adminGroups = [AdminGroup::NOT_INITIALIZED_GROUP => 'Не выбрана']; - foreach (AdminGroup::find()->all() as $adminGroupId => $adminGroup) { - $adminGroups[$adminGroup->id] = $adminGroup->name; - } - $adminGroups = [AdminGroup::NOT_INITIALIZED_GROUP => 'Не выбрана'] + ++ $adminGroups = [AdminGroup::NOT_INITIALIZED_GROUP => 'Не выбрана'] + + ArrayHelper::map(AdminGroup::find()->all(), 'id', 'name'); - $adminArr = []; - foreach (Admin::find()->with('adminGroup')->all() as $admin) { - $adminArr[] = ['id' => $admin->id, 'name' => $admin->name, 'groupName' => $admin->adminGroup->name ?? "Другие"]; - } + $admins = ArrayHelper::map( - Admin::find()->with('adminGroup')->all(), - 'id', - 'name', ++ Admin::find()->with('adminGroup')->all(), ++ 'id', ++ 'name', + function($model) { + return $model->adminGroup->name ?? 'Другие'; + } + ); - $admins = ArrayHelper::map($adminArr, 'id', 'name', 'groupName'); $positions = EmployeePosition::find()->orderBy('posit')->all(); $cityStores = ArrayHelper::map(CityStore::find()->select(['id', 'name'])->all(), 'id', 'name'); diff --cc erp24/api3/core/services/ReportService.php index eb87a4c1,7d183959..90e8a5c7 --- a/erp24/api3/core/services/ReportService.php +++ b/erp24/api3/core/services/ReportService.php @@@ -345,91 -353,25 +345,91 @@@ class ReportServic $shift_id = $data->shift_type == 0 ? [1, 2, 5, 8] : ($data->shift_type == 1 ? [1, 5, 8] : [2]); - $timetablesMonth = Timetable::find()->alias('t')->select(['admin_id', 'a.name as adminName', 't.store_id', 't.shift_id']) - ->innerJoin('admin a', 'admin_id = a.id') - ->where(['t.store_id' => $data->stores]) + // Получаем сотрудников из фактических смен за месяц + $timetablesMonthData = TimetableFactModel::find() + ->select(['admin_id', 'store_id', 'shift_id']) + ->where(['store_id' => $data->stores]) ->andWhere(['>=', 'date', date("Y-m-01", strtotime($currentDate))]) ->andWhere(['<=', 'date', $currentDate]) - ->andWhere(['shift_id' => $shift_id, 'tabel' => 0, 'slot_type_id' => Timetable::TIMESLOT_WORK]) - ->andWhere(['t.active' => 1]) - ->asArray()->all(); + ->andWhere(['shift_id' => $shift_id]) + ->andWhere(['>', 'work_time', 0]) + ->groupBy(['admin_id', 'store_id', 'shift_id']) + ->asArray() + ->all(); + + // Если в факте нет данных за месяц, используем план как fallback + if (empty($timetablesMonthData)) { + $timetablesMonthData = Timetable::find()->alias('t') + ->select(['admin_id', 'store_id', 'shift_id']) + ->where(['t.store_id' => $data->stores]) + ->andWhere(['>=', 't.date', date("Y-m-01", strtotime($currentDate))]) + ->andWhere(['<=', 't.date', $currentDate]) + ->andWhere(['t.shift_id' => $shift_id, 'tabel' => 0, 'slot_type_id' => Timetable::TIMESLOT_WORK]) + ->groupBy(['admin_id', 'store_id', 'shift_id']) + ->asArray() + ->all(); + } - $timetables = Timetable::find()->alias('t')->select(['admin_id', 'a.name as adminName', 't.store_id', 't.shift_id']) - ->innerJoin('admin a', 'admin_id = a.id') - ->where(['t.store_id' => $data->stores]) - ->andWhere(['date' => $currentDate, 'tabel' => 0]) - ->andWhere(['shift_id' => $shift_id, 'slot_type_id' => Timetable::TIMESLOT_WORK]) - ->andWhere(['t.active' => 1]) - ->asArray()->all(); + // Получаем сотрудников из фактических смен за день + $timetablesData = TimetableFactModel::find() + ->select(['admin_id', 'store_id', 'shift_id']) + ->where(['store_id' => $data->stores]) + ->andWhere(['date' => $currentDate]) + ->andWhere(['shift_id' => $shift_id]) + ->andWhere(['>', 'work_time', 0]) + ->groupBy(['admin_id', 'store_id', 'shift_id']) + ->asArray() + ->all(); + + // Если в факте нет данных, используем план как fallback + if (empty($timetablesData)) { + $timetablesData = Timetable::find()->alias('t') + ->select(['admin_id', 'store_id', 'shift_id']) + ->where(['t.store_id' => $data->stores]) + ->andWhere(['t.date' => $currentDate, 'tabel' => 0]) + ->andWhere(['t.shift_id' => $shift_id, 'slot_type_id' => Timetable::TIMESLOT_WORK]) + ->groupBy(['admin_id', 'store_id', 'shift_id']) + ->asArray() + ->all(); + } - $adminIdsMonth = ArrayHelper::getColumn($timetablesMonth, 'admin_id'); - $adminIds = ArrayHelper::getColumn($timetables, 'admin_id'); + // Получаем имена админов отдельным запросом + // Извлекаем уникальные ID администраторов для использования в последующих запросах + $adminIdsMonth = array_unique(ArrayHelper::getColumn($timetablesMonthData, 'admin_id')); + $adminIds = array_unique(ArrayHelper::getColumn($timetablesData, 'admin_id')); + $allAdminIds = array_unique(array_merge($adminIdsMonth, $adminIds)); - ++ + $adminNamesMap = []; + if (!empty($allAdminIds)) { + $admins = Admin::find() + ->select(['id', 'name']) + ->where(['id' => $allAdminIds]) + ->indexBy('id') + ->asArray() + ->all(); + $adminNamesMap = ArrayHelper::map($admins, 'id', 'name'); + } + + // Формируем массивы с именами админов + $timetablesMonth = []; + foreach ($timetablesMonthData as $item) { + $timetablesMonth[] = [ + 'admin_id' => $item['admin_id'], + 'adminName' => $adminNamesMap[$item['admin_id']] ?? '', + 'store_id' => $item['store_id'], + 'shift_id' => $item['shift_id'], + ]; + } + + $timetables = []; + foreach ($timetablesData as $item) { + $timetables[] = [ + 'admin_id' => $item['admin_id'], + 'adminName' => $adminNamesMap[$item['admin_id']] ?? '', + 'store_id' => $item['store_id'], + 'shift_id' => $item['shift_id'], + ]; + } // Подсчет должностей на смене для этого дня $employeePositionsOnShift = $this->countEmployeesByPosition($timetables, $positionMap); @@@ -613,7 -555,14 +613,14 @@@ $adminNames = []; foreach ($timetables as $timetable) { - $adminNames[$timetable['store_id']][] = [ + $storeId = $timetable['store_id']; - ++ + if (!isset($adminNames[$storeId])) { + $adminNames[$storeId] = []; + } + + // Один администратор может работать в несколько смен в один день + $adminNames[$storeId][] = [ 'id' => $timetable['admin_id'], 'name' => $timetable['adminName'], 'shift_id' => $timetable['shift_id'], @@@ -671,53 -615,6 +673,53 @@@ $storeSaleReturnTotal += (int)$storeSale['total']; } + // Добавляем админов из продаж, которые отсутствуют в $adminNames + if (!isset($adminNames[$store->id])) { + $adminNames[$store->id] = []; + } - ++ + // Собираем уникальные admin_id из продаж + $salesAdminIds = []; + foreach ($storeSaleArr as $sale) { + $salesAdminIds[$sale['admin_id']] = true; + } + foreach ($storeSaleReturnArr as $sale) { + $salesAdminIds[$sale['admin_id']] = true; + } + + // Определяем админов из продаж, которых нет в смене + $missingAdminIds = []; + foreach ($salesAdminIds as $adminId => $_) { + $adminExists = false; + foreach ($adminNames[$store->id] as $admin) { + if ($admin['id'] == $adminId) { + $adminExists = true; + break; + } + } + if (!$adminExists) { + $missingAdminIds[] = $adminId; + } + } - ++ + // Batch-загрузка имен отсутствующих админов одним запросом + if (!empty($missingAdminIds)) { + $missingAdmins = Admin::find() + ->select(['id', 'name']) + ->where(['id' => $missingAdminIds]) + ->indexBy('id') + ->asArray() + ->all(); - ++ + foreach ($missingAdminIds as $adminId) { + $adminNames[$store->id][] = [ + 'id' => $adminId, + 'name' => $missingAdmins[$adminId]['name'] ?? 'Unknown', + 'shift_id' => 0, // У админов из продаж нет shift_id + ]; + } + } + $storeVisitorsQuantityTotal += $storeVisitorsQuantity; $storeSaleQuantityTotal += $storeSaleQuantity; $storeSaleTotalTotal += $storeSaleTotal; @@@ -954,25 -833,20 +956,25 @@@ $store_guids[$store_id] = $eitStores[$store_id]['export_val']; } + // Получаем все уникальные admin_id сотрудников за период из фактических смен $cond = ['or']; foreach ($data->date as $ind => $dateStartEnd) { - $cond[]= ['between', 'date', - date("Y-m-d 00:00:00", strtotime($dateStartEnd[0])), - date("Y-m-d 23:59:59", strtotime($dateStartEnd[1]))]; + $cond[] = ['between', 'date', + date("Y-m-d", strtotime($dateStartEnd[0])), + date("Y-m-d", strtotime($dateStartEnd[1]))]; } - - $employeesTotal = Sales::find()->select(["COUNT(*) as cnt", "admin_id"]) // , "DATA_FORMAT(date, '%Y-%m-%d') as day" ++ + $allAdminsInPeriod = TimetableFactModel::find() + ->select(['admin_id']) + ->distinct() ->where(['store_id' => $data->stores]) ->andWhere($cond) - ->groupBy(['admin_id'])->asArray()->all(); - $employeeCountTotal = count($employeesTotal); + ->andWhere(['>', 'work_time', 0]) + ->asArray() + ->all(); - // Получаем все уникальные admin_id сотрудников за период - $adminIdsInPeriod = ArrayHelper::getColumn($employeesTotal, 'admin_id'); + $adminIdsInPeriod = ArrayHelper::getColumn($allAdminsInPeriod, 'admin_id'); + $employeeCountTotal = count($adminIdsInPeriod); $positionMap = $this->buildPositionMap($adminIdsInPeriod); $adminSkillMap = $this->buildAdminSkillMap($adminIdsInPeriod); @@@ -983,19 -857,13 +985,19 @@@ $employeeCount = []; $weekEmployees = []; // Собираем сотрудников за неделю $storeWeekEmployees = []; // Сотрудники по магазинам за неделю - ++ + // Получаем сотрудников из фактических смен за неделю foreach ($data->stores as $store_id) { - $employees = Sales::find()->select(["COUNT(*) as cnt", "admin_id"]) - ->where(['between', 'date', - date("Y-m-d 00:00:00", strtotime($dateStartEnd[0])), - date("Y-m-d 23:59:59", strtotime($dateStartEnd[1]))]) - ->andWhere(['store_id' => $store_id]) - ->groupBy(['admin_id'])->asArray()->all(); + $employees = TimetableFactModel::find() + ->select(['admin_id']) + ->distinct() + ->where(['store_id' => $store_id]) + ->andWhere(['>=', 'date', date("Y-m-d", strtotime($dateStartEnd[0]))]) + ->andWhere(['<=', 'date', date("Y-m-d", strtotime($dateStartEnd[1]))]) + ->andWhere(['>', 'work_time', 0]) + ->asArray() + ->all(); - ++ $employeeCount[$store_id] = count($employees); // Сохраняем сотрудников для этого магазина @@@ -1449,18 -1321,16 +1451,18 @@@ $employeeCount = []; $allDayEmployees = []; // Собираем всех сотрудников за день по всем магазинам $storeEmployeesData = []; // Данные о сотрудниках по магазинам - ++ + // Получаем сотрудников из фактических смен за день foreach ($data->stores as $store_id) { - $employees = Sales::find()->select(["COUNT(*) as cnt", "admin_id"]) - ->where([ - 'between', - 'date', - date("Y-m-d 00:00:00", strtotime($day)), - date("Y-m-d 23:59:59", strtotime($day)) - ]) - ->andWhere(['store_id' => $store_id]) - ->groupBy(['admin_id'])->asArray()->all(); + $employees = TimetableFactModel::find() + ->select(['admin_id']) + ->distinct() + ->where(['store_id' => $store_id]) + ->andWhere(['date' => $day]) + ->andWhere(['>', 'work_time', 0]) + ->asArray() + ->all(); - ++ $employeeCount[$store_id] = count($employees); // Сохраняем данные сотрудников для этого магазина