diff --git a/app/Services/MetricCalculators/PlanCalculator.php b/app/Services/MetricCalculators/PlanCalculator.php index 4cd8aa4..d108b73 100644 --- a/app/Services/MetricCalculators/PlanCalculator.php +++ b/app/Services/MetricCalculators/PlanCalculator.php @@ -28,32 +28,31 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf $startDate = Carbon::parse($startDate); $endDate = Carbon::parse($endDate); $startYear = $startDate->copy()->startOfYear()->setHours(9); - $startPreviousMonth = $endDate->copy()->startOfMonth()->setHours(9); - $startCurrentMonth = $endDate->copy()->startOfMonth()->setHours(9); - $currentMonth = $endDate->month; - // Кол-во дней в выбранном диапазоне (для нормы за период — пропорционально дням, а не месяцам) + // Кол-во дней в выбранном диапазоне (для нормы за период) и с начала года (для долга) $daysInRange = max(1, $startDate->diffInDays($endDate)); + $daysFromYearStart = max(1, $startYear->diffInDays($endDate)); + $daysFromYearStartToRangeStart = max(0, $startYear->diffInDays($startDate)); - // Получаем фактические выписки с начала года по прошлый месяц - $previousOutcomeMonth = DB::table('report_duties as r') + // Факт с начала года по конец выбранного периода, день в день + $actualYearToDateByDept = 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) // Используем < + ->where('r.period_end', '>=', $startYear) + ->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'); - // Получаем фактические выписки за текущий месяц - $actualCurrentMonth = DB::table('report_duties as r') + // Факт с начала года по начало выбранного периода (то, что уже "закрыто" до этого периода) + $actualToRangeStartByDept = 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) + ->where('r.period_end', '>=', $startYear) + ->where('r.period_end', '<=', $startDate) ->select('r.rf_department_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total')) ->groupBy('r.rf_department_id') ->get() @@ -74,30 +73,26 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf // План на 1 день (равномерно, от годового плана) $dailyPlan = $annualPlan > 0 ? $annualPlan / 365 : 0; - // ===== БЕЗОПАСНОЕ ПОЛУЧЕНИЕ ЗНАЧЕНИЙ ===== - // Факт за прошлые месяцы (без текущего) - $actualToLastMonth = (int) ($previousOutcomeMonth[$departmentId]->total ?? 0); + // Факт с начала года по конец выбранного периода + $actualYearToDate = (int) ($actualYearToDateByDept[$departmentId]->total ?? 0); - // Факт за текущий месяц (с проверкой существования ключа) - $actualCurrent = (int) ($actualCurrentMonth[$departmentId]->total ?? 0); + // Факт с начала года по начало выбранного периода + $actualToRangeStart = (int) ($actualToRangeStartByDept[$departmentId]->total ?? 0); - // Общий факт с начала года - $actualYearToDate = $actualToLastMonth + $actualCurrent; + // План с начала года на дату конца периода — день в день, а не помесячно + $cumulativePlan = (int) round($dailyPlan * $daysFromYearStart); - // ===== 1. План с начала года нарастающим ===== - $cumulativePlan = $oneMonthPlan * $currentMonth; + // План с начала года на дату начала периода (то, что должно было быть выполнено ДО этого периода) + $cumulativePlanAtStart = (int) round($dailyPlan * $daysFromYearStartToRangeStart); - // ===== 2. Долг за прошлые месяцы ===== - $expectedToLastMonth = $oneMonthPlan * ($currentMonth - 1); - $debtFromYearStart = max(0, $expectedToLastMonth - $actualToLastMonth); + // Долг с начала года на дату конца периода (для справки, в тултипе) + $debtFromYearStart = max(0, $cumulativePlan - $actualYearToDate); - // ===== 3. Остаток плана на текущий месяц (с безопасной проверкой) ===== - $currentMonthPlanOnly = max(0, $oneMonthPlan - $actualCurrent); + // Долг, накопленный ДО начала выбранного периода — им "утяжеляем" план за период, + // чтобы % выполнения учитывал непогашенный долг, а не только текущий период + $debtAtPeriodStart = max(0, $cumulativePlanAtStart - $actualToRangeStart); - // ===== 4. ИТОГОВЫЙ ДОЛГ ===== - $totalDebt = $currentMonthPlanOnly + $debtFromYearStart; - - // ===== 5. Процент выполнения плана ===== + // Процент выполнения плана с начала года на дату конца периода $cumulativePercent = $cumulativePlan > 0 ? round($actualYearToDate * 100 / $cumulativePlan) : 0; @@ -106,12 +101,9 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf 'year_plan' => $annualPlan, 'month_plan' => $oneMonthPlan, 'period_plan' => (int) round($dailyPlan * $daysInRange), // норма за выбранный период (дневной план × кол-во дней) - 'total_debt' => $totalDebt, - 'current_mouth_dept' => $currentMonthPlanOnly, + 'debt_at_period_start' => $debtAtPeriodStart, // непогашенный долг на начало периода '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, ]; diff --git a/app/Services/StatisticsService.php b/app/Services/StatisticsService.php index 058f487..2a5fdba 100644 --- a/app/Services/StatisticsService.php +++ b/app/Services/StatisticsService.php @@ -321,7 +321,8 @@ class StatisticsService // План/факт именно за выбранный период (а не с начала года), тот же принцип, // что и в "Норма"/"Выбыло" на странице Штаба (PlanCalculator::period_plan vs outcome). - $recipientPlan = collect($departmentsPlans)->sum('period_plan'); + // К плану добавляем непогашенный долг на начало периода, чтобы он влиял на % выполнения. + $recipientPlan = collect($departmentsPlans)->sum('period_plan') + collect($departmentsPlans)->sum('debt_at_period_start'); $recipientProgress = $grandTotals['outcome_sum']; return [ diff --git a/resources/js/Pages/Statistic/Components/OutcomeColumn.vue b/resources/js/Pages/Statistic/Components/OutcomeColumn.vue index 9425cc6..99877e4 100644 --- a/resources/js/Pages/Statistic/Components/OutcomeColumn.vue +++ b/resources/js/Pages/Statistic/Components/OutcomeColumn.vue @@ -15,30 +15,35 @@ const props = defineProps({ 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, + month_plan: 0, + period_plan: 0, + debt_at_period_start: 0, + cumulative_plan: 0, + debt_from_year: 0, + actual_year_to_date: 0, + cumulative_percent: 0, }) } }) +// План за период + непогашенный долг, накопленный до начала периода +const planWithDebt = computed(() => { + return Number(props.plan?.period_plan ?? 0) + Number(props.plan?.debt_at_period_start ?? 0) +}) + +// % выполнения плана за период с учётом долга (иначе долг никак не влияет на оценку) +const periodPercent = computed(() => { + if (!planWithDebt.value) return 0 + return Math.round((Number(props.value) * 100) / planWithDebt.value) +}) + +// Цвет долга с начала года (на дату конца выбранного периода) const typePlan = computed(() => { if (props.plan.debt_from_year > 20) return 'error' if (props.plan.debt_from_year > 10) return 'warning' return 'success' }) - -// % выполнения плана именно за выбранный период (не с начала года) -const periodPercent = computed(() => { - const planForPeriod = Number(props.plan?.period_plan ?? 0) - if (!planForPeriod) return 0 - return Math.round((Number(props.value) * 100) / planForPeriod) -}) - План / Выбывшие: {{ plan.period_plan }} / {{ value }} + План (с долгом) / Выбывшие: {{ planWithDebt }} / {{ value }} Долг с начала года: {{ plan.debt_from_year }} -