where('parent_admin_id', $adminId) ->where('status', 'enable') ->column('id'); $all = []; foreach ($childIds as $cid) { $cid = intval($cid); if ($cid <= 0) { continue; } $all[] = $cid; foreach (self::getDescendantAdminIds($cid) as $descId) { $all[] = $descId; } } return $all; } /** * 非超管可见管理员 ID;超管返回空数组表示不限制 * * @return int[] */ public static function getVisibleAdminIdsForOperator(int $operatorAdminId, bool $isSuperAdmin): array { if ($isSuperAdmin || $operatorAdminId <= 0) { return []; } $ids = self::getDescendantAdminIds($operatorAdminId); $ids[] = $operatorAdminId; return array_values(array_unique($ids)); } /** * @return array{used_rate:string,remaining_rate:string} */ public static function getShareRemainder(int $parentAdminId, ?int $excludeAdminId = null): array { $used = '0.00'; if ($parentAdminId <= 0) { return ['used_rate' => $used, 'remaining_rate' => '100.00']; } $query = Db::name('admin') ->where('parent_admin_id', $parentAdminId) ->where('status', 'enable'); if ($excludeAdminId !== null && $excludeAdminId > 0) { $query->where('id', '<>', $excludeAdminId); } $rows = $query->column('commission_share_rate'); foreach ($rows as $rate) { if ($rate === null || $rate === '') { continue; } $used = bcadd($used, bcadd(strval($rate), '0', 2), 2); } $remaining = bcsub('100.00', $used, 2); if (bccomp($remaining, '0', 2) < 0) { $remaining = '0.00'; } return ['used_rate' => $used, 'remaining_rate' => $remaining]; } public static function validateCommissionShareRate(?int $parentAdminId, mixed $rateRaw, ?int $excludeAdminId = null): ?string { if ($parentAdminId === null || $parentAdminId <= 0) { return null; } if ($rateRaw === null || $rateRaw === '') { return (string) __('Sub-agent commission share rate is required'); } $rate = bcadd(strval($rateRaw), '0', 2); if (bccomp($rate, '0', 2) < 0 || bccomp($rate, '100', 2) > 0) { return (string) __('Commission share rate must be between 0 and 100'); } $remainder = self::getShareRemainder($parentAdminId, $excludeAdminId); if (bccomp(bcadd($remainder['used_rate'], $rate, 2), '100.00', 2) > 0) { return (string) __('Sum of sibling commission share rates cannot exceed 100%'); } return null; } /** * 将渠道本期总佣金按管理员树分配,返回各管理员实得金额 * * @return array */ public static function distributeChannelCommission(int $channelId, string $totalCommission, string $calcBaseAmount): array { if ($channelId <= 0 || bccomp($totalCommission, '0', 2) <= 0) { return []; } $roots = Db::name('admin') ->where('channel_id', $channelId) ->where('status', 'enable') ->whereRaw('(parent_admin_id IS NULL OR parent_admin_id = 0)') ->order('id', 'asc') ->column('id'); if ($roots === []) { return []; } $rootCount = count($roots); $perRoot = bcdiv($totalCommission, strval($rootCount), 2); $assigned = '0.00'; $merged = []; foreach ($roots as $index => $rootId) { $rootId = intval($rootId); if ($rootId <= 0) { continue; } $isLast = $index === $rootCount - 1; $rootAmount = $isLast ? bcsub($totalCommission, $assigned, 2) : $perRoot; if (!$isLast) { $assigned = bcadd($assigned, $rootAmount, 2); } $parts = self::distributeFromAdmin($rootId, $rootAmount, $calcBaseAmount); foreach ($parts as $adminId => $amount) { if (!isset($merged[$adminId])) { $merged[$adminId] = '0.00'; } $merged[$adminId] = bcadd($merged[$adminId], $amount, 2); } } $out = []; foreach ($merged as $adminId => $amount) { if (bccomp($amount, '0', 2) <= 0) { continue; } $effectiveRate = bccomp($calcBaseAmount, '0', 2) <= 0 ? '0.0000' : bcdiv($amount, $calcBaseAmount, 6); $out[] = [ 'admin_id' => intval($adminId), 'commission_amount' => $amount, 'commission_rate' => $effectiveRate, 'calc_base_amount' => $calcBaseAmount, ]; } return $out; } /** * @return array admin_id => amount */ private static function distributeFromAdmin(int $adminId, string $amount, string $calcBaseAmount): array { if ($adminId <= 0 || bccomp($amount, '0', 2) <= 0) { return []; } $children = Db::name('admin') ->where('parent_admin_id', $adminId) ->where('status', 'enable') ->order('id', 'asc') ->field(['id', 'commission_share_rate']) ->select() ->toArray(); $givenToChildren = '0.00'; $result = []; foreach ($children as $child) { $childId = intval($child['id'] ?? 0); if ($childId <= 0) { continue; } $rate = bcadd(strval($child['commission_share_rate'] ?? '0'), '0', 2); if (bccomp($rate, '0', 2) <= 0) { continue; } $childAmount = bcmul($amount, bcdiv($rate, '100', 4), 2); if (bccomp($childAmount, '0', 2) <= 0) { continue; } $givenToChildren = bcadd($givenToChildren, $childAmount, 2); $childParts = self::distributeFromAdmin($childId, $childAmount, $calcBaseAmount); foreach ($childParts as $aid => $part) { if (!isset($result[$aid])) { $result[$aid] = '0.00'; } $result[$aid] = bcadd($result[$aid], $part, 2); } } $selfKeep = bcsub($amount, $givenToChildren, 2); if (bccomp($selfKeep, '0', 2) > 0) { if (!isset($result[$adminId])) { $result[$adminId] = '0.00'; } $result[$adminId] = bcadd($result[$adminId], $selfKeep, 2); } return $result; } /** * @return array */ public static function buildSplitPreview(int $channelId, string $commissionTotal, string $calcBaseAmount): array { $rows = self::distributeChannelCommission($channelId, $commissionTotal, $calcBaseAmount); if ($rows === []) { return []; } $adminIds = array_map(static fn(array $r): int => intval($r['admin_id']), $rows); $adminNames = Db::name('admin')->where('id', 'in', $adminIds)->column('username', 'id'); $parentMap = Db::name('admin')->where('id', 'in', $adminIds)->column('parent_admin_id', 'id'); $shareRates = Db::name('admin')->where('id', 'in', $adminIds)->column('commission_share_rate', 'id'); $out = []; foreach ($rows as $row) { $aid = intval($row['admin_id']); $parentId = intval($parentMap[$aid] ?? 0); $shareRate = $parentId > 0 ? bcadd(strval($shareRates[$aid] ?? '0'), '0', 2) : '100.00'; $out[] = [ 'admin_id' => $aid, 'admin_username' => strval($adminNames[$aid] ?? ('#' . $aid)), 'share_rate' => $shareRate, 'commission_amount' => strval($row['commission_amount']), ]; } return $out; } }