From: Vladimir Fomichev Date: Tue, 2 Dec 2025 14:18:22 +0000 (+0300) Subject: Вывод employee_count_on_shift в отчете X-Git-Url: https://gitweb.erp-flowers.ru/?a=commitdiff_plain;h=a877dd874433befb98bfb0f2fe0de536d926fd71;p=erp24_rep%2Fyii-erp24%2F.git Вывод employee_count_on_shift в отчете --- diff --git a/erp24/actions/grade/AdminUpdateAction.php b/erp24/actions/grade/AdminUpdateAction.php index 968766fa..8a581d0b 100644 --- a/erp24/actions/grade/AdminUpdateAction.php +++ b/erp24/actions/grade/AdminUpdateAction.php @@ -97,6 +97,7 @@ class AdminUpdateAction extends Action AdminGroup::GROUP_WORKERS, // 45 AdminGroup::GROUP_ADMINISTRATORS, // 50 AdminGroup::GROUP_FLORIST_SUPPORT_NIGHT, // 72 + AdminGroup::GROUP_FLORIST, // 89 ]; // Ищем группу "Работники магазинов" по имени diff --git a/erp24/api3/core/services/ReportService.php b/erp24/api3/core/services/ReportService.php index 7e405a36..c0798580 100644 --- a/erp24/api3/core/services/ReportService.php +++ b/erp24/api3/core/services/ReportService.php @@ -20,6 +20,7 @@ use yii_app\records\StoreStaffing; use yii_app\records\StoreVisitors; use yii_app\records\WriteOffs; use yii_app\records\WriteOffsErp; +use yii_app\records\TimetableFactModel; class ReportService { @@ -321,27 +322,19 @@ class ReportService $totalEmployeePositionsOnShift = []; $totalEmployeeSkillsScores = []; - $employees = Sales::find()->select(["COUNT(*) as cnt", "admin_id"]) - ->where([ - 'between', - 'date', - date("Y-m-d 00:00:00", strtotime($data->date_start)), - date("Y-m-d 23:59:59", strtotime($data->date_end))]) - ->andWhere(['store_id' => $data->stores]) - ->groupBy(['admin_id'])->asArray()->all(); - $employeeCount = count($employees); - - // Получаем все уникальные admin_id сотрудников за период - $allAdminsInPeriod = Timetable::find()->alias('t') + // Получаем все уникальные admin_id сотрудников за период из фактических смен + $allAdminsInPeriod = TimetableFactModel::find() ->select(['admin_id']) ->distinct() - ->where(['t.store_id' => $data->stores]) + ->where(['store_id' => $data->stores]) ->andWhere(['>=', 'date', date("Y-m-01", strtotime($data->date_start))]) ->andWhere(['<=', 'date', $data->date_end]) - ->andWhere(['tabel' => 0, 'slot_type_id' => Timetable::TIMESLOT_WORK]) - ->asArray()->all(); + ->andWhere(['>', 'work_time', 0]) + ->asArray() + ->all(); $adminIdsInPeriod = ArrayHelper::getColumn($allAdminsInPeriod, 'admin_id'); + $employeeCount = count($adminIdsInPeriod); $positionMap = $this->buildPositionMap($adminIdsInPeriod); $adminSkillMap = $this->buildAdminSkillMap($adminIdsInPeriod); @@ -352,20 +345,90 @@ class ReportService $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]) - ->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]) - ->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($timetablesMonthData, 'admin_id'); + $adminIds = 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'], + ]; + } $adminIdsMonth = ArrayHelper::getColumn($timetablesMonth, 'admin_id'); $adminIds = ArrayHelper::getColumn($timetables, 'admin_id'); @@ -610,6 +673,40 @@ class ReportService $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; + } + + // Добавляем админов из продаж, которых нет в смене + foreach ($salesAdminIds as $adminId => $_) { + $adminExists = false; + foreach ($adminNames[$store->id] as $admin) { + if ($admin['id'] == $adminId) { + $adminExists = true; + break; + } + } + if (!$adminExists) { + // Получаем имя админа + $adminName = Admin::findOne($adminId); + $adminNames[$store->id][] = [ + 'id' => $adminId, + 'name' => $adminName ? $adminName->name : 'Unknown', + 'shift_id' => 0, // У админов из продаж нет shift_id + ]; + } + } + $storeVisitorsQuantityTotal += $storeVisitorsQuantity; $storeSaleQuantityTotal += $storeSaleQuantity; $storeSaleTotalTotal += $storeSaleTotal; @@ -645,18 +742,34 @@ class ReportService $totalPayrollDaysTotal += $totalPayrollDays; $totalPayrollMonthTotal += $totalPayrollMonth; + // Инициализируем все необходимые поля для админов + if (isset($adminNames[$store->id])) { + foreach ($adminNames[$store->id] as &$adminRecord) { + // Инициализируем поля продаж, если они еще не установлены + if (!isset($adminRecord["sale_total"])) { + $adminRecord["sale_total"] = 0; + $adminRecord["sale_quantity"] = 0; + $adminRecord["sale_avg"] = 0; + $adminRecord["sale_return_quantity"] = 0; + $adminRecord["sale_return_total"] = 0; + $adminRecord["bonus_user_count"] = 0; + $adminRecord["bonus_user_per_sale_percent"] = 0; + $adminRecord["bonus_new_user_count"] = 0; + $adminRecord["bonus_repeat_user_count"] = 0; + } + // Инициализируем поля спецпродажи + $adminRecord["total_matrix_per_day"] = 0; + $adminRecord["total_matrix_per_day_percent"] = 0; + $adminRecord["total_wrap_per_day"] = 0; + $adminRecord["total_services_per_day"] = 0; + $adminRecord["total_potted_per_day"] = 0; + } + unset($adminRecord); + } + $totalSpecificPerDay = []; foreach (['matrix', 'wrap', 'services', 'potted'] as $spec) { $totalSpecificPerDay[$spec] = 0; - if (isset($adminNames[$store->id])) { - foreach ($adminNames[$store->id] as &$adminRecord) { - $adminRecord["total_" . $spec . "_per_day"] = 0; - if ($spec == 'matrix') { - $adminRecord["total_" . $spec . "_per_day_percent"] = 0; - } - } - unset($adminRecord); // Освобождаем ссылку после цикла - } foreach ($specificSales[$spec] as $specificSale) { if ($specificSale['store_id'] == $store->id) { $totalSpecificPerDay[$spec] += $specificSale['total']; @@ -728,6 +841,7 @@ class ReportService "employee_positions_on_shift" => $storeEmployeePositionsOnShift, "employee_skills_score" => $employeeSkillsScore, "employee_skills_score_is_estimated" => $skillsScoreIsEstimated, + "employee_count_on_shift" => isset($adminNames[$store->id]) ? count($adminNames[$store->id]) : 0, "visitors_quantity" => $storeVisitorsQuantity, "sale_quantity" => $storeSaleQuantity, "sale_total" => $storeSaleTotal, @@ -786,6 +900,7 @@ class ReportService "total_potted_per_day" => $totalPottedPerDayTotal, "employee_positions_on_shift" => $employeePositionsOnShift, "employee_skills_score" => $totalEmployeeSkillsScore, + "employee_count_on_shift" => count($timetables), ]; // Создаем итоговый массив для дня с правильным порядком полей @@ -828,20 +943,25 @@ class ReportService $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); @@ -852,13 +972,19 @@ class ReportService $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); // Сохраняем сотрудников для этого магазина @@ -1198,6 +1324,7 @@ class ReportService "employee_positions_on_shift" => $storeEmployeePositionsOnShift, "employee_skills_score" => $employeeSkillsScore, "employee_skills_score_is_estimated" => $skillsScoreIsEstimated, + "employee_count_on_shift" => $employeeCount[$store_id] ?? 0, ]; $stores []= ['id' => $store_id, 'guid' => $eitStores[$store_id]['export_val'], 'name' => $cityStoreNames[$store_id], 'data' => $store]; @@ -1238,6 +1365,7 @@ class ReportService $total["conversion"] = $total["visitors_quantity"] > 0 ? floor($total["sale_quantity"] / $total["visitors_quantity"] * 100) : 0;; $total["bonus_user_per_sale_percent"] = $total["sale_quantity"] > 0 ? floor($total["bonus_user_count"] / $total["sale_quantity"] * 100) : 0; $total["employee_positions_on_shift"] = $employeePositionsOnShift; + $total["employee_count_on_shift"] = count($weekEmployees); $report = [ "date_from" => $dateStartEnd[0], @@ -1289,24 +1417,19 @@ class ReportService } } - $cond = ['or']; - foreach ($days as $ind => $day) { - $cond[] = [ - 'between', - 'date', - date("Y-m-d 00:00:00", strtotime($day)), - date("Y-m-d 23:59:59", strtotime($day)) - ]; - } - $employeesTotal = Sales::find()->select(["COUNT(*) as cnt", "admin_id"] - ) // , "DATA_FORMAT(date, '%Y-%m-%d') as day" - ->where(['store_id' => $data->stores]) - ->andWhere($cond) - ->groupBy(['admin_id'])->asArray()->all(); - $employeeCountTotal = count($employeesTotal); + // Получаем все уникальные admin_id сотрудников за период из фактических смен + $allAdminsInPeriod = TimetableFactModel::find() + ->select(['admin_id']) + ->distinct() + ->where(['store_id' => $data->stores]) + ->andWhere(['>=', 'date', date("Y-m-01", strtotime($data->date))]) + ->andWhere(['<=', 'date', date("Y-m-d", strtotime($data->date))]) + ->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); @@ -1316,16 +1439,18 @@ class ReportService $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); // Сохраняем данные сотрудников для этого магазина diff --git a/erp24/records/AdminGroup.php b/erp24/records/AdminGroup.php index f5f1a3cb..65c6512c 100755 --- a/erp24/records/AdminGroup.php +++ b/erp24/records/AdminGroup.php @@ -83,6 +83,23 @@ class AdminGroup extends ActiveRecord ]; } + /** + * Возвращает список специальных групп работников (нужно для JS и логики грейдов) + * @return int[] + */ + public static function getWorkersGroups(): array + { + return [ + self::GROUP_FLORIST_DAY, // 30 + self::GROUP_FLORIST_NIGHT, // 35 + self::GROUP_FLORIST_SUPPORT_DAY, // 40 + self::GROUP_WORKERS, // 45 + self::GROUP_ADMINISTRATORS, // 50 + self::GROUP_FLORIST_SUPPORT_NIGHT, // 72 + self::GROUP_FLORIST, // 89 + ]; + } + public static function tableName() { return 'admin_group';