From 01a2e6bfa87c092820a7a6bfec1868898471481a Mon Sep 17 00:00:00 2001 From: chenxi Date: Wed, 9 Sep 2026 13:21:38 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E8=B4=A2=E5=8A=A1?= =?UTF-8?q?=E8=B4=A6=E5=8D=95=E8=A1=A8=E6=A0=BC=E6=8E=92=E7=89=88=E4=B8=8E?= =?UTF-8?q?=E4=BA=A4=E4=BA=92=20(#139)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/Controllers/AccountBills.php | 61 ++ .../Http/Controllers/AlipayBills.php | 71 ++- .../js/components/business/AccountBills.vue | 372 ++---------- .../business/TableColumnSettings.vue | 224 ++++++++ .../components/business/TaobaoAlipayBills.vue | 537 +++--------------- resources/views/business/pages/show.blade.php | 1 + 6 files changed, 492 insertions(+), 774 deletions(-) create mode 100644 resources/assets/js/components/business/TableColumnSettings.vue diff --git a/modules/EcommerceFinance/Http/Controllers/AccountBills.php b/modules/EcommerceFinance/Http/Controllers/AccountBills.php index 1b8e5104..beccc706 100644 --- a/modules/EcommerceFinance/Http/Controllers/AccountBills.php +++ b/modules/EcommerceFinance/Http/Controllers/AccountBills.php @@ -25,6 +25,17 @@ class AccountBills extends Controller abort(404, '未知的账户类型。'); } + $request->validate([ + 'store_name' => ['nullable', 'string', 'max:255'], + 'bill_item' => ['nullable', 'string', 'max:255'], + 'start' => ['nullable', 'date_format:Y-m-d'], + 'end' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:start'], + 'keyword' => ['nullable', 'string', 'max:255'], + 'export' => ['nullable', 'boolean'], + 'columns' => ['nullable', 'array', 'max:40'], + 'columns.*' => ['string', 'max:128'], + ]); + $query = AccountBill::query() ->where('company_id', company_id()) ->where('bill_type', $billType); @@ -60,6 +71,10 @@ class AccountBills extends Controller $dir = $request->get('dir') === 'asc' ? 'asc' : 'desc'; $query->orderBy($sort, $dir); + if ($request->boolean('export')) { + return $this->export($request, $query, $billType); + } + $perPage = min(max((int) $request->get('limit', 10), 1), 100); $rows = $query->paginate($perPage); @@ -87,6 +102,52 @@ class AccountBills extends Controller return response()->json($data); } + private function export(Request $request, $query, string $billType) + { + $definition = AccountBillColumns::for($billType); + $available = collect($definition['columns'] ?? [])->keyBy('field'); + $requested = $request->input('columns', []); + $columns = $requested === [] + ? $available->values() + : collect($requested)->unique()->map(fn (string $field) => $available->get($field))->filter()->values(); + + abort_if($columns->isEmpty(), 422, '至少选择一个可导出的字段。'); + + return response()->streamDownload(function () use ($query, $columns): void { + $csv = fopen('php://output', 'w'); + fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF)); + fputcsv($csv, $columns->pluck('label')->all()); + + foreach ($query->cursor() as $row) { + fputcsv($csv, $columns->map(function (array $column) use ($row): string { + $value = $this->columnValue($row, $column['field']); + + return $this->csvValue($value, $column['kind']); + })->all()); + } + fclose($csv); + }, $billType . '_account_bills.csv', ['Content-Type' => 'text/csv; charset=UTF-8']); + } + + private function columnValue(AccountBill $row, string $field): mixed + { + if (str_starts_with($field, 'payload.')) { + return data_get($row->payload ?? [], substr($field, 8), ''); + } + + return $row->getAttribute($field) ?? ''; + } + + private function csvValue(mixed $value, string $kind): string + { + $text = (string) ($value ?? ''); + if ($kind === 'num' && preg_match('/^\d{12,}$/', $text)) { + return "\t" . $text; + } + + return preg_match('/^[=+\-@]/', $text) ? "'" . $text : $text; + } + public function show(AccountBill $bill) { $this->authorizeStoreReceivingBills(); diff --git a/modules/EcommerceFinance/Http/Controllers/AlipayBills.php b/modules/EcommerceFinance/Http/Controllers/AlipayBills.php index 7cdcf6bc..14471a83 100644 --- a/modules/EcommerceFinance/Http/Controllers/AlipayBills.php +++ b/modules/EcommerceFinance/Http/Controllers/AlipayBills.php @@ -11,6 +11,34 @@ use RuntimeException; class AlipayBills extends Controller { + private const EXPORT_COLUMNS = [ + 'record_code' => '记录编码', + 'store_name' => '原始店铺', + 'custom_remark' => '自定义备注', + 'bill_item' => '账单项目', + 'alipay_account' => '平台渠道', + 'account_order_no' => '账务订单号', + 'business_serial_no' => '业务流水号', + 'merchant_order_no' => '商户订单号', + 'business_base_order_no' => '业务基础订单号', + 'goods_name' => '商品编码', + 'counterparty_account' => '对方账号', + 'occurred_at' => '发生时间', + 'income' => '收入', + 'expense' => '支出', + 'balance' => '收支净额', + 'business_type' => '业务类型', + 'remark' => '备注', + 'online_order_no' => '线上单号', + 'online_real_store' => '线上单号对应实际店铺', + 'business_description' => '业务描述', + 'business_bill_source' => '业务账单来源', + 'business_order_no' => '业务订单号', + 'created_at' => '创建时间', + 'updated_at' => '修改时间', + 'modifier' => '修改人', + ]; + /** * 淘系支付宝账户账单列表(店铺收款账户账单 › 淘系支付宝账户)。 * 经由 admin 路由组(web 会话)暴露,前端带 CSRF 调用即可。 @@ -68,8 +96,13 @@ class AlipayBills extends Controller 'start' => ['nullable', 'date_format:Y-m-d'], 'end' => ['nullable', 'date_format:Y-m-d', 'after_or_equal:start'], 'keyword' => ['nullable', 'string', 'max:255'], + 'columns' => ['nullable', 'array', 'max:25'], + 'columns.*' => ['string', 'max:64'], ]); + $columns = $this->requestedExportColumns($filters['columns'] ?? []); + unset($filters['columns']); + try { $rows = $repository->exportRows(array_map(static fn (mixed $value): string => trim((string) $value), $filters)); } catch (RuntimeException $exception) { @@ -80,17 +113,49 @@ class AlipayBills extends Controller return response()->json(['message' => '淘系支付宝账单共享业务镜像正在首次同步,完成后将自动显示。'], 503); } - return response()->streamDownload(function () use ($rows): void { + return response()->streamDownload(function () use ($rows, $columns): void { $csv = fopen('php://output', 'w'); fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF)); - fputcsv($csv, ['记录编码', '原始店铺', '平台渠道', '账单项目', '账务订单号', '业务流水号', '商户订单号', '业务基础订单号', '商品编码', '发生时间', '收入(元)', '支出(元)', '收支净额(元)', '收支类型', '线上单号', '业务描述', '业务账单来源', '创建时间', '修改时间']); + fputcsv($csv, array_values($columns)); foreach ($rows as $row) { - fputcsv($csv, [$row['record_code'], $row['store_name'], $row['alipay_account'], $row['bill_item'], $row['account_order_no'], $row['business_serial_no'], $row['merchant_order_no'], $row['business_base_order_no'], $row['goods_name'], $row['occurred_at'], $row['income'], $row['expense'], $row['balance'], $row['business_type'], $row['online_order_no'], $row['business_description'], $row['business_bill_source'], $row['created_at'], $row['updated_at']]); + fputcsv($csv, array_map( + fn (string $field): string => $this->csvValue($row[$field] ?? '', $field), + array_keys($columns), + )); } fclose($csv); }, 'taobao_alipay_bills.csv', ['Content-Type' => 'text/csv; charset=UTF-8']); } + /** @return array */ + private function requestedExportColumns(array $requested): array + { + if ($requested === []) { + return self::EXPORT_COLUMNS; + } + + $columns = []; + foreach ($requested as $field) { + if (isset(self::EXPORT_COLUMNS[$field])) { + $columns[$field] = self::EXPORT_COLUMNS[$field]; + } + } + + abort_if($columns === [], 422, '至少选择一个可导出的字段。'); + + return $columns; + } + + private function csvValue(mixed $value, string $field): string + { + $text = (string) ($value ?? ''); + if (preg_match('/(?:_no|_code|serial|goods_name)$/', $field) && preg_match('/^\d{12,}$/', $text)) { + return "\t" . $text; + } + + return preg_match('/^[=+\-@]/', $text) ? "'" . $text : $text; + } + public function show(AlipayBill $bill) { $this->authorizeStoreReceivingBills(); diff --git a/resources/assets/js/components/business/AccountBills.vue b/resources/assets/js/components/business/AccountBills.vue index 9a3dea5b..a32aed35 100644 --- a/resources/assets/js/components/business/AccountBills.vue +++ b/resources/assets/js/components/business/AccountBills.vue @@ -1,342 +1,72 @@ diff --git a/resources/assets/js/components/business/TableColumnSettings.vue b/resources/assets/js/components/business/TableColumnSettings.vue new file mode 100644 index 00000000..938a7441 --- /dev/null +++ b/resources/assets/js/components/business/TableColumnSettings.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/resources/assets/js/components/business/TaobaoAlipayBills.vue b/resources/assets/js/components/business/TaobaoAlipayBills.vue index 6b5d9c6a..096997ae 100644 --- a/resources/assets/js/components/business/TaobaoAlipayBills.vue +++ b/resources/assets/js/components/business/TaobaoAlipayBills.vue @@ -1,482 +1,119 @@ diff --git a/resources/views/business/pages/show.blade.php b/resources/views/business/pages/show.blade.php index 416d3a0f..540626c1 100644 --- a/resources/views/business/pages/show.blade.php +++ b/resources/views/business/pages/show.blade.php @@ -1060,6 +1060,7 @@ 'biz-card mt-6', 'business-blank-page' => ($page['slug'] ?? null) === 'store-receiving-bills', ]) + @if (($page['slug'] ?? null) === 'store-receiving-bills') data-table-preferences-user="{{ user()->id }}" @endif aria-label="{{ $activeTab['title'] ?? ($page['title'] ?? $section['title']) }}" >
From 5dcab54e0697949495a9217e03c276b4f1122e8b Mon Sep 17 00:00:00 2001 From: limeng <1131177064@qq.com> Date: Wed, 9 Sep 2026 13:56:38 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=BA=BF?= =?UTF-8?q?=E4=B8=8A=E4=BB=A3=E5=8F=91=E4=BB=B7=E6=A0=BC=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E5=92=8C=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Business/Pages.php | 111 ++++++++++-- ..._09_130000_add_daifa_material_no_index.php | 51 ++++++ .../DingOnlineDaifaPriceMirrorRepository.php | 159 ++++++++++++------ .../online-daifa-price.blade.php | 9 + routes/admin.php | 4 + .../Business/OnlineDaifaPricePageTest.php | 99 +++++++++++ ...gPayableWorkflowMirrorSynchronizerTest.php | 39 +++++ 7 files changed, 407 insertions(+), 65 deletions(-) create mode 100644 database/migrations/2026_09_09_130000_add_daifa_material_no_index.php diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index 712c8e08..df0ca982 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -742,20 +742,7 @@ class Pages extends Controller protected function contentForOnlineDaifaPrice(): array { - $clean = static fn (mixed $value, int $length = 80): string => mb_substr(trim((string) $value), 0, $length); - $date = static function (mixed $value): string { - $value = trim((string) $value); - return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1 ? $value : ''; - }; - $status = strtoupper(mb_substr(trim((string) request('status', '')), 0, 30)); - $filters = [ - 'keyword' => $clean(request('keyword', ''), 80), - 'product_id' => $clean(request('product_id', ''), 64), - 'shop_name' => $clean(request('shop_name', ''), 120), - 'status' => preg_match('/^[A-Z_]+$/', $status) === 1 ? $status : '', - 'created_from' => $date(request('created_from', '')), - 'created_to' => $date(request('created_to', '')), - ]; + $filters = $this->onlineDaifaPriceFilters(request()); $page = max((int) request('page', 1), 1); $limit = min(max((int) setting('default.list_limit', 25), 1), 100); @@ -795,6 +782,86 @@ class Pages extends Controller } } + public function exportOnlineDaifaPrice(Request $request): StreamedResponse + { + abort_unless($this->onlineDaifaPrices->ready(), 503); + + $filters = $this->onlineDaifaPriceFilters($request); + + return response()->streamDownload(function () use ($filters): void { + $csv = fopen('php://output', 'w'); + if ($csv === false) { + throw new \RuntimeException('无法创建线上代发价格导出流。'); + } + + fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF)); + fputcsv($csv, [ + '审批编号', '标题', '状态', '申请人', '收款人全称', '商品ID', '商品名称(匹配)', '店铺名称', + '代发交易单号', '店铺交易单号', '发货数量', '订单实付金额', '代发订单金额', '差价', + '供应商名称', '料号', '创建时间', + ]); + + foreach ($this->onlineDaifaPrices->exportRows($filters) as $row) { + $workflow = (array) ($row['workflow'] ?? []); + $approvalNumber = trim((string) ($workflow['businessId'] ?? '')) ?: (string) ($row['process_instance_id'] ?? ''); + $originator = trim((string) ($workflow['originatorUserName'] ?? '')) + ?: trim((string) ($workflow['originatorUserId'] ?? '')); + $matchedProduct = (array) ($row['matched_product'] ?? []); + $productName = ($matchedProduct['matched'] ?? false) + ? (string) ($matchedProduct['name'] ?? '') + : '(未匹配)'; + + fputcsv($csv, [ + $this->csvIdentifier($approvalNumber), + $this->csvText($workflow['title'] ?? ''), + $this->csvText($row['status_label'] ?? ''), + $this->csvText($originator), + $this->csvText($row['payee_name'] ?? ''), + $this->csvIdentifier($row['product_id'] ?? ''), + $this->csvText($productName), + $this->csvText($row['shop_name'] ?? ''), + $this->csvIdentifier($row['daifa_trade_no'] ?? ''), + $this->csvIdentifier($row['shop_trade_no'] ?? ''), + $row['ship_qty'] ?? null, + $row['paid_amount'] ?? null, + $row['daifa_amount'] ?? null, + $row['price_diff'] ?? null, + $this->csvText($row['supplier_name'] ?? ''), + $this->csvIdentifier($row['material_no'] ?? ''), + $this->csvText($row['workflow_created_at'] ?? ''), + ]); + } + + fclose($csv); + }, '线上代发价格_' . now()->format('Ymd_His') . '.csv', [ + 'Content-Type' => 'text/csv; charset=UTF-8', + 'Cache-Control' => 'private, no-store', + ]); + } + + /** @return array */ + private function onlineDaifaPriceFilters(Request $request): array + { + $clean = static fn (mixed $value, int $length = 80): string => mb_substr(trim((string) $value), 0, $length); + $date = static function (mixed $value): string { + $value = trim((string) $value); + + return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) === 1 ? $value : ''; + }; + $status = strtoupper(mb_substr(trim((string) $request->query('status', '')), 0, 30)); + + return [ + 'keyword' => $clean($request->query('keyword', ''), 80), + 'product_id' => $clean($request->query('product_id', ''), 64), + 'shop_name' => $clean($request->query('shop_name', ''), 120), + 'supplier_name' => $clean($request->query('supplier_name', ''), 120), + 'material_no' => $clean($request->query('material_no', ''), 120), + 'status' => preg_match('/^[A-Z_]+$/', $status) === 1 ? $status : '', + 'created_from' => $date($request->query('created_from', '')), + 'created_to' => $date($request->query('created_to', '')), + ]; + } + protected function contentForJingxiRefundData(): array { return [ @@ -2009,6 +2076,22 @@ class Pages extends Controller return preg_match('/^[=+\-@]/u', $text) === 1 ? "'" . $text : $text; } + /** + * Export identifiers as text so Excel does not round or truncate long IDs. + * Keep the formula-injection guard from csvText for values beginning with a + * formula marker. + */ + private function csvIdentifier(mixed $value): string + { + $text = $this->csvText($value); + + if ($text === '' || str_starts_with($text, "'")) { + return $text; + } + + return "'" . $text; + } + private function emptyProductCatalogContent( array $filters, int $limit, diff --git a/database/migrations/2026_09_09_130000_add_daifa_material_no_index.php b/database/migrations/2026_09_09_130000_add_daifa_material_no_index.php new file mode 100644 index 00000000..a7ef1d83 --- /dev/null +++ b/database/migrations/2026_09_09_130000_add_daifa_material_no_index.php @@ -0,0 +1,51 @@ +environment('testing') + || (string) config('system_database.mode', 'bootstrap') === 'bootstrap') { + return; + } + + $schema = Schema::connection($this->connection()); + if (! $schema->hasTable(self::TABLE) || $schema->hasIndex(self::TABLE, self::INDEX)) { + return; + } + + $schema->table(self::TABLE, function (Blueprint $table): void { + $table->index('material_no', self::INDEX); + }); + } + + public function down(): void + { + if (app()->environment('testing') + || (string) config('system_database.mode', 'bootstrap') === 'bootstrap') { + return; + } + + $schema = Schema::connection($this->connection()); + if (! $schema->hasTable(self::TABLE) || ! $schema->hasIndex(self::TABLE, self::INDEX)) { + return; + } + + $schema->table(self::TABLE, function (Blueprint $table): void { + $table->dropIndex(self::INDEX); + }); + } +}; diff --git a/modules/EcommerceFinance/Repositories/DingOnlineDaifaPriceMirrorRepository.php b/modules/EcommerceFinance/Repositories/DingOnlineDaifaPriceMirrorRepository.php index 301c24b8..53bec88a 100644 --- a/modules/EcommerceFinance/Repositories/DingOnlineDaifaPriceMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/DingOnlineDaifaPriceMirrorRepository.php @@ -3,6 +3,7 @@ namespace Modules\EcommerceFinance\Repositories; use Illuminate\Database\ConnectionInterface; +use Illuminate\Database\Query\Builder; use Illuminate\Support\Facades\Schema; use Modules\EcommerceFinance\Services\DingPayableWorkflowStatus; use RuntimeException; @@ -51,6 +52,66 @@ class DingOnlineDaifaPriceMirrorRepository $perPage = min(max($perPage, 1), 100); $state = $this->connection->table('business_mirror_table_states')->where('dataset_key', self::DATASET_KEY)->first(); + $rows = $this->filteredQuery($filters) + ->orderByDesc('workflow_created_at') + ->orderByDesc('process_instance_id') + ->orderByDesc('detail_index') + ->get(); + + $statusFilter = strtoupper(trim((string) ($filters['status'] ?? ''))); + $matched = []; + foreach ($rows as $row) { + $workflow = $this->decode((string) $row->workflow_payload, []); + if (! $this->matchesStatus($workflow, $statusFilter)) { + continue; + } + $matched[] = $row; + } + + $total = count($matched); + $pageRows = array_slice($matched, ($page - 1) * $perPage, $perPage); + + $data = []; + foreach ($pageRows as $row) { + $workflow = $this->decode((string) $row->workflow_payload, []); + $data[] = $this->presentRow($row, $workflow); + } + + return [ + 'data' => $data, + 'total' => $total, + 'status' => (string) ($state->status ?? 'unknown'), + 'last_synced_at' => $state->last_succeeded_at ? (string) $state->last_succeeded_at : null, + 'summary' => $this->summary($matched), + ]; + } + + /** @return \Generator> */ + public function exportRows(array $filters): \Generator + { + if (! $this->ready()) { + throw new RuntimeException('business_mirror_dataset_not_ready'); + } + + $statusFilter = strtoupper(trim((string) ($filters['status'] ?? ''))); + $rows = $this->filteredQuery($filters) + ->orderByDesc('workflow_created_at') + ->orderByDesc('process_instance_id') + ->orderByDesc('detail_index') + ->cursor(); + + foreach ($rows as $row) { + $workflow = $this->decode((string) $row->workflow_payload, []); + if (! $this->matchesStatus($workflow, $statusFilter)) { + continue; + } + + yield $this->presentRow($row, $workflow); + } + } + + private function filteredQuery(array $filters): Builder + { $query = $this->connection->table(self::TARGET_TABLE); if (($filters['product_id'] ?? '') !== '') { @@ -59,6 +120,12 @@ class DingOnlineDaifaPriceMirrorRepository if (($filters['shop_name'] ?? '') !== '') { $query->where('shop_name', (string) $filters['shop_name']); } + if (($filters['supplier_name'] ?? '') !== '') { + $query->whereRaw("supplier_name LIKE ? ESCAPE '!'", [$this->contains((string) $filters['supplier_name'])]); + } + if (($filters['material_no'] ?? '') !== '') { + $query->where('material_no', (string) $filters['material_no']); + } if (($filters['keyword'] ?? '') !== '') { $keyword = (string) $filters['keyword']; $query->where(function ($q) use ($keyword): void { @@ -74,64 +141,54 @@ class DingOnlineDaifaPriceMirrorRepository $query->where('workflow_created_at', '<', \Illuminate\Support\Carbon::parse((string) $filters['created_to'])->addDay()->startOfDay()->format('Y-m-d H:i:s')); } - $rows = $query->orderByDesc('workflow_created_at')->orderByDesc('process_instance_id')->orderByDesc('detail_index')->get(); + return $query; + } - $statusFilter = strtoupper(trim((string) ($filters['status'] ?? ''))); - $matched = []; - foreach ($rows as $row) { - $workflow = $this->decode((string) $row->workflow_payload, []); - if ($statusFilter !== '' && DingPayableWorkflowStatus::key($workflow) !== $statusFilter) { - continue; - } - $matched[] = $row; - } + private function matchesStatus(array $workflow, string $statusFilter): bool + { + return $statusFilter === '' || DingPayableWorkflowStatus::key($workflow) === $statusFilter; + } - $total = count($matched); - $pageRows = array_slice($matched, ($page - 1) * $perPage, $perPage); - - $data = []; - foreach ($pageRows as $row) { - $workflow = $this->decode((string) $row->workflow_payload, []); - $productId = (string) ($row->product_id ?? ''); - // 商品名称已在同步阶段按 商品ID 关联商品目录并写入镜像宽表 product_name 列, - // 页面直接读取,避免对 2500 万行的 ops_jst_sku_map 做临时 JOIN 导致超时。 - $resolvedName = $this->cleanName($row->product_name ?? null); - $product = $productId !== '' && $resolvedName !== null - ? ['matched' => true, 'name' => $resolvedName, 'sku_id' => null, 'i_id' => null] - : ['matched' => false, 'name' => null, 'sku_id' => null, 'i_id' => null]; - $data[] = [ - 'process_instance_id' => (string) $row->process_instance_id, - 'detail_index' => (int) $row->detail_index, - 'product_id' => $productId, - 'daifa_trade_no' => (string) ($row->daifa_trade_no ?? ''), - 'shop_trade_no' => (string) ($row->shop_trade_no ?? ''), - 'shop_name' => (string) ($row->shop_name ?? ''), - 'payee_name' => (string) ($row->payee_name ?? ''), - 'supplier_name' => (string) ($row->supplier_name ?? ''), - 'material_no' => (string) ($row->material_no ?? ''), - 'ship_qty' => $row->ship_qty === null ? null : (int) $row->ship_qty, - 'paid_amount' => $row->paid_amount === null ? null : (float) $row->paid_amount, - 'daifa_amount' => $row->daifa_amount === null ? null : (float) $row->daifa_amount, - 'price_diff' => $row->price_diff === null ? null : (float) $row->price_diff, - 'workflow' => $workflow, - 'scheme' => $this->decode((string) ($row->scheme_payload ?? '[]'), []), - 'tasks' => $this->decode((string) ($row->task_payload ?? '[]'), []), - 'status_key' => DingPayableWorkflowStatus::key($workflow), - 'status_label' => DingPayableWorkflowStatus::label($workflow), - 'matched_product' => $product, - 'workflow_created_at' => (string) ($row->workflow_created_at ?? ''), - ]; - } + /** @return array */ + private function presentRow(object $row, array $workflow): array + { + $productId = (string) ($row->product_id ?? ''); + // 商品名称已在同步阶段按 商品ID 关联商品目录并写入镜像宽表 product_name 列, + // 页面直接读取,避免对 2500 万行的 ops_jst_sku_map 做临时 JOIN 导致超时。 + $resolvedName = $this->cleanName($row->product_name ?? null); + $product = $productId !== '' && $resolvedName !== null + ? ['matched' => true, 'name' => $resolvedName, 'sku_id' => null, 'i_id' => null] + : ['matched' => false, 'name' => null, 'sku_id' => null, 'i_id' => null]; return [ - 'data' => $data, - 'total' => $total, - 'status' => (string) ($state->status ?? 'unknown'), - 'last_synced_at' => $state->last_succeeded_at ? (string) $state->last_succeeded_at : null, - 'summary' => $this->summary($matched), + 'process_instance_id' => (string) $row->process_instance_id, + 'detail_index' => (int) $row->detail_index, + 'product_id' => $productId, + 'daifa_trade_no' => (string) ($row->daifa_trade_no ?? ''), + 'shop_trade_no' => (string) ($row->shop_trade_no ?? ''), + 'shop_name' => (string) ($row->shop_name ?? ''), + 'payee_name' => (string) ($row->payee_name ?? ''), + 'supplier_name' => (string) ($row->supplier_name ?? ''), + 'material_no' => (string) ($row->material_no ?? ''), + 'ship_qty' => $row->ship_qty === null ? null : (int) $row->ship_qty, + 'paid_amount' => $row->paid_amount === null ? null : (float) $row->paid_amount, + 'daifa_amount' => $row->daifa_amount === null ? null : (float) $row->daifa_amount, + 'price_diff' => $row->price_diff === null ? null : (float) $row->price_diff, + 'workflow' => $workflow, + 'scheme' => $this->decode((string) ($row->scheme_payload ?? '[]'), []), + 'tasks' => $this->decode((string) ($row->task_payload ?? '[]'), []), + 'status_key' => DingPayableWorkflowStatus::key($workflow), + 'status_label' => DingPayableWorkflowStatus::label($workflow), + 'matched_product' => $product, + 'workflow_created_at' => (string) ($row->workflow_created_at ?? ''), ]; } + private function contains(string $value): string + { + return '%' . str_replace(['!', '%', '_'], ['!!', '!%', '!_'], trim($value)) . '%'; + } + /** @param list $rows */ private function summary(array $rows): array { diff --git a/resources/views/business/pages/product-inventory/online-daifa-price.blade.php b/resources/views/business/pages/product-inventory/online-daifa-price.blade.php index d7abafdc..fbe11a8c 100644 --- a/resources/views/business/pages/product-inventory/online-daifa-price.blade.php +++ b/resources/views/business/pages/product-inventory/online-daifa-price.blade.php @@ -66,6 +66,14 @@ +
+ + +
+
+ + +