hasCloseLog($orderId)) { return true; } $row = SplitOrder::where('order_id', $orderId) ->where('channel', 'huifu') ->order('id', 'desc') ->find(); return $row && intval($row['status']) === self::STATUS_CLOSED; } public function hasCloseLog($orderId) { $orderId = intval($orderId); if ($orderId <= 0) { return false; } if (!$this->manualLogSupportsClose()) { return false; } return (bool) SplitManualLog::where('order_id', $orderId)->where('action', 'close')->find(); } /** * @return int[] order_id 列表(仅有 close 日志、无 status=5 分账单时也视为结案) */ public function orderIdsWithCloseLog(array $orderIds) { $orderIds = array_values(array_unique(array_filter(array_map('intval', $orderIds)))); if (!$orderIds || !$this->manualLogSupportsClose()) { return []; } return array_map('intval', SplitManualLog::where('order_id', 'in', $orderIds) ->where('action', 'close') ->group('order_id') ->column('order_id')); } /** * 按订单号结案 * * @return array{order_id:int, split_order_id:int|null, already:bool} */ public function closeByOrderSn($orderSn, $reason, $adminId = 0, $adminName = '') { $orderSn = trim((string) $orderSn); if ($orderSn === '') { throw new \InvalidArgumentException('订单号不能为空'); } $reason = trim((string) $reason); if ($reason === '') { throw new \InvalidArgumentException('请填写结案原因'); } $order = Order::where('order_sn', $orderSn)->find(); if (!$order && strlen($orderSn) < 24) { $order = Order::where('order_sn', 'like', $orderSn . '%')->order('id', 'desc')->find(); } if (!$order) { throw new \RuntimeException('订单不存在: ' . $orderSn); } return $this->closeByOrderId(intval($order['id']), $reason, $adminId, $adminName); } /** * @return array{order_id:int, split_order_id:int|null, already:bool} */ public function closeByOrderId($orderId, $reason, $adminId = 0, $adminName = '') { $orderId = intval($orderId); $reason = trim((string) $reason); if ($reason === '') { throw new \InvalidArgumentException('请填写结案原因'); } if ($this->isClosed($orderId)) { return ['order_id' => $orderId, 'split_order_id' => null, 'already' => true]; } $order = Order::get($orderId); if (!$order) { throw new \RuntimeException('订单不存在'); } $split = SplitOrder::where('order_id', $orderId) ->where('channel', 'huifu') ->order('id', 'desc') ->find(); $config = new Config(); $retryMax = max(5, $config->getRetryMax()); Db::startTrans(); try { $splitOrderId = null; if ($split) { $status = intval($split['status']); if (in_array($status, [0, 1], true)) { throw new \RuntimeException('分账进行中,请稍后再结案或先同步状态'); } $splitOrderId = intval($split['id']); $split->status = self::STATUS_CLOSED; $split->retry_count = max(intval($split['retry_count']), $retryMax + 1); $split->next_retry_time = null; if ($this->splitOrderHasCloseColumns()) { $split->close_reason = mb_substr($reason, 0, 255); $split->close_admin_id = intval($adminId); $split->closetime = time(); } $note = "\n[已结案 " . date('Y-m-d H:i:s') . '] ' . $reason; $split->wx_result = trim((string) ($split['wx_result'] ?? '')) . $note; $split->save(); } $this->writeCloseLog($orderId, (string) $order['order_sn'], $reason, $adminId, $adminName, $splitOrderId); Db::commit(); return ['order_id' => $orderId, 'split_order_id' => $splitOrderId, 'already' => false]; } catch (\Exception $e) { Db::rollback(); throw $e; } } protected function writeCloseLog($orderId, $orderSn, $reason, $adminId, $adminName, $splitOrderId = null) { if (!$this->manualLogSupportsClose()) { return; } SplitManualLog::create([ 'order_id' => intval($orderId), 'order_sn' => $orderSn, 'action' => 'close', 'before_items' => [], 'after_items' => [], 'admin_id' => intval($adminId), 'admin_name' => $adminName ?: 'system', 'remark' => mb_substr($reason, 0, 255), 'retry_split_order_id'=> $splitOrderId ? intval($splitOrderId) : null, 'createtime' => time(), ]); } protected function splitOrderHasCloseColumns() { static $ok = null; if ($ok !== null) { return $ok; } $prefix = config('database.prefix'); try { $rows = Db::query("SHOW COLUMNS FROM `{$prefix}shopro_split_order` LIKE 'close_reason'"); $ok = !empty($rows); } catch (\Exception $e) { $ok = false; } return $ok; } protected function manualLogSupportsClose() { static $ok = null; if ($ok !== null) { return $ok; } $prefix = config('database.prefix'); try { $row = Db::query("SHOW COLUMNS FROM `{$prefix}shopro_split_manual_log` WHERE Field = 'action'"); if (empty($row[0]['Type'])) { $ok = false; return $ok; } $ok = strpos($row[0]['Type'], 'close') !== false; } catch (\Exception $e) { $ok = false; } return $ok; } }