CaiWuXiTong/tests/Feature/Business/AgreementSigningPageTest.php

488 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use App\Models\Business\CourierAgreementSignature;
use App\Models\Business\CourierQuoteChange;
use App\Models\Business\CourierQuoteSignature;
use App\Models\Business\CourierQuoteVersion;
use App\Services\Esign\EsignClient;
use Illuminate\Support\Facades\DB;
use Mockery;
use Modules\EcommerceFinance\Repositories\CourierInformationMirrorRepository;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Tests\Feature\FeatureTestCase;
class AgreementSigningPageTest extends FeatureTestCase
{
protected function grantWorkspaceAccess(): void
{
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
public function testAgreementSigningPageRendersTheSigningWorkspace(): void
{
$this->grantWorkspaceAccess();
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
]))
->assertOk()
->assertSeeText('协议签署')
->assertSeeText('签署任务')
->assertSeeText('发起签章')
->assertSeeText('模板管理')
->assertSeeText(EsignClient::configured() ? 'E签宝已连接' : 'E签宝未连接')
->assertSeeText('还没有协议签署任务')
->assertSeeText('收款账户变更证明.doc')
->assertSeeText('收款账户证明文件.doc')
->assertSeeText('公司主体变更证明.docx')
->assertSeeText('共用合同证明.docx')
->assertSeeText('授权委托书(快递).docx');
}
public function testAgreementSigningPageIncludesSinglePageRequestFormAndLivePreview(): void
{
$this->grantWorkspaceAccess();
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
'agreement_view' => 'create',
]))
->assertOk()
->assertSee('data-signing-request-form', false)
->assertSee('data-agreement-preview', false)
->assertSee('data-sign-request-send', false)
->assertDontSee('data-agreement-step-panel', false)
->assertSeeText('发起签章请求')
->assertSeeText('快递公司')
->assertSee('data-courier-search', false)
->assertSee('placeholder="输入快递名称搜索"', false)
->assertSee('aria-haspopup="listbox"', false)
->assertSee('data-courier-options', false)
->assertSeeText('文件类型')
->assertSeeText('收款账户证明')
->assertSeeText('TPL-ACCOUNT-CERT-V2')
->assertSeeText('我方签署主体')
->assertSeeText('公司全称')
->assertSee('name="company_full_name"', false)
->assertSee('data-company-name', false)
->assertSee('data-subject-linked-name', false)
->assertSeeText('签署截止日期')
->assertSeeText('对方 E 签宝账号')
->assertSeeText('对方签署主体类型')
->assertSee('对方签署个人姓名', false)
->assertSee('name="counterparty_signer_type"', false)
->assertSee('data-primary-esign-label', false)
->assertSee('data-secondary-esign-label', false)
->assertSee("labels.primary = '快递方 E 签宝账号'", false)
->assertSee("labels.secondary = '我方 E 签宝账号'", false)
->assertSee("labels.primary = '委托方 E 签宝账号'", false)
->assertSee("labels.secondary = '受托方 E 签宝账号'", false)
->assertSeeText('对方签署企业全称')
->assertSeeText('必须与对方 E 签宝企业认证名称一致')
->assertSeeText('对方 E 签宝实名认证姓名')
->assertSeeText('仅用于 E 签宝身份核验,不是收款人姓名')
->assertSee("setPreviewValue('proofSigner', '公司盖章')", false)
->assertSee("setPreviewValue('changeSigner', '公司盖章')", false)
->assertSeeText('合作合同统一使用我方账号 18052733960')
->assertSeeText('我方 E 签宝实名认证姓名')
->assertSee('name="secondary_operator_name"', false)
->assertSeeText('填写变量')
->assertSeeText('签署完成后自动归档')
->assertSeeText('人工确认后归档')
->assertSeeText('刷新预览')
->assertSeeText('推送至E签宝')
->assertSeeText('收款帐户资料证明');
}
public function testAgreementSigningPageUsesCourierNamesFromCourierInformation(): void
{
$this->grantWorkspaceAccess();
$courierInformation = Mockery::mock(CourierInformationMirrorRepository::class);
$courierInformation->shouldReceive('names')
->once()
->andReturn(['顺丰', '韵达']);
$courierInformation->shouldReceive('signingAccountsByName')
->once()
->with(['顺丰', '韵达'])
->andReturn(['顺丰' => '13800000001']);
$this->app->instance(CourierInformationMirrorRepository::class, $courierInformation);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
'agreement_view' => 'create',
]))
->assertOk()
->assertSee('data-courier-search', false)
->assertSee('data-courier-select required', false)
->assertSee('<option value="顺丰" data-esign-account="13800000001">顺丰</option>', false)
->assertSee('<option value="韵达" data-esign-account="">韵达</option>', false)
->assertDontSee('<option value="新北中通"', false);
}
public function testAgreementSigningPageShowsOurCompanyAsInitiator(): void
{
$this->grantWorkspaceAccess();
CourierAgreementSignature::create([
'company_id' => $this->company->id,
'business_no' => 'AGR-INITIATOR-001',
'idempotency_key' => 'initiator-001',
'document_type' => 'account-proof',
'template_code' => 'TPL-ACCOUNT-CERT-V2',
'template_version' => '2',
'source_file_name' => '收款账户证明文件.doc',
'status' => CourierAgreementSignature::STATUS_SIGNING,
'courier_name' => '新北中通',
'company_full_name' => $this->company->name,
'organization_name' => '新北中通',
'operator_name' => '张经理',
'operator_account' => '13800138000',
'payload' => ['signing_subject' => $this->company->name],
'environment' => 'sandbox',
'source_pdf_path' => 'courier-agreements/initiator/source.pdf',
'source_pdf_sha256' => hash('sha256', '%PDF-source'),
'esign_sign_flow_id' => 'flow-initiator-001',
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
]);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
]))
->assertOk()
->assertSee('<td>' . e($this->company->name) . '</td>', false)
->assertDontSee('<td>张经理</td>', false);
}
public function testAgreementSigningPageFiltersAgreementTasksBySearchStatusAndType(): void
{
$this->grantWorkspaceAccess();
$this->createAgreementTask([
'business_no' => 'AGR-FILTER-MATCH',
'document_type' => 'account-proof',
'status' => CourierAgreementSignature::STATUS_ARCHIVED,
'courier_name' => '筛选快递',
'operator_name' => '筛选签署人',
]);
$this->createAgreementTask([
'business_no' => 'AGR-FILTER-OTHER',
'document_type' => 'account-change',
'status' => CourierAgreementSignature::STATUS_SIGNING,
'courier_name' => '另一家快递',
'operator_name' => '另一位签署人',
]);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
'agreement_search' => 'AGR-FILTER-MATCH',
'agreement_status' => CourierAgreementSignature::STATUS_ARCHIVED,
'agreement_type' => 'account-proof',
]))
->assertOk()
->assertSeeText('AGR-FILTER-MATCH')
->assertDontSeeText('AGR-FILTER-OTHER')
->assertSee('name="agreement_search"', false)
->assertSee('value="AGR-FILTER-MATCH"', false)
->assertSee('value="archived" selected', false)
->assertSee('value="account-proof" selected', false);
}
public function testAgreementSigningPageFiltersQuoteConfirmationsByVersionCarrierAndStatus(): void
{
$this->grantWorkspaceAccess();
$this->createQuoteSignature('QB-FILTER-MATCH', CourierQuoteSignature::STATUS_ARCHIVED, '筛选快递');
$this->createQuoteSignature('QB-FILTER-OTHER', CourierQuoteSignature::STATUS_SIGNING, '另一家快递');
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
'agreement_search' => 'QB-FILTER-MATCH',
'agreement_status' => CourierQuoteSignature::STATUS_ARCHIVED,
'agreement_type' => 'quote-confirmation',
]))
->assertOk()
->assertSeeText('QB-FILTER-MATCH')
->assertDontSee('<td>QB-FILTER-OTHER</td>', false)
->assertDontSeeText('没有匹配的签署任务');
}
public function testAgreementSigningPageDistinguishesDefinitiveFailureFromUnknownResult(): void
{
$this->grantWorkspaceAccess();
foreach ([
[
'business_no' => 'AGR-DEFINITIVE-FAILURE',
'last_error' => 'E 签宝接口返回业务错误(1435002):签署账号 18810726376 与身份信息不一致',
],
[
'business_no' => 'AGR-UNKNOWN-RESULT',
'last_error' => '创建签署流程时连接超时,无法确认远端结果。',
],
] as $index => $error) {
CourierAgreementSignature::create([
'company_id' => $this->company->id,
'business_no' => $error['business_no'],
'idempotency_key' => 'status-display-' . $index,
'document_type' => 'authorization',
'template_code' => 'TPL-AUTHORIZATION-V1',
'template_version' => '1',
'source_file_name' => '授权委托书(快递).docx',
'status' => CourierAgreementSignature::STATUS_UNKNOWN,
'courier_name' => '测试快递',
'company_full_name' => $this->company->name,
'organization_name' => '测试快递有限公司',
'operator_name' => '测试人员',
'operator_account' => '18810726376',
'payload' => ['signing_subject' => $this->company->name],
'environment' => 'production',
'source_pdf_path' => "courier-agreements/status-{$index}/source.pdf",
'source_pdf_sha256' => hash('sha256', '%PDF-source-' . $index),
'last_error' => $error['last_error'],
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
]);
}
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
]))
->assertOk()
->assertSeeText('处理失败')
->assertSeeText('状态未知,请人工核对')
->assertSeeText('E 签宝接口返回业务错误(1435002):签署账号 188****6376 与身份信息不一致')
->assertDontSee('签署账号 18810726376', false);
}
public function testAgreementSigningPageKeepsRenderingWhenLegacyPayloadCannotBeDecrypted(): void
{
$this->grantWorkspaceAccess();
DB::table('business_courier_agreement_signatures')->insert([
'company_id' => $this->company->id,
'business_no' => 'AGR-LEGACY-001',
'idempotency_key' => 'legacy-payload-001',
'document_type' => 'account-proof',
'template_code' => 'TPL-ACCOUNT-CERT-V2',
'template_version' => '2',
'source_file_name' => '收款账户证明文件.doc',
'status' => CourierAgreementSignature::STATUS_COMPLETED,
'courier_name' => '新北中通',
'company_full_name' => $this->company->name,
'organization_name' => '新北中通',
'operator_name' => '张经理',
'operator_account' => '13800138000',
// This value is intentionally not decryptable by the current APP_KEY.
'payload' => 'legacy-ciphertext',
'environment' => 'sandbox',
'source_pdf_path' => 'courier-agreements/legacy/source.pdf',
'source_pdf_sha256' => hash('sha256', '%PDF-source'),
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
'created_at' => now(),
'updated_at' => now(),
]);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
]))
->assertOk()
->assertSeeText('AGR-LEGACY-001')
->assertSee('<td>' . e($this->company->name) . '</td>', false);
}
public function testAgreementSigningPageMapsEverySourceDocumentToItsOwnFieldsAndPreview(): void
{
$this->grantWorkspaceAccess();
$response = $this->loginAs()
->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
'agreement_view' => 'create',
]))
->assertOk();
$response
->assertSee('data-template-fields="account-proof"', false)
->assertSee('data-template-preview="account-proof"', false)
->assertSeeText('生效日期')
->assertSeeText('银行帐户')
->assertSeeText('开户名称(持卡人姓名)')
->assertSeeText('填写实际收款银行卡对应的持卡人姓名,无需与营业执照或公司全称一致。')
->assertSeeText('我司同意自')
->assertSee('data-template-fields="account-change"', false)
->assertSee('data-template-preview="account-change"', false)
->assertSeeText('原收款账户')
->assertSeeText('原账户停止使用日期')
->assertSeeText('新账户启用日期')
->assertSeeText('之前相应的账户')
->assertSee('data-template-fields="entity-change"', false)
->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)
->assertSeeText('合同合作方')
->assertSeeText('合同主体(企业全称)')
->assertSeeText('共用合同物流商(快递名称,可多个)')
->assertSeeText('填写合同主体经办人的真实姓名,须与上方 E 签宝账号实名认证一致')
->assertSee('name="shared_courier_names[]"', false)
->assertSee('data-shared-courier-add', false)
->assertSeeText('添加共用公司')
->assertSeeText('备注')
->assertSeeText('共同用一份合同,打款资料为同一份资料')
->assertSee('data-template-fields="cooperation-contract"', false)
->assertSee('data-template-preview="cooperation-contract"', false)
->assertSee('data-template-preview="quote-confirmation"', false)
->assertSee('data-quote-version', false)
->assertSeeText('报价确认单')
->assertSeeText('绑定已审核报价版本')
->assertSeeText('甲方名称')
->assertSee('name="cooperation_party_a_name"', false)
->assertSee('data-subject-linked-name', false)
->assertSeeText('乙方电话')
->assertSeeText('协议开始日期')
->assertSeeText('协议结束日期')
->assertSeeText('甲、乙双方根据')
->assertSee('data-template-fields="authorization"', false)
->assertSee('data-template-preview="authorization"', false)
->assertSeeText('委托方(个人或公司)')
->assertSeeText('授权事项(可多选)')
->assertSeeText('代签汇款账号变更')
->assertSeeText('委托有效期:从');
}
public function testAgreementSigningPageRejectsUsersWithoutItsSidebarPermission(): void
{
$subject = 'user_' . $this->user->id;
$this->user->roles()->sync([\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->id]);
setting()->set('system.sidebar_permissions', json_encode([
'configured' => [$subject],
'subjects' => [
$subject => ['courier-payables/freight-reconciliation'],
],
], JSON_UNESCAPED_UNICODE));
try {
$this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'agreement-signing',
]));
$this->fail('没有协议签署页面权限的用户不应访问该页面。');
} catch (HttpException $exception) {
$this->assertSame(404, $exception->getStatusCode());
}
}
private function createAgreementTask(array $overrides = []): CourierAgreementSignature
{
return CourierAgreementSignature::create(array_merge([
'company_id' => $this->company->id,
'business_no' => 'AGR-FILTER-' . uniqid(),
'idempotency_key' => 'filter-' . uniqid(),
'document_type' => 'account-proof',
'template_code' => 'TPL-ACCOUNT-CERT-V2',
'template_version' => '2',
'source_file_name' => '收款账户证明文件.doc',
'status' => CourierAgreementSignature::STATUS_SIGNING,
'courier_name' => '新北中通',
'company_full_name' => $this->company->name,
'organization_name' => '新北中通',
'operator_name' => '张经理',
'operator_account' => '13800138000',
'payload' => ['signing_subject' => $this->company->name],
'environment' => 'sandbox',
'source_pdf_path' => 'courier-agreements/filter/source.pdf',
'source_pdf_sha256' => str_repeat('a', 64),
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
], $overrides));
}
private function createQuoteSignature(string $versionCode, string $status, string $carrierName): CourierQuoteSignature
{
$change = CourierQuoteChange::create([
'company_id' => $this->company->id,
'number' => 'BG-' . $versionCode,
'source_quote_id' => 'QB-SOURCE-' . $versionCode,
'source_sheet_name' => $carrierName . '报价',
'carrier_name' => $carrierName,
'quote_type' => '标准报价',
'change_type' => '面单调整',
'status' => CourierQuoteChange::STATUS_APPROVED,
'effective_date' => now()->toDateString(),
'is_retrospective' => false,
'recalculation_status' => CourierQuoteChange::RECALCULATION_NOT_REQUIRED,
'summary' => '筛选测试报价。',
'created_by' => $this->user->id,
'approved_by' => $this->user->id,
'approved_at' => now(),
]);
$version = CourierQuoteVersion::create([
'company_id' => $this->company->id,
'version_code' => $versionCode,
'source_quote_id' => 'QB-SOURCE-' . $versionCode,
'source_change_id' => $change->id,
'sheet_name' => $carrierName . '报价',
'company_name' => $this->company->name,
'carrier_name' => $carrierName,
'quote_type' => '标准报价',
'effective_date' => now()->toDateString(),
'source_range' => 'A1:B2',
'values_json' => json_encode([['地区', '面单'], ['广东省', 6.5]], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
'formulas_json' => json_encode([[], []], JSON_THROW_ON_ERROR),
'status' => 'approved',
'signature_status' => '未签署',
'created_by' => $this->user->id,
'approved_by' => $this->user->id,
]);
return CourierQuoteSignature::create([
'company_id' => $this->company->id,
'quote_version_id' => $version->id,
'provider' => 'esign',
'environment' => 'sandbox',
'status' => $status,
'organization_name' => $carrierName,
'operator_name' => '筛选签署人',
'source_pdf_path' => 'courier-quotes/filter/' . $versionCode . '.pdf',
'source_pdf_sha256' => str_repeat('b', 64),
'source_pdf_generated_at' => now(),
'created_by' => $this->user->id,
'updated_by' => $this->user->id,
]);
}
}