diff --git a/app/Http/Controllers/Business/CourierAgreementSignatures.php b/app/Http/Controllers/Business/CourierAgreementSignatures.php index 60e336cf..8ab884b0 100644 --- a/app/Http/Controllers/Business/CourierAgreementSignatures.php +++ b/app/Http/Controllers/Business/CourierAgreementSignatures.php @@ -63,7 +63,10 @@ class CourierAgreementSignatures extends Controller $request->merge(['counterparty_signer_type' => 'organization']); } - $data = $request->validate($this->rules()); + $data = $request->validate($this->rules(), [ + 'operator_name.required_if' => '请填写快递方经办人的 E 签宝实名认证姓名。', + 'secondary_operator_name.required_if' => '请填写我方 E 签宝账号绑定的实名认证姓名。', + ]); $type = $data['document_type']; if ($type === 'authorization' && count($data['authorization_items'] ?? []) === 0) { throw ValidationException::withMessages(['authorization_items' => '授权委托书至少选择一项授权事项。']); @@ -100,6 +103,17 @@ class CourierAgreementSignatures extends Controller $data['operator_name'] = trim((string) $data['counterparty_organization_name']); } } + if ($type === 'cooperation-contract') { + $companyNames = [trim($data['cooperation_party_a_name']), trim($data['cooperation_party_b_name'])]; + foreach (['operator_name' => '快递方', 'secondary_operator_name' => '我方'] as $field => $party) { + $data[$field] = trim($data[$field]); + if (in_array($data[$field], $companyNames, true)) { + throw ValidationException::withMessages([ + $field => "{$party}实名认证姓名应填写账号绑定的个人姓名,不能填写甲乙方公司名称。", + ]); + } + } + } $this->validateEsignAccount($data['esign_account'], 'esign_account'); if (! empty($data['secondary_esign_account'])) { $this->validateEsignAccount($data['secondary_esign_account'], 'secondary_esign_account'); @@ -303,7 +317,8 @@ 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' => ['nullable', 'string', 'min:2', 'max:100'], + 'operator_name' => ['required_if:document_type,cooperation-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'], 'archive_mode' => ['required', 'in:auto,manual'], diff --git a/app/Support/CourierAgreementSignatureService.php b/app/Support/CourierAgreementSignatureService.php index f10dfa51..f0f6fe46 100644 --- a/app/Support/CourierAgreementSignatureService.php +++ b/app/Support/CourierAgreementSignatureService.php @@ -307,13 +307,13 @@ final class CourierAgreementSignatureService [ 'signer_type' => 1, 'organization_name' => $payload['cooperation_party_a_name'] ?? $signature->company_full_name, - 'operator_name' => $payload['cooperation_party_a_name'] ?? $signature->company_full_name, + 'operator_name' => $payload['secondary_operator_name'], 'operator_account' => $payload['secondary_esign_account'] ?? $signature->operator_account, ], [ 'signer_type' => 1, 'organization_name' => $payload['cooperation_party_b_name'] ?? $signature->courier_name, - 'operator_name' => $signature->operator_name ?: ($payload['cooperation_party_b_name'] ?? $signature->courier_name), + 'operator_name' => $signature->operator_name, 'operator_account' => $signature->operator_account, ], ]; diff --git a/resources/views/business/pages/courier-payables/agreement-signing.blade.php b/resources/views/business/pages/courier-payables/agreement-signing.blade.php index d97fda09..5b7b529e 100644 --- a/resources/views/business/pages/courier-payables/agreement-signing.blade.php +++ b/resources/views/business/pages/courier-payables/agreement-signing.blade.php @@ -1528,6 +1528,11 @@

主体变更、合作合同和授权委托书需要双方分别实名签署;合作合同统一使用我方账号 {{ $agreementCooperationSecondaryEsignAccount }}。

+ @@ -2004,6 +2009,8 @@ var accountSignerHelp = root.querySelector('[data-account-signer-help]'); var operatorNameInput = root.querySelector('[data-account-operator-name]'); 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 sharedCourierList = root.querySelector('[data-shared-courier-list]'); var sharedCourierAdd = root.querySelector('[data-shared-courier-add]'); var quoteVersionField = root.querySelector('[data-quote-confirmation-field]'); @@ -2704,6 +2711,7 @@ var courier = fieldValue('[name="courier_name"]', '快递公司'); var companyName = fieldValue('[name="company_full_name"]', '待填写公司全称'); var accountAgreement = templateType === 'account-proof' || templateType === 'account-change'; + var cooperationContract = templateType === 'cooperation-contract'; var accountSignerTypeInput = accountSignerTypeInputs.find(function (input) { return input.checked; }); @@ -2724,14 +2732,20 @@ : '填写对方在 E 签宝认证的企业全称'; } if (operatorNameInput) { - operatorNameInput.disabled = !accountAgreement || accountSignerIsPerson; - operatorNameInput.required = accountAgreement && !accountSignerIsPerson; - if (accountSignerIsPerson) { + var needsOperatorName = cooperationContract || (accountAgreement && !accountSignerIsPerson); + operatorNameInput.disabled = !needsOperatorName; + operatorNameInput.required = needsOperatorName; + if (accountAgreement && accountSignerIsPerson) { operatorNameInput.value = accountOrganizationInput ? accountOrganizationInput.value : ''; } } if (operatorRequiredMark) { - operatorRequiredMark.hidden = !accountAgreement || accountSignerIsPerson; + operatorRequiredMark.hidden = !operatorNameInput.required; + } + if (cooperationOperatorField) { + cooperationOperatorField.hidden = !cooperationContract; + secondaryOperatorInput.disabled = !cooperationContract; + secondaryOperatorInput.required = cooperationContract; } if (accountSignerLabel) { accountSignerLabel.textContent = accountSignerIsPerson ? '对方签署个人姓名' : '对方签署企业全称'; diff --git a/tests/Feature/Business/AgreementSigningPageTest.php b/tests/Feature/Business/AgreementSigningPageTest.php index 1643fc1a..a5f1a90b 100644 --- a/tests/Feature/Business/AgreementSigningPageTest.php +++ b/tests/Feature/Business/AgreementSigningPageTest.php @@ -93,6 +93,8 @@ class AgreementSigningPageTest extends FeatureTestCase ->assertSee("setPreviewValue('proofSigner', '公司盖章')", false) ->assertSee("setPreviewValue('changeSigner', '公司盖章')", false) ->assertSeeText('合作合同统一使用我方账号 18052733960') + ->assertSeeText('我方 E 签宝实名认证姓名') + ->assertSee('name="secondary_operator_name"', false) ->assertSeeText('填写变量') ->assertSeeText('签署完成后自动归档') ->assertSeeText('人工确认后归档') diff --git a/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php b/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php index 3bcd66e3..a49cfdab 100644 --- a/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php +++ b/tests/Feature/Business/CourierAgreementSignatureWorkflowTest.php @@ -181,25 +181,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase public function test_cooperation_contract_maps_party_variables_and_creates_two_signers(): void { $this->fakeEsign(); - $payload = [ - 'idempotency_key' => 'cooperation-001', - 'courier_name' => '新北中通', - 'document_type' => 'cooperation-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', - 'secondary_esign_account' => '13900139000', - 'operator_name' => '张经理', - 'archive_mode' => 'auto', - 'cooperation_party_a_name' => $this->company->name, - 'cooperation_party_a_phone' => '0519-88886666', - 'cooperation_party_b_name' => '新北中通快递有限公司', - 'cooperation_party_b_phone' => '18112896651', - 'cooperation_start_date' => '2026-01-01', - 'cooperation_end_date' => '2026-12-31', - ]; + $payload = $this->cooperationContractPayload('cooperation-001'); $this->loginAs() ->post(route('business.courier-agreement-signatures.store'), $payload) @@ -209,6 +191,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase $this->assertSame('cooperation-contract', $signature->document_type); $this->assertSame('TPL-COOPERATION-CONTRACT-V1', $signature->template_code); $this->assertSame('18052733960', $signature->payload['secondary_esign_account']); + $this->assertSame('李经理', $signature->payload['secondary_operator_name']); $this->assertSame('0519-88886666', $signature->payload['cooperation_party_a_phone']); $this->assertSame('2026-12-31', $signature->payload['cooperation_end_date']); @@ -223,10 +206,41 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase return count($signers) === 2 && ($signers[0]['orgSignerInfo']['orgName'] ?? null) === $this->company->name && ($signers[0]['orgSignerInfo']['transactorInfo']['psnAccount'] ?? null) === '18052733960' - && ($signers[1]['orgSignerInfo']['orgName'] ?? null) === '新北中通快递有限公司'; + && ($signers[0]['orgSignerInfo']['transactorInfo']['psnInfo']['psnName'] ?? null) === '李经理' + && ($signers[1]['orgSignerInfo']['orgName'] ?? null) === '新北中通快递有限公司' + && ($signers[1]['orgSignerInfo']['transactorInfo']['psnAccount'] ?? null) === '13800138000' + && ($signers[1]['orgSignerInfo']['transactorInfo']['psnInfo']['psnName'] ?? null) === '张经理'; }); } + public function test_cooperation_contract_requires_both_real_names_before_creating_a_task(): void + { + $this->fakeEsign(); + foreach (['operator_name', 'secondary_operator_name'] as $field) { + $payload = $this->cooperationContractPayload('missing-' . $field); + $payload[$field] = ' '; + $this->withExceptionHandling()->loginAs() + ->post(route('business.courier-agreement-signatures.store'), $payload) + ->assertSessionHasErrors($field); + } + $this->assertSame(0, CourierAgreementSignature::query()->count()); + Http::assertNothingSent(); + } + + public function test_cooperation_contract_rejects_company_names_as_personal_names(): void + { + $this->fakeEsign(); + foreach (['operator_name' => 'cooperation_party_b_name', 'secondary_operator_name' => 'cooperation_party_a_name'] as $field => $companyField) { + $payload = $this->cooperationContractPayload('company-' . $field); + $payload[$field] = $payload[$companyField]; + $this->withExceptionHandling()->loginAs() + ->post(route('business.courier-agreement-signatures.store'), $payload) + ->assertSessionHasErrors($field); + } + $this->assertSame(0, CourierAgreementSignature::query()->count()); + Http::assertNothingSent(); + } + public function test_shared_contract_accepts_multiple_couriers_and_preserves_note_in_pdf(): void { $this->fakeEsign(); @@ -716,6 +730,30 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase ]; } + private function cooperationContractPayload(string $idempotencyKey): array + { + return [ + 'idempotency_key' => $idempotencyKey, + 'courier_name' => '新北中通', + 'document_type' => 'cooperation-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', + 'secondary_esign_account' => '13900139000', + 'operator_name' => '张经理', + 'secondary_operator_name' => '李经理', + 'archive_mode' => 'auto', + 'cooperation_party_a_name' => $this->company->name, + 'cooperation_party_a_phone' => '0519-88886666', + 'cooperation_party_b_name' => '新北中通快递有限公司', + 'cooperation_party_b_phone' => '18112896651', + 'cooperation_start_date' => '2026-01-01', + 'cooperation_end_date' => '2026-12-31', + ]; + } + private function accountChangePayload(string $idempotencyKey): array { return [