You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

942 lines
39 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* Created by PhpStorm.
* User: weizongsong
* Date: 2019-04-12
* Time: 22:34
*/
namespace App\Http\Controllers\Admin;
use App\Customer;
use App\Libs\AlipayF2F;
use App\Models\AdminAreaLink;
use App\Models\AdminBuildingLink;
use App\Models\Area;
use App\Models\Balance;
use App\Models\Bed;
use App\Models\FactorItems;
use App\Models\OrderItems;
use App\Models\Orders;
use App\Models\Paramedic;
use App\Models\Product;
use App\Models\ProductItems;
use App\Models\ProductParamedicLevel;
use App\Models\Project;
use App\Models\Recharge;
use App\Models\Refund;
use App\Scopes\AdminProjectScope;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Spatie\Permission\Models\Role;
class StatisticsController extends CommonController
{
public $bladePath = "admin.statistics";
public $urlPrefix = "admin/statistics";
public $modelName = "数据统计";
public $noProjects = "用户名下没有可以管理的项目";
public function _checkProjects()
{
$projects = (new Project())->adminProject()->orderBy("id", "desc")->get();
view()->share(compact("projects"));
return $projects;
}
public function getYears()
{
$start_year = config("start_year");
$years = [];
for ($i = date("Y"); $i >= $start_year; $i--) {
$years[] = $i;
}
view()->share(compact("years"));
}
public function _getMonths()
{
$months = [];
for ($i = 1; $i <= 12; $i++) {
$mm = $i < 10 ? "0" . $i : $i;
$months[] = (date("Y") - 2) . "-" . $mm;
}
for ($i = 1; $i <= 12; $i++) {
$mm = $i < 10 ? "0" . $i : $i;
$months[] = (date("Y") - 1) . "-" . $mm;
}
for ($i = 1; $i <= 12; $i++) {
$mm = $i < 10 ? "0" . $i : $i;
$months[] = date("Y") . "-" . $mm;
}
view()->share(compact("months"));
return $months;
}
public function _getMonthData()
{
$projects = $this->_checkProjects();
$project_id = request()->project_id ?? $projects->first()->id;
$project = $projects->keyBy("id")->toArray()["{$project_id}"];
$month = request()->month ?? date("Y-m");
$months = $this->_getMonths();
DB::enableQueryLog();
$paramedics = (new Paramedic())->withoutGlobalScope(AdminProjectScope::class)->where(function ($query) use ($project_id, $month) {
$order_item_paramedic_ids = (new OrderItems())
->whereRaw("(DATE_FORMAT(`service_date`,'%Y-%m') = '{$month}' or DATE_FORMAT(`paid_at`,'%Y-%m') = '{$month}')")
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})->pluck("paramedic_id")->toArray();
$query->where("project_id", $project_id)->orWhereIn("id", $order_item_paramedic_ids);
})->with([
"orderItems" => function ($query) use ($month, $project_id) {
$query->whereRaw("(DATE_FORMAT(`service_date`,'%Y-%m') = '{$month}' or DATE_FORMAT(`paid_at`,'%Y-%m') = '{$month}')")
->where("total", ">", 0)
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})
->with([
"order",
"product" => function ($query) {
$query->withoutGlobalScope(AdminProjectScope::class);
},
"productItem",
"productParamedicLevel",
"paramedic" => function ($query) {
$query->withoutGlobalScope(AdminProjectScope::class);
},
"bed",
"room",
"building",
"area"
])
->orderBy("id");
}
])->get();
$allItems = collect();
foreach ($paramedics as $paramedic) {
foreach ($paramedic->orderItems as $orderItem) {
if ($orderItem->paid_at) {
$paid_at_this_month = Carbon::parse($orderItem->paid_at)->isSameMonth($month) ? true : false;
} else {
$paid_at_this_month = false;
}
if (Carbon::parse($orderItem->service_date)->isSameMonth($month)) {
$orderItem->service_date_in_this_month = $orderItem->service_date;
} else {
$orderItem->service_date_in_this_month = OrderItems::PREV_MONTH;
}
$orderItem->paid_at_this_month = $paid_at_this_month;
$orderItem = $orderItem->calculateFee();
}
$paramedic->originalOrderItems = $paramedic->orderItems;
$allItems = $allItems->concat($paramedic->orderItems);
$paramedic->orderItems = $paramedic->orderItems->groupBy("service_date_in_this_month");
}
$days = date("t", strtotime($month));
$dates = [];
for ($i = 1; $i <= $days; $i++) {
$dd = $i < 10 ? "0" . $i : $i;
$dates[] = $dd;
}
$dates[] = OrderItems::PREV_MONTH;
view()->share(compact("projects", "project_id", "month", "months", "dates", "paramedics", "allItems"));
return compact("projects", "project_id", "month", "months", "dates", "paramedics", "allItems");
}
public function salary(Request $request)
{
$projects = $this->_checkProjects();
if (!$projects->count()) {
return $this->error($this->noProjects);
}
$this->_getMonthData();
$laravel_duration = microtime(true) - LARAVEL_START;
return view($this->urlPrefix . "/salary", compact("laravel_duration"));
}
public function overview(Request $request)
{
$projects = $this->_checkProjects();
if (!$projects->count()) {
return $this->error($this->noProjects);
}
$project_id = request()->project_id ?? $projects->first()->id;
$project = Project::find($project_id);
$month = request()->month ?? date("Y-m");
$months = $this->_getMonths();
$start_timestamp = strtotime($month);
$end_timestamp = strtotime("+1 month", strtotime($month));
//根据项目获取相关数据
$prev_month_balance = Balance::whereRaw("UNIX_TIMESTAMP(`created_at`) < " . $start_timestamp)
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})->sum("money");
$this_month_balance = Balance::whereRaw("UNIX_TIMESTAMP(`created_at`) < " . $end_timestamp)
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})->sum("money");
$this_month_balances = Balance::whereRaw("UNIX_TIMESTAMP(`created_at`) >= " . $start_timestamp . " and UNIX_TIMESTAMP(`created_at`) < " . $end_timestamp)
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})->get();
return view($this->urlPrefix . "/overview", compact("project_id", "month", "project", "prev_month_balance", "this_month_balance", "this_month_balances"));
}
public function syncOrderItems(Request $request)
{
DB::enableQueryLog();
//采用指定订单号
$model = OrderItems::whereHas("order", function ($query) {
$query->whereIn("serial", ["20240101000016", "20240103000044", "20240103000055", "20240103000068", "20240104000010", "20240104000011", "20240104000012", "20240104000035", "20240104000040", "20240105000009", "20240105000042", "20240105000049", "20240105000051", "20240105000063", "20240105000064", "20231212000051", "20240104000023", "20240104000033", "20240104000036", "20240104000049", "20240104000059", "20240104000060", "20240105000043", "20240105000045", "20240105000048", "20240105000065", "20240105000066", "20240101000024", "20240104000007", "20240104000030", "20240104000034", "20240104000044", "20240104000065"]);
})->where("factors", "not like", "%所在科室%");
//采用指定订单号结束
if ($request->last_id) {
$model = $model->where("id", ">", $request->last_id);
}
$orderItems = $model->with("order")->limit(50)->get();
if (!$orderItems->count()) {
dd("已处理完毕");
}
DB::beginTransaction();
try {
foreach ($orderItems as $orderItem) {
$factors = json_decode($orderItem->factors, true);
$parent_factors = json_decode($orderItem->order->factors, true);
if (
!in_array("所在科室", collect($factors)->pluck("factor_name")->toArray())
&& in_array("所在科室", collect($parent_factors)->pluck("factor_name")->toArray())
) {
$add = collect($parent_factors)->keyBy("factor_name")["所在科室"];
$factors[] = $add;
$orderItem->update([
"factors" => json_encode($factors, JSON_UNESCAPED_UNICODE)
]);
}
}
DB::commit();
return $this->success("处理成功一批,正在跳转到下一批!", url($this->urlPrefix . "/salary/sync-order-items?month={$request->month}&last_id=" . $orderItems->last()->id));
} catch (\Exception $exception) {
DB::rollBack();
dd($exception->getMessage());
}
}
public function finance(Request $request)
{
$projects = $this->_checkProjects();
if (!$projects->count()) {
return $this->error($this->noProjects);
}
$project_id = request()->project_id ?? $projects->first()->id;
$project = $projects->keyBy("id")->toArray()["{$project_id}"];
$month = request()->month ?? date("Y-m");
$months = $this->_getMonths();
// 判断是否护士长
$userId = auth()->id();
$roleId = Role::where('name', 'like', '%护士长%')->where('guard_name', 'admin')->value('id');
$hushizhang = DB::table('model_has_roles')->where('role_id', $roleId)
->where('model_type', 'App\Admin')
->where('model_id', $userId)
->count();
// 是否院方管理
$roleId = Role::where('name', 'like', '%院方管理%')->where('guard_name', 'admin')->value('id');
$yuanfang = DB::table('model_has_roles')->where('role_id', $roleId)
->where('model_type', 'App\Admin')
->where('model_id', $userId)
->count();
// 获取这个护士长病区的订单或院方管理楼栋的订单
$user = auth()->user();
$orderIds = [];
if ($hushizhang) {
$areaId = AdminAreaLink::where('project_id', $project_id)->where('admin_id', $user->id)->pluck('area_id');
if ($areaId->isNotEmpty()) {
$bedList = Bed::whereIn('area_id', $areaId)->pluck('id');
if ($bedList->isNotEmpty()) {
$orderIds = Orders::whereIn('bed_id', $bedList)->pluck('id');
}
}
} elseif ($yuanfang) {
$buildingId = AdminBuildingLink::where('project_id', $project_id)->where('admin_id', $user->id)->pluck('building_id');
if ($buildingId->isNotEmpty()) {
$areaId = Area::whereIn('building_id', $buildingId)->pluck('id');
if ($areaId->isNotEmpty()) {
$bedList = Bed::whereIn('area_id', $areaId)->pluck('id');
if ($bedList->isNotEmpty()) {
$orderIds = Orders::whereIn('bed_id', $bedList)->pluck('id');
}
}
}
// 如果没有关联数据,$orderIds 保持为空数组,后续查询会返回空结果
}
$recharges = Recharge::with(["manager", "order", "patient"])
->where(function ($query) use ($hushizhang, $yuanfang, $orderIds) {
if ($hushizhang || $yuanfang) {
if (!empty($orderIds)) {
$query->whereIn('order_id', $orderIds);
} else {
// 如果没有关联数据,返回空结果
$query->whereIn('order_id', []);
}
}
})
->withCount("refunds")
->whereNotNull("paid_at")
->whereRaw("DATE_FORMAT(`paid_at`,'%Y-%m') = '{$month}'")
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})
->get();
$recharges_sum = $recharges->sum('money');
$refunds = Refund::whereNotNull("paid_at")
->where(function ($query) use ($hushizhang, $yuanfang, $orderIds) {
if ($hushizhang || $yuanfang) {
if (!empty($orderIds)) {
$query->whereIn('order_id', $orderIds);
} else {
// 如果没有关联数据,返回空结果
$query->whereIn('order_id', []);
}
}
})
->whereRaw("DATE_FORMAT(`paid_at`,'%Y-%m') = '{$month}'")
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})
->get();
$refunds_sum = $refunds->sum('money');
return view($this->bladePath . ".finance", compact("recharges_sum", "refunds_sum", "recharges", "refunds", "projects", "project_id", "months", "month"));
}
public function memberBalanceByDate(Request $request)
{
$is_export = $request->get('is_export', 0);
$projects = $this->_checkProjects();
if (!$projects->count()) {
return $this->error($this->noProjects);
}
$mode = $request->mode ?: 'customer';
$project_id = request()->project_id ?? $projects->first()->id;
$project = Project::find($project_id);
$before_date = $request->before_date ?: date("Y-m-d");
$before_datetime = strtotime($before_date . " 23:59:59");
$before_datetime_text = date("Y-m-d H:i:s", $before_datetime);
DB::enableQueryLog();
$customers = collect();
$orderBalances = collect();
if ($mode == 'order') {
$orderBalances = Orders::query()
->withoutGlobalScopes()
->leftJoin("project", "project.id", "=", "orders.project_id")
->leftJoin("customers", "customers.id", "=", "orders.customer_id")
->leftJoin("patient", "patient.id", "=", "orders.patient_id")
->leftJoin("balance", function ($join) use ($before_datetime_text) {
$join->on("balance.order_id", "=", "orders.id")
->whereNull("balance.deleted_at")
->where("balance.created_at", "<=", $before_datetime_text);
})
->whereNull("orders.deleted_at")
->where("orders.project_id", $project_id)
->whereExists(function ($query) use ($before_date) {
$query->select(DB::raw(1))
->from('order_items')
->whereColumn('order_items.order_id', 'orders.id')
->whereNull('order_items.deleted_at')
->whereDate('order_items.service_date', $before_date);
})
->groupBy(
"orders.id",
"orders.serial",
"project.name",
"orders.from_date",
"orders.to_date",
"orders.contact",
"orders.mobile",
"orders.status",
"customers.mobile",
"patient.name"
)
->orderBy("orders.from_date")
->orderBy("orders.id")
->get([
"orders.id",
"orders.serial",
"project.name as project_name",
"orders.from_date",
"orders.to_date",
"orders.contact",
"orders.mobile as contact_mobile",
DB::raw("'" . $before_datetime_text . "' as point_time"),
DB::raw("
CASE orders.status
WHEN " . Orders::STATUS_UNCONFIRMED . " THEN '" . Orders::TEXT_UNCONFIRMED . "'
WHEN " . Orders::STATUS_UNASSIGNED . " THEN '" . Orders::TEXT_UNASSIGNED . "'
WHEN " . Orders::STATUS_ONGOING . " THEN '" . Orders::TEXT_ONGOING . "'
WHEN " . Orders::STATUS_FINISHED . " THEN '" . Orders::TEXT_FINISHED . "'
ELSE orders.status
END as status_name
"),
"customers.mobile as customer_mobile",
"patient.name as patient_name",
DB::raw("COALESCE(SUM(balance.money), 0) as balance"),
]);
if ($is_export) {
$rows = $orderBalances->toArray();
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', '项目名称');
$sheet->setCellValue('B1', '时点');
$sheet->setCellValue('C1', '订单号');
$sheet->setCellValue('D1', '订单状态');
$sheet->setCellValue('E1', '客户手机号');
$sheet->setCellValue('F1', '联系人');
$sheet->setCellValue('G1', '联系电话');
$sheet->setCellValue('H1', '被陪护人');
$sheet->setCellValue('I1', '服务开始');
$sheet->setCellValue('J1', '服务结束');
$sheet->setCellValue('K1', '时点余额');
$count = count($rows);
for ($i = 2; $i <= $count + 1; $i++) {
$row = $rows[$i - 2];
$sheet->setCellValue('A' . $i, $row['project_name']);
$sheet->setCellValue('B' . $i, $row['point_time']);
$sheet->setCellValue('C' . $i, $row['serial']);
$sheet->setCellValue('D' . $i, $row['status_name']);
$sheet->setCellValue('E' . $i, $row['customer_mobile']);
$sheet->setCellValue('F' . $i, $row['contact']);
$sheet->setCellValue('G' . $i, $row['contact_mobile']);
$sheet->setCellValue('H' . $i, $row['patient_name']);
$sheet->setCellValue('I' . $i, $row['from_date']);
$sheet->setCellValue('J' . $i, $row['to_date']);
$sheet->setCellValue('K' . $i, $row['balance']);
}
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="' . ($project ? $project->name : '项目') . '_订单时点余额_' . date('YmdHis') . '.xlsx"');
header('Cache-Control: max-age=0');
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
exit;
}
} else {
$customers = (new Customer())
->whereNull("deleted_at")
->with([
"patients" => function ($query) use ($before_datetime) {
$query->whereRaw("UNIX_TIMESTAMP(`created_at`) <= {$before_datetime}")->orderBy("id", "desc");
},
])
->whereHas("orders", function ($query) use ($before_datetime, $project_id) {
$query
->where("project_id", $project_id);
})
->get();
}
$laravel_duration = microtime(true) - LARAVEL_START;
return view($this->bladePath . ".customer-balance", compact(
"customers",
"orderBalances",
"before_datetime",
"before_date",
"before_datetime_text",
"project_id",
"laravel_duration",
"mode"
));
}
public function fixMonthLastDayCheckout(Request $request)
{
$day = $request->day;
if (!$day) {
dd("日期不正确");
}
DB::enableQueryLog();
$orders = Orders::whereIn("serial", ["20201130000003", "20201130000005", "20201128000004", "20201126000006", "20201127000005", "20201126000003", "20201130000004", "20201127000008", "20201130000001", "20201126000008"])->get();
$orderItems = OrderItems::with(["balance", "order"])
->whereNotNull("paid_at")
->whereRaw("UNIX_TIMESTAMP(`paid_at`) > " . strtotime("2020-12-01"))
->whereRaw("UNIX_TIMESTAMP(`paid_at`) < " . strtotime("2020-12-04"))
->whereIn("order_id", $orders->pluck("id")->toArray())
->limit(100)->get();
try {
foreach ($orderItems as $orderItem) {
$new_paid_at = "{$day} 22:00:01";
$orderItem->update([
"paid_at" => $new_paid_at
]);
$orderItem->balance->update([
"created_at" => $new_paid_at,
"remark" => "本条经过时间校准处理。原始创建时间为:" . $orderItem->balance->created_at
]);
}
DB::commit();
dd("处理完毕");
} catch (\Exception $exception) {
DB::rollBack();
dd($exception->getMessage());
}
}
public function fixBalanceOrderId()
{
$balances = Balance::whereNull("order_id")->with("belongs")->limit("100")->get();
if (!$balances->count()) {
return $this->success("处理完毕", url($this->urlPrefix . "/overview"));
}
foreach ($balances as $balance) {
$balance->timestamps = false;
$balance->update([
"order_id" => $balance->belongs->order_id
]);
}
return $this->success("处理完毕" . $balances->count() . "条数据,正在进入下一批数据。", url($this->urlPrefix . "/finance/fix-balance-order-id?timer=" . time()));
}
public function manualQueryRecharge($id)
{
$recharge = Recharge::find($id);
$res = (new AlipayF2F())->manualQuery($recharge);
dd($res);
}
public function huli(Request $request)
{
$projects = (new StatisticsController())->_checkProjects();
$defaultProjectsId = ($projects[0]->id) ?? '';
$project_id = $request->get('project_id', $defaultProjectsId);
$month = request()->month ?? date("Y-m");
$userId = auth()->id();
// 判断是否护士长
$roleId = Role::where('name', 'like', '%护士长%')->where('guard_name', 'admin')->value('id');
$hushizhang = DB::table('model_has_roles')->where('role_id', $roleId)
->where('model_type', 'App\Admin')
->where('model_id', $userId)->count();
// 是否院方管理
$roleId = Role::where('name', 'like', '%院方管理%')->where('guard_name', 'admin')->value('id');
$yuanfang = DB::table('model_has_roles')->where('role_id', $roleId)
->where('model_type', 'App\Admin')
->where('model_id', $userId)->count();
$areaId = [];
$buildingId = [];
if ($hushizhang) {
$user = auth()->user();
$areaId = AdminAreaLink::where(function ($qeury) use ($project_id) {
if ($project_id) {
$qeury->where('project_id', $project_id);
}
})->where('admin_id', $user->id)->pluck('area_id');
} elseif ($yuanfang) {
$user = auth()->user();
$buildingId = AdminBuildingLink::where(function ($qeury) use ($project_id) {
if ($project_id) {
$qeury->where('project_id', $project_id);
}
})->where('admin_id', $user->id)->pluck('building_id');
}
$allAreas = Area::where('project_id', $project_id)->with('project', 'building')->where(function ($query) use ($areaId, $buildingId) {
if ($areaId) {
$query->whereIn('id', $areaId);
}
if ($buildingId) {
$query->whereIn('building_id', $buildingId);
}
})->get();
// 病区智能排序1.数字开头按数字升序 2.中文数字开头转阿拉伯数字后升序 3.其他按myindex+id
$sortedAreas = $allAreas->sort(function ($a, $b) {
$keyA = $this->_getAreaNameSortKey($a->name ?? '', $a->myindex ?? 0, $a->id);
$keyB = $this->_getAreaNameSortKey($b->name ?? '', $b->myindex ?? 0, $b->id);
if ($keyA[0] !== $keyB[0]) {
return $keyA[0] <=> $keyB[0];
}
if ($keyA[1] !== $keyB[1]) {
return $keyA[1] <=> $keyB[1];
}
return $keyA[2] <=> $keyB[2];
})->values();
$perPage = 40;
$currentPage = (int) $request->get('page', 1);
$currentPage = max(1, $currentPage);
$items = $sortedAreas->forPage($currentPage, $perPage)->values();
$data = new LengthAwarePaginator($items, $sortedAreas->count(), $perPage, $currentPage, [
'path' => $request->url(),
'query' => $request->query(),
]);
$data->appends($request->all())->render();
// ===== 重构:先从 OrderItems 获取真实价格数据,生成列,然后再统计每个病区的数据 =====
// 1. 获取当前分页中所有病区的床位 ID
$areaIds = collect($data->items())->pluck('id');
$beds = Bed::whereIn('area_id', $areaIds)->get();
$bedsByArea = $beds->groupBy('area_id');
$allBedIds = $beds->pluck('id');
// 2. 获取这些床位在当月的所有真实子订单(仅统计 total > 0且属于当前项目
$orderItems = OrderItems::whereIn('bed_id', $allBedIds)
->where('total', '>', 0)
->where('paid_at', 'like', '%' . $month . '%')
->whereHas('order', function ($query) use ($project_id) {
$query->where('project_id', $project_id);
})
->get();
// 3. 按「真实价格」去重生成表头列xx元/天),并取一条样本数据上的因子名称作为小字说明
// 使用 groupBy('total'),避免闭包分组可能导致的 array_key_exists 问题
$columns = $orderItems
->groupBy('total')
->map(function ($group, $total) {
/** @var OrderItems $sample */
$sample = $group->first();
$price = (float) $total;
$factor_item_name = '';
if ($sample && $sample->factors) {
$factors = json_decode($sample->factors, true) ?: [];
if (!empty($factors)) {
// 取第一条因子作为说明文本(前端展示不变:价格 + 小字)
$factor_item_name = $factors[0]['factor_item_name'] ?? '';
}
}
return [
'price' => $price,
'name' => $price . '元/天',
'factor_item_name' => $factor_item_name,
];
})
->sortBy('price')
->values()
->toArray();
// 4. 按病区 + 列价格 统计每个病区在每个价格档位上的总金额
$sumOrderTotal = 0;
foreach ($data as $item) {
// 当前病区所有床位 ID
$bedIds = $bedsByArea->get($item->id, collect())->pluck('id');
// 当前病区相关的所有子订单
$areaOrderItems = $orderItems->whereIn('bed_id', $bedIds->all());
// 病区总收入
$item->order_total = $areaOrderItems->sum('total');
$sumOrderTotal += $item->order_total;
// 子项:按表头列(价格)统计该病区在各价格档位上的总金额
$lies = [];
foreach ($columns as $col) {
$price = (float) $col['price'];
$colTotal = $areaOrderItems->where('total', $price)->sum('total');
$lies[] = [
'name' => $col['name'],
'factor_item_name' => $col['factor_item_name'],
'total_price' => $price,
'total' => $colTotal,
];
}
$item->lies = $lies;
}
// 表头列:使用上面生成的真实价格列(包含价格和 factor_item 名称)
$lie = $columns;
$months = $this->_getMonths();
return view($this->bladePath . ".huli", compact("sumOrderTotal", "data", "month", "lie", "projects", "project_id"));
}
/**
* 获取病区名称的排序键,用于智能排序
* 规则1.数字开头->按数字升序 2.中文数字开头->转阿拉伯数字后升序 3.其他->按myindex+id
*
* @return array [tier, primary_sort, secondary_sort] tier=0有数字 tier=1无数字
*/
private function _getAreaNameSortKey($name, $myindex, $id)
{
$name = trim((string) $name);
$myindex = (int) $myindex;
$id = (int) $id;
// 1. 以阿拉伯数字开头:提取数字
if (preg_match('/^(\d+)/', $name, $m)) {
return [0, (int) $m[1], 0];
}
// 2. 以中文数字开头:解析并转换
$cnNum = $this->_parseChineseNumber($name);
if ($cnNum !== null) {
return [0, $cnNum, 0];
}
// 3. 都不满足:按 myindex、id
return [1, $myindex, $id];
}
/**
* 解析病区名称开头的连续中文数字,转为阿拉伯数字
*
* @return int|null 解析到的数字,若无则 null
*/
private function _parseChineseNumber($str)
{
static $digits = [
'' => 0, '零' => 0, '一' => 1, '二' => 2, '两' => 2, '三' => 3, '四' => 4,
'五' => 5, '六' => 6, '七' => 7, '八' => 8, '九' => 9,
'十' => 10, '百' => 100, '千' => 1000, '万' => 10000,
];
$s = preg_replace('/\s+/', '', $str);
if ($s === '') {
return null;
}
$len = mb_strlen($s);
$i = 0;
$result = 0;
$temp = 0; // 用于「十」「百」前的累积
while ($i < $len) {
$ch = mb_substr($s, $i, 1);
if (!isset($digits[$ch])) {
break;
}
$val = $digits[$ch];
if ($val >= 10) {
// 十、百、千、万:乘数
if ($temp === 0) {
$temp = 1;
}
$result += $temp * $val;
$temp = 0;
} else {
$temp = $val;
}
$i++;
}
$result += $temp;
if ($i === 0) {
return null;
}
return $result;
}
/**
* 获取动态列
*/
public function getLies($bedIds, $productItem, $factor, $month)
{
$list = [];
// 修复如果床位ID为空直接返回空数组避免 whereIn 空数组导致的SQL错误
if (empty($bedIds)) {
return $list;
}
foreach ($productItem as $item) {
foreach ($factor as $factor_item) {
$query = OrderItems::where('product_item_id', $item->id)
->whereIn("bed_id", $bedIds)
->where('paid_at', 'like', '%' . $month . '%');
// 使用 MySQL 5.7 的 JSON_SEARCH 精确匹配包含指定 factor_item_id 的子订单
$factorItemId = (int) $factor_item->id;
$query->whereRaw("JSON_SEARCH(factors, 'one', ?, NULL, '$[*].factor_item_id') IS NOT NULL", [$factorItemId]);
// 1列对应的真实总收入该因子 + 产品子项 + 病区 + 月份 下所有子订单的 total 之和
$total = (float) $query->sum('total');
// 2表头展示的价格按“公式”计算保证不是 0近似真实下单价
// 价格 = 产品子项基础价 + 因子价格
$totalPrice = (float) $item->price + (float) $factor_item->price;
$list[] = [
'name' => $totalPrice . '元/天',
'factor_item_name' => $factor_item->name ?? '',
'total_price' => $totalPrice,
'product_item_id' => $item->id,
'factor_item_id' => $factor_item->id,
'total' => $total
];
}
}
return $list;
}
/**
* 床位统计
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function bed()
{
$is_export = \request('is_export', 0);
$projects = $this->_checkProjects();
if (!$projects->count()) {
return $this->error($this->noProjects);
}
$project_id = request()->project_id ?? $projects->first()->id;
$project = Project::find($project_id);
$month = request()->month ?? date("Y-m");
$months = $this->_getMonths();
// 当月天数
$days = date('t', strtotime($month));
$area = Area::withCount('beds')->where('project_id', $project_id)->get();
$beds = Bed::whereIn('area_id', $area->pluck('id'))->where('project_id', $project_id)->get();
$totalBed = $beds->count();
// 订单总数
$orderTotal = Orders::where('project_id', $project_id)
->where('created_at', 'like', '%' . $month . '%')
->whereIn('bed_id', $beds->pluck('id'))
->count();
foreach ($area as $item) {
// 床位占比
$item->bed_rate = 0;
if ($totalBed) {
$item->bed_rate = round($item->beds_count / $totalBed, 4) * 100;
}
// 订单数
$bedsIdTemp = Bed::where('area_id', $item->id)->where('project_id', $project_id)->pluck('id');
$item->order_total = OrderItems::where('created_at', 'like', '%' . $month . '%')
->where('total', '>', 0)
->whereIn('bed_id', $bedsIdTemp)
->count();
$item->rate = 0;
if ($bedsIdTemp->count()) {
// 配比
$item->rate = round($item->order_total / ($days * $bedsIdTemp->count()), 4) * 100;
}
}
// 导出
if ($is_export) {
$area = $area->toArray();
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setCellValue('A1', '病区');
$sheet->setCellValue('B1', '床位数量');
$sheet->setCellValue('C1', '床位占比');
$sheet->setCellValue('D1', '陪护人天');
$sheet->setCellValue('E1', '陪护率');
$count = count($area); //计算有多少条数据
for ($i = 2; $i <= $count + 1; $i++) {
$sheet->setCellValue('A' . $i, $area[$i - 2]['name']);
$sheet->setCellValue('B' . $i, $area[$i - 2]['beds_count']);
$sheet->setCellValue('C' . $i, $area[$i - 2]['bed_rate']);
$sheet->setCellValue('D' . $i, $area[$i - 2]['order_total']);
$sheet->setCellValue('E' . $i, $area[$i - 2]['rate']);
}
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment;filename="' . $project->name . '_' . date('YmdHis') . '.xlsx"');
header('Cache-Control: max-age=0');
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
exit;
}
return view($this->bladePath . ".bed", compact("area", "orderTotal", "totalBed", "project", "project_id", "month", "projects"));
}
/**
* 收入统计
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function income()
{
$is_export = \request('is_export', 0);
$projects = $this->_checkProjects();
if (!$projects->count()) {
return $this->error($this->noProjects);
}
$project_id = request()->project_id ?? $projects->first()->id;
$project = Project::find($project_id);
$month = request()->month ?? date("Y-m");
$months = $this->_getMonths();
// 当月天数
$days = date('t', strtotime($month));
$areaList = Area::withCount('beds')->where('project_id', $project_id)->get();
// 合计
$total['order_total'] = $total['shouxufei'] = $total['zengshishui'] = $total['shijishouru'] = $total['guanlifei'] = 0;
$area = [];
foreach ($areaList as $item) {
$beds = Bed::where('area_id', $item->id)->where('project_id', $project_id)->get();
$orderTotal = OrderItems::where('paid_at', 'like', '%' . $month . '%')
->whereIn('bed_id', $beds->pluck('id'))
->sum("total");
$item->order_total = $orderTotal;
$item->shouxufei = round($orderTotal * 0.006, 2);
$item->zengshishui = round($orderTotal * 0.06, 2);
$item->shijishouru = round($orderTotal - $item->shouxufei - $item->zengshishui, 2);
$item->guanlifei = round($item->shijishouru * 0.06, 2);
if (!empty($item->order_total)) {
$area[] = $item;
}
// 合计
$total['order_total'] += $item->order_total;
$total['shouxufei'] += $item->shouxufei;
$total['zengshishui'] += $item->zengshishui;
$total['shijishouru'] += $item->shijishouru;
$total['guanlifei'] += $item->guanlifei;
}
$refund['order_total'] = abs(Balance::where('created_at', 'like', '%' . $month . '%')
->where('belongs_type', 'App\Models\Refund')
->whereHas("order", function ($query) use ($project_id) {
$query->where("project_id", $project_id);
})->sum("money"));
$refund['shijishouru'] = $refund['order_total'];
$refund['guanlifei'] = round($refund['order_total'] * 0.06, 2);
// 总计
$zongji['order_total'] = $total['order_total'] - $refund['order_total'];
$zongji['shouxufei'] = $total['shouxufei'] * 2;
$zongji['zengshishui'] = $total['zengshishui'] * 2;
$zongji['shijishouru'] = $total['shijishouru'] - $refund['shijishouru'];
$zongji['guanlifei'] = $total['guanlifei'] - $refund['guanlifei'];
$admin = Auth::guard("admin")->user();
return view($this->bladePath . ".income", compact("admin", "zongji", "refund", "total", "area", "project", "project_id", "month", "projects"));
}
}