97 lines
4.8 KiB
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,
|
|
]);
|
|
}
|
|
}
|