492 lines
24 KiB
PHP
492 lines
24 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Business;
|
|
|
|
use App\Models\Business\CourierAgreementSignature;
|
|
use App\Models\Business\CourierQuoteSignature;
|
|
use App\Models\Business\CourierQuoteVersion;
|
|
use App\Models\Business\CourierSigningSubject;
|
|
use App\Services\Esign\EsignClient;
|
|
use App\Support\CourierAgreementFileArchive;
|
|
use App\Support\CourierAgreementSignatureService;
|
|
use App\Support\CourierQuoteConfirmation;
|
|
use App\Support\CourierQuoteSignatureService;
|
|
use App\Utilities\SystemAccess;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Routing\Controller;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Validation\ValidationException;
|
|
use RuntimeException;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class CourierAgreementSignatures extends Controller
|
|
{
|
|
private const COOPERATION_SECONDARY_ESIGN_ACCOUNT = '18052733960';
|
|
|
|
public function __construct(
|
|
private readonly CourierAgreementSignatureService $service,
|
|
private readonly CourierQuoteSignatureService $quoteSignatureService,
|
|
private readonly CourierQuoteConfirmation $quoteConfirmation,
|
|
private readonly CourierAgreementFileArchive $agreementFileArchive,
|
|
) {
|
|
}
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
abort_unless(EsignClient::configured(), 422, 'E 签宝沙箱尚未配置 App ID 或 AppSecret。');
|
|
$requestedType = (string) $request->input('document_type');
|
|
if ($requestedType === 'quote-confirmation') {
|
|
abort_unless(
|
|
Schema::hasTable('business_courier_quote_versions') && Schema::hasTable('business_courier_quote_signatures'),
|
|
503,
|
|
'报价签署数据表尚未完成迁移,请先执行数据库迁移。',
|
|
);
|
|
} else {
|
|
abort_unless(Schema::hasTable('business_courier_agreement_signatures'), 503, '协议签署数据表尚未完成迁移,请先执行数据库迁移。');
|
|
}
|
|
|
|
if ($request->input('document_type') === 'cooperation-contract') {
|
|
// 合作合同统一由我方固定 E 签宝账号完成第二签署方签署。
|
|
$request->merge([
|
|
'secondary_esign_account' => self::COOPERATION_SECONDARY_ESIGN_ACCOUNT,
|
|
]);
|
|
}
|
|
|
|
if (in_array($requestedType, ['account-proof', 'account-change'], true)
|
|
&& ! $request->filled('counterparty_signer_type')) {
|
|
// Existing integrations omit this field and historically meant an
|
|
// enterprise signer, so keep that behavior as the compatibility default.
|
|
$request->merge(['counterparty_signer_type' => 'organization']);
|
|
}
|
|
|
|
$data = $request->validate($this->rules());
|
|
$type = $data['document_type'];
|
|
if ($type === 'authorization' && count($data['authorization_items'] ?? []) === 0) {
|
|
throw ValidationException::withMessages(['authorization_items' => '授权委托书至少选择一项授权事项。']);
|
|
}
|
|
if ($type === 'shared-contract') {
|
|
$sharedCouriers = collect($data['shared_courier_names'] ?? [])
|
|
->map(static fn ($name): string => trim((string) $name))
|
|
->filter()
|
|
->unique()
|
|
->values()
|
|
->all();
|
|
if ($sharedCouriers === [] && filled($data['shared_courier_name'] ?? null)) {
|
|
$sharedCouriers = [trim((string) $data['shared_courier_name'])];
|
|
}
|
|
if ($sharedCouriers === []) {
|
|
throw ValidationException::withMessages(['shared_courier_names' => '请至少填写一家共用合同的快递公司。']);
|
|
}
|
|
$data['shared_courier_names'] = $sharedCouriers;
|
|
// Keep the first name for old records and integrations that still read the scalar field.
|
|
$data['shared_courier_name'] = $sharedCouriers[0];
|
|
$data['shared_note'] = trim((string) ($data['shared_note'] ?? '')) ?: null;
|
|
}
|
|
if (in_array($type, ['account-proof', 'account-change'], true)) {
|
|
$data['counterparty_signer_type'] = $data['counterparty_signer_type'] ?? 'organization';
|
|
$data['operator_name'] = trim((string) ($data['operator_name'] ?? '')) ?: null;
|
|
|
|
if ($data['counterparty_signer_type'] === 'organization' && blank($data['operator_name'])) {
|
|
throw ValidationException::withMessages(['operator_name' => '企业签署主体必须填写 E 签宝实名认证姓名。']);
|
|
}
|
|
|
|
if ($data['counterparty_signer_type'] === 'person') {
|
|
// A personal signer uses the same real name for the signer and
|
|
// the E-sign identity; the UI should not require it twice.
|
|
$data['operator_name'] = trim((string) $data['counterparty_organization_name']);
|
|
}
|
|
}
|
|
$this->validateEsignAccount($data['esign_account'], 'esign_account');
|
|
if (! empty($data['secondary_esign_account'])) {
|
|
$this->validateEsignAccount($data['secondary_esign_account'], 'secondary_esign_account');
|
|
}
|
|
|
|
if ($type === 'quote-confirmation') {
|
|
return $this->sendQuoteConfirmation($data);
|
|
}
|
|
|
|
// signing_subject identifies our initiating company. The E-sign
|
|
// organization must be the counterparty that receives this request.
|
|
$data['organization_name'] = $this->counterpartyOrganizationName($data);
|
|
$data['operator_name'] = trim((string) ($data['operator_name'] ?? '')) ?: null;
|
|
$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);
|
|
}
|
|
|
|
/**
|
|
* Create a signing subject for the current tenant.
|
|
*
|
|
* The subject is deliberately kept separate from the global companies table:
|
|
* one tenant may need to sign agreements using more than one legal entity.
|
|
*/
|
|
public function storeSubject(Request $request): JsonResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
|
|
if ($response = $this->subjectsTableUnavailable()) {
|
|
return $response;
|
|
}
|
|
|
|
$data = $this->subjectData($request);
|
|
$subject = DB::transaction(function () use ($data): CourierSigningSubject {
|
|
$this->ensureSubjectNameAvailable($data['name']);
|
|
|
|
$isFirst = ! CourierSigningSubject::query()->exists();
|
|
|
|
return CourierSigningSubject::create([
|
|
'company_id' => company_id(),
|
|
'name' => $data['name'],
|
|
'unified_social_credit_code' => $data['unified_social_credit_code'],
|
|
'is_default' => $isFirst,
|
|
'created_by' => user_id(),
|
|
'updated_by' => user_id(),
|
|
]);
|
|
});
|
|
|
|
return $this->subjectResponse($subject, '公司主体已新增。');
|
|
}
|
|
|
|
public function updateSubject(Request $request, CourierSigningSubject $subject): JsonResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
$this->ensureSubjectBelongsToCurrentCompany($subject);
|
|
|
|
if ($response = $this->subjectsTableUnavailable()) {
|
|
return $response;
|
|
}
|
|
|
|
$data = $this->subjectData($request);
|
|
$this->ensureSubjectNameAvailable($data['name'], $subject);
|
|
|
|
$subject->update([
|
|
'name' => $data['name'],
|
|
'unified_social_credit_code' => $data['unified_social_credit_code'],
|
|
'updated_by' => user_id(),
|
|
]);
|
|
|
|
return $this->subjectResponse($subject->fresh(), '公司主体已更新。');
|
|
}
|
|
|
|
public function setDefaultSubject(CourierSigningSubject $subject): JsonResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
$this->ensureSubjectBelongsToCurrentCompany($subject);
|
|
|
|
if ($response = $this->subjectsTableUnavailable()) {
|
|
return $response;
|
|
}
|
|
|
|
DB::transaction(function () use ($subject): void {
|
|
CourierSigningSubject::query()
|
|
->where('is_default', true)
|
|
->update([
|
|
'is_default' => false,
|
|
'updated_by' => user_id(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
$subject->forceFill([
|
|
'is_default' => true,
|
|
'updated_by' => user_id(),
|
|
])->save();
|
|
});
|
|
|
|
return $this->subjectResponse($subject->fresh(), '默认签署主体已切换。');
|
|
}
|
|
|
|
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|RedirectResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
try {
|
|
[$path, $suffix] = match ($kind) {
|
|
'source' => [$this->agreementFileArchive->sourcePath($agreementSignature), '签署原文'],
|
|
'signed' => [$agreementSignature->signed_pdf_path, '已签署协议'],
|
|
'certificate' => [$agreementSignature->certificate_path, '签署完成证书'],
|
|
default => abort(404),
|
|
};
|
|
} catch (RuntimeException $exception) {
|
|
return redirect()
|
|
->route('business.pages.show', [
|
|
'section' => 'courier-payables',
|
|
'page' => 'agreement-signing',
|
|
'agreement_view' => 'tasks',
|
|
])
|
|
->with('error', $exception->getMessage());
|
|
}
|
|
|
|
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']);
|
|
}
|
|
|
|
public function synchronizeQuote(CourierQuoteSignature $quoteSignature): RedirectResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
$signature = $this->quoteSignatureService->synchronize($quoteSignature, user_id());
|
|
|
|
return redirect()
|
|
->route('business.pages.show', ['section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'tasks'])
|
|
->with('success', "报价确认单状态已同步:{$signature->statusLabel()}。");
|
|
}
|
|
|
|
public function downloadQuote(CourierQuoteSignature $quoteSignature, string $kind): StreamedResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
[$path, $suffix] = match ($kind) {
|
|
'source' => [$quoteSignature->source_pdf_path, '报价确认单原文'],
|
|
'signed' => [$quoteSignature->signed_pdf_path, '已签署报价确认单'],
|
|
'certificate' => [$quoteSignature->certificate_path, '签署完成证书'],
|
|
default => abort(404),
|
|
};
|
|
|
|
abort_if(blank($path) || ! \Storage::disk('uploads')->exists($path), 404, '归档文件不存在。');
|
|
$versionCode = $quoteSignature->version?->version_code ?: (string) $quoteSignature->quote_version_id;
|
|
|
|
return \Storage::disk('uploads')->download($path, $versionCode . '-' . $suffix . '.pdf', ['Content-Type' => 'application/pdf']);
|
|
}
|
|
|
|
public function previewQuote(CourierQuoteVersion $quoteVersion): JsonResponse
|
|
{
|
|
$this->ensureCanManage();
|
|
abort_unless($quoteVersion->status === 'approved', 422, '只有已审核报价版本可以生成报价确认单。');
|
|
|
|
return response()->json($this->quoteConfirmation->data($quoteVersion));
|
|
}
|
|
|
|
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))],
|
|
'quote_version_id' => ['required_if:document_type,quote-confirmation', 'nullable', 'integer', 'min:1'],
|
|
'signing_subject' => ['required', 'string', 'min:2', 'max:255'],
|
|
'company_full_name' => ['required', 'string', 'min:2', 'max:255'],
|
|
'signature_date' => ['required', 'date'],
|
|
'signing_deadline' => $date,
|
|
'esign_account' => ['required', 'string', 'max:255'],
|
|
'secondary_esign_account' => ['required_if:document_type,entity-change,cooperation-contract,authorization', 'nullable', 'string', 'max:255'],
|
|
'operator_name' => ['nullable', 'string', 'min:2', 'max:100'],
|
|
'counterparty_organization_name' => ['required_if:document_type,account-proof,account-change', 'nullable', 'string', 'min:2', 'max:255'],
|
|
'counterparty_signer_type' => ['required_if:document_type,account-proof,account-change', 'nullable', 'in:organization,person'],
|
|
'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'],
|
|
'contract_partner_name' => ['required_if:document_type,shared-contract', 'string', 'max:255'],
|
|
'shared_courier_names' => ['nullable', 'array', 'min:1'],
|
|
'shared_courier_names.*' => ['required', 'string', 'max:255'],
|
|
'shared_courier_name' => ['nullable', 'string', 'max:255'],
|
|
'shared_note' => ['nullable', 'string', 'max:2000'],
|
|
'cooperation_party_a_name' => ['required_if:document_type,cooperation-contract', 'string', 'max:255'],
|
|
'cooperation_party_a_phone' => ['required_if:document_type,cooperation-contract', 'string', 'max:64'],
|
|
'cooperation_party_b_name' => ['required_if:document_type,cooperation-contract', 'string', 'max:255'],
|
|
'cooperation_party_b_phone' => ['required_if:document_type,cooperation-contract', 'string', 'max:64'],
|
|
'cooperation_start_date' => ['required_if:document_type,cooperation-contract', 'date'],
|
|
'cooperation_end_date' => ['required_if:document_type,cooperation-contract', 'date', 'after_or_equal:cooperation_start_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 sendQuoteConfirmation(array $data): RedirectResponse
|
|
{
|
|
$version = CourierQuoteVersion::query()
|
|
->where('status', 'approved')
|
|
->findOrFail((int) $data['quote_version_id']);
|
|
$this->ensureQuoteVersionMatchesCourier($version, $data['courier_name']);
|
|
|
|
$signature = $this->quoteSignatureService->prepareVersion($version, user_id(), $data['signature_date']);
|
|
$account = trim((string) $data['esign_account']);
|
|
$signature = $this->quoteSignatureService->send($signature, [
|
|
'organization_name' => $version->carrier_name,
|
|
'operator_name' => trim((string) ($data['operator_name'] ?? '')) ?: $version->carrier_name,
|
|
'operator_account' => $account,
|
|
'notice_type' => filter_var($account, FILTER_VALIDATE_EMAIL) !== false ? '2' : '1',
|
|
'expires_at' => \Illuminate\Support\Carbon::parse($data['signing_deadline'])->endOfDay(),
|
|
], user_id());
|
|
|
|
return redirect()
|
|
->route('business.pages.show', ['section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'tasks'])
|
|
->with('success', "报价确认单 {$version->version_code} 已推送到 E 签宝,等待快递方签署并自动回传归档。")
|
|
->with('quote_signature_id', $signature->id);
|
|
}
|
|
|
|
private function ensureQuoteVersionMatchesCourier(CourierQuoteVersion $version, string $courier): void
|
|
{
|
|
abort_unless(
|
|
trim($version->carrier_name) === trim($courier),
|
|
422,
|
|
'所选报价版本与快递公司不一致,请重新选择。',
|
|
);
|
|
}
|
|
|
|
private function counterpartyOrganizationName(array $data): string
|
|
{
|
|
return match ($data['document_type']) {
|
|
'account-proof', 'account-change' => trim((string) $data['counterparty_organization_name']),
|
|
'shared-contract' => trim((string) (($data['shared_courier_names'][0] ?? null) ?: ($data['shared_courier_name'] ?? $data['courier_name']))),
|
|
'entity-change' => trim((string) ($data['former_company_name'] ?? $data['courier_name'])),
|
|
'authorization' => trim((string) ($data['principal_name'] ?? $data['courier_name'])),
|
|
default => trim((string) $data['courier_name']),
|
|
};
|
|
}
|
|
|
|
private function ensureCanManage(): void
|
|
{
|
|
abort_unless(
|
|
in_array(SystemAccess::roleName(user()), [SystemAccess::SUPER_ADMIN, SystemAccess::ADMIN], true),
|
|
403,
|
|
'当前账号没有协议签署管理权限。',
|
|
);
|
|
}
|
|
|
|
private function subjectData(Request $request): array
|
|
{
|
|
$data = $request->validate([
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'unified_social_credit_code' => ['nullable', 'string', 'max:32', 'regex:/^[0-9A-Za-z]{18}$/'],
|
|
]);
|
|
|
|
$data['name'] = trim($data['name']);
|
|
if (mb_strlen($data['name']) < 2) {
|
|
throw ValidationException::withMessages(['name' => '公司名称至少填写 2 个字符。']);
|
|
}
|
|
|
|
$data['unified_social_credit_code'] = trim((string) ($data['unified_social_credit_code'] ?? '')) ?: null;
|
|
|
|
return $data;
|
|
}
|
|
|
|
private function ensureSubjectNameAvailable(string $name, ?CourierSigningSubject $except = null): void
|
|
{
|
|
$query = CourierSigningSubject::query()->where('name', $name);
|
|
|
|
if ($except) {
|
|
$query->where($query->getModel()->getQualifiedKeyName(), '<>', $except->getKey());
|
|
}
|
|
|
|
if ($query->exists()) {
|
|
throw ValidationException::withMessages(['name' => '当前公司下已有同名签署主体。']);
|
|
}
|
|
}
|
|
|
|
private function subjectResponse(CourierSigningSubject $subject, string $message): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'message' => $message,
|
|
'subject' => $this->serializeSubject($subject),
|
|
'subjects' => CourierSigningSubject::query()
|
|
->orderByDesc('is_default')
|
|
->orderBy('name')
|
|
->get()
|
|
->map(fn (CourierSigningSubject $item): array => $this->serializeSubject($item))
|
|
->values(),
|
|
]);
|
|
}
|
|
|
|
private function serializeSubject(CourierSigningSubject $subject): array
|
|
{
|
|
return [
|
|
'id' => $subject->id,
|
|
'name' => $subject->name,
|
|
'unified_social_credit_code' => $subject->unified_social_credit_code,
|
|
'is_default' => (bool) $subject->is_default,
|
|
];
|
|
}
|
|
|
|
private function subjectsTableUnavailable(): ?JsonResponse
|
|
{
|
|
if (Schema::hasTable('business_courier_signing_subjects')) {
|
|
return null;
|
|
}
|
|
|
|
return response()->json([
|
|
'message' => '公司主体数据表尚未完成迁移,请先执行数据库迁移。',
|
|
], 503);
|
|
}
|
|
|
|
private function ensureSubjectBelongsToCurrentCompany(CourierSigningSubject $subject): void
|
|
{
|
|
abort_unless((int) $subject->company_id === (int) company_id(), 404, '签署主体不存在。');
|
|
}
|
|
|
|
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 位手机号或有效邮箱。']);
|
|
}
|
|
}
|
|
}
|