where('id', $id)->field(['id', 'name', 'pid'])->find(); if (empty($row)) { break; } $r = is_array($row) ? $row : $row->toArray(); array_unshift($names, (string) ($r['name'] ?? '')); $pid = (int) ($r['pid'] ?? 0); if ($pid <= 0) { break; } $id = $pid; } if ($names === []) { return ''; } $names = array_filter($names, static fn (string $n): bool => $n !== ''); return implode(' / ', $names); } /** * 管理员在 zyt_admin_dept 中的部门路径(多部门用「;」、含父级「 / 」) */ private static function buildAdminDeptPathJoined(int $adminId): string { if ($adminId <= 0) { return ''; } $deptIds = Db::name('admin_dept')->where('admin_id', $adminId)->column('dept_id'); $pathSegmentsC = []; foreach ($deptIds as $didC) { $pc = self::buildDeptPath((int) $didC); if ($pc !== '') { $pathSegmentsC[] = $pc; } } $pathSegmentsC = array_values(array_unique($pathSegmentsC)); return $pathSegmentsC === [] ? '' : implode(';', $pathSegmentsC); } /** * @param int[] $allowRoleIds */ private static function roleIntersect(array $adminInfo, array $allowRoleIds): bool { $myRoles = array_map('intval', $adminInfo['role_id'] ?? []); $allow = array_map('intval', $allowRoleIds); return count(array_intersect($myRoles, $allow)) > 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 : []); } /** * 菜单权限:非全量角色可额外查看「关联处方开方人为本账号」的业务订单(医助代建单) * * perms: tcm.prescriptionOrder/viewOrdersForOwnPrescription */ public static function canViewOrdersForOwnPrescription(array $adminInfo): bool { $perms = AuthLogic::getAuthByAdminId((int) ($adminInfo['admin_id'] ?? 0)); return in_array('tcm.prescriptionOrder/viewOrdersForOwnPrescription', $perms, true); } /** * 业务订单关联处方的开方人是否为指定管理员(处方 creator_id) */ public static function isPrescriptionOrderPrescriber(int $prescriptionId, int $adminId): bool { if ($prescriptionId <= 0 || $adminId <= 0) { return false; } $cid = (int) Prescription::where('id', $prescriptionId)->whereNull('delete_time')->value('creator_id'); return $cid === $adminId; } /** * @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; } if (self::canViewOrdersForOwnPrescription($adminInfo)) { return self::isPrescriptionOrderPrescriber((int) $row->prescription_id, $adminId); } return false; } /** * 撤回:仅创建人或全量角色(避免开方医生仅「查看医助代建单」权限误撤他人单子) * * @param PrescriptionOrder $row */ public static function canWithdrawOrder($row, int $adminId, array $adminInfo): bool { if (self::canSeeAllPrescriptionOrders($adminInfo)) { return true; } return (int) $row->creator_id === $adminId; } 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 : []); } /** * 列表顶栏金额统计:与「关联支付单」审核白名单同档的角色可见「全量」统计(不缩小到本人/医助诊单域) */ public static function canViewOrderListStatsAllScope(array $adminInfo): bool { if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) { return true; } $allow = Config::get('project.prescription_order_payment_audit_roles', [0, 3, 4, 9, 6]); if (!is_array($allow)) { $allow = [0, 3]; } return self::roleIntersect($adminInfo, $allow); } /** * @param array $row */ public static function maskInternalCostIfNeeded(array &$row, array $adminInfo): void { if (!self::canViewInternalCost($adminInfo)) { unset($row['internal_cost']); } } /** * 菜单权限:查看/编辑业务订单「备注」(对内 remark_extra) * * perms: tcm.prescriptionOrder/editRemarkExtra */ public static function canEditOrderRemarkExtra(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/editRemarkExtra', $perms, true); } /** * @param array $row */ public static function maskRemarkExtraIfNeeded(array &$row, array $adminInfo): void { if (!self::canEditOrderRemarkExtra($adminInfo)) { unset($row['remark_extra']); } } /** 定金门槛(元),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 => '全部费用', 8 => '驼奶费用', ]; $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 ((int) ($rx['void_status'] ?? 0) === 1) { 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']; // 处理省市区字段 if (isset($params['shipping_province'])) { $order->shipping_province = (string) $params['shipping_province']; } if (isset($params['shipping_city'])) { $order->shipping_city = (string) $params['shipping_city']; } if (isset($params['shipping_district'])) { $order->shipping_district = (string) $params['shipping_district']; } $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 = self::canEditOrderRemarkExtra($adminInfo) ? (string) ($params['remark_extra'] ?? '') : ''; $order->remark_assistant = (string) ($params['remark_assistant'] ?? ''); $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::maskRemarkExtraIfNeeded($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::maskRemarkExtraIfNeeded($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']); } // 诊单医助 / 诊单创建人姓名(tcm_diagnosis.admin_id);部门见下方:优先业务订单 creator_id $arr['assistant_id'] = 0; $arr['assistant_name'] = ''; $arr['assistant_dept_path'] = ''; $arr['diagnosis_creator_id'] = 0; $arr['diagnosis_creator_name'] = ''; $arr['diagnosis_creator_dept_path'] = ''; $diagIdForMeta = (int) ($arr['diagnosis_id'] ?? 0); if ($diagIdForMeta > 0) { // 勿在 field 中写 admin_id:未执行建表补充列时会报 Unknown column $dg = Diagnosis::where('id', $diagIdForMeta)->whereNull('delete_time')->find(); if ($dg !== null) { $drow = $dg->toArray(); $astId = (int) ($drow['assistant_id'] ?? 0); $arr['assistant_id'] = $astId; if ($astId > 0) { $an = Admin::where('id', $astId)->whereNull('delete_time')->value('name'); $arr['assistant_name'] = $an !== null && (string) $an !== '' ? (string) $an : ''; $deptIds = Db::name('admin_dept')->where('admin_id', $astId)->column('dept_id'); $pathSegments = []; foreach ($deptIds as $did) { $p = self::buildDeptPath((int) $did); if ($p !== '') { $pathSegments[] = $p; } } $pathSegments = array_values(array_unique($pathSegments)); $arr['assistant_dept_path'] = $pathSegments === [] ? '' : implode(';', $pathSegments); } $creId = (int) ($drow['admin_id'] ?? 0); $arr['diagnosis_creator_id'] = $creId; if ($creId > 0) { $cn = Admin::where('id', $creId)->whereNull('delete_time')->value('name'); $arr['diagnosis_creator_name'] = $cn !== null && (string) $cn !== '' ? (string) $cn : ''; } } } // 「创建人所属部门」:优先 zyt_tcm_prescription_order.creator_id;无部门时再试诊单 admin_id(非处方开方人) $deptPath = self::buildAdminDeptPathJoined((int) ($arr['creator_id'] ?? 0)); if ($deptPath === '') { $dgAdmin = (int) ($arr['diagnosis_creator_id'] ?? 0); if ($dgAdmin > 0) { $deptPath = self::buildAdminDeptPathJoined($dgAdmin); } } $arr['diagnosis_creator_dept_path'] = $deptPath; // 挂号预约摘要:优先处方 appointment_id;否则按诊单 id 匹配预约表 patient_id(诊单) $arr['linked_appointment'] = null; $rxApptId = 0; if (isset($arr['prescription']) && is_array($arr['prescription'])) { $rxApptId = (int) ($arr['prescription']['appointment_id'] ?? 0); } $resolvedApptId = $rxApptId; if ($resolvedApptId <= 0 && $diagIdForMeta > 0) { $apFallback = Appointment::where('patient_id', $diagIdForMeta)->order('id', 'desc')->find(); if ($apFallback !== null) { $resolvedApptId = (int) $apFallback->id; } } if ($resolvedApptId > 0) { $brief = self::buildAppointmentBrief($resolvedApptId); if ($brief !== null) { $brief['resolved_from'] = $rxApptId === $resolvedApptId ? 'prescription' : 'diagnosis'; $arr['linked_appointment'] = $brief; } } return $arr; } /** * 业务订单详情场景:仅更新关联处方的患者姓名与手机号(不重置消费者处方审核、不改变其它处方字段) */ public static function patchPrescriptionPatient( int $prescriptionOrderId, string $patientName, string $phone, int $adminId, array $adminInfo ): bool { self::$error = ''; $order = PrescriptionOrder::where('id', $prescriptionOrderId)->whereNull('delete_time')->find(); if (!$order) { self::setError('订单不存在'); return false; } if (!self::canAccessOrder($order, $adminId, $adminInfo)) { self::setError('无权限操作'); return false; } $rxId = (int) ($order->prescription_id ?? 0); if ($rxId <= 0) { self::setError('该订单未关联处方'); return false; } $rx = Prescription::where('id', $rxId)->whereNull('delete_time')->find(); if (!$rx) { self::setError('处方不存在'); return false; } if (!PrescriptionLogic::canViewPrescription($rx, $adminId, $adminInfo)) { self::setError('无权限修改此处方'); return false; } $patientName = trim($patientName); $phone = trim($phone); if ($patientName === '') { self::setError('患者姓名不能为空'); return false; } if (mb_strlen($patientName) > 50) { self::setError('患者姓名过长'); return false; } if ($phone === '') { self::setError('手机号不能为空'); return false; } if (strlen($phone) > 20) { self::setError('手机号过长'); return false; } $oldName = trim((string) ($rx->patient_name ?? '')); $oldPhone = trim((string) ($rx->phone ?? '')); try { $rx->save([ 'patient_name' => $patientName, 'phone' => $phone, ]); $nameFrom = $oldName === '' ? '—' : $oldName; $phoneFrom = $oldPhone === '' ? '—' : $oldPhone; $summary = sprintf( '关联处方 #%d:患者姓名「%s」→「%s」,手机「%s」→「%s」', $rxId, $nameFrom, $patientName, $phoneFrom, $phone ); self::writeLog($prescriptionOrderId, $adminId, $adminInfo, 'patch_rx_patient', $summary); return true; } catch (\Exception $e) { self::setError($e->getMessage()); return false; } } /** * 渠道字典 value => 名称(与挂号 channel_source 存值一致) * * @return array */ private static function channelSourceDictMap(): array { try { /** @var array $m */ $m = Db::name('dict_data')->where('type_value', 'channels')->column('name', 'value'); return $m; } catch (\Throwable) { return []; } } /** * @param array $row doctor_appointment 行 * @param array $channelDict * * @return array */ private static function formatAppointmentBriefFromRow(array $row, string $doctorName, array $channelDict = []): array { $apptId = (int) ($row['id'] ?? 0); $statusMap = [ 1 => '已预约', 2 => '已取消', 3 => '已完成', 4 => '已过号', ]; $typeMap = [ 'video' => '视频问诊', 'text' => '图文问诊', 'phone' => '电话问诊', ]; $periodRaw = (string) ($row['period'] ?? ''); $period = $periodRaw === 'afternoon' ? '下午' : ($periodRaw === 'morning' ? '上午' : $periodRaw); $timePart = (string) ($row['appointment_time'] ?? ''); if (strlen($timePart) > 5) { $timePart = substr($timePart, 0, 5); } $status = (int) ($row['status'] ?? 0); $atype = (string) ($row['appointment_type'] ?? ''); $chSrc = trim((string) ($row['channel_source'] ?? '')); if ($chSrc === '') { $legacy = $row['channels'] ?? null; if ($legacy !== null && $legacy !== '') { $chSrc = is_numeric($legacy) ? (string) (int) $legacy : trim((string) $legacy); } } $chDesc = ''; if ($chSrc !== '') { $chDesc = (string) ($channelDict[$chSrc] ?? ''); if ($chDesc === '' && is_numeric($chSrc)) { $chDesc = (string) ($channelDict[(string) (int) $chSrc] ?? ''); } if ($chDesc === '') { $chDesc = $chSrc; } } return [ 'id' => $apptId, 'appointment_date' => (string) ($row['appointment_date'] ?? ''), 'period' => $period, 'appointment_time' => $timePart, 'appointment_type' => $atype, 'appointment_type_desc' => $typeMap[$atype] ?? '未知', 'status' => $status, 'status_desc' => $statusMap[$status] ?? '未知', 'doctor_name' => $doctorName, 'channel_source' => $chSrc, 'channel_source_desc' => $chDesc, 'remark' => (string) ($row['remark'] ?? ''), ]; } /** * 挂号预约简要信息(业务订单详情展示) * * @return array|null */ private static function buildAppointmentBrief(int $appointmentId): ?array { if ($appointmentId <= 0) { return null; } $ap = Appointment::find($appointmentId); if ($ap === null) { return null; } $row = $ap->toArray(); $doctorName = ''; $did = (int) ($row['doctor_id'] ?? 0); if ($did > 0) { $doctorName = (string) (Admin::where('id', $did)->value('name') ?? ''); } $channelDict = self::channelSourceDictMap(); return self::formatAppointmentBriefFromRow($row, $doctorName, $channelDict); } /** * 业务订单列表行批量附加 linked_appointment(解析规则与详情一致:优先处方 appointment_id,否则诊单最近一条预约) * * @param array> $lists */ public static function appendLinkedAppointmentsToListRows(array &$lists): void { if ($lists === []) { return; } foreach ($lists as &$item) { $item['linked_appointment'] = null; } unset($item); $rxIds = array_values(array_unique(array_filter(array_map( static fn ($x) => (int) ($x['prescription_id'] ?? 0), $lists )))); $rxApptByRx = []; if ($rxIds !== []) { $rxApptByRx = Prescription::whereIn('id', $rxIds)->whereNull('delete_time')->column('appointment_id', 'id'); } $diagNeedFallback = []; foreach ($lists as $item) { $rxId = (int) ($item['prescription_id'] ?? 0); $dgId = (int) ($item['diagnosis_id'] ?? 0); $apFromRx = $rxId > 0 ? (int) ($rxApptByRx[$rxId] ?? 0) : 0; if ($apFromRx <= 0 && $dgId > 0) { $diagNeedFallback[$dgId] = true; } } $diagIds = array_keys($diagNeedFallback); $latestApptByDiag = []; if ($diagIds !== []) { try { $agg = Db::name('doctor_appointment') ->whereIn('patient_id', $diagIds) ->fieldRaw('patient_id, MAX(id) as max_id') ->group('patient_id') ->select() ->toArray(); foreach ($agg as $r) { $pid = (int) ($r['patient_id'] ?? 0); $mid = (int) ($r['max_id'] ?? 0); if ($pid > 0 && $mid > 0) { $latestApptByDiag[$pid] = $mid; } } } catch (\Throwable) { $latestApptByDiag = []; } } /** @var array $resolvedPairs index => [ resolvedApptId, rxAppointmentId ] */ $resolvedPairs = []; foreach ($lists as $idx => $item) { $rxId = (int) ($item['prescription_id'] ?? 0); $dgId = (int) ($item['diagnosis_id'] ?? 0); $rxApptId = $rxId > 0 ? (int) ($rxApptByRx[$rxId] ?? 0) : 0; $resolved = $rxApptId; if ($resolved <= 0 && $dgId > 0) { $resolved = (int) ($latestApptByDiag[$dgId] ?? 0); } if ($resolved > 0) { $resolvedPairs[$idx] = [$resolved, $rxApptId]; } } if ($resolvedPairs === []) { return; } $allApptIds = array_values(array_unique(array_map(static fn ($p) => $p[0], $resolvedPairs))); $appts = Appointment::whereIn('id', $allApptIds)->select()->toArray(); $apptById = []; foreach ($appts as $a) { $aid = (int) ($a['id'] ?? 0); if ($aid > 0) { $apptById[$aid] = $a; } } $doctorIds = array_values(array_unique(array_filter(array_map( static fn ($a) => (int) ($a['doctor_id'] ?? 0), $appts )))); $doctorNames = []; if ($doctorIds !== []) { $doctorNames = Admin::whereIn('id', $doctorIds)->column('name', 'id'); } $channelDict = self::channelSourceDictMap(); foreach ($resolvedPairs as $idx => $pair) { $resolvedId = $pair[0]; $rxApptId = $pair[1]; $row = $apptById[$resolvedId] ?? null; if ($row === null || !is_array($row)) { continue; } $did = (int) ($row['doctor_id'] ?? 0); $dname = $did > 0 ? (string) ($doctorNames[$did] ?? '') : ''; $brief = self::formatAppointmentBriefFromRow($row, $dname, $channelDict); $brief['resolved_from'] = $rxApptId === $resolvedId ? 'prescription' : 'diagnosis'; $lists[$idx]['linked_appointment'] = $brief; } } /** * 物流轨迹(顺丰/京东等,优先快递100;未配置密钥时返回官网链接) * * @return array|null */ public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo, string $phoneTail = ''): ?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 ?? ''), $phoneTail); $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; } // 已成功提交甘草 SCM(生成了甘草处方单号 或 submit_time>0),再改本地信息会与甘草侧不一致,禁止编辑 $gcOrderNo = trim((string) $order->gancao_reciperl_order_no); $gcSubmitTime = (int) $order->gancao_submit_time; if ($gcOrderNo !== '' || $gcSubmitTime > 0) { self::$error = sprintf( '订单已提交甘草(处方单号:%s),不可再编辑;如需修改请先走取消流程', $gcOrderNo !== '' ? $gcOrderNo : '—' ); 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']; // 处理省市区字段 if (isset($params['shipping_province'])) { $order->shipping_province = (string) $params['shipping_province']; } if (isset($params['shipping_city'])) { $order->shipping_city = (string) $params['shipping_city']; } if (isset($params['shipping_district'])) { $order->shipping_district = (string) $params['shipping_district']; } $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->dose_unit = (string) ($params['dose_unit'] ?? '剂'); $order->dose_count = isset($params['dose_count']) && (int)$params['dose_count'] > 0 ? (int)$params['dose_count'] : 1; $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); if (self::canEditOrderRemarkExtra($adminInfo)) { $order->remark_extra = (string) ($params['remark_extra'] ?? ''); } $order->remark_assistant = (string) ($params['remark_assistant'] ?? ''); $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, (int) $order->id); 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, (int) $order->id); 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::maskRemarkExtraIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * 确认发货:更新快递信息并将 fulfillment_status 从 2(履约中)推进到 5(已发货) * * @return array|false */ public static function ship(int $id, string $expressCompany, string $trackingNumber, string $shipMode, 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; } $shipMode = self::normalizeShipMode($shipMode); $shipModeText = $shipMode === 'direct' ? '药房直发' : '甘草药方发'; $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', '确认发货(' . $shipModeText . '),运单:' . $trackingNumber . '(' . $expressCompany . ')' ); } else { self::writeLog( (int) $order->id, $adminId, $adminInfo, 'fill_tracking', '更新发货信息(' . $shipModeText . '),运单:' . $trackingNumber . '(' . $expressCompany . ')' ); } $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::maskRemarkExtraIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } private static function normalizeShipMode(string $shipMode): string { $s = strtolower(trim($shipMode)); if ($s === 'direct') { return 'direct'; } return 'gancao'; } /** * @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::maskRemarkExtraIfNeeded($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::maskRemarkExtraIfNeeded($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; } $didAutoComplete = 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 && in_array((int) $order->fulfillment_status, [5, 6], true)) { $linkedPayOrderIds = self::linkedPayOrderIdList($id); $linkedPayOrders = self::linkedPayOrdersPayload($linkedPayOrderIds); $linkedPayPaidTotal = 0.0; foreach ($linkedPayOrders as $o) { $linkedPayPaidTotal += (float) ($o['amount'] ?? 0); } $linkedPayPaidTotal = round($linkedPayPaidTotal, 2); $order->fulfillment_status = 3; $order->paid = $linkedPayPaidTotal; $order->completion_request = 0; $order->completion_request_time = 0; $order->completion_request_by = 0; $order->completion_request_by_name = ''; $didAutoComplete = true; } } 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)/已签收(6):不同步为「待发货」,拒绝时也不整单解绑(与原先仅已发货一致) // - 其它状态:同步;拒绝时清除所有支付单关联关系 $currentStatus = (int) $order->fulfillment_status; if (in_array($currentStatus, [5, 6], true)) { // 保持履约节点不变 } else { self::syncFulfillmentStatus($order); if ($action === 'reject') { self::clearPayOrderLinks($order); } } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } if ($didAutoComplete) { $unassignSummary = self::clearDiagnosisAssistantOnComplete((int) $order->diagnosis_id); $paidStr = number_format((float) $order->paid, 2, '.', ''); self::writeLog((int) $order->id, $adminId, $adminInfo, 'auto_complete', '支付审核通过,根据完单申请自动完成订单,实付金额更新为 ¥' . $paidStr . $unassignSummary); } $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::maskRemarkExtraIfNeeded($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; } // 同步撤回处方表的审核状态 $prescriptionId = (int) $order->prescription_id; if ($prescriptionId > 0) { try { $prescription = Prescription::where('id', $prescriptionId)->whereNull('delete_time')->find(); if ($prescription) { $prescription->audit_status = 0; $prescription->audit_remark = ''; $prescription->audit_time = null; $prescription->audit_by = null; $prescription->audit_by_name = ''; $prescription->save(); } } catch (\Throwable $e) { // 记录日志但不影响主流程 Log::error('撤回处方审核时同步处方表失败: ' . $e->getMessage(), [ 'prescription_id' => $prescriptionId, 'order_id' => $id ]); } } self::writeLog((int) $order->id, $adminId, $adminInfo, 'revoke_rx_audit', '撤回处方审核,状态重置为待审核'); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::maskRemarkExtraIfNeeded($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::maskRemarkExtraIfNeeded($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/6) 的业务订单新增一条关联支付单(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 (!in_array((int) $order->fulfillment_status, [5, 6], true)) { 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::maskRemarkExtraIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** * 为「已发货/已签收」(fulfillment_status=5/6) 的业务订单关联已有支付单, * 关联后将支付审核状态重置为待审核以启动再次审核流程。 * * @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 (!in_array((int) $order->fulfillment_status, [5, 6], true)) { 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); $duplicated = array_values(array_intersect($payOrderIds, $existingIds)); if ($duplicated !== []) { self::$error = '支付单 #' . implode('、#', $duplicated) . ' 已绑定当前业务订单,请勿重复关联'; return false; } // 将支付单追加到业务订单关联列表 $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::maskRemarkExtraIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } /** 完成订单时可选的履约终态:3=已完成,7-12=业务结案类状态 */ private const COMPLETE_FULFILLMENT_TARGETS = [3, 7, 8, 9, 10, 11, 12]; /** 完成时是否按「实付=关联支付单金额」重算并清空诊单医助(与 clearDiagnosisAssistantOnComplete 的终态判断一致) */ private const COMPLETE_FULFILLMENT_TERMINAL = [3, 8, 9, 10, 11, 12]; /** * 将「已发货/已签收」且支付审核已通过的订单结案:可选「已完成」或 7-12 业务状态。 * * @return array|false */ public static function complete(int $id, int $adminId, array $adminInfo, int $targetFulfillmentStatus = 3) { self::$error = ''; if (!in_array($targetFulfillmentStatus, self::COMPLETE_FULFILLMENT_TARGETS, true)) { 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; } $curFs = (int) $order->fulfillment_status; if ($curFs !== 5 && $curFs !== 6) { self::$error = '仅「已发货」或「已签收」状态可完成订单'; return false; } if ((int) $order->payment_slip_audit_status !== 1) { self::$error = '支付单审核尚未通过,不可完成订单'; return false; } $label = self::fulfillmentStatusLabel($targetFulfillmentStatus); $linkedPayOrderIds = self::linkedPayOrderIdList($id); $linkedPayOrders = self::linkedPayOrdersPayload($linkedPayOrderIds); $linkedPayPaidTotal = 0.0; foreach ($linkedPayOrders as $o) { $linkedPayPaidTotal += (float) ($o['amount'] ?? 0); } $linkedPayPaidTotal = round($linkedPayPaidTotal, 2); if ($targetFulfillmentStatus === 3) { $order->fulfillment_status = 3; $order->paid = $linkedPayPaidTotal; } else { $order->fulfillment_status = $targetFulfillmentStatus; } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } $unassignSummary = ''; if (in_array($targetFulfillmentStatus, self::COMPLETE_FULFILLMENT_TERMINAL, true)) { $unassignSummary = self::clearDiagnosisAssistantOnComplete((int) $order->diagnosis_id); } if ($targetFulfillmentStatus === 3) { $logLine = '订单完成,状态变更为「' . $label . '」,实付金额更新为 ¥' . $linkedPayPaidTotal . $unassignSummary; } else { $logLine = '订单处理完成,状态变更为「' . $label . '」' . $unassignSummary; } self::writeLog( $id, $adminId, $adminInfo, 'complete', $logLine ); $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::maskRemarkExtraIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } private static function fulfillmentStatusLabel(int $fs): string { $m = [ 1 => '待双审通过', 2 => '待发货', 3 => '已完成', 4 => '已取消', 5 => '已发货', 6 => '已签收', 7 => '进行中', 8 => '暂不制药', 9 => '拒收', 10 => '退款', 11 => '保留药方', 12 => '制药缓发', ]; return $m[$fs] ?? ('状态' . (string) $fs); } /** * 完成订单时把关联诊单的 assistant_id 清空,让患者回到「待分配」状态 * 仅当该患者没有其它进行中的处方订单且无指派日志时才清空,避免误覆盖 * * @return string 追加到操作日志的描述(无变更则返回空串) */ private static function clearDiagnosisAssistantOnComplete(int $diagnosisId): string { if ($diagnosisId <= 0) { return ''; } try { $diag = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->find(); if (!$diag) { return ''; } $oldAssistant = (string) ($diag['assistant_id'] ?? ''); if ($oldAssistant === '' || $oldAssistant === '0') { return ''; } // 存在医助指派日志时,保留当前医助分配,不自动清空 $assignLogCount = DiagnosisAssignLog::where('diagnosis_id', $diagnosisId) ->whereNull('delete_time') ->count(); if ($assignLogCount > 0) { return ';检测到该患者已有医助指派记录,保留当前医助分配(assistant_id=' . $oldAssistant . ')'; } // 同一诊单下若仍有未完成/未取消的处方订单,则不清空 $pendingCount = PrescriptionOrder::where('diagnosis_id', $diagnosisId) ->whereNull('delete_time') ->whereNotIn('fulfillment_status', [3, 4, 8, 9, 10, 11, 12]) ->count(); if ($pendingCount > 0) { return ''; } Diagnosis::where('id', $diagnosisId) ->whereNull('delete_time') ->update(['assistant_id' => '']); return ';并已清空诊单医助分配(原 assistant_id=' . $oldAssistant . '),患者进入待分配状态'; } catch (\Throwable $e) { Log::warning('clearDiagnosisAssistantOnComplete failed: ' . $e->getMessage(), [ 'diagnosis_id' => $diagnosisId, ]); return ''; } } /** * 列表行是否展示「上传甘草药方」(需配置甘草、处方审核通过、未上传过、有权限;不要求支付审核)。 * * @param array $item * @param array $assistantByDiag diagnosis_id => assistant_id */ public static function canUploadGancaoRecipel(array $item, int $adminId, array $adminInfo, array $assistantByDiag = []): bool { if (!GancaoScmRecipelService::isConfigured()) { return false; } $pa = (int) ($item['prescription_audit_status'] ?? 0); $fs = (int) ($item['fulfillment_status'] ?? 0); if ($pa !== 1) { return false; } if (in_array($fs, [3, 4, 8, 9, 10, 11, 12], true)) { return false; } if (trim((string) ($item['gancao_reciperl_order_no'] ?? '')) !== '') { return false; } if (self::canSeeAllPrescriptionOrders($adminInfo)) { return true; } if ((int) ($item['creator_id'] ?? 0) === $adminId) { return true; } if ( self::canViewOrdersForOwnPrescription($adminInfo) && self::isPrescriptionOrderPrescriber((int) ($item['prescription_id'] ?? 0), $adminId) ) { return true; } $did = (int) ($item['diagnosis_id'] ?? 0); $aid = (int) ($assistantByDiag[$did] ?? 0); return $aid === $adminId && $adminId > 0; } /** * 将当前业务订单关联处方提交至甘草药管家(CTM_PREVIEW → CTM_SUBMIT_RECIPEL)。 * * @return array|false 成功返回甘草 result 主要字段 */ public static function submitGancaoRecipel(int $id, int $adminId, array $adminInfo) { self::$error = ''; if (!GancaoScmRecipelService::isConfigured()) { $why = GancaoScmRecipelService::whyNotConfigured(); self::$error = $why !== '' ? $why : '甘草处方上传未启用或未配置完整,请检查 .env(GANCAO_SCM_*)须写在任意 [分区] 之前,或使用 [GANCAO_SCM] 分区'; 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; } $item = $order->toArray(); $assistantByDiag = Diagnosis::where('id', (int) $order->diagnosis_id)->whereNull('delete_time')->column('assistant_id', 'id'); if (!self::canUploadGancaoRecipel($item, $adminId, $adminInfo, $assistantByDiag)) { self::$error = '须处方审核通过后方可上传甘草;已完成/已取消订单不可上传;同一订单仅可上传一次'; return false; } $rxId = (int) $order->prescription_id; if ($rxId <= 0) { self::$error = '订单未关联处方'; return false; } $rx = PrescriptionLogic::detail($rxId, $adminId, $adminInfo); if ($rx === null) { self::$error = PrescriptionLogic::getError() !== '' ? PrescriptionLogic::getError() : '处方不存在'; return false; } $c = Config::get('gancao_scm', []); $nameMap = is_array($c['herb_id_map'] ?? null) ? $c['herb_id_map'] : []; $herbs = is_array($rx['herbs'] ?? null) ? $rx['herbs'] : []; $docGidMap = GancaoScmRecipelService::doctorMedicineGidMapForHerbs($herbs); [, $missing] = GancaoScmRecipelService::buildMList($herbs, $nameMap, $docGidMap); if ($missing !== []) { self::$error = '以下药材无法匹配甘草药ID(请核对医师药品库 zyt_doctor_medicine 中同名字段的 gid,或在处方 herbs 中写 gc_id / config 的 herb_id_map):' . implode('、', $missing); return false; } $token = GancaoScmRecipelService::getToken(false); if ($token === null || $token === '') { $why = GancaoScmRecipelService::getLastGetTokenError(); self::$error = $why !== '' ? $why : '获取甘草开放平台 token 失败,请检查 GANCAO_SCM_GATEWAY_*(网关 OpenAPI)与 GANCAO_SCM_BIZ_*(业务账号,常与网关不同)、网关地址及服务器时间'; return false; } $orderArr = $order->toArray(); // dump($rx); // dump($orderArr); $previewPayload = GancaoScmRecipelService::buildPreviewPayload($rx, $orderArr, $token); $prevRet = GancaoScmRecipelService::ctmPreview($previewPayload); if ((int) ($prevRet['state'] ?? 0) !== 1) { self::$error = '甘草预检查通信失败:' . (string) ($prevRet['msg'] ?? ''); return false; } $prevBody = $prevRet['body'] ?? []; if (!GancaoScmRecipelService::isApiSuccess($prevBody)) { self::$error = '甘草预检查未通过:' . GancaoScmRecipelService::apiStatusMessage($prevBody); return false; } $result = $prevBody['result'] ?? []; if (is_array($result)) { $rules = $result['rule_check'] ?? []; if (is_array($rules)) { foreach ($rules as $ru) { if (!is_array($ru)) { continue; } $t = (int) ($ru['type'] ?? 0); if ($t >= 2) { self::$error = '甘草预检查规则拦截:' . (string) ($ru['msg'] ?? '') . '(code:' . (string) ($ru['code'] ?? '') . ')'; return false; } } } $mListOut = $result['m_list'] ?? []; if (is_array($mListOut)) { foreach ($mListOut as $rowM) { if (is_array($rowM) && isset($rowM['is_available']) && (int) $rowM['is_available'] === 0) { $tn = (string) ($rowM['title'] ?? ''); self::$error = '甘草药库缺药,请调整处方:' . ($tn !== '' ? $tn : '未知药材'); return false; } } } } $addrParts = GancaoScmRecipelService::splitCnAddress((string) $order->shipping_address); $appNo = (string) $order->order_no; $submitPayload = GancaoScmRecipelService::buildSubmitPayload($rx, $orderArr, $token, $addrParts, $appNo); $subRet = GancaoScmRecipelService::ctmSubmit($submitPayload); if ((int) ($subRet['state'] ?? 0) !== 1) { $errMsg = (string) ($subRet['msg'] ?? ''); $response = isset($subRet['response']) ? mb_substr((string) $subRet['response'], 0, 500) : ''; self::$error = '甘草下单通信失败:' . $errMsg . ($response !== '' ? ';响应:' . $response : ''); Log::error('Gancao CTM_SUBMIT failed', [ 'subRet' => $subRet, 'payload' => json_encode(GancaoScmRecipelService::maskSensitiveData($submitPayload), JSON_UNESCAPED_UNICODE) ]); return false; } $subBody = $subRet['body'] ?? []; if (!GancaoScmRecipelService::isApiSuccess($subBody)) { self::$error = '甘草下单失败:' . GancaoScmRecipelService::apiStatusMessage($subBody); Log::error('Gancao CTM_SUBMIT api error', [ 'body' => $subBody, 'payload' => json_encode(GancaoScmRecipelService::maskSensitiveData($submitPayload), JSON_UNESCAPED_UNICODE) ]); return false; } $gcNo = (string) (($subBody['result'] ?? [])['recipel_order_no'] ?? ''); if ($gcNo === '') { self::$error = '甘草返回缺少 recipel_order_no'; return false; } $order->gancao_reciperl_order_no = mb_substr($gcNo, 0, 32); $order->gancao_submit_time = time(); try { $order->save(); } catch (\Throwable $e) { self::$error = '本地保存甘草单号失败:' . $e->getMessage(); return false; } self::writeLog($id, $adminId, $adminInfo, 'gancao_submit', '上传甘草药方成功 ' . $gcNo); $fee = $subBody['result']['fee'] ?? []; $appNo = $submitPayload['app_order_no'] ?? ('PO' . (string) $order->id); return [ 'recipel_order_no' => $gcNo, 'app_order_no' => $appNo, 'fee' => is_array($fee) ? $fee : [], ]; } /** * 甘草预下单测试(仅 CTM_PREVIEW,不提交订单) * 用于在编辑订单时测试价格和配置 * * @param array $params 包含 id 和可选的 dose_count * @return array|false 成功返回预览结果(包含价格信息) */ public static function previewGancaoRecipel(int $id, int $adminId, array $adminInfo, array $params = []) { self::$error = ''; if (!GancaoScmRecipelService::isConfigured()) { $why = GancaoScmRecipelService::whyNotConfigured(); self::$error = $why !== '' ? $why : '甘草处方上传未启用或未配置完整'; 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; } $rxId = (int) $order->prescription_id; if ($rxId <= 0) { self::$error = '订单未关联处方'; return false; } $rx = PrescriptionLogic::detail($rxId, $adminId, $adminInfo); if ($rx === null) { self::$error = PrescriptionLogic::getError() !== '' ? PrescriptionLogic::getError() : '处方不存在'; return false; } $c = Config::get('gancao_scm', []); $nameMap = is_array($c['herb_id_map'] ?? null) ? $c['herb_id_map'] : []; $herbs = is_array($rx['herbs'] ?? null) ? $rx['herbs'] : []; $docGidMap = GancaoScmRecipelService::doctorMedicineGidMapForHerbs($herbs); [, $missing] = GancaoScmRecipelService::buildMList($herbs, $nameMap, $docGidMap); if ($missing !== []) { self::$error = '以下药材无法匹配甘草药ID:' . implode('、', $missing); return false; } $token = GancaoScmRecipelService::getToken(false); if ($token === null || $token === '') { $why = GancaoScmRecipelService::getLastGetTokenError(); self::$error = $why !== '' ? $why : '获取甘草开放平台 token 失败'; return false; } $orderArr = $order->toArray(); // 如果传递了 dose_count 参数,使用传递的值覆盖订单中的值 if (isset($params['dose_count']) && (int)$params['dose_count'] > 0) { $orderArr['dose_count'] = (int)$params['dose_count']; } // 如果传递了 medication_days 参数,使用传递的值覆盖订单中的值 if (isset($params['medication_days']) && (int)$params['medication_days'] > 0) { $orderArr['medication_days'] = (int)$params['medication_days']; } $previewPayload = GancaoScmRecipelService::buildPreviewPayload($rx, $orderArr, $token); $prevRet = GancaoScmRecipelService::ctmPreview($previewPayload); if ((int) ($prevRet['state'] ?? 0) !== 1) { self::$error = '甘草预检查通信失败:' . (string) ($prevRet['msg'] ?? ''); return false; } $prevBody = $prevRet['body'] ?? []; if (!GancaoScmRecipelService::isApiSuccess($prevBody)) { self::$error = '甘草预检查未通过:' . GancaoScmRecipelService::apiStatusMessage($prevBody); return false; } $result = $prevBody['result'] ?? []; // 检查规则拦截 if (is_array($result)) { $rules = $result['rule_check'] ?? []; if (is_array($rules)) { foreach ($rules as $ru) { if (!is_array($ru)) continue; $t = (int) ($ru['type'] ?? 0); if ($t >= 2) { self::$error = '甘草预检查规则拦截:' . (string) ($ru['msg'] ?? ''); return false; } } } // 检查药材可用性 $mListOut = $result['m_list'] ?? []; if (is_array($mListOut)) { foreach ($mListOut as $rowM) { if (is_array($rowM) && isset($rowM['is_available']) && (int) $rowM['is_available'] === 0) { $tn = (string) ($rowM['title'] ?? ''); self::$error = '甘草药库缺药:' . ($tn !== '' ? $tn : '未知药材'); return false; } } } } // 返回预览结果 $fee = $result['fee'] ?? []; return [ 'success' => true, 'fee' => is_array($fee) ? $fee : [], 'result' => $result, 'order_no' => (string) $order->order_no, 'dose_count' => (int) ($orderArr['dose_count'] ?? $rx['dose_count'] ?? 0), /** 本业务订单金额(与甘草预报价合计对比占比用) */ 'amount' => round((float) ($order->amount ?? 0), 2), ]; } 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) { // 忽略日志写入错误 } } }