connection = $database->connection( (string) config('upstream_mirrors.connection', 'business_mirror') ); } public function available(): bool { $schema = Schema::connection($this->connection->getName()); return $schema->hasTable(self::TABLE) && $schema->hasTable('business_mirror_table_states'); } public function ready(): bool { if (! $this->available()) { return false; } return ($this->state()['status'] ?? '') === 'succeeded'; } /** @return array|null */ public function state(): ?array { if (! $this->available()) { return null; } $row = $this->connection->table('business_mirror_table_states') ->where('dataset_key', self::STATE_KEY) ->first(); return $row === null ? null : (array) $row; } /** @param list> $rows */ public function upsertRecords(array $rows): int { if ($rows === []) { return 0; } $this->connection->table(self::TABLE)->upsert( $rows, ['record_id'], [ 'user_id', 'user_name', 'work_date', 'check_type', 'time_result', 'approve_tag_name', 'approve_id', 'proc_inst_id', 'source_type', 'base_check_time', 'user_check_time', 'synced_at', 'updated_at', ], ); return count($rows); } public function recordSuccess(int $upserted, CarbonInterface $from, CarbonInterface $to): void { $now = now(); $state = $this->state(); $values = [ 'source_key' => 'dingtalk_attendance', 'source_object' => 'attendance/list', 'target_table' => self::TABLE, 'status' => 'succeeded', 'run_mode' => 'incremental', 'cursor' => json_encode([ 'from' => $from->toDateTimeString(), 'to' => $to->toDateTimeString(), ], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), 'high_watermark' => json_encode( ['work_date' => $to->toDateString()], JSON_THROW_ON_ERROR, ), 'window_started_at' => $from, 'window_ends_at' => $to, 'initial_completed_at' => $state['initial_completed_at'] ?? $now, 'last_attempted_at' => $now, 'last_succeeded_at' => $now, 'last_upserted_rows' => $upserted, 'mirror_row_count' => $this->connection->table(self::TABLE)->count(), 'last_error_code' => null, 'updated_at' => $now, ]; $this->connection->table('business_mirror_table_states')->updateOrInsert( ['dataset_key' => self::STATE_KEY], $values + ['created_at' => $state['created_at'] ?? $now], ); } public function recordFailure(string $errorCode): void { if (! $this->available()) { return; } $now = now(); $state = $this->state(); $this->connection->table('business_mirror_table_states')->updateOrInsert( ['dataset_key' => self::STATE_KEY], [ 'source_key' => 'dingtalk_attendance', 'source_object' => 'attendance/list', 'target_table' => self::TABLE, 'status' => 'failed', 'run_mode' => 'incremental', 'last_attempted_at' => $now, 'last_error_code' => mb_substr($errorCode, 0, 120), 'updated_at' => $now, 'initial_completed_at' => $state['initial_completed_at'] ?? null, 'mirror_row_count' => $this->connection->table(self::TABLE)->count(), 'created_at' => $state['created_at'] ?? $now, ], ); } /** * 返回“已审批请假/休息”等不扣分的用户日期。 * * @param list $userIds * @return array> */ public function exemptDatesForUsers(array $userIds, string $from, string $to): array { $userIds = array_values(array_unique(array_filter(array_map('trim', $userIds)))); if ($userIds === []) { return []; } $approvalTags = array_values(array_filter(array_map( 'trim', (array) config('dingtalk.attendance.exempt_approval_tags', []), ))); $timeResults = array_values(array_filter(array_map( 'trim', (array) config('dingtalk.attendance.exempt_time_results', []), ))); if ($approvalTags === [] && $timeResults === []) { return []; } $query = $this->connection->table(self::TABLE) ->whereIn('user_id', $userIds) ->whereBetween('work_date', [$from, $to]) ->where(function ($scope) use ($approvalTags, $timeResults): void { if ($approvalTags !== []) { $scope->whereIn('approve_tag_name', $approvalTags); } if ($timeResults !== []) { if ($approvalTags === []) { $scope->whereIn('time_result', $timeResults); } else { $scope->orWhereIn('time_result', $timeResults); } } }) ->get(['user_id', 'work_date']); $dates = []; foreach ($query as $row) { $userId = trim((string) ($row->user_id ?? '')); $date = Carbon::parse($row->work_date)->toDateString(); if ($userId !== '' && $date !== '') { $dates[$userId][$date] = true; } } return $dates; } }