model = new OrderModel; } /** * 订单列表 * * @return \think\Response */ public function index() { if (!$this->request->isAjax()) { if (sheep_config('shop.platform.WechatMiniProgram.status')) { // 如果开启了小程序平台 // 设置小程序发货信息管理,消息通知跳转地址,有缓存, 不会每次都设置 try { $uploadshoppingInfo = new \addons\shopro\library\easywechatPlus\WechatMiniProgramShop(Wechat::miniProgram()); $uploadshoppingInfo->checkAndSetMessageJumpPath(); } catch (\Throwable $e) { \think\Log::error('WechatMiniProgram checkAndSetMessageJumpPath error: ' . $e->getMessage()); } } $exportConfig = (new \addons\shopro\library\Export())->getConfig(); $this->assignconfig("save_type", $exportConfig['save_type'] ?? 'download'); return $this->view->fetch(); } $orders = $this->model->withTrashed()->sheepFilter()->with(['user', 'items', 'address', 'activity_orders']) ->paginate(request()->param('list_rows', 10))->each(function ($order) { $order->pay_types = $order->pay_types; $order->pay_types_text = $order->pay_types_text; $order->items = collection($order->items); $order->items->each(function ($item) use ($order) { // 处理每个商品的 activity_order $item->activity_orders = new \think\Collection; foreach ($order->activity_orders as $activityOrder) { if ($activityOrder->goods_ids && in_array($item->goods_id, $activityOrder->goods_ids)) { $item->activity_orders->push($activityOrder); } } return $item; }); })->toArray(); foreach ($orders['data'] as &$order) { $order = $this->model->setOrderItemStatusByOrder($order); } $result = [ 'orders' => $orders, ]; // 查询各个状态下的订单数量 $searchStatus = $this->model->searchStatusList(); // 所有的数量 $result['all'] = $this->model->withTrashed()->sheepFilter(true, function ($filters) { unset($filters['status']); return $filters; })->count(); foreach ($searchStatus as $status => $text) { $result[$status] = $this->model->withTrashed()->sheepFilter(true, function ($filters) use ($status) { $filters['status'] = $status; return $filters; })->count(); } $this->success('获取成功', null, $result); } // 获取数据类型 public function getType() { $type = $this->model->typeList(); $payType = (new PayModel)->payTypeList(); $platform = $this->model->platformList(); $classify = (new \app\admin\model\shopro\activity\Activity)->classifies(); $activityType = $classify['activity']; $promoType = $classify['promo']; $applyRefundStatus = $this->model->applyRefundStatusList(); $status = $this->model->searchStatusList(); $result = [ 'type' => $type, 'pay_type' => $payType, 'platform' => $platform, 'activity_type' => $activityType, 'promo_types' => $promoType, 'apply_refund_status' => $applyRefundStatus, 'status' => $status ]; $data = []; foreach ($result as $key => $list) { $data[$key][] = ['name' => '全部', 'type' => 'all']; foreach ($list as $k => $v) { $data[$key][] = [ 'name' => $v, 'type' => $k ]; } } $this->success('获取成功', null, $data); } /** * 订单详情 * * @param $id * @return \think\Response */ public function detail($id) { if (!$this->request->isAjax()) { $this->assignconfig('order_id', $id); return $this->view->fetch(); } // 更新包裹信息(5分钟缓存) (new ExpressLib)->updateOrderExpress($id); $order = $this->model->withTrashed()->with(['user', 'items', 'address', 'activity_orders', 'pays', 'invoice'])->where('id', $id)->find(); if (!$order) { $this->error(__('No Results were found')); } $order->express = $order->express; if ($order->invoice) { $order->invoice->order_status = $order->invoice->order_status; $order->invoice->order_status_text = $order->invoice->order_status_text; $order->invoice->order_fee = $order->invoice->order_fee; } foreach ($order->activity_orders as $activityOrder) { // 处理每个活动中参与的商品 $activityOrder->items = new \think\Collection(); foreach ($order->items as $item) { if ($activityOrder->goods_ids && in_array($item->goods_id, $activityOrder->goods_ids)) { $activityOrder->items->push($item); } } } foreach ($order->items as $item) { // 处理 order_item 建议退款金额 $item->suggest_refund_fee = $item->suggest_refund_fee; } // 处理未支付订单 item status_code(此方法会将 $order 转为数组) $order = $order->setOrderItemStatusByOrder($order); $partnerSplit = $this->buildPartnerSplit($order); if (is_array($order)) { $order['partner_split'] = $partnerSplit; } else { $order->partner_split = $partnerSplit; } $this->success('获取成功', null, $order); } /** * 收益中心分成明细:精准查询本单实际收益(即时奖列人、均分奖按本单贡献及关联批次真实核算) */ protected function buildPartnerSplit($order) { try { $orderId = intval(is_array($order) ? ($order['id'] ?? 0) : ($order->id ?? 0)); if ($orderId <= 0) { return [ 'items' => [], 'total_cash' => '0.00', 'total_instant_cash' => '0.00', 'total_share_cash' => '0.00', 'total_all_cash' => '0.00', 'total_voucher' => '0.00', 'settle_date' => null, 'has_data' => false, ]; } $shareTypes = ['venue_share', 'county_share', 'madow_share']; $items = []; $totalInstantCash = 0; $totalShareCash = 0; $totalVoucher = 0; // 1. 查询即时收益流水(完全归属于本单的直接奖励) $rows = \app\admin\model\shopro\partner\Reward::with(['user']) ->where('order_id', $orderId) ->order('id', 'asc') ->select(); foreach ($rows as $row) { $type = (string) ($row->reward_type ?? ''); if (in_array($type, $shareTypes, true)) { continue; } $amount = round((float) ($row->amount ?? 0), 2); $baseAmount = round((float) ($row->base_amount ?? 0), 2); $madow = (float) ($row->madow ?? 0); $status = intval($row->status ?? 0); if ($status === 1 && $amount > 0) { $totalInstantCash = round($totalInstantCash + $amount, 2); } if ($type === 'voucher_grant' && $status === 1 && $baseAmount > 0) { $totalVoucher = round($totalVoucher + $baseAmount, 2); } // 友好展示金额 if ($type === 'voucher_grant') { $amountText = number_format($baseAmount, 2, '.', '') . ' 个'; } elseif ($madow > 0) { $amountText = \addons\shopro\service\partner\IdentityService::formatMadow($madow) . ' dmepa'; } elseif ($amount > 0) { $amountText = '¥' . number_format($amount, 2, '.', ''); } else { $amountText = '¥0.00'; } $items[] = [ 'display' => 'user', 'id' => intval($row->id), 'reward_type' => $type, 'reward_type_text' => (string) ($row->reward_type_text ?: \addons\shopro\service\partner\PartnerConfig::rewardTypeText($type)), 'role' => (string) ($row->role ?? ''), 'role_text' => (string) ($row->role_text ?? ''), 'user_id' => intval($row->user_id), 'user' => $row->user ? (is_object($row->user) ? $row->user->toArray() : $row->user) : null, 'amount' => number_format($amount, 2, '.', ''), 'base_amount' => number_format($baseAmount, 2, '.', ''), 'amount_text' => $amountText, 'madow' => \addons\shopro\service\partner\IdentityService::formatMadow($madow), 'status' => $status, 'status_text' => (string) ($row->status_text ?: ( $status === 1 ? '已入账' : ($status === 0 ? '待入账' : ($status === -1 ? '已取消' : '已退回')) )), 'formula' => (string) ($row->formula ?? ''), 'skip_reason_text' => (string) ($row->skip_reason_text ?? ''), 'wallet_type' => (string) ($row->wallet_type ?? ($amount > 0 ? \addons\shopro\service\partner\PartnerConfig::rewardWalletType() : 'system')), 'wallet_type_text' => (string) ($row->wallet_type_text ?: ($amount > 0 ? (\addons\shopro\service\partner\PartnerConfig::rewardWalletType() === 'money' ? '钱包余额' : '佣金钱包') : '平台留存')), ]; } // 2. 兼容传统分销佣金记录(如存在) try { $commRows = \app\admin\model\shopro\commission\Reward::with(['agent.user']) ->where('order_id', $orderId) ->order('id', 'asc') ->select(); if ($commRows) { foreach ($commRows as $crow) { $cCommission = round((float) ($crow->commission ?? 0), 2); $cStatus = intval($crow->status ?? 0); if ($cStatus === 1 && $cCommission > 0) { $totalInstantCash = round($totalInstantCash + $cCommission, 2); } $items[] = [ 'display' => 'user', 'id' => intval($crow->id), 'reward_type' => 'commission', 'reward_type_text' => '分销佣金', 'role' => 'commission', 'role_text' => '分销商', 'user_id' => intval($crow->agent ? ($crow->agent->user_id ?? 0) : 0), 'user' => ($crow->agent && $crow->agent->user) ? (is_object($crow->agent->user) ? $crow->agent->user->toArray() : $crow->agent->user) : null, 'amount' => number_format($cCommission, 2, '.', ''), 'base_amount' => '0.00', 'amount_text' => '¥' . number_format($cCommission, 2, '.', ''), 'madow' => '0.00000000', 'wallet_type' => 'commission', 'wallet_type_text' => '佣金钱包', 'status' => $cStatus, 'status_text' => (string) ($crow->status_text ?: ($cStatus === 1 ? '已入账' : '待入账')), 'formula' => '分销规则佣金', 'skip_reason_text' => '', ]; } } } catch (\Throwable $ce) { // 忽略非核心分销查询异常 } // 3. 精准查询均分加权分红(只核算本单真实注入奖池贡献及其实际参与结算批次) $paidRaw = is_array($order) ? ($order['paid_time'] ?? null) : ($order->getData('paid_time') ?? ($order['paid_time'] ?? null)); $paidTs = 0; $settleDate = null; if ($paidRaw) { $paidTs = is_numeric($paidRaw) ? intval($paidRaw) : strtotime((string) $paidRaw); if ($paidTs > 0) { $settleDate = date('Y-m-d', $paidTs); } } $orderStatus = (string) (is_array($order) ? ($order['status'] ?? '') : ($order->status ?? '')); $isPaid = in_array($orderStatus, ['paid', 'completed'], true) || ($paidTs > 0); if ($isPaid) { // 订单 ext 数据 $orderExtRaw = is_array($order) ? ($order['ext'] ?? []) : ($order->ext ?? []); $orderExt = is_array($orderExtRaw) ? $orderExtRaw : (json_decode($orderExtRaw ?: '', true) ?: []); // 获取本单的规则设定贡献 $orderRules = \addons\shopro\service\partner\PartnerConfig::getOrderEffectiveRules($order); $rewardService = new \addons\shopro\service\partner\PartnerRewardService(); $qualifiedPayFee = $rewardService->getQualifiedPayFee($order); $venueRuleContrib = isset($orderRules['venue_share']) && is_numeric($orderRules['venue_share']) ? round((float) $orderRules['venue_share'], 2) : (float) \addons\shopro\service\partner\PartnerConfig::get('partner_venue_share_per_order', 100); $countyRuleContrib = isset($orderRules['county_share']) && is_numeric($orderRules['county_share']) ? round((float) $orderRules['county_share'], 2) : (float) \addons\shopro\service\partner\PartnerConfig::get('partner_county_share_per_order', 50); // 精准查找包含本单 order_id 的实际结算批次 $matchedBatches = []; $batchCandidates = \app\admin\model\shopro\partner\Reward::where('order_id', '<', 0) ->whereIn('reward_type', $shareTypes) ->where('ext', 'like', '%' . $orderId . '%') ->order('id', 'asc') ->select(); foreach ($batchCandidates as $bc) { $bcExt = is_array($bc->ext) ? $bc->ext : (json_decode($bc->getData('ext') ?? '', true) ?: []); if (!empty($bcExt['orders']) && is_array($bcExt['orders']) && in_array($orderId, $bcExt['orders'])) { $matchedBatches[$bc->reward_type][] = [ 'model' => $bc, 'ext' => $bcExt, ]; } } // 获取当前活跃达标人数(备用) $activeService = new \addons\shopro\service\partner\ActiveService(); $windowStart = time() - (\addons\shopro\service\partner\PartnerConfig::activeDays() - 1) * 86400; $activeVenueCount = count($activeService->activeUserIds($windowStart, time(), ['is_venue' => 1])); $activeCountyCount = count($activeService->activeUserIds($windowStart, time(), ['is_county' => 1])); $activeTasterCount = count($activeService->activeUserIds($windowStart, time())); $shareDefinitions = [ 'venue_share' => [ 'name' => '酒道馆加权奖', 'role' => 'venue', 'role_text' => '酒道馆', 'unit' => '元', 'order_contrib' => $venueRuleContrib, 'wallet_type' => \addons\shopro\service\partner\PartnerConfig::rewardWalletType(), 'wallet_type_text' => \addons\shopro\service\partner\PartnerConfig::rewardWalletType() === 'money' ? '钱包余额' : '佣金钱包', 'settled_key' => 'venue_settled', 'default_user_count' => $activeVenueCount, ], 'county_share' => [ 'name' => '区县加权奖', 'role' => 'county', 'role_text' => '区县', 'unit' => '元', 'order_contrib' => $countyRuleContrib, 'wallet_type' => \addons\shopro\service\partner\PartnerConfig::rewardWalletType(), 'wallet_type_text' => \addons\shopro\service\partner\PartnerConfig::rewardWalletType() === 'money' ? '钱包余额' : '佣金钱包', 'settled_key' => 'county_settled', 'default_user_count' => $activeCountyCount, ], 'madow_share' => [ 'name' => '每日获得dmepa', 'role' => 'taster', 'role_text' => '品酒师', 'unit' => 'dmepa', 'order_contrib' => $qualifiedPayFee, 'wallet_type' => 'madow', 'wallet_type_text' => '分红dmepa', 'settled_key' => 'madow_settled', 'default_user_count' => $activeTasterCount, ], ]; foreach ($shareDefinitions as $type => $def) { $batchList = $matchedBatches[$type] ?? []; $contribPool = round((float) $def['order_contrib'], 2); if (!empty($batchList)) { // 1. 已在实际批次中结算完成 $firstItem = $batchList[0]; $firstModel = $firstItem['model']; $batchExt = $firstItem['ext']; $ordersInBatch = is_array($batchExt['orders'] ?? null) ? $batchExt['orders'] : []; $batchOrderCount = max(1, count($ordersInBatch)); // 参与用户数 $userCount = intval($batchExt['share_user_count'] ?? count(array_filter($batchList, function ($r) { return intval($r['model']->user_id ?? 0) > 0; }))); if ($type === 'venue_share' || $type === 'county_share') { $totalShareCash = round($totalShareCash + $contribPool, 2); } $batchTotalPool = round((float) ($batchExt['pool'] ?? ($batchExt['gmv'] ?? $firstModel->base_amount)), 2); if ($userCount > 0) { if ($type === 'madow_share') { $eachContrib = function_exists('bcdiv') ? bcdiv((string) $contribPool, (string) $userCount, 8) : number_format($contribPool / $userCount, 8, '.', ''); $eachContribText = \addons\shopro\service\partner\IdentityService::formatMadow($eachContrib); $batchEach = function_exists('bcdiv') ? bcdiv((string) $batchTotalPool, (string) $userCount, 8) : number_format($batchTotalPool / $userCount, 8, '.', ''); $batchEachText = \addons\shopro\service\partner\IdentityService::formatMadow($batchEach); $amountText = $eachContribText . ' 枚/人'; $tip = $batchOrderCount > 1 ? "日结均分(结算批次 #{$firstModel->order_id},合并 {$batchOrderCount} 单)" : "日结均分(结算批次 #{$firstModel->order_id})"; $formula = $batchOrderCount > 1 ? "本单GMV {$contribPool} / {$userCount} = {$eachContribText} 枚/人(批次合计 GMV {$batchTotalPool} / {$userCount} = {$batchEachText})" : "GMV {$contribPool} / {$userCount} = {$eachContribText} 枚/人"; } else { $eachContrib = function_exists('bcdiv') ? bcdiv((string) $contribPool, (string) $userCount, 2) : number_format($contribPool / $userCount, 2, '.', ''); $batchEach = function_exists('bcdiv') ? bcdiv((string) $batchTotalPool, (string) $userCount, 2) : number_format($batchTotalPool / $userCount, 2, '.', ''); $amountText = '¥' . $eachContrib . '/人'; $tip = $batchOrderCount > 1 ? "日结均分(结算批次 #{$firstModel->order_id},合并 {$batchOrderCount} 单)" : "日结均分(结算批次 #{$firstModel->order_id})"; $formula = $batchOrderCount > 1 ? "本单贡献 {$contribPool} / {$userCount} = {$eachContrib}(批次总奖池 {$batchTotalPool} / {$userCount} = {$batchEach})" : "奖池 {$contribPool} / {$userCount} = {$eachContrib}"; } $items[] = [ 'display' => 'share', 'id' => intval($firstModel->id), 'reward_type' => $type, 'reward_type_text' => $def['name'], 'role' => $def['role'], 'role_text' => $def['role_text'], 'user_id' => 0, 'user' => null, 'user_count' => $userCount, 'each_amount' => $type === 'madow_share' ? '0.00' : $eachContrib, 'each_madow' => $type === 'madow_share' ? $eachContribText : '0', 'total_amount' => number_format($contribPool, 2, '.', ''), 'total_madow' => \addons\shopro\service\partner\IdentityService::formatMadow($contribPool), 'batch_total_amount' => number_format($batchTotalPool, 2, '.', ''), 'amount_text' => $amountText, 'wallet_type' => $def['wallet_type'], 'wallet_type_text' => $def['wallet_type_text'], 'settle_date' => $firstModel->settle_date ?: $settleDate, 'status' => 1, 'status_text' => '已入账', 'unit' => $def['unit'], 'formula' => $formula, 'skip_reason_text' => '', 'tip' => $tip, ]; } else { // 分母为 0 进平台留存 $tip = "分母为0,本单奖池进平台留存"; $items[] = [ 'display' => 'share', 'id' => intval($firstModel->id), 'reward_type' => $type, 'reward_type_text' => $def['name'], 'role' => 'system', 'role_text' => '平台', 'user_id' => 0, 'user' => null, 'user_count' => 0, 'each_amount' => '0.00', 'each_madow' => '0', 'total_amount' => number_format($contribPool, 2, '.', ''), 'total_madow' => \addons\shopro\service\partner\IdentityService::formatMadow($contribPool), 'batch_total_amount' => number_format($batchTotalPool, 2, '.', ''), 'amount_text' => $type === 'madow_share' ? (\addons\shopro\service\partner\IdentityService::formatMadow($contribPool) . ' dmepa') : ('¥' . number_format($contribPool, 2, '.', '')), 'wallet_type' => 'system', 'wallet_type_text' => '平台留存', 'settle_date' => $firstModel->settle_date ?: $settleDate, 'status' => 1, 'status_text' => '已入账', 'unit' => $def['unit'], 'formula' => "参与人数为0,本单贡献 {$contribPool} 进平台", 'skip_reason_text' => '', 'tip' => $tip, ]; } } else { // 2. 尚未结算(待结算状态,展示本单贡献与预估) $userCount = $def['default_user_count']; if ($userCount > 0) { if ($type === 'madow_share') { $eachContrib = function_exists('bcdiv') ? bcdiv((string) $contribPool, (string) $userCount, 8) : number_format($contribPool / $userCount, 8, '.', ''); $eachContribText = \addons\shopro\service\partner\IdentityService::formatMadow($eachContrib); $amountText = $eachContribText . ' 枚/人 (预估)'; $formula = "待结算预估:本单GMV {$contribPool} / 当前达标 {$userCount} 人 = {$eachContribText} 枚/人"; } else { $eachContrib = function_exists('bcdiv') ? bcdiv((string) $contribPool, (string) $userCount, 2) : number_format($contribPool / $userCount, 2, '.', ''); $amountText = '¥' . $eachContrib . '/人 (预估)'; $formula = "待结算预估:本单贡献 {$contribPool} / 当前达标 {$userCount} 人 = {$eachContrib}"; } } else { $amountText = $type === 'madow_share' ? '0 dmepa' : '¥0.00'; $formula = "当前达标人数为0,结算时若无合格者将进平台留存"; } $items[] = [ 'display' => 'share', 'id' => 0, 'reward_type' => $type, 'reward_type_text' => $def['name'], 'role' => $def['role'], 'role_text' => $def['role_text'], 'user_id' => 0, 'user' => null, 'user_count' => $userCount, 'each_amount' => $type === 'madow_share' ? '0.00' : ($userCount > 0 ? $eachContrib : '0.00'), 'each_madow' => $type === 'madow_share' ? ($userCount > 0 ? $eachContribText : '0') : '0', 'total_amount' => number_format($contribPool, 2, '.', ''), 'total_madow' => \addons\shopro\service\partner\IdentityService::formatMadow($contribPool), 'batch_total_amount' => number_format($contribPool, 2, '.', ''), 'amount_text' => $amountText, 'wallet_type' => $def['wallet_type'], 'wallet_type_text' => $def['wallet_type_text'], 'settle_date' => $settleDate, 'status' => 0, 'status_text' => '待结算', 'unit' => $def['unit'], 'formula' => $formula, 'skip_reason_text' => '', 'tip' => '待日结均分结算', ]; } } } $totalAllCash = round($totalInstantCash + $totalShareCash, 2); return [ 'items' => $items, 'total_cash' => number_format($totalInstantCash, 2, '.', ''), // 直入钱包余额的即时现金 'total_instant_cash' => number_format($totalInstantCash, 2, '.', ''), 'total_share_cash' => number_format($totalShareCash, 2, '.', ''), 'total_all_cash' => number_format($totalAllCash, 2, '.', ''), 'total_voucher' => number_format($totalVoucher, 2, '.', ''), 'settle_date' => $settleDate, 'has_data' => count($items) > 0, ]; } catch (\Throwable $e) { \think\Log::error('buildPartnerSplit error: ' . $e->getMessage()); return [ 'items' => [], 'total_cash' => '0.00', 'total_instant_cash' => '0.00', 'total_share_cash' => '0.00', 'total_all_cash' => '0.00', 'total_voucher' => '0.00', 'settle_date' => null, 'has_data' => false, ]; } } /** * 批量发货渲染模板 * * @return void */ public function batchDispatch() { return $this->view->fetch(); } /** * 批量发货渲染模板 * * @return void */ public function dispatchList() { return $this->view->fetch(); } /** * 手动发货 * * @return void */ public function customDispatch() { if (!$this->request->isAjax()) { return $this->view->fetch(); } $params = $this->request->only(['order_id', 'order_item_ids', 'custom_type', 'custom_content']); $this->svalidate($params, '.custom_dispatch'); Db::transaction(function () use ($params) { $service = new OrderDispatchService($params); $service->customDispatch($params); }); $this->success('发货成功'); } /** * 发货 * * @description * 支持分包裹发货 * 支持手动发货 * 支持上传发货单发货 * 支持推送api运单发货 默认使用配置项 * 支持修改发货信息 * 支持取消发货 * * @remark 此处接口设计如此复杂是因为考虑到权限的问题,订单发货权限可以完成所有发货行为 * * @param array $action 发货行为(默认:confirm=确认发货, cancel=取消发货, change=修改运单, multiple=解析批量发货单) * @param int $order_id 订单id * @param array $order_item_ids 订单商品id * @param string $method 发货方式(input=手动发货, api=推送运单, upload=上传发货单) * @param array $sender 发货人信息 * @param array $express 物流信息 * * @return \think\Response */ public function dispatch() { if (!$this->request->isAjax()) { return $this->view->fetch(); } $params = $this->request->only(['action', 'order_id', 'order_ids', 'order_item_ids', 'method', 'sender', 'express', 'order_express_id']); $action = $params['action'] ?? 'confirm'; if (!in_array($action, ['confirm', 'cancel', 'change', 'multiple'])) { $this->error('发货参数错误'); } $service = new OrderDispatchService($params); switch ($action) { case 'confirm': $express = $service->confirm($params); $this->success('发货成功', null, $express); break; case 'cancel': $result = $service->cancel($params); if ($result) { $this->success('取消发货成功'); } break; case 'change': $express = $service->change($params); $this->success('修改成功', null, $express); break; case 'multiple': $params['file'] = $this->request->file('file'); $result = $service->multiple($params); $this->success('待发货列表', null, $result); break; } $this->error('操作失败'); } /** * 获取物流快递信息 */ public function updateExpress($order_express_id = 0) { $type = $this->request->param('type'); // 获取包裹 $orderExpress = OrderExpressModel::where('id', $order_express_id)->find(); if (!$orderExpress) { $this->error('包裹不存在'); } $orderAddress = OrderAddressModel::where('order_id', $orderExpress->order_id)->find(); if (!$orderAddress) { $this->error('订单不存在'); } $expressLib = new ExpressLib(); try { if ($type == 'subscribe') { // 重新订阅 $expressLib->subscribe([ 'express_code' => $orderExpress['express_code'], 'express_no' => $orderExpress['express_no'], 'mobile' => $orderAddress['mobile'] ]); } else { // 手动查询 $result = $expressLib->search([ 'order_id' => $orderExpress['order_id'], 'express_code' => $orderExpress['express_code'], 'express_no' => $orderExpress['express_no'], 'mobile' => $orderAddress['mobile'] ], $orderExpress); } } catch (HttpResponseException $e) { $data = $e->getResponse()->getData(); $message = $data ? ($data['msg'] ?? '') : $e->getMessage(); $this->error($message); } catch (\Exception $e) { $this->error(($type == 'subscribe' ? '订阅失败:' : '刷新失败:') . $e->getMessage()); } $this->success(($type == 'subscribe' ? '订阅成功' : '刷新成功')); } /** * 线下付款,确认收货 */ public function offlineConfirm($id) { $admin = $this->auth->getUserInfo(); $order = OrderModel::offline()->where('id', $id)->find(); if (!$order) { $this->error(__('No Results were found')); } $order = Db::transaction(function () use ($order, $admin) { // 加锁读订单 $order = $order->lock(true)->find($order->id); $user = User::get($order->user_id); $payOper = new PayOper($user); $order = $payOper->offline($order, $order->remain_pay_fee, 'order'); // 订单会变为已支付 paid // 触发订单支付完成事件 $data = ['order' => $order, 'user' => $user]; \think\Hook::listen('order_offline_paid_after', $data); $orderOper = new OrderOper(); // 确认收货 $order = $orderOper->confirm($order, [], $admin, 'admin'); return $order; }); $this->success('收货成功', $order); } /** * 线下付款,拒收 */ public function offlineRefuse($id) { $admin = $this->auth->getUserInfo(); $order = OrderModel::offline()->where('id', $id)->find(); if (!$order) { $this->error(__('No Results were found')); } $order = Db::transaction(function () use ($order, $admin) { // 加锁读订单 $order = $order->lock(true)->find($order->id); // 拒收 $orderOper = new OrderOper(); $order = $orderOper->refuse($order, $admin, 'admin'); // 交易关闭 $order = $orderOper->close($order, $admin, 'admin', '用户拒绝收货,管理员关闭订单', ['closed_type' => 'refuse']); return $order; }); $this->success('收货成功', $order); } /** * 订单改价,当剩余应支付金额为 0 时,订单将自动支付 * * @param int $id * @return void */ public function changeFee($id) { if (!$this->request->isAjax()) { return $this->view->fetch(); } $admin = $this->auth->getUserInfo(); $params = $this->request->param(); $this->svalidate($params, '.change_fee'); $order = $this->model->unpaid()->where('id', $id)->find(); if (!$order) { $this->error('订单不可改价'); } Db::transaction(function () use ($order, $admin, $params) { $pay_fee = $params['pay_fee']; $change_msg = $params['change_msg']; $payOper = new PayOper($order->user_id); $payed_fee = $payOper->getPayedFee($order, 'order'); if ($pay_fee < $payed_fee) { $this->error('改价金额不能低于订单已支付金额'); } // 记录原始值 $last_pay_fee = $order->pay_fee; // 上次pay_fee,非原始 pay_fee $order->pay_fee = $pay_fee; $order->save(); OrderActionModel::add($order, null, $admin, 'admin', "应支付金额由 ¥" . $last_pay_fee . " 改为 ¥" . $pay_fee . ",改价原因:" . $change_msg); // 检查订单支付状态, 改价可以直接将订单变为已支付 $order = $payOper->checkAndPaid($order, 'order'); }); $this->success('改价成功'); } /** * 修改收货人信息 * * @param Request $request * @param integer $id * @return void */ public function editConsignee($id) { $admin = $this->auth->getUserInfo(); $params = $this->request->param(); $this->svalidate($params, '.edit_consignee'); $order = $this->model->withTrashed()->where('id', $id)->find(); if (!$order) { $this->error(__('No Results were found')); } Db::transaction(function () use ($admin, $order, $params) { $orderAddress = OrderAddressModel::where('order_id', $order->id)->find(); if (!$orderAddress) { $this->error(__('No Results were found')); } $orderAddress->consignee = $params['consignee']; $orderAddress->mobile = $params['mobile']; $orderAddress->province_name = $params['province_name']; $orderAddress->city_name = $params['city_name']; $orderAddress->district_name = $params['district_name']; $orderAddress->address = $params['address']; $orderAddress->province_id = $params['province_id']; $orderAddress->city_id = $params['city_id']; $orderAddress->district_id = $params['district_id']; $orderAddress->save(); OrderActionModel::add($order, null, $admin, 'admin', "修改订单收货人信息"); }); $this->success('收货人信息修改成功'); } /** * 编辑商家备注 * * @param Request $request * @param integer $id * @return void */ public function editMemo($id) { if (!$this->request->isAjax()) { return $this->view->fetch(); } $admin = $this->auth->getUserInfo(); $params = $this->request->param(); $this->svalidate($params, '.edit_memo'); $order = $this->model->withTrashed()->where('id', $id)->find(); if (!$order) { $this->error(__('No Results were found')); } Db::transaction(function () use ($admin, $order, $params) { $order->memo = $params['memo']; $order->save(); OrderActionModel::add($order, null, $admin, 'admin', "修改卖家备注:" . $params['memo']); }); $this->success('卖家备注修改成功'); } /** * 拒绝用户全额退款申请 * * @param Request $request * @param integer $id * @return void */ public function applyRefundRefuse($id) { $admin = $this->auth->getUserInfo(); $params = $this->request->param(); // $this->svalidate($params, '.apply_refund_refuse'); $order = $this->model->withTrashed()->paid()->applyRefundIng()->where('id', $id)->find(); if (!$order) { $this->error('订单未找到或不可拒绝申请'); } Db::transaction(function () use ($admin, $order, $params) { $order->apply_refund_status = OrderModel::APPLY_REFUND_STATUS_REFUSE; $order->save(); OrderActionModel::add($order, null, $admin, 'admin', "拒绝用户申请全额退款"); }); $this->success('拒绝申请成功'); } /** * 全额退款 (必须没有进行过任何退款才能使用) * * @param Request $request * @param integer $id * @return void */ public function fullRefund($id) { if (!$this->request->isAjax()) { return $this->view->fetch(); } $admin = $this->auth->getUserInfo(); $admin = Admin::where('id', $admin['id'])->find(); $params = $this->request->param(); $refund_type = $params['refund_type'] ?? 'back'; Db::transaction(function () use ($admin, $id, $refund_type) { $order = $this->model->paid()->where('id', $id)->lock(true)->find(); if (!$order) { $this->error('订单不存在或不可退款'); } $orderRefund = new OrderRefund($order); $orderRefund->fullRefund($admin, [ 'refund_type' => $refund_type, 'remark' => '平台主动全额退款' ]); }); $this->success('全额退款成功'); } /** * 订单单商品退款 * * @param Request $request * @param integer $id * @param integer $item_id * @return void */ public function refund($id, $item_id) { if (!$this->request->isAjax()) { return $this->view->fetch(); } $admin = $this->auth->getUserInfo(); $params = $this->request->param(); $this->svalidate($params, '.refund'); $refund_money = round(floatval($params['refund_money']), 2); $refund_type = $params['refund_type'] ?? 'back'; if ($refund_money < 0) { $this->error('请输入正确的退款金额'); } $order = $this->model->paid()->where('id', $id)->find(); if (!$order) { $this->error('订单不存在或不可退款'); } $item = OrderItem::where('order_id', $order->id)->where('id', $item_id)->find(); if (!$item) { $this->error(__('No Results were found')); } if (in_array($item['refund_status'], [ OrderItem::REFUND_STATUS_AGREE, OrderItem::REFUND_STATUS_COMPLETED, ])) { $this->error('订单商品已退款,不能重复退款'); } $payOper = new PayOper($order->user_id); // 获取订单最大可退款金额(不含积分抵扣金额) $remain_max_refund_money = $payOper->getRemainRefundMoney($order->id); // 如果退款金额大于订单支付总金额 if (bccomp((string)$refund_money, $remain_max_refund_money, 2) === 1) { $this->error('退款总金额不能大于实际支付金额'); } Db::transaction(function () use ($admin, $order, $item, $refund_money, $refund_type) { // 重新锁定读查询 orderItem $item = OrderItem::where('order_id', $order->id)->lock(true)->where('id', $item->id)->find(); if (!$item) { $this->error('订单不存在或不可退款'); } $orderRefund = new OrderRefund($order); $orderRefund->refund($item, $refund_money, $admin, [ 'refund_type' => $refund_type, 'remark' => '平台主动退款' ]); }); $this->success('退款成功'); } /** * 获取订单操作记录 * * @param integer $id * @return void */ public function action($id) { if (!$this->request->isAjax()) { return $this->view->fetch(); } $actions = OrderActionModel::where('order_id', $id)->order('id', 'desc')->select(); $morphs = [ 'user' => \app\admin\model\shopro\user\User::class, 'admin' => \app\admin\model\Admin::class, 'system' => \app\admin\model\Admin::class, ]; $actions = morph_to($actions, $morphs, ['oper_type', 'oper_id']); foreach ($actions as &$action) { $action['oper'] = Operator::info($action['oper_type'], $action['oper'] ?? null); } $this->success('获取成功', null, $actions); } public function export() { $cellTitles = [ // 订单表字段 'order_id' => 'Id', 'order_sn' => '订单号', 'type_text' => '订单类型', 'user_nickname' => '下单用户', 'user_mobile' => '手机号', 'status_text' => '订单状态', 'pay_text' => '支付状态', 'pay_types_text' => '支付类型', 'remark' => '用户备注', 'memo' => '卖家备注', 'order_amount' => '订单总金额', 'score_amount' => '积分支付数量', 'dispatch_amount' => '运费', 'pay_fee' => '应付总金额', 'real_pay_fee' => '实付总金额', 'remain_pay_fee' => '剩余支付金额', 'total_discount_fee' => '总优惠金额', 'coupon_discount_fee' => '优惠券金额', 'promo_discount_fee' => '营销优惠金额', 'paid_time' => '支付完成时间', 'platform_text' => '交易平台', 'consignee_info' => '收货信息', 'createtime' => '下单时间', // 订单商品表字段 'activity_type_text' => '活动', 'promo_types' => '促销', 'goods_title' => '商品名称', 'goods_sku_text' => '商品规格', 'goods_num' => '购买数量', 'goods_original_price' => '商品原价', 'goods_price' => '商品价格', 'goods_weight' => '商品重量', 'discount_fee' => '优惠金额', 'goods_pay_fee' => '商品支付金额', 'dispatch_type_text' => '发货方式', 'dispatch_status_text' => '发货状态', 'aftersale_refund' => '售后/退款', 'comment_status_text' => '评价状态', 'refund_fee' => '退款金额', 'refund_msg' => '退款原因', 'express_name' => '快递公司', 'express_no' => '快递单号', ]; // 数据总条数 $total = $this->model->withTrashed()->sheepFilter()->count(); if ($total <= 0) { $this->error('导出数据为空'); } $export = new \addons\shopro\library\Export(); $params = [ 'file_name' => '订单列表', 'cell_titles' => $cellTitles, 'total' => $total, 'is_sub_cell' => true, 'sub_start_cell' => 'activity_type_text', 'sub_field' => 'items' ]; $total_order_amount = 0; $total_pay_fee = 0; $total_real_pay_fee = 0; $total_discount_fee = 0; $total_score_amount = 0; $result = $export->export($params, function ($pages) use (&$total_order_amount, &$total_pay_fee, &$total_real_pay_fee, &$total_discount_fee, &$total_score_amount, $total) { $datas = $this->model->withTrashed()->sheepFilter()->with(['user', 'items' => function ($query) { $query->with(['express']); }, 'address']) ->limit((($pages['page'] - 1) * $pages['list_rows']), $pages['list_rows']) ->select(); $datas = collection($datas); $datas = $datas->each(function ($order) { $order->pay_types_text = $order->pay_types_text; })->toArray(); $newDatas = []; foreach ($datas as &$order) { $order = $this->model->setOrderItemStatusByOrder($order); // 收货人信息 $consignee_info = ''; if ($order['address']) { $address = $order['address']; $consignee_info = ($address['consignee'] ? ($address['consignee'] . ':' . $address['mobile'] . '-') : '') . ($address['province_name'] . '-' . $address['city_name'] . '-' . $address['district_name']) . ' ' . $address['address']; } $data = [ 'order_id' => $order['id'], 'order_sn' => $order['order_sn'], 'type_text' => $order['type_text'], 'user_nickname' => $order['user'] ? $order['user']['nickname'] : '-', 'user_mobile' => $order['user'] ? $order['user']['mobile'] . ' ' : '-', 'status_text' => $order['status_text'], 'pay_text' => in_array($order['status'], [OrderModel::STATUS_PAID, OrderModel::STATUS_COMPLETED]) ? '已支付' : '未支付', 'pay_types_text' => is_array($order['pay_types_text']) ? join(',', $order['pay_types_text']) : ($order['pay_types_text'] ?: ''), 'remark' => $order['remark'], 'memo' => $order['memo'], 'order_amount' => $order['order_amount'], 'score_amount' => $order['score_amount'], 'dispatch_amount' => $order['dispatch_amount'], 'pay_fee' => $order['pay_fee'], 'real_pay_fee' => bcsub($order['pay_fee'], $order['remain_pay_fee'], 2), 'remain_pay_fee' => $order['remain_pay_fee'], 'total_discount_fee' => $order['total_discount_fee'], 'coupon_discount_fee' => $order['coupon_discount_fee'], 'promo_discount_fee' => $order['promo_discount_fee'], 'paid_time' => $order['paid_time'], 'platform_text' => $order['platform_text'], 'consignee_info' => $consignee_info, 'createtime' => $order['createtime'], ]; $items = []; foreach ($order['items'] as $item) { $items[] = [ 'activity_type_text' => $item['activity_type_text'], 'promo_types_text' => is_array($item['promo_types_text']) ? join(',', $item['promo_types_text']) : ($item['promo_types_text'] ?: '-'), 'goods_title' => $item['goods_title'], 'goods_sku_text' => $item['goods_sku_text'], 'goods_num' => $item['goods_num'], 'goods_original_price' => $item['goods_original_price'], 'goods_price' => $item['goods_price'], 'goods_weight' => $item['goods_weight'], 'discount_fee' => $item['discount_fee'], 'goods_pay_fee' => $item['pay_fee'], 'dispatch_type_text' => $item['dispatch_type_text'], 'dispatch_status_text' => $item['dispatch_status_text'], 'aftersale_refund' => $item['aftersale_status_text'] . '/' . $item['refund_status_text'], 'comment_status_text' => $item['comment_status_text'], 'refund_fee' => $item['refund_fee'], 'refund_msg' => $item['refund_msg'], 'express_name' => $item['express'] ? $item['express']['express_name'] : '-', 'express_no' => $item['express'] ? $item['express']['express_no'] . ' ' : '-', ]; } $data['items'] = $items; $newDatas[] = $data; } $total_order_amount += array_sum(array_column($newDatas, 'order_amount')); $total_score_amount += array_sum(array_column($newDatas, 'score_amount')); $total_pay_fee += array_sum(array_column($newDatas, 'pay_fee')); $total_real_pay_fee += array_sum(array_column($newDatas, 'real_pay_fee')); $total_discount_fee += array_sum(array_column($newDatas, 'discount_fee')); if ($pages['is_last_page']) { $newDatas[] = ['order_id' => "订单总数:" . $total . ";订单总金额:¥" . $total_order_amount . ";优惠总金额:¥" . $total_discount_fee . ";应付总金额:¥" . $total_pay_fee . ";实付总金额:¥" . $total_real_pay_fee . ";支付总积分:" . $total_score_amount]; } return $newDatas; }); $this->success('导出成功' . (isset($result['file_path']) && $result['file_path'] ? ',请在服务器: “' . $result['file_path'] . '” 查看' : ''), null, $result); } public function exportDelivery() { $cellTitles = [ // 订单表字段 'order_id' => 'Id', 'order_sn' => '订单号', 'type_text' => '订单类型', 'consignee_info' => '收货信息', 'remark' => '用户备注', 'memo' => '商家备注', 'createtime' => '下单时间', // 订单商品表字段 'order_item_id' => '子订单Id', 'goods_title' => '商品名称', 'goods_sku_text' => '商品规格', 'goods_num' => '购买数量', // 'dispatch_fee' => '发货费用', 'dispatch_type_text' => '发货方式', 'dispatch_status_text' => '发货状态', 'aftersale_refund' => '售后/退款', 'express_no' => '快递单号', ]; // 数据总条数 $total = $this->model->sheepFilter()->count(); // nosend 加了 noApplyRefund if ($total <= 0) { $this->error('导出数据为空'); } $export = new \addons\shopro\library\Export(); $params = [ 'file_name' => '订单发货单列表', 'cell_titles' => $cellTitles, 'total' => $total, 'is_sub_cell' => true, 'sub_start_cell' => 'order_item_id', 'sub_field' => 'items' ]; $result = $export->export($params, function ($pages) use (&$total) { // 未申请全额退款的 $datas = $this->model->sheepFilter()->with(['user', 'items' => function ($query) { // nosend 加了 noApplyRefund $query->with(['express']); }, 'address']) ->limit((($pages['page'] - 1) * $pages['list_rows']), $pages['list_rows']) ->select(); $datas = collection($datas)->toArray(); $newDatas = []; foreach ($datas as &$order) { $order = $this->model->setOrderItemStatusByOrder($order); if (in_array($order['status_code'], ['groupon_ing', 'groupon_invalid'])) { // 拼团正在进行中,不发货 $total--; // total 减少 1 continue; } // 收货人信息 $consignee_info = ''; if ($order['address']) { $address = $order['address']; $consignee_info = ($address['consignee'] ? ($address['consignee'] . ':' . $address['mobile'] . '-') : '') . ($address['province_name'] . '-' . $address['city_name'] . '-' . $address['district_name']) . ' ' . $address['address']; } $data = [ 'order_id' => $order['id'], 'order_sn' => $order['order_sn'], 'type_text' => $order['type_text'], 'consignee_info' => $consignee_info, 'remark' => $order['remark'], 'memo' => $order['memo'], 'createtime' => $order['createtime'] ]; $items = []; foreach ($order['items'] as $k => $item) { // 未发货,并且未退款,并且未在申请售后中,并且是快递物流的 if ( $item['dispatch_status'] == OrderItem::DISPATCH_STATUS_NOSEND && !in_array($item['refund_status'], [OrderItem::REFUND_STATUS_AGREE, OrderItem::REFUND_STATUS_COMPLETED]) && $item['aftersale_status'] != OrderItem::AFTERSALE_STATUS_ING && $item['dispatch_type'] == 'express' ) { $items[] = [ 'order_item_id' => $item['id'], 'goods_title' => strpos($item['goods_title'], '=') === 0 ? ' ' . $item['goods_title'] : $item['goods_title'], 'goods_sku_text' => $item['goods_sku_text'], 'goods_num' => $item['goods_num'], // 'dispatch_fee' => $item['dispatch_fee'], 'dispatch_type_text' => $item['dispatch_type_text'], 'dispatch_status_text' => $item['dispatch_status_text'], 'aftersale_refund' => $item['aftersale_status_text'] . '/' . $item['refund_status_text'], 'express_no' => $item['express'] ? $item['express']['express_no'] . ' ' : '', ]; } } $data['items'] = $items; $newDatas[] = $data; }; if ($pages['is_last_page']) { $newDatas[] = ['order_id' => "订单总数(仅快递物流的待发货订单):" . $total . ";备注:订单中同一包裹请填写相同运单号"]; } return $newDatas; }); $this->success('导出成功' . (isset($result['file_path']) && $result['file_path'] ? ',请在服务器: “' . $result['file_path'] . '” 查看' : ''), null, $result); } }