协议签署支持个人签署及已注销公司原法人签署 (#287)

This commit is contained in:
limeng 2026-10-10 11:12:13 +08:00
parent b92a3b0ed0
commit c1431b8b38
6 changed files with 412 additions and 47 deletions

View File

@ -56,7 +56,7 @@ class CourierAgreementSignatures extends Controller
]);
}
if (in_array($requestedType, ['account-proof', 'account-change'], true)
if (in_array($requestedType, ['account-proof', 'account-change', 'entity-change', 'cooperation-contract', 'authorization'], 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.
@ -105,8 +105,45 @@ class CourierAgreementSignatures extends Controller
$data['operator_name'] = trim((string) $data['counterparty_organization_name']);
}
}
if ($type === 'entity-change') {
$formerCompanyDeregistered = filter_var(
$data['entity_change_old_company_deregistered'] ?? false,
FILTER_VALIDATE_BOOLEAN,
);
$data['entity_change_old_company_deregistered'] = $formerCompanyDeregistered;
$data['counterparty_signer_type'] = $formerCompanyDeregistered ? 'person' : 'organization';
if (blank($data['operator_name'] ?? null)) {
throw ValidationException::withMessages(['operator_name' => $formerCompanyDeregistered
? '请填写原公司法人的 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 ($type === 'cooperation-contract') {
$companyNames = [trim($data['cooperation_party_a_name']), trim($data['cooperation_party_b_name'])];
$data['counterparty_signer_type'] = $data['counterparty_signer_type'] ?? 'organization';
if ($data['counterparty_signer_type'] === 'person') {
$data['operator_name'] = trim((string) $data['cooperation_party_b_name']);
} elseif (blank($data['operator_name'] ?? null)) {
throw ValidationException::withMessages(['operator_name' => '请填写快递方企业经办人的 E 签宝实名认证姓名。']);
}
if (blank($data['secondary_operator_name'] ?? null)) {
throw ValidationException::withMessages(['secondary_operator_name' => '请填写我方 E 签宝账号绑定的实名认证姓名。']);
}
$companyNames = [trim($data['cooperation_party_a_name'])];
if ($data['counterparty_signer_type'] === 'organization') {
$companyNames[] = 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)) {
@ -116,6 +153,20 @@ class CourierAgreementSignatures extends Controller
}
}
}
if ($type === 'authorization') {
$data['counterparty_signer_type'] = $data['counterparty_signer_type'] ?? 'organization';
$data['secondary_signer_type'] = $data['secondary_signer_type'] ?? 'organization';
if ($data['counterparty_signer_type'] === 'person') {
$data['operator_name'] = trim((string) $data['principal_name']);
} elseif (blank($data['operator_name'] ?? null)) {
throw ValidationException::withMessages(['operator_name' => '请填写委托方企业经办人的 E 签宝实名认证姓名。']);
}
if ($data['secondary_signer_type'] === 'person') {
$data['secondary_operator_name'] = trim((string) $data['trustee_name']);
} elseif (blank($data['secondary_operator_name'] ?? null)) {
throw ValidationException::withMessages(['secondary_operator_name' => '请填写受托方企业经办人的 E 签宝实名认证姓名。']);
}
}
$this->validateEsignAccount($data['esign_account'], 'esign_account');
if (! empty($data['secondary_esign_account'])) {
$this->validateEsignAccount($data['secondary_esign_account'], 'secondary_esign_account');
@ -318,11 +369,13 @@ class CourierAgreementSignatures extends Controller
'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' => ['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'],
'secondary_esign_account' => ['required_if:document_type,cooperation-contract,authorization', 'nullable', 'string', 'max:255'],
'secondary_operator_name' => ['nullable', 'string', 'min:2', 'max:100'],
'operator_name' => ['required_if:document_type,shared-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'],
'counterparty_signer_type' => ['required_if:document_type,account-proof,account-change,entity-change,cooperation-contract,authorization', 'nullable', 'in:organization,person'],
'secondary_signer_type' => ['nullable', 'in:organization,person'],
'entity_change_old_company_deregistered' => ['nullable', 'boolean'],
'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'],
@ -371,6 +424,10 @@ class CourierAgreementSignatures extends Controller
$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'] ?? []));
if (($data['document_type'] ?? null) === 'entity-change') {
$payload['former_legal_representative'] = trim((string) ($data['operator_name'] ?? ''));
$payload['entity_proof_date'] = $data['signature_date'] ?? null;
}
return $payload;
}

View File

@ -319,6 +319,7 @@ final class CourierAgreementSignatureService
];
if ($signature->document_type === 'cooperation-contract') {
$counterpartyIsPerson = ($payload['counterparty_signer_type'] ?? 'organization') === 'person';
return [
[
'signer_type' => 1,
@ -327,8 +328,8 @@ final class CourierAgreementSignatureService
'operator_account' => $payload['secondary_esign_account'] ?? $signature->operator_account,
],
[
'signer_type' => 1,
'organization_name' => $payload['cooperation_party_b_name'] ?? $signature->courier_name,
'signer_type' => $counterpartyIsPerson ? 0 : 1,
'organization_name' => $counterpartyIsPerson ? null : ($payload['cooperation_party_b_name'] ?? $signature->courier_name),
'operator_name' => $signature->operator_name,
'operator_account' => $signature->operator_account,
],
@ -336,27 +337,44 @@ final class CourierAgreementSignatureService
}
if ($signature->document_type === 'entity-change') {
$formerCompanyDeregistered = filter_var(
$payload['entity_change_old_company_deregistered'] ?? false,
FILTER_VALIDATE_BOOLEAN,
);
if ($formerCompanyDeregistered || ($payload['counterparty_signer_type'] ?? 'organization') === 'person') {
return [[
'signer_type' => 0,
'organization_name' => null,
'operator_name' => $signature->operator_name,
'operator_account' => $signature->operator_account,
]];
}
return [
array_merge($primary, ['organization_name' => $payload['former_company_name'] ?? $signature->organization_name]),
[
'signer_type' => 1,
'organization_name' => $payload['changed_company_name'] ?? $signature->organization_name,
'operator_name' => $payload['changed_company_name'] ?? $signature->courier_name,
'operator_name' => $payload['secondary_operator_name'] ?? $signature->operator_name,
'operator_account' => $payload['secondary_esign_account'] ?? $signature->operator_account,
],
];
}
if ($signature->document_type === 'authorization') {
$principalIsPerson = ($payload['counterparty_signer_type'] ?? 'organization') === 'person';
$trusteeIsPerson = ($payload['secondary_signer_type'] ?? 'organization') === 'person';
return [
array_merge($primary, [
'organization_name' => $payload['principal_name'] ?? $signature->organization_name,
'operator_name' => $payload['principal_name'] ?? $signature->operator_name,
]),
[
'signer_type' => 1,
'organization_name' => $payload['trustee_name'] ?? $signature->organization_name,
'operator_name' => $payload['trustee_name'] ?? $signature->courier_name,
'signer_type' => $principalIsPerson ? 0 : 1,
'organization_name' => $principalIsPerson ? null : ($payload['principal_name'] ?? $signature->organization_name),
'operator_name' => $signature->operator_name,
'operator_account' => $signature->operator_account,
],
[
'signer_type' => $trusteeIsPerson ? 0 : 1,
'organization_name' => $trusteeIsPerson ? null : ($payload['trustee_name'] ?? $signature->organization_name),
'operator_name' => $payload['secondary_operator_name'],
'operator_account' => $payload['secondary_esign_account'] ?? $signature->operator_account,
],
];

View File

@ -1495,10 +1495,10 @@
<p class="agreement-help">选择快递公司后自动带出工作平台的“电子签章经办人”(dzqzjbr);如资料未维护,可手工填写对方在 E 签宝实名绑定的手机号或邮箱。</p>
</div>
<div class="agreement-field agreement-field--full" data-account-signer-type-field>
<span class="agreement-field-label">对方签署主体类型 <span class="agreement-required" aria-hidden="true">*</span></span>
<span class="agreement-field-label" data-account-signer-type-label>对方签署主体类型 <span class="agreement-required" aria-hidden="true">*</span></span>
<div class="agreement-archive-options">
<label class="agreement-archive-choice"><input type="radio" name="counterparty_signer_type" value="organization" checked data-account-signer-type><span><strong>企业</strong><span>填写 E 签宝认证的企业全称和经办人姓名</span></span></label>
<label class="agreement-archive-choice"><input type="radio" name="counterparty_signer_type" value="person" data-account-signer-type><span><strong>个人</strong><span>填写 E 签宝实名认证的个人姓名,不需要企业全称</span></span></label>
<label class="agreement-archive-choice"><input type="radio" name="counterparty_signer_type" value="organization" checked data-account-signer-type><span><strong data-signer-type-organization-title>企业签署</strong><span data-signer-type-organization-help>填写 E 签宝认证的企业全称和经办人姓名</span></span></label>
<label class="agreement-archive-choice"><input type="radio" name="counterparty_signer_type" value="person" data-account-signer-type><span><strong data-signer-type-person-title>个人签署</strong><span data-signer-type-person-help>填写 E 签宝实名认证的个人姓名,不需要企业全称</span></span></label>
</div>
</div>
<div class="agreement-field" data-account-organization-field>
@ -1506,8 +1506,8 @@
<input class="agreement-control" id="agreement-counterparty-organization" name="counterparty_organization_name" type="text" placeholder="填写对方在 E 签宝认证的企业全称" autocomplete="organization" data-account-organization-name required>
<p class="agreement-help" data-account-signer-help>必须与对方 E 签宝企业认证名称一致;这是签署主体,不要求与银行卡持卡人姓名一致。</p>
</div>
<div class="agreement-field">
<label for="agreement-operator-name">对方 E 签宝实名认证姓名 <span class="agreement-required" data-account-operator-required aria-hidden="true">*</span></label>
<div class="agreement-field" data-primary-operator-field>
<label for="agreement-operator-name"><span data-primary-operator-label>对方 E 签宝实名认证姓名</span> <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" data-account-operator-help>仅用于 E 签宝身份核验,不是收款人姓名;收款人姓名请填写在下方账户资料中。</p>
</div>
@ -1528,10 +1528,10 @@
<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 class="agreement-field" data-secondary-operator-field hidden>
<label for="agreement-secondary-operator-name"><span data-secondary-operator-label>变更后主体 E 签宝实名认证姓名</span> <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">必须与第二签署方 E 签宝账号绑定的实名认证姓名一致,不能填写公司名称。</p>
</div>
</div>
</section>
@ -1596,6 +1596,9 @@
</div>
<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>
</div>
<div class="agreement-field">
<label for="agreement-former-company">变更前公司名称 <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-former-company" name="former_company_name" type="text" required>
@ -1650,7 +1653,7 @@
<input class="agreement-control" id="agreement-cooperation-party-a-phone" name="cooperation_party_a_phone" type="tel" value="{{ setting('company.phone') }}" autocomplete="tel" required>
</div>
<div class="agreement-field">
<label for="agreement-cooperation-party-b-name">乙方名称 <span class="agreement-required" aria-hidden="true">*</span></label>
<label for="agreement-cooperation-party-b-name"><span data-cooperation-party-b-label>乙方名称</span> <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-cooperation-party-b-name" name="cooperation_party_b_name" type="text" placeholder="快递公司完整签署名称" required>
</div>
<div class="agreement-field">
@ -1668,9 +1671,18 @@
</div>
<div data-template-fields="authorization" hidden>
<div class="agreement-form-grid">
<div class="agreement-field agreement-field--full">
<span class="agreement-field-label">受托方签署类型 <span class="agreement-required" aria-hidden="true">*</span></span>
<div class="agreement-archive-options">
<label class="agreement-archive-choice"><input type="radio" name="secondary_signer_type" value="organization" checked data-authorization-secondary-signer-type><span><strong>企业签署</strong><span>填写企业名称及其经办人的 E 签宝实名姓名</span></span></label>
<label class="agreement-archive-choice"><input type="radio" name="secondary_signer_type" value="person" data-authorization-secondary-signer-type><span><strong>个人签署</strong><span>受托方姓名须与 E 签宝账号实名认证一致</span></span></label>
</div>
</div>
</div>
<div class="agreement-form-grid">
<div class="agreement-field">
<label for="agreement-principal-name">委托方(个人或公司) <span class="agreement-required" aria-hidden="true">*</span></label>
<label for="agreement-principal-name"><span data-principal-name-label>委托方(个人或公司)</span> <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-principal-name" name="principal_name" type="text" required>
</div>
<div class="agreement-field">
@ -1678,7 +1690,7 @@
<input class="agreement-control" id="agreement-principal-id" name="principal_id_number" type="text" autocomplete="off" required>
</div>
<div class="agreement-field">
<label for="agreement-trustee-name">受托方(个人或公司) <span class="agreement-required" aria-hidden="true">*</span></label>
<label for="agreement-trustee-name"><span data-trustee-name-label>受托方(个人或公司)</span> <span class="agreement-required" aria-hidden="true">*</span></label>
<input class="agreement-control" id="agreement-trustee-name" name="trustee_name" type="text" required>
</div>
<div class="agreement-field">
@ -1785,12 +1797,14 @@
<article class="agreement-preview-paper" data-template-preview="entity-change" hidden>
<h3>公司主体变更证明</h3>
<p>致 <strong data-preview-bind="company">{{ $agreementCompanyName }}</strong>:</p>
<p>我司由于企业名称变更,原公司名称 <span class="agreement-preview-value" data-preview-bind="formerCompanyName">待填写</span> 于 <span class="agreement-preview-value" data-preview-bind="entityChangeDate">待填写</span> 变更为 <span class="agreement-preview-value" data-preview-bind="changedCompanyName">待填写</span>,自 <span class="agreement-preview-value" data-preview-bind="changedCompanyStartDate">待填写</span> 开始,我司将启用变更后的公司与贵公司开展工作,原公司名称停止使用。</p>
<p data-entity-organization-description>我司由于企业名称变更,原公司名称 <span class="agreement-preview-value" data-preview-bind="formerCompanyName">待填写</span> 于 <span class="agreement-preview-value" data-preview-bind="entityChangeDate">待填写</span> 变更为 <span class="agreement-preview-value" data-preview-bind="changedCompanyName">待填写</span>,自 <span class="agreement-preview-value" data-preview-bind="changedCompanyStartDate">待填写</span> 开始,我司将启用变更后的公司与贵公司开展工作,原公司名称停止使用。</p>
<p data-entity-person-description hidden>原合作主体 <span class="agreement-preview-value" data-preview-bind="formerCompanyName">待填写</span> 已注销,由原公司法人 <span class="agreement-preview-value" data-preview-bind="formerLegalRepresentative">待填写</span> 以个人身份确认本证明。自 <span class="agreement-preview-value" data-preview-bind="changedCompanyStartDate">待填写</span> 起,改由 <span class="agreement-preview-value" data-preview-bind="changedCompanyName">待填写</span> 与贵公司开展业务。</p>
<p>变更后,原公司的各项事宜由变更后的公司继续执行。原公司的结算未付款项,由变更之后的公司提供的收款账户收款。原公司的业务和款项若出现法律上的纠纷,由变更后的公司承担责任。</p>
<p>特此证明!</p>
<div class="agreement-preview-signature">
<p>变更前公司法人签字盖公章:</p>
<p>变更后公司法人签字盖公章:</p>
<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 data-preview-bind="signatureDate">{{ now()->format('Y年n月j日') }}</span></p>
</div>
<p class="agreement-preview-page-number">第 1 页 / 共 1 页</p>
@ -2010,8 +2024,10 @@
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 entityChangeDeregisteredInput = root.querySelector('[data-entity-change-deregistered]');
var secondaryOperatorField = root.querySelector('[data-secondary-operator-field]');
var secondaryOperatorInput = root.querySelector('input[name="secondary_operator_name"]');
var authorizationSecondarySignerTypeInputs = Array.prototype.slice.call(root.querySelectorAll('[data-authorization-secondary-signer-type]'));
var operatorHelp = root.querySelector('[data-account-operator-help]');
var sharedCourierList = root.querySelector('[data-shared-courier-list]');
var sharedCourierAdd = root.querySelector('[data-shared-courier-add]');
@ -2549,6 +2565,11 @@
if (templateType === 'cooperation-contract') {
labels.primary = '快递方 E 签宝账号';
labels.secondary = '我方 E 签宝账号';
} else if (templateType === 'entity-change') {
labels.primary = entityChangeDeregisteredInput && entityChangeDeregisteredInput.checked
? '原公司法人个人 E 签宝账号'
: '变更前主体 E 签宝账号';
labels.secondary = '变更后主体 E 签宝账号';
} else if (templateType === 'authorization') {
labels.primary = '委托方 E 签宝账号';
labels.secondary = '受托方 E 签宝账号';
@ -2713,14 +2734,30 @@
var courier = fieldValue('[name="courier_name"]', '快递公司');
var companyName = fieldValue('[name="company_full_name"]', '待填写公司全称');
var accountAgreement = templateType === 'account-proof' || templateType === 'account-change';
var entityChange = templateType === 'entity-change';
var cooperationContract = templateType === 'cooperation-contract';
var authorization = templateType === 'authorization';
var formerCompanyDeregistered = entityChange && entityChangeDeregisteredInput && entityChangeDeregisteredInput.checked;
var signerTypeFieldVisible = accountAgreement || cooperationContract || authorization;
var accountSignerTypeInput = accountSignerTypeInputs.find(function (input) {
return input.checked;
});
var accountSignerType = accountSignerTypeInput ? accountSignerTypeInput.value : 'organization';
var accountSignerIsPerson = accountAgreement && accountSignerType === 'person';
var otherSignerIsPerson = (cooperationContract || authorization) && accountSignerType === 'person';
var trusteeSignerTypeInput = authorizationSecondarySignerTypeInputs.find(function (input) {
return input.checked;
});
var trusteeIsPerson = authorization && trusteeSignerTypeInput && trusteeSignerTypeInput.value === 'person';
accountSignerTypeInputs.forEach(function (input) {
input.disabled = !signerTypeFieldVisible;
});
if (accountSignerTypeField) {
accountSignerTypeField.hidden = !accountAgreement;
accountSignerTypeField.hidden = !signerTypeFieldVisible;
setText('[data-signer-type-organization-title]', '企业签署');
setText('[data-signer-type-organization-help]', '企业名称和经办人姓名分别填写');
setText('[data-signer-type-person-title]', '个人签署');
setText('[data-signer-type-person-help]', '签署人姓名须与 E 签宝账号实名认证一致');
}
if (accountOrganizationField) {
accountOrganizationField.hidden = !accountAgreement;
@ -2734,20 +2771,21 @@
: '填写对方在 E 签宝认证的企业全称';
}
if (operatorNameInput) {
var needsOperatorName = cooperationContract || templateType === 'shared-contract' || (accountAgreement && !accountSignerIsPerson);
operatorNameInput.disabled = !needsOperatorName;
operatorNameInput.required = needsOperatorName;
if (accountAgreement && accountSignerIsPerson) {
var needsPrimaryOperator = templateType === 'shared-contract' || (accountAgreement && !accountSignerIsPerson) || entityChange || ((cooperationContract || authorization) && !otherSignerIsPerson);
operatorNameInput.disabled = !needsPrimaryOperator;
operatorNameInput.required = needsPrimaryOperator;
operatorNameInput.placeholder = formerCompanyDeregistered
? '填写原公司法人 E 签宝实名认证姓名'
: '填写与 E 签宝账号匹配的实名姓名';
if (accountSignerIsPerson) {
operatorNameInput.value = accountOrganizationInput ? accountOrganizationInput.value : '';
}
root.querySelector('[data-primary-operator-field]').hidden = !needsPrimaryOperator;
setText('[data-primary-operator-label]', formerCompanyDeregistered ? '原公司法人 E 签宝实名认证姓名' : entityChange ? '变更前主体 E 签宝实名认证姓名' : '对方 E 签宝实名认证姓名');
}
if (operatorRequiredMark) {
operatorRequiredMark.hidden = !operatorNameInput.required;
}
if (cooperationOperatorField) {
cooperationOperatorField.hidden = !cooperationContract;
secondaryOperatorInput.disabled = !cooperationContract;
secondaryOperatorInput.required = cooperationContract;
}
if (operatorHelp) {
operatorHelp.textContent = templateType === 'shared-contract'
@ -2763,10 +2801,14 @@
: '必须与对方 E 签宝企业认证名称一致;这是签署主体,不要求与银行卡持卡人姓名一致。';
}
activateTemplate(templateType);
setText('[data-cooperation-party-b-label]', otherSignerIsPerson ? '乙方个人姓名(E 签宝实名)' : '乙方名称');
root.querySelector('[name="cooperation_party_b_name"]').placeholder = otherSignerIsPerson ? '填写合作人的 E 签宝实名认证姓名' : '快递公司完整签署名称';
setText('[data-principal-name-label]', otherSignerIsPerson ? '委托方个人姓名(E 签宝实名)' : '委托方企业全称');
setText('[data-trustee-name-label]', trusteeIsPerson ? '受托方个人姓名(E 签宝实名)' : '受托方企业全称');
refreshSignerAccountLabels(templateType);
var secondary = root.querySelector('[data-secondary-signer-fields]');
if (secondary) {
var needsSecondary = templateType === 'entity-change' || templateType === 'cooperation-contract' || templateType === 'authorization';
var needsSecondary = (entityChange && !formerCompanyDeregistered) || templateType === 'cooperation-contract' || templateType === 'authorization';
secondary.hidden = !needsSecondary;
secondary.querySelectorAll('input').forEach(function (control) {
control.disabled = !needsSecondary;
@ -2788,9 +2830,16 @@
secondaryEsignAccountInput.dataset.cooperationDefault = 'true';
}
}
if (secondaryOperatorField) {
var needsSecondaryOperator = (entityChange && !formerCompanyDeregistered) || cooperationContract || (authorization && !trusteeIsPerson);
secondaryOperatorField.hidden = !needsSecondaryOperator;
secondaryOperatorInput.disabled = !needsSecondaryOperator;
secondaryOperatorInput.required = needsSecondaryOperator;
setText('[data-secondary-operator-label]', entityChange ? '变更后主体 E 签宝实名认证姓名' : cooperationContract ? '我方 E 签宝实名认证姓名' : '受托方 E 签宝实名认证姓名');
}
setText('[data-template-name]', documentOption.dataset.name);
setText('[data-template-code]', documentOption.dataset.code);
setText('[data-template-description]', 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);
@ -2822,6 +2871,19 @@
setPreviewValue('entityChangeDate', formatDateField('[name="entity_change_date"]', '待填写'));
setPreviewValue('changedCompanyName', fieldValue('[name="changed_company_name"]', '待填写'));
setPreviewValue('changedCompanyStartDate', formatDateField('[name="changed_company_start_date"]', '待填写'));
setPreviewValue('formerLegalRepresentative', fieldValue('[name="operator_name"]', '待填写'));
root.querySelectorAll('[data-entity-organization-signatures]').forEach(function (element) {
element.hidden = formerCompanyDeregistered;
});
root.querySelectorAll('[data-entity-person-signature]').forEach(function (element) {
element.hidden = !formerCompanyDeregistered;
});
root.querySelectorAll('[data-entity-organization-description]').forEach(function (element) {
element.hidden = formerCompanyDeregistered;
});
root.querySelectorAll('[data-entity-person-description]').forEach(function (element) {
element.hidden = !formerCompanyDeregistered;
});
}
if (templateType === 'shared-contract') {
@ -2873,6 +2935,12 @@
accountSignerTypeInputs.forEach(function (input) {
input.addEventListener('change', refreshPreview);
});
authorizationSecondarySignerTypeInputs.forEach(function (input) {
input.addEventListener('change', refreshPreview);
});
if (entityChangeDeregisteredInput) {
entityChangeDeregisteredInput.addEventListener('change', refreshPreview);
}
if (accountOrganizationInput) {
accountOrganizationInput.addEventListener('input', function () {

View File

@ -70,10 +70,18 @@
<div class="sign"><p>公司盖章:</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@elseif ($type === 'entity-change')
<p>致 <strong>{!! $mixedText($payload['company_full_name']) !!}</strong>:</p>
<p>我司由于企业名称变更,原公司名称 {!! $mixedText($payload['former_company_name']) !!} 于 {!! $mixedText($payload['entity_change_date']) !!} 变更为 {!! $mixedText($payload['changed_company_name']) !!},自 {!! $mixedText($payload['changed_company_start_date']) !!} 开始,我司将启用变更后的公司与贵公司开展工作,原公司名称停止使用。</p>
@if (filter_var($payload['entity_change_old_company_deregistered'] ?? false, FILTER_VALIDATE_BOOLEAN))
<p>原合作主体 {!! $mixedText($payload['former_company_name']) !!} 已注销,由原公司法人 {!! $mixedText($payload['former_legal_representative'] ?? '') !!} 以个人身份确认本证明。自 {!! $mixedText($payload['changed_company_start_date']) !!} 起,改由 {!! $mixedText($payload['changed_company_name']) !!} 与贵公司开展业务。</p>
@else
<p>我司由于企业名称变更,原公司名称 {!! $mixedText($payload['former_company_name']) !!} 于 {!! $mixedText($payload['entity_change_date']) !!} 变更为 {!! $mixedText($payload['changed_company_name']) !!},自 {!! $mixedText($payload['changed_company_start_date']) !!} 开始,我司将启用变更后的公司与贵公司开展工作,原公司名称停止使用。</p>
@endif
<p>变更后,原公司的各项事宜由变更后的公司继续执行。原公司的结算未付款项,由变更之后的公司提供的收款账户收款。原公司的业务和款项若出现法律上的纠纷,由变更后的公司承担责任。</p>
<p>特此证明!</p>
<div class="sign"><p>变更前公司法人签字盖公章:{!! $mixedText($payload['former_company_name']) !!}</p><p>变更后公司法人签字盖公章:{!! $mixedText($payload['changed_company_name']) !!}</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@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
@elseif ($type === 'shared-contract')
<p>与 {!! $mixedText($payload['contract_partner_name'] ?? '') !!} 合作的 {!! $mixedText($sharedCourierText) !!} 共同用一份合同,打款资料为同一份资料,同一个签字人以及同一个法人!</p>
<p>特此证明!</p>
@ -82,7 +90,7 @@
@endif
<div class="sign"><p>法人签字并公司盖章:</p><p>日期:{!! $mixedText($signatureDate) !!}</p></div>
@elseif ($type === 'cooperation-contract')
@include('business.partials.cooperation-contract', ['mode' => 'pdf', 'payload' => $payload])
@include('business.partials.cooperation-contract', ['mode' => 'pdf', 'payload' => $payload, 'signatureDate' => $signatureDate])
@else
<div class="account">
<div>委托方:{!! $mixedText($payload['principal_name']) !!}</div>

View File

@ -344,6 +344,11 @@ class AgreementSigningPageTest extends FeatureTestCase
->assertSee('data-template-preview="entity-change"', false)
->assertSeeText('变更前公司名称')
->assertSeeText('变更后公司名称')
->assertSeeText('变更前公司已注销,使用原法人个人签署')
->assertSeeText('变更后主体 E 签宝实名认证姓名')
->assertSeeText('原公司法人个人签字')
->assertSeeText('受托方签署类型')
->assertSeeText('个人签署')
->assertSeeText('变更后,原公司的各项事宜由变更后的公司继续执行')
->assertSee('data-template-fields="shared-contract"', false)
->assertSee('data-template-preview="shared-contract"', false)

View File

@ -178,6 +178,96 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
});
}
public function test_entity_change_sends_two_enterprise_signers_with_their_real_names(): void
{
$this->fakeEsign();
$payload = [
'idempotency_key' => 'entity-change-two-enterprise-signers',
'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' => '旧法人',
'secondary_esign_account' => '13900139000',
'secondary_operator_name' => '新法人',
'archive_mode' => 'auto',
'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->assertSame('旧法人', $signature->payload['former_legal_representative']);
$this->assertSame('新法人', $signature->payload['secondary_operator_name']);
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') === 1
&& data_get($signers, '0.orgSignerInfo.orgName') === '原公司有限公司'
&& data_get($signers, '0.orgSignerInfo.transactorInfo.psnInfo.psnName') === '旧法人'
&& data_get($signers, '1.signerType') === 1
&& data_get($signers, '1.orgSignerInfo.orgName') === '新公司有限公司'
&& data_get($signers, '1.orgSignerInfo.transactorInfo.psnInfo.psnName') === '新法人';
});
}
public function test_entity_change_can_use_the_former_legal_representative_as_a_person_after_deregistration(): void
{
$this->fakeEsign();
$payload = [
'idempotency_key' => 'entity-change-deregistered-former-person',
'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',
];
$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;
});
}
public function test_cooperation_contract_maps_party_variables_and_creates_two_signers(): void
{
$this->fakeEsign();
@ -213,6 +303,125 @@ class CourierAgreementSignatureWorkflowTest extends FeatureTestCase
});
}
public function test_cooperation_contract_uses_the_persons_real_name_instead_of_the_courier_name(): void
{
$this->fakeEsign();
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), [
'idempotency_key' => 'cooperation-person',
'courier_name' => '新北中通',
'document_type' => 'cooperation-contract',
'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',
'counterparty_signer_type' => 'person',
'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' => '13800138000',
'cooperation_start_date' => '2026-10-10',
'cooperation_end_date' => '2027-10-09',
])->assertRedirect()->assertSessionHasNoErrors();
$signature = CourierAgreementSignature::query()->firstOrFail();
$this->assertSame('张三', $signature->operator_name);
Http::assertSent(fn (ClientRequest $request): bool =>
str_ends_with($request->url(), '/v3/sign-flow/create-by-file')
&& count($request->data()['signers']) === 2
&& data_get($request->data(), 'signers.0.orgSignerInfo.transactorInfo.psnInfo.psnName') === '我方经办人'
&& data_get($request->data(), 'signers.1.signerType') === 0
&& data_get($request->data(), 'signers.1.psnSignerInfo.psnInfo.psnName') === '张三'
&& data_get($request->data(), 'signers.1.psnSignerInfo.psnAccount') === '13800138000'
&& data_get($request->data(), 'signers.1.orgSignerInfo') === null
);
}
public function test_registered_entity_change_rejects_missing_former_signer_real_name(): void
{
$this->fakeEsign();
$payload = [
'idempotency_key' => 'entity-change-missing-former-signer-name',
'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',
'archive_mode' => 'auto',
'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->withExceptionHandling()->loginAs()
->from(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'agreement-signing', 'agreement_view' => 'create']))
->post(route('business.courier-agreement-signatures.store'), $payload)
->assertSessionHasErrors('operator_name');
$this->assertSame(0, CourierAgreementSignature::query()->count());
}
public static function authorizationSignerTypes(): array
{
return [['person', 'person'], ['organization', 'person'], ['person', 'organization'], ['organization', 'organization']];
}
#[\PHPUnit\Framework\Attributes\DataProvider('authorizationSignerTypes')]
public function test_authorization_supports_independent_person_and_enterprise_signers(string $principalType, string $trusteeType): void
{
$this->fakeEsign();
$this->loginAs()->post(route('business.courier-agreement-signatures.store'), [
'idempotency_key' => 'authorization-' . $principalType . '-' . $trusteeType,
'courier_name' => '新北中通',
'document_type' => 'authorization',
'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',
'secondary_esign_account' => '13900139000',
'counterparty_signer_type' => $principalType,
'secondary_signer_type' => $trusteeType,
'operator_name' => $principalType === 'organization' ? '张三' : null,
'secondary_operator_name' => $trusteeType === 'organization' ? '李四' : null,
'archive_mode' => 'auto',
'principal_name' => $principalType === 'person' ? '张三' : '委托公司有限公司',
'principal_id_number' => 'TEST-PRINCIPAL-ID',
'trustee_name' => $trusteeType === 'person' ? '李四' : '受托公司有限公司',
'trustee_id_number' => 'TEST-TRUSTEE-ID',
'authorization_items' => ['1', '4'],
'bill_counterparty' => $this->company->name,
'loan_counterparty' => $this->company->name,
'payment_counterparty' => $this->company->name,
'authorization_start_date' => '2026-10-10',
'authorization_end_date' => '2027-10-09',
])->assertRedirect()->assertSessionHasNoErrors();
Http::assertSent(function (ClientRequest $request) use ($principalType, $trusteeType): bool {
if (! str_ends_with($request->url(), '/v3/sign-flow/create-by-file')) {
return false;
}
$signers = $request->data()['signers'];
foreach ([[$principalType, '张三', '13800138000'], [$trusteeType, '李四', '13900139000']] as $index => [$type, $name, $account]) {
$identityPath = $type === 'person' ? 'psnSignerInfo' : 'orgSignerInfo.transactorInfo';
if (data_get($signers, "$index.signerType") !== ($type === 'person' ? 0 : 1)
|| data_get($signers, "$index.$identityPath.psnInfo.psnName") !== $name
|| data_get($signers, "$index.$identityPath.psnAccount") !== $account
|| ($type === 'person' && data_get($signers, "$index.orgSignerInfo") !== null)) {
return false;
}
}
return count($signers) === 2;
});
}
public function test_cooperation_contract_requires_both_real_names_before_creating_a_task(): void
{
$this->fakeEsign();