Работа с планом

This commit is contained in:
Brusnitsyn
2026-07-06 14:56:46 +00:00
parent d1e087e4a8
commit ce6dace65c
3 changed files with 48 additions and 51 deletions

View File

@@ -28,32 +28,31 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf
$startDate = Carbon::parse($startDate); $startDate = Carbon::parse($startDate);
$endDate = Carbon::parse($endDate); $endDate = Carbon::parse($endDate);
$startYear = $startDate->copy()->startOfYear()->setHours(9); $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)); $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') ->join('duty_report_metric_results as mr', 'r.id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $departmentIds) ->whereIn('r.rf_department_id', $departmentIds)
->where('mr.rf_metrika_item_id', self::getMetricId()) ->where('mr.rf_metrika_item_id', self::getMetricId())
->where('r.period_end', '>=', $startYear) // Используем >= ->where('r.period_end', '>=', $startYear)
->where('r.period_end', '<=', $startPreviousMonth) // Используем < ->where('r.period_end', '<=', $endDate)
->select('r.rf_department_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total')) ->select('r.rf_department_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total'))
->groupBy('r.rf_department_id') ->groupBy('r.rf_department_id')
->get() ->get()
->keyBy('rf_department_id'); ->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') ->join('duty_report_metric_results as mr', 'r.id', '=', 'mr.rf_report_id')
->whereIn('r.rf_department_id', $departmentIds) ->whereIn('r.rf_department_id', $departmentIds)
->where('mr.rf_metrika_item_id', self::getMetricId()) ->where('mr.rf_metrika_item_id', self::getMetricId())
->where('r.period_end', '>', $startCurrentMonth) // Используем >= ->where('r.period_end', '>=', $startYear)
->where('r.period_end', '<=', $endDate) ->where('r.period_end', '<=', $startDate)
->select('r.rf_department_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total')) ->select('r.rf_department_id', DB::raw('SUM(CAST(mr.value AS DECIMAL)) as total'))
->groupBy('r.rf_department_id') ->groupBy('r.rf_department_id')
->get() ->get()
@@ -74,30 +73,26 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf
// План на 1 день (равномерно, от годового плана) // План на 1 день (равномерно, от годового плана)
$dailyPlan = $annualPlan > 0 ? $annualPlan / 365 : 0; $dailyPlan = $annualPlan > 0 ? $annualPlan / 365 : 0;
// ===== БЕЗОПАСНОЕ ПОЛУЧЕНИЕ ЗНАЧЕНИЙ ===== // Факт с начала года по конец выбранного периода
// Факт за прошлые месяцы (без текущего) $actualYearToDate = (int) ($actualYearToDateByDept[$departmentId]->total ?? 0);
$actualToLastMonth = (int) ($previousOutcomeMonth[$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, $cumulativePlan - $actualYearToDate);
$debtFromYearStart = max(0, $expectedToLastMonth - $actualToLastMonth);
// ===== 3. Остаток плана на текущий месяц (с безопасной проверкой) ===== // Долг, накопленный ДО начала выбранного периода — им "утяжеляем" план за период,
$currentMonthPlanOnly = max(0, $oneMonthPlan - $actualCurrent); // чтобы % выполнения учитывал непогашенный долг, а не только текущий период
$debtAtPeriodStart = max(0, $cumulativePlanAtStart - $actualToRangeStart);
// ===== 4. ИТОГОВЫЙ ДОЛГ ===== // Процент выполнения плана с начала года на дату конца периода
$totalDebt = $currentMonthPlanOnly + $debtFromYearStart;
// ===== 5. Процент выполнения плана =====
$cumulativePercent = $cumulativePlan > 0 $cumulativePercent = $cumulativePlan > 0
? round($actualYearToDate * 100 / $cumulativePlan) ? round($actualYearToDate * 100 / $cumulativePlan)
: 0; : 0;
@@ -106,12 +101,9 @@ class PlanCalculator extends BaseMetricService implements MetricCalculatorInterf
'year_plan' => $annualPlan, 'year_plan' => $annualPlan,
'month_plan' => $oneMonthPlan, 'month_plan' => $oneMonthPlan,
'period_plan' => (int) round($dailyPlan * $daysInRange), // норма за выбранный период (дневной план × кол-во дней) 'period_plan' => (int) round($dailyPlan * $daysInRange), // норма за выбранный период (дневной план × кол-во дней)
'total_debt' => $totalDebt, 'debt_at_period_start' => $debtAtPeriodStart, // непогашенный долг на начало периода
'current_mouth_dept' => $currentMonthPlanOnly,
'cumulative_plan' => $cumulativePlan, 'cumulative_plan' => $cumulativePlan,
'debt_from_year' => $debtFromYearStart, 'debt_from_year' => $debtFromYearStart,
'actual_to_last_month' => $actualToLastMonth,
'outcome_in_current_month' => $actualCurrent,
'actual_year_to_date' => $actualYearToDate, 'actual_year_to_date' => $actualYearToDate,
'cumulative_percent' => $cumulativePercent, 'cumulative_percent' => $cumulativePercent,
]; ];

View File

@@ -321,7 +321,8 @@ class StatisticsService
// План/факт именно за выбранный период (а не с начала года), тот же принцип, // План/факт именно за выбранный период (а не с начала года), тот же принцип,
// что и в "Норма"/"Выбыло" на странице Штаба (PlanCalculator::period_plan vs outcome). // что и в "Норма"/"Выбыло" на странице Штаба (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']; $recipientProgress = $grandTotals['outcome_sum'];
return [ return [

View File

@@ -15,30 +15,35 @@ const props = defineProps({
plan: { plan: {
type: Object, type: Object,
default: () => ({ 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, 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(() => { const typePlan = computed(() => {
if (props.plan.debt_from_year > 20) return 'error' if (props.plan.debt_from_year > 20) return 'error'
if (props.plan.debt_from_year > 10) return 'warning' if (props.plan.debt_from_year > 10) return 'warning'
return 'success' 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)
})
</script> </script>
<template> <template>
@@ -78,10 +83,9 @@ const periodPercent = computed(() => {
</template> </template>
<NSpace vertical> <NSpace vertical>
<NFlex align="center" justify="start" :wrap="false" size="small"> <NFlex align="center" justify="start" :wrap="false" size="small">
<NTag type="info">План / Выбывшие: {{ plan.period_plan }} / {{ value }}</NTag> <NTag type="info">План (с долгом) / Выбывшие: {{ planWithDebt }} / {{ value }}</NTag>
</NFlex> </NFlex>
<NTag :type="typePlan">Долг с начала года: {{ plan.debt_from_year }}</NTag> <NTag :type="typePlan">Долг с начала года: {{ plan.debt_from_year }}</NTag>
<!-- Долг с начала года: {{ plan.debt_from_year }}-->
</NSpace> </NSpace>
<!-- <br>План:--> <!-- <br>План:-->
<!-- <span class="font-medium">{{ plan.cumulative_plan }}</span>, долг:--> <!-- <span class="font-medium">{{ plan.cumulative_plan }}</span>, долг:-->