[Изменено]: столбец Выбыло в статистике #3
This commit is contained in:
115
app/Services/MetricCalculators/PlanCalculator.php
Normal file
115
app/Services/MetricCalculators/PlanCalculator.php
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services\MetricCalculators;
|
||||||
|
|
||||||
|
use App\Contracts\MetricCalculatorInterface;
|
||||||
|
use App\Models\Department;
|
||||||
|
use App\Services\Base\BaseMetricService;
|
||||||
|
use App\Services\DateRange;
|
||||||
|
use App\Services\DateRangeService;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class PlanCalculator extends BaseMetricService implements MetricCalculatorInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMetricId(): int
|
||||||
|
{
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function calculate(array $departmentIds, string $startDate, string $endDate): array
|
||||||
|
{
|
||||||
|
$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;
|
||||||
|
|
||||||
|
// Получаем фактические выписки с начала года по прошлый месяц
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,23 +10,21 @@ use App\Models\Report;
|
|||||||
use App\Models\ReportDuty;
|
use App\Models\ReportDuty;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\UserDepartment;
|
use App\Models\UserDepartment;
|
||||||
|
use App\Services\MetricCalculators\PlanCalculator;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class StatisticsService
|
class StatisticsService
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected BedDayService $bedDayService
|
protected BedDayService $bedDayService,
|
||||||
|
protected PlanCalculator $planCalculator,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function getStatisticsData(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array
|
public function getStatisticsData(User $user, string $startDate, string $endDate, bool $isRangeOneDay): array
|
||||||
{
|
{
|
||||||
$this->bedDayService->clearMemoryCache();
|
$this->bedDayService->clearMemoryCache();
|
||||||
|
|
||||||
// Годовой план
|
|
||||||
$recipientPlanOfYear = 0;
|
|
||||||
$progressPlanOfYear = 0;
|
|
||||||
|
|
||||||
// 1. Получаем отделения
|
// 1. Получаем отделения
|
||||||
$departments = Department::select('department_id', 'name_short', 'rf_department_type', 'user_name', 'order')
|
$departments = Department::select('department_id', 'name_short', 'rf_department_type', 'user_name', 'order')
|
||||||
->with('departmentType')
|
->with('departmentType')
|
||||||
@@ -41,11 +39,6 @@ class StatisticsService
|
|||||||
return $this->emptyResponse();
|
return $this->emptyResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Рассчитываем коэффициент периода (дни периода / 365)
|
|
||||||
$start = Carbon::parse($startDate);
|
|
||||||
$end = Carbon::parse($endDate);
|
|
||||||
$monthsInPeriod = ceil($start->diffInMonths($end));
|
|
||||||
|
|
||||||
$allDeptIds = $departments->flatten()->pluck('department_id')->toArray();
|
$allDeptIds = $departments->flatten()->pluck('department_id')->toArray();
|
||||||
|
|
||||||
// 2а. Нежелательные события по отделениям за период (прямой запрос)
|
// 2а. Нежелательные события по отделениям за период (прямой запрос)
|
||||||
@@ -114,6 +107,8 @@ class StatisticsService
|
|||||||
$grandRecipientPlan = 0;
|
$grandRecipientPlan = 0;
|
||||||
$grandProgressPlan = 0;
|
$grandProgressPlan = 0;
|
||||||
|
|
||||||
|
$departmentsPlans = $this->planCalculator->calculate($allDeptIds, $startDate, $endDate);
|
||||||
|
|
||||||
foreach ($departments as $typeName => $deptList) {
|
foreach ($departments as $typeName => $deptList) {
|
||||||
$groupedData[$typeName] = [];
|
$groupedData[$typeName] = [];
|
||||||
$totalsByType[$typeName] = $this->initTypeTotals();
|
$totalsByType[$typeName] = $this->initTypeTotals();
|
||||||
@@ -136,14 +131,6 @@ class StatisticsService
|
|||||||
$bedsCount = (int) ($beds[$deptId]->value ?? 0);
|
$bedsCount = (int) ($beds[$deptId]->value ?? 0);
|
||||||
$currentCount = (int) ($currentPatients[$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;
|
$plan = 0;
|
||||||
$emergency = 0;
|
$emergency = 0;
|
||||||
@@ -200,11 +187,6 @@ class StatisticsService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$grandRecipientPlan += $periodPlan;
|
|
||||||
$grandProgressPlan += $outcome;
|
|
||||||
|
|
||||||
$percentPlanOfYear = $periodPlan > 0 ? round($outcome * 100 / $periodPlan) : 0;
|
|
||||||
|
|
||||||
// Расчеты
|
// Расчеты
|
||||||
$allCount = $plan + $emergency;
|
$allCount = $plan + $emergency;
|
||||||
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
|
$percentLoaded = $bedsCount > 0 ? round($currentCount * 100 / $bedsCount) : 0;
|
||||||
@@ -225,8 +207,6 @@ class StatisticsService
|
|||||||
|
|
||||||
$departmentName = $dept->user_name ?? $dept->name_short;
|
$departmentName = $dept->user_name ?? $dept->name_short;
|
||||||
|
|
||||||
$progressPlanOfYear += $outcome;
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'department' => $departmentName,
|
'department' => $departmentName,
|
||||||
'department_id' => $deptId,
|
'department_id' => $deptId,
|
||||||
@@ -255,12 +235,25 @@ class StatisticsService
|
|||||||
'preoperativeDays' => $preoperativeValue,
|
'preoperativeDays' => $preoperativeValue,
|
||||||
'preoperativeSum' => $preoperativeSum,
|
'preoperativeSum' => $preoperativeSum,
|
||||||
'preoperativePatientCount' => $preoperativePatientCount,
|
'preoperativePatientCount' => $preoperativePatientCount,
|
||||||
|
'plan' => $departmentsPlans[$deptId],
|
||||||
|
|
||||||
'progressPlanOfYear' => $periodPlan,
|
// 'progressPlanOfYear' => $cumulativePlan,
|
||||||
'percentPlanOfYear' => $percentPlanOfYear,
|
// 'percentPlanOfYear' => $percentPlanOfYear,
|
||||||
'needPlanOfYear' => $periodPlan > 0 && $periodPlan > $outcome
|
// 'needPlanOfYear' => $cumulativePlan > 0 && $cumulativePlan > $outcome
|
||||||
? $periodPlan - $outcome
|
// ? $cumulativePlan - $outcome
|
||||||
: 0,
|
// : 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,
|
'lethality' => $lethality,
|
||||||
'type' => $typeName,
|
'type' => $typeName,
|
||||||
'isDepartment' => true,
|
'isDepartment' => true,
|
||||||
|
|||||||
@@ -11,65 +11,67 @@ const props = defineProps({
|
|||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
needCompletedToPlan: {
|
plan: {
|
||||||
type: [Number, String],
|
type: Object,
|
||||||
default: null
|
default: () => ({
|
||||||
},
|
actual_to_date: 0,
|
||||||
planOfYear: {
|
cumulative_plan: 0,
|
||||||
type: [Number, String],
|
current_mouth_dept: 0,
|
||||||
default: null
|
debt_from_year: 0,
|
||||||
},
|
month_plan: 0,
|
||||||
progressCompletedToPlan: {
|
outcome_in_current_mouth: 0,
|
||||||
type: [Number, String],
|
total_debt: 0,
|
||||||
default: null
|
year_plan: 0,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NFlex align="center" justify="center" class="relative!">
|
<NFlex align="center" justify="center" class="relative!">
|
||||||
<NTooltip v-if="needCompletedToPlan && !isTotalRow"
|
<!-- <NTooltip v-if="needCompletedToPlan && !isTotalRow"-->
|
||||||
trigger="hover"
|
<!-- trigger="hover"-->
|
||||||
:arrow="false"
|
<!-- :arrow="false"-->
|
||||||
>
|
<!-- >-->
|
||||||
<template #trigger>
|
<!-- <template #trigger>-->
|
||||||
<NTag :type="typePlan(planOfYear, needCompletedToPlan)"
|
<!-- <NTag :type="typePlan(planOfYear, needCompletedToPlan)"-->
|
||||||
round
|
<!-- round-->
|
||||||
:bordered="false"
|
<!-- :bordered="false"-->
|
||||||
size="tiny"
|
<!-- size="tiny"-->
|
||||||
class="absolute! -left-1.5 bottom-2.5 text-xs"
|
<!-- class="absolute! -left-1.5 bottom-2.5 text-xs"-->
|
||||||
>
|
<!-- >-->
|
||||||
{{ needCompletedToPlan }}
|
<!-- {{ needCompletedToPlan }}-->
|
||||||
</NTag>
|
<!-- </NTag>-->
|
||||||
</template>
|
<!-- </template>-->
|
||||||
Требуется выписать в текущем месяце
|
<!-- Требуется выписать в текущем месяце-->
|
||||||
</NTooltip>
|
<!-- </NTooltip>-->
|
||||||
<div>
|
<div>
|
||||||
{{ value }}
|
{{ value }}
|
||||||
</div>
|
</div>
|
||||||
<NTooltip v-if="progressCompletedToPlan && !isTotalRow"
|
<NTooltip v-if="plan && !isTotalRow"
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
:arrow="false"
|
:arrow="false"
|
||||||
>
|
>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NTag :type="percentType(progressCompletedToPlan)"
|
<NTag :type="percentType(plan.cumulative_percent)"
|
||||||
round
|
round
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
size="tiny"
|
size="tiny"
|
||||||
class="absolute! -right-1.5 bottom-2.5 text-xs"
|
class="absolute! -right-1.5 bottom-2.5 text-xs"
|
||||||
>
|
>
|
||||||
{{ progressCompletedToPlan }}%
|
{{ plan.cumulative_percent }}%
|
||||||
</NTag>
|
</NTag>
|
||||||
</template>
|
</template>
|
||||||
<NFlex align="center" justify="start" :wrap="false" size="small">
|
<NFlex align="center" justify="start" :wrap="false" size="small">
|
||||||
<NTag size="small" round type="default" :bordered="false">Месячный</NTag>
|
<NTag size="small" round type="info" :bordered="false">План на месяц: {{ plan.month_plan }}</NTag>
|
||||||
<NTag v-if="needCompletedToPlan === 0" size="small" round type="success" :bordered="false">Выполнен</NTag>
|
<NTag v-if="plan.current_mouth_dept === 0" size="small" round type="success" :bordered="false">Выполнен</NTag>
|
||||||
<NTag v-else size="small" round type="warning" :bordered="false">Не выполнен</NTag>
|
<NTag v-else size="small" round type="warning" :bordered="false">Не выполнен</NTag>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
Прогресс выполнения плана
|
Сначала года + текущий месяц:
|
||||||
<br>План:
|
<br>План:
|
||||||
<span class="font-medium">{{ planOfYear }}</span>, требуется выписать еще:
|
<span class="font-medium">{{ plan.cumulative_plan }}</span>, долг:
|
||||||
<span class="font-medium">{{ needCompletedToPlan }}</span>
|
<span class="font-medium">{{ plan.debt_from_year }}</span> +
|
||||||
|
<span class="font-medium">{{ plan.current_mouth_dept }}</span>
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -180,10 +180,8 @@ const columns = ref([
|
|||||||
OutcomeColumn,
|
OutcomeColumn,
|
||||||
{
|
{
|
||||||
isTotalRow: row.isTotalRow,
|
isTotalRow: row.isTotalRow,
|
||||||
progressCompletedToPlan: row.percentPlanOfYear,
|
value: row.outcome,
|
||||||
needCompletedToPlan: row.needPlanOfYear,
|
plan: row.plan,
|
||||||
planOfYear: row.progressPlanOfYear,
|
|
||||||
value: row.outcome
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user