available()) { $this->error('日志统计镜像表尚未建立,请先执行数据库迁移。'); return self::FAILURE; } if (! $client->isConfigured()) { $repository->recordFailure('dingtalk_credentials_missing'); $this->error('钉钉应用凭据未配置。'); return self::FAILURE; } $days = max((int) ($this->option('days') ?: config('dingtalk.worklog.lookback_days', 35)), 1); $templates = $this->templates(); $to = now(); $from = $to->copy()->subDays($days)->startOfDay(); try { $now = now(); $pages = 0; $reports = []; foreach ($templates as $template) { $result = $client->listReports($from, $to, $template); $pages += (int) $result['pages']; foreach ($result['reports'] as $report) { if (! is_array($report)) { continue; } $reportId = DingWorklogMirrorRepository::reportId($report); $reports[$reportId] = [ 'requested_template' => $template, 'report' => $report, ]; } } $financeOnlyTemplates = array_values(array_filter(array_map( 'trim', (array) config('dingtalk.worklog.finance_only_templates', ['空白日志']), ))); $financeMembers = null; $rows = []; foreach ($reports as $reportId => $entry) { $report = $entry['report']; $templateName = trim((string) ($report['template_name'] ?? $entry['requested_template'])); $creatorUserId = mb_substr(trim((string) ($report['creator_userid'] ?? $report['creator_id'] ?? '')), 0, 128); if (in_array($templateName, $financeOnlyTemplates, true)) { $financeMembers ??= array_fill_keys( array_column($financeRoster->cachedFinanceMembers(), 'userid'), true, ); if (! isset($financeMembers[$creatorUserId])) { continue; } } $submittedRaw = $report['create_time'] ?? $report['created_at'] ?? $report['submit_time'] ?? null; if ($submittedRaw === null || $submittedRaw === '') { continue; } try { $submittedAt = DingWorklogMirrorRepository::submittedAt($submittedRaw); } catch (Throwable) { continue; } $statisticsDate = DingWorklogMirrorRepository::statisticsDate($submittedAt); $rows[] = [ 'report_id' => $reportId, 'template_name' => mb_substr($templateName, 0, 191), 'creator_userid' => $creatorUserId, 'creator_name' => mb_substr(trim((string) ($report['creator_name'] ?? $report['creator_nick'] ?? '')), 0, 191), 'submitted_at' => $submittedAt->toDateTimeString(), 'statistics_date' => $statisticsDate->toDateString(), 'remark' => trim((string) ($report['remark'] ?? '')), 'raw_payload' => json_encode($report, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 'synced_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]; } $upserted = $repository->upsertReports($rows); $repository->recordSuccess($upserted, implode(' / ', $templates), $from, $to); $this->info(sprintf( '钉钉日志同步完成:读取 %d 页,写入/更新 %d 条。', $pages, $upserted, )); return self::SUCCESS; } catch (DingTalkWorklogException $exception) { $repository->recordFailure($exception->errorCode); report($exception); $this->error($exception->getMessage()); return self::FAILURE; } catch (Throwable $exception) { $repository->recordFailure('dingtalk_worklog_sync_failed'); report($exception); $this->error('钉钉日志同步失败,详细原因已记录到受保护日志。'); return self::FAILURE; } } /** @return list */ private function templates(): array { $configured = trim((string) $this->option('template')); $templates = $configured !== '' ? preg_split('/[,,]/u', $configured, -1, PREG_SPLIT_NO_EMPTY) : (array) config('dingtalk.worklog.template_names', [ config('dingtalk.worklog.template_name', '财务日报'), ]); $templates = array_values(array_unique(array_filter(array_map( static fn (mixed $template): string => trim((string) $template), $templates, )))); return $templates !== [] ? $templates : ['财务日报']; } }