CaiWuXiTong/app/Support/CourierOnboardingDingTalkProfiles.php

97 lines
4.8 KiB
PHP

<?php
namespace App\Support;
use App\Exceptions\CourierOnboardingDingTalkException;
use App\Models\Business\CourierOnboarding;
use App\Models\Business\CourierOnboardingEvent;
use App\Services\DingTalk\CourierOnboardingClient;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
/** 只回传钉钉审批通过的内部快递资料,不变更财务系统的审批状态。 */
class CourierOnboardingDingTalkProfiles
{
public const FINISHED_EVENTS = ['dingtalk_profile_synced', 'dingtalk_profile_closed'];
public function __construct(private readonly CourierOnboardingClient $client)
{
}
public function candidates(): Builder
{
return CourierOnboarding::allCompanies()->disableCache()
->whereNotNull('dingtalk_instance_id')->where('dingtalk_instance_id', '<>', '')
->whereNotIn('status', [CourierOnboarding::STATUS_CANCELLED, CourierOnboarding::STATUS_REJECTED])
->whereDoesntHave('events', fn (Builder $query) => $query->withoutGlobalScope(\App\Scopes\Company::class)
->withTrashed()->whereIn('event_type', self::FINISHED_EVENTS));
}
public function synchronize(int $companyId, int $id, string $token): string
{
$record = $this->candidates()->companyId($companyId)->find($id);
if ($record === null) {
return 'skipped';
}
$instanceId = (string) $record->dingtalk_instance_id;
$snapshot = $this->client->instance($token, $instanceId);
$values = collect($snapshot['formComponentValues'] ?? [])->pluck('value', 'name')->all();
$marker = '[财务入驻:' . $record->company_id . ':' . $record->id . ':' . $record->number . ']';
if (($snapshot['originatorUserId'] ?? '') !== config('courier_onboarding.dingtalk.originator_user_id')
|| ! is_string($values['原因'] ?? null) || ! str_contains($values['原因'], $marker)) {
throw new CourierOnboardingDingTalkException('钉钉审批与入驻申请不匹配,未同步资料。');
}
$status = strtoupper((string) ($snapshot['status'] ?? ''));
if (! in_array($status, ['COMPLETED', 'TERMINATED'], true)) {
return 'pending';
}
$approved = $status === 'COMPLETED' && strtolower((string) ($snapshot['result'] ?? '')) === 'agree';
$profile = [];
if ($approved) {
foreach (CourierOnboarding::PROFILE_FIELDS as $field => [$label]) {
$value = $values[$label] ?? null;
// 仅接受约定的普通文本值,不接收表格、文件或账号密码。
$profile[$field] = is_string($value) ? trim($value) : null;
}
$validator = Validator::make($profile, CourierOnboarding::profileRules(), [], CourierOnboarding::profileAttributes());
if ($validator->fails()) {
$fields = array_map(fn (string $field): string => CourierOnboarding::PROFILE_FIELDS[$field][0], array_keys($validator->errors()->messages()));
throw new CourierOnboardingDingTalkException('钉钉审批中的快递资料缺失或格式不正确:' . implode('、', $fields) . '。请补齐后等待重新同步。');
}
}
return DB::transaction(function () use ($companyId, $id, $instanceId, $approved, $profile): string {
$record = $this->candidates()->companyId($companyId)->lockForUpdate()->find($id);
if ($record === null || $record->dingtalk_instance_id !== $instanceId) {
return 'skipped';
}
if ($approved) {
$record->update($profile);
}
$this->event($record, $approved ? 'dingtalk_profile_synced' : 'dingtalk_profile_closed',
$approved ? '钉钉审批已通过,内部快递资料已同步到本入驻申请及快递资料。' : '钉钉审批未通过或已终止,保留财务系统原有资料及审核状态。');
return $approved ? 'synced' : 'closed';
});
}
public function recordFailure(CourierOnboarding $record, string $message): void
{
$latest = CourierOnboardingEvent::allCompanies()->companyId($record->company_id)
->where('onboarding_id', $record->id)->where('event_type', 'dingtalk_profile_failed')->latest('id')->first();
if ($latest?->note !== $message) {
$this->event($record, 'dingtalk_profile_failed', $message);
}
}
private function event(CourierOnboarding $record, string $type, string $note): void
{
CourierOnboardingEvent::create([
'company_id' => $record->company_id, 'onboarding_id' => $record->id,
'event_type' => $type, 'from_status' => $record->status, 'to_status' => $record->status,
'actor_type' => 'system', 'note' => $note,
]);
}
}