CaiWuXiTong/tests/Feature/Business/CourierContractManagementTest.php

202 lines
12 KiB
PHP

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use App\Models\Business\CourierAgreementSignature;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Mockery;
use Modules\EcommerceFinance\Repositories\CourierInformationMirrorRepository;
use Tests\Feature\FeatureTestCase;
class CourierContractManagementTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
Carbon::setTestNow(Carbon::parse('2026-10-15 15:30:00', config('app.timezone')));
Storage::fake('uploads');
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
// Contract management must work even when upstream courier mirrors are unavailable.
$this->app->instance(CourierInformationMirrorRepository::class, Mockery::mock(CourierInformationMirrorRepository::class));
}
protected function tearDown(): void
{
Carbon::setTestNow();
parent::tearDown();
}
public function test_only_archived_courier_cooperation_contracts_are_visible_without_duplicate_writes(): void
{
$contract = $this->signature('快递测试公司');
$this->signature('供应商不应出现', ['partner_type' => 'supplier']);
$this->signature('未知合作方不应出现', ['partner_type' => 'other']);
$this->signature('待归档不应出现', [], ['status' => 'completed']);
$this->signature('证明文件不应出现', [], ['document_type' => 'account-proof']);
$this->signature('其他租户不应出现', [], ['company_id' => $this->company->id + 10]);
$deleted = $this->signature('已删除不应出现');
$deleted->delete();
$count = DB::table('business_courier_agreement_signatures')->count();
$this->page()->assertOk()->assertSeeText('快递测试公司')->assertSeeText('KD-2026-001')
->assertSeeText('乙方签署企业')->assertSeeText('法人测试姓名')->assertSeeText($this->user->name)
->assertSeeText('查看已签署合同')->assertSeeText('合同管理')->assertSeeText('甲方签署主体')
->assertDontSeeText('供应商不应出现')->assertDontSeeText('未知合作方不应出现')
->assertDontSeeText('待归档不应出现')->assertDontSeeText('证明文件不应出现')
->assertDontSeeText('其他租户不应出现')->assertDontSeeText('已删除不应出现')
->assertSee(route('business.courier-contracts.download', ['agreementSignature' => $contract->id, 'kind' => 'signed']), false);
$this->page()->assertOk();
$this->assertSame($count, DB::table('business_courier_agreement_signatures')->count());
}
public function test_reminders_use_contract_dates_and_include_today_and_day_thirty(): void
{
$this->signature('今天到期快递', ['cooperation_end_date' => '2026-10-15']);
$this->signature('三十天边界快递', ['cooperation_end_date' => '2026-11-14']);
$this->signature('有效期快递', ['cooperation_end_date' => '2026-11-15']);
$this->signature('届满快递', ['cooperation_end_date' => '2026-10-14']);
$this->signature('未来开始快递', ['cooperation_start_date' => '2026-10-20', 'cooperation_end_date' => '2026-11-01']);
$response = $this->page()->assertOk()->assertSeeText('今天到期')->assertSeeText('已届满 1 天');
$counts = $response->viewData('content')['courierContractCounts'];
$this->assertSame(['due' => 2, 'expired' => 1, 'active' => 1, 'upcoming' => 1, 'unknown' => 0], $counts);
$this->page(['contract_status' => 'due'])->assertSeeText('今天到期快递')->assertSeeText('三十天边界快递')
->assertDontSeeText('有效期快递')->assertDontSeeText('届满快递')->assertDontSeeText('未来开始快递');
}
public function test_missing_invalid_and_personal_metadata_is_not_guessed_from_signer_or_deadline(): void
{
$this->signature('缺日期快递', ['cooperation_start_date' => null, 'cooperation_end_date' => null,
'cooperation_contract_number' => null, 'cooperation_party_b_legal_representative' => null],
['operator_name' => '经办人不是法人', 'expires_at' => '2026-10-16']);
$this->signature('无效日期快递', ['cooperation_end_date' => '2026-02-30']);
$this->signature('倒置日期快递', ['cooperation_start_date' => '2027-01-01']);
$this->signature('个人签署快递', ['counterparty_signer_type' => 'person']);
$legacy = $this->signature('旧快递合同', ['partner_type' => null]);
$payload = $legacy->payload;
unset($payload['partner_type']);
$legacy->update(['payload' => $payload]);
$this->page()->assertOk()->assertSeeText('期限待核对')->assertSeeText('无法计算')->assertSeeText('未填写')
->assertSeeText('不适用(个人签署)')->assertSeeText('旧快递合同')->assertDontSeeText('经办人不是法人');
$this->page(['contract_status' => 'unknown'])->assertSeeText('缺日期快递')->assertSeeText('无效日期快递')
->assertSeeText('倒置日期快递')->assertDontSeeText('个人签署快递');
}
public function test_search_date_filter_and_pagination_apply_to_all_archives_not_just_the_current_page(): void
{
setting()->set('default.list_limit', 2);
setting()->save();
for ($i = 1; $i <= 5; $i++) {
$this->signature('分页快递-' . $i, ['cooperation_contract_number' => 'PAGE-' . $i, 'cooperation_end_date' => '2026-11-0' . $i]);
}
$this->page()->assertSeeText('第 1 / 3 页,本页 2 条,共 5 条');
$this->page(['page' => 3])->assertSeeText('分页快递-5')->assertDontSeeText('分页快递-1');
$this->page(['contract_search' => 'PAGE-5'])->assertSeeText('分页快递-5')->assertDontSeeText('分页快递-1');
$this->page(['contract_search' => '乙方签署企业', 'contract_end_from' => '2026-11-04', 'contract_end_to' => '2026-11-03'])
->assertSeeText('分页快递-3')->assertSeeText('分页快递-4')->assertDontSeeText('分页快递-5');
}
public function test_unreadable_archive_and_missing_attachment_are_explicit_and_not_exposed(): void
{
$unreadable = $this->signature('无法解密不应出现');
DB::table('business_courier_agreement_signatures')->where('id', $unreadable->id)->update(['payload' => 'invalid-encrypted-payload']);
$missing = $this->signature('附件缺失快递');
Storage::disk('uploads')->delete($missing->signed_pdf_path);
$this->page()->assertOk()->assertSeeText('加密快照无法读取')->assertDontSeeText('无法解密不应出现')
->assertSeeText('归档附件缺失,请核对')->assertDontSee($missing->signed_pdf_path, false);
$this->withExceptionHandling();
$this->download($unreadable)->assertNotFound();
$this->download($missing)->assertNotFound();
}
public function test_download_rechecks_tenant_archive_type_and_partner_and_never_serves_unsigned_originals(): void
{
$contract = $this->signature('可下载合同');
$this->download($contract)->assertOk()->assertHeader('content-type', 'application/pdf');
$this->download($contract, 'certificate')->assertOk();
$this->withExceptionHandling();
foreach ([
$this->signature('其他公司', [], ['company_id' => $this->company->id + 10]),
$this->signature('待归档', [], ['status' => 'completed']),
$this->signature('供应商', ['partner_type' => 'supplier']),
$this->signature('非合同', [], ['document_type' => 'account-proof']),
] as $ineligible) {
$this->download($ineligible)->assertNotFound();
}
$this->download($contract, 'source')->assertNotFound();
}
public function test_read_failure_is_not_presented_as_an_empty_healthy_archive(): void
{
Schema::drop('business_courier_agreement_signatures');
$this->page()->assertOk()->assertSeeText('协议签署数据表尚未建立')->assertSeeText('合同数据暂时不可用,请重试。');
}
public function test_courier_information_access_is_required_for_archive_downloads(): void
{
$contract = $this->signature('页面权限测试');
$this->user->syncRoles([\App\Models\Auth\Role::where('name', 'user')->firstOrFail()->id]);
setting()->set('system.sidebar_permissions', json_encode([
'configured' => ['user_' . $this->user->id],
'subjects' => ['user_' . $this->user->id => ['courier-payables/courier-information']],
], JSON_UNESCAPED_UNICODE));
setting()->save();
$this->page()->assertOk();
$this->download($contract)->assertOk();
setting()->set('system.sidebar_permissions', json_encode([
'configured' => ['user_' . $this->user->id], 'subjects' => ['user_' . $this->user->id => []],
], JSON_UNESCAPED_UNICODE));
setting()->save();
$this->withExceptionHandling();
$this->download($contract)->assertForbidden();
}
public function test_object_storage_is_not_queried_per_row_when_opening_the_contract_list(): void
{
$this->signature('远程附件列表');
$this->loginAs();
config(['filesystems.disks.uploads.driver' => 's3']);
Storage::shouldReceive('disk')->never();
$data = app(\App\Business\CourierContractPageData::class)->page();
$this->assertNull($data['courierContractError']);
$this->assertTrue($data['courierContractRows']->items()[0]['has_signed_file']);
}
private function page(array $query = [])
{
return $this->loginAs()->get(route('business.pages.show', ['section' => 'courier-payables',
'page' => 'courier-information']) . '?' . http_build_query(['courier_tab' => 'contracts'] + $query));
}
private function download(CourierAgreementSignature $signature, string $kind = 'signed')
{
return $this->loginAs()->getJson(route('business.courier-contracts.download', ['agreementSignature' => $signature->id, 'kind' => $kind]), ['X-Requested-With' => 'XMLHttpRequest']);
}
private function signature(string $name, array $payload = [], array $attributes = []): CourierAgreementSignature
{
$key = 'test-' . bin2hex(random_bytes(8));
Storage::disk('uploads')->put($key . '/signed.pdf', '%PDF-1.4 synthetic signed contract');
Storage::disk('uploads')->put($key . '/certificate.pdf', '%PDF-1.4 synthetic certificate');
return CourierAgreementSignature::create($attributes + [
'company_id' => $this->company->id, 'business_no' => $key, 'idempotency_key' => $key,
'document_type' => 'cooperation-contract', 'template_code' => 'TPL-COOPERATION-CONTRACT-V1',
'template_version' => '1', 'source_file_name' => 'contract.pdf', 'status' => 'archived',
'courier_name' => $name, 'company_full_name' => '甲方企业', 'organization_name' => '乙方签署企业',
'operator_name' => '经办人', 'payload' => $payload + [
'partner_type' => 'courier', 'cooperation_contract_number' => 'KD-2026-001',
'cooperation_party_a_name' => '甲方企业', 'cooperation_party_b_name' => '乙方签署企业',
'cooperation_party_b_legal_representative' => '法人测试姓名',
'cooperation_start_date' => '2026-01-01', 'cooperation_end_date' => '2026-11-01',
],
'source_pdf_path' => $key . '/source.pdf', 'source_pdf_sha256' => '',
'signed_pdf_path' => $key . '/signed.pdf', 'certificate_path' => $key . '/certificate.pdf',
'archived_at' => now(), 'created_by' => $this->user->id, 'updated_by' => $this->user->id,
]);
}
}