From 125e9ea8aa0218761152b9aef558ef1ac7204efe Mon Sep 17 00:00:00 2001
From: limeng <1131177064@qq.com>
Date: Sat, 17 Oct 2026 12:48:24 +0800
Subject: [PATCH 1/2] =?UTF-8?q?fix:=20=E5=AD=98=E9=87=8F=E6=8A=A5=E4=BB=B7?=
=?UTF-8?q?=E5=AE=A1=E6=A0=B8=E7=94=9F=E6=95=88=E5=B9=B6=E6=94=AF=E6=8C=81?=
=?UTF-8?q?=E7=AD=BE=E7=BD=B2=E5=8E=9F=E4=BB=B6=E9=A2=84=E8=A7=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Business/CourierStockQuoteEntries.php | 2 +-
app/Models/Business/CourierQuoteVersion.php | 14 ++++++++
app/Support/CourierQuoteChangeService.php | 4 +--
app/Support/CourierRequoteService.php | 2 +-
app/Support/QuoteManagementSnapshot.php | 31 +++++++++++++----
.../partials/stock-quote-review.blade.php | 2 +-
.../quote-change-show.blade.php | 2 +-
.../quote-management.blade.php | 11 ++++++
.../stock-quote-entry.blade.php | 2 +-
.../Business/CourierStockQuoteBatchTest.php | 2 +-
.../Business/CourierStockQuoteEntryTest.php | 34 +++++++++++++++++--
11 files changed, 90 insertions(+), 16 deletions(-)
diff --git a/app/Http/Controllers/Business/CourierStockQuoteEntries.php b/app/Http/Controllers/Business/CourierStockQuoteEntries.php
index beb94014..a2c58aca 100644
--- a/app/Http/Controllers/Business/CourierStockQuoteEntries.php
+++ b/app/Http/Controllers/Business/CourierStockQuoteEntries.php
@@ -166,7 +166,7 @@ class CourierStockQuoteEntries extends Controller
$headers = ['Cache-Control' => 'private, no-store', 'X-Content-Type-Options' => 'nosniff'];
if ($preview) {
$mime = $attachment['mime_type'] ?? $disk->mimeType($attachment['path']);
- abort_unless(in_array($mime, ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'], true), 415, '此格式请下载原文件查看。');
+ abort_unless(in_array($mime, ['application/pdf', 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'], true), 415, '此格式请下载原文件查看。');
return $disk->response($attachment['path'], $attachment['name'], [...$headers, 'Content-Type' => $mime]);
}
diff --git a/app/Models/Business/CourierQuoteVersion.php b/app/Models/Business/CourierQuoteVersion.php
index 74916822..05bd7336 100644
--- a/app/Models/Business/CourierQuoteVersion.php
+++ b/app/Models/Business/CourierQuoteVersion.php
@@ -61,6 +61,20 @@ class CourierQuoteVersion extends Model
return $this->status === self::STATUS_VOIDED;
}
+ /** 存量报价审核后按执行日期生效,不以补签或历史签署资料为前置条件。 */
+ public function isApprovedStockQuote(): bool
+ {
+ return $this->status === self::STATUS_APPROVED
+ && $this->change?->status === CourierQuoteChange::STATUS_APPROVED
+ && $this->change->isStockQuote();
+ }
+
+ public function signatureLabel(): string
+ {
+ return $this->isApprovedStockQuote() && !in_array($this->signature_status, ['历史签署资料已归档', '已签署并归档'], true)
+ ? '存量报价,审核生效(无需签章)' : (string) $this->signature_status;
+ }
+
public function change(): BelongsTo
{
return $this->belongsTo(CourierQuoteChange::class, 'source_change_id');
diff --git a/app/Support/CourierQuoteChangeService.php b/app/Support/CourierQuoteChangeService.php
index cd9f578b..7f6d64e1 100644
--- a/app/Support/CourierQuoteChangeService.php
+++ b/app/Support/CourierQuoteChangeService.php
@@ -142,7 +142,7 @@ final class CourierQuoteChangeService
$hasHistoricalFiles = $change->isStockQuote()
&& collect(CourierStockQuoteEntry::query()->findOrFail(data_get($change->quote_payload, 'stock_quote_entry_id'))->files)->contains('kind', 'signed');
if ($change->isStockQuote() && $historicalDocumentsConfirmed && ! $hasHistoricalFiles) {
- throw ValidationException::withMessages(['stock_signature_verified' => '尚未上传历史签署资料,请补充资料或保留待确认/待补签状态。']);
+ throw ValidationException::withMessages(['stock_signature_verified' => '尚未上传历史签署资料,请补充资料或取消历史资料核验;不影响存量报价审核生效。']);
}
$historicalSigned = $hasHistoricalFiles && $historicalDocumentsConfirmed;
$values = $source['values'];
@@ -182,7 +182,7 @@ final class CourierQuoteChangeService
'values_json' => json_encode($values, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
'formulas_json' => json_encode($formulas, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
'status' => 'approved',
- 'signature_status' => $change->isStockQuote() ? ($historicalSigned ? '历史签署资料已归档' : '待确认/待补签') : '未签署',
+ 'signature_status' => $change->isStockQuote() ? ($historicalSigned ? '历史签署资料已归档' : '存量报价,审核生效(无需签章)') : '未签署',
'created_by' => $change->created_by,
'approved_by' => $userId,
]);
diff --git a/app/Support/CourierRequoteService.php b/app/Support/CourierRequoteService.php
index c2ff6478..5bc9dbbb 100644
--- a/app/Support/CourierRequoteService.php
+++ b/app/Support/CourierRequoteService.php
@@ -370,7 +370,7 @@ final class CourierRequoteService
{
return $versions
->filter(static fn (CourierQuoteVersion $version): bool => ! $version->isVoided()
- && self::isSignedVersion($version)
+ && ($version->isApprovedStockQuote() || self::isSignedVersion($version))
&& ($includeTemporary || ! $version->isTemporary())
&& $version->coversDate($date))
->sortBy([
diff --git a/app/Support/QuoteManagementSnapshot.php b/app/Support/QuoteManagementSnapshot.php
index 9a1090cb..cbd3353e 100644
--- a/app/Support/QuoteManagementSnapshot.php
+++ b/app/Support/QuoteManagementSnapshot.php
@@ -165,11 +165,12 @@ final class QuoteManagementSnapshot
'column_count' => $columnCount,
'formula_count' => $this->formulaCount($formulas),
'status' => $this->versionLifecycleStatus($version),
- 'signature_status' => $version->signature_status,
+ 'signature_status' => $version->signatureLabel(),
'values' => $values,
'formulas' => $formulas,
'source_change_id' => $version->source_change_id,
'signed_files' => $this->signedFiles($version),
+ 'stock_originals' => $this->stockOriginals($version),
'signed_at' => $version->signature?->completed_at?->format('Y-m-d H:i')
?? $version->signature?->archived_at?->format('Y-m-d H:i'),
];
@@ -205,6 +206,24 @@ final class QuoteManagementSnapshot
return $files;
}
+ private function stockOriginals(CourierQuoteVersion $version): array
+ {
+ if (!$version->change?->isStockQuote() || !($entry = $version->change->stockEntry)) return [];
+ $files = [];
+ foreach ($entry->files as $index => $file) {
+ if (($file['kind'] ?? '') !== 'signed') continue;
+ // 早期手动上传未记录 MIME;入口按扩展名提供,响应仍按实际 MIME 白名单校验。
+ $previewable = isset($file['mime_type'])
+ ? in_array($file['mime_type'], ['application/pdf', 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp'], true)
+ : in_array(strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)), ['pdf', 'jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'], true);
+ $files[] = [
+ 'name' => $file['name'], 'previewable' => $previewable,
+ 'url' => route('business.courier-stock-quotes.download', [$entry, $index, 'preview' => $previewable ? 1 : 0]),
+ ];
+ }
+ return $files;
+ }
+
private function decodeMatrix(?string $json): array
{
if ($json === null || trim($json) === '') {
@@ -224,7 +243,7 @@ final class QuoteManagementSnapshot
return '已作废';
}
- if (! $this->isSigned($version)) {
+ if (! $this->isExecutable($version)) {
return '待生效(待签章)';
}
@@ -235,10 +254,10 @@ final class QuoteManagementSnapshot
return $version->isTemporary() ? '生效中(临时)' : '生效中';
}
- /** 回签单已归档(以签署记录为准,版本上的文字状态可能滞后)。 */
- private function isSigned(CourierQuoteVersion $version): bool
+ /** 存量报价审核生效;其他报价仍要求回签归档。 */
+ private function isExecutable(CourierQuoteVersion $version): bool
{
- return in_array($version->signature_status, ['已签署并归档', '历史签署资料已归档'], true)
+ return $version->isApprovedStockQuote() || in_array($version->signature_status, ['已签署并归档', '历史签署资料已归档'], true)
|| $version->signature?->status === \App\Models\Business\CourierQuoteSignature::STATUS_ARCHIVED;
}
@@ -261,7 +280,7 @@ final class QuoteManagementSnapshot
: 'name:' . $version->company_name)) as $group) {
$current = CourierRequoteService::resolveAt($group, $today);
$base = CourierRequoteService::resolveAt($group, $today, false);
- $started = $group->filter(fn (CourierQuoteVersion $version): bool => ! $version->isVoided() && $this->isSigned($version)
+ $started = $group->filter(fn (CourierQuoteVersion $version): bool => ! $version->isVoided() && $this->isExecutable($version)
&& $version->effective_date !== null && $version->effective_date->lte($today));
foreach ($started as $version) {
if ($current !== null && $version->id === $current->id) {
diff --git a/resources/views/business/pages/courier-payables/partials/stock-quote-review.blade.php b/resources/views/business/pages/courier-payables/partials/stock-quote-review.blade.php
index 9cb8e9db..7ae666d2 100644
--- a/resources/views/business/pages/courier-payables/partials/stock-quote-review.blade.php
+++ b/resources/views/business/pages/courier-payables/partials/stock-quote-review.blade.php
@@ -22,7 +22,7 @@
@if(collect($stockEntry->files)->contains('kind', 'signed'))
@endif
- 未核验签署依据时,审核后仍为“待确认/待补签”。历史资料归档与本系统电子签署分别记录。
+ 存量报价审核通过后按实际执行日期生效,无需签章。历史签署资料可另行核验归档,不影响报价生效。
@endif
diff --git a/resources/views/business/pages/courier-payables/quote-change-show.blade.php b/resources/views/business/pages/courier-payables/quote-change-show.blade.php
index 3cfe18ba..5230df4e 100644
--- a/resources/views/business/pages/courier-payables/quote-change-show.blade.php
+++ b/resources/views/business/pages/courier-payables/quote-change-show.blade.php
@@ -88,7 +88,7 @@
追溯核算{{ $change->recalculationStatusLabel() }}
{{ $stockEntry ? '录入范围' : '变价明细' }}{{ $stockEntry ? $stockEntry->source_range : ($requoteDiff ? ($requoteDiff['same_structure'] ? count($requoteDiff['cells']) . ' 处价格变动' : '计费方式调整') : $change->items->count() . ' 项') }}
新报价版本{{ $change->version?->version_code ?? '尚未生成' }}
- 签署状态{{ $change->version?->signature_status ?? '未进入签署' }}
+ 签署状态{{ $change->version?->signatureLabel() ?? '未进入签署' }}
变更依据与说明
{{ $change->summary }}
diff --git a/resources/views/business/pages/courier-payables/quote-management.blade.php b/resources/views/business/pages/courier-payables/quote-management.blade.php
index a40cea8c..1f4bd7d2 100644
--- a/resources/views/business/pages/courier-payables/quote-management.blade.php
+++ b/resources/views/business/pages/courier-payables/quote-management.blade.php
@@ -849,6 +849,7 @@
原工作表内容 · 只读
+
@@ -1114,6 +1115,16 @@
setDetail('range', text(quote.source_range, '-'));
setDetail('date', text(quote.period_label || quote.effective_date, 'Excel 未标注'));
setDetail('formulas', String(quote.formula_count));
+ var originals = root.querySelector('[data-stock-original-links]');
+ originals.replaceChildren();
+ (quote.stock_originals || []).forEach(function (file, index, files) {
+ var link = create('a', 'quote-button', (file.previewable ? '预览签署原件' : '下载签署原件') + (files.length > 1 ? ' ' + (index + 1) : ''));
+ link.href = file.url;
+ link.title = file.name;
+ link.target = '_blank';
+ link.rel = 'noopener noreferrer';
+ originals.appendChild(link);
+ });
var signedBox = root.querySelector('[data-quote-signed]');
var files = quote.signed_files || {};
signedBox.hidden = !files.signed;
diff --git a/resources/views/business/pages/courier-payables/stock-quote-entry.blade.php b/resources/views/business/pages/courier-payables/stock-quote-entry.blade.php
index f36cb4d2..4c74ba7b 100644
--- a/resources/views/business/pages/courier-payables/stock-quote-entry.blade.php
+++ b/resources/views/business/pages/courier-payables/stock-quote-entry.blade.php
@@ -69,7 +69,7 @@
-
+
diff --git a/tests/Feature/Business/CourierStockQuoteBatchTest.php b/tests/Feature/Business/CourierStockQuoteBatchTest.php
index 36bb81bd..de14a2e9 100644
--- a/tests/Feature/Business/CourierStockQuoteBatchTest.php
+++ b/tests/Feature/Business/CourierStockQuoteBatchTest.php
@@ -364,7 +364,7 @@ class CourierStockQuoteBatchTest extends FeatureTestCase
$reviewer->companies()->attach($this->company->id);
$changes = app(CourierQuoteChangeService::class);
$approved = $changes->approve($entry->change, (int) $reviewer->id);
- $this->assertSame('待确认/待补签', $approved->version->signature_status);
+ $this->assertSame('存量报价,审核生效(无需签章)', $approved->version->signature_status);
$change = $changes->create([
'source_quote_id' => $approved->version->version_code, 'change_type' => '费率调整', 'effective_date' => '2026-10-16',
'summary' => '基于存量版本调价,原件继续保留',
diff --git a/tests/Feature/Business/CourierStockQuoteEntryTest.php b/tests/Feature/Business/CourierStockQuoteEntryTest.php
index bfae5f2c..24947b53 100644
--- a/tests/Feature/Business/CourierStockQuoteEntryTest.php
+++ b/tests/Feature/Business/CourierStockQuoteEntryTest.php
@@ -75,12 +75,18 @@ class CourierStockQuoteEntryTest extends FeatureTestCase
$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('存量报价,审核生效(无需签章)', $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('生效中', 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
@@ -110,6 +116,30 @@ class CourierStockQuoteEntryTest extends FeatureTestCase
$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();
--
2.47.2
From 6b203bc101281673d1a0852d08219473b804684b Mon Sep 17 00:00:00 2001
From: limeng <1131177064@qq.com>
Date: Sat, 17 Oct 2026 12:48:17 +0800
Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E9=85=8D=E4=BB=B6=E8=A1=A5=E5=8F=91?=
=?UTF-8?q?=E6=89=A3=E6=AC=BE=E6=8E=92=E9=99=A4=E5=A4=96=E9=87=87=E7=BA=BF?=
=?UTF-8?q?=E4=B8=8B=E5=BF=AB=E9=80=92=E5=B9=B6=E6=8F=90=E4=BE=9B=E5=8F=AF?=
=?UTF-8?q?=E6=89=A3=E6=AC=BE=E6=B8=85=E5=8D=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Business/AccessoryReshipDeductions.php | 2 +-
.../AccessoryReshipRateCalculator.php | 11 ++++++++
.../View/AccessoryReshipPageData.php | 13 +++++++---
.../accessory-reship-deductions.blade.php | 12 ++++-----
tests/Unit/AccessoryReshipPageDataTest.php | 26 +++++++++++++++++++
5 files changed, 54 insertions(+), 10 deletions(-)
diff --git a/app/Http/Controllers/Business/AccessoryReshipDeductions.php b/app/Http/Controllers/Business/AccessoryReshipDeductions.php
index a9b8a3b4..95364911 100644
--- a/app/Http/Controllers/Business/AccessoryReshipDeductions.php
+++ b/app/Http/Controllers/Business/AccessoryReshipDeductions.php
@@ -35,7 +35,7 @@ class AccessoryReshipDeductions extends Controller
fwrite($file, "\xEF\xBB\xBF");
fputcsv($file, ['交易单号','补发快递单号','补发快递','补发时间','原始条形码','处理后条形码','补发运费','匹配状态','首发快递公司(候选)','首发快递单号(候选)','首发所属月份(候选)','扣款比例(%)','扣款金额','说明']);
foreach ($data->rows($filters) as $row) {
- if ($filters['match_status'] !== 'all' && $row['status'] !== $filters['match_status']) continue;
+ if (!AccessoryReshipPageData::inCategory($row, $filters['match_status'])) continue;
$values = [$row['trade_no'], $row['express_no'], ($row['express_name1'] ?: ($row['express_name2'] ?? '')), $row['ship_time'], $row['barcode'], $row['normalized_barcode'], $row['freight'], $row['deduction_state'] === 'ready' ? '可扣款' : ($row['status'] === 'matched' ? '已匹配待核对' : '未匹配不扣款'), implode(' / ', array_column($row['matches'], 'courier')), implode(' / ', array_column($row['matches'], 'express_no')), implode(' / ', array_column($row['matches'], 'month')), $row['deduction_rate_basis_points'] === null ? '' : number_format($row['deduction_rate_basis_points'] / 100, 2, '.', '') . '%', $row['deduction_cents'] === null ? '' : number_format($row['deduction_cents'] / 100, 2, '.', ''), $row['deduction_note']];
fputcsv($file, array_map(static function ($value) {
$value = (string) $value;
diff --git a/modules/EcommerceFinance/Services/AccessoryReshipRateCalculator.php b/modules/EcommerceFinance/Services/AccessoryReshipRateCalculator.php
index b5b30e97..5b7d6fcc 100644
--- a/modules/EcommerceFinance/Services/AccessoryReshipRateCalculator.php
+++ b/modules/EcommerceFinance/Services/AccessoryReshipRateCalculator.php
@@ -44,6 +44,13 @@ class AccessoryReshipRateCalculator
$row['deduction_rate_basis_points'] = null;
$row['deduction_state'] = 'unmatched';
$row['deduction_note'] = '三个月内未匹配首发快递单号';
+ $reshipCourier = trim((string) (($row['express_name1'] ?? '') ?: ($row['express_name2'] ?? '')));
+ if (in_array($reshipCourier, ['外采快递', '线下快递'], true)) {
+ $row['deduction_cents'] = 0;
+ $row['deduction_state'] = 'excluded_courier';
+ $row['deduction_note'] = '补发快递为' . $reshipCourier . ',不参与扣款';
+ return $row;
+ }
if ($row['status'] === 'unmatched') {
return $row;
}
@@ -79,6 +86,10 @@ class AccessoryReshipRateCalculator
$row['deduction_cents'] = self::amount($row['freight_cents'], $rate);
$row['deduction_state'] = $rate === 0 ? 'zero_rate' : 'ready';
$row['deduction_note'] = $rate === 0 ? '扣款比例0%,不扣款' : '按所选首发快递配置比例核算,仅计一次';
+ if ($rate > 0 && $row['deduction_cents'] === 0) {
+ $row['deduction_state'] = 'zero_amount';
+ $row['deduction_note'] = '扣款金额为0,不扣款';
+ }
}
return $row;
}
diff --git a/modules/EcommerceFinance/View/AccessoryReshipPageData.php b/modules/EcommerceFinance/View/AccessoryReshipPageData.php
index 0019b53b..1a6fe40f 100644
--- a/modules/EcommerceFinance/View/AccessoryReshipPageData.php
+++ b/modules/EcommerceFinance/View/AccessoryReshipPageData.php
@@ -23,8 +23,8 @@ class AccessoryReshipPageData
{
$month = request()->query('month', now()->format('Y-m'));
abort_unless(is_string($month) && preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $month) && (int) substr($month, 0, 4) >= 2000, 422, '请选择有效业务月份。');
- $status = request()->query('match_status', 'matched');
- $filters = ['month' => $month, 'match_status' => in_array($status, ['matched', 'unmatched', 'all'], true) ? $status : 'matched'];
+ $status = request()->query('match_status', 'ready');
+ $filters = ['month' => $month, 'match_status' => in_array($status, ['ready', 'matched', 'unmatched', 'all'], true) ? $status : 'ready'];
foreach (['trade_no','express_no','courier','ship_date','barcode','normalized_barcode','freight','deduction_state','original_courier','original_express_no','original_month','deduction_amount'] as $field) {
$value = request()->query($field, '');
abort_unless(is_string($value) && mb_strlen($value) <= 200, 422, '筛选条件格式无效。');
@@ -93,7 +93,7 @@ class AccessoryReshipPageData
if ($row['deduction_state'] === 'selection_required') $summary['ambiguous']++;
if (isset($summary[$row['deduction_state']])) $summary[$row['deduction_state']]++;
}
- if ($filters['match_status'] !== 'all' && $filters['match_status'] !== $row['status']) continue;
+ if (!self::inCategory($row, $filters['match_status'])) continue;
if ($total >= ($page - 1) * 50 && count($items) < 50) $items[] = $row;
$total++;
}
@@ -113,4 +113,11 @@ class AccessoryReshipPageData
'reshipRows' => new LengthAwarePaginator($items, $total, 50, $page, ['path' => request()->url(), 'query' => request()->except('page')]),
];
}
+
+ public static function inCategory(array $row, string $category): bool
+ {
+ return $category === 'all' || ($category === 'ready'
+ ? $row['deduction_state'] === 'ready' && $row['deduction_cents'] > 0
+ : $row['status'] === $category);
+ }
}
diff --git a/resources/views/business/pages/courier-payables/accessory-reship-deductions.blade.php b/resources/views/business/pages/courier-payables/accessory-reship-deductions.blade.php
index 13d7f429..ace6ec5d 100644
--- a/resources/views/business/pages/courier-payables/accessory-reship-deductions.blade.php
+++ b/resources/views/business/pages/courier-payables/accessory-reship-deductions.blade.php
@@ -1,7 +1,7 @@
@php
$filters = $reshipFilters ?? [];
$summary = $reshipSummary ?? [];
- $labels = ['matched' => '已匹配项', 'unmatched' => '未匹配项', 'all' => '全部配件单'];
+ $labels = ['ready' => '可扣款', 'matched' => '已匹配项', 'unmatched' => '未匹配项', 'all' => '全部配件单'];
$startMonth = \Carbon\Carbon::createFromFormat('!Y-m', ($filters['month'] ?? now()->format('Y-m')))->subMonths(2)->format('Y年n月');
@endphp
@@ -18,11 +18,11 @@
@foreach (['trade_no'=>'交易单号','express_no'=>'补发快递单号','courier'=>'补发快递','ship_date'=>'补发日期','barcode'=>'原始条形码','normalized_barcode'=>'处理后条形码','freight'=>'补发运费','original_courier'=>'首发快递','original_express_no'=>'首发单号','original_month'=>'所属月份','deduction_amount'=>'可扣金额'] as $field => $label)
@endforeach
-
-
+
+
清空筛选
- @if (!$reshipError)
导出筛选结果@endif
+ @if (!$reshipError)
导出当前列表 导出可扣款清单@endif
匹配范围:{{ $startMonth }} 至 {{ \Carbon\Carbon::createFromFormat('!Y-m', $filters['month'])->format('Y年n月') }}
@@ -31,7 +31,7 @@
@endforeach
按配置比例扣款合计{{ $reshipError ? '—' : '¥ ' . number_format($summary['amount'] / 100, 2) }}
-
匹配规则与扣款口径
配件仓库:常州简尚家居有限公司。配件条码取第一个英文逗号前的内容,再去掉最后一个“-”及其后内容。首发仓库排除该仓库,首发多个条码拆分后按完整条码匹配。仅匹配到首发快递单号的记录列为可扣款,扣款金额=补发运费×首发快递配置比例,按分四舍五入。未配置比例暂不核算,0%不扣款。
本页生成扣款核对清单,尚未记入应付账。首发快递名称相同自动任选一条,无需核对;名称不同请选择需要扣款的快递,仅计一次;运费缺失或异常需核实;重复匹配不会新增扣款记录。
+
匹配规则与扣款口径
配件仓库:常州简尚家居有限公司。配件条码取第一个英文逗号前的内容,再去掉最后一个“-”及其后内容。首发仓库排除该仓库,首发多个条码拆分后按完整条码匹配。补发快递为外采快递或线下快递不参与扣款。可扣款列表仅包含已确定首发归属、已配置比例且扣款金额大于0的记录,扣款金额=补发运费×首发快递配置比例,按分四舍五入。未配置比例暂不核算,0%不扣款。
本页生成扣款核对清单,尚未记入应付账。首发快递名称相同自动任选一条,无需核对;名称不同请选择需要扣款的快递,仅计一次;运费缺失或异常需核实;重复匹配不会新增扣款记录。
@if ($reshipError)
{{ $reshipError }}
@endif
@if ($summary['ambiguous'] || $summary['invalid_amount'] || $summary['missing_config'] || $summary['zero_rate'])
待选择扣款快递 {{ $summary['selection_required'] }} 单;运费异常 {{ $summary['invalid_amount'] }} 单;待配置 {{ $summary['missing_config'] }} 单,0%不扣款 {{ $summary['zero_rate'] }} 单。待核算项不计入合计。
@endif
@@ -44,7 +44,7 @@
@foreach (['交易单号','补发快递单号','补发快递','补发日期','原始条形码','处理后条形码','补发运费','匹配状态','首发快递','首发单号','所属月份','扣款比例','可扣金额','说明'] as $heading){{ $heading }} | @endforeach
@forelse ($reshipRows as $row)
- | {{ $row['trade_no'] }} | {{ $row['express_no'] }} | {{ $row['express_name1'] ?: $row['express_name2'] }} | {{ substr((string) $row['ship_time'], 0, 10) }} | {{ $row['barcode'] }} | {{ $row['normalized_barcode'] }} | {{ $row['freight_cents'] === null ? '待核实' : number_format($row['freight_cents'] / 100, 2) }} | {{ $row['deduction_state'] === 'ready' ? '可扣款' : ($row['status'] === 'matched' ? ($row['deduction_state'] === 'zero_rate' ? '比例0%不扣款' : '待核算') : '未匹配不扣款') }} |
+ |
| {{ $row['trade_no'] }} | {{ $row['express_no'] }} | {{ $row['express_name1'] ?: $row['express_name2'] }} | {{ substr((string) $row['ship_time'], 0, 10) }} | {{ $row['barcode'] }} | {{ $row['normalized_barcode'] }} | {{ $row['freight_cents'] === null ? '待核实' : number_format($row['freight_cents'] / 100, 2) }} | {{ in_array($row['deduction_state'], ['excluded_courier', 'zero_amount'], true) ? '不扣款' : ($row['deduction_state'] === 'ready' ? '可扣款' : ($row['status'] === 'matched' ? ($row['deduction_state'] === 'zero_rate' ? '比例0%不扣款' : '待核算') : '未匹配不扣款')) }} |
@if (count($row['courier_candidates']) > 1)
|