mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 21:40:50 +00:00
- 列表/统计/导出口径与中台一致:常规排除双标记,已删除只列 用户已删且后台未删的订单 - 删除去掉状态门槛,改由中台守卫(用户已删)拦截,ApiException 消息透出(原先被吞成「删除失败」) - 修复 softDelete 两处死代码:centerServices->get 传 string 撞 BaseDao ?array 严格类型;扩展表 eb_pt_normal_order 无 is_del 列, 源表同步移除,删除标记以中台为唯一真源 - 前端删除入口只在「已删除」页签展示,批量发货反之;确认文案与 导出参数同步对齐
630 lines
26 KiB
PHP
630 lines
26 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||
// +----------------------------------------------------------------------
|
||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||
// +----------------------------------------------------------------------
|
||
// | Author: CRMEB Team <admin@crmeb.com>
|
||
// +----------------------------------------------------------------------
|
||
declare(strict_types=1);
|
||
|
||
namespace modules\product\normal\service;
|
||
|
||
use app\services\order\OrderCenterServices;
|
||
use crmeb\basic\BaseService;
|
||
use modules\product\normal\dao\NormalOrderDao;
|
||
use modules\product\normal\handler\NormalOrderAdapter;
|
||
|
||
/**
|
||
* 实物商品订单服务
|
||
* 统一管理扩展表(eb_pt_normal_order)与中台表(eb_order_center)的同步
|
||
* 所有写操作自动保证两表一致性
|
||
*/
|
||
class OrderServices extends BaseService
|
||
{
|
||
/**
|
||
* 订单扩展表 DAO
|
||
*/
|
||
protected $dao;
|
||
|
||
/**
|
||
* 中台订单服务
|
||
*/
|
||
protected OrderCenterServices $centerServices;
|
||
|
||
public function __construct(NormalOrderDao $dao)
|
||
{
|
||
$this->dao = $dao;
|
||
$this->centerServices = app(OrderCenterServices::class);
|
||
}
|
||
|
||
// ===== 查询 =====
|
||
|
||
/**
|
||
* 订单列表(分页,查扩展表)
|
||
* @param array $where
|
||
* @return array ['list' => [], 'count' => int]
|
||
*/
|
||
public function getList(array $where): array
|
||
{
|
||
$page = max(1, (int)($where['page'] ?? 1));
|
||
$limit = max(1, (int)($where['limit'] ?? 20));
|
||
|
||
$condition = [];
|
||
if (!empty($where['order_no'])) {
|
||
$condition['order_no'] = $where['order_no'];
|
||
}
|
||
if (!empty($where['status'])) {
|
||
$condition['status'] = $where['status'];
|
||
}
|
||
if (!empty($where['real_name'])) {
|
||
$condition['real_name'] = $where['real_name'];
|
||
}
|
||
|
||
$list = $this->dao->selectList($condition, '*', $page, $limit, 'id DESC');
|
||
$count = $this->dao->count($condition);
|
||
|
||
return compact('list', 'count');
|
||
}
|
||
|
||
/**
|
||
* 获取订单扩展信息
|
||
* @param string $orderNo
|
||
* @return array
|
||
*/
|
||
public function getExtendInfo(string $orderNo): array
|
||
{
|
||
$info = $this->dao->getOne(['order_no' => $orderNo]);
|
||
return $info ? $info->toArray() : [];
|
||
}
|
||
|
||
/**
|
||
* 从扩展表获取单条订单数据
|
||
* @param array $where
|
||
* @param string $field
|
||
* @return array|null
|
||
*/
|
||
public function getOne(array $where, string $field = '*'): ?array
|
||
{
|
||
$info = $this->dao->getOne($where, $field);
|
||
return $info ? $info->toArray() : null;
|
||
}
|
||
|
||
// ===== 创建 =====
|
||
|
||
/**
|
||
* 创建订单扩展记录
|
||
* @param string $orderNo
|
||
* @param int $productId
|
||
* @return void
|
||
*/
|
||
public function createOrder(string $orderNo, int $productId): void
|
||
{
|
||
$this->dao->save([
|
||
'order_no' => $orderNo,
|
||
'product_id' => $productId,
|
||
'status' => NormalOrderAdapter::EXT_STATUS_PENDING_PAYMENT,
|
||
]);
|
||
}
|
||
|
||
// ===== 状态同步(扩展表 + 中台表) =====
|
||
|
||
/**
|
||
* 更新订单扩展表状态,并同步中台表 status_code
|
||
* @param string $orderNo 订单号
|
||
* @param string $status 扩展表状态常量(NormalOrderAdapter::EXT_STATUS_*)
|
||
* @param string $centerStatusCode 中台状态码(OrderTypeAdapterInterface::STATUS_*)
|
||
* @param array $extraCenterFields 额外中台字段(如 ['pay_time' => time()])
|
||
* @return void
|
||
*/
|
||
public function updateStatus(string $orderNo, string $status, string $centerStatusCode = '', array $extraCenterFields = []): void
|
||
{
|
||
// 更新扩展表
|
||
$this->dao->update(['order_no' => $orderNo], ['status' => $status]);
|
||
|
||
// 同步中台表
|
||
if ($centerStatusCode) {
|
||
$centerData = array_merge(['status_code' => $centerStatusCode], $extraCenterFields);
|
||
$this->centerServices->updateRecord($orderNo, $centerData);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 只同步中台表数据(不修改扩展表)
|
||
* @param string $orderNo
|
||
* @param array $data
|
||
* @return void
|
||
*/
|
||
public function syncCenter(string $orderNo, array $data): void
|
||
{
|
||
$this->centerServices->updateRecord($orderNo, $data);
|
||
}
|
||
|
||
// ===== 配送 =====
|
||
|
||
/**
|
||
* 更新配送信息(扩展表 + 中台表同步)
|
||
* 同时更新扩展表状态为待收货,同步中台 delivery_time
|
||
* @param string $orderNo
|
||
* @param string $deliveryName 快递公司/配送员
|
||
* @param string $deliveryId 快递单号/电话
|
||
* @param string $deliveryType 配送方式(express/send/fictitious)
|
||
* @return void
|
||
*/
|
||
public function updateDeliveryInfo(string $orderNo, string $deliveryName, string $deliveryId, string $deliveryType = 'express'): void
|
||
{
|
||
// 更新扩展表配送信息 + 状态
|
||
$this->dao->update(['order_no' => $orderNo], [
|
||
'delivery_name' => $deliveryName,
|
||
'delivery_id' => $deliveryId,
|
||
'delivery_type' => $deliveryType,
|
||
'status' => NormalOrderAdapter::EXT_STATUS_PENDING_RECEIVE,
|
||
]);
|
||
|
||
// 同步中台表 extra_info.delivery + delivery_time
|
||
$centerOrder = $this->centerServices->getOne(['order_no' => $orderNo], 'extra_info');
|
||
$extraInfo = $centerOrder['extra_info'] ?? [];
|
||
if (is_string($extraInfo)) {
|
||
$extraInfo = json_decode($extraInfo, true) ?: [];
|
||
}
|
||
$extraInfo['delivery'] = [
|
||
'delivery_type' => $deliveryType,
|
||
'delivery_name' => $deliveryName,
|
||
'delivery_id' => $deliveryId,
|
||
];
|
||
$this->centerServices->updateRecord($orderNo, [
|
||
'extra_info' => json_encode($extraInfo, JSON_UNESCAPED_UNICODE),
|
||
'delivery_time' => time(),
|
||
]);
|
||
|
||
// 普通发货路径不经过 OrderDeliveryServices::doDelivery,落库后补发统一发货事件。
|
||
$eventOrder = $this->centerServices->getOne(['order_no' => $orderNo]);
|
||
$eventOrder = $eventOrder ? (is_object($eventOrder) ? $eventOrder->toArray() : (array)$eventOrder) : [];
|
||
$eventOrder['order_id'] = (string)($eventOrder['order_no'] ?? $orderNo);
|
||
$eventExtraInfo = $eventOrder['extra_info'] ?? $extraInfo;
|
||
if (is_string($eventExtraInfo)) {
|
||
$eventExtraInfo = json_decode($eventExtraInfo, true) ?: [];
|
||
}
|
||
$storeNames = [];
|
||
foreach (($eventExtraInfo['items'] ?? []) as $item) {
|
||
$name = (string)($item['store_name'] ?? '');
|
||
if ($name !== '') {
|
||
$storeNames[] = $name;
|
||
}
|
||
}
|
||
$storeName = implode('|', $storeNames) ?: (string)($eventOrder['title'] ?? '');
|
||
$eventType = $deliveryType === 'send' ? 'deliver' : ($deliveryType === 'express' ? 'postage' : 'virtual');
|
||
event('OrderDelivery', [
|
||
$eventOrder,
|
||
$storeName,
|
||
[
|
||
'delivery_name' => $deliveryName,
|
||
'delivery_id' => $deliveryId,
|
||
'delivery_type' => $deliveryType,
|
||
],
|
||
$eventType,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 拆单发货(中台化,旧拆单服务已随 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];
|
||
}
|
||
|
||
// ===== 价格 =====
|
||
|
||
/**
|
||
* 更新订单价格(扩展表 + 中台表同步)
|
||
* @param string $orderNo
|
||
* @param float $totalPrice 商品总价
|
||
* @param float $payPrice 实际支付
|
||
* @param float $payPostage 邮费
|
||
* @return void
|
||
*/
|
||
public function updatePrice(string $orderNo, float $totalPrice, float $payPrice, float $payPostage = 0): void
|
||
{
|
||
// 记录改价前实付金额,用于派发事件时计算差额
|
||
$oldOrder = $this->centerServices->getOne(['order_no' => $orderNo], 'pay_price');
|
||
$oldPayPrice = (float)(is_array($oldOrder) ? ($oldOrder['pay_price'] ?? 0) : ($oldOrder?->pay_price ?? 0));
|
||
|
||
$priceData = [
|
||
'total_price' => $totalPrice,
|
||
'pay_price' => $payPrice,
|
||
'pay_postage' => $payPostage,
|
||
];
|
||
|
||
// 更新扩展表
|
||
$this->dao->update(['order_no' => $orderNo], $priceData);
|
||
|
||
// 同步中台表
|
||
$this->centerServices->updateRecord($orderNo, $priceData);
|
||
|
||
// 改价完成后派发事件:记录改价日志 + 提醒用户按新价格支付(监听器内部自行隔离失败)
|
||
try {
|
||
$centerOrder = $this->centerServices->getOne(['order_no' => $orderNo]);
|
||
$order = $centerOrder ? (is_array($centerOrder) ? $centerOrder : $centerOrder->toArray()) : [];
|
||
$order['order_id'] = $orderNo;
|
||
$order['change_price'] = (string)round($oldPayPrice - $payPrice, 2); // 差额(正=降价,负=涨价)
|
||
event('OrderPriceChange', [$order, $payPrice]);
|
||
} catch (\Throwable $e) {
|
||
\think\facade\Log::error('派发 OrderPriceChange 事件失败:' . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
// ===== 地址 =====
|
||
|
||
/**
|
||
* 更新收货地址(扩展表 + 中台表 extra_info.contact 同步)
|
||
* @param string $orderNo
|
||
* @param string $realName 收货人姓名
|
||
* @param string $phone 收货人电话
|
||
* @param string $address 收货地址
|
||
* @return void
|
||
*/
|
||
public function updateAddress(string $orderNo, string $realName, string $phone, string $address): void
|
||
{
|
||
// 更新扩展表
|
||
$this->dao->update(['order_no' => $orderNo], [
|
||
'real_name' => $realName,
|
||
'user_phone' => $phone,
|
||
'user_address' => $address,
|
||
]);
|
||
|
||
// 同步中台表 extra_info.contact
|
||
$centerOrder = $this->centerServices->getOne(['order_no' => $orderNo], 'extra_info');
|
||
$extraInfo = $centerOrder['extra_info'] ?? [];
|
||
if (is_string($extraInfo)) {
|
||
$extraInfo = json_decode($extraInfo, true) ?: [];
|
||
}
|
||
$parts = explode(' ', $address, 2);
|
||
$extraInfo['contact'] = [
|
||
'name' => $realName,
|
||
'phone' => $phone,
|
||
'address' => $address,
|
||
'address_obj' => [
|
||
'detail' => $parts[1] ?? $parts[0] ?? '',
|
||
],
|
||
];
|
||
$this->centerServices->updateRecord($orderNo, [
|
||
'extra_info' => json_encode($extraInfo, JSON_UNESCAPED_UNICODE),
|
||
]);
|
||
}
|
||
|
||
// ===== 积分 =====
|
||
|
||
/**
|
||
* 更新订单赠送积分信息(仅同步中台表 extra_info)
|
||
* @param string $orderNo
|
||
* @param int $giveIntegral 赠送积分数
|
||
* @return void
|
||
*/
|
||
public function updateIntegral(string $orderNo, int $giveIntegral): void
|
||
{
|
||
$centerOrder = $this->centerServices->getOne(['order_no' => $orderNo], 'extra_info');
|
||
$extraInfo = $centerOrder['extra_info'] ?? [];
|
||
if (is_string($extraInfo)) {
|
||
$extraInfo = json_decode($extraInfo, true) ?: [];
|
||
}
|
||
$extraInfo['give_integral'] = $giveIntegral;
|
||
$this->centerServices->updateRecord($orderNo, [
|
||
'extra_info' => json_encode($extraInfo, JSON_UNESCAPED_UNICODE),
|
||
]);
|
||
}
|
||
|
||
// ===== 软删除 =====
|
||
|
||
/**
|
||
* 软删除订单(中台 is_system_del = 1)
|
||
* 中台 delete 守卫要求用户已删,仅用于后台清理用户删除过的订单;
|
||
* 扩展表 eb_pt_normal_order 无删除列,删除标记以中台为唯一真源
|
||
* @param int $id 中台表 id
|
||
* @return void
|
||
*/
|
||
public function softDelete(int $id): void
|
||
{
|
||
$this->centerServices->delete($id);
|
||
}
|
||
|
||
/**
|
||
* 批量软删除订单
|
||
* @param array $ids 中台表 id 数组
|
||
* @return void
|
||
*/
|
||
public function batchSoftDelete(array $ids): void
|
||
{
|
||
foreach ($ids as $id) {
|
||
$this->softDelete((int)$id);
|
||
}
|
||
}
|
||
|
||
// ===== 确认收货 =====
|
||
|
||
/**
|
||
* 确认收货(扩展表状态 + 中台表状态同步)
|
||
* @param string $orderNo
|
||
* @return void
|
||
*/
|
||
public function confirmReceipt(string $orderNo): void
|
||
{
|
||
$extend = $this->dao->getOne(['order_no' => $orderNo]);
|
||
if (!$extend) {
|
||
throw new \RuntimeException('订单不存在');
|
||
}
|
||
if (($extend['status'] ?? '') !== NormalOrderAdapter::EXT_STATUS_PENDING_RECEIVE) {
|
||
throw new \RuntimeException('订单尚未发货或已确认收货');
|
||
}
|
||
|
||
// 后台确认收货与用户端共用安装分流:无安装进入待评价,有安装进入待安装。
|
||
$targetStatus = !empty($extend['has_installation'])
|
||
? NormalOrderAdapter::EXT_STATUS_PENDING_INSTALL
|
||
: NormalOrderAdapter::EXT_STATUS_PENDING_REVIEW;
|
||
NormalOrderAdapter::instance()->advance($orderNo, $targetStatus);
|
||
$this->centerServices->updateRecord($orderNo, ['take_time' => time()]);
|
||
}
|
||
|
||
// ===== 支付确认(线下支付) =====
|
||
|
||
/**
|
||
* 确认线下支付
|
||
* @param string $orderNo
|
||
* @return void
|
||
*/
|
||
public function confirmOfflinePay(string $orderNo): void
|
||
{
|
||
$this->dao->update(['order_no' => $orderNo], [
|
||
'paid' => 1,
|
||
'status' => NormalOrderAdapter::EXT_STATUS_PENDING_SHIPPED,
|
||
]);
|
||
$this->centerServices->syncStatus($orderNo, 'processing', ['pay_time' => time(), 'pay_status' => 1, 'pay_type' => 'offline']);
|
||
}
|
||
|
||
// ===== 配送信息修改(已发货订单) =====
|
||
|
||
/**
|
||
* 修改已发货订单的配送信息(不改状态,仅更新快递公司/快递单号)
|
||
* 扩展表 + 中台表 extra_info.delivery 同步
|
||
* @param string $orderNo 订单号
|
||
* @param string $deliveryName 快递公司名称
|
||
* @param string $deliveryId 快递单号
|
||
* @param string $deliveryCode 快递公司编码
|
||
* @return void
|
||
*/
|
||
public function updateDistribution(string $orderNo, string $deliveryName, string $deliveryId, string $deliveryCode = ''): void
|
||
{
|
||
// 更新扩展表配送信息
|
||
$extData = [
|
||
'delivery_name' => $deliveryName,
|
||
'delivery_id' => $deliveryId,
|
||
];
|
||
if ($deliveryCode) {
|
||
$extData['delivery_code'] = $deliveryCode;
|
||
}
|
||
$this->dao->update(['order_no' => $orderNo], $extData);
|
||
|
||
// 同步中台表 extra_info.delivery
|
||
$centerOrder = $this->centerServices->getOne(['order_no' => $orderNo], 'extra_info');
|
||
$extraInfo = $centerOrder['extra_info'] ?? [];
|
||
if (is_string($extraInfo)) {
|
||
$extraInfo = json_decode($extraInfo, true) ?: [];
|
||
}
|
||
$extraInfo['delivery'] = array_merge($extraInfo['delivery'] ?? [], [
|
||
'delivery_name' => $deliveryName,
|
||
'delivery_id' => $deliveryId,
|
||
]);
|
||
if ($deliveryCode) {
|
||
$extraInfo['delivery']['delivery_code'] = $deliveryCode;
|
||
}
|
||
$this->centerServices->updateRecord($orderNo, [
|
||
'extra_info' => json_encode($extraInfo, JSON_UNESCAPED_UNICODE),
|
||
]);
|
||
}
|
||
}
|