order_no = self::generateOrderNo(); $order->patient_id = $params['patient_id']; $order->creator_id = $params['creator_id']; $order->order_type = $params['order_type']; $order->amount = $params['amount']; // 付呗且申请审核支付单:待审核(5);否则待支付(1) if ($channel === 'fubei') { $order->payment_method = 'fubei'; $order->status = $requirePaymentSlipAudit ? 5 : 1; } else { $order->status = 1; // 待支付 } $order->remark = $params['remark'] ?? ''; $order->save(); return $order; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 创建企业微信对外收款订单(参考 https://developer.work.weixin.qq.com/document/path/93665) * 创建后返回 order_no,供员工在企业微信发起收款时作为 out_trade_no 使用 * 收款成功后,微信支付回调会自动更新订单为已支付 * @param array $params patient_id, order_type, amount, creator_id, remark * @return array|false ['order_no' => '...', 'order' => Order] */ public static function createForWechatWork(array $params) { $order = self::create($params); if (!$order) { return false; } return [ 'order_no' => $order->order_no, 'order' => $order->toArray(), ]; } /** * @notes 从企业微信对外收款账单同步订单(员工直接在企业微信发起收款,未经过后台创建) * 拉取 get_bill_list:trade_state 1=已完成(已支付) 3=已完成有退款;会创建订单,并在状态变化时更新(如已支付→已退款) * @param int $beginTime 开始时间戳(秒) * @param int $endTime 结束时间戳(秒) * @return array ['created' => int, 'updated' => int, 'skipped' => int, 'errors' => string[]] */ public static function syncFromWechatWorkBills(int $beginTime, int $endTime): array { $result = ['created' => 0, 'updated' => 0, 'skipped' => 0, 'errors' => []]; $service = new WechatWorkExternalPayService(); $wechatWorkConfig = config('pay.wechat_work', []); $defaultCreatorId = $wechatWorkConfig['default_creator_id'] ?? ''; $defaultOrderType = $wechatWorkConfig['default_order_type'] ?? 6; $payeeUserids = []; $firstResp = $service->getBillList($beginTime, $endTime, '', '', 100); if (count($firstResp['bill_list'] ?? []) === 0 && !empty($firstResp['next_cursor'])) { $admins = Admin::where('work_wechat_userid', '<>', '')->whereNull('delete_time')->column('work_wechat_userid'); $payeeUserids = array_values(array_unique(array_filter($admins))); } if (empty($payeeUserids)) { $payeeUserids = ['']; } foreach ($payeeUserids as $payeeUserid) { $cursor = ''; do { $resp = $service->getBillList($beginTime, $endTime, $payeeUserid, $cursor, 100); if (($resp['errcode'] ?? -1) !== 0) { $result['errors'][] = '获取账单失败: ' . ($resp['errmsg'] ?? '未知错误'); break; } $billList = $resp['bill_list'] ?? []; $cursor = $resp['next_cursor'] ?? ''; foreach ($billList as $bill) { $tradeState = (int)($bill['trade_state'] ?? 0); // 1:已完成(已支付) 3:已完成有退款 — 见企业微信 get_bill_list 文档 if (!in_array($tradeState, [1, 3], true)) { $result['skipped']++; continue; } $targetStatus = $tradeState === 3 ? 4 : 2; // 4=已退款 2=已支付 $orderNo = (string)($bill['out_trade_no'] ?? $bill['trade_no'] ?? ''); $totalFee = (int)($bill['total_fee'] ?? $bill['total_amount'] ?? 0); $tradeNo = (string)($bill['transaction_id'] ?? $bill['trade_no'] ?? ''); $payTime = $bill['pay_time'] ?? $bill['time_end'] ?? ''; $billPayeeUserid = (string)($bill['payee_userid'] ?? ''); if (empty($orderNo) || $totalFee <= 0) { $result['skipped']++; continue; } $amount = round($totalFee / 100, 2); $orderType = ($amount == 5.00) ? 1 : $defaultOrderType; $creatorId = 0; if ($billPayeeUserid) { $admin = Admin::where('work_wechat_userid', $billPayeeUserid)->whereNull('delete_time')->find(); if ($admin) { $creatorId = (int)$admin->id; } } $payerExternalUserid = (string)($bill['external_userid'] ?? ''); $paymentTimeStr = $payTime ? (is_numeric($payTime) ? date('Y-m-d H:i:s', (int)$payTime) : (string)$payTime) : date('Y-m-d H:i:s'); $order = Order::where('order_no', $orderNo)->find(); if ($order) { $needSave = false; $currentStatus = (int)$order->status; if ($currentStatus !== $targetStatus) { // 待支付:随账单变为已支付或已退款(含未同步到「已支付」即发生退款) if ($currentStatus === 1) { $order->status = $targetStatus; $needSave = true; } elseif ($currentStatus === 2 && $targetStatus === 4) { $order->status = 4; $needSave = true; } elseif ($currentStatus === 4 && $targetStatus === 2) { // 账单从「有退款」变回「已完成」等异常纠偏 $order->status = 2; $needSave = true; } // 已取消(3) 不自动改支付状态,避免覆盖后台取消 } if ($currentStatus === 1 || $targetStatus === 2) { if ($order->payment_method !== 'wechat') { $order->payment_method = 'wechat'; $needSave = true; } if ($paymentTimeStr !== '' && (string)$order->payment_time !== $paymentTimeStr) { $order->payment_time = $paymentTimeStr; $needSave = true; } } if ($tradeNo !== '' && (string)$order->trade_no !== $tradeNo) { $order->trade_no = $tradeNo; $needSave = true; } if (abs((float)$order->amount - $amount) > 0.000001) { $order->amount = $amount; $needSave = true; } $defaultCreatorIdInt = (int)$defaultCreatorId; if ($billPayeeUserid && $creatorId !== $defaultCreatorIdInt && (int)$order->creator_id === $defaultCreatorIdInt) { $order->payee_userid = $billPayeeUserid; $order->payer_external_userid = $payerExternalUserid; $order->creator_id = $creatorId; $needSave = true; } elseif ($billPayeeUserid !== '' && (string)$order->payee_userid !== $billPayeeUserid) { $order->payee_userid = $billPayeeUserid; $needSave = true; } if ($payerExternalUserid !== '' && (string)$order->payer_external_userid !== $payerExternalUserid) { $order->payer_external_userid = $payerExternalUserid; $needSave = true; } if ($amount == 5.00 && (int)$order->order_type !== 1) { $order->order_type = 1; $needSave = true; } if ($needSave) { $order->save(); $result['updated']++; } else { $result['skipped']++; } } else { try { $order = new Order(); $order->order_no = $orderNo; $order->patient_id = null; $order->creator_id = $creatorId; $order->order_type = $orderType; $order->amount = $amount; $order->status = $targetStatus; $order->payment_method = 'wechat'; $order->payment_time = $paymentTimeStr; $order->trade_no = $tradeNo; $remark = '企业微信直接收款同步,待关联患者'; if ($tradeState === 3) { $refundCent = (int)($bill['total_refund_fee'] ?? 0); if ($refundCent > 0) { $remark .= '(账单含退款' . round($refundCent / 100, 2) . '元)'; } } $order->remark = $remark; $order->payee_userid = $billPayeeUserid; $order->payer_external_userid = $payerExternalUserid; $order->save(); $result['created']++; } catch (\Exception $e) { $result['errors'][] = "订单 {$orderNo}: " . $e->getMessage(); Log::error("企业微信账单同步创建订单失败: {$orderNo}, " . $e->getMessage()); } } } } while ($cursor); } return $result; } /** * @notes 从回调数据创建订单(员工直接在企业微信发起收款时,订单不存在则自动创建) * @param string $orderNo 商户订单号(out_trade_no) * @param float $amount 金额(元) * @param string $tradeNo 微信交易号 * @return Order|null */ public static function createFromCallback(string $orderNo, float $amount, string $tradeNo = ''): ?Order { try { $wechatWorkConfig = config('pay.wechat_work', []); $creatorId = $wechatWorkConfig['default_creator_id'] ?? 1; $orderType = $wechatWorkConfig['default_order_type'] ?? 6; $order = new Order(); $order->order_no = $orderNo; $order->patient_id = null; // 待关联,管理员可后续编辑 $order->creator_id = $creatorId; $order->order_type = $orderType; $order->amount = $amount; $order->status = 2; // 已支付 $order->payment_method = 'wechat'; $order->payment_time = date('Y-m-d H:i:s'); $order->trade_no = $tradeNo; $order->remark = '企业微信直接收款,待关联患者'; $order->save(); return $order; } catch (\Exception $e) { return null; } } /** * @notes 编辑订单 * @param int $id * @param array $params * @return bool */ public static function edit(int $id, array $params): bool { try { $order = Order::find($id); if (!$order) { self::setError('订单不存在'); return false; } if (isset($params['remark'])) { $order->remark = $params['remark']; } // 支持关联患者(待关联订单可后续关联) if (array_key_exists('patient_id', $params)) { $order->patient_id = $params['patient_id'] ? (int)$params['patient_id'] : null; } // 支持修改订单类型 if (isset($params['order_type'])) { $order->order_type = (int)$params['order_type']; } $order->save(); return true; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 支付订单 * @param int $id * @param string $paymentMethod * @return bool */ public static function pay(int $id, string $paymentMethod): bool { try { $order = Order::find($id); if (!$order) { self::setError('订单不存在'); return false; } $st = (int)$order->status; // 1=待支付;5=待审核(付呗+申请支付单审核) 时允许录入手动到账 if ($st === 1) { // 正常待支付 } elseif ($st === 5 && (string)($order->payment_method ?? '') === 'fubei') { // 待审核的付呗单,通过人工确认后标记已支付 } else { self::setError('订单状态不允许支付'); return false; } $order->status = 2; // 已支付 $order->payment_method = $paymentMethod; $order->payment_time = date('Y-m-d H:i:s'); $order->save(); return true; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 取消订单 * @param int $id * @return bool */ public static function cancel(int $id): bool { try { $order = Order::find($id); if (!$order) { self::setError('订单不存在'); return false; } $st = (int)$order->status; if ($st !== 1 && !($st === 5 && (string)($order->payment_method ?? '') === 'fubei')) { self::setError('只有待支付或待审核(付呗)的订单才能取消'); return false; } $order->status = 3; // 已取消 $order->save(); return true; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 退款订单 * @param int $id * @return bool */ public static function refund(int $id): bool { try { $order = Order::find($id); if (!$order) { self::setError('订单不存在'); return false; } if ($order->status != 2) { self::setError('只有已支付的订单才能退款'); return false; } $order->status = 4; // 已退款 $order->save(); return true; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 删除订单 * @param int $id * @return bool */ public static function delete(int $id): bool { try { $order = Order::find($id); if (!$order) { self::setError('订单不存在'); return false; } // 删除订单 $order->delete(); return true; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 今日收益(按角色权限:超管/主管看全部,普通员工看自己) * @param int $adminId 当前管理员ID * @param array $adminInfo 管理员信息(含 root、role 等) * @return array ['amount' => string, 'count' => int] */ public static function todayRevenue(int $adminId, array $adminInfo = []): array { $query = Order::where('status', 2) ->whereBetweenTime('payment_time', date('Y-m-d') . ' 00:00:00', date('Y-m-d') . ' 23:59:59') ->whereNull('delete_time'); if (empty($adminInfo['root']) || $adminInfo['root'] != 1) { $supervisorRoles = config('project.order_edit_all_roles', [0, 3]); $roleIds = \app\common\model\auth\AdminRole::where('admin_id', $adminId)->column('role_id'); if (count(array_intersect($roleIds, $supervisorRoles)) === 0) { $query->where('creator_id', $adminId); } } $row = $query->fieldRaw('COUNT(*) as cnt, COALESCE(SUM(amount), 0) as total')->find(); $total = is_object($row) ? ($row->total ?? 0) : ($row['total'] ?? 0); $cnt = is_object($row) ? ($row->cnt ?? 0) : ($row['cnt'] ?? 0); return [ 'amount' => number_format((float)$total, 2, '.', ''), 'count' => (int)$cnt, ]; } /** * @notes 获取订单详情 * @param int $id * @return array|null */ public static function detail(int $id): ?array { $order = Order::with(['patient', 'creator']) ->find($id); if (!$order) { return null; } return $order->toArray(); } private static $error = ''; public static function setError(string $error): void { self::$error = $error; } public static function getError(): string { return self::$error; } /** * @notes 生成支付宝签名 * @param array $params * @param string $privateKey * @return string */ private static function generateAlipaySign(array $params, string $privateKey): string { // 移除sign字段 unset($params['sign']); // 按键排序 ksort($params); // 构建签名字符串 $signStr = ''; foreach ($params as $key => $value) { if ($value !== '' && $value !== null) { $signStr .= $key . '=' . $value . '&'; } } $signStr = rtrim($signStr, '&'); // 使用私钥签名 $privateKeyResource = openssl_pkey_get_private($privateKey); openssl_sign($signStr, $sign, $privateKeyResource, OPENSSL_ALGO_SHA256); openssl_free_key($privateKeyResource); return base64_encode($sign); } /** * @notes 生成微信签名 * @param array $params * @param string $apiKey * @return string */ private static function generateWechatSign(array $params, string $apiKey): string { // 移除sign字段 unset($params['sign']); // 按键排序 ksort($params); // 构建签名字符串 $signStr = ''; foreach ($params as $key => $value) { if ($value !== '' && $value !== null) { $signStr .= $key . '=' . $value . '&'; } } $signStr .= 'key=' . $apiKey; // MD5签名 return strtoupper(md5($signStr)); } /** * @notes 生成随机字符串 * @param int $length * @return string */ private static function generateNonceStr(int $length = 32): string { $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; $str = ''; for ($i = 0; $i < $length; $i++) { $str .= $chars[mt_rand(0, strlen($chars) - 1)]; } return $str; } /** * @notes 支付宝支付 * @param $order * @return string|false */ public static function alipayPay($order) { try { // 这里调用支付宝SDK生成支付链接 // 示例:生成支付宝支付URL $payUrl = self::generateAlipayUrl($order); return $payUrl; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 微信支付 * @param $order * @return array|false */ public static function wechatPay($order) { try { // 这里调用微信SDK生成支付二维码或链接 // 示例:生成微信支付数据 $payData = self::generateWechatPayData($order); return $payData; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * @notes 生成支付宝支付URL * @param $order * @return string */ private static function generateAlipayUrl($order): string { try { // 获取订单号 $orderNo = is_array($order) ? $order['order_no'] : $order->order_no; $amount = is_array($order) ? ($order['amount'] ?? 0) : $order->amount; // 获取支付宝配置 $alipayConfig = config('pay.alipay'); if (empty($alipayConfig['app_id']) || empty($alipayConfig['merchant_private_key'])) { throw new \Exception('支付宝配置不完整'); } // 使用支付宝SDK生成支付URL $params = [ 'app_id' => $alipayConfig['app_id'], 'method' => 'alipay.trade.page.pay', 'charset' => 'UTF-8', 'sign_type' => 'RSA2', 'timestamp' => date('Y-m-d H:i:s'), 'version' => '1.0', 'notify_url' => $alipayConfig['notify_url'] ?: config('app.url') . '/api/order/alipay-notify', 'return_url' => $alipayConfig['return_url'] ?: config('app.url') . '/order', 'biz_content' => json_encode([ 'out_trade_no' => $orderNo, 'product_code' => 'FAST_INSTANT_TRADE_PAY', 'total_amount' => $amount, 'subject' => '订单支付', 'body' => '订单号:' . $orderNo, ], JSON_UNESCAPED_UNICODE), ]; // 生成签名 $sign = self::generateAlipaySign($params, $alipayConfig['merchant_private_key']); $params['sign'] = $sign; // 生成支付URL $payUrl = $alipayConfig['gateway_url'] . '?' . http_build_query($params); return $payUrl; } catch (\Exception $e) { self::setError($e->getMessage()); return ''; } } /** * @notes 生成微信支付数据 * @param $order * @return array */ private static function generateWechatPayData($order): array { try { // 获取订单号 $orderNo = is_array($order) ? $order['order_no'] : $order->order_no; $amount = is_array($order) ? ($order['amount'] ?? 0) : $order->amount; // 获取微信配置 $wechatConfig = config('pay.wechat'); if (empty($wechatConfig['app_id']) || empty($wechatConfig['mch_id']) || empty($wechatConfig['api_key'])) { throw new \Exception('微信支付配置不完整'); } // 生成微信支付数据 $payData = [ 'appid' => $wechatConfig['app_id'], 'mch_id' => $wechatConfig['mch_id'], 'nonce_str' => self::generateNonceStr(), 'body' => '订单支付', 'out_trade_no' => $orderNo, 'total_fee' => (int)($amount * 100), // 转换为分 'spbill_create_ip' => request()->ip() ?: '127.0.0.1', 'notify_url' => $wechatConfig['notify_url'] ?: config('app.url') . '/api/order/wechat-notify', 'trade_type' => 'NATIVE', // 二维码支付 ]; // 生成签名 $sign = self::generateWechatSign($payData, $wechatConfig['api_key']); $payData['sign'] = $sign; // 返回支付数据 return [ 'pay_url' => $wechatConfig['gateway_url'] . '/pay/unifiedorder', 'pay_data' => $payData, 'order_no' => $orderNo, ]; } catch (\Exception $e) { self::setError($e->getMessage()); return []; } } private static $orderTypeNames = [ 0 => '退款统计', 1 => '挂号费', 2 => '问诊费', 3 => '药品费用', 4 => '首付费用', 5 => '尾款费用', 6 => '其他费用', ]; /** * @notes 订单统计(按订单类型或退款) * @param array $params order_type(-1全部已支付类型合计,0退款,1挂号费,2问诊费,3药品费用,4首付,5尾款,6其他), days(0今天,7,30) * @return array */ public static function orderStats(array $params = [], int $adminId = 0, ?array $adminInfo = null) { $allPaidOrderTypes = [1, 2, 3, 4, 5, 6]; $orderType = array_key_exists('order_type', $params) ? (int) $params['order_type'] : 1; if ($orderType !== -1 && $orderType !== 0 && !array_key_exists($orderType, self::$orderTypeNames)) { $orderType = 1; } $orderTypeName = $orderType === -1 ? '全部(已支付)' : self::$orderTypeNames[$orderType]; $days = isset($params['days']) ? (int)$params['days'] : 7; // 数据范围:当前用户可见 admin id 集合;null 表示全部 $visibleAdminIds = ($adminInfo !== null && $adminId > 0) ? \app\common\service\DataScope\DataScopeService::getVisibleAdminIds($adminId, $adminInfo) : null; $endTime = !empty($params['end_time']) ? strtotime($params['end_time']) : time(); if ($days === 0) { $startTime = strtotime(date('Y-m-d')); } else { $days = $days > 0 ? min($days, 90) : 7; $startTime = strtotime("-{$days} days", $endTime); } $startStr = date('Y-m-d H:i:s', $startTime); $endStr = date('Y-m-d H:i:s', $endTime); $depts = \app\common\model\dept\Dept::where('status', 1) ->whereNull('delete_time') ->order('sort desc, id asc') ->column('name', 'id'); $query = Order::where('create_time', 'between', [$startStr, $endStr]) ->whereNull('delete_time'); if ($orderType === 0) { $query->where('status', 4); } elseif ($orderType === -1) { $query->whereIn('order_type', $allPaidOrderTypes)->where('status', 2); } else { $query->where('order_type', $orderType)->where('status', 2); } if ($visibleAdminIds !== null) { if ($visibleAdminIds === []) { $query->whereRaw('0 = 1'); } else { $query->whereIn('creator_id', $visibleAdminIds); } } $orderRows = $query ->field('creator_id, count(*) as cnt, sum(amount) as total_amount') ->group('creator_id') ->select() ->toArray(); $creatorIds = array_unique(array_column($orderRows, 'creator_id')); $creatorIds = array_filter($creatorIds); if (empty($creatorIds)) { return [ 'order_type' => $orderType, 'order_type_name' => $orderTypeName, 'date_range' => [date('Y-m-d', $startTime), date('Y-m-d', $endTime)], 'today_total' => 0, 'today_amount' => '0.00', 'today_ranking' => [], 'departments' => [], 'ranking' => [], 'chart' => ['xAxis' => [], 'series' => []], ]; } $counts = []; $amounts = []; foreach ($orderRows as $row) { $counts[$row['creator_id']] = (int)$row['cnt']; $amounts[$row['creator_id']] = round((float)$row['total_amount'], 2); } $adminDepts = \app\common\model\auth\AdminDept::whereIn('admin_id', $creatorIds) ->select() ->toArray(); $adminToDepts = []; foreach ($adminDepts as $ad) { $adminId = $ad['admin_id']; if (!isset($adminToDepts[$adminId])) { $adminToDepts[$adminId] = []; } $adminToDepts[$adminId][] = (int)$ad['dept_id']; } $admins = \app\common\model\auth\Admin::whereIn('id', $creatorIds) ->whereNull('delete_time') ->column('name', 'id'); $todayStart = strtotime(date('Y-m-d')); $todayEnd = time(); $todayStr = date('Y-m-d H:i:s', $todayStart); $todayEndStr = date('Y-m-d H:i:s', $todayEnd); $todayQuery = Order::where('create_time', 'between', [$todayStr, $todayEndStr]) ->whereNull('delete_time'); if ($orderType === 0) { $todayQuery->where('status', 4); } elseif ($orderType === -1) { $todayQuery->whereIn('order_type', $allPaidOrderTypes)->where('status', 2); } else { $todayQuery->where('order_type', $orderType)->where('status', 2); } if ($visibleAdminIds !== null) { if ($visibleAdminIds === []) { $todayQuery->whereRaw('0 = 1'); } else { $todayQuery->whereIn('creator_id', $visibleAdminIds); } } $todayRows = $todayQuery ->field('creator_id, count(*) as cnt, sum(amount) as total_amount') ->group('creator_id') ->select() ->toArray(); $todayCounts = []; $todayAmounts = []; foreach ($todayRows as $row) { $todayCounts[$row['creator_id']] = (int)$row['cnt']; $todayAmounts[$row['creator_id']] = round((float)$row['total_amount'], 2); } $todayTotal = array_sum($todayCounts); $todayAmount = array_sum($todayAmounts); $todayRanking = []; foreach ($creatorIds as $cid) { $cnt = (int)($todayCounts[$cid] ?? 0); $amt = (float)($todayAmounts[$cid] ?? 0); if ($cnt > 0 || $amt > 0) { $todayRanking[] = [ 'id' => (int)$cid, 'name' => $admins[$cid] ?? '未知', 'count' => $cnt, 'amount' => number_format($amt, 2, '.', ''), ]; } } usort($todayRanking, fn($a, $b) => $b['count'] <=> $a['count']); // 数据范围启用时(非 root/全部范围),无可见成员的部门不渲染 $hideEmptyDept = $visibleAdminIds !== null; $deptData = []; $assignedIds = []; foreach ($depts as $deptId => $deptName) { $members = []; foreach ($adminToDepts as $adminId => $deptIdsArr) { if (!in_array((int)$deptId, $deptIdsArr)) { continue; } $assignedIds[] = $adminId; $members[] = [ 'id' => (int)$adminId, 'name' => $admins[$adminId] ?? '未知', 'count' => (int)($counts[$adminId] ?? 0), 'amount' => number_format((float)($amounts[$adminId] ?? 0), 2, '.', ''), ]; } if ($hideEmptyDept && $members === []) { continue; } $deptTotal = array_sum(array_column($members, 'count')); $deptAmount = array_sum(array_map(fn($m) => (float)$m['amount'], $members)); $deptData[] = [ 'dept_id' => (int)$deptId, 'dept_name' => $deptName, 'members' => $members, 'total' => $deptTotal, 'amount' => number_format($deptAmount, 2, '.', ''), ]; } $unassignedIds = array_diff($creatorIds, array_unique($assignedIds)); if (!empty($unassignedIds)) { $members = []; foreach ($unassignedIds as $adminId) { $members[] = [ 'id' => (int)$adminId, 'name' => $admins[$adminId] ?? '未知', 'count' => (int)($counts[$adminId] ?? 0), 'amount' => number_format((float)($amounts[$adminId] ?? 0), 2, '.', ''), ]; } $deptData[] = [ 'dept_id' => 0, 'dept_name' => '未分配部门', 'members' => $members, 'total' => array_sum(array_column($members, 'count')), 'amount' => number_format(array_sum(array_map(fn($m) => (float)$m['amount'], $members)), 2, '.', ''), ]; } $ranking = []; foreach ($creatorIds as $adminId) { $ranking[] = [ 'id' => (int)$adminId, 'name' => $admins[$adminId] ?? '未知', 'count' => (int)($counts[$adminId] ?? 0), 'amount' => number_format((float)($amounts[$adminId] ?? 0), 2, '.', ''), ]; } usort($ranking, fn($a, $b) => $b['count'] <=> $a['count']); $chartNames = []; $chartData = []; foreach ($ranking as $i => $r) { $chartNames[] = ($i + 1) . '.' . $r['name']; $chartData[] = $r['count']; } return [ 'order_type' => $orderType, 'order_type_name' => $orderTypeName, 'date_range' => [date('Y-m-d', $startTime), date('Y-m-d', $endTime)], 'today_total' => $todayTotal, 'today_amount' => number_format($todayAmount, 2, '.', ''), 'today_ranking' => $todayRanking, 'departments' => $deptData, 'ranking' => $ranking, 'chart' => [ 'xAxis' => $chartNames, 'series' => [['name' => '单数', 'data' => $chartData]], ], ]; } /** * 与订单列表一致:超管或主管角色可见他人创建的订单 */ public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool { if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) { return true; } $supervisorRoles = config('project.order_edit_all_roles', [0, 3]); $myRoles = array_map('intval', $adminInfo['role_id'] ?? []); return count(array_intersect($myRoles, $supervisorRoles)) > 0; } /** * 业务订单可关联支付单列表:仅展示创建时间在此日 0 点及之后的支付单(编辑时当前单已关联的旧单仍保留在列表中) */ private static function payOrderLinkableMinCreateTs(): int { return strtotime('2026-04-20 00:00:00'); } /** * zyt_order.create_time 可能为 int 时间戳或 datetime 字符串,不可直接 (int) 强转(字符串会得到 0) */ private static function orderCreateTimeToUnix(mixed $value): int { if ($value === null || $value === '') { return 0; } if ($value instanceof \DateTimeInterface) { return $value->getTimestamp(); } if (is_int($value)) { return $value; } if (is_float($value)) { return (int) $value; } if (is_string($value)) { if (is_numeric($value) && strlen($value) <= 12) { return (int) $value; } $ts = strtotime($value); return $ts !== false ? $ts : 0; } return 0; } /** * 某诊单下可关联的支付单(待支付/已支付),供关联业务订单多选(主管/诊单医助看该诊单全部;否则仅本人创建) * 已占用(关联到未撤回/未取消的业务订单)的支付单会排除;编辑某业务订单时传 $exceptPrescriptionOrderId 以保留当前单已选中的项 * 列表仅含 2026-04-20 及之后创建的支付单(已关联到当前编辑业务订单的除外) * * @return array> */ public static function listPaidOrdersForDiagnosis(int $diagnosisId, int $adminId, array $adminInfo, ?int $exceptPrescriptionOrderId = null): array { if ($diagnosisId <= 0) { return []; } $exists = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->count() > 0; if (!$exists) { return []; } $q = Order::where('patient_id', $diagnosisId) ->whereIn('status', [2]) // 1=待支付, 2=已支付, 5=待审核 ->whereNull('delete_time'); if (!self::isOrderListSupervisor($adminId, $adminInfo)) { $assistantId = (int) Diagnosis::where('id', $diagnosisId)->value('assistant_id'); if ($assistantId !== $adminId) { $q->where('creator_id', $adminId); } } $rows = $q ->field(['id', 'order_no', 'order_type', 'amount', 'status', 'create_time', 'creator_id', 'remark']) ->order('id', 'desc') ->select() ->toArray(); $activePoIds = PrescriptionOrder::whereNull('delete_time') ->where('fulfillment_status', '<>', 4) ->column('id'); $busyPayIds = []; if ($activePoIds !== []) { $busyPayIds = PrescriptionOrderPayOrder::whereIn('prescription_order_id', $activePoIds) ->column('pay_order_id'); $busyPayIds = array_unique(array_map('intval', $busyPayIds)); } $whiteList = []; if ($exceptPrescriptionOrderId !== null && $exceptPrescriptionOrderId > 0) { $whiteList = array_map( 'intval', PrescriptionOrderPayOrder::where('prescription_order_id', $exceptPrescriptionOrderId)->column('pay_order_id') ); } $minCreateTs = self::payOrderLinkableMinCreateTs(); $out = []; foreach ($rows as $row) { $oid = (int) ($row['id'] ?? 0); if ($oid <= 0) { continue; } $busy = in_array($oid, $busyPayIds, true); $allowed = in_array($oid, $whiteList, true); if ($busy && ! $allowed) { continue; } $ct = self::orderCreateTimeToUnix($row['create_time'] ?? null); if ($ct < $minCreateTs && ! $allowed) { continue; } $out[] = $row; } return $out; } /** * 校验支付单均可关联到指定诊单(已支付、归属与权限) * 默认仅允许关联 2026-04-20 及之后创建的支付单;编辑业务订单时传入 $prescriptionOrderIdForDateGrace,已关联到该业务订单的旧单除外 * * @param int[] $ids zyt_order.id */ public static function assertPayOrdersLinkable( array $ids, int $diagnosisId, int $adminId, array $adminInfo, ?int $prescriptionOrderIdForDateGrace = null ): ?string { $ids = array_values(array_unique(array_filter(array_map('intval', $ids), static fn(int $v): bool => $v > 0))); if ($diagnosisId <= 0) { return '诊单无效'; } if ($ids === []) { return null; } $diagExists = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->count() > 0; if (!$diagExists) { return '诊单不存在'; } $supervisor = self::isOrderListSupervisor($adminId, $adminInfo); $assistantId = (int) Diagnosis::where('id', $diagnosisId)->value('assistant_id'); $isDiagAssistant = $assistantId === $adminId; $orders = Order::whereIn('id', $ids)->whereNull('delete_time')->select(); if (count($orders) !== count($ids)) { return '部分支付单不存在或已删除'; } $minTs = self::payOrderLinkableMinCreateTs(); foreach ($orders as $o) { if ((int) $o->patient_id !== $diagnosisId) { return '支付单与诊单不匹配'; } if ((int) $o->status !== 2) { return '仅能关联已支付订单'; } if (! $supervisor && ! $isDiagAssistant && (int) $o->creator_id !== $adminId) { return '无权限关联所选支付单'; } $ct = self::orderCreateTimeToUnix($o->create_time ?? null); if ($ct < $minTs) { $grace = false; if ($prescriptionOrderIdForDateGrace !== null && $prescriptionOrderIdForDateGrace > 0) { $grace = PrescriptionOrderPayOrder::where('prescription_order_id', $prescriptionOrderIdForDateGrace) ->where('pay_order_id', (int) $o->id) ->count() > 0; } if (! $grace) { return '仅可关联 2026年4月20日及之后创建的支付单'; } } } return null; } /** * 批量指派给医助:将「创建人」变更为目标医助(须为医助 role_id=2),用于列表筛选项为创建人=该医助 * * @param int[] $orderIds * @return array{success: int, per_log: list, fail: int, errors: list}|false */ public static function batchAssignAssistant(array $orderIds, int $assistantAdminId) { $assistantAdminId = (int) $assistantAdminId; if ($assistantAdminId <= 0) { self::setError('请选择医助'); return false; } if ( AdminRole::where('admin_id', $assistantAdminId) ->where('role_id', 2) ->count() === 0 ) { self::setError('目标账号不是医助角色'); return false; } $ids = array_values(array_unique(array_filter(array_map('intval', $orderIds), static fn (int $id) => $id > 0))); if ($ids === []) { self::setError('请选择订单'); return false; } if (count($ids) > 200) { self::setError('单次最多200单'); return false; } $targetName = (string) Admin::where('id', $assistantAdminId)->whereNull('delete_time')->value('name'); if ($targetName === '') { $targetName = (string) $assistantAdminId; } $perLog = []; $errors = []; foreach ($ids as $oid) { $order = Order::where('id', $oid)->whereNull('delete_time')->find(); if (! $order) { $errors[] = "订单{$oid}不存在或已删除"; continue; } $oldCreatorId = (int) $order->creator_id; if ($oldCreatorId === $assistantAdminId) { $errors[] = "订单{$oid}创建人已是该医助,已跳过"; continue; } $oldName = $oldCreatorId > 0 ? (string) Admin::where('id', $oldCreatorId)->whereNull('delete_time')->value('name') : ''; if ($oldName === '' && $oldCreatorId > 0) { $oldName = (string) $oldCreatorId; } elseif ($oldName === '') { $oldName = '—'; } $order->creator_id = $assistantAdminId; if (! $order->save()) { $errors[] = "订单{$oid}保存失败"; continue; } $perLog[] = [ 'order_id' => (int) $order->id, 'summary' => '创建人由「' . $oldName . '」变更为医助「' . $targetName . '」', ]; } if ($perLog === []) { self::setError($errors !== [] ? implode(';', $errors) : '未能变更任何订单'); return false; } return [ 'success' => count($perLog), 'per_log' => $perLog, 'fail' => count($errors), 'errors' => $errors, ]; } }