CaiWuXiTong/modules/EcommerceFinance/Repositories/DingAttendanceMirrorRepository.php

202 lines
6.4 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Carbon\CarbonInterface;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\DatabaseManager;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
class DingAttendanceMirrorRepository
{
public const TABLE = 'ding_attendance_records';
public const STATE_KEY = 'dingtalk.attendance.finance_daily_records';
private readonly ConnectionInterface $connection;
public function __construct(DatabaseManager $database)
{
$this->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<string,mixed>|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<array<string,mixed>> $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<string> $userIds
* @return array<string,array<string,true>>
*/
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;
}
}