修复合作合同双方 E 签宝个人实名信息 #285

Merged
limeng merged 1 commits from fix/limeng-cooperation-real-name into main 2026-10-10 10:48:09 +08:00
5 changed files with 97 additions and 28 deletions

View File

@ -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'],

View File

@ -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,
],
];

View File

@ -1528,6 +1528,11 @@
<input class="agreement-control" id="agreement-secondary-esign-account" name="secondary_esign_account" type="text" placeholder="填写另一签署方的手机号或邮箱" autocomplete="email" disabled>
<p class="agreement-help">主体变更、合作合同和授权委托书需要双方分别实名签署;合作合同统一使用我方账号 {{ $agreementCooperationSecondaryEsignAccount }}。</p>
</div>
<div class="agreement-field" data-cooperation-operator-field hidden>
<label for="agreement-secondary-operator-name">我方 E 签宝实名认证姓名 <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-secondary-operator-name" name="secondary_operator_name" type="text" placeholder="填写我方账号绑定的个人真实姓名" autocomplete="name" disabled>
<p class="agreement-help">双方实名认证姓名均填写各自账号绑定的个人姓名;甲乙方公司名称在下方合同资料中填写。</p>
</div>
</div>
</section>
@ -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 ? '对方签署个人姓名' : '对方签署企业全称';

View File

@ -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('人工确认后归档')

View File

@ -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 [