401 lines
25 KiB
PHP
401 lines
25 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature\Business;
|
||
|
||
use App\Models\Auth\Permission;
|
||
use App\Models\Auth\Role;
|
||
use App\Models\Business\CourierQuoteChange;
|
||
use App\Models\Business\CourierQuoteVersion;
|
||
use App\Models\Business\CourierStockQuoteEntry;
|
||
use App\Support\CourierQuoteChangeService;
|
||
use App\Support\QuoteManagementSnapshot;
|
||
use Illuminate\Http\UploadedFile;
|
||
use Illuminate\Support\Facades\Storage;
|
||
use Modules\EcommerceFinance\Repositories\CourierInformationMirrorRepository;
|
||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||
use Tests\Feature\FeatureTestCase;
|
||
|
||
class CourierStockQuoteEntryTest extends FeatureTestCase
|
||
{
|
||
private array $temporaryFiles = [];
|
||
|
||
protected function setUp(): void
|
||
{
|
||
parent::setUp();
|
||
Storage::fake('uploads');
|
||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
$mirror = \Mockery::mock(CourierInformationMirrorRepository::class);
|
||
$mirror->shouldReceive('overviewSource')->andReturn(['express' => [
|
||
(object) ['expcode' => 'EXP-001', 'expname' => '同名快递网点'],
|
||
(object) ['expcode' => 'EXP-002', 'expname' => '同名快递网点'],
|
||
]]);
|
||
app()->instance(CourierInformationMirrorRepository::class, $mirror);
|
||
app()->instance(QuoteManagementSnapshot::class, new QuoteManagementSnapshot('/nonexistent-stock-quote-fixture.json'));
|
||
}
|
||
|
||
protected function tearDown(): void
|
||
{
|
||
foreach ($this->temporaryFiles as $path) {
|
||
if (is_file($path)) {
|
||
unlink($path);
|
||
}
|
||
}
|
||
parent::tearDown();
|
||
}
|
||
|
||
public function test_entry_uses_existing_courier_and_retains_original_file_formula_and_real_date(): void
|
||
{
|
||
$this->loginAs()->get(route('business.courier-stock-quotes.single'))->assertOk()
|
||
->assertSee('value="同名快递网点"', false)->assertSeeText('实际开始执行日期')->assertDontSee('name="courier_expcode"', false);
|
||
$data = $this->payload();
|
||
$originalHash = hash_file('sha256', $data['source_quote']->getRealPath());
|
||
$this->loginAs()->post(route('business.courier-stock-quotes.store'), $data)->assertRedirect();
|
||
$entry = CourierStockQuoteEntry::firstOrFail();
|
||
$this->assertSame('EXP-001', $entry->courier_expcode);
|
||
$this->assertSame($originalHash, $entry->files[0]['sha256']);
|
||
$this->assertSame('原始报价.xlsx', $entry->files[0]['name']);
|
||
$this->assertSame('=B2/2', $entry->sourceQuote()['formulas'][1][2]);
|
||
$this->assertSame('2025-12-01', $entry->change->effective_date->toDateString());
|
||
$this->assertFalse($entry->change->isRetrospective());
|
||
Storage::disk('uploads')->assertExists($entry->files[0]['path']);
|
||
$this->assertDatabaseCount('business_courier_quote_versions', 0);
|
||
$this->assertDatabaseCount('business_courier_onboardings', 0);
|
||
$this->loginAs()->get(route('business.courier-quote-changes.show', $entry->change))->assertOk()
|
||
->assertSeeText('存量报价原文与核对')->assertSeeText('=B2/2')->assertSeeText('首重1kg');
|
||
}
|
||
|
||
public function test_review_requires_confirmation_and_never_claims_unsigned_quote_is_signed(): void
|
||
{
|
||
$entry = $this->createEntry();
|
||
$this->loginAs()->post(route('business.courier-quote-changes.submit', $entry->change))->assertRedirect();
|
||
$reviewer = $this->reviewer();
|
||
$this->withExceptionHandling()->loginAs($reviewer, $this->company)
|
||
->post(route('business.courier-quote-changes.approve', $entry->change))->assertSessionHasErrors('stock_rules_confirmed');
|
||
$this->assertDatabaseCount('business_courier_quote_versions', 0);
|
||
$this->loginAs($reviewer, $this->company)->post(route('business.courier-quote-changes.approve', $entry->change), ['stock_rules_confirmed' => '1'])->assertRedirect();
|
||
$version = CourierQuoteVersion::firstOrFail();
|
||
$this->assertSame('存量报价,审核生效(无需签章)', $version->signature_status);
|
||
$this->assertSame('EXP-001', $version->courier_expcode);
|
||
$this->assertSame(CourierQuoteChange::RECALCULATION_NOT_REQUIRED, $entry->change->fresh()->recalculation_status);
|
||
$this->assertSame('2025-12-01', $version->effective_date->toDateString());
|
||
$this->assertDatabaseCount('business_courier_quote_signatures', 0);
|
||
$this->assertSame('生效中', app(QuoteManagementSnapshot::class)->load()['quotes'][0]['status']);
|
||
$this->assertSame($version->id, \App\Support\CourierRequoteService::resolveAt(collect([$version->fresh()]), now())->id);
|
||
// 已上线旧记录无需改库或重新审核,也按存量审核规则生效。
|
||
$version->update(['signature_status' => '待确认/待补签']);
|
||
$snapshot = app(QuoteManagementSnapshot::class)->load()['quotes'][0];
|
||
$this->assertSame('生效中', $snapshot['status']);
|
||
$this->assertSame('存量报价,审核生效(无需签章)', $snapshot['signature_status']);
|
||
}
|
||
|
||
public function test_historical_documents_require_reviewer_verification_and_remain_distinct_from_esign(): void
|
||
{
|
||
$entry = $this->createEntry(['signed_files' => [UploadedFile::fake()->create('历史合同.pdf', 5, 'application/pdf')]]);
|
||
$this->loginAs()->post(route('business.courier-quote-changes.submit', $entry->change));
|
||
$this->loginAs($this->reviewer(), $this->company)->post(route('business.courier-quote-changes.approve', $entry->change), [
|
||
'stock_rules_confirmed' => '1', 'stock_signature_verified' => '1',
|
||
])->assertRedirect();
|
||
$version = CourierQuoteVersion::firstOrFail();
|
||
$this->assertSame('历史签署资料已归档', $version->signature_status);
|
||
$this->assertDatabaseCount('business_courier_quote_signatures', 0);
|
||
$snapshot = app(QuoteManagementSnapshot::class)->load()['quotes'][0];
|
||
$this->assertSame('生效中', $snapshot['status']);
|
||
$this->assertArrayHasKey('signed', $snapshot['signed_files']);
|
||
$this->loginAs()->get(route('business.courier-quote-changes.show', $entry->change))->assertOk()
|
||
->assertSeeText('尚无本系统电子签署记录')->assertDontSeeText('生成签署 PDF');
|
||
}
|
||
|
||
public function test_claiming_verified_history_without_documents_is_rejected(): void
|
||
{
|
||
$entry = $this->createEntry();
|
||
$this->loginAs()->post(route('business.courier-quote-changes.submit', $entry->change));
|
||
$this->withExceptionHandling()->loginAs($this->reviewer(), $this->company)
|
||
->post(route('business.courier-quote-changes.approve', $entry->change), ['stock_rules_confirmed' => '1', 'stock_signature_verified' => '1'])
|
||
->assertSessionHasErrors('stock_signature_verified');
|
||
$this->assertDatabaseCount('business_courier_quote_versions', 0);
|
||
}
|
||
|
||
public function test_approved_stock_quote_exposes_all_originals_without_requiring_signature_verification(): void
|
||
{
|
||
$entry = $this->createEntry(['signed_files' => [
|
||
UploadedFile::fake()->createWithContent('signed-original.pdf', "%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\n%%EOF"),
|
||
UploadedFile::fake()->image('签署原件二.png'),
|
||
]]);
|
||
$this->loginAs()->post(route('business.courier-quote-changes.submit', $entry->change));
|
||
$this->loginAs($this->reviewer(), $this->company)->post(route('business.courier-quote-changes.approve', $entry->change), ['stock_rules_confirmed' => '1'])->assertRedirect();
|
||
$quote = app(QuoteManagementSnapshot::class)->load()['quotes'][0];
|
||
$this->assertSame('生效中', $quote['status']);
|
||
$this->assertCount(2, $quote['stock_originals']);
|
||
$this->assertSame([], $quote['signed_files']);
|
||
$this->loginAs()->get(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'quote-management']))
|
||
->assertOk()->assertSee('data-stock-original-links', false)->assertSee('预览签署原件');
|
||
foreach ($quote['stock_originals'] as $original) {
|
||
$this->assertTrue($original['previewable']);
|
||
$response = $this->loginAs()->get($original['url'])->assertOk()->assertHeader('X-Content-Type-Options', 'nosniff');
|
||
$this->assertStringStartsWith('inline;', $response->headers->get('Content-Disposition'));
|
||
}
|
||
$version = CourierQuoteVersion::firstOrFail();
|
||
$version->update(['status' => CourierQuoteVersion::STATUS_VOIDED]);
|
||
$this->assertNull(\App\Support\CourierRequoteService::resolveAt(collect([$version->fresh()]), now()));
|
||
}
|
||
|
||
public function test_same_code_and_type_cannot_be_imported_twice_but_same_names_with_different_codes_can(): void
|
||
{
|
||
$this->createEntry();
|
||
$this->withExceptionHandling()->loginAs()->post(route('business.courier-stock-quotes.store'), $this->payload())
|
||
->assertSessionHasErrors('courier_name');
|
||
$this->loginAs()->post(route('business.courier-stock-quotes.store'), [...$this->payload(), 'courier_expcode' => 'EXP-002'])->assertRedirect();
|
||
$this->assertDatabaseCount('business_courier_stock_quote_entries', 2);
|
||
$this->withExceptionHandling()->loginAs()->post(route('business.courier-stock-quotes.store'), [...$this->payload(), 'courier_expcode' => 'UNREGISTERED'])
|
||
->assertSessionHasErrors('courier_expcode');
|
||
}
|
||
|
||
public function test_multi_sheet_upload_requires_exact_sheet_and_invalid_workbooks_leave_no_record(): void
|
||
{
|
||
$this->withExceptionHandling()->loginAs()->post(route('business.courier-stock-quotes.store'), [...$this->payload(), 'source_quote' => $this->workbook(true)])
|
||
->assertSessionHasErrors('sheet_name');
|
||
$this->assertDatabaseCount('business_courier_stock_quote_entries', 0);
|
||
$this->loginAs()->post(route('business.courier-stock-quotes.store'), [...$this->payload(), 'source_quote' => $this->workbook(true), 'sheet_name' => '报价'])
|
||
->assertRedirect();
|
||
}
|
||
|
||
public function test_manual_name_can_be_corrected_in_draft_and_follows_review_and_later_price_change(): void
|
||
{
|
||
$entry = $this->createEntry(['courier_expcode' => null, 'courier_name' => '手填名称待修正', 'signed_files' => [UploadedFile::fake()->create('签署原件.pdf', 1, 'application/pdf')]]);
|
||
$files = $entry->files;
|
||
$this->assertNull($entry->courier_expcode);
|
||
$this->loginAs()->patch(route('business.courier-stock-quotes.update', $entry), [
|
||
'courier_name' => '发货数据完整快递名称', 'effective_date' => '2025-12-01', 'billing_rules' => '首重1kg,续重按原报价', 'summary' => '更正为发货名称',
|
||
])->assertRedirect();
|
||
$entry->refresh();
|
||
$this->assertSame('发货数据完整快递名称', $entry->courier_name);
|
||
$this->assertSame('发货数据完整快递名称', $entry->change->carrier_name);
|
||
$this->assertSame('报价', $entry->sheet_name);
|
||
$this->assertSame($files, $entry->files);
|
||
$savedDetails = json_decode($entry->change->events()->where('event_type', 'saved')->latest('id')->firstOrFail()->details_json, true);
|
||
$this->assertSame('发货数据完整快递名称', $savedDetails['carrier_name']);
|
||
$service = app(CourierQuoteChangeService::class);
|
||
$reviewer = (int) $this->reviewer()->id;
|
||
$service->submit($entry->change, (int) $this->user->id);
|
||
$service->approve($entry->change->fresh(), $reviewer, true);
|
||
$original = CourierQuoteVersion::firstOrFail();
|
||
$this->assertNull($original->courier_expcode);
|
||
$this->assertSame('发货数据完整快递名称', $original->company_name);
|
||
$this->assertSame('生效中', app(QuoteManagementSnapshot::class)->load()['quotes'][0]['status']);
|
||
$change = $service->create([
|
||
'source_quote_id' => $original->version_code, 'change_type' => '费率调整', 'effective_date' => '2026-01-01', 'summary' => '后续变价',
|
||
'items' => [['source_cell' => 'B2', 'source_row' => 1, 'source_column' => 1, 'original_amount' => 5, 'new_amount' => 7]],
|
||
], (int) $this->user->id);
|
||
$service->submit($change, (int) $this->user->id);
|
||
$changed = $service->approve($change->fresh(), $reviewer);
|
||
$this->assertSame('发货数据完整快递名称', $changed->version->company_name);
|
||
$this->assertNull($changed->version->courier_expcode);
|
||
$this->assertEquals(5, json_decode($original->fresh()->values_json, true)[1][1]);
|
||
$this->assertEquals(7, json_decode($changed->version->values_json, true)[1][1]);
|
||
$this->assertSame($files, $entry->fresh()->files);
|
||
}
|
||
|
||
public function test_shipping_names_can_be_suggested_without_a_courier_directory_record(): void
|
||
{
|
||
$shipping = \Mockery::mock(\Modules\EcommerceFinance\Repositories\ShippingOrderRepository::class);
|
||
$shipping->shouldReceive('recentCourierNames')->once()->with('缺资料', 20)->andReturn(['缺资料发货快递']);
|
||
app()->instance(\Modules\EcommerceFinance\Repositories\ShippingOrderRepository::class, $shipping);
|
||
$this->loginAs()->getJson(route('business.courier-stock-quotes.courier-names', ['q' => '缺资料']))->assertOk()->assertJsonPath('items.0', '缺资料发货快递');
|
||
$this->assertDatabaseCount('business_courier_onboardings', 0);
|
||
}
|
||
|
||
public function test_name_only_versions_of_different_quote_types_do_not_supersede_each_other(): void
|
||
{
|
||
$service = app(CourierQuoteChangeService::class);
|
||
$reviewer = (int) $this->reviewer()->id;
|
||
foreach (['标准快递报价', '大件快递报价'] as $type) {
|
||
$entry = $this->createEntry(['courier_expcode' => null, 'courier_name' => '同家无编号快递', 'quote_type' => $type,
|
||
'signed_files' => [UploadedFile::fake()->create('签署原件.pdf', 1, 'application/pdf')]]);
|
||
$service->submit($entry->change, (int) $this->user->id);
|
||
$service->approve($entry->change->fresh(), $reviewer, true);
|
||
}
|
||
$this->assertSame(['生效中', '生效中'], array_column(app(QuoteManagementSnapshot::class)->load()['quotes'], 'status'));
|
||
}
|
||
|
||
public function test_draft_replacement_retains_previous_original_and_updates_preview_and_audit(): void
|
||
{
|
||
$entry = $this->createEntry();
|
||
$originalPath = $entry->files[0]['path'];
|
||
$this->loginAs()->patch(route('business.courier-stock-quotes.update', $entry), [
|
||
'effective_date' => '2025-11-01', 'billing_rules' => '修正后首重1kg', 'summary' => '更正原报价附件',
|
||
'source_quote' => $this->workbook(false, 8),
|
||
])->assertRedirect();
|
||
$entry->refresh();
|
||
$this->assertCount(2, $entry->files);
|
||
$this->assertSame('previous_source_quote', $entry->files[0]['kind']);
|
||
Storage::disk('uploads')->assertExists($originalPath);
|
||
$this->assertEquals(8, $entry->sourceQuote()['values'][1][1]);
|
||
$this->assertEquals(8, json_decode($entry->change->proposed_values_json, true)[1][1]);
|
||
$details = json_decode($entry->change->events()->where('event_type', 'saved')->firstOrFail()->details_json, true);
|
||
$this->assertSame('修正后首重1kg', $details['billing_rules']);
|
||
$this->assertCount(2, $details['files']);
|
||
}
|
||
|
||
public function test_followup_price_change_reuses_version_and_keeps_exact_courier_identity(): void
|
||
{
|
||
$entry = $this->createEntry();
|
||
app(CourierQuoteChangeService::class)->submit($entry->change, (int) $this->user->id);
|
||
app(CourierQuoteChangeService::class)->approve($entry->change->fresh(), (int) $this->reviewer()->id);
|
||
$version = CourierQuoteVersion::firstOrFail();
|
||
$this->loginAs()->get(route('business.courier-quote-changes.start', ['source_quote_id' => $version->version_code]))
|
||
->assertOk()->assertSeeText('编辑新价格');
|
||
$change = app(CourierQuoteChangeService::class)->create([
|
||
'source_quote_id' => $version->version_code, 'change_type' => '费率调整', 'effective_date' => '2026-10-16',
|
||
'summary' => '存量快递后续变价', 'items' => [['source_cell' => 'B2', 'source_row' => 1, 'source_column' => 1, 'original_amount' => 5, 'new_amount' => 6]],
|
||
], (int) $this->user->id);
|
||
app(CourierQuoteChangeService::class)->submit($change, (int) $this->user->id);
|
||
$approved = app(CourierQuoteChangeService::class)->approve($change->fresh(), (int) $this->reviewer()->id);
|
||
$this->assertSame('EXP-001', $approved->version->courier_expcode);
|
||
$this->assertSame('首重1kg,续重按原报价', $approved->version->billing_rules);
|
||
$this->assertEquals(6, json_decode($approved->version->values_json, true)[1][1]);
|
||
$this->assertEquals(5, json_decode($version->fresh()->values_json, true)[1][1]);
|
||
}
|
||
|
||
public function test_other_company_cannot_download_or_edit_stock_quote(): void
|
||
{
|
||
$entry = $this->createEntry();
|
||
\DB::table('business_courier_stock_quote_entries')->where('id', $entry->id)->update(['company_id' => $this->company->id + 999]);
|
||
$this->withExceptionHandling()->loginAs()
|
||
->get(route('business.courier-stock-quotes.download', [$entry, 0]))->assertNotFound();
|
||
$this->loginAs()->get(route('business.courier-stock-quotes.edit', $entry))->assertNotFound();
|
||
$this->assertNull(CourierStockQuoteEntry::find($entry->id));
|
||
}
|
||
|
||
public function test_same_name_couriers_keep_separate_effective_versions(): void
|
||
{
|
||
$reviewer = $this->reviewer();
|
||
foreach (['EXP-001', 'EXP-002'] as $code) {
|
||
$entry = $this->createEntry(['courier_expcode' => $code, 'signed_files' => [UploadedFile::fake()->create('回签.pdf', 1, 'application/pdf')]]);
|
||
app(CourierQuoteChangeService::class)->submit($entry->change, (int) $this->user->id);
|
||
app(CourierQuoteChangeService::class)->approve($entry->change->fresh(), (int) $reviewer->id, true);
|
||
}
|
||
$quotes = app(QuoteManagementSnapshot::class)->load()['quotes'];
|
||
$this->assertCount(2, $quotes);
|
||
$this->assertSame(['生效中', '生效中'], array_column($quotes, 'status'));
|
||
$this->assertSame(['EXP-001', 'EXP-002'], array_column($quotes, 'courier_expcode'));
|
||
}
|
||
|
||
private function createEntry(array $overrides = []): CourierStockQuoteEntry
|
||
{
|
||
$this->loginAs()->post(route('business.courier-stock-quotes.store'), [...$this->payload(), ...$overrides])->assertRedirect();
|
||
|
||
return CourierStockQuoteEntry::latest('id')->firstOrFail();
|
||
}
|
||
|
||
public function test_same_courier_can_import_disjoint_periods_and_resolve_history_without_temporary_price_semantics(): void
|
||
{
|
||
$first = $this->createEntry(['effective_date' => '2025-12-01', 'end_date' => '2026-09-30']);
|
||
$second = $this->createEntry(['effective_date' => '2026-10-01', 'end_date' => null]);
|
||
$reviewer = $this->reviewer();
|
||
foreach ([$first, $second] as $entry) {
|
||
app(CourierQuoteChangeService::class)->submit($entry->change, $this->user->id);
|
||
app(CourierQuoteChangeService::class)->approve($entry->change->fresh(), $reviewer->id);
|
||
}
|
||
$versions = CourierQuoteVersion::with('change')->get();
|
||
$this->assertCount(2, $versions);
|
||
$this->assertFalse($versions[0]->isTemporary());
|
||
$this->assertSame('2025-12-01 至 2026-09-30', $versions[0]->periodLabel());
|
||
$this->assertSame($versions[0]->id, \App\Support\CourierRequoteService::resolveAt($versions, new \DateTimeImmutable('2026-09-30'))->id);
|
||
$this->assertSame($versions[1]->id, \App\Support\CourierRequoteService::resolveAt($versions, new \DateTimeImmutable('2026-10-01'))->id);
|
||
$quotes = app(QuoteManagementSnapshot::class)->load()['quotes'];
|
||
$this->assertSame($quotes[0]['group_key'], $quotes[1]['group_key']);
|
||
$this->assertSame('已到期', $quotes[0]['status']);
|
||
$this->assertSame('生效中', $quotes[1]['status']);
|
||
}
|
||
|
||
public function test_overlapping_periods_and_shared_boundary_day_are_rejected_but_earlier_history_is_allowed(): void
|
||
{
|
||
$this->createEntry(['effective_date' => '2026-10-01']);
|
||
foreach ([['2026-09-01', '2026-10-01'], ['2026-10-02', null], ['2026-11-01', '2026-10-01']] as [$start, $end]) {
|
||
$this->withExceptionHandling()->loginAs()->post(route('business.courier-stock-quotes.store'), [...$this->payload(), 'effective_date' => $start, 'end_date' => $end])->assertSessionHasErrors();
|
||
}
|
||
$this->assertDatabaseCount('business_courier_stock_quote_entries', 1);
|
||
$this->createEntry(['effective_date' => '2025-12-01', 'end_date' => '2026-09-30']);
|
||
$this->assertDatabaseCount('business_courier_stock_quote_entries', 2);
|
||
}
|
||
|
||
public function test_explicit_period_end_preserves_originals_and_audits_change_before_next_period_import(): void
|
||
{
|
||
$entry = $this->createEntry(['effective_date' => '2025-12-01']);
|
||
$reviewer = $this->reviewer();
|
||
app(CourierQuoteChangeService::class)->submit($entry->change, $this->user->id);
|
||
app(CourierQuoteChangeService::class)->approve($entry->change->fresh(), $reviewer->id);
|
||
$original = $entry->values_json;
|
||
$files = $entry->files;
|
||
$this->loginAs($reviewer, $this->company)->post(route('business.courier-stock-quotes.period-end', $entry), ['end_date' => '2026-09-30', 'reason' => '依据新旧报价交接日期'])->assertRedirect();
|
||
$this->assertSame('2026-09-30', $entry->fresh()->end_date->toDateString());
|
||
$this->assertSame('2026-09-30', $entry->change->fresh()->version->end_date->toDateString());
|
||
$this->assertSame($original, $entry->fresh()->values_json);
|
||
$this->assertSame($files, $entry->fresh()->files);
|
||
$this->assertDatabaseHas('business_courier_quote_change_events', ['quote_change_id' => $entry->source_change_id, 'event_type' => 'period_end_updated', 'created_by' => $reviewer->id]);
|
||
$this->createEntry(['effective_date' => '2026-10-01']);
|
||
$this->withExceptionHandling()->loginAs($reviewer, $this->company)->post(route('business.courier-stock-quotes.period-end', $entry), ['end_date' => '2026-10-01', 'reason' => '重叠边界验证'])->assertSessionHasErrors('courier_name');
|
||
$this->get(route('business.pages.show', ['section' => 'courier-payables', 'page' => 'quote-management']))->assertOk()->assertSeeText('执行期间重叠');
|
||
$this->assertSame('2026-09-30', $entry->fresh()->end_date->toDateString());
|
||
}
|
||
|
||
public function test_period_end_requires_admin_and_current_company(): void
|
||
{
|
||
$entry = $this->createEntry();
|
||
$reader = $this->reviewer();
|
||
$reader->roles()->detach();
|
||
$this->withExceptionHandling()->loginAs($reader, $this->company)->post(route('business.courier-stock-quotes.period-end', $entry), ['end_date' => '2026-09-30', 'reason' => '权限检查'])->assertForbidden();
|
||
\DB::table('business_courier_stock_quote_entries')->where('id', $entry->id)->update(['company_id' => $this->company->id + 999]);
|
||
$this->withExceptionHandling()->loginAs($this->reviewer(), $this->company)->post(route('business.courier-stock-quotes.period-end', $entry), ['end_date' => '2026-09-30', 'reason' => '公司隔离检查'])->assertNotFound();
|
||
}
|
||
|
||
public function test_period_prefill_preserves_selected_code_for_same_named_couriers(): void
|
||
{
|
||
$response = $this->loginAs()->get(route('business.courier-stock-quotes.single', ['courier_name' => '同名快递网点', 'courier_expcode' => 'EXP-002', 'quote_type' => '标准快递报价']))->assertOk();
|
||
$document = new \DOMDocument;
|
||
@$document->loadHTML($response->getContent(), LIBXML_NONET);
|
||
$code = (new \DOMXPath($document))->query('//input[@name="courier_expcode"]')->item(0)->getAttribute('value');
|
||
$this->assertSame('EXP-002', $code);
|
||
$this->post(route('business.courier-stock-quotes.store'), [...$this->payload(), 'courier_name' => '同名快递网点', 'courier_expcode' => $code])->assertRedirect();
|
||
$this->assertSame('EXP-002', CourierStockQuoteEntry::firstOrFail()->courier_expcode);
|
||
}
|
||
|
||
private function payload(): array
|
||
{
|
||
return ['courier_expcode' => 'EXP-001', 'quote_type' => '标准快递报价', 'source_quote' => $this->workbook(),
|
||
'effective_date' => '2025-12-01', 'billing_rules' => '首重1kg,续重按原报价', 'summary' => '补录历史合作报价'];
|
||
}
|
||
|
||
private function workbook(bool $multipleSheets = false, int $price = 5): UploadedFile
|
||
{
|
||
$book = new Spreadsheet();
|
||
$sheet = $book->getActiveSheet()->setTitle('报价');
|
||
$sheet->fromArray([['省份', '首重', '续重'], ['江苏省', $price, null]]);
|
||
$sheet->setCellValue('C2', '=B2/2');
|
||
if ($multipleSheets) {
|
||
$book->createSheet()->setTitle('其他网点')->setCellValue('A1', '其他报价');
|
||
}
|
||
$path = tempnam(sys_get_temp_dir(), 'stock-quote-');
|
||
$this->temporaryFiles[] = $path;
|
||
IOFactory::createWriter($book, 'Xlsx')->save($path);
|
||
$book->disconnectWorksheets();
|
||
|
||
return new UploadedFile($path, '原始报价.xlsx', null, null, true);
|
||
}
|
||
|
||
private function reviewer()
|
||
{
|
||
$user = user_model_class()::create([
|
||
'name' => '报价审核员', 'email' => 'stock-reviewer-' . uniqid() . '@example.com',
|
||
'password' => 'local-test-password', 'locale' => 'zh-CN', 'enabled' => 1,
|
||
'landing_page' => 'dashboard', 'created_from' => 'core::test',
|
||
]);
|
||
$user->companies()->attach($this->company->id);
|
||
$user->attachRole(Role::where('name', 'admin')->firstOrFail());
|
||
$user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||
|
||
return $user;
|
||
}
|
||
}
|