修复共用合同 E 签宝主体与实名认证字段 #286
@ -64,7 +64,9 @@ class CourierAgreementSignatures extends Controller
|
||||
}
|
||||
|
||||
$data = $request->validate($this->rules(), [
|
||||
'operator_name.required_if' => '请填写快递方经办人的 E 签宝实名认证姓名。',
|
||||
'operator_name.required_if' => $requestedType === 'shared-contract'
|
||||
? '请填写合同主体经办人的 E 签宝实名认证姓名。'
|
||||
: '请填写快递方经办人的 E 签宝实名认证姓名。',
|
||||
'secondary_operator_name.required_if' => '请填写我方 E 签宝账号绑定的实名认证姓名。',
|
||||
]);
|
||||
$type = $data['document_type'];
|
||||
@ -317,7 +319,7 @@ class CourierAgreementSignatures extends Controller
|
||||
'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' => ['required_if:document_type,cooperation-contract', 'nullable', 'string', 'min:2', 'max:100'],
|
||||
'operator_name' => ['required_if:document_type,cooperation-contract,shared-contract', 'nullable', 'string', 'min:2', 'max:100'],
|
||||
'secondary_operator_name' => ['required_if:document_type,cooperation-contract', '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'],
|
||||
@ -408,7 +410,7 @@ class CourierAgreementSignatures extends Controller
|
||||
{
|
||||
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']))),
|
||||
'shared-contract' => trim((string) $data['contract_partner_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']),
|
||||
|
||||
@ -92,6 +92,7 @@ final class CourierAgreementSignatureService
|
||||
|
||||
$flowCreationStarted = false;
|
||||
try {
|
||||
$signers = $this->signersFor($signature);
|
||||
$client = app(EsignClient::class);
|
||||
$bytes = Storage::disk('uploads')->get($signature->source_pdf_path);
|
||||
$fileName = 'agreement-' . $signature->business_no . '.pdf';
|
||||
@ -103,11 +104,13 @@ final class CourierAgreementSignatureService
|
||||
$signer = [
|
||||
'flow_title' => $this->safeTitle($signature->company_full_name . ' ' . $signature->documentLabel()),
|
||||
'organization_name' => $signature->organization_name,
|
||||
'operator_name' => $signature->operator_name ?: $signature->courier_name,
|
||||
'operator_name' => $signature->document_type === 'shared-contract'
|
||||
? $signature->operator_name
|
||||
: ($signature->operator_name ?: $signature->courier_name),
|
||||
'operator_account' => $signature->operator_account,
|
||||
'notice_types' => '1',
|
||||
];
|
||||
$signer['signers'] = $this->signersFor($signature);
|
||||
$signer['signers'] = $signers;
|
||||
$flowCreationStarted = true;
|
||||
$signFlowId = $client->createSignFlow($fileId, $fileName, $signer, $signature->expires_at->getTimestampMs());
|
||||
|
||||
@ -284,6 +287,19 @@ final class CourierAgreementSignatureService
|
||||
{
|
||||
$payload = $signature->payload ?? [];
|
||||
|
||||
if ($signature->document_type === 'shared-contract') {
|
||||
if (blank($signature->operator_name)) {
|
||||
throw new RuntimeException('请填写合同主体经办人的 E 签宝实名认证姓名。');
|
||||
}
|
||||
|
||||
return [[
|
||||
'signer_type' => 1,
|
||||
'organization_name' => $signature->organization_name,
|
||||
'operator_name' => $signature->operator_name,
|
||||
'operator_account' => $signature->operator_account,
|
||||
]];
|
||||
}
|
||||
|
||||
if (in_array($signature->document_type, ['account-proof', 'account-change'], true)) {
|
||||
$isPerson = ($payload['counterparty_signer_type'] ?? 'organization') === 'person';
|
||||
|
||||
|
||||
@ -1509,7 +1509,7 @@
|
||||
<div class="agreement-field">
|
||||
<label for="agreement-operator-name">对方 E 签宝实名认证姓名 <span class="agreement-required" data-account-operator-required aria-hidden="true">*</span></label>
|
||||
<input class="agreement-control" id="agreement-operator-name" name="operator_name" type="text" placeholder="填写与 E 签宝账号匹配的实名姓名" autocomplete="name" data-account-operator-name required>
|
||||
<p class="agreement-help">仅用于 E 签宝身份核验,不是收款人姓名;收款人姓名请填写在下方账户资料中。</p>
|
||||
<p class="agreement-help" data-account-operator-help>仅用于 E 签宝身份核验,不是收款人姓名;收款人姓名请填写在下方账户资料中。</p>
|
||||
</div>
|
||||
<div class="agreement-field" data-quote-confirmation-field hidden>
|
||||
<label for="agreement-quote-version">报价版本 <span class="agreement-required" aria-hidden="true">*</span></label>
|
||||
@ -1616,11 +1616,12 @@
|
||||
|
||||
<div class="agreement-form-grid" data-template-fields="shared-contract" hidden>
|
||||
<div class="agreement-field">
|
||||
<label for="agreement-contract-partner">快递公司甲(主体) <span class="agreement-required" aria-hidden="true">*</span></label>
|
||||
<input class="agreement-control" id="agreement-contract-partner" name="contract_partner_name" type="text" placeholder="与...合作的公司" required>
|
||||
<label for="agreement-contract-partner">合同主体(企业全称) <span class="agreement-required" aria-hidden="true">*</span></label>
|
||||
<input class="agreement-control" id="agreement-contract-partner" name="contract_partner_name" type="text" placeholder="填写合同主体在 E 签宝认证的企业全称" autocomplete="organization" required>
|
||||
<p class="agreement-help">该企业作为本证明的签署主体,名称须与 E 签宝企业认证一致。</p>
|
||||
</div>
|
||||
<div class="agreement-field agreement-field--full">
|
||||
<label>共用合同的快递公司(可多个) <span class="agreement-required" aria-hidden="true">*</span></label>
|
||||
<label>共用合同物流商(快递名称,可多个) <span class="agreement-required" aria-hidden="true">*</span></label>
|
||||
<div class="agreement-shared-courier-list" data-shared-courier-list>
|
||||
<div class="agreement-shared-courier-row">
|
||||
<input class="agreement-control" name="shared_courier_names[]" type="text" placeholder="第1家快递公司名称" data-shared-courier required>
|
||||
@ -1631,7 +1632,7 @@
|
||||
<span class="material-icons-outlined" aria-hidden="true">add</span>
|
||||
<span>添加共用公司</span>
|
||||
</button>
|
||||
<p class="agreement-help">填写与快递公司甲共用同一份合同的快递公司,可添加多个。</p>
|
||||
<p class="agreement-help">填写归属上述合同主体、共用合同和打款资料的快递名称,可添加多个。</p>
|
||||
</div>
|
||||
<div class="agreement-field agreement-field--full">
|
||||
<label for="agreement-shared-note">备注</label>
|
||||
@ -2011,6 +2012,7 @@
|
||||
var operatorRequiredMark = root.querySelector('[data-account-operator-required]');
|
||||
var cooperationOperatorField = root.querySelector('[data-cooperation-operator-field]');
|
||||
var secondaryOperatorInput = root.querySelector('input[name="secondary_operator_name"]');
|
||||
var operatorHelp = root.querySelector('[data-account-operator-help]');
|
||||
var sharedCourierList = root.querySelector('[data-shared-courier-list]');
|
||||
var sharedCourierAdd = root.querySelector('[data-shared-courier-add]');
|
||||
var quoteVersionField = root.querySelector('[data-quote-confirmation-field]');
|
||||
@ -2716,7 +2718,7 @@
|
||||
return input.checked;
|
||||
});
|
||||
var accountSignerType = accountSignerTypeInput ? accountSignerTypeInput.value : 'organization';
|
||||
var accountSignerIsPerson = accountSignerType === 'person';
|
||||
var accountSignerIsPerson = accountAgreement && accountSignerType === 'person';
|
||||
if (accountSignerTypeField) {
|
||||
accountSignerTypeField.hidden = !accountAgreement;
|
||||
}
|
||||
@ -2732,7 +2734,7 @@
|
||||
: '填写对方在 E 签宝认证的企业全称';
|
||||
}
|
||||
if (operatorNameInput) {
|
||||
var needsOperatorName = cooperationContract || (accountAgreement && !accountSignerIsPerson);
|
||||
var needsOperatorName = cooperationContract || templateType === 'shared-contract' || (accountAgreement && !accountSignerIsPerson);
|
||||
operatorNameInput.disabled = !needsOperatorName;
|
||||
operatorNameInput.required = needsOperatorName;
|
||||
if (accountAgreement && accountSignerIsPerson) {
|
||||
@ -2747,6 +2749,11 @@
|
||||
secondaryOperatorInput.disabled = !cooperationContract;
|
||||
secondaryOperatorInput.required = cooperationContract;
|
||||
}
|
||||
if (operatorHelp) {
|
||||
operatorHelp.textContent = templateType === 'shared-contract'
|
||||
? '填写合同主体经办人的真实姓名,须与上方 E 签宝账号实名认证一致;快递名称填写在下方物流商名单。'
|
||||
: '仅用于 E 签宝身份核验,不是收款人姓名;收款人姓名请填写在下方账户资料中。';
|
||||
}
|
||||
if (accountSignerLabel) {
|
||||
accountSignerLabel.textContent = accountSignerIsPerson ? '对方签署个人姓名' : '对方签署企业全称';
|
||||
}
|
||||
|
||||
@ -348,8 +348,9 @@ class AgreementSigningPageTest extends FeatureTestCase
|
||||
->assertSee('data-template-fields="shared-contract"', false)
|
||||
->assertSee('data-template-preview="shared-contract"', false)
|
||||
->assertSeeText('合同合作方')
|
||||
->assertSeeText('快递公司甲(主体)')
|
||||
->assertSeeText('共用合同的快递公司(可多个)')
|
||||
->assertSeeText('合同主体(企业全称)')
|
||||
->assertSeeText('共用合同物流商(快递名称,可多个)')
|
||||
->assertSeeText('填写合同主体经办人的真实姓名,须与上方 E 签宝账号实名认证一致')
|
||||
->assertSee('name="shared_courier_names[]"', false)
|
||||
->assertSee('data-shared-courier-add', false)
|
||||
->assertSeeText('添加共用公司')
|
||||
|
||||
@ -265,20 +265,68 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
|
||||
|
||||
$signature = CourierAgreementSignature::query()->firstOrFail();
|
||||
$this->assertSame(['快递公司乙', '快递公司丙'], $signature->payload['shared_courier_names']);
|
||||
$this->assertSame('快递公司甲', $signature->organization_name);
|
||||
$this->assertSame('张经理', $signature->operator_name);
|
||||
$this->assertSame('快递公司乙', $signature->payload['shared_courier_name']);
|
||||
$this->assertSame('请在3个工作日内完成签章。', $signature->payload['shared_note']);
|
||||
$pdf = Storage::disk('uploads')->get($signature->source_pdf_path);
|
||||
$this->assertStringStartsWith('%PDF-', $pdf);
|
||||
$html = view('business.pdf.courier-agreement', [
|
||||
'signature' => $signature,
|
||||
'type' => $signature->document_type,
|
||||
'title' => $signature->documentLabel(),
|
||||
'payload' => $signature->payload,
|
||||
'signatureDate' => '2026年9月21日',
|
||||
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
|
||||
])->render();
|
||||
foreach (['快递公司甲', '快递公司乙', '快递公司丙', '请在3个工作日内完成签章。'] as $text) {
|
||||
$this->assertStringContainsString($text, strip_tags($html));
|
||||
}
|
||||
|
||||
Http::assertSent(function (ClientRequest $request): bool {
|
||||
if ($request->url() !== 'https://smlopenapi.esign.test/v3/sign-flow/create-by-file') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return data_get($request->data(), 'signers.0.orgSignerInfo.orgName') === '快递公司乙';
|
||||
return count($request->data()['signers'] ?? []) === 1
|
||||
&& data_get($request->data(), 'signers.0.signerType') === 1
|
||||
&& data_get($request->data(), 'signers.0.orgSignerInfo.orgName') === '快递公司甲'
|
||||
&& data_get($request->data(), 'signers.0.orgSignerInfo.transactorInfo.psnAccount') === '13800138000'
|
||||
&& data_get($request->data(), 'signers.0.orgSignerInfo.transactorInfo.psnInfo.psnName') === '张经理';
|
||||
});
|
||||
}
|
||||
|
||||
public function test_shared_contract_rejects_missing_real_name_without_creating_a_task_or_remote_request(): void
|
||||
{
|
||||
Http::fake();
|
||||
$payload = [
|
||||
'idempotency_key' => 'shared-contract-missing-name',
|
||||
'courier_name' => '快递公司乙',
|
||||
'document_type' => 'shared-contract',
|
||||
'signing_subject' => $this->company->name,
|
||||
'company_full_name' => $this->company->name,
|
||||
'signature_date' => '2026-09-21',
|
||||
'signing_deadline' => now()->addDays(7)->toDateString(),
|
||||
'esign_account' => '13800138000',
|
||||
'archive_mode' => 'auto',
|
||||
'contract_partner_name' => '合同主体有限公司',
|
||||
'shared_courier_names' => ['快递公司乙', '快递公司丙'],
|
||||
];
|
||||
|
||||
$this->withExceptionHandling()->loginAs()
|
||||
->postJson(route('business.courier-agreement-signatures.store'), $payload)
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors('operator_name');
|
||||
|
||||
$payload['operator_name'] = ' ';
|
||||
$this->postJson(route('business.courier-agreement-signatures.store'), $payload)
|
||||
->assertStatus(422)
|
||||
->assertJsonValidationErrors('operator_name');
|
||||
|
||||
$this->assertDatabaseCount('business_courier_agreement_signatures', 0);
|
||||
Http::assertNothingSent();
|
||||
}
|
||||
|
||||
public function test_quote_confirmation_binds_an_approved_version_and_uses_only_the_courier_signer(): void
|
||||
{
|
||||
$this->fakeEsign();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user