where('status', 1)->find(); if ($exist && !$rerun) { return $exist; } if ($rerun) { $this->clearDaily($settleDate); } Db::transaction(function () use ($settleDate) { $this->settle($settleDate); }); return DailyReport::where('settle_date', $settleDate)->find(); } catch (\Exception $e) { DailyReport::create([ 'settle_date' => $settleDate, 'status' => 0, 'error_msg' => mb_substr($e->getMessage(), 0, 500), 'createtime' => time(), 'updatetime' => time(), ]); throw $e; } finally { flock($fp, LOCK_UN); fclose($fp); } } protected function settle($settleDate) { $start = strtotime($settleDate . ' 00:00:00'); $end = strtotime($settleDate . ' 23:59:59'); $rateService = new RateService(); $active = new ActiveService(); $orders = $rateService->qualifiedOrderQuery($start, $end)->field('o.id,o.pay_fee')->select(); $orderCount = count($orders); $gmv = 0; foreach ($orders as $order) { $gmv = bcadd((string) $gmv, (string) $order['pay_fee'], 2); } $windowStart = strtotime($settleDate . ' 00:00:00') - (PartnerConfig::activeDays() - 1) * 86400; $tasterIds = $active->activeUserIds($windowStart, $end); $venueIds = $active->activeUserIds($windowStart, $end, ['is_venue' => 1]); $countyIds = $active->activeUserIds($windowStart, $end, ['is_county' => 1]); $madow = (new MadowService())->share($settleDate, $gmv, $tasterIds); $venuePer = (float) PartnerConfig::get('partner_venue_share_per_order', 100); $countyPer = (float) PartnerConfig::get('partner_county_share_per_order', 50); $venuePool = round($orderCount * $venuePer, 2); $countyPool = round($orderCount * $countyPer, 2); $this->cashShare($settleDate, $venueIds, $venuePool, 'venue_share', 'venue', '酒道馆'); $this->cashShare($settleDate, $countyIds, $countyPool, 'county_share', 'county', '区县'); $voucherTotal = (new VoucherService())->releaseByDate($settleDate); $rateLog = $rateService->snapshot($settleDate, $gmv, [ 'madow_pool' => $madow['pool'], 'share_user_count' => $madow['count'], 'venue_pool' => $venuePool, 'county_pool' => $countyPool, ]); DailyReport::create([ 'settle_date' => $settleDate, 'order_count' => $orderCount, 'order_count_n' => $rateLog->order_count_n ?? $rateService->countN(), 'gmv' => $gmv, 'rate' => $rateLog->rate ?? 0, 'madow_pool' => $madow['pool'], 'share_user_count' => $madow['count'], 'venue_pool' => $venuePool, 'venue_user_count' => count($venueIds), 'county_pool' => $countyPool, 'county_user_count' => count($countyIds), 'voucher_release_total' => $voucherTotal, 'status' => 1, 'error_msg' => '', 'createtime' => time(), 'updatetime' => time(), ]); } protected function cashShare($settleDate, $userIds, $pool, $type, $role, $label) { $count = count($userIds); $orderId = PartnerRewardService::dailyOrderId($settleDate); if ($pool <= 0) { return; } if ($count <= 0) { PartnerRewardService::write([ 'order_id' => $orderId, 'user_id' => 0, 'role' => 'system', 'reward_type' => $type, 'amount' => $pool, 'base_amount' => $pool, 'settle_date' => $settleDate, 'ext' => ['formula' => "{$label}分母为0,奖池 {$pool} 进平台"], 'status' => 1, ]); return; } $each = function_exists('bcdiv') ? bcdiv((string) $pool, (string) $count, 2) : number_format($pool / $count, 2, '.', ''); $distributed = 0; foreach ($userIds as $userId) { PartnerRewardService::write([ 'order_id' => $orderId, 'user_id' => $userId, 'role' => $role, 'reward_type' => $type, 'amount' => $each, 'base_amount' => $pool, 'settle_date' => $settleDate, 'ext' => ['formula' => "{$label}奖池 {$pool} / {$count} = {$each}", 'share_user_count' => $count], 'status' => 1, ]); if ((float) $each > 0) { Wallet::change($userId, 'commission', $each, $type, [ 'settle_date' => $settleDate, ]); AgentModel::where('user_id', $userId)->setInc('total_income', $each); } $distributed = bcadd((string) $distributed, (string) $each, 2); } $tail = bcsub((string) $pool, (string) $distributed, 2); if ((float) $tail > 0) { PartnerRewardService::write([ 'order_id' => $orderId, 'user_id' => 0, 'role' => 'system', 'reward_type' => $type, 'amount' => $tail, 'base_amount' => $pool, 'settle_date' => $settleDate, 'ext' => ['formula' => "{$label}尾差 {$tail} 进平台"], 'status' => 1, ]); } } public function clearDaily($settleDate) { (new VoucherService())->rollbackRelease($settleDate); (new MadowService())->rollback($settleDate); $cashRows = RewardModel::where('settle_date', $settleDate) ->whereIn('reward_type', ['venue_share', 'county_share']) ->where('status', 1) ->where('user_id', '>', 0) ->select(); foreach ($cashRows as $row) { if ((float) $row->amount > 0) { Wallet::change($row->user_id, 'commission', -$row->amount, 'reward_back', [ 'settle_date' => $settleDate, 'rerun' => 1, ], '日结重跑扣回'); AgentModel::where('user_id', $row->user_id)->setDec('total_income', $row->amount); } } RewardModel::where('settle_date', $settleDate) ->whereIn('reward_type', PartnerConfig::dailyTypes()) ->delete(); DailyReport::where('settle_date', $settleDate)->delete(); \app\admin\model\shopro\partner\RateLog::where('settle_date', $settleDate)->delete(); } /** * 日结试算(Dry Run,只读模拟计算) * 纯计算昨日或指定日期的分润池、人均分配额、沉淀留存与综合现金拨比,不入账不写表 */ public function preview($date = null) { $settleDate = $date ?: date('Y-m-d', strtotime('-1 day')); $start = strtotime($settleDate . ' 00:00:00'); $end = strtotime($settleDate . ' 23:59:59'); $rateService = new RateService(); $active = new ActiveService(); $orders = $rateService->qualifiedOrderQuery($start, $end)->field('o.id,o.pay_fee')->select(); $orderCount = count($orders); $gmv = 0; foreach ($orders as $order) { $gmv = bcadd((string) $gmv, (string) $order['pay_fee'], 2); } $windowStart = strtotime($settleDate . ' 00:00:00') - (PartnerConfig::activeDays() - 1) * 86400; $tasterIds = $active->activeUserIds($windowStart, $end); $venueIds = $active->activeUserIds($windowStart, $end, ['is_venue' => 1]); $countyIds = $active->activeUserIds($windowStart, $end, ['is_county' => 1]); $tasterCount = count($tasterIds); $venueCount = count($venueIds); $countyCount = count($countyIds); // 汇率预测 $orderCountN = $rateService->countN(); $rate = $rateService->calcRate($orderCountN); // 马豆池 (1:1 GMV) $madowPool = (float) $gmv; $madowEach = $tasterCount > 0 ? (function_exists('bcdiv') ? bcdiv((string) $madowPool, (string) $tasterCount, 8) : number_format($madowPool / $tasterCount, 8, '.', '')) : '0.00000000'; // 酒道馆与区县均分池 $venuePer = (float) PartnerConfig::get('partner_venue_share_per_order', 100); $countyPer = (float) PartnerConfig::get('partner_county_share_per_order', 50); $venuePool = round($orderCount * $venuePer, 2); $countyPool = round($orderCount * $countyPer, 2); $venueEach = $venueCount > 0 ? round($venuePool / $venueCount, 2) : 0; $countyEach = $countyCount > 0 ? round($countyPool / $countyCount, 2) : 0; // 沉淀金额(若无人合格分母为0,则全部沉淀至平台) $venueSaved = $venueCount <= 0 ? $venuePool : 0; $countySaved = $countyCount <= 0 ? $countyPool : 0; $totalSaved = bcadd((string) $venueSaved, (string) $countySaved, 2); // 当日即时奖产生总额(推三返一、创客极差、平级、酒道馆/区县团队奖等) $instantCash = (float) RewardModel::whereBetween('createtime', [$start, $end]) ->whereIn('reward_type', PartnerConfig::instantTypes()) ->where('status', 1) ->sum('amount'); // 当日预计现金支出 = 即时奖 + 酒道馆实际发放 + 区县实际发放 $venueActualOut = $venueCount > 0 ? $venuePool : 0; $countyActualOut = $countyCount > 0 ? $countyPool : 0; $estimatedDailyCash = bcadd((string) $venueActualOut, (string) $countyActualOut, 2); $totalCashPayout = bcadd((string) $instantCash, (string) $estimatedDailyCash, 2); // 综合现金拨比 $payoutRatio = (float) $gmv > 0 ? round(($totalCashPayout / $gmv) * 100, 2) : 0; // 兑换券释放预计 $voucherReleaseEstimate = (float) \app\admin\model\shopro\partner\VoucherPlan::where('status', 1) ->where('released_days', '<', 365) ->sum('daily_amount'); // 是否已经执行过正式日结 $existingReport = DailyReport::where('settle_date', $settleDate)->find(); return [ 'settle_date' => $settleDate, 'is_settled' => ($existingReport && $existingReport->status == 1), 'existing_report' => $existingReport, 'order_count' => $orderCount, 'order_count_n' => $orderCountN, 'gmv' => number_format($gmv, 2, '.', ''), 'rate' => number_format($rate, 8, '.', ''), 'madow' => [ 'pool' => number_format($madowPool, 2, '.', ''), 'user_count' => $tasterCount, 'each' => $madowEach, ], 'venue' => [ 'pool' => number_format($venuePool, 2, '.', ''), 'user_count' => $venueCount, 'each' => number_format($venueEach, 2, '.', ''), 'saved' => number_format($venueSaved, 2, '.', ''), ], 'county' => [ 'pool' => number_format($countyPool, 2, '.', ''), 'user_count' => $countyCount, 'each' => number_format($countyEach, 2, '.', ''), 'saved' => number_format($countySaved, 2, '.', ''), ], 'voucher' => [ 'release_estimate' => number_format($voucherReleaseEstimate, 2, '.', ''), ], 'financial' => [ 'instant_cash' => number_format($instantCash, 2, '.', ''), 'daily_cash' => number_format($estimatedDailyCash, 2, '.', ''), 'total_cash' => number_format($totalCashPayout, 2, '.', ''), 'saved_cash' => number_format($totalSaved, 2, '.', ''), 'payout_ratio' => $payoutRatio . '%', 'payout_ratio_val' => $payoutRatio, 'is_alert' => $payoutRatio > 70, ], ]; } }