fix(shopro): 优化到店自提核销码生成与过期判断

到店自提:支付成功后为实物商品自动生成核销码,无需依赖门店手动发货后再出码。

自提模板过期:固定截至日期已过期时不再写入 expiretime,避免核销码创建即失效。

核销状态:expiretime 为 0 或空视为未过期;CanUse 查询与后台模型状态判断保持一致。

纳入版本库:addons 下 OrderOperSendGet、Verify 相关改动(原被 .gitignore 忽略,使用 git add -f 跟踪)。

Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
可乐加冰
2026-09-23 22:42:10 +08:00
父节点 54e2d6eec9
当前提交 eb31a121f6
共有 3 个文件被更改,包括 500 次插入 和 2 次删除

查看文件

@@ -0,0 +1,390 @@
<?php
namespace addons\shopro\library\traits\model\order;
use addons\shopro\exception\Exception;
use addons\shopro\model\Dispatch;
use addons\shopro\model\DispatchAutosend;
use addons\shopro\model\DispatchSelfetch;
use addons\shopro\model\DispatchStore;
use addons\shopro\model\Goods;
use addons\shopro\model\Verify;
use addons\shopro\model\User;
use addons\shopro\model\Order;
use addons\shopro\model\OrderAction;
use addons\shopro\model\OrderItem;
use addons\shopro\model\Store;
use think\Cache;
use think\Db;
trait OrderOperSendGet
{
/**
* 支付完成检测并发货,检测拼团是否成功(grouponCheckAndSend),比这个先执行
*/
public function checkDispatchAndSend($order, $params)
{
$user = $params['user'] ?? null;
if (!$user) {
$user = User::where('id', $order['user_id'])->find();
}
// 拼团不自动发货,等成团完成才发货
$orderExt = $order['ext_arr'];
$buy_type = ($orderExt && isset($orderExt['buy_type'])) ? $orderExt['buy_type'] : '';
if (strpos($order['activity_type'], 'groupon') !== false && $buy_type == 'groupon') {
// 拼团订单,并且是拼团购买(拼团不能加入购物车,一个订单只能有一个商品),自动发货要等到拼团成功才进行
return true;
}
// 检测需要自动发货的 item
$this->systemCheckAutoSend($order);
return true;
}
/**
* 拼团完成,检测需要自动发货的商品,并发货(执行的是被 checkDispatchAndSend 方法 过滤掉的拼团的自动发货订单)
*/
public function grouponCheckAndSend($order)
{
$this->systemCheckAutoSend($order);
return true;
}
/**
* 系统检测自动发货
*/
private function systemCheckAutoSend($order)
{
// 判断订单是否有需要发货的商品,并进行自动发货(selfetch虚拟自提,autosend)
foreach ($order->item as $key => $item) {
// 判断不是未发货状态,或者退款完成,continue
if (
$item['dispatch_status'] != \addons\shopro\model\OrderItem::DISPATCH_STATUS_NOSEND
|| in_array($item['refund_status'], [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH])
) {
// 订单已发货,或者已完成退款
continue;
}
switch ($item['dispatch_type']) {
case 'selfetch':
// 到店自提:支付成功后自动生成核销码(含实物商品)
$this->selfetchSendItem($order, $item, ['oper_type' => 'system']);
break;
case 'autosend':
// 自动发货
$this->autoSendItem($order, $item, ['oper_type' => 'system']);
}
}
}
/**
* 门店订单发货
*/
public function storeOrderSend($order)
{
$store = Store::info();
foreach ($order['item'] as $key => $item) {
switch ($item['dispatch_type']) {
case 'selfetch':
$this->selfetchSendItem($order, $item, ['oper_type' => 'store', 'oper' => $store]);
break;
case 'store':
// 商家配送
$this->storeSendItem($order, $item, ['oper_type' => 'store', 'oper' => $store]);
}
}
}
/**
* admin 门店订单发货
*/
public function adminStoreOrderSend($order, $items, $data)
{
foreach ($items as $key => $item) {
switch ($item['dispatch_type']) {
case 'selfetch':
$this->selfetchSendItem($order, $item, $data);
break;
case 'store':
// 商家配送
$this->storeSendItem($order, $item, $data);
}
}
}
/**
* autosend 单商品自动发货
*/
private function autoSendItem($order, $item, $data = [])
{
// 获取配送模板
$dispatch = Dispatch::where('type', $item['dispatch_type'])->where('id', $item['dispatch_id'])->find();
$dispatch_autosend_ids = explode(',', $dispatch->type_ids);
$dispatchAutosend = DispatchAutosend::where('id', 'in', $dispatch_autosend_ids)
->order('id', 'asc')->find();
$ext = [];
if ($dispatchAutosend) {
if (in_array($dispatchAutosend['type'], ['text', 'params'])) {
$ext['autosend_type'] = $dispatchAutosend['type'];
$autosend_content = $dispatchAutosend['content'];
if ($dispatchAutosend['type'] == 'params') {
$autosend_content = DispatchAutosend::getParamsContent($autosend_content);
}
$ext['autosend_content'] = $autosend_content;
} else if ($dispatchAutosend['type'] == 'card') {
// 电子卡密,待补充
}
}
$this->sendItem($order, $item, [
'ext' => $ext,
"oper_type" => $data['oper_type'],
]);
}
/**
* selfetch 自提/到店 单商品发货
*/
private function selfetchSendItem($order, $item, $data = [])
{
// 获取配送模板
$dispatch = Dispatch::where('type', $item['dispatch_type'])->where('id', $item['dispatch_id'])->find();
$type_ids = $dispatch['type_ids'] ?? '';
$dispatch_selfetch_ids = explode(',', $type_ids);
$dispatchSelfetch = DispatchSelfetch::where('id', 'in', $dispatch_selfetch_ids)
->order('id', 'asc')->find();
$expiretime = null; // 核销券过期时间
if ($dispatchSelfetch) {
if ($dispatchSelfetch['expire_type'] == 'day') {
$expire_day = $dispatchSelfetch['expire_day'] > 0 ? $dispatchSelfetch['expire_day'] : 0;
$expiretime = $expire_day ? (time() + ($expire_day * 86400)) : null;
} else {
$expiretime = (int)$dispatchSelfetch['expire_time'] ?: null;
// 固定截至日期已过期时,不写入过期时间,避免核销码一生成即失效
if ($expiretime && $expiretime <= time()) {
$expiretime = null;
}
}
}
if ($item['goods_type'] == 'virtual') {
// 商品购买多件,自动生成多个码
for ($i = 0; $i < $item['goods_num']; $i++) {
$this->createVerify($order, $item, [
'expiretime' => $expiretime
]);
}
} else {
// 商品购买多件,只生成一个码
$this->createVerify($order, $item, [
'expiretime' => $expiretime
]);
}
$this->sendItem($order, $item, [
"oper_type" => $data['oper_type'],
]);
}
/**
* store 商家配送 单商品发货
*/
private function storeSendItem($order, $item, $data = [])
{
// 获取配送模板
$dispatch = Dispatch::where('type', $item['dispatch_type'])->where('id', $item['dispatch_id'])->find();
$type_ids = $dispatch['type_ids'] ?? '';
$dispatch_store_ids = explode(',', $type_ids);
$dispatchStore = DispatchStore::where('id', 'in', $dispatch_store_ids)
->order('id', 'asc')->find();
$expiretime = null; // 核销券过期时间
if ($dispatchStore) {
// 可以干一些事
}
$this->sendItem($order, $item, [
"oper_type" => $data['oper_type'],
]);
}
// 发货
public function sendItem($order, $item, $data = [])
{
// 订单发货前
$hookData = ['order' => $order, 'item' => $item];
\think\Hook::listen('order_send_before', $hookData);
$item->express_name = $data['express_name'] ?? null;
$item->express_code = $data['express_code'] ?? null;
$item->express_no = $data['express_no'] ?? null;
$ext = ['send_time' => time()];
if (isset($data['ext']) && $data['ext']) {
$ext = array_merge($ext, $data['ext']);
}
$item->ext = json_encode($item->setExt($item, $ext));
$item->dispatch_status = \addons\shopro\model\OrderItem::DISPATCH_STATUS_SENDED; // 已发货状态
$item->save();
$oper_type = $data['oper_type'] ?? 'system';
$oper = $data['oper'] ?? null;
extract($this->getOper($oper_type, $oper));
\addons\shopro\model\OrderAction::operAdd($order, $item, $oper, $oper_type, $oper_iden . '发货订单');
// 订单发货后
$hookData = ['order' => $order, 'item' => $item];
\think\Hook::listen('order_send_after', $hookData);
return $item;
}
/**
* 核销二维码并确认收货
*/
public function verifyGeted($order, $item, $verify, $data = [])
{
// 获取操作人
$oper_type = $data['oper_type'] ?? 'system';
$oper = $data['oper'] ?? null;
extract($this->getOper($oper_type, $oper));
// 使用掉核销券
$verify->usetime = time();
$verify->oper_type = $oper_type;
$verify->oper_id = $oper ? $oper['id'] : 0;
$verify->save();
// 查询当前订单 item 所有核销券
$verifiy_num = Verify::canUse()->where('order_item_id', $item['id'])
->where('type', 'verify')
->count();
// 判断所有核销码全部核销才能确认收货
if (!$verifiy_num) {
// 没有可用优惠券,订单收货
$this->getedItem($order, $item, $data);
}
}
/**
* 收货订单
*/
public function getedItem($order, $item, $data = [])
{
// 订单确认收货前
$hookData = ['order' => $order, 'item' => $item];
\think\Hook::listen('order_confirm_before', $hookData);
$ext = ['confirm_time' => time()];
if (isset($data['ext']) && $data['ext']) {
$ext = array_merge($ext, $data['ext']);
}
$item->ext = json_encode($item->setExt($item, $ext));
$item->dispatch_status = OrderItem::DISPATCH_STATUS_GETED; // 确认收货
$item->save();
$oper_type = $data['oper_type'] ?? 'system';
$oper = $data['oper'] ?? null;
extract($this->getOper($oper_type, $oper));
OrderAction::operAdd($order, $item, $oper, $oper_type, $oper_iden . '确认收货');
// 订单确认收货后
$hookData = ['order' => $order, 'item' => $item];
\think\Hook::listen('order_confirm_after', $hookData);
return $order;
}
/**
* 生成码
*/
private function createVerify($order, $item = null, $data = [])
{
$verify = new Verify();
$verify->user_id = $order['user_id'];
$verify->type = 'verify';
$verify->order_id = $order['id'];
$verify->order_item_id = $item['id'] ?? 0;
$verify->expiretime = $data['expiretime'];
$i = 0;
do {
// 循环生成唯一核销码
$is_error = false;
try {
$verify->code = Verify::getCode();
$verify->save();
} catch (\think\exception\PDOException $e) {
if ($e->getCode() == '10501') {
$is_error = true;
}
$i++;
if ($i > 5) {
if (strpos(request()->url(), 'addons') !== false) {
// 接口响应
new Exception('核销码生成失败');
} else {
// 后台响应
throw new \Exception('核销码生成失败');
}
}
}
} while ($is_error);
}
/**
* 根据 oper_type 获取对应的用户
*/
private function getOper($oper_type, $origin_oper = null)
{
$oper = null;
$oper_iden = '系统自动';
if ($oper_type == 'system') {
// 系统自动操作
$oper = null;
$oper_iden = '系统自动';
} else if ($oper_type == 'user') {
// 用户操作
$oper = $origin_oper ?: User::info();
$oper_iden = '用户';
} else if ($oper_type == 'admin') {
// 管理员操作
$oper = $origin_oper ?: null;
$oper_iden = '管理员';
} else if ($oper_type == 'store') {
// 门店管理员操作
$oper = $origin_oper ?: Store::info();
$oper_iden = '门店管理员';
}
return compact("oper", "oper_iden");
}
}

查看文件

@@ -0,0 +1,108 @@
<?php
namespace addons\shopro\model;
use think\Model;
use addons\shopro\exception\Exception;
use think\Db;
/**
* 核销码
*/
class Verify extends Model
{
// 表名,不含前缀
protected $name = 'shopro_verify';
// 自动写入时间戳字段
protected $autoWriteTimestamp = 'int';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = 'updatetime';
// 追加属性
protected $append = [
'type_name',
'status_code',
'status_name'
];
public static function getCode() {
return mt_rand(1000000000, 9999999999);
}
public function getTypeList() {
return [
'verify' => '核销券'
];
}
public function scopeCanUse($query) {
return $query->where('usetime', null)->where(function($query) {
$query->where('expiretime', null)->whereOr('expiretime', 0)->whereOr('expiretime', '>', time());
});
}
public function getTypeNameAttr($value, $data) {
$value = isset($data['type']) ? $data['type'] : '';
$list = $this->getTypeList();
return isset($list[$value]) ? $list[$value] : '';
}
public function getStatusNameAttr($value, $data) {
$status_name = '';
switch ($this->status_code) {
case 'used':
$status_name = '已使用';
break;
case 'nouse':
$status_name = '未使用';
break;
case 'expire':
$status_name = '已过期';
break;
}
return $status_name;
}
public function getStatusCodeAttr($value, $data) {
$status_code = '';
if ($data['usetime']) {
// 已使用
$status_code = 'used';
} else {
// 未使用
if (empty($data['expiretime']) || $data['expiretime'] > time()) {
// 不过期,或者过期时间大于当前时间
$status_code = 'nouse';
} else {
// 已过期
$status_code = 'expire';
}
}
return $status_code;
}
public function order()
{
return $this->belongsTo(\addons\shopro\model\Order::class, 'order_id', 'id');
}
public function orderItem()
{
return $this->belongsTo(\addons\shopro\model\OrderItem::class, 'order_item_id', 'id');
}
}

查看文件

@@ -36,7 +36,7 @@ class Verify extends Model
public function scopeCanUse($query)
{
return $query->where('usetime', null)->where(function ($query) {
$query->where('expiretime', null)->whereOr('expiretime', '>', time());
$query->where('expiretime', null)->whereOr('expiretime', 0)->whereOr('expiretime', '>', time());
});
}
@@ -79,7 +79,7 @@ class Verify extends Model
$status_code = 'used';
} else {
// 未使用
if (is_null($data['expiretime']) || $data['expiretime'] > time()) {
if (empty($data['expiretime']) || $data['expiretime'] > time()) {
// 不过期,或者过期时间大于当前时间
$status_code = 'nouse';
} else {