From 2c82f488a1e8b8c9fb4460cae0daac45bcac29e0 Mon Sep 17 00:00:00 2001 From: brusnitsyn Date: Fri, 5 Jun 2026 16:20:35 +0900 Subject: [PATCH] =?UTF-8?q?[=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=BE]:=20=D1=81=D1=82=D0=BE=D0=BB=D0=B1=D0=B5=D1=86=20=D0=92?= =?UTF-8?q?=D1=8B=D0=B1=D1=8B=D0=BB=D0=BE=20=D0=B2=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B5=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MetricCalculators/PlanCalculator.php | 115 ++++++++++++++++++ app/Services/StatisticsService.php | 53 ++++---- .../Statistic/Components/OutcomeColumn.vue | 72 +++++------ resources/js/Pages/Statistic/Index.vue | 6 +- 4 files changed, 177 insertions(+), 69 deletions(-) create mode 100644 app/Services/MetricCalculators/PlanCalculator.php diff --git a/app/Services/MetricCalculators/PlanCalculator.php b/app/Services/MetricCalculators/PlanCalculator.php new file mode 100644 index 0000000..080c2b0 --- /dev/null +++ b/app/Services/MetricCalculators/PlanCalculator.php @@ -0,0 +1,115 @@ +copy()->startOfYear()->setHours(9); + $startPreviousMonth = $endDate->copy()->startOfMonth()->setHours(9); + $startCurrentMonth = $endDate->copy()->startOfMonth()->setHours(9); + $currentMonth = $endDate->month; + + // Получаем фактические выписки с начала года по прошлый месяц + $previousOutcomeMonth = DB::table('report_duties as r') + ->join('duty_report_metric_results as mr', 'r.id', '=', 'mr.rf_report_id') + ->whereIn('r.rf_department_id', $departmentIds) + ->where('mr.rf_metrika_item_id', self::getMetricId()) + ->where('r.period_end', '>=', $startYear) // Используем >= + ->where('r.period_end', '<', $startPreviousMonth) // Используем < + ->select('r.rf_department_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total')) + ->groupBy('r.rf_department_id') + ->get() + ->keyBy('rf_department_id'); + + // Получаем фактические выписки за текущий месяц + $actualCurrentMonth = DB::table('report_duties as r') + ->join('duty_report_metric_results as mr', 'r.id', '=', 'mr.rf_report_id') + ->whereIn('r.rf_department_id', $departmentIds) + ->where('mr.rf_metrika_item_id', self::getMetricId()) + ->where('r.period_end', '>=', $startCurrentMonth) // Используем >= + ->where('r.period_end', '<=', $endDate) + ->select('r.rf_department_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total')) + ->groupBy('r.rf_department_id') + ->get() + ->keyBy('rf_department_id'); + + $results = []; + + foreach ($departmentIds as $departmentId) { + $department = Department::find($departmentId); + + // Получаем годовой план + $annualPlanModel = $department->recipientPlanOfYear(); + $annualPlan = $annualPlanModel ? (int) $annualPlanModel->value : 0; + + // План на 1 месяц (равномерно) + $oneMonthPlan = $annualPlan > 0 ? ceil($annualPlan / 12) : 0; + + // ===== БЕЗОПАСНОЕ ПОЛУЧЕНИЕ ЗНАЧЕНИЙ ===== + // Факт за прошлые месяцы (без текущего) + $actualToLastMonth = (int) ($previousOutcomeMonth[$departmentId]->total ?? 0); + + // Факт за текущий месяц (с проверкой существования ключа) + $actualCurrent = (int) ($actualCurrentMonth[$departmentId]->total ?? 0); + + // Общий факт с начала года + $actualYearToDate = $actualToLastMonth + $actualCurrent; + + // ===== 1. План с начала года нарастающим ===== + $cumulativePlan = $oneMonthPlan * $currentMonth; + + // ===== 2. Долг за прошлые месяцы ===== + $expectedToLastMonth = $oneMonthPlan * ($currentMonth - 1); + $debtFromYearStart = max(0, $expectedToLastMonth - $actualToLastMonth); + + // ===== 3. Остаток плана на текущий месяц (с безопасной проверкой) ===== + $currentMonthPlanOnly = max(0, $oneMonthPlan - $actualCurrent); + + // ===== 4. ИТОГОВЫЙ ДОЛГ ===== + $totalDebt = $currentMonthPlanOnly + $debtFromYearStart; + + // ===== 5. Процент выполнения плана ===== + $cumulativePercent = $cumulativePlan > 0 + ? round($actualYearToDate * 100 / $cumulativePlan) + : 0; + + $results[$departmentId] = [ + 'year_plan' => $annualPlan, + 'month_plan' => $oneMonthPlan, + 'total_debt' => $totalDebt, + 'current_mouth_dept' => $currentMonthPlanOnly, + 'cumulative_plan' => $cumulativePlan, + 'debt_from_year' => $debtFromYearStart, + 'actual_to_last_month' => $actualToLastMonth, + 'outcome_in_current_month' => $actualCurrent, + 'actual_year_to_date' => $actualYearToDate, + 'cumulative_percent' => $cumulativePercent, + ]; + } + + return $results; + } +} diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php index 43ce97e..58cf0fb 100644 --- a/app/Services/StatisticsService.php +++ b/app/Services/StatisticsService.php @@ -10,23 +10,21 @@ use App\Models\Report; use App\Models\ReportDuty; use App\Models\User; use App\Models\UserDepartment; +use App\Services\MetricCalculators\PlanCalculator; use Carbon\Carbon; use Illuminate\Support\Facades\DB; class StatisticsService { public function __construct( - protected BedDayService $bedDayService + protected BedDayService $bedDayService, + protected PlanCalculator $planCalculator, ) {} public function getStatisticsData(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array { $this->bedDayService->clearMemoryCache(); - // Годовой план - $recipientPlanOfYear = 0; - $progressPlanOfYear = 0; - // 1. Получаем отделения $departments = Department::select('department_id', 'name_short', 'rf_department_type', 'user_name', 'order') ->with('departmentType') @@ -41,11 +39,6 @@ class StatisticsService return $this->emptyResponse(); } - // Рассчитываем коэффициент периода (дни периода / 365) - $start = Carbon::parse($startDate); - $end = Carbon::parse($endDate); - $monthsInPeriod = ceil($start->diffInMonths($end)); - $allDeptIds = $departments->flatten()->pluck('department_id')->toArray(); // 2а. Нежелательные события по отделениям за период (прямой запрос) @@ -114,6 +107,8 @@ class StatisticsService $grandRecipientPlan = 0; $grandProgressPlan = 0; + $departmentsPlans = $this->planCalculator->calculate($allDeptIds, $startDate, $endDate); + foreach ($departments as $typeName => $deptList) { $groupedData[$typeName] = []; $totalsByType[$typeName] = $this->initTypeTotals(); @@ -136,14 +131,6 @@ class StatisticsService $bedsCount = (int) ($beds[$deptId]->value ?? 0); $currentCount = (int) ($currentPatients[$deptId]->value ?? 0); - // Получаем годовой план - $annualPlanModel = $dept->recipientPlanOfYear(); - $annualPlan = $annualPlanModel ? (int) $annualPlanModel->value : 0; - $oneMonthPlan = ceil($annualPlan / 12); - - // Рассчитываем план на период - $periodPlan = round($oneMonthPlan * $monthsInPeriod); - // Счетчики $plan = 0; $emergency = 0; @@ -200,11 +187,6 @@ class StatisticsService } } - $grandRecipientPlan += $periodPlan; - $grandProgressPlan += $outcome; - - $percentPlanOfYear = $periodPlan > 0 ? round($outcome * 100 / $periodPlan) : 0; - // Расчеты $allCount = $plan + $emergency; $percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0; @@ -225,8 +207,6 @@ class StatisticsService $departmentName = $dept->user_name ?? $dept->name_short; - $progressPlanOfYear += $outcome; - $data = [ 'department' => $departmentName, 'department_id' => $deptId, @@ -255,12 +235,25 @@ class StatisticsService 'preoperativeDays' => $preoperativeValue, 'preoperativeSum' => $preoperativeSum, 'preoperativePatientCount' => $preoperativePatientCount, + 'plan' => $departmentsPlans[$deptId], - 'progressPlanOfYear' => $periodPlan, - 'percentPlanOfYear' => $percentPlanOfYear, - 'needPlanOfYear' => $periodPlan > 0 && $periodPlan > $outcome - ? $periodPlan - $outcome - : 0, +// 'progressPlanOfYear' => $cumulativePlan, +// 'percentPlanOfYear' => $percentPlanOfYear, +// 'needPlanOfYear' => $cumulativePlan > 0 && $cumulativePlan > $outcome +// ? $cumulativePlan - $outcome +// : 0, + // Плановые показатели +// 'cumulative_plan' => $cumulativePlan, // План с начала года нарастающим (включая текущий месяц) +// 'current_month_plan_only' => $currentMonthPlanOnly, // План только на текущий месяц +// 'debt_from_year_start' => $debtFromYearStart, // Долг с начала года (невыполненный план за прошлые месяцы) +// 'total_debt' => $totalDebt, // ИТОГО долг = план на текущий месяц + долг с начала года +// 'currentMonthDebt' => $currentMonthDebt, // Выписать по плану в текущем месяце + + // Фактические показатели +// 'actual_year_to_date' => $actualToDate, // Факт с начала года (включая текущий месяц) + + // Процент выполнения +// 'cumulative_percent' => $cumulativePlan > 0 ? round($actualToDate * 100 / $cumulativePlan, 2) : 0, 'lethality' => $lethality, 'type' => $typeName, 'isDepartment' => true, diff --git a/resources/js/Pages/Statistic/Components/OutcomeColumn.vue b/resources/js/Pages/Statistic/Components/OutcomeColumn.vue index 006116f..badb9c8 100644 --- a/resources/js/Pages/Statistic/Components/OutcomeColumn.vue +++ b/resources/js/Pages/Statistic/Components/OutcomeColumn.vue @@ -11,65 +11,67 @@ const props = defineProps({ type: [Number, String], default: '' }, - needCompletedToPlan: { - type: [Number, String], - default: null - }, - planOfYear: { - type: [Number, String], - default: null - }, - progressCompletedToPlan: { - type: [Number, String], - default: null + plan: { + type: Object, + default: () => ({ + actual_to_date: 0, + cumulative_plan: 0, + current_mouth_dept: 0, + debt_from_year: 0, + month_plan: 0, + outcome_in_current_mouth: 0, + total_debt: 0, + year_plan: 0, + }) } }) diff --git a/resources/js/Pages/Statistic/Index.vue b/resources/js/Pages/Statistic/Index.vue index c4925e9..e1f86c1 100644 --- a/resources/js/Pages/Statistic/Index.vue +++ b/resources/js/Pages/Statistic/Index.vue @@ -180,10 +180,8 @@ const columns = ref([ OutcomeColumn, { isTotalRow: row.isTotalRow, - progressCompletedToPlan: row.percentPlanOfYear, - needCompletedToPlan: row.needPlanOfYear, - planOfYear: row.progressPlanOfYear, - value: row.outcome + value: row.outcome, + plan: row.plan, } ) }