CaiWuXiTong/app/Http/Controllers/Business/CourierAgreementSignatures.php

166 lines
9.3 KiB
PHP

<?php
namespace App\Http\Controllers\Business;
use App\Models\Business\CourierAgreementSignature;
use App\Services\Esign\EsignClient;
use App\Support\CourierAgreementSignatureService;
use App\Utilities\SystemAccess;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Schema;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\StreamedResponse;
class CourierAgreementSignatures extends Controller
{
public function __construct(private readonly CourierAgreementSignatureService $service)
{
}
public function store(Request $request): RedirectResponse
{
$this->ensureCanManage();
abort_unless(EsignClient::configured(), 422, 'E 签宝沙箱尚未配置 App ID 或 AppSecret。');
abort_unless(Schema::hasTable('business_courier_agreement_signatures'), 503, '协议签署数据表尚未完成迁移,请先执行数据库迁移。');
$data = $request->validate($this->rules());
$type = $data['document_type'];
if ($type === 'authorization' && count($data['authorization_items'] ?? []) === 0) {
throw ValidationException::withMessages(['authorization_items' => '授权委托书至少选择一项授权事项。']);
}
$this->validateEsignAccount($data['esign_account'], 'esign_account');
if (! empty($data['secondary_esign_account'])) {
$this->validateEsignAccount($data['secondary_esign_account'], 'secondary_esign_account');
}
$data['organization_name'] = $data['signing_subject'];
$data['operator_name'] = $data['operator_name'] ?? $data['courier_name'];
$data['operator_account'] = $data['esign_account'];
$data['payload'] = $this->payload($data);
$signature = $this->service->send($data, user_id());
return redirect()
->route('business.pages.show', ['section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'tasks'])
->with('success', $signature->status === CourierAgreementSignature::STATUS_SIGNING
? "{$signature->documentLabel()}已推送到 E 签宝,等待对方签署。"
: "{$signature->documentLabel()}签署任务已受理。")
->with('agreement_signature_id', $signature->id);
}
public function synchronize(CourierAgreementSignature $agreementSignature): RedirectResponse
{
$this->ensureCanManage();
$signature = $this->service->synchronize($agreementSignature, user_id());
return redirect()
->route('business.pages.show', ['section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'tasks'])
->with('success', "{$signature->documentLabel()}状态已同步:{$signature->statusLabel()}。");
}
public function archive(CourierAgreementSignature $agreementSignature): RedirectResponse
{
$this->ensureCanManage();
abort_unless($agreementSignature->status === CourierAgreementSignature::STATUS_COMPLETED && $agreementSignature->remote_status === '2', 422, '只有签署完成的协议可以人工归档。');
$signature = $this->service->archive($agreementSignature, null, user_id());
return redirect()
->route('business.pages.show', ['section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'tasks'])
->with('success', "{$signature->documentLabel()}已人工确认并归档。");
}
public function download(CourierAgreementSignature $agreementSignature, string $kind): StreamedResponse
{
$this->ensureCanManage();
[$path, $suffix] = match ($kind) {
'source' => [$agreementSignature->source_pdf_path, '签署原文'],
'signed' => [$agreementSignature->signed_pdf_path, '已签署协议'],
'certificate' => [$agreementSignature->certificate_path, '签署完成证书'],
default => abort(404),
};
abort_if(blank($path) || ! \Storage::disk('uploads')->exists($path), 404, '归档文件不存在。');
return \Storage::disk('uploads')->download($path, $agreementSignature->business_no . '-' . $suffix . '.pdf', ['Content-Type' => 'application/pdf']);
}
private function rules(): array
{
$date = ['required', 'date', 'after_or_equal:today', 'before_or_equal:' . now()->addDays(90)->toDateString()];
return [
'idempotency_key' => ['required', 'string', 'max:80', 'regex:/^[A-Za-z0-9_-]+$/'],
'courier_name' => ['required', 'string', 'max:255'],
'document_type' => ['required', 'in:' . implode(',', array_keys(CourierAgreementSignature::DOCUMENTS))],
'signing_subject' => ['required', 'string', 'min:2', 'max:255'],
'company_full_name' => ['required', 'string', 'min:2', 'max:255'],
'signing_deadline' => $date,
'esign_account' => ['required', 'string', 'max:255'],
'secondary_esign_account' => ['required_if:document_type,entity-change,authorization', 'nullable', 'string', 'max:255'],
'operator_name' => ['nullable', 'string', 'max:100'],
'archive_mode' => ['required', 'in:auto,manual'],
'account_proof_effective_date' => ['required_if:document_type,account-proof', 'date'],
'account_proof_account_name' => ['required_if:document_type,account-proof', 'string', 'max:255'],
'account_proof_bank_name' => ['required_if:document_type,account-proof', 'string', 'max:255'],
'account_proof_bank_account' => ['required_if:document_type,account-proof', 'string', 'max:64', 'regex:/^[0-9A-Za-z-]+$/'],
'account_change_date' => ['required_if:document_type,account-change', 'date'],
'old_account_name' => ['required_if:document_type,account-change', 'string', 'max:255'],
'old_account_number' => ['required_if:document_type,account-change', 'string', 'max:64', 'regex:/^[0-9A-Za-z-]+$/'],
'old_account_stop_date' => ['required_if:document_type,account-change', 'date'],
'new_account_effective_date' => ['required_if:document_type,account-change', 'date'],
'new_account_name' => ['required_if:document_type,account-change', 'string', 'max:255'],
'new_bank_name' => ['required_if:document_type,account-change', 'string', 'max:255'],
'new_bank_account' => ['required_if:document_type,account-change', 'string', 'max:64', 'regex:/^[0-9A-Za-z-]+$/'],
'former_company_name' => ['required_if:document_type,entity-change', 'string', 'max:255'],
'entity_change_date' => ['required_if:document_type,entity-change', 'date'],
'changed_company_name' => ['required_if:document_type,entity-change', 'string', 'max:255'],
'changed_company_start_date' => ['required_if:document_type,entity-change', 'date'],
'entity_proof_date' => ['required_if:document_type,entity-change', 'date'],
'contract_partner_name' => ['required_if:document_type,shared-contract', 'string', 'max:255'],
'shared_courier_name' => ['required_if:document_type,shared-contract', 'string', 'max:255'],
'shared_proof_date' => ['required_if:document_type,shared-contract', 'date'],
'principal_name' => ['required_if:document_type,authorization', 'string', 'max:100'],
'principal_id_number' => ['required_if:document_type,authorization', 'string', 'max:64'],
'trustee_name' => ['required_if:document_type,authorization', 'string', 'max:100'],
'trustee_id_number' => ['required_if:document_type,authorization', 'string', 'max:64'],
'authorization_items' => ['nullable', 'array'],
'authorization_items.*' => ['in:1,2,3,4'],
'bill_counterparty' => ['required_if:document_type,authorization', 'string', 'max:255'],
'loan_counterparty' => ['required_if:document_type,authorization', 'string', 'max:255'],
'payment_counterparty' => ['required_if:document_type,authorization', 'string', 'max:255'],
'authorization_start_date' => ['required_if:document_type,authorization', 'date'],
'authorization_end_date' => ['required_if:document_type,authorization', 'date', 'after_or_equal:authorization_start_date'],
];
}
private function payload(array $data): array
{
$allowed = array_keys($this->rules());
$payload = array_intersect_key($data, array_flip($allowed));
unset($payload['esign_account'], $payload['idempotency_key'], $payload['operator_name']);
$payload['authorization_items'] = array_values(array_map('strval', $data['authorization_items'] ?? []));
return $payload;
}
private function ensureCanManage(): void
{
abort_unless(
in_array(SystemAccess::roleName(user()), [SystemAccess::SUPER_ADMIN, SystemAccess::ADMIN], true),
403,
'当前账号没有协议签署管理权限。',
);
}
private function validateEsignAccount(string $account, string $field): void
{
$valid = preg_match('/^1\d{10}$/', $account) === 1
|| filter_var($account, FILTER_VALIDATE_EMAIL) !== false;
if (! $valid) {
throw ValidationException::withMessages([$field => 'E签宝账号必须是 11 位手机号或有效邮箱。']);
}
}
}