From e0b7a66d3704d8fa0b2711e00a0e384a25f36c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E6=9C=A8?= <594326199@qq.com> Date: Thu, 29 Oct 2026 16:25:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E5=94=AE=E5=90=8E?= =?UTF-8?q?=E9=80=80=E8=B4=A7=E9=80=80=E6=AC=BE=E6=9F=A5=E8=AF=A2=E4=B8=8E?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=20Co-authored-by:=20=E8=8B=8F=E6=9C=A8=20<59?= =?UTF-8?q?4326199@qq.com>=20Co-committed-by:=20=E8=8B=8F=E6=9C=A8=20<5943?= =?UTF-8?q?26199@qq.com>?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Business/Pages.php | 75 +++++++++++++++---- .../NonJingxiRefundMirrorRepository.php | 14 ++-- .../other/after-sales-return-refund.blade.php | 3 +- .../refunds/non-jingxi-refund-data.blade.php | 26 ++++++- routes/admin.php | 4 + .../Business/NonJingxiRefundPageTest.php | 26 +++++-- tests/Feature/Business/PagesTest.php | 5 +- tests/Feature/Business/WarningPageTest.php | 54 +++++++++++++ .../NonJingxiRefundMirrorRepositoryTest.php | 24 ++++-- 9 files changed, 192 insertions(+), 39 deletions(-) diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 5bbdc7d1..1e95b163 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -6992,16 +6992,37 @@ class Pages extends Controller 'refundPageSlug' => 'after-sales-return-refund', 'refundPageLabel' => '售后退货退款预警', 'refundWarningMode' => true, - 'refundShowExport' => false, + 'refundShowExport' => true, + 'refundExportRouteName' => 'business.other.after-sales-return-refund.export', ]); } public function exportNonJingxiRefunds(Request $request) + { + return $this->exportRefundRows( + $request, + 'refunds', + 'non-jingxi-refund-data', + 'after_sales_refunds', + ); + } + + public function exportAfterSalesReturnRefunds(Request $request) + { + return $this->exportRefundRows( + $request, + 'other', + 'after-sales-return-refund', + 'after_sales_refund_warning', + ); + } + + private function exportRefundRows(Request $request, string $section, string $page, string $filenamePrefix) { $filters = $this->nonJingxiRefundFilters($request); $redirectParameters = array_filter([ - 'section' => 'refunds', - 'page' => 'non-jingxi-refund-data', + 'section' => $section, + 'page' => $page, ...$filters, ], static fn (mixed $value): bool => $value !== '' && $value !== null); @@ -7026,6 +7047,7 @@ class Pages extends Controller $filters, $redirectParameters, $request->query('columns'), + $filenamePrefix, ); } catch (Throwable $exception) { report($exception); @@ -7035,7 +7057,7 @@ class Pages extends Controller } } - /** @return array{from:string,to:string,order_no:string,shop_name:string,sku_id:string} */ + /** @return array{from:string,to:string,order_no:string,waybill_no:string,shop_name:string,sku_id:string} */ private function nonJingxiRefundFilters( Request $request, ?string $defaultFrom = null, @@ -7053,20 +7075,28 @@ class Pages extends Controller 'from' => max($from, '2026-01-01'), 'to' => $to, 'order_no' => mb_substr(trim((string) $request->query('order_no', '')), 0, 100), + 'waybill_no' => mb_substr(trim((string) $request->query('waybill_no', '')), 0, 100), 'shop_name' => mb_substr(trim((string) $request->query('shop_name', '')), 0, 191), 'sku_id' => mb_substr(trim((string) $request->query('sku_id', '')), 0, 100), ]; } - private function streamNonJingxiRefundCsv(iterable $rows, array $filters, array $redirectParameters, mixed $requestedFields = null) + private function streamNonJingxiRefundCsv( + iterable $rows, + array $filters, + array $redirectParameters, + mixed $requestedFields = null, + string $filenamePrefix = 'after_sales_refunds', + ) { $columns = [ ['label' => '序号/主键ID', 'field' => 'Id', 'identifier' => true], - ['label' => '退款日期', 'field' => 'refundDate'], + ['label' => '退款日期', 'field' => 'refundDate', 'date' => true], ['label' => '业务来源', 'field' => 'dataSource'], ['label' => '商品明细', 'field' => 'detailStatus'], ['label' => '售后单ID', 'field' => 'as_id', 'identifier' => true], ['label' => '外部售后单号', 'field' => 'outer_as_id', 'identifier' => true], + ['label' => '运单号', 'field' => 'waybillNo', 'identifier' => true], ['label' => '订单ID', 'field' => 'o_id', 'identifier' => true], ['label' => '', 'field' => ''], ['label' => '子订单ID', 'field' => 'so_id', 'identifier' => true], @@ -7091,11 +7121,11 @@ class Pages extends Controller ['label' => '退款商品成本', 'field' => 'refundCostGoods', 'number' => true], ['label' => '退款回款金额', 'field' => 'RefundMoneyIncome', 'number' => true], ['label' => '售后金额', 'field' => 'AfSalesMoney', 'number' => true], - ['label' => '创建时间', 'field' => 'createTime'], + ['label' => '创建时间', 'field' => 'createTime', 'date' => true], ['label' => '状态', 'field' => 'status'], ['label' => '店铺状态', 'field' => 'shop_status'], - ['label' => '创建记录/创建时间', 'field' => 'created'], - ['label' => '确认日期', 'field' => 'confirm_date'], + ['label' => '创建记录/创建时间', 'field' => 'created', 'date' => true], + ['label' => '确认日期', 'field' => 'confirm_date', 'date' => true], ]; $columns = $this->filterExportColumns($columns, $requestedFields); @@ -7125,11 +7155,13 @@ class Pages extends Controller fputcsv($csv, array_map( fn (array $column): mixed => $column['field'] === '' ? '' - : (($column['number'] ?? false) - ? data_get($row, $column['field']) - : (($column['identifier'] ?? false) - ? $this->csvIdentifier(data_get($row, $column['field'])) - : $this->csvText(data_get($row, $column['field'])))), + : (($column['date'] ?? false) + ? $this->refundDateText(data_get($row, $column['field'])) + : (($column['number'] ?? false) + ? data_get($row, $column['field']) + : (($column['identifier'] ?? false) + ? $this->csvIdentifier(data_get($row, $column['field'])) + : $this->csvText(data_get($row, $column['field']))))), $columns, )); if (++$written % 1000 === 0) { @@ -7143,13 +7175,26 @@ class Pages extends Controller } finally { fclose($csv); } - }, 'after_sales_refunds_' . $filters['from'] . '_' . $filters['to'] . '.csv', [ + }, $filenamePrefix . '_' . $filters['from'] . '_' . $filters['to'] . '.csv', [ 'Content-Type' => 'text/csv; charset=UTF-8', 'Cache-Control' => 'private, no-store', 'X-Accel-Buffering' => 'no', ]); } + private function refundDateText(mixed $value): ?string + { + if ($value === null || $value === '') { + return null; + } + + try { + return Carbon::parse((string) $value)->format('Y-m-d H:i:s'); + } catch (Throwable) { + return (string) $value; + } + } + protected function contentForSmallPaymentWorkOrders(): array { $request = request(); diff --git a/modules/EcommerceFinance/Repositories/NonJingxiRefundMirrorRepository.php b/modules/EcommerceFinance/Repositories/NonJingxiRefundMirrorRepository.php index 01be7a0c..b5d128dc 100644 --- a/modules/EcommerceFinance/Repositories/NonJingxiRefundMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/NonJingxiRefundMirrorRepository.php @@ -154,7 +154,7 @@ class NonJingxiRefundMirrorRepository } /** - * @param array{from:string,to:string,order_no:string,shop_name:string,sku_id:string} $filters + * @param array{from:string,to:string,order_no:string,waybill_no:string,shop_name:string,sku_id:string} $filters * @return array{data:list,total:int} */ public function paginate(array $filters, int $page, int $perPage): array @@ -177,7 +177,7 @@ class NonJingxiRefundMirrorRepository * Export all rows matching the supplied filters in the same order and * shape as the paginated page data, without loading the full result set. * - * @param array{from:string,to:string,order_no:string,shop_name:string,sku_id:string} $filters + * @param array{from:string,to:string,order_no:string,waybill_no:string,shop_name:string,sku_id:string} $filters * @return iterable */ public function exportRows(array $filters, int $chunkSize = 1000): iterable @@ -204,7 +204,7 @@ class NonJingxiRefundMirrorRepository private function selectColumns(): array { return [ - 'r.as_date as refundDate', 'r.as_id', 'r.outer_as_id', + 'r.as_date as refundDate', 'r.as_id', 'r.outer_as_id', 'r.l_id as waybillNo', 'r.o_id', 'r.so_id', 'r.type', 'r.node', 'r.shop_type', 'r.shop_name', 'r.shop_site as platform', 'r.refund as totalRefundMoney', 'i.outer_oi_id', 'i.shop_sku_id', 'i.i_id', 'i.sku_id', 'i.qty', @@ -389,7 +389,7 @@ class NonJingxiRefundMirrorRepository }); if ($supplement) { $query->select([ - 'r.as_date as refundDate', 'r.as_id', 'r.outer_as_id', + 'r.as_date as refundDate', 'r.as_id', 'r.outer_as_id', 'r.l_id as waybillNo', 'r.o_id', 'r.so_id', 'r.type', 'r.node', 'r.shop_type', 'r.shop_name', 'r.refund as totalRefundMoney', 'i.outer_oi_id', 'i.shop_sku_id', 'i.i_id', 'i.sku_id', 'i.qty', 'i.shop_amount as detailRefundMoney', 'i.type as refundType', @@ -434,7 +434,7 @@ class NonJingxiRefundMirrorRepository return implode('|', [$row->_source_rank, $row->_head_id, $row->_item_id, $row->_source_id]); } - /** @param array{from:string,to:string,order_no:string,shop_name:string,sku_id:string} $filters */ + /** @param array{from:string,to:string,order_no:string,waybill_no:string,shop_name:string,sku_id:string} $filters */ private function applyFilters(Builder $query, array $filters): void { $query->where('r.as_date', '>=', $filters['from'] . ' 00:00:00') @@ -443,6 +443,10 @@ class NonJingxiRefundMirrorRepository if ($filters['shop_name'] !== '') { $query->whereRaw("r.shop_name LIKE ? ESCAPE '!'", [$this->contains($filters['shop_name'])]); } + + if ($filters['waybill_no'] !== '') { + $query->where('r.l_id', trim($filters['waybill_no'])); + } } private function applySkuFilter(Builder $query, string $value): void diff --git a/resources/views/business/pages/other/after-sales-return-refund.blade.php b/resources/views/business/pages/other/after-sales-return-refund.blade.php index 407043b5..bad69aa9 100644 --- a/resources/views/business/pages/other/after-sales-return-refund.blade.php +++ b/resources/views/business/pages/other/after-sales-return-refund.blade.php @@ -3,5 +3,6 @@ 'refundPageSlug' => 'after-sales-return-refund', 'refundPageLabel' => '售后退货退款预警', 'refundWarningMode' => true, - 'refundShowExport' => false, + 'refundShowExport' => true, + 'refundExportRouteName' => 'business.other.after-sales-return-refund.export', ]) diff --git a/resources/views/business/pages/refunds/non-jingxi-refund-data.blade.php b/resources/views/business/pages/refunds/non-jingxi-refund-data.blade.php index a18eeb74..1c08a26c 100644 --- a/resources/views/business/pages/refunds/non-jingxi-refund-data.blade.php +++ b/resources/views/business/pages/refunds/non-jingxi-refund-data.blade.php @@ -6,6 +6,7 @@ ['label' => '商品明细', 'field' => 'detailStatus'], ['label' => '售后单ID', 'field' => 'as_id'], ['label' => '外部售后单号', 'field' => 'outer_as_id'], + ['label' => '运单号', 'field' => 'waybillNo'], ['label' => '订单ID', 'field' => 'o_id'], ['label' => '', 'field' => ''], ['label' => '子订单ID', 'field' => 'so_id'], @@ -53,10 +54,24 @@ $refundPageLabel = $refundPageLabel ?? '售后(退货退款)'; $refundWarningMode = $refundWarningMode ?? false; $refundShowExport = $refundShowExport ?? true; + $refundExportRouteName = $refundExportRouteName ?? 'business.refunds.non-jingxi-refund-data.export'; $refundPageUrl = route('business.pages.show', [ 'section' => $refundPageSection, 'page' => $refundPageSlug, ]); + $refundExportUrl = route($refundExportRouteName); + $refundDateFields = ['refundDate', 'createTime', 'created', 'confirm_date']; + $formatRefundDate = static function (mixed $value): mixed { + if ($value === null || $value === '') { + return null; + } + + try { + return \Illuminate\Support\Carbon::parse((string) $value)->format('Y-m-d H:i:s'); + } catch (\Throwable) { + return $value; + } + }; @endphp
订单号
+
+ + +
@@ -189,7 +208,7 @@ total() === 0 || $refundError || $refundPending || $refundIndexPending) aria-disabled="true" @endif > @@ -205,7 +224,7 @@ class="upstream-data-table refund-data-table refund-data-table--wide" aria-label="{{ $refundPageLabel }}" @if ($refundShowExport) - data-finance-export-base="{{ route('business.refunds.non-jingxi-refund-data.export') }}" + data-finance-export-base="{{ $refundExportUrl }}" data-finance-export-query="{{ json_encode($filters, JSON_UNESCAPED_UNICODE) }}" data-finance-export-disabled="{{ $rows->total() === 0 || $refundError || $refundPending || $refundIndexPending ? 'true' : 'false' }}" @endif @@ -229,6 +248,9 @@ @php $field = $column['field']; $value = $field === '' ? null : data_get($row, $field); + if (in_array($field, $refundDateFields, true)) { + $value = $formatRefundDate($value); + } $isNumber = (bool) ($column['number'] ?? false); @endphp $isNumber])> diff --git a/routes/admin.php b/routes/admin.php index 22ca39f2..36fe3972 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -475,6 +475,10 @@ Route::get('workspace/refunds/non-jingxi-refund-data/export', 'Business\Pages@ex ->middleware('business.page:refunds,non-jingxi-refund-data') ->name('business.refunds.non-jingxi-refund-data.export'); +Route::get('workspace/other/after-sales-return-refund/export', 'Business\Pages@exportAfterSalesReturnRefunds') + ->middleware('business.page:other,after-sales-return-refund') + ->name('business.other.after-sales-return-refund.export'); + Route::get('workspace/financial-operations/small-payment-work-orders/export', 'Business\Pages@exportSmallPaymentWorkOrders') ->middleware('business.page:financial-operations,small-payment-work-orders') ->name('business.financial-operations.small-payment-work-orders.export'); diff --git a/tests/Feature/Business/NonJingxiRefundPageTest.php b/tests/Feature/Business/NonJingxiRefundPageTest.php index 9e5ee825..94052a57 100644 --- a/tests/Feature/Business/NonJingxiRefundPageTest.php +++ b/tests/Feature/Business/NonJingxiRefundPageTest.php @@ -39,15 +39,17 @@ class NonJingxiRefundPageTest extends FeatureTestCase 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', + 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ] && $page === 2 && $limit === 25 )->andReturn([ 'data' => [(object) [ 'Id' => 102, - 'refundDate' => '2026-08-20 10:00:00', + 'refundDate' => '2026-08-20 10:00:00.123456', 'as_id' => 2002, 'outer_as_id' => 'AS-2002', + 'waybillNo' => 'WB-2002', 'o_id' => 3002, 'so_id' => 'ORDER-1', 'type' => '退款', @@ -86,7 +88,7 @@ class NonJingxiRefundPageTest extends FeatureTestCase 'section' => 'refunds', 'page' => 'non-jingxi-refund-data', ]) . '?' . http_build_query([ - 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', + 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', 'page' => 2, ]); @@ -97,6 +99,9 @@ class NonJingxiRefundPageTest extends FeatureTestCase ->assertSee('data-sync-state="syncing"', false) ->assertSeeText('业务镜像同步中,当前显示最近成功数据') ->assertSeeText('AS-2002') + ->assertSeeText('WB-2002') + ->assertSeeText('2026-08-20 10:00:00') + ->assertDontSee('2026-08-20 10:00:00.123456', false) ->assertSeeText('业务来源') ->assertSeeText('商品明细') ->assertSeeText('SKU-1') @@ -108,11 +113,13 @@ class NonJingxiRefundPageTest extends FeatureTestCase ->assertSee('data-finance-export-base=', false) ->assertSee('data-export-field="refundDate"', false) ->assertSee('value="2026-08-01"', false) - ->assertSee('value="ORDER-1"', false); + ->assertSee('value="ORDER-1"', false) + ->assertSee('name="waybill_no"', false) + ->assertSee('value="WB-2002"', false); $this->assertSame(1, preg_match('/data-finance-export-query="([^"]+)"/', $response->getContent(), $exportQuery)); $this->assertSame([ - 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', + 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ], json_decode(html_entity_decode($exportQuery[1], ENT_QUOTES | ENT_HTML5, 'UTF-8'), true, 512, JSON_THROW_ON_ERROR)); } @@ -128,13 +135,15 @@ class NonJingxiRefundPageTest extends FeatureTestCase 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', + 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ])->andReturn([(object) [ 'Id' => 102, - 'refundDate' => '2026-08-20 10:00:00', + 'refundDate' => '2026-08-20 10:00:00.123456', 'as_id' => 2002, 'outer_as_id' => 'AS-2002', + 'waybillNo' => 'WB-2002', 'o_id' => 3002, 'so_id' => 'ORDER-1', 'type' => '退款', @@ -170,6 +179,7 @@ class NonJingxiRefundPageTest extends FeatureTestCase 'from' => '2026-08-01', 'to' => '2026-08-21', 'order_no' => 'ORDER-1', + 'waybill_no' => 'WB-2002', 'shop_name' => '测试店', 'sku_id' => 'SKU-1', ])); @@ -179,6 +189,9 @@ class NonJingxiRefundPageTest extends FeatureTestCase $this->assertStringStartsWith("\xEF\xBB\xBF", $content); $this->assertStringContainsString('序号/主键ID', $content); $this->assertStringContainsString('AS-2002', $content); + $this->assertStringContainsString('WB-2002', $content); + $this->assertStringContainsString('2026-08-20 10:00:00', $content); + $this->assertStringNotContainsString('.123456', $content); $this->assertStringContainsString('99.5', $content); } @@ -204,7 +217,8 @@ class NonJingxiRefundPageTest extends FeatureTestCase $content = $response->streamedContent(); $header = strtok($content, "\r\n"); $this->assertSame("\xEF\xBB\xBF店铺名称,退款日期", $header); - $this->assertStringContainsString('测试店,2026-08-20', $content); + $lines = preg_split('/\r?\n/', trim($content)); + $this->assertSame(['测试店', '2026-08-20 00:00:00'], str_getcsv($lines[1])); $this->assertStringNotContainsString('SKU编码', $content); } diff --git a/tests/Feature/Business/PagesTest.php b/tests/Feature/Business/PagesTest.php index 68b21e82..53dfe66b 100644 --- a/tests/Feature/Business/PagesTest.php +++ b/tests/Feature/Business/PagesTest.php @@ -1066,6 +1066,7 @@ class PagesTest extends FeatureTestCase 'from' => '2026-08-24', 'to' => '2026-08-24', 'order_no' => '', + 'waybill_no' => '', 'shop_name' => '天猫旗舰店', 'sku_id' => '', ] && $page === 1 && $limit === 10 @@ -1187,7 +1188,7 @@ class PagesTest extends FeatureTestCase }); $this->assertSame([ - '序号/主键ID', '退款日期', '业务来源', '商品明细', '售后单ID', '外部售后单号', '订单ID', '', + '序号/主键ID', '退款日期', '业务来源', '商品明细', '售后单ID', '外部售后单号', '运单号', '订单ID', '', '子订单ID', '类型', '节点', '是否自营', '店铺名称', '平台', '责任部门', '退款总金额', '原始邮编', '店铺商品ID', '店铺SKU编码', '商品ID', 'SKU编码', '买家实付金额', '数量', '商品单位成本', '明细退款金额', '退款类型', @@ -1196,7 +1197,7 @@ class PagesTest extends FeatureTestCase ], $headers->get('labels')); $this->assertSame([ - 'Id', 'refundDate', 'dataSource', 'detailStatus', 'as_id', 'outer_as_id', 'o_id', '', 'so_id', 'type', + 'Id', 'refundDate', 'dataSource', 'detailStatus', 'as_id', 'outer_as_id', 'waybillNo', 'o_id', '', 'so_id', 'type', 'node', 'isZC', 'shop_name', 'platform', 'responsibleDepartment', 'totalRefundMoney', 'originPostcode', 'shop_i_id', 'shop_sku_id', 'i_id', 'sku_id', 'buyer_paid_amount', 'qty', 'unitCostGoods', 'detailRefundMoney', diff --git a/tests/Feature/Business/WarningPageTest.php b/tests/Feature/Business/WarningPageTest.php index 2d34e5e4..41b290f5 100644 --- a/tests/Feature/Business/WarningPageTest.php +++ b/tests/Feature/Business/WarningPageTest.php @@ -69,10 +69,14 @@ class WarningPageTest extends FeatureTestCase ->assertSeeText('售后退货退款') ->assertSeeText('聚水潭售后数据已接入') ->assertSeeText('AS-WARNING-001') + ->assertSeeText('WB-WARNING-001') ->assertSeeText('售后测试店') ->assertSeeText('退款总金额') + ->assertSeeText('运单号') ->assertSee('data-refund-page="after-sales-warning"', false) ->assertSee('action="' . $afterSalesRefundUrl . '"', false) + ->assertSee('name="waybill_no"', false) + ->assertSee(route('business.other.after-sales-return-refund.export'), false) ->assertDontSee(route('business.refunds.non-jingxi-refund-data.export'), false) ->assertDontSeeText('售后退货退款预警规则待接入'); } @@ -108,6 +112,55 @@ class WarningPageTest extends FeatureTestCase ->assertSeeText('AS-WARNING-001'); } + public function test_warning_page_exports_filtered_rows_without_refund_page_permission(): void + { + $regular = $this->regularAccount(); + $subject = 'user_' . $regular->id; + setting()->set('system.sidebar_permissions', [ + 'configured' => [$subject], + 'subjects' => [$subject => ['other/warnings']], + ]); + + $filters = [ + 'from' => '2026-10-23', + 'to' => '2026-10-29', + 'order_no' => 'ORDER-WARNING-001', + 'waybill_no' => 'WB-WARNING-001', + 'shop_name' => '售后测试店', + 'sku_id' => 'SKU-WARNING-001', + ]; + $repository = \Mockery::mock(NonJingxiRefundMirrorRepository::class); + $repository->shouldReceive('available', 'ready', 'queryReady')->andReturnTrue(); + $repository->shouldReceive('exportRows')->once()->with($filters)->andReturn([(object) [ + 'Id' => 9001, + 'refundDate' => '2026-10-28 10:00:00.123456', + 'waybillNo' => 'WB-WARNING-001', + 'outer_as_id' => 'AS-WARNING-001', + 'shop_name' => '售后测试店', + 'sku_id' => 'SKU-WARNING-001', + ]]); + $this->app->instance(NonJingxiRefundMirrorRepository::class, $repository); + + $response = $this->loginAs($regular)->get(route( + 'business.other.after-sales-return-refund.export', + $filters + ['columns' => 'refundDate,waybillNo,outer_as_id,shop_name,sku_id'], + )); + + $response->assertOk()->assertDownload( + 'after_sales_refund_warning_2026-10-23_2026-10-29.csv' + ); + $content = $response->streamedContent(); + $lines = preg_split('/\r?\n/', trim($content)); + $this->assertSame( + ['退款日期', '运单号', '外部售后单号', '店铺名称', 'SKU编码'], + str_getcsv(ltrim($lines[0], "\xEF\xBB\xBF")), + ); + $this->assertSame( + ['2026-10-28 10:00:00', "'WB-WARNING-001", "'AS-WARNING-001", '售后测试店', "'SKU-WARNING-001"], + str_getcsv($lines[1]), + ); + } + private function regularAccount(): User { $name = 'fixture-warning-page-' . User::count(); @@ -155,6 +208,7 @@ class WarningPageTest extends FeatureTestCase 'detailStatus' => '已关联', 'as_id' => 8001, 'outer_as_id' => 'AS-WARNING-001', + 'waybillNo' => 'WB-WARNING-001', 'o_id' => 'ORDER-WARNING-001', 'so_id' => 'SUB-WARNING-001', 'type' => '退货退款', diff --git a/tests/Unit/EcommerceFinance/NonJingxiRefundMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/NonJingxiRefundMirrorRepositoryTest.php index 7e5d7c61..8aec6316 100644 --- a/tests/Unit/EcommerceFinance/NonJingxiRefundMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/NonJingxiRefundMirrorRepositoryTest.php @@ -69,11 +69,12 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase ['Id' => 2, 'erpShopName' => '二号店', 'responsibleDepartment' => '部门乙'], ]); - $filters = ['from' => '2026-08-18', 'to' => '2026-08-21', 'order_no' => '', 'shop_name' => '', 'sku_id' => '']; + $filters = ['from' => '2026-08-18', 'to' => '2026-08-21', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '']; $page2 = $this->repository->paginate($filters, 2, 1); $this->assertSame(2, $page2['total']); $this->assertSame(11, $page2['data'][0]->Id); + $this->assertSame('WB-1', $page2['data'][0]->waybillNo); $this->assertSame('ZIP-1', $page2['data'][0]->originPostcode); $this->assertSame('ITEM-1', $page2['data'][0]->shop_i_id); $this->assertSame(88, $page2['data'][0]->buyer_paid_amount); @@ -92,8 +93,11 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase $filters['sku_id'] = ''; $filters['order_no'] = '8902'; $this->assertSame(1, $this->repository->paginate($filters, 1, 25)['total']); - $filters['order_no'] = ''; + $filters['waybill_no'] = 'WB-1'; + $this->assertSame(1, $this->repository->paginate($filters, 1, 25)['total']); + + $filters['waybill_no'] = ''; $exportRows = iterator_to_array($this->repository->exportRows($filters, 1)); $this->assertCount(2, $exportRows); $this->assertSame(12, $exportRows[0]->Id); @@ -137,7 +141,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase ['Id' => 2, 'shop_id' => 'SHOP-1', 'shop_site' => '天猫'], ['Id' => 3, 'shop_id' => 'JINGXI', 'shop_site' => '京喜'], ]); - $filters = ['from' => '2026-06-01', 'to' => '2026-07-31', 'order_no' => '', 'shop_name' => '', 'sku_id' => '']; + $filters = ['from' => '2026-06-01', 'to' => '2026-07-31', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '']; $result = $this->repository->paginate($filters, 1, 25); $this->assertSame(3, $result['total']); $this->assertSame(['202', '201', '101'], array_map(fn ($r) => (string) $r->as_id, $result['data'])); @@ -176,7 +180,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase $differentShop['as_id'] = '203'; $differentShop['shop_id'] = 'SHOP-2'; $db->table('ops_jst_qmcust_refunds')->insert([$sameId, $sameExternal, $differentShop]); - $filters = ['from' => '2026-07-01', 'to' => '2026-07-31', 'order_no' => '', 'shop_name' => '', 'sku_id' => '']; + $filters = ['from' => '2026-07-01', 'to' => '2026-07-31', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '']; $result = $this->repository->paginate($filters, 1, 25); $this->assertSame(2, $result['total']); $this->assertEqualsCanonicalizing(['101', '203'], array_map(fn ($r) => (string) $r->as_id, $result['data'])); @@ -203,7 +207,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase ['asi_id' => 501, 'as_id' => '202', 'sku_id' => 'MATCH'], ['asi_id' => 502, 'as_id' => '202', 'sku_id' => 'MATCH'], ]); - $filters = ['from' => '2026-07-01', 'to' => '2026-07-31', 'order_no' => '', 'shop_name' => '', 'sku_id' => '']; + $filters = ['from' => '2026-07-01', 'to' => '2026-07-31', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '']; $expected = ['202:502', '202:501', '201:201', '102:14', '102:13', '102:12', '101:11']; $identity = fn ($r) => $r->as_id . ':' . $r->Id; $this->assertSame($expected, array_map($identity, iterator_to_array($this->repository->exportRows($filters, 1)))); @@ -235,7 +239,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase foreach (array_chunk($headers, 100) as $chunk) { DB::connection('business_mirror')->table('ops_jst_return_refund_info')->insert($chunk); } - $filters = ['from' => '2026-07-01', 'to' => '2026-07-01', 'order_no' => '', 'shop_name' => '', 'sku_id' => '']; + $filters = ['from' => '2026-07-01', 'to' => '2026-07-01', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => '']; $rows = iterator_to_array($this->repository->exportRows($filters)); $this->assertSame(range(11001, 10001), array_map(static fn ($row): int => (int) $row->as_id, $rows)); @@ -253,7 +257,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('business_mirror_dataset_not_ready'); $this->repository->paginate( - ['from' => '2026-08-18', 'to' => '2026-08-21', 'order_no' => '', 'shop_name' => '', 'sku_id' => ''], + ['from' => '2026-08-18', 'to' => '2026-08-21', 'order_no' => '', 'waybill_no' => '', 'shop_name' => '', 'sku_id' => ''], 1, 25, ); @@ -294,6 +298,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase $table->integer('Id'); $table->integer('as_id'); $table->dateTime('as_date'); $table->string('outer_as_id')->nullable(); $table->integer('o_id'); $table->string('so_id')->nullable(); $table->string('type')->nullable(); $table->string('node')->nullable(); $table->string('shop_type')->nullable(); $table->string('shop_name')->nullable(); $table->string('shop_site')->nullable(); + $table->string('l_id')->nullable(); $table->decimal('refund', 18, 2)->nullable(); $table->decimal('receive_refund', 18, 2)->nullable(); $table->decimal('buyer_apply_refund', 18, 2)->nullable(); $table->dateTime('createTime')->nullable(); $table->string('status')->nullable(); $table->string('shop_status')->nullable(); $table->dateTime('created')->nullable(); @@ -338,6 +343,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase $table->string('as_id'); $table->dateTime('as_date'); $table->string('outer_as_id')->nullable(); $table->integer('o_id')->nullable(); $table->string('so_id')->nullable(); $table->string('type')->nullable(); $table->string('node')->nullable(); $table->string('shop_type')->nullable(); $table->string('shop_name')->nullable(); + $table->string('l_id')->nullable(); $table->string('shop_id')->nullable(); $table->string('refund')->nullable(); $table->string('buyer_apply_refund')->nullable(); $table->string('status')->nullable(); $table->string('shop_status')->nullable(); $table->dateTime('created')->nullable(); $table->dateTime('confirm_date')->nullable(); $table->dateTime('local_created')->nullable(); @@ -356,7 +362,8 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase { return ['as_id' => (string) $id, 'as_date' => $date, 'outer_as_id' => 'AS-Q-' . $id, 'o_id' => 9000 + $id, 'so_id' => 'SO-Q-' . $id, 'type' => '仅退款', - 'shop_id' => $shop, 'shop_name' => '测试店', 'refund' => '12.34', 'status' => 'Confirmed']; + 'shop_id' => $shop, 'shop_name' => '测试店', 'l_id' => 'WB-Q-' . $id, + 'refund' => '12.34', 'status' => 'Confirmed']; } private function markReady(): void @@ -378,6 +385,7 @@ class NonJingxiRefundMirrorRepositoryTest extends TestCase 'Id' => $id, 'as_id' => $asId, 'as_date' => $date, 'outer_as_id' => 'AS-' . $asId, 'o_id' => 8900 + $id, 'so_id' => 'SO-' . $id, 'type' => '退款', 'node' => '完成', 'shop_type' => '自营', 'shop_name' => $shopName, 'shop_site' => $platform, + 'l_id' => 'WB-' . $id, 'refund' => 100, 'receive_refund' => 80, 'buyer_apply_refund' => 100, 'createTime' => $date, 'status' => '完成', 'shop_status' => '完成', 'created' => $date, 'confirm_date' => $date,