From 3638a8cee949189abe9315effdd9d89f8e55384f Mon Sep 17 00:00:00 2001 From: renshen <1005457301@qq.com> Date: Sat, 17 Oct 2026 13:05:10 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=A7=E6=AC=BE=E5=AF=B9=E8=B4=A6=EF=BC=9A?= =?UTF-8?q?=E5=B7=AE=E5=BC=82=E6=94=AF=E6=8C=81=E5=8B=BE=E9=80=89=E5=A4=9A?= =?UTF-8?q?=E6=9D=A1=E6=8E=A8=E9=80=81=20(#379)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/SupplierReconciliation.php | 26 ++++++- .../finance-data.blade.php | 77 ++++++++++++++++++- .../SupplierReconciliationDingTalkTest.php | 31 ++++++++ 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Business/SupplierReconciliation.php b/app/Http/Controllers/Business/SupplierReconciliation.php index f4f4e14e..fa53c93d 100644 --- a/app/Http/Controllers/Business/SupplierReconciliation.php +++ b/app/Http/Controllers/Business/SupplierReconciliation.php @@ -130,10 +130,12 @@ class SupplierReconciliation extends Controller 'supplier' => ['required', 'string', 'max:191'], 'month' => ['nullable', 'string', 'regex:/^\d{4}-\d{2}$/'], 'reconciler' => ['nullable', 'string', 'max:191'], - 'scope' => ['required', 'in:all,row'], + 'scope' => ['required', 'in:all,row,selected'], 'barcode' => ['nullable', 'string', 'max:191'], 'difference_type' => ['nullable', 'string', 'max:32'], 'unit_price' => ['nullable', 'numeric'], + 'selected_rows' => ['nullable', 'array', 'max:600'], + 'selected_rows.*' => ['string', 'max:2048'], ]); [$supplier, $companyId] = $this->returnTarget($request); $detail = $this->pageData->financeDetailForFilters($supplier, $companyId, $request->all()); @@ -152,6 +154,18 @@ class SupplierReconciliation extends Controller && (string) ($row['type'] ?? '') === $type && abs((float) ($row['unitPrice'] ?? 0) - $unitPrice) < 0.005, )); + } elseif ($request->input('scope') === 'selected') { + $selected = []; + foreach ((array) $request->input('selected_rows', []) as $token) { + $row = json_decode((string) $token, true); + if (is_array($row)) { + $selected[$this->differenceRowKey($row)] = true; + } + } + $differences = array_values(array_filter( + $differences, + fn (array $row): bool => isset($selected[$this->differenceRowKey($row)]), + )); } if ($differences === []) { @@ -196,4 +210,14 @@ class SupplierReconciliation extends Controller return [trim((string) $request->input('supplier')), (int) (company_id() ?: 1)]; } + /** @param array $row */ + private function differenceRowKey(array $row): string + { + return implode("\0", [ + trim((string) ($row['barcode'] ?? '')), + trim((string) ($row['type'] ?? '')), + number_format((float) ($row['unitPrice'] ?? 0), 4, '.', ''), + ]); + } + } diff --git a/resources/views/business/pages/supplier-reconciliation/finance-data.blade.php b/resources/views/business/pages/supplier-reconciliation/finance-data.blade.php index d9e19dd9..07820ac3 100644 --- a/resources/views/business/pages/supplier-reconciliation/finance-data.blade.php +++ b/resources/views/business/pages/supplier-reconciliation/finance-data.blade.php @@ -37,6 +37,8 @@ .supplier-reconciliation-page .sr-robot-config summary { cursor: pointer; color: #5B5343; font-size: .8125rem; font-weight: 600; } .supplier-reconciliation-page .sr-robot-form { display: grid; grid-template-columns: minmax(260px, 2fr) minmax(180px, 1fr) auto; gap: 10px; align-items: end; margin-top: 12px; } .supplier-reconciliation-page .sr-robot-check { display: flex; align-items: center; gap: 6px; min-height: 36px; color: var(--vip-content-muted, #595959); font-size: .75rem; } + .supplier-reconciliation-page .sr-select-cell { width: 34px; text-align: center; } + .supplier-reconciliation-page .sr-select-cell input { width: 15px; height: 15px; accent-color: var(--vip-content-accent-dark, #856632); } .supplier-reconciliation-page .sr-robot-form input[type="url"], .supplier-reconciliation-page .sr-robot-form input[type="password"] { width: 100%; min-width: 0; } @media (max-width: 900px) { @@ -138,6 +140,22 @@ 推送全部差异({{ $differenceCount }} 条) +
+ @csrf + + + + + +
@endif
@@ -231,6 +249,9 @@ + @@ -254,6 +275,18 @@ }; @endphp + @empty - + @endforelse
+ + 比对结果 条码 单价
+ @if ($row['type'] !== '一致') + + @else + — + @endif + {{ $row['type'] }} {{ $row['barcode'] }} @@ -294,7 +327,7 @@
该厂家还没有上传对账单,或我方还没有这家供应商的结算数据。
该厂家还没有上传对账单,或我方还没有这家供应商的结算数据。
@@ -302,3 +335,45 @@ @endif + +@if ($detail && $differenceCount > 0) + +@endif diff --git a/tests/Feature/Business/SupplierReconciliationDingTalkTest.php b/tests/Feature/Business/SupplierReconciliationDingTalkTest.php index 9156bfb1..cf3d6f26 100644 --- a/tests/Feature/Business/SupplierReconciliationDingTalkTest.php +++ b/tests/Feature/Business/SupplierReconciliationDingTalkTest.php @@ -29,6 +29,8 @@ class SupplierReconciliationDingTalkTest extends FeatureTestCase $this->loginAs()->get($this->pageUrl()) ->assertOk() ->assertSeeText('推送全部差异(3 条)') + ->assertSeeText('推送选中(') + ->assertSee('aria-label="选择全部差异"', false) ->assertSeeText('推送本条') ->assertSeeText('厂家钉钉群机器人') ->assertSeeText('未配置,配置后才能推送差异') @@ -140,6 +142,35 @@ class SupplierReconciliationDingTalkTest extends FeatureTestCase }); } + public function test_push_selected_differences_only_sends_checked_rows(): void + { + $this->seedReconciliation(); + $this->saveRobot(); + Http::fake(['*' => Http::response(['errcode' => 0, 'errmsg' => 'ok'])]); + + $this->loginAs() + ->post(route('business.supplier-reconciliation.push-differences'), [ + 'supplier' => '晨丰', + 'month' => '2026-09', + 'reconciler' => '张三', + 'scope' => 'selected', + 'selected_rows' => [ + json_encode(['barcode' => 'B-1', 'type' => '金额不一致', 'unitPrice' => 50]), + json_encode(['barcode' => 'D-1', 'type' => '厂家多', 'unitPrice' => 80]), + ], + ]) + ->assertStatus(302) + ->assertSessionHas('success', '已推送 2 条差异到 晨丰 钉钉群(1 条消息)。'); + + Http::assertSent(function ($request): bool { + $text = (string) ($request['markdown']['text'] ?? ''); + + return str_contains($text, 'B-1') + && str_contains($text, 'D-1') + && ! str_contains($text, '`C-1`'); + }); + } + public function test_push_without_robot_configuration_is_rejected_without_http_call(): void { $this->seedReconciliation();