From 54bad282983363c7ec69b8932adc9712dade41f2 Mon Sep 17 00:00:00 2001 From: yueyang <2495544247@qq.com> Date: Thu, 3 Sep 2026 12:02:13 +0800 Subject: [PATCH 1/2] feat: show jushuitan after-sales work orders --- .../WorkOrderMirrorRepository.php | 109 ++++++++++++++++++ .../View/OrderDataPageData.php | 42 +++++++ .../order-data/after-sales-expenses.blade.php | 85 ++++++++++++++ .../Business/OrderDataPagesUpstreamTest.php | 62 ++++++++++ .../WorkOrderMirrorRepositoryTest.php | 39 +++++++ 5 files changed, 337 insertions(+) diff --git a/modules/EcommerceFinance/Repositories/WorkOrderMirrorRepository.php b/modules/EcommerceFinance/Repositories/WorkOrderMirrorRepository.php index ebc190ec..d288d58c 100644 --- a/modules/EcommerceFinance/Repositories/WorkOrderMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/WorkOrderMirrorRepository.php @@ -149,6 +149,77 @@ class WorkOrderMirrorRepository ]; } + /** + * Return after-sales alert work orders for the after-sales expense page. + * The work-order values describe an operational alert; they are not added + * to the order/refund monetary columns. + * + * @param array $filters + * @return array{data:list>,total:int,page:int,per_page:int,last_page:int,ready:bool,sync_status:array} + */ + public function paginateAfterSales(int $page, int $perPage, array $filters = []): array + { + $page = max($page, 1); + $perPage = min(max($perPage, 1), 50); + $syncStatus = $this->syncStatus(); + + if (! $syncStatus['ready']) { + return [ + 'data' => [], + 'total' => 0, + 'page' => $page, + 'per_page' => $perPage, + 'last_page' => 1, + 'ready' => false, + 'sync_status' => $syncStatus, + ]; + } + + $main = $this->datasets->get(self::MAIN_DATASET)->targetTable(); + $config = $this->datasets->get(self::CONFIG_DATASET)->targetTable(); + $latestConfigs = $this->connection->table($config . ' AS config_versions') + ->selectRaw('work_type, MAX(id) AS id') + ->groupBy('work_type'); + + $query = $this->connection->table($main . ' AS m') + ->joinSub($latestConfigs, 'config_versions', static function ($join): void { + $join->on('config_versions.work_type', '=', 'm.work_type'); + }) + ->join($config . ' AS c', 'c.id', '=', 'config_versions.id') + ->where('c.work_type_name', 'like', '%售后%') + ->where(static function (Builder $active): void { + $active->whereNull('m.is_deleted')->orWhere('m.is_deleted', 0); + }); + + $this->applyAfterSalesFilters($query, $filters); + $total = (clone $query)->count(); + $rows = $query + ->select([ + 'm.id', 'm.work_type', 'c.work_type_name', 'c.group_name', + 'm.order_no', 'm.title', 'm.status', 'm.create_user', + 'm.submit_time', 'm.expect_end_time', 'm.end_time', + 'm.description', 'm.create_time', 'm.update_time', + ]) + ->orderByDesc('m.create_time') + ->orderByDesc('m.id') + ->forPage($page, $perPage) + ->get() + ->map(static fn (object $row): array => (array) $row) + ->all(); + + $this->hydrateRelatedRows($rows); + + return [ + 'data' => $rows, + 'total' => $total, + 'page' => $page, + 'per_page' => $perPage, + 'last_page' => max((int) ceil($total / $perPage), 1), + 'ready' => true, + 'sync_status' => $syncStatus, + ]; + } + /** @param array $filters */ private function applyFilters(Builder $query, array $filters): void { @@ -185,6 +256,44 @@ class WorkOrderMirrorRepository } } + /** @param array $filters */ + private function applyAfterSalesFilters(Builder $query, array $filters): void + { + if (($filters['from'] ?? '') !== '') { + $query->where('m.create_time', '>=', $filters['from'] . ' 00:00:00'); + } + if (($filters['to'] ?? '') !== '') { + $query->where('m.create_time', '<', Carbon::parse($filters['to'])->addDay()->toDateString()); + } + if (($filters['order_no'] ?? '') !== '') { + $query->whereRaw("m.order_no LIKE ? ESCAPE '!'", [$this->contains($filters['order_no'])]); + } + if (($filters['shop_name'] ?? '') !== '') { + // Older Tmall/JD alert forms store the shop text in a generic + // question/field_1 value instead of the explicit shop_name key. + $this->whereFormValueLike($query, ['shop_name', 'question', 'field_1'], $filters['shop_name']); + } + if (($filters['sku_id'] ?? '') !== '') { + $this->whereFormValueLike($query, ['goods_id', 'id', 'question', 'field_1'], $filters['sku_id']); + } + } + + /** @param list $fieldCodes */ + private function whereFormValueLike(Builder $query, array $fieldCodes, string $value): void + { + $valueTable = $this->datasets->get(self::VALUE_DATASET)->targetTable(); + $query->whereExists(function (Builder $valueQuery) use ($valueTable, $fieldCodes, $value): void { + $valueQuery->selectRaw('1') + ->from($valueTable . ' AS filter_values') + ->whereColumn('filter_values.main_order_no', 'm.order_no') + ->whereIn('filter_values.field_code', $fieldCodes) + ->whereRaw("filter_values.field_value LIKE ? ESCAPE '!'", [$this->contains($value)]) + ->where(static function (Builder $active): void { + $active->whereNull('filter_values.is_deleted')->orWhere('filter_values.is_deleted', 0); + }); + }); + } + /** @param list> $rows */ private function hydrateRelatedRows(array &$rows): void { diff --git a/modules/EcommerceFinance/View/OrderDataPageData.php b/modules/EcommerceFinance/View/OrderDataPageData.php index ce21bf04..3a369b40 100644 --- a/modules/EcommerceFinance/View/OrderDataPageData.php +++ b/modules/EcommerceFinance/View/OrderDataPageData.php @@ -6,6 +6,7 @@ use Illuminate\Pagination\Paginator; use Illuminate\Support\Collection; use Modules\EcommerceFinance\Repositories\OrderDataRepository; use Modules\EcommerceFinance\Repositories\OrderDataMirrorRepository; +use Modules\EcommerceFinance\Repositories\WorkOrderMirrorRepository; use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; use Throwable; @@ -18,6 +19,7 @@ class OrderDataPageData private readonly OrderDataRepository $orders, private readonly UpstreamQueryCacheFactory $caches, private readonly ?OrderDataMirrorRepository $mirrorOrders = null, + private readonly ?WorkOrderMirrorRepository $workOrderMirror = null, ) { } @@ -42,6 +44,7 @@ class OrderDataPageData 'orderDataFilters' => $filters, 'orderDataStatus' => ['X-Caiwu-Data-Status' => 'mirror'], 'orderDataError' => null, + 'afterSalesWorkOrders' => $this->afterSalesWorkOrders($filters), ]; } @@ -57,6 +60,7 @@ class OrderDataPageData 'orderDataFilters' => $filters, 'orderDataStatus' => $cache->responseHeaders(), 'orderDataError' => null, + 'afterSalesWorkOrders' => $this->afterSalesWorkOrders($filters), ]; } catch (ServiceUnavailableHttpException $exception) { return $this->failed($filters, $page, $limit, $exception->getMessage()); @@ -118,6 +122,44 @@ class OrderDataPageData 'orderDataFilters' => $filters, 'orderDataStatus' => [], 'orderDataError' => $message, + 'afterSalesWorkOrders' => $this->emptyAfterSalesWorkOrders(), + ]; + } + + /** @param array $filters */ + private function afterSalesWorkOrders(array $filters): array + { + if ($this->workOrderMirror === null) { + return $this->emptyAfterSalesWorkOrders(); + } + + try { + return $this->workOrderMirror->paginateAfterSales( + max((int) request('work_order_page', 1), 1), + 10, + $filters, + ); + } catch (Throwable) { + return $this->emptyAfterSalesWorkOrders(); + } + } + + /** @return array{data:list>,total:int,page:int,per_page:int,last_page:int,ready:bool,sync_status:array} */ + private function emptyAfterSalesWorkOrders(): array + { + return [ + 'data' => [], + 'total' => 0, + 'page' => max((int) request('work_order_page', 1), 1), + 'per_page' => 10, + 'last_page' => 1, + 'ready' => false, + 'sync_status' => [ + 'ready' => false, + 'code' => 'unavailable', + 'last_succeeded_at' => null, + 'datasets' => [], + ], ]; } diff --git a/resources/views/business/pages/order-data/after-sales-expenses.blade.php b/resources/views/business/pages/order-data/after-sales-expenses.blade.php index 25f9c5b1..5cca1d2b 100644 --- a/resources/views/business/pages/order-data/after-sales-expenses.blade.php +++ b/resources/views/business/pages/order-data/after-sales-expenses.blade.php @@ -126,6 +126,10 @@ $filters = $orderData['orderDataFilters']; $status = data_get($orderData, 'orderDataStatus.X-Caiwu-Data-Status'); $orderDataError = $orderData['orderDataError']; + $afterSalesWorkOrders = $orderData['afterSalesWorkOrders'] ?? [ + 'data' => [], 'total' => 0, 'page' => 1, 'per_page' => 10, 'last_page' => 1, + 'ready' => false, 'sync_status' => [], + ]; @endphp
@@ -315,6 +319,87 @@
+ +
+
+
+

聚水潭售后工单

+

售后预警工单仅作为经营处理线索展示,不计入上方订单退款金额。

+
+ @if (($afterSalesWorkOrders['ready'] ?? false) && ($afterSalesWorkOrders['total'] ?? 0) > 0) + + 共 {{ $afterSalesWorkOrders['total'] }} 条 + + @endif +
+ + @if (!($afterSalesWorkOrders['ready'] ?? false)) +
+ + 聚水潭售后工单镜像尚未完成首次同步,当前不把空镜像解释为没有工单。 +
+ @endif + +
+ + + + + + + + + + + + + + @forelse (($afterSalesWorkOrders['data'] ?? []) as $workOrder) + + + + + + + + + + @empty + + + + @endforelse + +
工单号工单类型标题状态创建人创建时间表单信息
{{ $workOrder['order_no'] ?? '-' }}{{ $workOrder['work_type_name'] ?? '-' }}{{ $workOrder['title'] ?? '-' }}{{ $workOrder['status'] ?? '-' }}{{ $workOrder['create_user'] ?? '-' }}{{ $workOrder['create_time'] ?? '-' }} + @forelse (($workOrder['form_values'] ?? []) as $formValue) +
{{ $formValue['field_name'] ?? $formValue['field_code'] ?? '字段' }}:{{ $formValue['field_value'] ?? '-' }}
+ @empty + - + @endforelse +
暂无售后工单
+
+ + @if (($afterSalesWorkOrders['ready'] ?? false) && ($afterSalesWorkOrders['last_page'] ?? 1) > 1) + + @endif +