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; } 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']); } } 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(); 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'] = []; return; } $ids = self::linkedPayOrderIdList($id); $arr['pay_order_ids'] = $ids; $arr['linked_pay_orders'] = self::linkedPayOrdersPayload($ids); } /** * @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; } } $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; } 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); 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']); $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); } } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } $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(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } $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; } $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; } 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); } else { $order->payment_slip_audit_status = 2; $order->payment_slip_audit_remark = mb_substr(trim($remark), 0, 500); } self::syncFulfillmentStatus($order); if ($action === 'reject') { self::clearPayOrderLinks($order); } try { $order->save(); } catch (\Throwable $e) { self::$error = $e->getMessage(); return false; } $out = $order->toArray(); self::maskInternalCostIfNeeded($out, $adminInfo); self::attachLinkedPayOrders($out); return $out; } }