供应商对账:内部可搜索厂家并隔离厂家名称

This commit is contained in:
苏木 2026-10-20 10:34:51 +08:00
parent 4511f6c71a
commit c7bb212fb9
3 changed files with 86 additions and 7 deletions

View File

@ -170,8 +170,10 @@ class SupplierReconciliationPageData
$supplierNames = array_keys($suppliers);
sort($supplierNames);
$selected = trim((string) request()->query('supplier', ''));
if ($selected === '' || ! in_array($selected, $supplierNames, true)) {
$supplierQuery = trim((string) request()->query('supplier', ''));
$selected = in_array($supplierQuery, $supplierNames, true) ? $supplierQuery : '';
$supplierNotFound = $supplierQuery !== '' && $selected === '';
if ($supplierQuery === '') {
$selected = $supplierNames[0] ?? '';
}
@ -184,6 +186,8 @@ class SupplierReconciliationPageData
return [
'view' => 'business.pages.supplier-reconciliation.supplier-data',
'supplierNames' => $supplierNames,
'supplierQuery' => $supplierQuery,
'supplierNotFound' => $supplierNotFound,
'selectedSupplier' => $selected,
'uploads' => $selected !== '' ? $this->statements->uploads($selected, $companyId) : [],
'statementRows' => array_slice($statementRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE),

View File

@ -63,16 +63,29 @@
<div class="sr-card">
<h2>厂家对账单 · 上传与实时数据</h2>
<form method="GET" class="sr-toolbar" aria-label="选择厂家">
<label class="sr-field"><span>厂家</span>
<select name="supplier" onchange="this.form.submit()">
<form method="GET" class="sr-toolbar" aria-label="查询厂家">
<label class="sr-field"><span>厂家名称</span>
<input type="search"
name="supplier"
value="{{ $supplierQuery !== '' ? $supplierQuery : $selectedSupplier }}"
list="supplier-name-options"
placeholder="输入厂家名称搜索"
autocomplete="off"
required>
<datalist id="supplier-name-options">
@foreach ($supplierNames as $supplier)
<option value="{{ $supplier }}" @selected($selectedSupplier === $supplier)>{{ $supplier }}</option>
<option value="{{ $supplier }}"></option>
@endforeach
</select>
</datalist>
</label>
<button type="submit" class="sr-button is-primary">查询厂家</button>
<a href="{{ $statementTemplateUrl }}" class="sr-button">下载对账单模板</a>
</form>
@if ($supplierNotFound)
<div class="biz-alert biz-alert-error" role="alert">
未找到名为「{{ $supplierQuery }}」的厂家,请输入完整名称并从搜索建议中选择。
</div>
@endif
<p class="sr-hint">
厂家只能按模板上传(表头列名与顺序必须与模板完全一致):快递名称、快递单号、单据编号(采购单号)可以留空;
单价或金额留空时按数量自动补齐(都留空按 0,未定价行可直接导出上传);其余列必填;

View File

@ -155,10 +155,48 @@ class SupplierReconciliationTest extends FeatureTestCase
}
}
public function test_supplier_page_uses_a_searchable_exact_supplier_picker(): void
{
$this->seedStatement('晨丰', [
$this->statementRow('ZSJ-1', '128.00', '2', '256.00'),
]);
$this->seedStatement('鸣飞家具', [
array_merge($this->statementRow('SJ-1', '96.00', '1', '96.00'), ['supplier' => '鸣飞家具']),
]);
$pageUrl = route('business.pages.show', [
'company_id' => $this->company->id,
'section' => 'supplier-reconciliation',
'page' => 'supplier-data',
]);
$this->loginAs()->get($pageUrl)
->assertOk()
->assertSee('name="supplier"', false)
->assertSee('list="supplier-name-options"', false)
->assertSeeText('查询厂家')
->assertSee('value="晨丰"', false)
->assertSee('value="鸣飞家具"', false);
$this->loginAs()->get($pageUrl . '?supplier=' . urlencode('鸣飞家具'))
->assertOk()
->assertSeeText('鸣飞家具 · 上传记录')
->assertDontSeeText('晨丰 · 上传记录');
$this->loginAs()->get($pageUrl . '?supplier=' . urlencode('不存在的厂家'))
->assertOk()
->assertSeeText('未找到名为「不存在的厂家」的厂家')
->assertDontSeeText('晨丰 · 上传记录')
->assertDontSeeText('鸣飞家具 · 上传记录');
}
public function test_vendor_portal_shows_only_all_returns_for_the_logged_in_supplier(): void
{
$store = app(SupplierPayableOverrideStore::class);
$companyId = (int) $this->company->id;
$this->seedStatement('鸣飞家具', [
array_merge($this->statementRow('OTHER-SUPPLIER', '100.00', '1', '100.00'), ['supplier' => '鸣飞家具']),
]);
for ($i = 1; $i <= 20; $i++) {
$store->save('final:supplier:return-reconciliation', sprintf('portal:%02d', $i), [
'summary_month' => '2026-09',
@ -198,6 +236,8 @@ class SupplierReconciliationTest extends FeatureTestCase
->assertSeeText('PORTAL-01')
->assertDontSeeText('PORTAL-20')
->assertDontSeeText('OTHER-SUPPLIER-RETURN')
->assertDontSeeText('鸣飞家具')
->assertDontSee('supplier-name-options', false)
->assertDontSeeText('预估单价');
$this->loginAs($vendor)->get($portalUrl . '?return_page=2')
@ -283,6 +323,28 @@ class SupplierReconciliationTest extends FeatureTestCase
$this->assertCount(0, app(SupplierStatementStore::class)->rows('鸣飞家具', (int) $this->company->id));
}
public function test_vendor_portal_rejects_a_statement_for_another_supplier(): void
{
$this->withExceptionHandling();
$vendor = $this->createVendorPortalUser('晨丰');
$file = UploadedFile::fake()->createWithContent(
'statement.csv',
"\xEF\xBB\xBF" . self::HEADER . "\n"
. ",,PORTAL-WRONG-SUPPLIER,2026-09-30,鸣飞家具,ZSJ-1,规格甲,2,128.00,256.00\n",
);
$this->loginAs($vendor)
->post(route('portal.supplier-reconciliation.statement.upload', ['company_id' => $this->company->id]), [
'import' => $file,
])
->assertSessionHasErrors([
'import' => '第 2 行:供应商必须填写「晨丰」,不能提交其他厂家的数据。',
]);
$this->assertCount(0, app(SupplierStatementStore::class)->rows('晨丰', (int) $this->company->id));
$this->assertCount(0, app(SupplierStatementStore::class)->rows('鸣飞家具', (int) $this->company->id));
}
public function test_return_bookkeeping_note_is_identical_on_finance_and_supplier_views(): void
{
$companyId = (int) $this->company->id;