1901 lines
70 KiB
PHP
1901 lines
70 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace app\adminapi\logic\tcm;
|
||
|
||
use app\adminapi\logic\auth\AuthLogic;
|
||
use app\adminapi\logic\order\OrderLogic;
|
||
use app\common\model\Order;
|
||
use app\common\model\tcm\Diagnosis;
|
||
use app\common\model\tcm\Prescription;
|
||
use app\common\model\tcm\PrescriptionOrder;
|
||
use app\common\model\tcm\PrescriptionOrderLog;
|
||
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
||
use app\common\model\auth\Admin;
|
||
use app\common\service\ExpressTrackService;
|
||
use app\common\service\gancao\GancaoScmRecipelService;
|
||
use think\facade\Config;
|
||
use think\facade\Log;
|
||
|
||
class PrescriptionOrderLogic
|
||
{
|
||
private static string $error = '';
|
||
|
||
public static function setError(string $msg): void
|
||
{
|
||
self::$error = $msg;
|
||
}
|
||
|
||
public static function getError(): string
|
||
{
|
||
return self::$error;
|
||
}
|
||
|
||
/**
|
||
* @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 : []);
|
||
}
|
||
|
||
/**
|
||
* @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<string,mixed> $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<int, array<string, mixed>>
|
||
*/
|
||
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<string,mixed> $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<string,mixed> $params
|
||
* @return array<string,mixed>|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<string,mixed>|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<string,mixed> $params
|
||
* @return array<string,mixed>|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'];
|
||
|
||
// 处理省市区字段
|
||
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);
|
||
$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<string,mixed>|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<string,mixed>|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<string,mixed>|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<string,mixed>|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<string,mixed>|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::attachLinkedPayOrders($out);
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* 撤回支付单审核:将已通过/已驳回的支付单审核重置为待审核
|
||
*
|
||
* @return array<string,mixed>|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<int, array<string, mixed>>
|
||
*/
|
||
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<string,mixed> $params id, order_type, amount, remark
|
||
* @return array<string,mixed>|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<string,mixed> $params id, pay_order_id
|
||
* @return array<string,mixed>|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<string,mixed>|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;
|
||
}
|
||
|
||
// 计算关联订单总额
|
||
$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;
|
||
|
||
try {
|
||
$order->save();
|
||
} catch (\Throwable $e) {
|
||
self::$error = $e->getMessage();
|
||
return false;
|
||
}
|
||
|
||
self::writeLog($id, $adminId, $adminInfo, 'complete', '订单完成,状态变更为「已完成」,实付金额更新为 ¥' . $linkedPayPaidTotal);
|
||
|
||
$out = $order->toArray();
|
||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||
self::attachLinkedPayOrders($out);
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* 列表行是否展示「上传甘草药方」(需配置甘草、处方审核通过、未上传过、有权限;不要求支付审核)。
|
||
*
|
||
* @param array<string,mixed> $item
|
||
* @param array<int, int|string> $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 ($fs === 3 || $fs === 4) {
|
||
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;
|
||
}
|
||
$did = (int) ($item['diagnosis_id'] ?? 0);
|
||
$aid = (int) ($assistantByDiag[$did] ?? 0);
|
||
|
||
return $aid === $adminId && $adminId > 0;
|
||
}
|
||
|
||
/**
|
||
* 将当前业务订单关联处方提交至甘草药管家(CTM_PREVIEW → CTM_SUBMIT_RECIPEL)。
|
||
*
|
||
* @return array<string,mixed>|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<string,mixed>|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),
|
||
];
|
||
}
|
||
|
||
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) {
|
||
// 忽略日志写入错误
|
||
}
|
||
}
|
||
}
|