Files
lotteryLaravel/app/Services/AgentSettlement/ShareSettlementCalculator.php
kang e3ffffad9c feat: 增强代理和玩家管理功能
- 在 SyncAdminAuthorizationCommand 中新增对代理线路和结算菜单操作的同步功能,确保缺失的菜单操作行能够被创建。
- 更新多个控制器中的权限检查逻辑,使用 hasPermissionCode 替代原有的权限验证方式,提升权限管理的灵活性。
- 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。
- 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。
- 在 AdminUser 和 AgentNode 模型中增强角色与用户的权限管理功能,支持更细粒度的权限控制。
2026-06-04 09:17:47 +08:00

112 lines
3.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services\AgentSettlement;
/**
* 差额占成结算(设计文档 §11.4§12.4)。
*/
final class ShareSettlementCalculator
{
/**
* @param array<string, float|int> $totalSharesByCode 自下而上,如 C,B,A百分比 0-100
* @param array<string, float|int> $extraRebateByCode 谁设置谁承担
* @param list<string> $chainFromPlayer 自下而上参与方 code末位为平台侧用 platform
*/
public function calculate(
float $sharedNetWinLoss,
array $totalSharesByCode,
array $extraRebateByCode = [],
float $gameWinLoss = 0,
float $basicRebate = 0,
array $chainFromPlayer = [],
): ShareSettlementResult {
if ($gameWinLoss !== 0.0 || $basicRebate !== 0.0) {
$extraTotal = array_sum(array_map(floatval(...), $extraRebateByCode));
$playerNet = $gameWinLoss - $basicRebate - $extraTotal;
$shared = $gameWinLoss - $basicRebate;
} else {
$playerNet = $sharedNetWinLoss - array_sum(array_map(floatval(...), $extraRebateByCode));
$shared = $sharedNetWinLoss;
}
$ordered = $chainFromPlayer !== [] ? $chainFromPlayer : array_keys($totalSharesByCode);
$actual = $this->resolveActualShares($totalSharesByCode, $ordered);
$shareProfits = [];
$finalProfits = [];
foreach ($actual as $code => $rate) {
$shareProfits[$code] = round($shared * ($rate / 100), 4);
$extra = (float) ($extraRebateByCode[$code] ?? 0);
$finalProfits[$code] = round($shareProfits[$code] - $extra, 4);
}
$tierSettlements = $this->buildTierSettlements($playerNet, $finalProfits, $ordered);
return new ShareSettlementResult(
playerNetSettlement: round($playerNet, 4),
sharedNetWinLoss: round($shared, 4),
shareProfits: $shareProfits,
finalProfits: $finalProfits,
tierSettlements: $tierSettlements,
);
}
/**
* @param array<string, float|int> $totalSharesByCode
* @param list<string> $orderedBottomUp
* @return array<string, float>
*/
private function resolveActualShares(array $totalSharesByCode, array $orderedBottomUp): array
{
$actual = [];
$prev = 0.0;
foreach ($orderedBottomUp as $code) {
$total = (float) ($totalSharesByCode[$code] ?? 0);
$actual[$code] = max(0, $total - $prev);
$prev = $total;
}
$topTotal = $prev;
$actual['platform'] = max(0, 100 - $topTotal);
return $actual;
}
/**
* @param array<string, float> $finalProfits
* @param list<string> $orderedBottomUp
* @return array<string, float>
*/
private function buildTierSettlements(float $playerNet, array $finalProfits, array $orderedBottomUp): array
{
$keys = ['P_to_'.($orderedBottomUp[0] ?? 'agent')];
for ($i = 0; $i < count($orderedBottomUp) - 1; $i++) {
$keys[] = $orderedBottomUp[$i].'_to_'.$orderedBottomUp[$i + 1];
}
if (count($orderedBottomUp) >= 1) {
$last = $orderedBottomUp[count($orderedBottomUp) - 1];
$keys[] = $last.'_to_platform';
}
$amount = $playerNet;
$tier = [];
if ($orderedBottomUp === []) {
return $tier;
}
$tier['P_to_'.$orderedBottomUp[0]] = round($amount, 4);
for ($i = 0; $i < count($orderedBottomUp); $i++) {
$code = $orderedBottomUp[$i];
$keep = (float) ($finalProfits[$code] ?? 0);
$amount = round($amount - $keep, 4);
if ($i < count($orderedBottomUp) - 1) {
$next = $orderedBottomUp[$i + 1];
$tier[$code.'_to_'.$next] = $amount;
} else {
$tier[$code.'_to_platform'] = $amount;
}
}
return $tier;
}
}