From 22362638efaf047c1df72282cce66c133562279a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=98=8A=E5=A4=A9?= <442384644@qq.com> Date: Fri, 23 Oct 2026 16:11:02 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E8=AE=A2=E5=8D=95):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E6=8B=86=E5=8D=95=E5=8F=91=E8=B4=A7=E4=B8=8E=E6=8B=86?= =?UTF-8?q?=E5=8D=95=E9=80=80=E6=AC=BE=E6=AE=8B=E7=95=99=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 发货服务删除 splitDelivery/getCartList/tidyCartList,通用发货任务改走 delivery() - 删除子单收货联动 checkMaster、拆单开票 splitOrderInvoice 及 pid 分支 - 普通订单模块与开放接口移除拆单控制器方法、路由与拆单文案 - 订单详情 related_orders 恒为 null,文档同步为不支持拆单 --- .../05-用户端开发/user-order-detail.md | 5 +- backend/app/adminapi/route/order.php | 6 - backend/app/jobs/OrderExpressJob.php | 4 +- backend/app/lang/en_us.php | 5 - backend/app/lang/zh_cn.php | 5 - .../services/order/OrderCenterServices.php | 7 +- .../services/order/OrderInvoiceServices.php | 46 +--- .../services/order/OrderLifecycleServices.php | 9 - .../app/services/order/OrderOutServices.php | 65 +----- .../app/services/order/OrderTakeServices.php | 15 -- .../controller/admin/OrderController.php | 122 ---------- .../modules/product/normal/route/admin.php | 2 - .../product/normal/service/OrderServices.php | 213 ------------------ help/docs/api/管理端接口.md | 2 +- help/docs/项目功能.md | 6 +- 15 files changed, 16 insertions(+), 496 deletions(-) diff --git a/.crmeb/skills/crmeb-dev/references/05-用户端开发/user-order-detail.md b/.crmeb/skills/crmeb-dev/references/05-用户端开发/user-order-detail.md index a04904475..75365a03b 100644 --- a/.crmeb/skills/crmeb-dev/references/05-用户端开发/user-order-detail.md +++ b/.crmeb/skills/crmeb-dev/references/05-用户端开发/user-order-detail.md @@ -319,9 +319,8 @@ GET /api/center/order/detail/{orderNo} {"type": "warning", "text": "签收后请在7天内确认收货"} ], - // 11. related_orders — 关联订单(拆单/换货时存在) + // 11. related_orders — 关联订单(换货等衍生单时存在;系统不支持拆单,无子订单) "related_orders": [ // ← 中台查关联订单 - {"order_no": "cp123456790", "relation": "split", "title": "子订单1", "status_text": "已发货"}, {"order_no": "cp123456791", "relation": "exchange", "title": "换货订单", "status_text": "进行中"} ], @@ -593,7 +592,7 @@ eb_order_center 实体列 > extra_info JSON > 扩展订单表回退 > Handler | countdown | `status_code=pending_payment` | 中台 | | verification | `shipping_type=2` 或需核销 | 中台 | | notice | 按状态规则 | 中台 | -| related_orders | 有拆单/换货 | 中台 | +| related_orders | 有换货等衍生单 | 中台 | | extensions.fulfillment | 各产品类型注入 | 产品类型监听器 | | extensions.activity | 交易模式注入 | 交易模式监听器 | | extensions.after_sale | 有售后记录 | 售后模块监听器 | diff --git a/backend/app/adminapi/route/order.php b/backend/app/adminapi/route/order.php index e524bf80e..30162c297 100644 --- a/backend/app/adminapi/route/order.php +++ b/backend/app/adminapi/route/order.php @@ -40,12 +40,6 @@ Route::group('order', function () { // Route::put('delivery/:id', [\app\adminapi\controller\order\OrderCenterController::class, 'updateDelivery']); //获取商家寄件金额 → modules/product/normal/OrderController::getSheetInfo // Route::post('price', [\app\adminapi\controller\order\OrderCenterController::class, 'getPrice']); - //获取订单可拆分商品列表 → modules/product/normal/OrderController::getSplitCartInfo - // Route::get('split_cart_info/:id', [\app\adminapi\controller\order\OrderCenterController::class, 'splitCartInfo']); - //拆单发送货 → modules/product/normal/OrderController::splitDelivery - // Route::put('split_delivery/:id', [\app\adminapi\controller\order\OrderCenterController::class, 'splitDelivery']); - //获取订单拆分子订单列表 → modules/product/normal/OrderController::getSplitOrderList(TODO) - // Route::get('split_order/:id', [\app\adminapi\controller\order\OrderCenterController::class, 'splitOrder']); //订单退款表单 → modules/product/normal/OrderController::getRefundForm // Route::get('refund/:id', [\app\adminapi\controller\order\OrderCenterController::class, 'refund']); //订单退款 → modules/product/normal/OrderController::processRefund diff --git a/backend/app/jobs/OrderExpressJob.php b/backend/app/jobs/OrderExpressJob.php index e7e82384b..a94fb7c23 100644 --- a/backend/app/jobs/OrderExpressJob.php +++ b/backend/app/jobs/OrderExpressJob.php @@ -39,13 +39,13 @@ class OrderExpressJob extends BaseJobs 'sh_delivery_id' => '', 'sh_delivery_uid' => '', 'fictitious_content' => '', - 'cart_ids' => [], 'day_type' => 0, 'pickup_time' => [], 'service_type' => '', ]; try { - app()->make(OrderDeliveryServices::class)->splitDelivery($id, $data, false); + // 整单发货(系统不支持拆单发货,统一走通用发货入口) + app()->make(OrderDeliveryServices::class)->delivery($id, $data); } catch (\Throwable $e) { Log::error('订单ID' . $id . '发货失败,失败原因:' . $e->getMessage()); } diff --git a/backend/app/lang/en_us.php b/backend/app/lang/en_us.php index 8346a83a6..a2ad7c242 100644 --- a/backend/app/lang/en_us.php +++ b/backend/app/lang/en_us.php @@ -624,10 +624,6 @@ return [ '请选择奖品类型' => 'Please select the type of prize', '请填写奖品赠送{:type}数' => 'Please fill in the {:type} number of prizes given', '请选择优惠券' => 'Please select a coupon', - '生成新订单失败' => 'Failed to generate new order', - '修改原来订单商品拆分状态失败' => 'Failed to modify the original order item split status', - '新增拆分订单商品信息失败' => 'Failed to add product information for split order', - '保存新订单商品信息失败' => 'Failed to save new order product information', '协议类型缺失' => 'Protocol type missing', '请填写协议名称' => 'Please fill in the agreement name', '请填写协议内容' => 'Please fill in the content of the agreement', @@ -1098,7 +1094,6 @@ return [ '正在申请退款中' => 'Applying for a refund', '申请失败' => 'Application failed', '退款件数大于订单件数' => 'The number of refunds is greater than the number of orders', - '该订单已全部拆分' => 'The order has been split', '申请退款失败' => 'Failed to apply for refund', '存在待处理退款单' => 'There is a pending refund order', '该订单无法删除' => 'This order cannot be deleted', diff --git a/backend/app/lang/zh_cn.php b/backend/app/lang/zh_cn.php index 66fd48468..eda72c3a4 100644 --- a/backend/app/lang/zh_cn.php +++ b/backend/app/lang/zh_cn.php @@ -624,10 +624,6 @@ return [ '请选择奖品类型' => '请选择奖品类型', '请填写奖品赠送{:type}数' => '请填写奖品赠送{:type}数', '请选择优惠券' => '请选择优惠券', - '生成新订单失败' => '生成新订单失败', - '修改原来订单商品拆分状态失败' => '修改原来订单商品拆分状态失败', - '新增拆分订单商品信息失败' => '新增拆分订单商品信息失败', - '保存新订单商品信息失败' => '保存新订单商品信息失败', '协议类型缺失' => '协议类型缺失', '请填写协议名称' => '请填写协议名称', '请填写协议内容' => '请填写协议内容', @@ -1098,7 +1094,6 @@ return [ '正在申请退款中' => '正在申请退款中', '申请失败' => '申请失败', '退款件数大于订单件数' => '退款件数大于订单件数', - '该订单已全部拆分' => '该订单已全部拆分', '申请退款失败' => '申请退款失败', '存在待处理退款单' => '存在待处理退款单', '该订单无法删除' => '该订单无法删除', diff --git a/backend/app/services/order/OrderCenterServices.php b/backend/app/services/order/OrderCenterServices.php index ac4750c45..e5a15e9bd 100644 --- a/backend/app/services/order/OrderCenterServices.php +++ b/backend/app/services/order/OrderCenterServices.php @@ -1000,7 +1000,7 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac 'order' => 100, ]; - // 105. 关联订单(拆单/换货) + // 105. 关联订单(换货衍生单) $relatedOrders = $detail['related_orders'] ?? null; $sections[] = [ 'key' => 'related_orders', @@ -1466,14 +1466,13 @@ class OrderCenterServices extends BaseService implements OrderCenterSyncInterfac } /** - * 查询关联订单(拆单/换货) + * 查询关联订单(换货等衍生单) + * 系统不支持拆单发货/拆单退款,一个订单不会派生子订单,当前恒为 null * @param array $detail 订单详情 * @return array|null 返回关联订单数组或null */ protected function buildRelatedOrders(array $detail): ?array { - // 当前暂无拆单/换货功能,返回 null - // 后续实现拆单时,按 order_no 前缀或 parent_order_no 查询关联订单 return null; } diff --git a/backend/app/services/order/OrderInvoiceServices.php b/backend/app/services/order/OrderInvoiceServices.php index 87a884a9d..f47ae2710 100644 --- a/backend/app/services/order/OrderInvoiceServices.php +++ b/backend/app/services/order/OrderInvoiceServices.php @@ -262,7 +262,7 @@ class OrderInvoiceServices extends BaseService if (!$orderCenterId || empty($invoice)) { return null; } - // 同一订单已有申请则跳过,避免拆单/重试重复写入 + // 同一订单已有申请则跳过,避免重复写入 if ($this->dao->getOne(['order_id' => $orderCenterId, 'is_del' => 0])) { return null; } @@ -304,50 +304,6 @@ class OrderInvoiceServices extends BaseService return true; } - /** - * 拆分订单同步拆分申请开票记录 - * @param int $oid - * @return bool - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function splitOrderInvoice(int $oid) - { - /** @var OrderCenterDao $orderCenterServices */ - $orderCenterServices = app()->make(OrderCenterDao::class); - $orderInfo = $orderCenterServices->getOne(['id' => $oid, 'is_del' => 0]); - if (!$orderInfo) { - throw new AdminException('订单不存在'); - } - $pid = $orderInfo['pid'] > 0 ? $orderInfo['pid'] : $orderInfo['id']; - //查询开票记录 - $orderInvoice = $this->dao->get(['order_id' => $oid]); - //查询子订单 - $spliteOrder = $orderCenterServices->getColumn(['pid' => $pid, 'is_system_del' => 0], 'id,order_id'); - if ($spliteOrder && $orderInvoice) { - $data = $orderInvoice->toArray(); - unset($data['id']); - $data['add_time'] = strtotime($data['add_time']); - $data_all = []; - foreach ($spliteOrder as $order) { - if (!$this->dao->count(['order_id' => $order['id']])) { - $data['order_id'] = $order['id']; - $data_all[] = $data; - } - } - if ($data_all) { - $this->transaction(function () use ($data_all, $orderInvoice, $orderInfo) { - $this->dao->saveAll($data_all); - if ($orderInfo['pid'] <= 0) { - $this->dao->update(['id' => $orderInvoice['id']], ['is_del' => 1]); - } - }); - } - } - return true; - } - /** * 开具发票 * @param $id diff --git a/backend/app/services/order/OrderLifecycleServices.php b/backend/app/services/order/OrderLifecycleServices.php index fb6ce3cfa..572f5855e 100644 --- a/backend/app/services/order/OrderLifecycleServices.php +++ b/backend/app/services/order/OrderLifecycleServices.php @@ -30,8 +30,6 @@ use app\services\user\UserServices; use app\services\user\member\MemberCardServices; use app\services\order\RefundExecutorServices; use app\services\pay\PayServices; -// @deprecated eb_store_order_status 表已删除 -use app\services\order\OrderTakeServices; use think\facade\Log; /** @@ -313,13 +311,6 @@ class OrderLifecycleServices extends BaseService 'content' => $content, 'add_time' => time(), ]); - - // 检查主订单是否需要修改状态 - if (($order['pid'] ?? 0) > 0) { - /** @var OrderTakeServices $storeOrderTake */ - $storeOrderTake = app()->make(OrderTakeServices::class); - $storeOrderTake->checkMaster($order['pid']); - } } catch (\Throwable $e) { Log::error($e->getMessage()); throw $e; diff --git a/backend/app/services/order/OrderOutServices.php b/backend/app/services/order/OrderOutServices.php index b3c69cee9..6cf2fa9dc 100644 --- a/backend/app/services/order/OrderOutServices.php +++ b/backend/app/services/order/OrderOutServices.php @@ -42,9 +42,10 @@ class OrderOutServices extends BaseService /** * 发货类型 + * 系统不支持拆单/分批发货,此处不声明拆分类型(旧拆分发货已随拆单功能下线) * @var string[] */ - public $deliveryType = ['send' => '商家配送', 'express' => '快递配送', 'fictitious' => '虚拟发货', 'delivery_part_split' => '拆分部分发货', 'delivery_split' => '拆分发货完成']; + public $deliveryType = ['send' => '商家配送', 'express' => '快递配送', 'fictitious' => '虚拟发货']; /** * OrderOutServices constructor. @@ -196,43 +197,6 @@ class OrderOutServices extends BaseService return $order; } - /** - * 格式化订单商品 - * @param array $cartInfo - * @param array $list - * @return array - */ - public function tidyCartList(array $cartInfo, array $list, $cartId = 0): array - { - $list[] = [ - 'cart_id' => $cartId, - 'store_name' => $cartInfo['productInfo']['store_name'] ?? '', - 'suk' => $cartInfo['productInfo']['attrInfo']['suk'] ?? '', - 'image' => $cartInfo['productInfo']['attrInfo']['image'] ?: $cartInfo['productInfo']['image'], - 'price' => sprintf("%.2f", $cartInfo['truePrice'] ?? '0.00'), - 'cart_num' => $cartInfo['cart_num'] ?? 0, - 'surplus_num' => $cartInfo['surplus_num'] ?? 0, - 'refund_num' => $cartInfo['refund_num'] ?? 0 - ]; - return $list; - } - - /** - * 获取订单可以拆分商品信息 - * @param string $orderId 订单号 - * @return array - */ - public function getCartList(string $orderId): array - { - $order = $this->dao->get(['order_id' => $orderId]); - if (!$order) { - throw new ApiException('订单不存在'); - } - - // 从订单中台 extra_info.items 构造可拆分商品列表(旧 cart_info 表已废弃) - return $this->buildOrderItemsFromExtraInfo((string)$order['order_id']); - } - /** * 订单收货 * @param string $orderId 订单号 @@ -291,27 +255,6 @@ class OrderOutServices extends BaseService return $deliveryServices->delivery((int)$orderInfo['id'], $data); } - /** - * 订单拆单发送货 - * @param string $orderId 订单号 - * @param array $data - * @return bool - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function splitDelivery(string $orderId, array $data): bool - { - $orderInfo = $this->dao->get(['order_id' => $orderId]); - if (!$orderInfo) { - throw new ApiException('订单未能查到,不能发货'); - } - - /** @var OrderDeliveryServices $deliveryServices */ - $deliveryServices = app()->make(OrderDeliveryServices::class); - return $deliveryServices->splitDelivery((int)$orderInfo['id'], $data); - } - /** * 设置发票 * @param string $orderId 订单号 @@ -381,8 +324,8 @@ class OrderOutServices extends BaseService /** * 从订单中台 extra_info.items 构造订单商品列表 - * 用于订单列表/详情/拆单等场景(旧订单购物车明细服务已删除) - * 字段映射保持与 tidyCartList 输出一致,refund_num 默认 0(items 中不存储退款数) + * 用于订单列表/详情等场景(旧订单购物车明细服务已删除) + * 字段映射与后台发货列表一致,refund_num 默认 0(items 中不存储退款数) * @param string $orderNo 订单号 * @return array 商品列表数组 */ diff --git a/backend/app/services/order/OrderTakeServices.php b/backend/app/services/order/OrderTakeServices.php index ea21d606e..09fd573f9 100644 --- a/backend/app/services/order/OrderTakeServices.php +++ b/backend/app/services/order/OrderTakeServices.php @@ -723,19 +723,4 @@ class OrderTakeServices extends BaseService return true; } - /** - * 检查主订单是否需要修改状态 - * @param $pid - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\DbException - * @throws \think\db\exception\ModelNotFoundException - */ - public function checkMaster($pid) - { - $p_order = $this->dao->get((int)$pid, ['id,pid,status']); - //主订单全部发货 且子订单没有待收货 有待评价 - if ($p_order['status'] == 1 && !$this->dao->count(['pid' => $pid, 'status' => 2]) && $this->dao->count(['pid' => $pid, 'status' => 3])) { - $this->dao->update($p_order['id'], ['status' => 2]); - } - } } diff --git a/backend/modules/product/normal/controller/admin/OrderController.php b/backend/modules/product/normal/controller/admin/OrderController.php index 53c615457..a1e956357 100644 --- a/backend/modules/product/normal/controller/admin/OrderController.php +++ b/backend/modules/product/normal/controller/admin/OrderController.php @@ -667,34 +667,6 @@ class OrderController extends AuthController } } - /** - * 获取可拆分发货商品列表 - */ - public function getSplitCartInfo(OrderCenterServices $centerServices) - { - $orderNo = $this->request->param('order_no', ''); - if (empty($orderNo)) { - return app('json')->fail('参数错误'); - } - try { - $centerOrder = $centerServices->getOne(['order_no' => $orderNo], 'extra_info'); - if (!$centerOrder) { - return app('json')->fail('订单不存在'); - } - $centerOrder = $centerOrder->toArray(); - - $extraInfo = $centerOrder['extra_info'] ?? []; - if (is_string($extraInfo)) $extraInfo = json_decode($extraInfo, true) ?: []; - // 商品快照:下单时写入 extra_info.items(新架构唯一商品快照来源) - $items = $extraInfo['items'] ?? []; - - return app('json')->success($items); - } catch (\Throwable $e) { - Log::error('[NormalOrder] getSplitCartInfo failed: ' . $e->getMessage()); - return app('json')->fail('获取拆分商品列表失败'); - } - } - /** * 批量发货 * 上传 Excel 文件(列:订单号、快递公司、快递单号),逐条发货 @@ -1447,100 +1419,6 @@ class OrderController extends AuthController } } - /** - * 拆单发货(中台化) - * 将订单中部分商品拆出为独立子订单并单独发货(OrderServices::splitOrderDelivery) - * 基于 extra_info.items 快照整行拆分,金额按比例分摊 - */ - public function splitDelivery(OrderCenterServices $centerServices, OrderServices $orderServices) - { - $orderNo = $this->request->param('order_no', ''); - $deliveryType = $this->request->param('delivery_type', 'express'); - $cartIds = $this->request->param('cart_ids', []); - - if (empty($orderNo)) { - return app('json')->fail('参数错误'); - } - if (empty($cartIds) || !is_array($cartIds)) { - return app('json')->fail('请选择要拆单发货的商品'); - } - - // 校验 cart_ids 格式 - foreach ($cartIds as &$cart) { - if (!isset($cart['cart_id']) || !$cart['cart_id'] || !isset($cart['cart_num']) || !$cart['cart_num']) { - return app('json')->fail('请重新选择发货商品,或填写发货件数'); - } - $cart['cart_id'] = (int)$cart['cart_id']; - $cart['cart_num'] = (int)$cart['cart_num']; - } - - $centerOrder = $centerServices->getOne(['order_no' => $orderNo], 'id, uid, status_code'); - if (!$centerOrder) { - return app('json')->fail('订单不存在'); - } - - try { - // 构建发货数据 - $deliveryData = [ - 'delivery_type' => $deliveryType, - ]; - - switch ($deliveryType) { - case 'express': - $deliveryData['delivery_name'] = $this->request->param('delivery_name', ''); - $deliveryData['delivery_id'] = $this->request->param('delivery_id', ''); - if (empty($deliveryData['delivery_name'])) { - return app('json')->fail('请选择快递公司'); - } - if (empty($deliveryData['delivery_id'])) { - return app('json')->fail('请填写快递单号'); - } - $deliveryName = $deliveryData['delivery_name']; - break; - case 'send': - $deliveryData['delivery_name'] = $this->request->param('sh_delivery_name', ''); - $deliveryData['delivery_id'] = $this->request->param('sh_delivery_id', ''); - if (empty($deliveryData['delivery_name'])) { - return app('json')->fail('请选择送货人'); - } - if (empty($deliveryData['delivery_id'])) { - return app('json')->fail('请输入送货人电话'); - } - $deliveryName = $deliveryData['delivery_name']; - break; - case 'fictitious': - $deliveryData['delivery_name'] = '虚拟发货'; - $deliveryData['delivery_id'] = $this->request->param('fictitious_content', ''); - $deliveryName = '虚拟发货'; - break; - default: - return app('json')->fail('不支持的发货类型'); - } - - // 中台拆单发货:拆出子订单并立即发货(模块内自治实现) - [$childOrderNo] = $orderServices->splitOrderDelivery($orderNo, $cartIds, $deliveryData); - - // 写入订单记录 - /** @var OrderLogDao $logDao */ - $logDao = app()->make(OrderLogDao::class); - $logDao->save([ - 'order_no' => $orderNo, - 'uid' => (int)($centerOrder['uid'] ?? 0), - 'operator_id' => (int)($this->adminInfo['id'] ?? 0), - 'operator_type' => 'admin', - 'from_status' => $centerOrder['status_code'] ?? '', - 'to_status' => $centerOrder['status_code'] ?? '', - 'content' => '管理员拆单发货:' . $deliveryName . ',子订单:' . $childOrderNo, - 'add_time' => time(), - ]); - - return app('json')->success(['child_order_no' => $childOrderNo], '拆单发货成功'); - } catch (\Throwable $e) { - Log::error('[NormalOrder] splitDelivery failed: ' . $e->getMessage()); - return app('json')->fail('拆单发货失败:' . $e->getMessage()); - } - } - /** * 物流查询 * 根据订单号查询物流轨迹 diff --git a/backend/modules/product/normal/route/admin.php b/backend/modules/product/normal/route/admin.php index 43d593a5e..a3ead0061 100644 --- a/backend/modules/product/normal/route/admin.php +++ b/backend/modules/product/normal/route/admin.php @@ -56,8 +56,6 @@ Route::group('normal', function () { Route::get('order/shipment/delivery_persons', [OrderController::class, 'getDeliveryPersons'])->option(['real_name' => '配送员列表']); Route::get('order/shipment/express_temp', [OrderController::class, 'getExpressTemp'])->option(['real_name' => '电子面单模板']); Route::get('order/shipment/sheet_info', [OrderController::class, 'getSheetInfo'])->option(['real_name' => '发件人配置信息']); - Route::get('order/shipment/split_cart_info/:order_no', [OrderController::class, 'getSplitCartInfo'])->option(['real_name' => '可拆单商品列表']); - Route::post('order/split_delivery', [OrderController::class, 'splitDelivery'])->option(['real_name' => '拆单发货']); // 修改配送信息 Route::put('order/update_distribution', [OrderController::class, 'updateDistribution'])->option(['real_name' => '修改配送信息']); // 备注|删除|改价 diff --git a/backend/modules/product/normal/service/OrderServices.php b/backend/modules/product/normal/service/OrderServices.php index 40ad087b0..80ccf3d27 100644 --- a/backend/modules/product/normal/service/OrderServices.php +++ b/backend/modules/product/normal/service/OrderServices.php @@ -208,219 +208,6 @@ class OrderServices extends BaseService ]); } - /** - * 拆单发货(中台化,旧拆单服务已随 eb_store_order_cart_info 表删除) - * 将订单中部分商品拆出为独立子订单并立即发货,原订单保留剩余商品待后续发货 - * 仅支持整行拆分(不支持同商品部分数量拆分,部分数量场景走售后引擎) - * @param string $orderNo 原订单号 - * @param array $cartIds 拆分商品行 [{cart_id, cart_num}],cart_num 须等于快照行数量 - * @param array $deliveryData 发货数据 {delivery_type, delivery_name, delivery_id} - * @return array [子订单号, 原订单号] - */ - public function splitOrderDelivery(string $orderNo, array $cartIds, array $deliveryData): array - { - // 1. 读取中台订单与商品快照 - $centerOrder = $this->centerServices->getOne(['order_no' => $orderNo]); - if (!$centerOrder) { - throw new \RuntimeException('订单不存在'); - } - $centerOrder = $centerOrder->toArray(); - // 仅已支付、未退款/未取消的订单可拆单发货 - if ((int)($centerOrder['pay_status'] ?? 0) !== 1) { - throw new \RuntimeException('订单未支付,不能拆单发货'); - } - if (in_array($centerOrder['status_code'] ?? '', ['refunded', 'canceled', 'after_sale'], true)) { - throw new \RuntimeException('订单当前状态不支持拆单发货'); - } - $extraInfo = $centerOrder['extra_info'] ?? []; - if (is_string($extraInfo)) { - $extraInfo = json_decode($extraInfo, true) ?: []; - } - $items = $extraInfo['items'] ?? []; - if (count($items) < 2) { - throw new \RuntimeException('订单商品不足两行,无需拆单'); - } - - // 2. 整行拆分校验:按 cart_id 匹配快照行,数量必须整行 - $selectMap = []; - foreach ($cartIds as $cart) { - $cartId = (int)($cart['cart_id'] ?? 0); - $cartNum = (int)($cart['cart_num'] ?? 0); - if ($cartId <= 0 || $cartNum <= 0) { - throw new \RuntimeException('拆分商品参数错误'); - } - $matched = null; - foreach ($items as $item) { - if ((int)($item['cart_id'] ?? 0) === $cartId) { - $matched = $item; - break; - } - } - if ($matched === null) { - throw new \RuntimeException('拆分商品不在订单内'); - } - if ($cartNum !== (int)($matched['cart_num'] ?? 0)) { - throw new \RuntimeException('仅支持整行拆分,拆分数量须等于商品数量'); - } - $selectMap[$cartId] = true; - } - - // 3. 快照分组:拆分行 / 剩余行 - $selected = $remain = []; - foreach ($items as $item) { - if (isset($selectMap[(int)($item['cart_id'] ?? 0)])) { - $selected[] = $item; - } else { - $remain[] = $item; - } - } - if (!$selected || !$remain) { - throw new \RuntimeException('拆分后原订单与子订单商品均不能为空'); - } - - // 4. 金额分摊:按商品金额占比拆分,尾差归原订单(保证两单之和等于原单) - $selGoods = $remGoods = '0'; - $selPostage = $remPostage = '0'; - foreach ($selected as $item) { - $selGoods = bcadd($selGoods, bcmul((string)($item['true_price'] ?? 0), (string)($item['cart_num'] ?? 0), 2), 2); - $selPostage = bcadd($selPostage, (string)($item['postage_price'] ?? 0), 2); - } - foreach ($remain as $item) { - $remGoods = bcadd($remGoods, bcmul((string)($item['true_price'] ?? 0), (string)($item['cart_num'] ?? 0), 2), 2); - $remPostage = bcadd($remPostage, (string)($item['postage_price'] ?? 0), 2); - } - $goodsTotal = bcadd($selGoods, $remGoods, 2); - if (bccomp($goodsTotal, '0', 2) <= 0) { - throw new \RuntimeException('订单金额异常,不能拆单'); - } - // 比例分摊的金额列:商品总价/实付/优惠/积分抵扣/会员折/运费(运费按行归集已算出,其余按比例) - $ratioFields = ['total_price', 'pay_price', 'coupon_price', 'deduction_price', 'member_discount', 'vip_discount', 'use_integral_num']; - $childAmounts = []; - foreach ($ratioFields as $field) { - $original = (string)($centerOrder[$field] ?? 0); - $isInt = $field === 'use_integral_num'; - $scale = $isInt ? 0 : 2; - $childVal = $isInt - ? bcmul($original, bcdiv($selGoods, $goodsTotal, 4), 0) - : bcdiv(bcmul($original, $selGoods, 4), $goodsTotal, 2); - $childAmounts[$field] = $childVal; - // 原单金额 = 原值 - 子单值(尾差归原单) - $centerOrder[$field] = $isInt - ? bcsub($original, $childVal, 0) - : bcsub($original, $childVal, 2); - } - // 运费按 items 行归集 - $childAmounts['pay_postage'] = $selPostage; - $centerOrder['pay_postage'] = $remPostage; - // 件数按行数量 - $selNum = array_sum(array_column($selected, 'cart_num')); - $remNum = array_sum(array_column($remain, 'cart_num')); - - // 5. 事务:子订单创建 + 原订单扣减 + 扩展表同步 + 子订单发货 - $childNo = $this->centerServices->getNewOrderId('cp'); - $now = time(); - $this->transaction(function () use ($orderNo, $childNo, $centerOrder, $extraInfo, $selected, $remain, $childAmounts, $selNum, $remNum, $now) { - /** @var \app\dao\order\OrderCenterDao $centerDao */ - $centerDao = app(\app\dao\order\OrderCenterDao::class); - // 5.1 扩展表:复制原扩展行为子订单(发货字段清空,状态待发货) - $extRow = $this->dao->getOne(['order_no' => $orderNo]); - $extData = $extRow ? $extRow->toArray() : []; - unset($extData['id']); - $extData['order_no'] = $childNo; - $extData['product_id'] = (int)($selected[0]['product_id'] ?? 0); - $extData['sku_id'] = $selected[0]['attr_info']['unique'] ?? ($extData['sku_id'] ?? ''); - $extData['pay_price'] = $childAmounts['pay_price']; - $extData['total_price'] = $childAmounts['total_price']; - $extData['status'] = NormalOrderAdapter::EXT_STATUS_PENDING_SHIPPED; - $extData['delivery_name'] = ''; - $extData['delivery_id'] = ''; - $extData['delivery_type'] = ''; - $extData['add_time'] = $now; - $extData['update_time'] = $now; - $childExt = $this->dao->save($extData); - if (!$childExt) { - throw new \RuntimeException('生成子订单扩展记录失败'); - } - - // 5.2 中台表:复制原行为子订单(items=拆分行,金额分摊,发货状态清零) - $childCenter = $centerOrder; - unset($childCenter['id']); - $childCenter['order_no'] = $childNo; - $childCenter['source_id'] = (int)$childExt->id; - foreach ($childAmounts as $field => $val) { - $childCenter[$field] = $val; - } - $childCenter['total_num'] = $selNum; - $childCenter['title'] = $selected[0]['store_name'] ?? ''; - $childCenter['image'] = $selected[0]['image'] ?? ''; - $childCenter['refund_status'] = 0; - $childCenter['refund_price'] = '0.00'; - $childCenter['delivery_time'] = 0; - $childCenter['take_time'] = 0; - $childCenter['refund_time'] = 0; - $childCenter['add_time'] = $now; - // 子单 extra_info:items=拆分行 + 记录拆单来源 - $childExtra = $extraInfo; - $childExtra['items'] = $selected; - $childExtra['sku_info'] = $selected[0]['attr_info'] ?? ($childExtra['sku_info'] ?? []); - $childExtra['split_parent'] = $orderNo; - $childCenter['extra_info'] = json_encode($childExtra, JSON_UNESCAPED_UNICODE); - if (!$centerDao->save($childCenter)) { - throw new \RuntimeException('生成子订单失败'); - } - - // 5.3 中台表:原订单扣减(items=剩余行 + 金额 + 标题) - $parentExtra = $extraInfo; - $parentExtra['items'] = $remain; - $parentExtra['sku_info'] = $remain[0]['attr_info'] ?? ($parentExtra['sku_info'] ?? []); - // 拆单血缘记录(追加,不覆盖既有子单) - $children = $parentExtra['split_children'] ?? []; - $children[] = $childNo; - $parentExtra['split_children'] = $children; - $centerDao->update(['order_no' => $orderNo], [ - 'total_price' => $centerOrder['total_price'], - 'pay_price' => $centerOrder['pay_price'], - 'pay_postage' => $centerOrder['pay_postage'], - 'coupon_price' => $centerOrder['coupon_price'], - 'deduction_price'=> $centerOrder['deduction_price'], - 'member_discount'=> $centerOrder['member_discount'], - 'vip_discount' => $centerOrder['vip_discount'], - 'use_integral_num'=> $centerOrder['use_integral_num'], - 'total_num' => $remNum, - 'title' => $remain[0]['store_name'] ?? '', - 'image' => $remain[0]['image'] ?? '', - 'extra_info' => json_encode($parentExtra, JSON_UNESCAPED_UNICODE), - ]); - - // 5.4 扩展表:原订单金额同步 - $this->dao->update(['order_no' => $orderNo], [ - 'pay_price' => $centerOrder['pay_price'], - 'total_price' => $centerOrder['total_price'], - ]); - }); - - // 6. 子订单发货(复用统一发货落库:扩展表状态+中台 delivery/extra_info.delivery/delivery_time) - $this->updateDeliveryInfo($childNo, (string)($deliveryData['delivery_name'] ?? ''), (string)($deliveryData['delivery_id'] ?? ''), (string)($deliveryData['delivery_type'] ?? 'express')); - - // 7. 双订单日志 - /** @var \app\dao\order\OrderLogDao $logDao */ - $logDao = app(\app\dao\order\OrderLogDao::class); - $uid = (int)($centerOrder['uid'] ?? 0); - $statusCode = (string)($centerOrder['status_code'] ?? ''); - $logDao->save([ - 'order_no' => $orderNo, 'uid' => $uid, 'operator_id' => 0, 'operator_type' => 'admin', - 'from_status' => $statusCode, 'to_status' => $statusCode, - 'content' => '拆单发货:拆出子订单 ' . $childNo . '(' . $selNum . ' 件)', 'add_time' => $now, - ]); - $logDao->save([ - 'order_no' => $childNo, 'uid' => $uid, 'operator_id' => 0, 'operator_type' => 'admin', - 'from_status' => $statusCode, 'to_status' => $statusCode, - 'content' => '拆单发货(来源订单 ' . $orderNo . '):' . ($deliveryData['delivery_name'] ?? ''), 'add_time' => $now, - ]); - - return [$childNo, $orderNo]; - } - // ===== 价格 ===== /** diff --git a/help/docs/api/管理端接口.md b/help/docs/api/管理端接口.md index 67ef8fa99..9cf63fec4 100644 --- a/help/docs/api/管理端接口.md +++ b/help/docs/api/管理端接口.md @@ -174,7 +174,7 @@ AllowOriginMiddleware 统一订单入口位于 `center.php`/`order.php`: - 订单列表、详情和各订单类型扩展。 -- 订单备注、状态、发货、拆单、配送和收货。 +- 订单备注、状态、发货、配送和收货。 - 订单打印、导出和发票。 - 售后列表、详情、审核、退货和退款。 diff --git a/help/docs/项目功能.md b/help/docs/项目功能.md index 5e037a222..1ceee942f 100644 --- a/help/docs/项目功能.md +++ b/help/docs/项目功能.md @@ -49,7 +49,7 @@ - 订单中台统一列表和详情。 - 按订单号、用户、状态、来源、支付和时间筛选。 - 订单备注、改价、取消、删除和打印。 -- 发货、拆单发货、配送信息修改和物流查询。 +- 发货、配送信息修改和物流查询。 - 用户确认收货与后台确认收货。 - 订单商品快照、状态日志和操作记录。 - 不同 `source_type` 的订单状态、按钮和详情扩展。 @@ -146,7 +146,7 @@ - 运费模板、指定包邮、不配送区域和区域运费。 - 快递公司与物流查询。 - 电子面单公司、模板、业务配置和打印记录。 -- 订单发货、拆单发货、物流轨迹和收货。 +- 订单发货、物流轨迹和收货。 - 云打印渠道、打印机和小票配置。 ### 2.11 财务 @@ -367,7 +367,7 @@ App/H5: ### 4.3 发货与收货 -1. 管理员在订单中台选择整单或拆单发货。 +1. 管理员在订单中台整单发货(系统不支持拆单/分批发货)。 2. 选择快递/配送方式并写入单号和商品明细。 3. 可调用电子面单和打印驱动。 4. 通知用户并记录订单日志。