文件
可乐加冰 077cbb5c72 ```
feat(shopro_wechat_trade): 添加自提订单准备功能

- 在ConfirmOrders命令中添加prepareSelfetchOrder方法,用于处理自提订单的发货逻辑
- 当订单项为自提类型且未发货时,调用checkDispatchAndSend进行发货操作
- 添加异常处理机制,确保自提订单处理失败时不会中断整体流程
- 增加dry-run模式支持,便于测试验证
- 添加日志输出,提供详细的执行状态反馈
```
2026-10-09 12:45:47 +08:00

232 行
8.8 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace addons\shopro_wechat_trade\command;
use addons\shopro\model\Order;
use addons\shopro\model\OrderItem;
use addons\shopro_wechat_trade\library\Bridge;
use addons\shopro_wechat_trade\library\SyncLogger;
use addons\shopro_wechat_trade\library\WxShippingService;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Db;
/**
* 商城确认收货 + 微信 notify_confirm_receive(按 order_sn / order_id)
*/
class ConfirmOrders extends Command
{
protected function configure()
{
$this->setName('shopro_wechat_trade:confirm-orders')
->addOption('sn', null, Option::VALUE_REQUIRED, '订单号,多个用英文逗号分隔')
->addOption('id', null, Option::VALUE_REQUIRED, '订单 id,多个用英文逗号分隔')
->addOption('skip-mall', null, Option::VALUE_NONE, '跳过商城 getedItem,仅调微信 confirm(商城已全部收货时用)')
->addOption('skip-wechat', null, Option::VALUE_NONE, '仅商城确认收货,不同步微信')
->addOption('dry-run', null, Option::VALUE_NONE, '只打印将执行的操作')
->setDescription('Confirm orders in mall and sync WeChat notify_confirm_receive');
}
protected function execute(Input $input, Output $output)
{
if (!Bridge::isEnabled()) {
$output->writeln('[FAIL] shopro_wechat_trade 未启用或 wechat_trade_enable=0');
return 1;
}
$orderIds = $this->resolveOrderIds($input);
if (!$orderIds) {
$output->writeln('请指定 --sn=订单号 或 --id=订单id');
return 1;
}
$skipMall = (bool) $input->getOption('skip-mall');
$skipWechat = (bool) $input->getOption('skip-wechat');
$dryRun = (bool) $input->getOption('dry-run');
$service = new WxShippingService();
$orderModel = new Order();
$ok = 0;
$fail = 0;
foreach ($orderIds as $orderId) {
$order = Order::with('item')->where('id', $orderId)->find();
if (!$order) {
$output->writeln("[SKIP] 订单不存在 id={$orderId}");
$fail++;
continue;
}
$output->writeln('--- order#' . $order['id'] . ' ' . ($order['order_sn'] ?? '') . ' ---');
if (!Bridge::shouldHandleOrder($order)) {
$output->writeln('[SKIP] 非小程序 wechat/huifu_wechat 或缺少 transaction_id');
$fail++;
continue;
}
try {
$this->prepareSelfetchOrder($orderModel, $order, $dryRun, $output);
} catch (\Exception $e) {
$output->writeln(' [FAIL] 自提准备: ' . $e->getMessage());
$fail++;
continue;
}
if (!$skipMall) {
try {
$this->mallConfirmItems($orderModel, $order, $dryRun, $output);
} catch (\Exception $e) {
$output->writeln(' [FAIL] 商城确认: ' . $e->getMessage());
$fail++;
continue;
}
} else {
$output->writeln(' [info] skip-mall:不修改商城收货状态');
}
if ($skipWechat) {
$output->writeln(' [info] skip-wechat:跳过微信同步');
$ok++;
continue;
}
if (!SyncLogger::hasSuccessfulShipping($orderId)) {
$output->writeln(' [WARN] 无 shipping success,先尝试 upload_shipping_info');
if ($dryRun) {
$output->writeln(' [dry-run] syncOrderShipping');
} else {
try {
$service->syncOrderShipping($orderId);
$output->writeln(' [OK] shipping 同步成功');
} catch (\Exception $e) {
$output->writeln(' [FAIL] shipping: ' . $e->getMessage());
$fail++;
continue;
}
}
}
if (SyncLogger::hasSuccessfulConfirm($orderId)) {
$output->writeln(' [OK] 微信 confirm 已是 success,跳过');
$ok++;
continue;
}
if ($dryRun) {
$output->writeln(' [dry-run] syncOrderConfirm');
$ok++;
continue;
}
try {
$service->syncOrderConfirm($orderId);
$output->writeln(' [OK] 微信 confirm 同步成功');
$ok++;
} catch (\Exception $e) {
$output->writeln(' [FAIL] confirm: ' . $e->getMessage());
$fail++;
}
}
$output->writeln('');
$output->writeln("完成:成功 {$ok},失败 {$fail}");
if (!$skipWechat && !$dryRun && $ok > 0) {
$output->writeln('汇付分账(非 FREEZE 后):php think shopro_huifu:split-retry');
}
return $fail > 0 ? 1 : 0;
}
protected function resolveOrderIds(Input $input)
{
$ids = [];
$snOpt = trim((string) $input->getOption('sn'));
if ($snOpt !== '') {
foreach (preg_split('/[\s,,]+/', $snOpt) as $sn) {
$sn = trim($sn);
if ($sn === '') {
continue;
}
$row = Db::name('shopro_order')->where('order_sn', $sn)->find();
if (!$row && strlen($sn) < 24) {
$row = Db::name('shopro_order')->where('order_sn', 'like', $sn . '%')->order('id', 'desc')->find();
}
if ($row) {
$ids[] = intval($row['id']);
}
}
}
$idOpt = trim((string) $input->getOption('id'));
if ($idOpt !== '') {
foreach (preg_split('/[\s,,]+/', $idOpt) as $id) {
$id = intval($id);
if ($id > 0) {
$ids[] = $id;
}
}
}
return array_values(array_unique($ids));
}
/**
* 自提/到店:未发货时先走 selfetchSendItem,便于 upload_shipping_info(logistics_type=4)
*/
protected function prepareSelfetchOrder(Order $orderModel, &$order, $dryRun, Output $output)
{
$needsShip = false;
foreach ($order->item ?? [] as $item) {
$itemArr = is_object($item) && method_exists($item, 'toArray') ? $item->toArray() : (array) $item;
if (($itemArr['dispatch_type'] ?? '') !== 'selfetch') {
continue;
}
if (intval($itemArr['dispatch_status'] ?? 0) < OrderItem::DISPATCH_STATUS_SENDED) {
$needsShip = true;
break;
}
}
if (!$needsShip) {
return;
}
if ($dryRun) {
$output->writeln(' [dry-run] 自提发货 checkDispatchAndSend');
return;
}
$orderModel->checkDispatchAndSend($order, []);
$order = Order::with('item')->where('id', $order['id'])->find();
if (!$order) {
throw new \RuntimeException('自提发货后订单不存在');
}
$output->writeln(' [OK] 自提已发货(待同步微信 upload_shipping_info)');
sleep(2);
}
protected function mallConfirmItems(Order $orderModel, $order, $dryRun, Output $output)
{
$items = $order->item ?? [];
foreach ($items as $item) {
$itemArr = is_object($item) && method_exists($item, 'toArray') ? $item->toArray() : (array) $item;
$status = intval($itemArr['dispatch_status'] ?? 0);
if ($status >= OrderItem::DISPATCH_STATUS_GETED) {
continue;
}
if ($status < OrderItem::DISPATCH_STATUS_SENDED) {
$output->writeln(' [FAIL] item#' . ($itemArr['id'] ?? '') . ' 未发货,请先发货再确认');
throw new \RuntimeException('存在未发货商品');
}
if (in_array(intval($itemArr['refund_status'] ?? 0), [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH], true)) {
$output->writeln(' [SKIP] item#' . ($itemArr['id'] ?? '') . ' 已退款');
continue;
}
if ($dryRun) {
$output->writeln(' [dry-run] getedItem item#' . ($itemArr['id'] ?? ''));
continue;
}
Db::transaction(function () use ($orderModel, $order, $item) {
$orderModel->getedItem($order, $item, ['oper_type' => 'admin']);
});
$output->writeln(' [OK] 商城确认 item#' . ($itemArr['id'] ?? ''));
}
}
}