更新增加统计【活动期间输赢总数据】

This commit is contained in:
2026-07-27 15:41:31 +08:00
parent f2543b9aa4
commit f91eb186ca
19 changed files with 375 additions and 122 deletions

View File

@@ -30,6 +30,7 @@ class PlayxConfig extends Backend
$payload = $row->toArray();
$payload['active_mall_open_date'] = MallPointsConversion::resolveActiveMallOpenDate($row);
$payload['active_event_end_date'] = MallPointsConversion::resolveActiveEventEndDate($row);
return $this->success('', [
'row' => $payload,
@@ -75,6 +76,10 @@ class PlayxConfig extends Backend
}
$mallOpenDate = MallPointsConversion::normalizeDate($data['mall_open_date'] ?? null);
$mallEventEndDate = MallPointsConversion::normalizeDate($data['mall_event_end_date'] ?? null);
if ($mallOpenDate !== null && $mallEventEndDate !== null && $mallEventEndDate < $mallOpenDate) {
return $this->error(__('Event end date cannot be earlier than start date'));
}
$row = MallBusinessConfig::order('id', 'asc')->find();
$now = time();
@@ -90,6 +95,9 @@ class PlayxConfig extends Backend
'mall_open_date' => $mallOpenDate,
'mall_open_date_previous' => null,
'mall_open_date_effective_on' => $effectiveOn,
'mall_event_end_date' => $mallEventEndDate,
'mall_event_end_date_previous' => null,
'mall_event_end_date_effective_on' => MallPointsConversion::nextCalendarDate(),
'create_time' => $now,
'update_time' => $now,
]);
@@ -98,6 +106,10 @@ class PlayxConfig extends Backend
$currentPending = MallPointsConversion::normalizeDate($row->mall_open_date ?? null);
$openDateChanged = $mallOpenDate !== $currentPending;
$previousEnd = MallPointsConversion::resolveActiveEventEndDate($row);
$currentPendingEnd = MallPointsConversion::normalizeDate($row->mall_event_end_date ?? null);
$endDateChanged = $mallEventEndDate !== $currentPendingEnd;
$row->return_ratio = $returnF;
$row->unlock_ratio = $unlockF;
$row->points_to_cash_ratio = $cashF;
@@ -111,6 +123,12 @@ class PlayxConfig extends Backend
$row->mall_open_date_effective_on = $mallOpenDate !== null ? MallPointsConversion::nextCalendarDate() : null;
}
if ($endDateChanged) {
$row->mall_event_end_date_previous = $previousEnd;
$row->mall_event_end_date = $mallEventEndDate;
$row->mall_event_end_date_effective_on = MallPointsConversion::nextCalendarDate();
}
$row->save();
}

View File

@@ -44,6 +44,7 @@ class UserAsset extends Backend
'today_limit_date',
'points_claim_cap',
'total_points_claimed',
'event_period_win_loss_net',
'create_time',
'update_time',
];

View File

@@ -27,6 +27,7 @@ return [
'Added successfully' => 'Added successfully!',
'Deleted successfully' => 'Deleted successfully!',
'Parameter error' => 'Parameter error!',
'Event end date cannot be earlier than start date' => 'Event end date cannot be earlier than start date',
'Lifetime points source is disabled' => 'Lifetime points source is disabled',
'Operation completed' => 'Operation completed!',
'Lifetime points calculation completed' => 'Lifetime conversion done: %d processed, %d updated, %d skipped',

View File

@@ -27,6 +27,7 @@ return [
'Added successfully' => '添加成功!',
'Deleted successfully' => '删除成功!',
'Parameter error' => '参数错误!',
'Event end date cannot be earlier than start date' => '活动结束日不能早于开始日',
'Lifetime points source is disabled' => '终身总输赢积分来源未启用',
'Operation completed' => '操作完成!',
'Lifetime points calculation completed' => '终身总输赢积分折算完成:共处理 %d 条,更新 %d 条,跳过 %d 条',

View File

@@ -139,6 +139,11 @@ class Playx extends Api
return MallPointsConversion::configForConversion();
}
private function dailyPushSettlementDay(): string
{
return date('Y-m-d');
}
private function syncAssetByDailyPush(
string $playxUserId,
string $username,
@@ -146,7 +151,10 @@ class Playx extends Api
int $convertedPoints,
int $todayLimit,
bool $applyAssetDelta,
string $phone = ''
string $phone = '',
?array $pointsConfig = null,
?float $yesterdayWinLossNet = null,
?float $previousYesterdayWinLossNet = null
): ?MallUserAsset
{
$asset = $this->ensureAssetForPlayx($playxUserId, $username, $phone);
@@ -169,6 +177,16 @@ class Playx extends Api
MallPointsConversion::applyDailyConversionDelta($asset, $convertedPoints);
}
$asset->today_limit = $todayLimit;
if (is_array($pointsConfig) && $yesterdayWinLossNet !== null) {
MallPointsConversion::applyEventPeriodWinLossForDailyPush(
$asset,
$date,
$yesterdayWinLossNet,
$pointsConfig,
$this->dailyPushSettlementDay(),
$previousYesterdayWinLossNet
);
}
}
$asset->save();
@@ -234,7 +252,7 @@ class Playx extends Api
}
$pointsConfig = $this->getPointsConversionConfig();
$unlockRatio = floatval($pointsConfig['unlock_ratio'] ?? 0);
$settlementDay = $this->dailyPushSettlementDay();
$results = [];
$allDeduped = true;
@@ -255,8 +273,8 @@ class Playx extends Api
$exists = MallDailyPush::where('user_id', $playxUserId)->where('date', $date)->find();
if ($exists) {
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig);
$todayLimit = intval(round(floatval($yesterdayTotalDeposit) * $unlockRatio));
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig, $settlementDay);
$todayLimit = MallPointsConversion::dailyPushTodayLimit($date, floatval($yesterdayTotalDeposit), $pointsConfig, $settlementDay);
$asset = $this->syncAssetByDailyPush($playxUserId, $username, $date, $convertedPoints, $todayLimit, false, $phone);
if (!$asset) {
return $this->error(__('Failed to ensure PlayX user asset'));
@@ -283,16 +301,16 @@ class Playx extends Api
'lifetime_total_withdraw' => $lifetimeTotalWithdraw,
'create_time' => time(),
];
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig);
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig, $settlementDay);
$pushData['converted_points'] = $convertedPoints;
if ($lifetimeWinLossNet !== null) {
$pushData['lifetime_win_loss_net'] = $lifetimeWinLossNet;
}
MallDailyPush::create($pushData);
$todayLimit = intval(round(floatval($yesterdayTotalDeposit) * $unlockRatio));
$todayLimit = MallPointsConversion::dailyPushTodayLimit($date, floatval($yesterdayTotalDeposit), $pointsConfig, $settlementDay);
$asset = $this->syncAssetByDailyPush($playxUserId, $username, $date, $convertedPoints, $todayLimit, true, $phone);
$asset = $this->syncAssetByDailyPush($playxUserId, $username, $date, $convertedPoints, $todayLimit, true, $phone, $pointsConfig, floatval($yesterdayWinLossNet));
if (!$asset) {
throw new \RuntimeException(__('Failed to ensure PlayX user asset'));
}
@@ -336,9 +354,9 @@ class Playx extends Api
$exists = MallDailyPush::where('user_id', $playxUserId)->where('date', $date)->find();
if ($exists) {
$pointsConfig = $this->getPointsConversionConfig();
$unlockRatio = floatval($pointsConfig['unlock_ratio'] ?? 0);
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig);
$todayLimit = intval(round(floatval($yesterdayTotalDeposit) * $unlockRatio));
$settlementDay = $this->dailyPushSettlementDay();
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig, $settlementDay);
$todayLimit = MallPointsConversion::dailyPushTodayLimit($date, floatval($yesterdayTotalDeposit), $pointsConfig, $settlementDay);
$asset = $this->syncAssetByDailyPush(
$playxUserId,
strval($body['username'] ?? ''),
@@ -363,6 +381,7 @@ class Playx extends Api
Db::startTrans();
try {
$pointsConfig = $this->getPointsConversionConfig();
$settlementDay = $this->dailyPushSettlementDay();
$lifetimeWinLossNet = null;
if (isset($body['lifetime_win_loss_net']) && is_numeric($body['lifetime_win_loss_net'])) {
$lifetimeWinLossNet = floatval($body['lifetime_win_loss_net']);
@@ -377,15 +396,14 @@ class Playx extends Api
'lifetime_total_withdraw' => $body['lifetime_total_withdraw'] ?? 0,
'create_time' => time(),
];
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig);
$convertedPoints = MallPointsConversion::dailyConvertedPoints($date, floatval($yesterdayWinLossNet), $pointsConfig, $settlementDay);
$pushData['converted_points'] = $convertedPoints;
if ($lifetimeWinLossNet !== null) {
$pushData['lifetime_win_loss_net'] = $lifetimeWinLossNet;
}
MallDailyPush::create($pushData);
$unlockRatio = floatval($pointsConfig['unlock_ratio'] ?? 0);
$todayLimit = intval(round(floatval($yesterdayTotalDeposit) * $unlockRatio));
$todayLimit = MallPointsConversion::dailyPushTodayLimit($date, floatval($yesterdayTotalDeposit), $pointsConfig, $settlementDay);
$asset = $this->syncAssetByDailyPush(
$playxUserId,
@@ -394,7 +412,9 @@ class Playx extends Api
$convertedPoints,
$todayLimit,
true,
$phone
$phone,
$pointsConfig,
floatval($yesterdayWinLossNet)
);
if (!$asset) {
throw new \RuntimeException(__('Failed to ensure PlayX user asset'));

View File

@@ -260,13 +260,24 @@ class MallDailyPushLogReplay
$newLocked = MallPointsConversion::dailyConvertedPoints(
$record['date'],
floatval($record['yesterday_win_loss_net']),
$pointsConfig
$pointsConfig,
MallPointsConversion::nextCalendarDate($record['date'])
);
$oldLocked = $daily
? MallPointsConversion::dailyConvertedPoints($record['date'], $oldWinLossNet, $pointsConfig)
? MallPointsConversion::dailyConvertedPoints(
$record['date'],
$oldWinLossNet,
$pointsConfig,
MallPointsConversion::nextCalendarDate($record['date'])
)
: 0;
$lockedDelta = $newLocked - $oldLocked;
$todayLimit = intval(round($record['yesterday_total_deposit'] * $unlockRatio));
$todayLimit = MallPointsConversion::dailyPushTodayLimit(
$record['date'],
floatval($record['yesterday_total_deposit']),
$pointsConfig,
MallPointsConversion::nextCalendarDate($record['date'])
);
$dailyData = [
'user_id' => $record['user_id'],
'date' => $record['date'],
@@ -352,6 +363,15 @@ class MallDailyPushLogReplay
MallPointsConversion::applyDailyConversionDelta($asset, $lockedDelta);
}
$this->applyDailyLimit($asset, $record['date'], $todayLimit);
$settlementDay = MallPointsConversion::nextCalendarDate($record['date']);
MallPointsConversion::applyEventPeriodWinLossForDailyPush(
$asset,
$record['date'],
floatval($record['yesterday_win_loss_net']),
$pointsConfig,
$settlementDay,
$daily ? $oldWinLossNet : null
);
$asset->save();
}
Db::commit();

View File

@@ -25,7 +25,11 @@ final class MallPointsConversion
* mall_open_date: ?string,
* mall_open_date_previous: ?string,
* mall_open_date_effective_on: ?string,
* active_mall_open_date: ?string
* active_mall_open_date: ?string,
* mall_event_end_date: ?string,
* mall_event_end_date_previous: ?string,
* mall_event_end_date_effective_on: ?string,
* active_event_end_date: ?string
* }
*/
public static function configFromRow(MallBusinessConfig $row): array
@@ -41,6 +45,10 @@ final class MallPointsConversion
'mall_open_date_previous' => self::normalizeDate($row->mall_open_date_previous ?? null),
'mall_open_date_effective_on' => self::normalizeDate($row->mall_open_date_effective_on ?? null),
'active_mall_open_date' => self::resolveActiveMallOpenDate($row),
'mall_event_end_date' => self::normalizeDate($row->mall_event_end_date ?? null),
'mall_event_end_date_previous' => self::normalizeDate($row->mall_event_end_date_previous ?? null),
'mall_event_end_date_effective_on' => self::normalizeDate($row->mall_event_end_date_effective_on ?? null),
'active_event_end_date' => self::resolveActiveEventEndDate($row),
];
}
@@ -74,13 +82,16 @@ final class MallPointsConversion
*
* @param array<string, mixed> $config
*/
public static function dailyConvertedPoints(string $businessDate, float $yesterdayWinLossNet, array $config): int
{
public static function dailyConvertedPoints(
string $businessDate,
float $yesterdayWinLossNet,
array $config,
?string $settlementCalendarDate = null
): int {
if (empty($config['daily_points_enabled'])) {
return 0;
}
$openDate = $config['active_mall_open_date'] ?? null;
if ($openDate !== null && $openDate !== '' && $businessDate < $openDate) {
if (!self::isDailyPushSettlementAllowed($businessDate, $config, $settlementCalendarDate)) {
return 0;
}
$ratio = floatval($config['return_ratio'] ?? 0);
@@ -88,6 +99,85 @@ final class MallPointsConversion
return self::lossIntegerToPoints($yesterdayWinLossNet, $ratio);
}
/**
* 每日推送是否允许折算昨日净输赢、昨日充值解锁上限(活动开始/结束日规则)
*/
public static function isDailyPushSettlementAllowed(
string $businessDate,
array $config,
?string $settlementCalendarDate = null
): bool {
$settlementDay = self::normalizeDate($settlementCalendarDate ?? date('Y-m-d')) ?? date('Y-m-d');
$start = self::normalizeDate($config['active_mall_open_date'] ?? null);
if ($start !== null && $businessDate < $start) {
return false;
}
$end = self::normalizeDate($config['active_event_end_date'] ?? null);
if ($end === null) {
return true;
}
if ($businessDate > $end) {
return false;
}
$endPlusOne = self::nextCalendarDate($end);
if ($settlementDay === $endPlusOne && $businessDate >= $end) {
return false;
}
if ($settlementDay > $endPlusOne) {
return false;
}
return true;
}
/**
* 活动开放期内按推送累加净输赢;关闭期间不累加,再开启后接着累加(不重置)。
*
* @param float|null $previousWinLossNet 更新已有每日推送时传旧「昨日净输赢」;新建推送传 null
*/
public static function applyEventPeriodWinLossForDailyPush(
MallUserAsset $asset,
string $businessDate,
float $yesterdayWinLossNet,
array $config,
?string $settlementCalendarDate = null,
?float $previousWinLossNet = null
): void {
if (!self::isDailyPushSettlementAllowed($businessDate, $config, $settlementCalendarDate)) {
return;
}
$current = floatval($asset->event_period_win_loss_net ?? 0);
if ($previousWinLossNet === null) {
$asset->event_period_win_loss_net = round($current + $yesterdayWinLossNet, 2);
return;
}
$delta = $yesterdayWinLossNet - $previousWinLossNet;
if ($delta === 0.0) {
return;
}
$asset->event_period_win_loss_net = round($current + $delta, 2);
}
public static function dailyPushTodayLimit(
string $businessDate,
float $yesterdayTotalDeposit,
array $config,
?string $settlementCalendarDate = null
): int {
if (!self::isDailyPushSettlementAllowed($businessDate, $config, $settlementCalendarDate)) {
return 0;
}
$unlockRatio = floatval($config['unlock_ratio'] ?? 0);
return intval(round($yesterdayTotalDeposit * $unlockRatio));
}
/**
* @param array<string, mixed> $config
* @deprecated 使用 dailyConvertedPoints
@@ -219,6 +309,27 @@ final class MallPointsConversion
return $previous;
}
public static function resolveActiveEventEndDate(MallBusinessConfig $row): ?string
{
$pending = self::normalizeDate($row->mall_event_end_date ?? null);
$previous = self::normalizeDate($row->mall_event_end_date_previous ?? null);
$effectiveOn = self::normalizeDate($row->mall_event_end_date_effective_on ?? null);
$today = date('Y-m-d');
if ($pending === null) {
if ($effectiveOn !== null && $today >= $effectiveOn) {
return null;
}
return $previous;
}
if ($effectiveOn === null || $today >= $effectiveOn) {
return $pending;
}
return $previous;
}
public static function normalizeDate(mixed $value): ?string
{
if ($value === null || $value === '') {

View File

@@ -32,6 +32,9 @@ class MallBusinessConfig extends Model
'mall_open_date' => 'string',
'mall_open_date_previous' => 'string',
'mall_open_date_effective_on' => 'string',
'mall_event_end_date' => 'string',
'mall_event_end_date_previous' => 'string',
'mall_event_end_date_effective_on' => 'string',
];
/**

View File

@@ -28,6 +28,7 @@ class MallUserAsset extends Model
'total_points_claimed' => 'integer',
'lifetime_converted_points' => 'integer',
'daily_converted_points' => 'integer',
'event_period_win_loss_net' => 'float',
];
/**