修复已注销主体变更遗漏现公司签署 (#289)

This commit is contained in:
limeng 2026-10-10 13:28:37 +08:00
parent 0234bae83f
commit 153b823ab8
6 changed files with 110 additions and 48 deletions

View File

@ -121,16 +121,11 @@ class CourierAgreementSignatures extends Controller
? '请填写原公司法人的 E 签宝实名认证姓名。'
: '请填写变更前主体签署人的 E 签宝实名认证姓名。']);
}
if ($formerCompanyDeregistered) {
$data['secondary_esign_account'] = null;
$data['secondary_operator_name'] = null;
} else {
if (blank($data['secondary_esign_account'] ?? null)) {
throw ValidationException::withMessages(['secondary_esign_account' => '普通主体变更需要填写变更后主体的 E 签宝账号。']);
}
if (blank($data['secondary_operator_name'] ?? null)) {
throw ValidationException::withMessages(['secondary_operator_name' => '请填写变更后主体签署人的 E 签宝实名认证姓名。']);
}
if (blank($data['secondary_esign_account'] ?? null)) {
throw ValidationException::withMessages(['secondary_esign_account' => '请填写变更后主体的 E 签宝账号。']);
}
if (blank($data['secondary_operator_name'] ?? null)) {
throw ValidationException::withMessages(['secondary_operator_name' => '请填写变更后主体签署人的 E 签宝实名认证姓名。']);
}
}
if ($type === 'cooperation-contract') {

View File

@ -342,22 +342,25 @@ final class CourierAgreementSignatureService
$payload['entity_change_old_company_deregistered'] ?? false,
FILTER_VALIDATE_BOOLEAN,
);
if ($formerCompanyDeregistered || ($payload['counterparty_signer_type'] ?? 'organization') === 'person') {
return [[
if (blank($payload['secondary_esign_account'] ?? null) || blank($payload['secondary_operator_name'] ?? null)) {
throw new RuntimeException('请填写变更后主体的 E 签宝账号和实名认证姓名。');
}
$formerSigner = $formerCompanyDeregistered || ($payload['counterparty_signer_type'] ?? 'organization') === 'person'
? [
'signer_type' => 0,
'organization_name' => null,
'operator_name' => $signature->operator_name,
'operator_account' => $signature->operator_account,
]];
}
]
: array_merge($primary, ['organization_name' => $payload['former_company_name'] ?? $signature->organization_name]);
return [
array_merge($primary, ['organization_name' => $payload['former_company_name'] ?? $signature->organization_name]),
$formerSigner,
[
'signer_type' => 1,
'organization_name' => $payload['changed_company_name'] ?? $signature->organization_name,
'operator_name' => $payload['secondary_operator_name'] ?? $signature->operator_name,
'operator_account' => $payload['secondary_esign_account'] ?? $signature->operator_account,
'operator_name' => $payload['secondary_operator_name'],
'operator_account' => $payload['secondary_esign_account'],
],
];
}

View File

@ -1597,7 +1597,7 @@
<div class="agreement-form-grid" data-template-fields="entity-change" hidden>
<div class="agreement-field agreement-field--full">
<label class="agreement-checkbox-choice"><input type="checkbox" name="entity_change_old_company_deregistered" value="1" data-entity-change-deregistered><span><strong>变更前公司已注销,使用原法人个人签署</strong><span>旧主体账号不可用时,使用原公司法人实名认证的个人账号签署。</span></span></label>
<label class="agreement-checkbox-choice"><input type="checkbox" name="entity_change_old_company_deregistered" value="1" data-entity-change-deregistered><span><strong>变更前公司已注销,使用原法人个人签署</strong><span>原法人以个人身份签字,变更后公司仍须法人签字并盖章。</span></span></label>
</div>
<div class="agreement-field">
<label for="agreement-former-company">变更前公司名称 <span class="agreement-required" aria-hidden="true">*</span></label>
@ -1803,8 +1803,8 @@
<p>特此证明!</p>
<div class="agreement-preview-signature">
<p data-entity-organization-signatures>变更前公司法人签字盖公章:</p>
<p data-entity-organization-signatures>变更后公司法人签字盖公章:</p>
<p data-entity-person-signature hidden>原公司法人个人签字:<span class="agreement-preview-value" data-preview-bind="formerLegalRepresentative">待填写</span></p>
<p>变更后公司法人签字盖公章:<span class="agreement-preview-value" data-preview-bind="changedCompanyName">待填写</span>(签署人:<span class="agreement-preview-value" data-preview-bind="changedLegalRepresentative">待填写</span>)</p>
<p>日期:<span data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p>
</div>
<p class="agreement-preview-page-number">第 1 页 / 共 1 页</p>
@ -2811,7 +2811,7 @@
refreshSignerAccountLabels(templateType);
var secondary = root.querySelector('[data-secondary-signer-fields]');
if (secondary) {
var needsSecondary = (entityChange && !formerCompanyDeregistered) || templateType === 'cooperation-contract' || templateType === 'authorization';
var needsSecondary = entityChange || templateType === 'cooperation-contract' || templateType === 'authorization';
secondary.hidden = !needsSecondary;
secondary.querySelectorAll('input').forEach(function (control) {
control.disabled = !needsSecondary;
@ -2834,7 +2834,7 @@
}
}
if (secondaryOperatorField) {
var needsSecondaryOperator = (entityChange && !formerCompanyDeregistered) || cooperationContract || (authorization && !trusteeIsPerson);
var needsSecondaryOperator = entityChange || cooperationContract || (authorization && !trusteeIsPerson);
secondaryOperatorField.hidden = !needsSecondaryOperator;
secondaryOperatorInput.disabled = !needsSecondaryOperator;
secondaryOperatorInput.required = needsSecondaryOperator;
@ -2842,7 +2842,7 @@
}
setText('[data-template-name]', documentOption.dataset.name);
setText('[data-template-code]', documentOption.dataset.code);
setText('[data-template-description]', formerCompanyDeregistered ? '旧公司作为历史主体保留,仅向原法人发送个人签署请求。' : otherSignerIsPerson ? '使用合作人的个人实名认证姓名和账号签署。' : documentOption.dataset.description);
setText('[data-template-description]', formerCompanyDeregistered ? '原法人以个人身份签字,变更后公司仍须法人签字并盖章;双方分别接收签署请求。' : otherSignerIsPerson ? '使用合作人的个人实名认证姓名和账号签署。' : documentOption.dataset.description);
setText('[data-variable-template-name]', documentOption.dataset.name);
setText('[data-preview-template-code]', documentOption.dataset.code);
setPreviewValue('company', companyName);
@ -2875,6 +2875,7 @@
setPreviewValue('changedCompanyName', fieldValue('[name="changed_company_name"]', '待填写'));
setPreviewValue('changedCompanyStartDate', formatDateField('[name="changed_company_start_date"]', '待填写'));
setPreviewValue('formerLegalRepresentative', fieldValue('[name="operator_name"]', '待填写'));
setPreviewValue('changedLegalRepresentative', fieldValue('[name="secondary_operator_name"]', '待填写'));
root.querySelectorAll('[data-entity-organization-signatures]').forEach(function (element) {
element.hidden = formerCompanyDeregistered;
});

View File

@ -77,11 +77,15 @@
@endif
<p>变更后,原公司的各项事宜由变更后的公司继续执行。原公司的结算未付款项,由变更之后的公司提供的收款账户收款。原公司的业务和款项若出现法律上的纠纷,由变更后的公司承担责任。</p>
<p>特此证明!</p>
@if (filter_var($payload['entity_change_old_company_deregistered'] ?? false, FILTER_VALIDATE_BOOLEAN))
<div class="sign"><p>原公司法人个人签字:{!! $mixedText($payload['former_legal_representative'] ?? '') !!}</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@else
<div class="sign"><p>变更前公司法人签字盖公章:{!! $mixedText($payload['former_company_name']) !!}(签署人:{!! $mixedText($payload['former_legal_representative'] ?? '') !!})</p><p>变更后公司法人签字盖公章:{!! $mixedText($payload['changed_company_name']) !!}(签署人:{!! $mixedText($payload['secondary_operator_name'] ?? '') !!})</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@endif
<div class="sign">
@if (filter_var($payload['entity_change_old_company_deregistered'] ?? false, FILTER_VALIDATE_BOOLEAN))
<p>原公司法人个人签字:{!! $mixedText($payload['former_legal_representative'] ?? '') !!}</p>
@else
<p>变更前公司法人签字盖公章:{!! $mixedText($payload['former_company_name']) !!}(签署人:{!! $mixedText($payload['former_legal_representative'] ?? '') !!})</p>
@endif
<p>变更后公司法人签字盖公章:{!! $mixedText($payload['changed_company_name']) !!}(签署人:{!! $mixedText($payload['secondary_operator_name'] ?? '') !!})</p>
<p>日期:{!! $mixedText($signatureDate) !!}</p>
</div>
@elseif ($type === 'shared-contract')
<p>与 {!! $mixedText($payload['company_full_name'] ?? $signature->company_full_name) !!} 合作的 {!! $mixedText($sharedCourierText) !!},均归属于 {!! $mixedText($payload['contract_partner_name'] ?? '') !!},共用一份合同及同一份打款资料,合同签署及结算主体均为 {!! $mixedText($payload['contract_partner_name'] ?? '') !!}。</p>
<p>特此证明!</p>

View File

@ -345,7 +345,9 @@ class AgreementSigningPageTest extends FeatureTestCase
->assertSeeText('变更前公司名称')
->assertSeeText('变更后公司名称')
->assertSeeText('变更前公司已注销,使用原法人个人签署')
->assertSeeText('原法人以个人身份签字,变更后公司仍须法人签字并盖章。')
->assertSeeText('变更后主体 E 签宝实名认证姓名')
->assertSee('data-preview-bind="changedLegalRepresentative"', false)
->assertSeeText('原公司法人个人签字')
->assertSeeText('受托方签署类型')
->assertSeeText('个人签署')

View File

@ -224,7 +224,7 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
});
}
public function test_entity_change_can_use_the_former_legal_representative_as_a_person_after_deregistration(): void
public function test_deregistered_entity_change_sends_the_former_person_and_current_enterprise(): void
{
$this->fakeEsign();
@ -238,34 +238,91 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
'signing_deadline' => now()->addDays(7)->toDateString(),
'esign_account' => '13800138000',
'operator_name' => '原法人',
'secondary_esign_account' => '13900139000',
'secondary_operator_name' => '新法人',
'archive_mode' => 'auto',
'entity_change_old_company_deregistered' => '1',
'former_company_name' => '已注销原公司有限公司',
'entity_change_date' => '2026-10-01',
'changed_company_name' => '新公司有限公司',
'changed_company_start_date' => '2026-10-10',
];
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect();
$signature = CourierAgreementSignature::query()->firstOrFail();
$this->assertTrue($signature->payload['entity_change_old_company_deregistered']);
$this->assertSame('原法人', $signature->payload['former_legal_representative']);
$this->assertSame('13900139000', $signature->payload['secondary_esign_account']);
$this->assertSame('新法人', $signature->payload['secondary_operator_name']);
$html = view('business.pdf.courier-agreement', [
'signature' => $signature,
'type' => $signature->document_type,
'title' => $signature->documentLabel(),
'payload' => $signature->payload,
'signatureDate' => '2026年10月10日',
'fontPath' => str_replace('\\', '/', resource_path('fonts/DroidSansFallback.ttf')),
])->render();
$this->assertStringContainsString('原公司法人个人签字:原法人', strip_tags($html));
$this->assertStringContainsString('变更后公司法人签字盖公章:新公司有限公司(签署人:新法人)', strip_tags($html));
$this->assertStringNotContainsString('变更前公司法人签字盖公章', $html);
Http::assertSent(function (ClientRequest $request): bool {
if ($request->url() !== 'https://smlopenapi.esign.test/v3/sign-flow/create-by-file') {
return false;
}
$signers = $request->data()['signers'] ?? [];
return count($signers) === 2
&& data_get($signers, '0.signerType') === 0
&& data_get($signers, '0.psnSignerInfo.psnAccount') === '13800138000'
&& data_get($signers, '0.psnSignerInfo.psnInfo.psnName') === '原法人'
&& data_get($signers, '0.orgSignerInfo') === null
&& data_get($signers, '1.signerType') === 1
&& data_get($signers, '1.orgSignerInfo.orgName') === '新公司有限公司'
&& data_get($signers, '1.orgSignerInfo.transactorInfo.psnAccount') === '13900139000'
&& data_get($signers, '1.orgSignerInfo.transactorInfo.psnInfo.psnName') === '新法人';
});
}
public function test_deregistered_entity_change_rejects_missing_current_signer_before_creating_a_task(): void
{
Http::fake();
$payload = [
'idempotency_key' => 'entity-change-missing-current-signer',
'courier_name' => '新北中通',
'document_type' => 'entity-change',
'signing_subject' => $this->company->name,
'company_full_name' => $this->company->name,
'signature_date' => '2026-10-10',
'signing_deadline' => now()->addDays(7)->toDateString(),
'esign_account' => '13800138000',
'operator_name' => '原法人',
'archive_mode' => 'auto',
'entity_change_old_company_deregistered' => '1',
'former_company_name' => '已注销原公司有限公司',
'entity_change_date' => '2026-10-01',
'changed_company_name' => '新公司有限公司',
'changed_company_start_date' => '2026-10-10',
'secondary_esign_account' => '13900139000',
'secondary_operator_name' => '新法人',
];
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), $payload)->assertRedirect();
$signature = CourierAgreementSignature::query()->firstOrFail();
$this->assertTrue($signature->payload['entity_change_old_company_deregistered']);
$this->assertSame('原法人', $signature->payload['former_legal_representative']);
$this->assertNull($signature->payload['secondary_esign_account'] ?? null);
Http::assertSent(function (ClientRequest $request): bool {
if ($request->url() !== 'https://smlopenapi.esign.test/v3/sign-flow/create-by-file') {
return false;
}
$signers = $request->data()['signers'] ?? [];
return count($signers) === 1
&& data_get($signers, '0.signerType') === 0
&& data_get($signers, '0.psnSignerInfo.psnAccount') === '13800138000'
&& data_get($signers, '0.psnSignerInfo.psnInfo.psnName') === '原法人'
&& data_get($signers, '0.orgSignerInfo') === null;
});
$this->withExceptionHandling()->loginAs();
foreach (['secondary_esign_account', 'secondary_operator_name'] as $field) {
$missing = $payload;
unset($missing[$field]);
$this->postJson(route('business.courier-agreement-signatures.store'), $missing)
->assertStatus(422)
->assertJsonValidationErrors($field);
}
$payload['secondary_esign_account'] = 'not-an-account';
$this->postJson(route('business.courier-agreement-signatures.store'), $payload)
->assertStatus(422)
->assertJsonValidationErrors('secondary_esign_account');
$this->assertDatabaseCount('business_courier_agreement_signatures', 0);
Http::assertNothingSent();
}
public function test_cooperation_contract_maps_party_variables_and_creates_two_signers(): void