0; } /** 与 order 列表一致:超管或 order_edit_all_roles 可见全部业务订单 */ public static function canSeeAllPrescriptionOrders(array $adminInfo): bool { if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) { return true; } $roles = Config::get('project.order_edit_all_roles', [0, 3]); return self::roleIntersect($adminInfo, is_array($roles) ? $roles : []); } /** * @param PrescriptionOrder $row */ public static function canAccessOrder($row, int $adminId, array $adminInfo): bool { if (self::canSeeAllPrescriptionOrders($adminInfo)) { return true; } if ((int) $row->creator_id === $adminId) { return true; } $aid = (int) Diagnosis::where('id', (int) $row->diagnosis_id)->whereNull('delete_time')->value('assistant_id'); return $aid === $adminId; } /** * @param PrescriptionOrder $row */ public static function canWithdrawOrder($row, int $adminId, array $adminInfo): bool { return self::canAccessOrder($row, $adminId, $adminInfo); } public static function canViewInternalCost(array $adminInfo): bool { if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) { return true; } $allow = Config::get('project.prescription_order_finance_roles', [0, 3]); $allow = array_map('intval', is_array($allow) ? $allow : []); $myRoles = array_map('intval', $adminInfo['role_id'] ?? []); return count(array_intersect($myRoles, $allow)) > 0; } /** * 业务订单详情内是否返回/展示处方「药材明细」(菜单权限 tcm.prescriptionOrder/viewRxHerbs) */ private static function canViewPrescriptionHerbsInOrderDetail(array $adminInfo): bool { if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) { return true; } $perms = AuthLogic::getAuthByAdminId((int) ($adminInfo['admin_id'] ?? 0)); return in_array('tcm.prescriptionOrder/viewRxHerbs', $perms, true); } public static function canAuditPrescriptionOrder(array $adminInfo): bool { if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) { return true; } $allow = Config::get('project.prescription_audit_roles', [0, 3, 6]); return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []); } public static function canAuditPaymentSlipOrder(array $adminInfo): bool { if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) { return true; } $allow = Config::get('project.prescription_order_payment_audit_roles', [0, 3]); return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []); } /** * @param array $row */ public static function maskInternalCostIfNeeded(array &$row, array $adminInfo): void { if (!self::canViewInternalCost($adminInfo)) { unset($row['internal_cost']); } } /** 定金门槛(元),0 表示不校验 */ public static function depositMinAmount(): float { return round((float) Config::get('prescription_order.link_pay_min_amount', 0), 2); } /** * @param int[] $payOrderIds */ public static function assertDepositAndLinkPayRules(float $orderAmount, array $payOrderIds): ?string { $min = self::depositMinAmount(); if ($min <= 0) { return null; } if ($orderAmount < $min) { return '订单金额须大于等于定金门槛 ¥' . $min . '(当前 ¥' . $orderAmount . ')'; } if ($payOrderIds === []) { return '未关联支付单,关联支付单总金额须大于等于定金门槛 ¥' . $min; } // 检查关联支付单的总金额 $rows = Order::whereIn('id', $payOrderIds)->whereNull('delete_time')->column('amount', 'id'); $totalAmount = 0.0; foreach ($payOrderIds as $pid) { $pid = (int) $pid; if ($pid <= 0) { continue; } $amt = round((float) ($rows[$pid] ?? 0), 2); $totalAmount += $amt; } if ($totalAmount < $min) { return '关联支付单总金额须大于等于定金门槛 ¥' . $min . '(当前总金额 ¥' . $totalAmount . ')'; } return null; } public static function generateOrderNo(): string { return 'PO' . date('YmdHis') . mt_rand(100000, 999999); } private static function normalizeExpressCompany($raw): string { $ec = strtolower(trim((string) ($raw ?? ''))); if ($ec === '') { return 'auto'; } if (!in_array($ec, ['sf', 'jd', 'jt', 'jtexpress', 'auto'], true)) { return 'auto'; } // 统一 jtexpress 为 jt if ($ec === 'jtexpress') { return 'jt'; } return $ec; } /** 业务订单「处方审核」驳回后,允许医生继续编辑已通过的消费者处方 */ public static function allowDoctorEditApprovedPrescriptionDueToBusinessReject(int $prescriptionId): bool { return PrescriptionOrder::where('prescription_id', $prescriptionId) ->where('prescription_audit_status', 2) ->whereNull('delete_time') ->where('fulfillment_status', '<>', 4) ->count() > 0; } /** * 医生保存消费者处方后:重置关联业务订单的处方/支付单审核为待审(因处方内容已变) */ public static function onConsumerPrescriptionSaved(int $prescriptionId): void { $rows = PrescriptionOrder::where('prescription_id', $prescriptionId) ->where('prescription_audit_status', 2) ->whereNull('delete_time') ->where('fulfillment_status', '<>', 4) ->select(); foreach ($rows as $order) { $order->prescription_audit_status = 0; $order->prescription_audit_remark = ''; $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; self::syncFulfillmentStatus($order); $order->save(); } } private static function syncFulfillmentStatus(PrescriptionOrder $order): void { $fs = (int) $order->fulfillment_status; if ($fs === 3 || $fs === 4) { return; } $pa = (int) $order->prescription_audit_status; $pay = (int) $order->payment_slip_audit_status; if ($pa === 1 && $pay === 1) { $order->fulfillment_status = 2; } elseif ($pa === 2 || $pay === 2) { $order->fulfillment_status = 1; } } /** * @param mixed $raw * @return int[] */ private static function normalizePayOrderIds($raw): array { if (!is_array($raw)) { return []; } $ids = array_map('intval', $raw); return array_values(array_unique(array_filter($ids, static fn (int $v): bool => $v > 0))); } /** * 解除业务订单与支付单的关联(撤回/驳回后支付单可再次被选用) */ private static function clearPayOrderLinks(PrescriptionOrder $order): void { PrescriptionOrderPayOrder::where('prescription_order_id', (int) $order->id)->delete(); $order->linked_pay_order_id = null; } /** * 支付单不可同时关联多条「有效」业务订单(未删除且未撤回/取消);$exceptPrescriptionOrderId 为当前编辑单 ID 时跳过自检 * * @param int[] $payOrderIds */ private static function assertPayOrdersExclusive(array $payOrderIds, ?int $exceptPrescriptionOrderId): ?string { $ids = array_values(array_unique(array_filter(array_map('intval', $payOrderIds), static fn (int $v): bool => $v > 0))); if ($ids === []) { return null; } $activePoIds = PrescriptionOrder::whereNull('delete_time') ->where('fulfillment_status', '<>', 4) ->column('id'); if ($activePoIds === []) { return null; } $activeSet = array_fill_keys(array_map('intval', $activePoIds), true); $links = PrescriptionOrderPayOrder::whereIn('pay_order_id', $ids)->select(); foreach ($links as $link) { $poId = (int) $link->prescription_order_id; if ($exceptPrescriptionOrderId !== null && $poId === $exceptPrescriptionOrderId) { continue; } if (!isset($activeSet[$poId])) { continue; } return '支付单 #' . (int) $link->pay_order_id . ' 已关联其他有效业务订单,不可重复关联'; } return null; } /** * @param int[] $payOrderIds */ private static function replacePayOrderLinks(int $prescriptionOrderId, array $payOrderIds): void { PrescriptionOrderPayOrder::where('prescription_order_id', $prescriptionOrderId)->delete(); $t = time(); foreach ($payOrderIds as $pid) { $link = new PrescriptionOrderPayOrder(); $link->prescription_order_id = $prescriptionOrderId; $link->pay_order_id = $pid; $link->create_time = $t; $link->save(); } } /** * @return int[] */ private static function linkedPayOrderIdList(int $prescriptionOrderId): array { return array_map( 'intval', PrescriptionOrderPayOrder::where('prescription_order_id', $prescriptionOrderId)->order('id', 'asc')->column('pay_order_id') ); } /** * @return array> */ private static function linkedPayOrdersPayload(array $ids): array { if ($ids === []) { return []; } $rows = Order::whereIn('id', $ids)->whereNull('delete_time') ->field(['id', 'order_no', 'order_type', 'amount', 'status', 'create_time', 'creator_id', 'remark']) ->order('id', 'asc') ->select() ->toArray(); $creatorIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'creator_id'))))); $creatorNames = []; if ($creatorIds !== []) { $creatorNames = \app\common\model\auth\Admin::whereIn('id', $creatorIds)->column('name', 'id'); } $typeMap = [ 1 => '挂号费', 2 => '问诊费', 3 => '药品费用', 4 => '首付费用', 5 => '尾款费用', 6 => '其他费用', 7 => '全部费用', ]; $statusMap = [ 1 => '待支付', 2 => '已支付', 3 => '已取消', 4 => '已退款', 5 => '待审核', ]; foreach ($rows as &$r) { $ot = (int) ($r['order_type'] ?? 0); $st = (int) ($r['status'] ?? 0); $r['order_type_desc'] = $typeMap[$ot] ?? '—'; $r['status_desc'] = $statusMap[$st] ?? '—'; $cid = (int) ($r['creator_id'] ?? 0); $r['creator_name'] = $cid > 0 ? (string) ($creatorNames[$cid] ?? '') : ''; } unset($r); return $rows; } /** * @param array $arr */ private static function attachLinkedPayOrders(array &$arr): void { $id = (int) ($arr['id'] ?? 0); if ($id <= 0) { $arr['pay_order_ids'] = []; $arr['linked_pay_orders'] = []; $arr['linked_pay_paid_total'] = 0.0; $arr['deposit_min_amount'] = self::depositMinAmount(); return; } $ids = self::linkedPayOrderIdList($id); $arr['pay_order_ids'] = $ids; $arr['linked_pay_orders'] = self::linkedPayOrdersPayload($ids); $sum = 0.0; foreach ($arr['linked_pay_orders'] as $o) { $sum += (float) ($o['amount'] ?? 0); } $arr['linked_pay_paid_total'] = round($sum, 2); $arr['deposit_min_amount'] = self::depositMinAmount(); } /** * @param array $params * @return array|false */ public static function create(array $params, int $adminId, array $adminInfo) { self::$error = ''; $rxId = (int) $params['prescription_id']; $diagId = (int) $params['diagnosis_id']; $payOrderIds = self::normalizePayOrderIds($params['pay_order_ids'] ?? []); $rx = PrescriptionLogic::detail($rxId, $adminId, $adminInfo); if ($rx === null) { $err = PrescriptionLogic::getError(); self::$error = $err !== '' ? $err : '处方不存在'; return false; } if ((int) ($rx['diagnosis_id'] ?? 0) !== $diagId) { self::$error = '诊单与处方不匹配'; return false; } if (PrescriptionOrder::where('prescription_id', $rxId)->whereNull('delete_time')->where('fulfillment_status', '<>', 4)->count() > 0) { self::$error = '该处方已存在有效业务订单(未撤回前不可重复创建)'; return false; } $diag = Diagnosis::where('id', $diagId)->whereNull('delete_time')->find(); if (!$diag) { self::$error = '诊单不存在'; return false; } if ($payOrderIds !== []) { $linkErr = OrderLogic::assertPayOrdersLinkable($payOrderIds, $diagId, $adminId, $adminInfo); if ($linkErr !== null) { self::$error = $linkErr; return false; } $exErr = self::assertPayOrdersExclusive($payOrderIds, null); if ($exErr !== null) { self::$error = $exErr; return false; } } $orderAmtPreview = round((float) ($params['amount'] ?? 0), 2); $depErrCreate = self::assertDepositAndLinkPayRules($orderAmtPreview, $payOrderIds); if ($depErrCreate !== null) { self::$error = $depErrCreate; return false; } $internalCost = null; if (isset($params['internal_cost']) && $params['internal_cost'] !== '' && $params['internal_cost'] !== null) { if (self::canViewInternalCost($adminInfo)) { $internalCost = round((float) $params['internal_cost'], 2); } } $medDays = $params['medication_days'] ?? null; $medDays = $medDays === '' || $medDays === null ? null : (int) $medDays; $order = new PrescriptionOrder(); $order->order_no = self::generateOrderNo(); $order->prescription_id = $rxId; $order->diagnosis_id = $diagId; $order->creator_id = $adminId; $order->recipient_name = (string) $params['recipient_name']; $order->recipient_phone = (string) $params['recipient_phone']; $order->shipping_address = (string) $params['shipping_address']; $order->is_follow_up = (int) ($params['is_follow_up'] ?? 0) === 1 ? 1 : 0; $order->medication_days = $medDays > 0 ? $medDays : null; $order->prev_staff = (string) ($params['prev_staff'] ?? ''); $order->service_channel = (string) ($params['service_channel'] ?? ''); $order->service_package = (string) ($params['service_package'] ?? ''); $order->tracking_number = (string) ($params['tracking_number'] ?? ''); $order->express_company = self::normalizeExpressCompany($params['express_company'] ?? 'auto'); $order->fee_type = (int) $params['fee_type']; $order->amount = round((float) $params['amount'], 2); $order->internal_cost = $internalCost; $order->remark_extra = (string) ($params['remark_extra'] ?? ''); $order->linked_pay_order_id = $payOrderIds !== [] ? $payOrderIds[0] : null; $order->prescription_audit_status = 0; $order->prescription_audit_remark = ''; $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; $order->fulfillment_status = 1; try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } self::writeLog((int) $order->id, $adminId, $adminInfo, 'create', '创建业务订单'); if ($payOrderIds !== []) { self::replacePayOrderLinks((int) $order->id, $payOrderIds); } $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } public static function detail(int $id, int $adminId, array $adminInfo): ?array { self::$error = ''; $row = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$row) { self::$error = '订单不存在'; return null; } if (!self::canAccessOrder($row, $adminId, $adminInfo)) { self::$error = '无权限查看'; return null; } $arr = $row->toArray(); self::maskInternalCostIfNeeded($arr, $adminInfo); self::attachLinkedPayOrders($arr); // 添加创建人姓名 $creatorId = (int) ($arr['creator_id'] ?? 0); if ($creatorId > 0) { $creatorName = Admin::where('id', $creatorId)->value('name'); $arr['creator_name'] = $creatorName ? (string) $creatorName : ''; } else { $arr['creator_name'] = ''; } $rxId = (int) ($arr['prescription_id'] ?? 0); $arr['prescription'] = null; $arr['prescription_detail_error'] = ''; $arr['doctor_name'] = ''; if ($rxId > 0) { $rxDetail = PrescriptionLogic::detail($rxId, $adminId, $adminInfo); if ($rxDetail !== null) { $arr['prescription'] = $rxDetail; // 添加开方人姓名(优先使用处方的 doctor_name,否则从 creator_id 获取) $doctorName = (string) ($rxDetail['doctor_name'] ?? ''); if ($doctorName === '') { $rxCreatorId = (int) ($rxDetail['creator_id'] ?? 0); if ($rxCreatorId > 0) { $doctorName = (string) (Admin::where('id', $rxCreatorId)->value('name') ?? ''); } } $arr['doctor_name'] = $doctorName; } else { $err = PrescriptionLogic::getError(); $arr['prescription_detail_error'] = $err !== '' ? $err : '处方不存在'; } } $canHerbs = self::canViewPrescriptionHerbsInOrderDetail($adminInfo); $arr['prescription_detail_herbs_visible'] = $canHerbs; if (! $canHerbs && isset($arr['prescription']) && is_array($arr['prescription'])) { unset($arr['prescription']['herbs']); } return $arr; } /** * 物流轨迹(顺丰/京东等,优先快递100;未配置密钥时返回官网链接) * * @return array|null */ public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo): ?array { self::$error = ''; $row = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$row) { self::$error = '订单不存在'; return null; } if (!self::canAccessOrder($row, $adminId, $adminInfo)) { self::$error = '无权限查看'; return null; } $num = trim((string) ($row->tracking_number ?? '')); if ($num === '') { self::$error = '未填写快递单号'; return null; } $ec = trim($expressCompanyOverride) !== '' ? self::normalizeExpressCompany($expressCompanyOverride) : self::normalizeExpressCompany($row->express_company ?? 'auto'); // 优先从数据库查询物流追踪信息 $trackingData = \app\common\service\ExpressTrackingService::getDetailByTrackingNumber($num); if ($trackingData && !empty($trackingData['traces'])) { // 从数据库获取到数据,直接返回 $payload = [ 'carrier' => $trackingData['express_company'], 'carrier_label' => $trackingData['express_company_name'], 'kuaidi_com' => $trackingData['express_company'], 'traces' => array_map(function($trace) { return [ 'time' => $trace['trace_time'], 'ftime' => $trace['trace_time'], 'context' => $trace['trace_context'], 'location' => $trace['location'], 'status' => $trace['status'], 'statusCode' => $trace['status_code'], ]; }, $trackingData['traces']), 'state' => $trackingData['current_state'], 'state_text' => $trackingData['current_state_text'], 'source' => 'database', 'hint' => '', 'official_url' => '', 'last_query_time' => date('Y-m-d H:i:s', $trackingData['last_query_time']), 'query_count' => $trackingData['query_count'], ]; } else { // 数据库没有数据,调用快递100 API $payload = ExpressTrackService::query($ec, $num, (string) ($row->recipient_phone ?? '')); $payload['source'] = 'api'; } $payload['official_urls'] = ExpressTrackService::officialUrls($num); $payload['tracking_number'] = $num; $payload['express_company_used'] = $ec; return $payload; } /** * @param array $params * @return array|false */ public static function edit(array $params, int $adminId, array $adminInfo) { self::$error = ''; $id = (int) $params['id']; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAccessOrder($order, $adminId, $adminInfo)) { self::$error = '无权限编辑'; return false; } $fs = (int) $order->fulfillment_status; if ($fs === 3 || $fs === 4) { self::$error = '已完成或已取消的订单不可编辑'; return false; } $medDays = $params['medication_days'] ?? null; $medDays = $medDays === '' || $medDays === null ? null : (int) $medDays; $order->recipient_name = (string) $params['recipient_name']; $order->recipient_phone = (string) $params['recipient_phone']; $order->shipping_address = (string) $params['shipping_address']; $order->is_follow_up = (int) ($params['is_follow_up'] ?? 0) === 1 ? 1 : 0; $order->medication_days = $medDays > 0 ? $medDays : null; $order->prev_staff = (string) ($params['prev_staff'] ?? ''); $order->service_channel = (string) ($params['service_channel'] ?? ''); $order->service_package = (string) ($params['service_package'] ?? ''); $order->tracking_number = (string) ($params['tracking_number'] ?? ''); if (array_key_exists('express_company', $params)) { $order->express_company = self::normalizeExpressCompany($params['express_company']); } $order->fee_type = (int) $params['fee_type']; $order->amount = round((float) $params['amount'], 2); $order->remark_extra = (string) ($params['remark_extra'] ?? ''); $diagId = (int) $order->diagnosis_id; if (array_key_exists('pay_order_ids', $params)) { $payOrderIds = self::normalizePayOrderIds($params['pay_order_ids']); // 过滤掉已删除的支付单ID(拒绝审核时可能软删除了待审核支付单) if (!empty($payOrderIds)) { $existingIds = Order::whereIn('id', $payOrderIds) ->whereNull('delete_time') ->column('id'); $payOrderIds = array_values(array_intersect($payOrderIds, $existingIds)); } $linkErr = OrderLogic::assertPayOrdersLinkable($payOrderIds, $diagId, $adminId, $adminInfo); if ($linkErr !== null) { self::$error = $linkErr; return false; } $exErr = self::assertPayOrdersExclusive($payOrderIds, (int) $order->id); if ($exErr !== null) { self::$error = $exErr; return false; } self::replacePayOrderLinks((int) $order->id, $payOrderIds); $order->linked_pay_order_id = $payOrderIds !== [] ? $payOrderIds[0] : null; } elseif (array_key_exists('linked_pay_order_id', $params)) { $lp = $params['linked_pay_order_id']; if ($lp === '' || $lp === null || (int) $lp <= 0) { self::replacePayOrderLinks((int) $order->id, []); $order->linked_pay_order_id = null; } else { $single = [(int) $lp]; $linkErr = OrderLogic::assertPayOrdersLinkable($single, $diagId, $adminId, $adminInfo); if ($linkErr !== null) { self::$error = $linkErr; return false; } $exErr = self::assertPayOrdersExclusive($single, (int) $order->id); if ($exErr !== null) { self::$error = $exErr; return false; } self::replacePayOrderLinks((int) $order->id, $single); $order->linked_pay_order_id = $single[0]; } } if (array_key_exists('internal_cost', $params) && self::canViewInternalCost($adminInfo)) { $raw = $params['internal_cost']; if ($raw === '' || $raw === null) { $order->internal_cost = null; } else { $order->internal_cost = round((float) $raw, 2); } } $finalPayIds = self::linkedPayOrderIdList((int) $order->id); $depErr = self::assertDepositAndLinkPayRules(round((float) $order->amount, 2), $finalPayIds); if ($depErr !== null) { self::$error = $depErr; return false; } // 编辑后重置支付审核状态为待审核(需要重新审核) // 但如果当前状态是"已发货"(5),则不重置审核状态 if ((int) $order->fulfillment_status !== 5) { $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; } self::syncFulfillmentStatus($order); try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } self::writeLog((int) $order->id, $adminId, $adminInfo, 'edit', '编辑了业务订单及相关信息'); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * 确认发货:更新快递信息并将 fulfillment_status 从 2(履约中)推进到 5(已发货) * * @return array|false */ public static function ship(int $id, string $expressCompany, string $trackingNumber, int $adminId, array $adminInfo) { self::$error = ''; $trackingNumber = trim($trackingNumber); if ($trackingNumber === '') { self::$error = '快递单号不能为空'; return false; } $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAccessOrder($order, $adminId, $adminInfo)) { self::$error = '无权限操作'; return false; } if ((int) $order->fulfillment_status !== 2 && (int) $order->fulfillment_status !== 5) { self::$error = '仅「履约中」或「已发货」状态的订单可更新快递信息'; return false; } $order->express_company = self::normalizeExpressCompany($expressCompany); $order->tracking_number = $trackingNumber; // 仅履约中(2)时才推进到已发货(5),已发货(5)则只更新快递信息保持状态 $isFirstShip = false; if ((int) $order->fulfillment_status === 2) { $order->fulfillment_status = 5; $isFirstShip = true; } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } if ($isFirstShip) { self::writeLog((int) $order->id, $adminId, $adminInfo, 'ship', '确认发货,运单:' . $trackingNumber . '(' . $expressCompany . ')'); } else { self::writeLog((int) $order->id, $adminId, $adminInfo, 'fill_tracking', '更新发货信息,运单:' . $trackingNumber . '(' . $expressCompany . ')'); } $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * @return array|false */ public static function withdraw(int $id, int $adminId, array $adminInfo) { self::$error = ''; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canWithdrawOrder($order, $adminId, $adminInfo)) { self::$error = '无权限撤回'; return false; } if ((int) $order->fulfillment_status !== 1) { self::$error = '仅「待双审通过」状态可撤回;双审通过后请走履约/完成流程'; return false; } $order->fulfillment_status = 4; self::clearPayOrderLinks($order); try { $order->save(); // 撤回订单后,将关联的处方审核状态改回"待审核" $prescriptionId = (int) $order->prescription_id; if ($prescriptionId > 0) { Prescription::where('id', $prescriptionId) ->whereNull('delete_time') ->update([ 'audit_status' => 0, // 0=待审核 'audit_time' => null, 'audit_by' => null, 'audit_by_name' => '', 'audit_remark' => '', 'update_time' => time(), ]); } } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } self::writeLog((int) $order->id, $adminId, $adminInfo, 'withdraw', '撤回订单,状态变更为「已取消」'); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * @return array|false */ public static function auditPrescription(int $id, string $action, string $remark, int $adminId, array $adminInfo) { self::$error = ''; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAuditPrescriptionOrder($adminInfo)) { self::$error = '无处方审核权限'; return false; } if ((int) $order->prescription_audit_status !== 0) { self::$error = '当前无需处方审核'; return false; } $fs = (int) $order->fulfillment_status; if ($fs === 3 || $fs === 4) { self::$error = '订单已结束'; return false; } if ($action === 'reject' && trim($remark) === '') { self::$error = '驳回时请填写审核意见'; return false; } if ($action === 'approve') { $order->prescription_audit_status = 1; $order->prescription_audit_remark = mb_substr(trim($remark), 0, 500); } else { $order->prescription_audit_status = 2; $order->prescription_audit_remark = mb_substr(trim($remark), 0, 500); $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; } self::syncFulfillmentStatus($order); if ($action === 'reject') { self::clearPayOrderLinks($order); } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } if ($action === 'approve') { $syncRemark = trim($remark) !== '' ? trim($remark) : '业务订单处方审核通过'; PrescriptionLogic::syncApproveConsumerPrescriptionIfPending( (int) $order->prescription_id, $adminId, $adminInfo, $syncRemark ); } $actionName = $action === 'approve' ? 'audit_rx_approve' : 'audit_rx_reject'; $summary = $action === 'approve' ? '处方审核通过' . ($remark ? ',意见:' . $remark : '') : '处方审核驳回,意见:' . $remark; self::writeLog((int) $order->id, $adminId, $adminInfo, $actionName, $summary); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * @return array|false */ public static function auditPaymentSlip(int $id, string $action, string $remark, int $adminId, array $adminInfo) { self::$error = ''; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAuditPaymentSlipOrder($adminInfo)) { self::$error = '无支付单审核权限'; return false; } if ((int) $order->prescription_audit_status !== 1) { self::$error = '请先通过处方审核'; return false; } if ((int) $order->payment_slip_audit_status !== 0) { self::$error = '当前无需支付单审核'; return false; } $fs = (int) $order->fulfillment_status; if ($fs === 3 || $fs === 4) { self::$error = '订单已结束'; return false; } // 已发货状态(5)允许重新发起支付单审核(新增支付单场景) if ($action === 'reject' && trim($remark) === '') { self::$error = '驳回时请填写审核意见'; return false; } if ($action === 'approve') { $order->payment_slip_audit_status = 1; $order->payment_slip_audit_remark = mb_substr(trim($remark), 0, 500); // 将关联的待审核支付单(status=5)更新为已支付(status=2) $payOrderIds = self::linkedPayOrderIdList($id); if (!empty($payOrderIds)) { Order::whereIn('id', $payOrderIds) ->where('status', 5) // 只更新待审核状态的 ->update([ 'status' => 2, // 已支付 'payment_time' => date('Y-m-d H:i:s') ]); } // 如果有完单申请,自动完成订单 if ((int) $order->completion_request === 1 && (int) $order->fulfillment_status === 5) { $order->fulfillment_status = 3; // 已完成 $order->completion_request = 0; // 清除完单申请标记 self::writeLog((int) $order->id, $adminId, $adminInfo, 'auto_complete', '支付审核通过,根据完单申请自动完成订单'); } } else { $order->payment_slip_audit_status = 2; $order->payment_slip_audit_remark = mb_substr(trim($remark), 0, 500); // 拒绝审核时,软删除待审核状态的关联支付单(status=5) $payOrderIds = self::linkedPayOrderIdList($id); if (!empty($payOrderIds)) { Order::whereIn('id', $payOrderIds) ->where('status', 5) // 只删除待审核状态的 ->update([ 'delete_time' => date('Y-m-d H:i:s') ]); } // 拒绝审核时,清除完单申请 if ((int) $order->completion_request === 1) { $order->completion_request = 0; $order->completion_request_time = 0; $order->completion_request_by = 0; $order->completion_request_by_name = ''; } } // 状态同步和关联清除逻辑: // - 如果是"已发货"状态(5),无论同意还是拒绝,都保持状态不变 // - 如果不是"已发货"状态,同步状态;拒绝时清除所有支付单关联关系 $currentStatus = (int) $order->fulfillment_status; if ($currentStatus === 5) { // 已发货状态:保持已发货状态不变 // 不调用 syncFulfillmentStatus // 拒绝时也不清除关联(只删除了待审核支付单) } else { self::syncFulfillmentStatus($order); if ($action === 'reject') { self::clearPayOrderLinks($order); } } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } $actionName = $action === 'approve' ? 'audit_pay_approve' : 'audit_pay_reject'; $summary = $action === 'approve' ? '支付单审核通过' . ($remark ? ',意见:' . $remark : '') : '支付单审核驳回,意见:' . $remark; self::writeLog((int) $order->id, $adminId, $adminInfo, $actionName, $summary); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * 撤回处方审核:将已通过/已驳回的处方审核重置为待审核 * * @return array|false */ public static function revokeRxAudit(int $id, int $adminId, array $adminInfo) { self::$error = ''; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAuditPrescriptionOrder($adminInfo)) { self::$error = '无处方审核权限'; return false; } $rxStatus = (int) $order->prescription_audit_status; if ($rxStatus === 0) { self::$error = '当前已是待审核状态,无需撤回'; return false; } $fs = (int) $order->fulfillment_status; if ($fs === 3 || $fs === 4) { self::$error = '订单已结束,不可撤回审核'; return false; } // 如果处方审核已通过,且支付审核也已通过或已驳回,不允许撤回处方审核 if ($rxStatus === 1 && (int) $order->payment_slip_audit_status !== 0) { self::$error = '支付单审核已进行,请先撤回支付单审核'; return false; } $order->prescription_audit_status = 0; $order->prescription_audit_remark = ''; // 如果支付审核不是待审核,也一并重置 if ((int) $order->payment_slip_audit_status !== 0) { $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; } self::syncFulfillmentStatus($order); try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } self::writeLog((int) $order->id, $adminId, $adminInfo, 'revoke_rx_audit', '撤回处方审核,状态重置为待审核'); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * 撤回支付单审核:将已通过/已驳回的支付单审核重置为待审核 * * @return array|false */ public static function revokePayAudit(int $id, int $adminId, array $adminInfo) { self::$error = ''; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAuditPaymentSlipOrder($adminInfo)) { self::$error = '无支付单审核权限'; return false; } if ((int) $order->prescription_audit_status !== 1) { self::$error = '处方审核未通过,无法撤回支付单审核'; return false; } $payStatus = (int) $order->payment_slip_audit_status; if ($payStatus === 0) { self::$error = '当前已是待审核状态,无需撤回'; return false; } $fs = (int) $order->fulfillment_status; if ($fs === 3 || $fs === 4) { self::$error = '订单已结束,不可撤回审核'; return false; } $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; self::syncFulfillmentStatus($order); // 将关联的已支付支付单(status=2,且payment_method='manual')恢复为待审核(status=5) $payOrderIds = self::linkedPayOrderIdList($id); if (!empty($payOrderIds)) { Order::whereIn('id', $payOrderIds) ->where('status', 2) // 只恢复已支付状态的 ->where('payment_method', 'manual') // 只恢复手动创建的 ->update([ 'status' => 5, // 待审核 'payment_time' => null ]); } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } self::writeLog((int) $order->id, $adminId, $adminInfo, 'revoke_pay_audit', '撤回支付单审核,状态重置为待审核'); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * @return array> */ public static function getLogs(int $orderId, int $adminId, array $adminInfo): array { self::$error = ''; $order = PrescriptionOrder::where('id', $orderId)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return []; } if (!self::canAccessOrder($order, $adminId, $adminInfo)) { self::$error = '无权限查看'; return []; } return PrescriptionOrderLog::where('prescription_order_id', $orderId) ->order('id', 'desc') ->select() ->toArray(); } /** * 为「已发货」(fulfillment_status=5) 的业务订单新增一条关联支付单(zyt_order), * 创建后将支付单链接到业务订单,并将处方/支付审核状态重置为待审核以启动再次审核流程。 * * @param array $params id, order_type, amount, remark * @return array|false */ public static function addPayOrder(array $params, int $adminId, array $adminInfo) { self::$error = ''; $id = (int) $params['id']; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAccessOrder($order, $adminId, $adminInfo)) { self::$error = '无权限操作'; return false; } if ((int) $order->fulfillment_status !== 5) { self::$error = '仅「已发货」状态的订单可新增支付单'; return false; } $orderType = (int) ($params['order_type'] ?? 3); $amount = round((float) ($params['pay_amount'] ?? 0), 2); $remark = mb_substr(trim((string) ($params['pay_remark'] ?? '')), 0, 200); $completionRequest = (int) ($params['completion_request'] ?? 0); if ($amount <= 0) { self::$error = '支付单金额须大于 0'; return false; } // 创建 zyt_order 并标记为待审核(需要审核后才算已支付) $payOrder = new Order(); $payOrder->order_no = OrderLogic::generateOrderNo(); $payOrder->patient_id = (int) $order->diagnosis_id; $payOrder->creator_id = $adminId; $payOrder->order_type = $orderType; $payOrder->amount = $amount; $payOrder->status = 5; // 待审核 $payOrder->payment_method = 'manual'; $payOrder->payment_time = null; // 审核通过后再设置支付时间 $payOrder->remark = $remark; try { $payOrder->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } // 将新支付单追加到业务订单关联列表 $existingIds = self::linkedPayOrderIdList($id); $newIds = array_unique(array_merge($existingIds, [(int) $payOrder->id])); self::replacePayOrderLinks($id, $newIds); $order->linked_pay_order_id = $newIds[0]; // 重置支付单审核为待审核,以启动新一轮审核(处方审核保持通过状态不变) $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; // 处理完单申请 if ($completionRequest === 1) { $order->completion_request = 1; $order->completion_request_time = time(); $order->completion_request_by = $adminId; $order->completion_request_by_name = (string) ($adminInfo['name'] ?? ''); } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } $logMsg = '新增支付单 #' . $payOrder->id . '(¥' . $amount . '),类型:' . $orderType . ',等待支付审核'; if ($completionRequest === 1) { $logMsg .= ',并申请完成订单'; } self::writeLog($id, $adminId, $adminInfo, 'add_pay_order', $logMsg); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * 为「已发货」(fulfillment_status=5) 的业务订单关联已有支付单, * 关联后将支付审核状态重置为待审核以启动再次审核流程。 * * @param array $params id, pay_order_id * @return array|false */ public static function linkPayOrder(array $params, int $adminId, array $adminInfo) { self::$error = ''; $id = (int) $params['id']; $completionRequest = (int) ($params['completion_request'] ?? 0); // 支持单个或多个支付单ID $payOrderIds = []; if (isset($params['pay_order_ids']) && is_array($params['pay_order_ids'])) { // 多个支付单(数组) $payOrderIds = array_values(array_unique(array_filter(array_map('intval', $params['pay_order_ids'])))); } elseif (isset($params['pay_order_id'])) { // 单个支付单(兼容旧接口) $payOrderIds = [(int) $params['pay_order_id']]; } if (empty($payOrderIds)) { self::$error = '请选择要关联的支付单'; return false; } $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAccessOrder($order, $adminId, $adminInfo)) { self::$error = '无权限操作'; return false; } if ((int) $order->fulfillment_status !== 5) { self::$error = '仅「已发货」状态的订单可关联支付单'; return false; } $diagId = (int) $order->diagnosis_id; // 验证所有支付单是否可关联 $linkErr = OrderLogic::assertPayOrdersLinkable($payOrderIds, $diagId, $adminId, $adminInfo); if ($linkErr !== null) { self::$error = $linkErr; return false; } // 验证所有支付单是否已被其他业务订单占用 $exErr = self::assertPayOrdersExclusive($payOrderIds, $id); if ($exErr !== null) { self::$error = $exErr; return false; } // 将支付单追加到业务订单关联列表 $existingIds = self::linkedPayOrderIdList($id); $newIds = array_unique(array_merge($existingIds, $payOrderIds)); self::replacePayOrderLinks($id, $newIds); $order->linked_pay_order_id = $newIds[0]; // 重置支付单审核为待审核,以启动新一轮审核(处方审核保持通过状态不变) $order->payment_slip_audit_status = 0; $order->payment_slip_audit_remark = ''; // 处理完单申请 if ($completionRequest === 1) { $order->completion_request = 1; $order->completion_request_time = time(); $order->completion_request_by = $adminId; $order->completion_request_by_name = (string) ($adminInfo['name'] ?? ''); } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } // 计算总金额 $totalAmount = 0; $payOrders = Order::whereIn('id', $payOrderIds)->select(); foreach ($payOrders as $po) { $totalAmount += round((float) $po->amount, 2); } $logMsg = '关联支付单 ' . count($payOrderIds) . ' 笔(共¥' . $totalAmount . '),等待支付审核'; if ($completionRequest === 1) { $logMsg .= ',并申请完成订单'; } self::writeLog($id, $adminId, $adminInfo, 'link_pay_order', $logMsg); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * 将「已发货」(fulfillment_status=5) 且支付审核已通过的订单标记为「已完成」(3)。 * * @return array|false */ public static function complete(int $id, int $adminId, array $adminInfo) { self::$error = ''; $order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find(); if (!$order) { self::$error = '订单不存在'; return false; } if (!self::canAccessOrder($order, $adminId, $adminInfo)) { self::$error = '无权限操作'; return false; } if ((int) $order->fulfillment_status !== 5) { self::$error = '仅「已发货」状态可标记为已完成'; return false; } if ((int) $order->payment_slip_audit_status !== 1) { self::$error = '支付单审核尚未通过,不可完成订单'; return false; } $order->fulfillment_status = 3; try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } self::writeLog($id, $adminId, $adminInfo, 'complete', '订单完成,状态变更为「已完成」'); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } private static function writeLog(int $orderId, int $adminId, array $adminInfo, string $action, string $summary): void { $adminName = $adminInfo['name'] ?? ''; if ($adminName === '' && $adminId > 0) { $adminName = (string) \app\common\model\auth\Admin::where('id', $adminId)->value('name'); } $log = new PrescriptionOrderLog(); $log->prescription_order_id = $orderId; $log->admin_id = $adminId; $log->admin_name = mb_substr($adminName, 0, 64); $log->action = mb_substr($action, 0, 32); $log->summary = mb_substr($summary, 0, 500); $log->create_time = time(); try { $log->save(); } catch (\Throwable $e) { // 忽略日志写入错误 } } }