feat: 完善售后退货退款查询与导出
Co-authored-by: 苏木 <594326199@qq.com> Co-committed-by: 苏木 <594326199@qq.com>
This commit is contained in:
parent
393784a5e3
commit
e0b7a66d37
@ -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();
|
||||
|
||||
@ -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<object>,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<int,object>
|
||||
*/
|
||||
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
|
||||
|
||||
@ -3,5 +3,6 @@
|
||||
'refundPageSlug' => 'after-sales-return-refund',
|
||||
'refundPageLabel' => '售后退货退款预警',
|
||||
'refundWarningMode' => true,
|
||||
'refundShowExport' => false,
|
||||
'refundShowExport' => true,
|
||||
'refundExportRouteName' => 'business.other.after-sales-return-refund.export',
|
||||
])
|
||||
|
||||
@ -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
|
||||
|
||||
<div
|
||||
@ -168,6 +183,10 @@
|
||||
<label for="non-jingxi-refund-order">订单号</label>
|
||||
<input id="non-jingxi-refund-order" type="search" name="order_no" value="{{ $filters['order_no'] }}" placeholder="完整订单或售后单号(精确查询)">
|
||||
</div>
|
||||
<div class="upstream-data-field">
|
||||
<label for="non-jingxi-refund-waybill">运单号</label>
|
||||
<input id="non-jingxi-refund-waybill" type="search" name="waybill_no" value="{{ $filters['waybill_no'] }}" placeholder="完整运单号(精确查询)">
|
||||
</div>
|
||||
<div class="upstream-data-field">
|
||||
<label for="non-jingxi-refund-shop">店铺名称</label>
|
||||
<input id="non-jingxi-refund-shop" type="search" name="shop_name" value="{{ $filters['shop_name'] }}" placeholder="店铺名称">
|
||||
@ -189,7 +208,7 @@
|
||||
<a
|
||||
class="upstream-data-button finance-table-fallback-export {{ $rows->total() === 0 || $refundError || $refundPending || $refundIndexPending ? 'is-disabled' : '' }}"
|
||||
data-finance-fallback-export
|
||||
href="{{ $rows->total() === 0 || $refundError || $refundPending || $refundIndexPending ? '#' : route('business.refunds.non-jingxi-refund-data.export', $filters) }}"
|
||||
href="{{ $rows->total() === 0 || $refundError || $refundPending || $refundIndexPending ? '#' : route($refundExportRouteName, $filters) }}"
|
||||
@if ($rows->total() === 0 || $refundError || $refundPending || $refundIndexPending) aria-disabled="true" @endif
|
||||
>
|
||||
<span class="material-icons-outlined" aria-hidden="true">download</span>
|
||||
@ -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
|
||||
<td @class(['is-number' => $isNumber])>
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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' => '退货退款',
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user