Commit 657119da by yink

feat: 添加订单退款功能并优化积分解冻逻辑

- 在订单管理中添加退款功能,支持管理员手动退款
- 优化积分解冻逻辑,增加7天解冻期
- 更新商品库存管理,确保退款时库存正确恢复
- 修改积分记录处理逻辑,支持退款时的积分返还
parent 5ab76182
......@@ -10,6 +10,7 @@
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Admin\Actions\TransferToPlatForm;
use App\Models\StoreAdminUsers;
class OrderDivideRecordController extends AdminController
{
......@@ -29,21 +30,23 @@ protected function grid()
$grid->column('order_price', '订单商品价格');
$grid->column('divide_price', '佣金');
//$grid->column('proportion', '分佣比例%');
$grid->column('sh_type', '推荐类型')->display(function ($val) {
$arr = ['平台', '直推', '间推', '商家'];
return $arr[$val];
$grid->column('sh_type', '分佣类型')->display(function ($val) {
return OrderDivideRecord::COMMISSION_TYPE[$val];
});
$grid->column('is_div', '是否分账')->display(function ($val) {
$grid->column('is_div', '是否分账')->display(function ($val) {
return $val == 1 ? '是' : '否';
});
$grid->column('um_id', '推荐公司/分享人')->display(function ($val) {
$grid->column('um_id', '商户/员工')->display(function ($val) {
$stype = $this->sh_type;
$name = '';
if ($stype == 3) {
if ($stype == 0) {
$name = '平台';
} else if ($stype == 3) {
$name = Merchant::where('id', $val)->value('name');
} else {
$name = $stype ? User::where('id', $val)->value('phone') : '';
}else if($stype == 5){
$name = StoreAdminUsers::where('id', $val)->value('name');
}
return $name;
});
$grid->column('remark', '备注')->width(80);
......@@ -56,6 +59,41 @@ protected function grid()
$grid->disableDeleteButton();
$grid->disableRowSelector();
// 添加导出字段映射
$titles = [
'id' => 'ID',
'order.order_sn' => '订单号',
'users.phone' => '会员手机号',
'order_price' => '商品价格',
'divide_price' => '分佣金额',
'sh_type' => '分佣类型',
'is_div' => '是否分账',
'um_id' => '推荐人/商家',
'created_at' => '创建时间'
];
// 添加导出按钮
$grid->export($titles)->rows(function ($rows) {
foreach ($rows as $index => &$row) {
// 处理分佣类型显示
$row['is_div'] = $row['is_div'] == 1? '是' : '否';
$row['um_id'] = $row['um_id'] == 0? '平台' : $row['um_id'];
// 处理商家/员工显示
if ($row['sh_type'] == 0) {
$row['um_id'] = '平台';
} else if ($row['sh_type'] == 3) {
$row['um_id'] = Merchant::where('id', $row['um_id'])->value('name');
}else if($row['sh_type'] == 5){
$row['um_id'] = StoreAdminUsers::where('id', $row['um_id'])->value('name');
}
$row['sh_type'] = OrderDivideRecord::COMMISSION_TYPE[$row['sh_type']];
}
return $rows;
});
$grid->filter(function (Grid\Filter $filter) {
// 更改为 panel 布局
$filter->panel();
......@@ -64,12 +102,17 @@ protected function grid()
$filter->between('created_at', '创建时间')->datetime()->width(4);
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
//分账给平台
if ($actions->row->is_div == 0 && in_array($actions->row->sh_type, [1, 2])) {
$actions->append(new TransferToPlatForm('分账给平台', $actions->row->id));
}
});
//先注释,目前没有手动分账金额给平台说法
// $grid->actions(function (Grid\Displayers\Actions $actions) {
// //分账给平台
// if ($actions->row->is_div == 0 && in_array($actions->row->sh_type, [1, 2])) {
// $actions->append(new TransferToPlatForm('分账给平台', $actions->row->id));
// }
// });
});
}
......
......@@ -17,6 +17,8 @@
use App\Admin\Forms\VerifierCodeForm;
use App\Admin\Forms\ShippingForm;
use Dcat\Admin\Admin;
use App\Http\Controllers\Api\OrderController;
use Illuminate\Support\Facades\DB;
class OrderInfoController extends AdminController
......@@ -33,6 +35,9 @@ protected function grid()
$grid->column('id')->sortable();
$grid->column('order_sn', '订单号')->width(80);
$grid->column('mobile', '手机号');
$grid->column('is_div', '已分账')->display(function ($val) {
return $val ? '是' : '否';
});
$grid->column('goods', '商品信息')->expand(function (Grid\Displayers\Expand $expand) {
$expand->button('查看');
......@@ -170,7 +175,18 @@ protected function grid()
});
$titles = ['id' => 'ID', 'goods_name' => '名称', 'address_id' => '邮箱'];
$titles = [
'id' => 'ID',
'order_sn' => '订单号',
'mobile' => '手机号',
'goods_name' => '商品名称',
'goods_attr' => '商品规格',
'goods_number' => '商品数量',
'goods_price' => '商品单价',
'goods_amount' => '订单金额',
'order_status' => '订单状态',
'created_at' => '下单时间'
];
// 添加导出按钮
$grid->export($titles)->rows(function ($rows) {
......@@ -198,7 +214,45 @@ protected function grid()
return $rows;
});
// 添加退款操作按钮
$grid->actions(function (Grid\Displayers\Actions $actions) {
//只对特定状态的订单显示退款按钮
if ($this->is_div == 0 ) {
$actions->append('<a href="javascript:void(0)" class="refund-order" data-id="'.$this->id.'"><i class="feather icon-refresh-ccw"></i> 退款</a>');
}
});
// 添加退款操作的JS代码
Admin::script(<<<JS
$('.refund-order').click(function() {
var id = $(this).data('id');
Dcat.confirm('确定要给该笔订单退款吗?支付金额会原路返回。', null, function() {
Dcat.loading();
$.ajax({
url: '/order-refund',
type: 'POST',
data: {
id: id,
_token: Dcat.token
},
success: function(data) {
Dcat.loading(false);
if (data.status) {
Dcat.success(data.message);
Dcat.reload();
} else {
Dcat.error(data.message);
}
},
error: function() {
Dcat.loading(false);
Dcat.error('请求失败,请重试');
}
});
});
});
JS);
});
}
......@@ -266,4 +320,32 @@ protected function form()
$form->disableViewButton();
});
}
//管理员退款
/**
* 管理员退款功能
* 功能:处理订单退款操作,包括退款金额原路返回和库存恢复
* 参数:无显式参数,通过request获取订单ID
* 返回值:json格式,包含操作状态和消息
* 异常:捕获数据库操作异常并回滚
* 使用示例:前端通过AJAX调用/order-refund接口
* 注意事项:1.仅对特定状态订单可退款 2.使用事务保证数据一致性 3.记录操作日志
*/
public function OrderAdminRefund()
{
$id = request('id');
$orderObj = OrderInfo::find($id);
if (!$orderObj) {
return response()->json(['status' => false, 'message' => '订单不存在']);
}
if ($orderObj->is_div == 0) {
OrderController::canceOrderFunc($orderObj);
}else{
return response()->json(['status' => false, 'message' => '已分账,不允许退款!']);
}
}
}
......@@ -56,6 +56,7 @@
$router->resource('setting', 'SystemSettingController'); //数值设置
$router->resource('orderInfo', 'OrderInfoController'); //订单管理
$router->post('order-refund', 'OrderInfoController@OrderAdminRefund'); // 添加退款路由
$router->resource('order-divide-record', 'OrderDivideRecordController'); //订单分佣记录
......
......@@ -2,7 +2,29 @@
namespace App\Command;
use Illuminate\Http\Request;
class Log{
class Log {
/**
* 获取当前请求对象
* @return Request|null 返回当前请求对象或null
*
* 功能说明:
* 1. 尝试通过Laravel的request()辅助函数获取当前请求
* 2. 如果辅助函数不可用,则返回null
*
* 使用示例:
* $request = Log::getCurrentRequest();
*
* 注意事项:
* 1. 此方法依赖Laravel的请求上下文
* 2. 在非HTTP请求环境(如命令行)中会返回null
*/
static private function getCurrentRequest(): ?Request
{
if (function_exists('request')) {
return request();
}
return null;
}
// 私有方法:构建用户信息
static private function buildUserInfo(?Request $request): string {
if ($request && $request->user()) {
......@@ -41,8 +63,9 @@ static private function ensureLogDir(string $logPath): void {
}
//保留兼容以前日志
static public function add(string $logKey, mixed $logInfo, ?Request $request = null): void {
static public function add(string $logKey, mixed $logInfo): void {
try {
$request = self::getCurrentRequest();
$day = date('Y-m-d');
$logPath = storage_path("logs/a_runLog_".$day.".log");
self::ensureLogDir($logPath);
......@@ -55,8 +78,9 @@ static public function add(string $logKey, mixed $logInfo, ?Request $request = n
}
// 中间件请求
static public function RequestLog(string $logKey, mixed $logInfo, ?Request $request = null): void {
static public function RequestLog(string $logKey, mixed $logInfo,Request $Request=null): void {
try {
$request = self::getCurrentRequest();
$day = date('Y-m-d');
$logPath = storage_path("logs/a_runLog_Request".$day.".log");
self::ensureLogDir($logPath);
......@@ -68,20 +92,20 @@ static public function RequestLog(string $logKey, mixed $logInfo, ?Request $requ
}
}
//addByName==info
// 错误日志
static public function error(string $logKey,mixed $logInfo, ?Request $request = null): void {
self::writeNamedLog('a_error_', $logKey, $logInfo, $request, 'addByName');
static public function error(string $logKey, mixed $logInfo): void {
self::writeNamedLog('a_error_', $logKey, $logInfo, 'addByName');
}
// 信息日志
static public function info( mixed $logInfo, ?Request $request = null): void {
self::writeNamedLog('a_runLog_','debug', $logInfo, $request, 'info');
static public function info(mixed $logInfo): void {
self::writeNamedLog('a_runLog_', 'debug', $logInfo, 'info');
}
// 私有方法:处理命名日志
static private function writeNamedLog(string $prefix, string $logKey, mixed $logInfo, ?Request $request, string $methodName): void {
static private function writeNamedLog(string $prefix, string $logKey, mixed $logInfo, string $methodName): void {
try {
$request = self::getCurrentRequest();
$logPath = storage_path("logs/{$prefix}{$logKey}.log");
self::ensureLogDir($logPath);
$userInfo = self::buildUserInfo($request);
......
......@@ -19,7 +19,6 @@
class HfOrderController extends BaseController
{
//退款回调
public function refundNotify()
{
......
......@@ -141,7 +141,7 @@ public function updateOrderStatusToDiv(Request $request)
if ($orderList) {
foreach ($orderList as $kk => $order) {
$orderObj = OrderInfo::find($order->id);
//分账
if (Adapay::handlePaymentConfirmAndDivide($orderObj->order_no, $orderObj->id)) {
$orderObj->is_div = 1;
$orderObj->save();
......@@ -157,6 +157,77 @@ public function updateOrderStatusToDiv(Request $request)
/**
* 轮训用户积分解冻中积分,改成已解冻,积分进入用户账户
* @return bool 返回操作结果
*
* 功能说明:
* 1. 查询所有解冻中(状态为2)的积分记录
* 2. 检查解冻结束日期是否已到
* 3. 将符合条件的记录状态更新为已完成(1)
* 4. 将积分转入用户账户
*
* 使用示例:
* UserPointChangeRec::pointUnfreezeEnd();
*
* 注意事项:
* 1. 仅处理状态为解冻中(2)的积分记录
* 2. 仅处理解冻结束日期小于当前时间的记录
* 3. 操作在事务中进行,保证数据一致性
*/
public static function pointUnfreezeEnd(Request $request)
{
DB::beginTransaction();
try {
// 查询解冻中积分
$records = DB::table('user_point_change_rec')
->where([
'point_state' => 2, // 解冻中
'change_type' => 1 // 增加积分
])
->where('freeze_end_date', '<=', date('Y-m-d H:i:s'))
->get();
if ($records->isEmpty()) {
Log::add('point_unfreeze_end', [
'msg' => '没有找到可完成的解冻积分记录'
]);
return true;
}
foreach ($records as $record) {
// 更新积分记录状态为已完成
DB::table('user_point_change_rec')
->where('id', $record->id)
->update([
'point_state' => 1, // 已完成
'updated_at' => date('Y-m-d H:i:s')
]);
// 将积分转入用户账户
$user = User::find($record->user_id);
$user->balance += $record->point_amount;
$user->total_revenue += $record->point_amount;
$user->save();
}
DB::commit();
Log::add('point_unfreeze_end', [
'count' => count($records),
'msg' => '成功完成积分解冻'
]);
return true;
} catch (\Exception $e) {
DB::rollBack();
Log::add('point_unfreeze_end_error', [
'error' => $e->getMessage()
]);
return false;
}
}
}
......@@ -12,6 +12,7 @@
use App\Models\User;
use App\Models\UserAddress;
use App\Models\OrderDivideRecord;
use App\Models\UserPointChangeRec;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
......@@ -168,22 +169,15 @@ public function info(Request $request)
{
$user = $request->user();
$merchant_id = $user->merchant_id ?? 0;
$total_revenue = $user->total_revenue ?? 0; // 总金额
$merObj = Merchant::where('id', $merchant_id)->first();
$balanceValue = $merObj->balance ?? 0;
$user_id = $user->id ?? 0;
// 使用 Tools 类计算 T+1 未到账金额
$pendingCashout = Tools::calculatePendingCashout($user_id);
// 调整金额计算公式
$displayBalance = $balanceValue + $pendingCashout;
$displayCashout = $total_revenue - $displayBalance;
//计算冻结中、解冻中的积分
$frozen_balance = UserPointChangeRec::where('user_id', $user_id)->where('change_type', 1)->where('point_state', 3)->sum('point_amount');
$thawing_balance = UserPointChangeRec::where('user_id', $user_id)->where('change_type', 1)->where('point_state', 2)->sum('point_amount');
$balance = number_format($displayBalance, 2, '.', '');// 余额
$cashout = number_format($displayCashout, 2, '.', '');// 已提现
$balance = number_format($user->balance, 2, '.', '');// 余额
$frozen_balance = number_format($frozen_balance, 2, '.', '');// 冻结中
$thawing_balance = number_format($thawing_balance, 2, '.', '');// 解冻中
return $this->JsonResponse([
'user_id' => $user->id,
......@@ -194,9 +188,9 @@ public function info(Request $request)
//'status' => $user->status,
'merchant_id' => $merchant_id,
'buycode' => $user->buycode ?? '',
'balance' => $balance ?? 0,
'total_revenue' => $total_revenue ?? 0,
'cashout' => $cashout ?? 0
'balance' => $balance ?? 0,// 可用积分余额
'frozen_balance' => $frozen_balance ?? 0,// 冻结中的积分
'thawing_balance' => $thawing_balance ?? 0 // 解冻中的积分
]);
}
......@@ -480,6 +474,11 @@ public function getMyFriend(Request $request)
//绑定直购码
public function bindBuycode(Request $request)
{
$userObj = $request->user();
if ($userObj->buycode) {
return $this->JsonResponse('已经绑定过直购码');//直接返回成功
}
$code = $request->code ?? '';
$merObj = Merchant::where("buycode", $code)->first();
......@@ -488,7 +487,6 @@ public function bindBuycode(Request $request)
}
DB::beginTransaction();
try {
$userObj = $request->user();
$userObj->buycode = $code;
$userObj->merchant_id = $merObj->id;
$userObj->save();
......
......@@ -6,6 +6,7 @@
use Dcat\Admin\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class OrderDivideRecord extends Model
{
......@@ -13,14 +14,17 @@ class OrderDivideRecord extends Model
use SoftDeletes;
protected $table = 'order_divide_record';
//佣金类别-//就是字段sh_type
public const COMMISSION_TYPE = [
1 => '直推佣金',//需求调整,不再分账,改成积分,支付时抵扣
2 => '间推佣金',//需求调整,不再分账,改成积分,支付时抵扣
3 => '用户取货佣金',
0 => '平台',
1 => '直推', //需求调整,不再分账,改成积分,支付时抵扣
2 => '间推', //需求调整,不再分账,改成积分,支付时抵扣
3 => '商户',
4 => '手动调账分账',
5 => '员工佣金',
5 => '员工',
];
/**
* 收益分配
* @param int $order_id 订单ID
......@@ -40,6 +44,10 @@ public static function divide($order_id, $payconfirm_no = '')
'merchant_member_id' => '',
'merchant_amount' => 0,
];
$employeePreData = [
'employee_member_id' => '',
'employee_amount' => 0,
];
//商户是否实名
$isMerchantRealName = 0;
......@@ -48,6 +56,17 @@ public static function divide($order_id, $payconfirm_no = '')
$isMerchantRealName = ($hfCompanyMObj->status == 'succeeded') ? 1 : 0;
}
//查询用户绑定的员工
$employee = DB::table('store_employee_user_rec')
->where('user_id', $buyer_id)
->first();
$isEmployeeRealName = 0;
if ($employee) {
$employeeObj = StoreAdminUsers::find($employee->employee_id);
$hfEmployeeMObj = HfCompanyMember::where('merchant_id', $employeeObj->merchant_id)->first();
$isEmployeeRealName = ($hfEmployeeMObj->status == 'succeeded') ? 1 : 0;
}
$ogList = OrderGoods::where("order_id", $order_id)->get(); //订单商品
foreach ($ogList as $kk => $item) {
$goods_amount = $item->goods_price * $item->goods_number;
......@@ -61,6 +80,13 @@ public static function divide($order_id, $payconfirm_no = '')
//商户分佣记录
if ($merchant_id && $merchant_commission >= 1 && $merchant_commission < 100) {
$divide_price = number_format($goods_amount * ($merchant_commission / 100), 2);
// 如果有员工绑定,扣除员工佣金部分
if ($employee && $employeeObj->commission_rate >= 1 && $employeeObj->commission_rate < 100) {
$employee_divide = number_format($divide_price * ($employeeObj->commission_rate / 100), 2);
$divide_price -= $employee_divide;
}
//收益直接到商户账户
$merObj = Merchant::find($merchant_id);
//汇付参数
......@@ -72,6 +98,15 @@ public static function divide($order_id, $payconfirm_no = '')
$commissionPreData['merchant_amount'] += $divide_price;
}
}
//员工分佣记录
if ($employee && $employeeObj->commission_rate >= 1 && $employeeObj->commission_rate < 100) {
// 基于商家分到佣金计算员工分佣
$merchant_divide_price = number_format($goods_amount * ($merchant_commission / 100), 2);
$employee_divide_price = number_format($merchant_divide_price * ($employeeObj->commission_rate / 100), 2);
$employeePreData['employee_amount'] += $employee_divide_price;
$employeePreData['employee_member_id'] = $hfEmployeeMObj->member_id;
}
}
//商户佣金
......@@ -82,8 +117,16 @@ public static function divide($order_id, $payconfirm_no = '')
}
}
//员工佣金
if ($employee && $employeePreData['employee_amount'] > 0) {
self::addRecord($sp_ogid, $order_id, $orderObj->goods_amount, $employeePreData['employee_amount'], $employeeObj->commission_rate, $employeeObj->member_id, $buyer_id, 5, $isEmployeeRealName, $payconfirm_no);
if ($isEmployeeRealName) {
array_push($div_members, ['member_id' => $employeePreData['employee_member_id'], 'amount' => sprintf("%.2f", $employeePreData['employee_amount']), 'fee_flag' => 'N']);
}
}
//平台本身
$aimeiyuePrice = $total_amount - $commissionPreData['merchant_amount'];
$aimeiyuePrice = $total_amount - $commissionPreData['merchant_amount'] - $employeePreData['employee_amount'];
self::addRecord($sp_ogid, $order_id, $orderObj->goods_amount, $aimeiyuePrice, '', 0, $buyer_id, 0, 1, $payconfirm_no);
array_push($div_members, ['member_id' => 0, 'amount' => sprintf("%.2f", $aimeiyuePrice), 'fee_flag' => 'Y']);
......@@ -91,7 +134,10 @@ public static function divide($order_id, $payconfirm_no = '')
if (!$isMerchantRealName) {
$commissionPreData['merchant_amount'] = 0;
}
$confirm_amt = $aimeiyuePrice + $commissionPreData['merchant_amount'];
if (!$isEmployeeRealName) {
$employeePreData['employee_amount'] = 0;
}
$confirm_amt = $aimeiyuePrice + $commissionPreData['merchant_amount'] + $employeePreData['employee_amount'];
return ['div_members' => $div_members, 'confirm_amt' => sprintf("%.2f", $confirm_amt)];
}
......@@ -104,7 +150,7 @@ public static function divide($order_id, $payconfirm_no = '')
* @param string $commission 佣金比例
* @param int $um_id 用户或商户ID
* @param int $buyer_id 买家ID
* @param int $sh_type 分账类型 推送类型 1:直推 2:间推 3:公司 4:手动分账 5:员工
* @param int $sh_type 分账类型 推送类型 0:平台 1:直推 2:间推 3:商户 4:手动分账 5:员工
* @param int $isExistAccount 是否实名
* @param string $payconfirm_no 支付确认编号
*/
......@@ -134,4 +180,4 @@ public function users()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
}
\ No newline at end of file
}
......@@ -19,13 +19,22 @@ public function user()
return $this->belongsTo(User::class, 'user_id');
}
//积分来源定义【source】
public const source = [
1 => '直推',
2 => '间推',
3 => '购物',
4 => '退款',
9 => '手动补偿',
];
/**
* 用户积分变更记录
* @param int $user_id 用户ID
* @param int $point_amount 积分数量
* @param int $change_type 变更类型:1-增加,0-减少 (默认1)
* @param int $source 积分来源:1-直推,2-间推,3-购物,9-手动补偿 (默认1)
* @param int $source 积分来源:1-直推,2-间推,3-购物,4-退款,9-手动补偿 (默认1)
* @param int $point_state 积分状态:1-已完成,2-解冻中,3-冻结中 (默认1)
* @param int $order_id 关联订单ID
* @param string $remark 备注信息
......@@ -42,7 +51,7 @@ public function user()
* 1. 积分数量必须为正整数
* 2. 变更类型必须为0或1
*/
public static function pointChangeRecord($user_id, $point_amount, $change_type = 1, $source = 1,$point_state = 1,$order_id='', $remark = '' )
public static function pointChangeRecord($user_id, $point_amount, $change_type = 1, $source = 1,$point_state = 1,$order_id=0, $remark = '' )
{
DB::beginTransaction();
try {
......@@ -119,7 +128,7 @@ public static function pointChangeRecord($user_id, $point_amount, $change_type
* 2. 解冻后状态变为解冻中(2)
* 3. 解冻结束日期为当前时间+7天
*/
public static function pointUnfreeze($orderObj)
public static function pointUnfreezeimg($orderObj)
{
DB::beginTransaction();
try {
......@@ -167,4 +176,7 @@ public static function pointUnfreeze($orderObj)
return false;
}
}
}
......@@ -21,6 +21,14 @@
Route::namespace('App\Http\Controllers\Api')->middleware('acceptJson')->group(function () {
//轮训接口
Route::post('updateOrderStatusToDiv','OrderDivideRecordController@updateOrderStatusToDiv'); //分佣订单解冻轮训
Route::post('pointUnfreezeEnd','OrderDivideRecordController@pointUnfreezeEnd'); //订单积分解冻轮训
//调试路由
Route::post('simulate-login','LoginController@simulateLogin'); //模拟登陆
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment