fix(shopro): 修复拼团参团记录与后台订单展示时机
支付成功后正确识别 buy_type 并写入参团记录,我的拼团仅展示已支付订单。 后台与门店订单列表默认隐藏拼团进行中订单,成团成功后再进入备货/发货流程。 纳入版本库:Groupon、ActivityGroupon、OrderScope、Create 等 addons 拼团相关改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
这个提交包含在:
@@ -0,0 +1,429 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\traits;
|
||||
|
||||
use addons\shopro\exception\Exception;
|
||||
use addons\shopro\model\Activity;
|
||||
use addons\shopro\model\ActivityGroupon;
|
||||
use addons\shopro\model\ActivityGrouponLog;
|
||||
use addons\shopro\model\Order;
|
||||
use addons\shopro\model\OrderItem;
|
||||
use addons\shopro\model\UserFake;
|
||||
|
||||
/**
|
||||
* 拼团
|
||||
*/
|
||||
trait Groupon
|
||||
{
|
||||
/**
|
||||
* *、redis 没有存团完整信息,只存了团当前人数,团成员(当前人数,团成员均没有存虚拟用户)
|
||||
* *、redis userList 没有存这个人的购买状态
|
||||
* *、团 解散,成团,虚拟成团,没有修改 redis 团信息(因为直接修改了数据库,参团判断,先判断的数据库后判断的 redis)
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 增加拼团预成员人数
|
||||
*/
|
||||
protected function grouponCacheForwardNum($activityGroupon, $activity, $user, $payed = 'nopay')
|
||||
{
|
||||
if (!$this->hasRedis()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$keys = $this->getKeys([
|
||||
'groupon_id' => $activityGroupon['id'],
|
||||
'goods_id' => $activityGroupon['goods_id'],
|
||||
], [
|
||||
'activity_id' => $activity['id'],
|
||||
'activity_type' => $activity['type'],
|
||||
]);
|
||||
|
||||
extract($keys);
|
||||
|
||||
$redis = $this->getRedis();
|
||||
|
||||
// 将拼团团信息存入 redis 没有用,还得维护团状态,先不存
|
||||
// $redis->HSET($activityHashKey, $grouponKey, json_encode($activityGroupon));
|
||||
|
||||
// 当前团人数 grouponNumKey 如果不存在,自动创建
|
||||
$current_num = $redis->HINCRBY($activityHashKey, $grouponNumKey, 1);
|
||||
|
||||
if ($current_num > $activityGroupon['num']) {
|
||||
// 再把刚加上的减回来
|
||||
$current_num = $redis->HINCRBY($activityHashKey, $grouponNumKey, -1);
|
||||
|
||||
new Exception('该团已满,请参与其它团或自己开团');
|
||||
}
|
||||
|
||||
// 将用户加入拼团缓存,用来判断同一个人在一个团,多次下单,取消订单删除
|
||||
$userList = $redis->HGET($activityHashKey, $grouponUserlistKey);
|
||||
$userList = json_decode($userList, true);
|
||||
$userList = $userList ? : [];
|
||||
$userList[] = [
|
||||
'user_id' => $user['id'],
|
||||
// 'status' => $payed // 太复杂,先不做
|
||||
];
|
||||
$redis->HSET($activityHashKey, $grouponUserlistKey, json_encode($userList));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 拼团团成员预成员退回
|
||||
protected function grouponCacheBackNum($order)
|
||||
{
|
||||
$items = OrderItem::where('order_id', $order['id'])->select();
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
// 不是拼团,或者 没有配置 redis
|
||||
if (strpos($item['activity_type'], 'groupon') === false || !$this->hasRedis()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 扩展字段
|
||||
$order_ext = $order['ext_arr'];
|
||||
// 团 id
|
||||
$groupon_id = $order_ext['groupon_id'] ?? 0;
|
||||
|
||||
if (!$groupon_id) {
|
||||
continue; // 商品独立购买,未参团,或者开新团
|
||||
}
|
||||
|
||||
// 实例化 redis
|
||||
$redis = $this->getRedis();
|
||||
|
||||
$keys = $this->getKeys([
|
||||
'groupon_id' => $groupon_id,
|
||||
'goods_id' => $item['goods_id'],
|
||||
'goods_sku_price_id' => $item['goods_sku_price_id'],
|
||||
], [
|
||||
'activity_id' => $item['activity_id'],
|
||||
'activity_type' => $item['activity_type'],
|
||||
]);
|
||||
|
||||
extract($keys);
|
||||
|
||||
// 扣除预参团成员
|
||||
if ($redis->EXISTS($activityHashKey) && $redis->HEXISTS($activityHashKey, $grouponNumKey)) {
|
||||
$sale = $redis->HINCRBY($activityHashKey, $grouponNumKey, -1);
|
||||
}
|
||||
|
||||
$userList = $redis->HGET($activityHashKey, $grouponUserlistKey);
|
||||
$userList = json_decode($userList, true);
|
||||
$userList = $userList ?: [];
|
||||
foreach($userList as $key => $user) {
|
||||
if ($user['user_id'] == $item['user_id']) {
|
||||
unset($userList[$key]);
|
||||
}
|
||||
}
|
||||
$redis->HSET($activityHashKey, $grouponUserlistKey, json_encode($userList));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断加入旧拼团
|
||||
*/
|
||||
protected function checkJoinGroupon($goods_info, $user, $groupon_id)
|
||||
{
|
||||
$goods = $goods_info['detail'];
|
||||
$activity = $goods['activity'];
|
||||
$rules = $activity['rules'];
|
||||
|
||||
// 获取团信息
|
||||
$activityGroupon = ActivityGroupon::with('activity')->where('id', $groupon_id)->find();
|
||||
if (!$activityGroupon) {
|
||||
new Exception('要参与的团不存在');
|
||||
}
|
||||
// 判断团所属活动是否正常
|
||||
if (!$activityGroupon->activity || $activityGroupon->activity['id'] != $activity['id']) { // 修复,后台手动将活动删除,然后又立即给这个商品创建新的拼团活动,导致参与新活动的旧团错乱问题
|
||||
new Exception('要参与的活动已结束');
|
||||
}
|
||||
if ($activityGroupon['status'] != 'ing') {
|
||||
new Exception('要参与的团已成团,请选择其它团或自己开团');
|
||||
}
|
||||
|
||||
if ($activityGroupon['current_num'] >= $activityGroupon['num']) {
|
||||
new Exception('该团已满,请参与其它团或自己开团');
|
||||
}
|
||||
|
||||
if (!$this->hasRedis()) {
|
||||
// 没有 redis 直接判断数据库团信息,因为 current_num 支付成功才会累加,故无法保证超员,
|
||||
// 该团可加入
|
||||
return $activityGroupon;
|
||||
}
|
||||
|
||||
$keys = $this->getKeys([
|
||||
'groupon_id' => $activityGroupon['id'],
|
||||
'goods_id' => $activityGroupon['goods_id'],
|
||||
], [
|
||||
'activity_id' => $activity['id'],
|
||||
'activity_type' => $activity['type'],
|
||||
]);
|
||||
|
||||
extract($keys);
|
||||
|
||||
$redis = $this->getRedis();
|
||||
|
||||
$current_num = $redis->HGET($activityHashKey, $grouponNumKey);
|
||||
if ($current_num >= $activityGroupon['num']) {
|
||||
new Exception('该团已满,请参与其它团或自己开团');
|
||||
}
|
||||
|
||||
// 将用户加入拼团缓存,用来判断同一个人在一个团,多次下单,订单失效删除
|
||||
$userList = $redis->HGET($activityHashKey, $grouponUserlistKey);
|
||||
$userList = json_decode($userList, true);
|
||||
$userIds = array_column($userList, 'user_id');
|
||||
if (in_array($user['id'], $userIds)) {
|
||||
new Exception('您已参与该团,请不要重复参团');
|
||||
}
|
||||
|
||||
return $activityGroupon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 支付成功真实加入团
|
||||
*/
|
||||
protected function joinGroupon($order, $user) {
|
||||
$item = $order->item;
|
||||
$goods_item = $item[0]; // 拼团只能单独购买
|
||||
|
||||
// 扩展字段
|
||||
$order_ext = $order['ext_arr'];
|
||||
// 团 id
|
||||
$groupon_id = (int)($order_ext['groupon_id'] ?? 0);
|
||||
$buy_type = $order_ext['buy_type'] ?? 'alone';
|
||||
|
||||
// 不是拼团购买,比如拼团单独购买
|
||||
if ($buy_type !== 'groupon') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($groupon_id) {
|
||||
// 加入旧团,查询团
|
||||
$activityGroupon = ActivityGroupon::where('id', $groupon_id)->find();
|
||||
if (!$activityGroupon) {
|
||||
\think\Log::write('joinGroupon: 团不存在, order_id=' . $order['id'] . ', groupon_id=' . $groupon_id);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 加入新团,创建团
|
||||
$activityGroupon = $this->joinNewGroupon($order, $user);
|
||||
}
|
||||
if (!$activityGroupon) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加参团记录
|
||||
$activityGrouponLog = $this->addGrouponLog($order, $user, $activityGroupon);
|
||||
if (!$activityGrouponLog) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->checkGrouponStatus($activityGroupon);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 支付成功开启新拼团
|
||||
*/
|
||||
protected function joinNewGroupon($order, $user)
|
||||
{
|
||||
$item = $order->item;
|
||||
$goodsItem = $item[0]; // 拼团只能单独购买
|
||||
|
||||
// 获取活动
|
||||
$activity = Activity::where('id', $goodsItem['activity_id'])->find();
|
||||
$rules = $activity['rules'];
|
||||
|
||||
// 小于 0 不限结束时间单位小时
|
||||
$expiretime = 0;
|
||||
if (isset($rules['valid_time']) && $rules['valid_time'] > 0) {
|
||||
// 转为 秒
|
||||
$expiretime = $rules['valid_time'] * 3600;
|
||||
}
|
||||
|
||||
// 开团
|
||||
$activityGroupon = new ActivityGroupon();
|
||||
$activityGroupon->user_id = $user['id'];
|
||||
$activityGroupon->goods_id = $goodsItem['goods_id'];
|
||||
$activityGroupon->activity_id = $goodsItem['activity_id'];
|
||||
$activityGroupon->num = $rules['team_num'] ?? 1; // 避免活动找不到
|
||||
$activityGroupon->current_num = 0; // 真实团成员等支付完成之后再增加
|
||||
$activityGroupon->status = 'ing';
|
||||
$activityGroupon->expiretime = $expiretime > 0 ? (time() + $expiretime) : 0;
|
||||
$activityGroupon->save();
|
||||
|
||||
// 记录团 id
|
||||
$order->ext = json_encode($order->setExt($order, ['groupon_id' => $activityGroupon->id])); // 团 id
|
||||
$order->save();
|
||||
|
||||
// 将团信息存入缓存,增加缓存中当前团人数
|
||||
$this->grouponCacheForwardNum($activityGroupon, $activity, $user, 'payed');
|
||||
|
||||
if ($expiretime > 0) {
|
||||
// 增加自动关闭拼团队列(如果有虚拟成团,会判断虚拟成团)
|
||||
\think\Queue::later($expiretime, '\addons\shopro\job\ActivityGrouponAutoOper@expire', [
|
||||
'activity' => $activity,
|
||||
'activity_groupon_id' => $activityGroupon->id
|
||||
], 'shopro');
|
||||
}
|
||||
|
||||
return $activityGroupon;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 增加团成员记录
|
||||
*/
|
||||
protected function addGrouponLog($order, $user, $activityGroupon) {
|
||||
if (!$activityGroupon) {
|
||||
\think\Log::write('groupon-notfund: order_id: ' . $order['id']);
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = $order->item;
|
||||
$goodsItem = $item[0]; // 拼团只能单独购买
|
||||
|
||||
// 增加团成员数量
|
||||
$activityGroupon->setInc('current_num', 1);
|
||||
|
||||
// 增加参团记录
|
||||
$activityGrouponLog = new ActivityGrouponLog();
|
||||
$activityGrouponLog->user_id = $user['id'];
|
||||
$activityGrouponLog->user_nickname = $user['nickname'];
|
||||
$activityGrouponLog->user_avatar = $user['avatar'];
|
||||
$activityGrouponLog->groupon_id = $activityGroupon['id'] ?? 0;
|
||||
$activityGrouponLog->goods_id = $goodsItem['goods_id'];
|
||||
$activityGrouponLog->goods_sku_price_id = $goodsItem['goods_sku_price_id'];
|
||||
$activityGrouponLog->activity_id = $goodsItem['activity_id'];
|
||||
$activityGrouponLog->is_leader = ($activityGroupon['user_id'] == $user['id']) ? 1 : 0;
|
||||
$activityGrouponLog->is_fictitious = 0;
|
||||
$activityGrouponLog->order_id = $order['id'];
|
||||
$activityGrouponLog->save();
|
||||
|
||||
return $activityGrouponLog;
|
||||
}
|
||||
|
||||
|
||||
// 虚拟成团,增加虚拟成员,并判断是否完成,然后将团状态改为,虚拟成团成功
|
||||
protected function finishFictitiousGroupon($activityGroupon, $num = 0, $users = []) {
|
||||
// 拼团剩余人数
|
||||
$surplus_num = $activityGroupon['num'] - $activityGroupon['current_num'];
|
||||
|
||||
// 团已经满员
|
||||
if ($surplus_num <= 0) {
|
||||
if ($activityGroupon['status'] == 'ing') {
|
||||
// 已满员但还是进行中状态,检测并完成团,起到纠正作用
|
||||
return $this->checkGrouponStatus($activityGroupon);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 本次虚拟人数, 如果传入 num 则使用 num 和 surplus_num 中最小值, 如果没有传入,默认剩余人数全部虚拟
|
||||
$fictitious_num = $num ? ($num > $surplus_num ? $surplus_num : $num) : $surplus_num;
|
||||
|
||||
// 查询虚拟用户
|
||||
$userFakes = UserFake::orderRaw('rand()')->limit($fictitious_num)->select();
|
||||
|
||||
if (count($userFakes) < $fictitious_num && $num == 0) {
|
||||
// 虚拟用户不足,并且是自动虚拟成团进程,自动解散团
|
||||
return $this->invalidRefundGroupon($activityGroupon);
|
||||
}
|
||||
|
||||
// 增加团人数
|
||||
$activityGroupon->setInc('current_num', $fictitious_num);
|
||||
|
||||
for ($i = 0; $i < $fictitious_num; $i ++) {
|
||||
// 先用传过来的
|
||||
$avatar = isset($users[$i]['avatar']) ? $users[$i]['avatar'] : '';
|
||||
$nickname = isset($users[$i]['nickname']) ? $users[$i]['nickname'] : '';
|
||||
|
||||
// 如果没有,用查的虚拟的
|
||||
$avatar = $avatar ? : $userFakes[$i]['avatar'];
|
||||
$nickname = $nickname ? : $userFakes[$i]['nickname'];
|
||||
|
||||
// 增加参团记录
|
||||
$activityGrouponLog = new ActivityGrouponLog();
|
||||
$activityGrouponLog->user_id = 0;
|
||||
$activityGrouponLog->user_nickname = $nickname;
|
||||
$activityGrouponLog->user_avatar = $avatar;
|
||||
$activityGrouponLog->groupon_id = $activityGroupon['id'] ?? 0;
|
||||
$activityGrouponLog->goods_id = $activityGroupon['goods_id'];
|
||||
$activityGrouponLog->goods_sku_price_id = 0; // 没有订单,所以也就没有 goods_sku_price_id
|
||||
$activityGrouponLog->activity_id = $activityGroupon['activity_id'];
|
||||
$activityGrouponLog->is_leader = 0; // 不是团长
|
||||
$activityGrouponLog->is_fictitious = 1; // 虚拟用户
|
||||
$activityGrouponLog->order_id = 0; // 虚拟成员没有订单
|
||||
$activityGrouponLog->save();
|
||||
}
|
||||
|
||||
return $this->checkGrouponStatus($activityGroupon);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 团过期退款,或者后台手动解散退款
|
||||
*/
|
||||
protected function invalidRefundGroupon($activityGroupon, $user = null) {
|
||||
$activityGroupon->status = 'invalid'; // 拼团失败
|
||||
$activityGroupon->save();
|
||||
|
||||
// 查询参团真人
|
||||
$logs = ActivityGrouponLog::with('order')->where('groupon_id', $activityGroupon['id'])->where('is_fictitious', 0)->select();
|
||||
|
||||
foreach ($logs as $key => $log) {
|
||||
$order = $log['order'];
|
||||
if ($order && $order['status'] > 0) {
|
||||
// 退款,只能有一个 item
|
||||
$item = $order['item'][0];
|
||||
if ($item && in_array($item['refund_status'], [OrderItem::REFUND_STATUS_NOREFUND, OrderItem::REFUND_STATUS_ING])) {
|
||||
// 未申请退款,或者退款中,直接全额退款
|
||||
Order::startRefund($order, $order['item'][0], $order['pay_fee'], $user, '拼团失败退款');
|
||||
}
|
||||
}
|
||||
|
||||
// 修改 logs 为已退款
|
||||
$log->is_refund = 1;
|
||||
$log->save();
|
||||
}
|
||||
|
||||
// 触发拼团失败行为
|
||||
$data = ['groupon' => $activityGroupon];
|
||||
\think\Hook::listen('activity_groupon_fail', $data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 检查团状态
|
||||
*/
|
||||
protected function checkGrouponStatus($activityGroupon) {
|
||||
if (!$activityGroupon) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 重新获取团信息
|
||||
$activityGroupon = ActivityGroupon::where('id', $activityGroupon['id'])->find();
|
||||
if ($activityGroupon['current_num'] >= $activityGroupon['num'] && !in_array($activityGroupon['status'], ['finish', 'finish-fictitious'])) {
|
||||
// 查询是否有虚拟团成员
|
||||
$fictitiousCount = ActivityGrouponLog::where('groupon_id', $activityGroupon['id'])->where('is_fictitious', 1)->count();
|
||||
|
||||
// 将团设置为已完成
|
||||
$activityGroupon->status = $fictitiousCount ? 'finish-fictitious' : 'finish';
|
||||
$activityGroupon->finishtime = time();
|
||||
$activityGroupon->save();
|
||||
|
||||
// 触发成团行为
|
||||
$data = ['groupon' => $activityGroupon];
|
||||
\think\Hook::listen('activity_groupon_finish', $data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\library\traits\model\order;
|
||||
|
||||
use addons\shopro\library\Wechat;
|
||||
use addons\shopro\model\Order;
|
||||
use addons\shopro\model\OrderItem;
|
||||
use addons\shopro\model\Store;
|
||||
use think\Cache;
|
||||
|
||||
trait OrderScope
|
||||
{
|
||||
// 已失效
|
||||
public function scopeInvalid($query)
|
||||
{
|
||||
return $query->where('status', Order::STATUS_INVALID);
|
||||
}
|
||||
|
||||
// 已取消
|
||||
public function scopeCancel($query)
|
||||
{
|
||||
return $query->where('status', Order::STATUS_CANCEL);
|
||||
}
|
||||
|
||||
// 未支付
|
||||
public function scopeNopay($query)
|
||||
{
|
||||
return $query->where('status', Order::STATUS_NOPAY);
|
||||
}
|
||||
|
||||
// 未发货
|
||||
public function scopeNosend($query)
|
||||
{
|
||||
$where = [
|
||||
'dispatch_status' => OrderItem::DISPATCH_STATUS_NOSEND,
|
||||
'refund_status' => ['not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH]] // 没有退款完成
|
||||
];
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
})->whereNotExists(function ($query) use($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where(['aftersale_status' => OrderItem::AFTERSALE_STATUS_AFTERING])->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
// 待收货
|
||||
public function scopeNoget($query)
|
||||
{
|
||||
$where = [
|
||||
'dispatch_status' => OrderItem::DISPATCH_STATUS_SENDED,
|
||||
'refund_status' => ['not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH]] // 没有退款完成
|
||||
];
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 待评价
|
||||
public function scopeNocomment($query)
|
||||
{
|
||||
$where = [
|
||||
'dispatch_status' => OrderItem::DISPATCH_STATUS_GETED,
|
||||
'refund_status' => ['not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH]], // 没有退款完成
|
||||
'comment_status' => OrderItem::COMMENT_STATUS_NO
|
||||
];
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
// 售后 (后台要用,虽然有专门的售后单列表)
|
||||
public function scopeAftersale($query)
|
||||
{
|
||||
return $query->whereExists(function ($query) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')
|
||||
->where('aftersale_status', '<>', OrderItem::AFTERSALE_STATUS_NOAFTER);
|
||||
// ->where(function($query) {
|
||||
// // 只要申请过售后都算
|
||||
// $query->where('aftersale_status', '<>', OrderItem::AFTERSALE_STATUS_NOAFTER)
|
||||
// ->whereOr('refund_status', '<>', OrderItem::REFUND_STATUS_NOREFUND);
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
// 退款 (即将废弃,有专门的售后单列表)
|
||||
public function scopeRefundStatus($query)
|
||||
{
|
||||
$where = [
|
||||
'refund_status' => ['<>', OrderItem::REFUND_STATUS_NOREFUND], // 只要申请过退款
|
||||
];
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 已支付
|
||||
public function scopePayed($query)
|
||||
{
|
||||
return $query->where('status', 'in', [Order::STATUS_PAYED, Order::STATUS_FINISH]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 排除拼团进行中的订单(成团成功后再进入待发货/备货流程)
|
||||
*/
|
||||
public function scopeExcludeGrouponIng($query)
|
||||
{
|
||||
$orderTable = (new Order())->getQuery()->getTable();
|
||||
$grouponTable = (new \addons\shopro\model\ActivityGroupon())->getQuery()->getTable();
|
||||
|
||||
return $query->whereNotExists(function ($sub) use ($orderTable, $grouponTable) {
|
||||
$sub->table($grouponTable)
|
||||
->whereRaw($grouponTable . '.id = CAST(JSON_UNQUOTE(JSON_EXTRACT(' . $orderTable . '.ext, "$.groupon_id")) AS UNSIGNED)')
|
||||
->where($grouponTable . '.status', 'ing');
|
||||
});
|
||||
}
|
||||
|
||||
// 已完成
|
||||
public function scopeFinish($query)
|
||||
{
|
||||
return $query->where('status', Order::STATUS_FINISH);
|
||||
}
|
||||
|
||||
public function scopeCanAftersale($query)
|
||||
{
|
||||
return $query->where('status', 'in', [Order::STATUS_PAYED, Order::STATUS_FINISH]);
|
||||
}
|
||||
|
||||
public function scopeCanDelete($query)
|
||||
{
|
||||
return $query->where('status', 'in', [
|
||||
Order::STATUS_CANCEL,
|
||||
Order::STATUS_INVALID,
|
||||
Order::STATUS_FINISH
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 门店订单 scope
|
||||
|
||||
|
||||
// 获取门店 id 条件
|
||||
private function getStoreWhere($store_id) {
|
||||
$where = [];
|
||||
|
||||
if ($store_id == 'current') {
|
||||
// 默认手机商户端,取当前登陆的门店
|
||||
$store = Store::info();
|
||||
$where['store_id'] = $store['id'];
|
||||
} else {
|
||||
if ($store_id) {
|
||||
$where['store_id'] = $store_id;
|
||||
} else {
|
||||
$where['store_id'] = ['<>', 0];
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全部订单
|
||||
*/
|
||||
public function scopeStore($query, $store_id = 'current')
|
||||
{
|
||||
$where = $this->getStoreWhere($store_id);
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 未发货
|
||||
*/
|
||||
public function scopeStoreNosend($query, $store_id = 'current') {
|
||||
$where = [
|
||||
'dispatch_status' => OrderItem::DISPATCH_STATUS_NOSEND,
|
||||
'refund_status' => ['not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH]] // 没有退款完成
|
||||
];
|
||||
|
||||
$where = array_merge($where, $this->getStoreWhere($store_id));
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 未收货
|
||||
*/
|
||||
public function scopeStoreNoget($query, $store_id = 'current') {
|
||||
$where = [
|
||||
'dispatch_status' => OrderItem::DISPATCH_STATUS_SENDED,
|
||||
'refund_status' => ['not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH]] // 没有退款完成
|
||||
];
|
||||
|
||||
$where = array_merge($where, $this->getStoreWhere($store_id));
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 待评价
|
||||
public function scopeStoreNocomment($query, $store_id = 'current')
|
||||
{
|
||||
$where = [
|
||||
'dispatch_status' => OrderItem::DISPATCH_STATUS_GETED,
|
||||
'refund_status' => ['not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH]], // 没有退款完成
|
||||
'comment_status' => OrderItem::COMMENT_STATUS_NO
|
||||
];
|
||||
|
||||
$where = array_merge($where, $this->getStoreWhere($store_id));
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
// 门店售后订单
|
||||
public function scopeStoreAftersale($query, $store_id = 'current')
|
||||
{
|
||||
$where = [
|
||||
'aftersale_status' => ['<>', OrderItem::AFTERSALE_STATUS_NOAFTER]
|
||||
];
|
||||
|
||||
$where = array_merge($where, $this->getStoreWhere($store_id));
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
// 退款
|
||||
public function scopeStoreRefundStatus($query, $store_id = 'current')
|
||||
{
|
||||
$where = [
|
||||
'refund_status' => ['<>', OrderItem::REFUND_STATUS_NOREFUND], // 只要申请过退款
|
||||
];
|
||||
|
||||
$where = array_merge($where, $this->getStoreWhere($store_id));
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 已完成,这个只手机门店订单查询
|
||||
*/
|
||||
public function scopeStoreFinish($query) {
|
||||
$store = Store::info();
|
||||
// 收完货就算已完成
|
||||
$where = [
|
||||
'store_id' => $store['id'],
|
||||
'dispatch_status' => OrderItem::DISPATCH_STATUS_GETED,
|
||||
'refund_status' => ['not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH]] // 没有退款完成
|
||||
];
|
||||
|
||||
return $query->whereExists(function ($query) use ($where) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
$query->table($table_name)->where('order_id=' . $order_table_name . '.id')->where($where);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\listener\order;
|
||||
|
||||
use addons\shopro\exception\Exception;
|
||||
use addons\shopro\model\Cart;
|
||||
use addons\shopro\model\Config;
|
||||
use addons\shopro\model\Order;
|
||||
use addons\shopro\model\OrderItem;
|
||||
use addons\shopro\model\User;
|
||||
use addons\shopro\library\traits\StockSale;
|
||||
use addons\shopro\library\traits\ActivityCache;
|
||||
use addons\shopro\library\traits\Groupon;
|
||||
use addons\shopro\model\ActivityGroupon;
|
||||
|
||||
/**
|
||||
* 订单创建
|
||||
*/
|
||||
class Create
|
||||
{
|
||||
use StockSale, ActivityCache, Groupon;
|
||||
|
||||
// 订单创建前
|
||||
public function orderCreateBefore(&$params)
|
||||
{
|
||||
$user = $params['user'];
|
||||
$order_type = $params['order_type'];
|
||||
$groupon_id = $params['groupon_id'];
|
||||
$buy_type = $params['buy_type'];
|
||||
$goods_original_amount = $params['goods_original_amount'];
|
||||
$goods_amount = $params['goods_amount'];
|
||||
$dispatch_amount = $params['dispatch_amount'];
|
||||
$total_amount = $params['total_amount'];
|
||||
$score_amount = $params['score_amount'];
|
||||
$total_fee = $params['total_fee'];
|
||||
$discount_fee = $params['discount_fee'];
|
||||
$coupon_fee = $params['coupon_fee'];
|
||||
$new_goods_list = $params['new_goods_list'];
|
||||
$activity_type = $params['activity_type'];
|
||||
$user_coupons = $params['user_coupons'];
|
||||
$user_address = $params['user_address'];
|
||||
$from = $params['from'];
|
||||
|
||||
// 重新获取 user
|
||||
$user = User::where('id', $user->id)->find();
|
||||
|
||||
// 如果需要支付积分
|
||||
if ($score_amount) {
|
||||
// 判断个人积分是否充足
|
||||
if ($user->score < $score_amount) {
|
||||
// 积分不足
|
||||
new Exception('积分不足');
|
||||
}
|
||||
}
|
||||
|
||||
// 限购
|
||||
$this->limitBuy($new_goods_list, $user);
|
||||
|
||||
// 是拼团, 并且不是单独购买 ,单独购买直接正常走商品流程
|
||||
if (strpos($activity_type, 'groupon') !== false && $buy_type != 'alone') {
|
||||
// 参与现有团
|
||||
if ($groupon_id) {
|
||||
// 获取当前团,并判断当前团是否可参与
|
||||
$activityGroupon = $this->checkJoinGroupon($new_goods_list[0], $user, $groupon_id);
|
||||
} else {
|
||||
// 开新团,放到 支付成功之后
|
||||
// $activityGroupon = $this->newGroupon($new_goods_list[0], $user);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断 并 增加 redis 销量
|
||||
if ($order_type == 'goods') {
|
||||
$this->cacheForwardSale($new_goods_list);
|
||||
}
|
||||
|
||||
// (开新团不判断)参与旧团 增加预拼团人数,上面加入团的时候已经判断过一次了,所以这里 99.99% 会加入成功的
|
||||
if (isset($activityGroupon) && $activityGroupon) {
|
||||
// 将团信息缓存,用在后续下单流程
|
||||
$key = 'grouponinfo-' . $user['id'];
|
||||
cache($key, json_encode($activityGroupon), 60);
|
||||
|
||||
// 增加拼团预成员人数, 拼团只能单独购买
|
||||
$detail = $new_goods_list[0]['detail'];
|
||||
$this->grouponCacheForwardNum($activityGroupon, $detail['activity'], $user);
|
||||
}
|
||||
}
|
||||
|
||||
// 订单创建后
|
||||
public function orderCreateAfter(&$params)
|
||||
{
|
||||
$user = $params['user'];
|
||||
$order = $params['order'];
|
||||
$from = $params['from'];
|
||||
$groupon = $params['groupon'];
|
||||
$buy_type = $params['buy_type'];
|
||||
$new_goods_list = $params['new_goods_list'];
|
||||
|
||||
// 删除购物车
|
||||
if ($from == 'cart') {
|
||||
foreach ($new_goods_list as $delCart) {
|
||||
Cart::where([
|
||||
'user_id' => $user->id,
|
||||
'goods_id' => $delCart['goods_id'],
|
||||
'sku_price_id' => $delCart['sku_price_id'],
|
||||
])->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新订单扩展字段
|
||||
$order_ext = $order['ext_arr'];
|
||||
$order_ext['buy_type'] = ($buy_type === 'groupon') ? 'groupon' : 'alone'; // 购买方式
|
||||
$order_ext['groupon_id'] = (int)($groupon['id'] ?? 0); // 参团时写入团 id,开团支付后再更新
|
||||
|
||||
// 判断需要支付的金额是否大于 0
|
||||
if ($order['total_fee'] <= 0) {
|
||||
// 更新订单扩展字段
|
||||
$order->ext = json_encode($order_ext);
|
||||
$order->save();
|
||||
|
||||
$order = (new Order)->paymentProcess($order, [
|
||||
'order_sn' => $order['order_sn'],
|
||||
'transaction_id' => '',
|
||||
'notify_time' => date('Y-m-d H:i:s'),
|
||||
'buyer_email' => $user->id,
|
||||
'payment_json' => json_encode([]),
|
||||
'pay_fee' => $order->total_fee,
|
||||
'pay_type' => $order['type'] == 'score' ? 'score' : 'wallet' // 支付方式 积分完全支付,或者不需要支付,使用 wallet
|
||||
]);
|
||||
} else {
|
||||
// 默认取第一个商品
|
||||
$goods_one = $new_goods_list[0]['detail'];
|
||||
$activity_one = $goods_one['activity'];
|
||||
|
||||
// 获取第一个商品的活动规则,活动不存在,自动会使用全局自动关闭
|
||||
$rules = $activity_one['rules'] ?? [];
|
||||
// 优先使用活动的订单关闭时间
|
||||
if (isset($rules['order_auto_close']) && $rules['order_auto_close'] > 0) {
|
||||
$close_minue = $rules['order_auto_close'];
|
||||
} else {
|
||||
// 添加自动关闭队列
|
||||
$config = Config::where('name', 'order')->cache(300)->find(); // 读取配置自动缓存 5 分钟
|
||||
$config = isset($config) ? json_decode($config['value'], true) : [];
|
||||
|
||||
$close_minue = (isset($config['order_auto_close']) && $config['order_auto_close'] > 0)
|
||||
? $config['order_auto_close'] : 15;
|
||||
}
|
||||
|
||||
// 更新订单,将过期时间存入订单,前台展示支付倒计时
|
||||
$order_ext['expired_time'] = time() + ($close_minue * 60);
|
||||
|
||||
\think\Queue::later(($close_minue * 60), '\addons\shopro\job\OrderAutoOper@autoClose', ['order' => $order], 'shopro');
|
||||
|
||||
// 更新订单扩展字段
|
||||
$order->ext = json_encode($order_ext);
|
||||
$order->save();
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断是否限制购买
|
||||
*/
|
||||
private function limitBuy($new_goods_list, $user)
|
||||
{
|
||||
foreach ($new_goods_list as $key => $goods) {
|
||||
$detail = $goods['detail'];
|
||||
$activity = $detail['activity'];
|
||||
|
||||
$rules = $activity['rules'] ?? [];
|
||||
// 不存在,或者 0 不限制
|
||||
if (!isset($rules['limit_buy']) || $rules['limit_buy'] <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 查询用户老订单,判断本次下单数量,判断是否超过购买限制, 未支付的或者已完成的都算
|
||||
$buy_num = OrderItem::where('user_id', $user['id'])->where('goods_id', $detail['id'])->where('activity_id', $activity['id'])
|
||||
->whereExists(function ($query) use ($detail, $activity) {
|
||||
$order_table_name = (new Order())->getQuery()->getTable();
|
||||
$table_name = (new OrderItem())->getQuery()->getTable();
|
||||
|
||||
$query->table($order_table_name)->where('order_id=' . $order_table_name . '.id')
|
||||
->where('status', '>=', Order::STATUS_NOPAY); // 未支付,或者已支付的订单都算
|
||||
})->sum('goods_num');
|
||||
|
||||
if (($buy_num + $goods['goods_num']) > $rules['limit_buy']) {
|
||||
$msg = '该商品限购 ' . $rules['limit_buy'] . ' 件';
|
||||
|
||||
if ($buy_num < $rules['limit_buy']) {
|
||||
$msg .= ',当前还可购买 ' . ($rules['limit_buy'] - $buy_num) . ' 件';
|
||||
}
|
||||
|
||||
new Exception($msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\model;
|
||||
|
||||
use addons\shopro\exception\Exception;
|
||||
use addons\shopro\model\Order;
|
||||
use think\Model;
|
||||
|
||||
/**
|
||||
* 活动-拼团
|
||||
*/
|
||||
class ActivityGroupon extends Model
|
||||
{
|
||||
// 表名,不含前缀
|
||||
protected $name = 'shopro_activity_groupon';
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = 'int';
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = 'createtime';
|
||||
protected $updateTime = 'updatetime';
|
||||
|
||||
protected $hidden = [];
|
||||
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
];
|
||||
|
||||
|
||||
// 获取正在进行中的团
|
||||
public static function getActivityGroupon($params)
|
||||
{
|
||||
$goods_id = $params['goods_id'] ?? 0;
|
||||
$activity_id = $params['activity_id'] ?? 0;
|
||||
|
||||
$activityGroupons = self::with('leader')
|
||||
->where('goods_id', $goods_id)
|
||||
->where('activity_id', $activity_id)
|
||||
->where('status', 'ing')
|
||||
->limit(10)
|
||||
->select();
|
||||
|
||||
return $activityGroupons;
|
||||
}
|
||||
|
||||
|
||||
// 团详情
|
||||
public static function getActivityGrouponDetail ($id) {
|
||||
$activityGroupon = self::with(['my', 'grouponLog', 'activity' => function ($query) {
|
||||
$query->removeOption('soft_delete')->with('activity_goods_sku_price'); // 关联团所属活动,并关联活动规格
|
||||
}])->where('id', $id)->find();
|
||||
|
||||
if (!$activityGroupon) {
|
||||
new Exception('团未找到');
|
||||
}
|
||||
|
||||
$detail = Goods::getGoodsDetail($activityGroupon['goods_id']); // 正常团选规格时候要用
|
||||
$activityGroupon['goods'] = $detail;
|
||||
|
||||
// 初始化拼团价为商品价,防止活动被物理删除
|
||||
$activityGroupon['goods']['groupon_price'] = $activityGroupon['goods']['price'];
|
||||
// 初始化活动状态为已结束
|
||||
$activityGroupon['activity_status'] = 'ended';
|
||||
|
||||
// 处理当时的活动价格
|
||||
if ($activityGroupon['activity']) {
|
||||
$activity = $activityGroupon['activity'];
|
||||
$activityGroupon['activity_status'] = $activity['status_code'];
|
||||
|
||||
$currentGoodsActivitySkuPrices = [];
|
||||
foreach ($activity['activity_goods_sku_price'] as $k => $skuPrice) {
|
||||
if ($skuPrice['status'] == 'up' && $skuPrice['goods_id'] == $activityGroupon['goods_id']) {
|
||||
$currentGoodsActivitySkuPrices[] = $skuPrice;
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentGoodsActivitySkuPrices) {
|
||||
// 当时参加活动真实销量
|
||||
$activityGroupon['goods']['sales'] = array_sum(array_column($currentGoodsActivitySkuPrices, 'sales'));
|
||||
// 这个是活动最低价
|
||||
$activityGroupon['goods']['groupon_price'] = $activityGroupon['goods']['price'] = min(array_column($currentGoodsActivitySkuPrices, 'price'));
|
||||
}
|
||||
}
|
||||
|
||||
return $activityGroupon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取我的拼团列表
|
||||
*
|
||||
* @param [type] $type
|
||||
* @return void
|
||||
*/
|
||||
public static function getMyGroupon($type) {
|
||||
$user = User::info();
|
||||
|
||||
$logs = \addons\shopro\model\ActivityGrouponLog::with(['groupon', 'order.firstItem', 'goods' => function ($query) {
|
||||
$query->removeOption('soft_delete'); // 商品查询包括被软删除的
|
||||
}])->whereExists(function ($query) {
|
||||
$logTable = (new \addons\shopro\model\ActivityGrouponLog())->getQuery()->getTable();
|
||||
$orderTable = (new Order())->getQuery()->getTable();
|
||||
$query->table($orderTable)->where($orderTable . '.id=' . $logTable . '.order_id')
|
||||
->where('status', '>=', Order::STATUS_PAYED);
|
||||
});
|
||||
|
||||
if ($type != 'all') {
|
||||
$type = $type == 'finish' ? ['finish', 'finish-fictitious'] : [$type];
|
||||
$logs = $logs->whereExists(function ($query) use ($type) {
|
||||
$log_name = (new \addons\shopro\model\ActivityGrouponLog())->getQuery()->getTable();
|
||||
$groupon_name = (new \addons\shopro\model\ActivityGroupon())->getQuery()->getTable();
|
||||
$query->table($groupon_name)->where('id=' . $log_name . '.groupon_id')
|
||||
->where('status', 'in', $type);
|
||||
});
|
||||
}
|
||||
|
||||
$logs = $logs->where('user_id', $user->id)
|
||||
->order('id', 'desc')
|
||||
->paginate(10)->toArray();
|
||||
|
||||
// 将列表的显示价格,都查当时购买的价格,和当时对应的活动的 真实销量
|
||||
if ($grouponLogs = $logs['data']) {
|
||||
// 拿到所有活动 ids
|
||||
$activity_ids = array_column($grouponLogs, 'activity_id');
|
||||
|
||||
// 一次获取所有活动,包括被软删除的活动,并关联规格
|
||||
$activities = \addons\shopro\model\Activity::withTrashed()
|
||||
->with('activityGoodsSkuPrice')
|
||||
->where('id', 'in', $activity_ids)->select();
|
||||
$activities = array_column($activities, null, 'id');
|
||||
|
||||
foreach ($grouponLogs as $key => $grouponLog) {
|
||||
if (isset($grouponLog['goods']) && $grouponLog['goods'] && isset($activities[$grouponLog['activity_id']])) {
|
||||
$activity = $activities[$grouponLog['activity_id']];
|
||||
|
||||
// 拿到当前商品对应的活动规格
|
||||
$currentGoodsActivitySkuPrices = [];
|
||||
foreach ($activity['activityGoodsSkuPrice'] as $k => $skuPrice) {
|
||||
if ($skuPrice['status'] == 'up' && $skuPrice['goods_id'] == $grouponLog['goods_id']) {
|
||||
$currentGoodsActivitySkuPrices[] = $skuPrice;
|
||||
}
|
||||
}
|
||||
|
||||
// 当时参加活动真实销量
|
||||
if ($currentGoodsActivitySkuPrices) {
|
||||
$grouponLogs[$key]['goods']['sales'] = array_sum(array_column($currentGoodsActivitySkuPrices, 'sales'));
|
||||
}
|
||||
// 这个是购买时候的活动单价
|
||||
if (isset($grouponLog['order']['first_item'])) {
|
||||
$grouponLogs[$key]['goods']['price'] = $grouponLog['order']['first_item']['goods_price'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$logs['data'] = $grouponLogs;
|
||||
}
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
|
||||
public function activity()
|
||||
{
|
||||
return $this->belongsTo(\addons\shopro\model\Activity::class, 'activity_id', 'id');
|
||||
}
|
||||
|
||||
|
||||
public function grouponLog()
|
||||
{
|
||||
return $this->hasMany(\addons\shopro\model\ActivityGrouponLog::class, 'groupon_id', 'id');
|
||||
}
|
||||
|
||||
public function leader() {
|
||||
return $this->hasOne(\addons\shopro\model\ActivityGrouponLog::class, 'groupon_id', 'id')->where('is_leader', 1);
|
||||
}
|
||||
|
||||
public function my() {
|
||||
$user = User::info();
|
||||
return $this->hasOne(\addons\shopro\model\ActivityGrouponLog::class, 'groupon_id', 'id')->where('user_id', ($user ? $user->id : 0));
|
||||
}
|
||||
|
||||
public function goods()
|
||||
{
|
||||
return $this->belongsTo(\addons\shopro\model\Goods::class, 'goods_id', 'id');
|
||||
}
|
||||
}
|
||||
412
addons/shopro/model/store/Order.php
普通文件
412
addons/shopro/model/store/Order.php
普通文件
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro\model\store;
|
||||
|
||||
use addons\shopro\model\ActivityGroupon;
|
||||
use think\Model;
|
||||
use addons\shopro\exception\Exception;
|
||||
use addons\shopro\library\traits\model\order\OrderScope;
|
||||
use think\Db;
|
||||
use think\Queue;
|
||||
use traits\model\SoftDelete;
|
||||
use addons\shopro\library\traits\model\order\OrderOper;
|
||||
use addons\shopro\library\traits\model\order\OrderStatus;
|
||||
use addons\shopro\model\Order as BaseOrder;
|
||||
use addons\shopro\model\OrderItem;
|
||||
use addons\shopro\model\Store;
|
||||
use addons\shopro\model\User;
|
||||
use addons\shopro\model\Verify;
|
||||
|
||||
/**
|
||||
* 商户订单模型
|
||||
*/
|
||||
class Order extends BaseOrder
|
||||
{
|
||||
//
|
||||
protected $append = [
|
||||
'status_code',
|
||||
'status_name',
|
||||
'ext_arr'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* 获取订单列表(已付款的),覆盖点 baseOrder 中 orderOper getList 方法
|
||||
*/
|
||||
public static function getList($params) {
|
||||
$user = User::info();
|
||||
$store = Store::info();
|
||||
|
||||
// 查询分页列表
|
||||
$orders = self::buildQueryList($params)->paginate(10);
|
||||
|
||||
// 查询搜索条件下的总金额
|
||||
$type = $params['type'] ?? 'all';
|
||||
$orderIds = self::buildQueryList($params)->column('id');
|
||||
$orderItems = OrderItem::where('order_id', 'in', $orderIds)
|
||||
->where('store_id', $store['id']);
|
||||
// if ($type != 'all') { // 订单中只有一个发货,待收货中也会把所有商品都查出来,所以这里不加条件了
|
||||
// $where= []; // 一个订单中可能有多个商品在这个门店,并且有可能有的发货了,有的没发货
|
||||
// switch ($type) {
|
||||
// case 'nosend':
|
||||
// $where = ['dispatch_status' => OrderItem::DISPATCH_STATUS_NOSEND];
|
||||
// break;
|
||||
// case 'noget':
|
||||
// $where = ['dispatch_status' => OrderItem::DISPATCH_STATUS_SENDED];
|
||||
// break;
|
||||
// case 'finish':
|
||||
// $where = ['dispatch_status' => OrderItem::DISPATCH_STATUS_GETED];
|
||||
// break;
|
||||
// }
|
||||
// $orderItems = $orderItems->where($where)->where('refund_status', 'not in', [
|
||||
// OrderItem::REFUND_STATUS_OK,
|
||||
// OrderItem::REFUND_STATUS_FINISH
|
||||
// ]);
|
||||
// }
|
||||
$total_money = $orderItems->sum('pay_price'); // pay_price 是不包含运费的金额(商家配送,和到店自提都是没有运费的,所以可以用这个来计算)
|
||||
|
||||
|
||||
$result = [
|
||||
'total_num' => $orders->total(),
|
||||
'total_money' => $total_money,
|
||||
'result' => $orders
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 要单独查询总金额
|
||||
*/
|
||||
private static function buildQueryList($params) {
|
||||
$date_type = $params['date_type'] ?? 'today';
|
||||
$date = $params['date'] ?? '';
|
||||
$date = explode(',', $date);
|
||||
$type = $params['type'] ?? 'all';
|
||||
|
||||
// 只查付款的订单
|
||||
$order = (new self())->with('item')->payed();
|
||||
|
||||
switch ($type) {
|
||||
case 'all':
|
||||
$order = $order->store();
|
||||
break;
|
||||
case 'nosend':
|
||||
$order = $order->storeNosend();
|
||||
break;
|
||||
case 'noget':
|
||||
$order = $order->storeNoget();
|
||||
break;
|
||||
case 'finish':
|
||||
$order = $order->storeFinish();
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($date_type) {
|
||||
case 'today':
|
||||
$start = strtotime(date('Y-m-d'));
|
||||
$end = strtotime(date('Y-m-d', (time() + 86400)));
|
||||
break;
|
||||
case 'yesterday':
|
||||
$start = strtotime(date('Y-m-d', (time() - 86400)));
|
||||
$end = strtotime(date('Y-m-d'));
|
||||
break;
|
||||
case 'week':
|
||||
// $start = strtotime(date('Y-m-d', (time() - (86400 * 6)))); // 减 6 天
|
||||
$start = strtotime(date('Y-m-d', strtotime("-1 week Monday"))); // 周一开始
|
||||
$end = strtotime(date('Y-m-d', (time() + 86400))); // 到今天结束
|
||||
break;
|
||||
case 'month':
|
||||
// $start = strtotime(date('Y-m-d', (time() - (86400 * 30)))); // 减 30 天
|
||||
$start = strtotime(date('Y-m-d', mktime(0, 0, 0, date("m"), 1, date("Y")))); // 减 30 天
|
||||
$end = strtotime(date('Y-m-d', (time() + 86400))); // 到今天结束
|
||||
break;
|
||||
case 'custom':
|
||||
$date_start = $date[0] ?? date('Y-m-d'); // 默认查 today
|
||||
$date_end = $date[1] ?? date('Y-m-d'); // 默认查 today
|
||||
$start = strtotime($date_start);
|
||||
$end = strtotime($date_end) + 86400;
|
||||
break;
|
||||
default:
|
||||
$start = strtotime(date('Y-m-d'));
|
||||
$end = strtotime(date('Y-m-d', (time() + 86400)));
|
||||
break;
|
||||
}
|
||||
|
||||
$order = $order->where('createtime', '>=', $start)
|
||||
->where('createtime', '<', $end)
|
||||
->excludeGrouponIng()
|
||||
->order('id', 'desc');
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订单详情 覆盖点 baseOrder 中 orderOper detail 方法
|
||||
*/
|
||||
public static function detail($params)
|
||||
{
|
||||
$user = User::info();
|
||||
$store = Store::info();
|
||||
extract($params);
|
||||
|
||||
$order = (new self())->with('item')->payed()->store();
|
||||
|
||||
if (isset($order_sn)) {
|
||||
$order = $order->where('order_sn', $order_sn);
|
||||
}
|
||||
if (isset($id)) {
|
||||
$order = $order->where('id', $id);
|
||||
}
|
||||
|
||||
$order = $order->find();
|
||||
|
||||
if (!$order) {
|
||||
new Exception('订单不存在');
|
||||
}
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订单发货
|
||||
*/
|
||||
public static function operSend($params) {
|
||||
$user = User::info();
|
||||
$store = Store::info();
|
||||
extract($params);
|
||||
|
||||
Db::transaction(function () use ($id) {
|
||||
$order = (new self())->store()->payed()->where('id', $id)->find();
|
||||
if (!$order) {
|
||||
new Exception('订单不存在');
|
||||
}
|
||||
// 加锁查询要发货的 item,防止多次请求造成产生多个核销码
|
||||
$item = OrderItem::where('order_id', $order['id'])
|
||||
->where('dispatch_status', \addons\shopro\model\OrderItem::DISPATCH_STATUS_NOSEND)
|
||||
->where('refund_status', 'not in', [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH])
|
||||
->lock(true)->select();
|
||||
|
||||
if (!$item) {
|
||||
new Exception('订单已发货');
|
||||
}
|
||||
|
||||
// 订单里面这个门店相关的全部发货
|
||||
$order['item'] = $item;
|
||||
|
||||
$order->storeOrderSend($order);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 商家核销订单, 覆盖点 baseOrder 中 orderOper operConfirm 方法
|
||||
*/
|
||||
public static function operConfirm($params)
|
||||
{
|
||||
$user = User::info();
|
||||
$store = Store::info();
|
||||
extract($params);
|
||||
$id = $params['id'] ?? 0;
|
||||
$codes = $params['codes'] ?? [];
|
||||
|
||||
Db::transaction(function () use ($store, $codes) {
|
||||
// 查询核销码
|
||||
$verifies = Verify::canUse()->with(['order', 'orderItem'])->where('type', 'verify')
|
||||
->where('code', 'in', $codes)->lock(true)->select();
|
||||
|
||||
if (!$verifies) {
|
||||
new Exception('核销码不可用');
|
||||
}
|
||||
|
||||
$check_num = 0;
|
||||
foreach ($verifies as $key => $verify) {
|
||||
$order = $verify['order'];
|
||||
$orderItem = $verify['orderItem'];
|
||||
|
||||
if (!$order || $order['status'] <= 0) {
|
||||
new Exception('订单不存在');
|
||||
}
|
||||
|
||||
if (!$orderItem || $orderItem['store_id'] != $store['id']) {
|
||||
new Exception('您不能操作该核销码');
|
||||
}
|
||||
|
||||
if ($orderItem['dispatch_status'] != OrderItem::DISPATCH_STATUS_SENDED) {
|
||||
new Exception('当前商品已核销');
|
||||
}
|
||||
|
||||
if (in_array($orderItem['refund_status'], [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH])) {
|
||||
new Exception('当前商品已退款');
|
||||
}
|
||||
|
||||
$check_num ++;
|
||||
$order->verifyGeted($order, $orderItem, $verify, ['oper_type' => 'store']);
|
||||
}
|
||||
|
||||
if ($check_num <= 0) {
|
||||
// 一个核销码也没核销
|
||||
new Exception('核销失败,无效的核销码');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* -------------------------- 访问器 ------------------------ */
|
||||
|
||||
/**
|
||||
* 覆盖 BaseOrder 中 OrderStatus trait getStatus 方法
|
||||
*/
|
||||
protected function getStatus($data, $type)
|
||||
{
|
||||
$btns = [];
|
||||
$status_name = '';
|
||||
$status_desc = '';
|
||||
|
||||
switch ($this->status_code) {
|
||||
case 'cancel':
|
||||
$status_name = '已取消';
|
||||
break;
|
||||
case 'invalid':
|
||||
$status_name = '交易关闭';
|
||||
break;
|
||||
case 'nopay':
|
||||
$status_name = '待付款';
|
||||
break;
|
||||
// 收完货就算已完成
|
||||
case 'nocomment':
|
||||
case 'commented':
|
||||
case 'finish':
|
||||
$status_name = '已完成';
|
||||
break;
|
||||
case 'noget':
|
||||
$dispatchType = [];
|
||||
foreach ($this->item as $key => $item) {
|
||||
// 获取 item status
|
||||
$dispatchType[] = $item['dispatch_type'];
|
||||
}
|
||||
$dispatchType = array_unique(array_filter($dispatchType)); // 过滤重复,过滤空值
|
||||
|
||||
$status_name = '待收货';
|
||||
if (in_array('selfetch', $dispatchType)) {
|
||||
$status_name = '待提货/到店';
|
||||
} else if (in_array('selfetch', $dispatchType)) {
|
||||
$status_name = '配送中';
|
||||
}
|
||||
break;
|
||||
case 'nosend':
|
||||
$dispatchType = [];
|
||||
foreach ($this->item as $key => $item) {
|
||||
// 获取 item status
|
||||
$dispatchType[] = $item['dispatch_type'];
|
||||
}
|
||||
$dispatchType = array_unique(array_filter($dispatchType)); // 过滤重复,过滤空值
|
||||
|
||||
$status_name = '待发货';
|
||||
if (in_array('store', $dispatchType)) {
|
||||
$status_name = '待配送';
|
||||
} else if (in_array('selfetch', $dispatchType)) {
|
||||
$status_name = '待备货';
|
||||
}
|
||||
break;
|
||||
case 'refund_finish':
|
||||
$status_name = '退款完成';
|
||||
break;
|
||||
case 'refund_ing':
|
||||
$status_name = '退款处理中';
|
||||
break;
|
||||
case 'groupon_ing':
|
||||
$status_name = '等待成团';
|
||||
break;
|
||||
case 'groupon_invalid':
|
||||
$status_name = '拼团失败';
|
||||
break;
|
||||
}
|
||||
|
||||
return $type == 'status_name' ? $status_name : ($type == 'btns' ? $btns : $status_desc);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取支付成功之后的子状态, 门店订单,覆盖 baseOrder model orderStatus trait 中的 getPayedStatusCode 方法
|
||||
*/
|
||||
public function getPayedStatusCode($data)
|
||||
{
|
||||
$status_code = '';
|
||||
|
||||
// 循环判断 item 状态
|
||||
$itemStatusCode = [];
|
||||
foreach ($this->item as $key => $item) {
|
||||
// 获取 item status
|
||||
$itemStatusCode[] = (new OrderItem)->getBaseStatusCode($item->toArray(), 'order');
|
||||
}
|
||||
|
||||
// 取出不重复不为空的 status_code
|
||||
$statusCodes = array_unique(array_filter($itemStatusCode));
|
||||
|
||||
if (in_array('nosend', $statusCodes)) {
|
||||
$status_code = 'nosend';
|
||||
} else if (in_array('noget', $statusCodes)) {
|
||||
$status_code = 'noget';
|
||||
} else if (in_array('nocomment', $statusCodes)) {
|
||||
// 存在待评价,就是待评价
|
||||
$status_code = 'nocomment';
|
||||
} else if (in_array('commented', $statusCodes)) {
|
||||
// 存在已评价,就是已评价
|
||||
$status_code = 'commented'; // 走这里,说明只评价了一部分
|
||||
} else if (in_array('refund_finish', $statusCodes)) {
|
||||
// 都在退款完成
|
||||
$status_code = 'refund_finish';
|
||||
} else if (in_array('refund_ing', $statusCodes)) {
|
||||
// 都在退款中
|
||||
$status_code = 'refund_ing';
|
||||
} // 售后都不在总状态显示
|
||||
|
||||
|
||||
$ext_arr = json_decode($data['ext'], true);
|
||||
// 是拼团订单
|
||||
if (
|
||||
strpos($data['activity_type'], 'groupon') !== false &&
|
||||
isset($ext_arr['groupon_id']) && $ext_arr['groupon_id']
|
||||
) {
|
||||
$groupon = ActivityGroupon::where('id', $ext_arr['groupon_id'])->find();
|
||||
if ($groupon) {
|
||||
if ($groupon['status'] == 'ing') {
|
||||
// 尚未成团
|
||||
$status_code = 'groupon_ing';
|
||||
} else if ($groupon['status'] == 'invalid') {
|
||||
$status_code = 'groupon_invalid';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $status_code;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* -------------------------- 模型关联 ------------------------ */
|
||||
|
||||
/**
|
||||
* 关联订单 item,只查 store_id 是当前门店的
|
||||
*/
|
||||
public function item()
|
||||
{
|
||||
$store = Store::info();
|
||||
return $this->hasMany(\addons\shopro\model\OrderItem::class, 'order_id', 'id')->where('store_id', ($store ? $store['id'] : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联订单第一个 item,只查 store_id 是当前门店的
|
||||
*/
|
||||
public function firstItem()
|
||||
{
|
||||
$store = Store::info();
|
||||
return $this->hasOne(\addons\shopro\model\OrderItem::class, 'order_id', 'id')->where('store_id', ($store ? $store['id'] : 0));
|
||||
}
|
||||
/* -------------------------- 模型关联 ------------------------ */
|
||||
}
|
||||
@@ -1317,6 +1317,9 @@ class Order extends Base
|
||||
}
|
||||
}
|
||||
|
||||
// 拼团未成功订单不在后台订单列表展示,成团后再进入备货/发货流程
|
||||
$orders = $orders->excludeGrouponIng();
|
||||
|
||||
return $orders;
|
||||
}
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户