, * missed_count:int, * penalty_points:string, * penalty_points_formatted:string, * penalty_formula:string * }> * } */ public function summary(string $from, string $to, CarbonInterface $at): array { $timezone = (string) config('app.timezone', 'Asia/Shanghai'); $now = Carbon::instance($at)->timezone($timezone); $requestedStart = Carbon::parse($from, $timezone)->startOfDay(); $requestedEnd = Carbon::parse($to, $timezone)->startOfDay(); $configuredStart = Carbon::parse( (string) config('dingtalk.worklog.penalty_start_date', '2026-11-04'), $timezone, )->startOfDay(); $latestClosed = $now->hour < 12 ? $now->subDays(2)->startOfDay() : $now->subDay()->startOfDay(); $end = $requestedEnd->lessThan($latestClosed) ? $requestedEnd->copy() : $latestClosed; $earliest = $this->repository->earliestStatisticsDate(); if ($earliest === null) { return [ 'start_date' => null, 'end_date' => null, 'total_points' => '0', 'total_points_formatted' => '0', 'members' => [], ]; } $earliestDate = Carbon::parse($earliest, $timezone)->startOfDay(); $start = $requestedStart->greaterThan($earliestDate) ? $requestedStart->copy() : $earliestDate; if ($configuredStart->greaterThan($start)) { $start = $configuredStart; } if ($start->greaterThan($end)) { return [ 'start_date' => null, 'end_date' => null, 'total_points' => '0', 'total_points_formatted' => '0', 'members' => [], ]; } $submitted = []; foreach ($this->repository->submittedDatesByUser($start->toDateString(), $end->toDateString()) as $row) { if ($row['creator_userid'] !== '') { $submitted[$row['creator_userid']][$row['statistics_date']] = true; } } $members = []; foreach ($this->roster->cachedFinanceMembers() as $member) { $userId = trim((string) ($member['userid'] ?? '')); if ($userId === '') { continue; } $members[$userId] = [ 'userid' => $userId, 'name' => trim((string) ($member['name'] ?? '')) ?: $userId, 'missed_dates' => [], 'missed_count' => 0, 'penalty_points' => '0', 'penalty_formula' => [], ]; } foreach ($members as $userId => &$member) { $sequence = 0; for ($date = $start->copy(); $date->lessThanOrEqualTo($end); $date->addDay()) { $dateKey = $date->toDateString(); if (isset($submitted[$userId][$dateKey])) { continue; } $sequence++; $increment = bcmul(self::BASE_POINTS, bcpow('2', (string) ($sequence - 1), 0), 0); $member['penalty_points'] = bcadd($member['penalty_points'], $increment, 0); $member['penalty_formula'][] = $increment; $member['missed_dates'][] = $dateKey; $member['missed_count']++; } } unset($member); $rows = array_values(array_filter($members, static fn (array $member): bool => $member['missed_count'] > 0)); usort($rows, static fn (array $left, array $right): int => (bccomp($right['penalty_points'], $left['penalty_points']) ?: strcmp($left['name'], $right['name'])) ); $totalPoints = array_reduce( $rows, static fn (string $total, array $member): string => bcadd($total, $member['penalty_points'], 0), '0', ); return [ 'start_date' => $start->toDateString(), 'end_date' => $end->toDateString(), 'total_points' => $totalPoints, 'total_points_formatted' => $this->formatPoints($totalPoints), 'members' => array_map(function (array $member): array { $member['penalty_points_formatted'] = $this->formatPoints($member['penalty_points']); $member['penalty_formula'] = implode(' + ', $member['penalty_formula']); return $member; }, $rows), ]; } private function formatPoints(string $points): string { return preg_replace('/\B(?=(\d{3})+(?!\d))/', ',', $points) ?: $points; } }