From ab9384da0b2bf7d61178b49c219843236544dd0a Mon Sep 17 00:00:00 2001 From: chenxi Date: Thu, 6 Aug 2026 18:30:20 +0800 Subject: [PATCH 1/4] feat: connect jd wallet bills to jiansun --- app/Http/Controllers/Business/Pages.php | 84 ++++++++ .../Repositories/JdWalletBillRepository.php | 187 ++++++++++++++++++ .../Services/JianSunSnapshotRefresher.php | 28 +++ .../partials/embedded-table.blade.php | 122 ++++++++++++ .../MarketingAccountBillsTabsTest.php | 11 +- .../JdWalletBillRepositoryTest.php | 79 ++++++++ 6 files changed, 507 insertions(+), 4 deletions(-) create mode 100644 modules/EcommerceFinance/Repositories/JdWalletBillRepository.php create mode 100644 resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php create mode 100644 tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index dde88efc..7b2bcdb5 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -16,6 +16,7 @@ use Illuminate\Support\Collection; use Modules\EcommerceFinance\Repositories\AlibabaBillRepository; use Modules\EcommerceFinance\Repositories\EdgeBandingRepository; use Modules\EcommerceFinance\Repositories\InventoryRepository; +use Modules\EcommerceFinance\Repositories\JdWalletBillRepository; use Modules\EcommerceFinance\Repositories\PinduoduoSpreadBillRepository; use Modules\EcommerceFinance\Repositories\ProductPriceRepository; use Modules\EcommerceFinance\Repositories\RawBoardPayablesRepository; @@ -939,6 +940,89 @@ class Pages extends Controller ]; } + // 京东钱包 - 按附件表1分别展示充值账单和消耗账单,数据来自 JianSun 上游只读表。 + if ($tab === 'jd-wallet') { + $request = request(); + $ledger = $request->query('jd_ledger') === 'consumption' ? 'consumption' : 'recharge'; + $storeName = mb_substr(trim((string) $request->query('store_name')), 0, 191); + $fromDate = $this->dateFilter($request->query('from')); + $toDate = $this->dateFilter($request->query('to')); + $page = max(1, (int) $request->query('page', 1)); + $pageSize = min(max((int) setting('default.list_limit', 25), 1), 100); + $filters = [ + 'store_name' => $storeName !== '' ? $storeName : null, + 'from' => $fromDate !== '' ? $fromDate : null, + 'to' => $toDate !== '' ? $toDate : null, + ]; + $repository = app(JdWalletBillRepository::class); + $cache = $this->upstreamCaches->forSource('jiansun'); + $scopePrefix = 'jd-wallet-bills.' . $ledger; + + try { + $result = $cache->remember( + $scopePrefix . '.list', + ['filters' => $filters, 'page' => $page, 'limit' => $pageSize], + fn () => $ledger === 'consumption' + ? $repository->paginateConsumption($filters['store_name'], $filters['from'], $filters['to'], $page, $pageSize) + : $repository->paginateRecharge($filters['store_name'], $filters['from'], $filters['to'], $page, $pageSize), + ); + $summary = $cache->remember( + $scopePrefix . '.summary', + ['filters' => $filters], + fn () => $ledger === 'consumption' + ? $repository->summaryConsumption($filters['store_name'], $filters['from'], $filters['to']) + : $repository->summaryRecharge($filters['store_name'], $filters['from'], $filters['to']), + ); + $shopOptions = $cache->remember( + 'jd-wallet-bills.options', + [], + fn () => $repository->shopOptions(), + ); + } catch (ServiceUnavailableHttpException $exception) { + return [ + 'view' => 'banking.marketing_jd_wallet_bills.partials.embedded-table', + 'ledger' => $ledger, + 'bills' => new LengthAwarePaginator([], 0, $pageSize, $page, [ + 'path' => $request->url(), 'query' => $request->query(), + ]), + 'filters' => $filters, + 'summary' => ['amount_total' => 0, 'balance_total' => 0, 'count' => 0], + 'shopOptions' => [], + 'error' => $exception->getMessage(), + ]; + } catch (Throwable $exception) { + report($exception); + + return [ + 'view' => 'banking.marketing_jd_wallet_bills.partials.embedded-table', + 'ledger' => $ledger, + 'bills' => new LengthAwarePaginator([], 0, $pageSize, $page, [ + 'path' => $request->url(), 'query' => $request->query(), + ]), + 'filters' => $filters, + 'summary' => ['amount_total' => 0, 'balance_total' => 0, 'count' => 0], + 'shopOptions' => [], + 'error' => '京东账单数据暂时无法读取,请稍后重试。', + ]; + } + + return [ + 'view' => 'banking.marketing_jd_wallet_bills.partials.embedded-table', + 'ledger' => $ledger, + 'bills' => new LengthAwarePaginator( + Collection::make($result['rows'] ?? []), + (int) ($result['total'] ?? 0), + $pageSize, + $page, + ['path' => $request->url(), 'query' => $request->query()], + ), + 'filters' => $filters, + 'summary' => $summary, + 'shopOptions' => $shopOptions, + 'error' => null, + ]; + } + return [ 'view' => 'banking.marketing_account_bills.partials.embedded-table', 'platform' => $tab, diff --git a/modules/EcommerceFinance/Repositories/JdWalletBillRepository.php b/modules/EcommerceFinance/Repositories/JdWalletBillRepository.php new file mode 100644 index 00000000..bef1757c --- /dev/null +++ b/modules/EcommerceFinance/Repositories/JdWalletBillRepository.php @@ -0,0 +1,187 @@ +query = $factory->forSource(self::SOURCE); + } + + /** @return string[] */ + public function shopOptions(): array + { + $rows = $this->query->select( + "SELECT TOP (500) shopName FROM (SELECT shopName FROM " . self::RECHARGE_TABLE + . " UNION SELECT shopName FROM " . self::CONSUMPTION_TABLE + . ") shops WHERE shopName IS NOT NULL AND LTRIM(RTRIM(shopName)) <> '' ORDER BY shopName" + ); + + return array_values(array_map( + static fn (array $row): string => (string) ($row['shopName'] ?? ''), + $rows, + )); + } + + /** @return array{rows: array>, total: int} */ + public function paginateRecharge( + ?string $shopName = null, + ?string $fromDate = null, + ?string $toDate = null, + int $page = 1, + int $pageSize = 25, + ): array { + return $this->paginate(self::RECHARGE_TABLE, $this->rechargeWhere($shopName, $fromDate, $toDate), $page, $pageSize, [ + 'id', 'shopName', 'amount', 'balanceAmount', 'orderTypeName', 'pk', + 'createDate', 'updateTime', 'statusName', 'remark', + ], 'createDate', 'id', fn (array $row): array => $this->normalizeRecharge($row)); + } + + /** @return array{rows: array>, total: int} */ + public function paginateConsumption( + ?string $shopName = null, + ?string $fromDate = null, + ?string $toDate = null, + int $page = 1, + int $pageSize = 25, + ): array { + return $this->paginate(self::CONSUMPTION_TABLE, $this->consumptionWhere($shopName, $fromDate, $toDate), $page, $pageSize, [ + 'id', 'shopName', 'amount', 'balanceAmount', 'orderTypeName', 'creatTime', 'settleTime', 'pk', + 'invoiceApplyTypeName', + ], 'creatTime', 'id', fn (array $row): array => $this->normalizeConsumption($row)); + } + + /** @return array{amount_total: float, balance_total: float, count: int} */ + public function summaryRecharge(?string $shopName = null, ?string $fromDate = null, ?string $toDate = null): array + { + return $this->summary(self::RECHARGE_TABLE, $this->rechargeWhere($shopName, $fromDate, $toDate)); + } + + /** @return array{amount_total: float, balance_total: float, count: int} */ + public function summaryConsumption(?string $shopName = null, ?string $fromDate = null, ?string $toDate = null): array + { + return $this->summary(self::CONSUMPTION_TABLE, $this->consumptionWhere($shopName, $fromDate, $toDate)); + } + + /** @param array{sql:string,bindings:array} $where */ + private function paginate(string $table, array $where, int $page, int $pageSize, array $fields, string $dateField, string $idField, callable $normalize): array + { + $page = max(1, $page); + $pageSize = min(max(1, $pageSize), 100); + $total = (int) $this->query->scalar('SELECT COUNT(*) FROM ' . $table . ' src' . $where['sql'], $where['bindings']); + + if ($total === 0) { + return ['rows' => [], 'total' => 0]; + } + + $start = (($page - 1) * $pageSize) + 1; + $end = $page * $pageSize; + $select = implode(', ', array_map(static fn (string $field): string => 'src.' . $field, $fields)); + $sql = 'SELECT * FROM (' + . 'SELECT ' . $select . ', ROW_NUMBER() OVER (ORDER BY src.' . $dateField . ' DESC, src.' . $idField . ' DESC) AS rn ' + . 'FROM ' . $table . ' src' . $where['sql'] + . ') AS t WHERE t.rn BETWEEN ? AND ? ORDER BY t.rn'; + $rows = $this->query->select($sql, array_merge($where['bindings'], [$start, $end])); + + return ['rows' => array_map($normalize, $rows), 'total' => $total]; + } + + /** @param array{sql:string,bindings:array} $where */ + private function summary(string $table, array $where): array + { + $row = $this->query->first( + 'SELECT COALESCE(SUM(ABS(amount)), 0) AS amount_total, COALESCE(SUM(balanceAmount), 0) AS balance_total, COUNT(*) AS cnt FROM ' + . $table . ' src' . $where['sql'], + $where['bindings'], + ) ?? []; + + return [ + 'amount_total' => round((float) ($row['amount_total'] ?? 0), 2), + 'balance_total' => round((float) ($row['balance_total'] ?? 0), 2), + 'count' => (int) ($row['cnt'] ?? 0), + ]; + } + + /** @return array{sql:string,bindings:array} */ + private function rechargeWhere(?string $shopName, ?string $fromDate, ?string $toDate): array + { + return $this->dateWhere('src.createDate', $shopName, $fromDate, $toDate); + } + + /** @return array{sql:string,bindings:array} */ + private function consumptionWhere(?string $shopName, ?string $fromDate, ?string $toDate): array + { + return $this->dateWhere('src.creatTime', $shopName, $fromDate, $toDate); + } + + /** @return array{sql:string,bindings:array} */ + private function dateWhere(string $dateField, ?string $shopName, ?string $fromDate, ?string $toDate): array + { + $conditions = []; + $bindings = []; + if ($shopName !== null && $shopName !== '') { + $conditions[] = 'src.shopName = ?'; + $bindings[] = $shopName; + } + if ($fromDate !== null && preg_match('/^\d{4}-\d{2}-\d{2}$/', $fromDate) === 1) { + $conditions[] = $dateField . ' >= ?'; + $bindings[] = $fromDate . ' 00:00:00'; + } + if ($toDate !== null && preg_match('/^\d{4}-\d{2}-\d{2}$/', $toDate) === 1) { + $conditions[] = $dateField . ' <= ?'; + $bindings[] = $toDate . ' 23:59:59'; + } + + return ['sql' => $conditions === [] ? '' : ' WHERE ' . implode(' AND ', $conditions), 'bindings' => $bindings]; + } + + private function normalizeRecharge(array $row): array + { + return [ + 'id' => (string) ($row['id'] ?? $row['pk'] ?? ''), + 'store_name' => (string) ($row['shopName'] ?? ''), + 'amount' => round((float) ($row['amount'] ?? 0), 2), + 'balance' => round((float) ($row['balanceAmount'] ?? 0), 2), + 'transaction_type' => (string) ($row['orderTypeName'] ?? ''), + 'order_number' => (string) ($row['pk'] ?? ''), + 'recharge_time' => (string) ($row['createDate'] ?? ''), + 'arrival_time' => (string) ($row['updateTime'] ?? ''), + 'serial_number' => (string) ($row['id'] ?? $row['pk'] ?? ''), + 'status' => (string) ($row['statusName'] ?? ''), + 'notes' => (string) ($row['remark'] ?? ''), + ]; + } + + private function normalizeConsumption(array $row): array + { + $amount = abs((float) ($row['amount'] ?? 0)); + + return [ + 'id' => (string) ($row['id'] ?? $row['pk'] ?? ''), + 'store_name' => (string) ($row['shopName'] ?? ''), + 'amount' => round($amount, 2), + 'balance' => round((float) ($row['balanceAmount'] ?? 0), 2), + 'transaction_type' => (string) ($row['orderTypeName'] ?? ''), + 'investment_date' => (string) ($row['creatTime'] ?? ''), + 'settlement_date' => (string) ($row['settleTime'] ?? ''), + 'serial_number' => (string) ($row['pk'] ?? $row['id'] ?? ''), + 'notes' => (string) ($row['invoiceApplyTypeName'] ?? ''), + ]; + } +} diff --git a/modules/EcommerceFinance/Services/JianSunSnapshotRefresher.php b/modules/EcommerceFinance/Services/JianSunSnapshotRefresher.php index c0a38bbe..1c09a17f 100644 --- a/modules/EcommerceFinance/Services/JianSunSnapshotRefresher.php +++ b/modules/EcommerceFinance/Services/JianSunSnapshotRefresher.php @@ -8,6 +8,7 @@ use Modules\EcommerceFinance\Repositories\AlibabaBillRepository; use Modules\EcommerceFinance\Repositories\EdgeBandingRepository; use Modules\EcommerceFinance\Repositories\GoodsReconciliationRepository; use Modules\EcommerceFinance\Repositories\InventoryRepository; +use Modules\EcommerceFinance\Repositories\JdWalletBillRepository; use Modules\EcommerceFinance\Repositories\PinduoduoSpreadBillRepository; use Modules\EcommerceFinance\Repositories\ProductPriceRepository; use Modules\EcommerceFinance\Repositories\RawBoardPayablesRepository; @@ -32,6 +33,7 @@ class JianSunSnapshotRefresher implements UpstreamSnapshotRefresher private readonly TmallTaobaoSpreadBillRepository $taobaoAlipay, private readonly SupplierPayableRepository $supplier, private readonly SupplierPayableReportRepository $supplierPayableReports, + private readonly ?JdWalletBillRepository $jdWallet = null, ) { } @@ -112,6 +114,32 @@ class JianSunSnapshotRefresher implements UpstreamSnapshotRefresher ), 'pinduoduo-bills.options' => $this->pinduoduo->storeOptions(), + 'jd-wallet-bills.recharge.list' => $this->jdWallet?->paginateRecharge( + $storeName, + $fromDate, + $toDate, + $page, + $limit, + ), + 'jd-wallet-bills.recharge.summary' => $this->jdWallet?->summaryRecharge( + $storeName, + $fromDate, + $toDate, + ), + 'jd-wallet-bills.consumption.list' => $this->jdWallet?->paginateConsumption( + $storeName, + $fromDate, + $toDate, + $page, + $limit, + ), + 'jd-wallet-bills.consumption.summary' => $this->jdWallet?->summaryConsumption( + $storeName, + $fromDate, + $toDate, + ), + 'jd-wallet-bills.options' => $this->jdWallet?->shopOptions(), + 'taobao-alipay-bills.list' => $this->taobaoAlipay->paginate( $storeName, $fromDate, diff --git a/resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php b/resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php new file mode 100644 index 00000000..76e99f15 --- /dev/null +++ b/resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php @@ -0,0 +1,122 @@ + + + +@php + $ledger = $ledger ?? 'recharge'; + $filters = $filters ?? ['store_name' => '', 'from' => '', 'to' => '']; + $summary = $summary ?? ['amount_total' => 0, 'balance_total' => 0, 'count' => 0]; + $shopOptions = is_array($shopOptions ?? null) ? $shopOptions : []; + $amountWan = (float) ($summary['amount_total'] ?? 0) / 10000; + $balanceWan = (float) ($summary['balance_total'] ?? 0) / 10000; + $isConsumption = $ledger === 'consumption'; + $currentUrl = request()->url(); +@endphp + +
+ + +
+
+
{{ $isConsumption ? '消耗金额合计' : '充值金额合计' }}(万元)
+
{{ number_format($amountWan, 2) }}
+
+
+
余额合计(万元)
+
{{ number_format($balanceWan, 2) }}
+
+
+
账单笔数
+
{{ number_format((int) ($summary['count'] ?? 0)) }}
+
+
+ + @if (! empty($error)) + + @endif + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + 清除 +
+
共匹配 {{ $bills->total() }} 条
+
+
+ +
+ + + @if ($isConsumption) + + @else + + @endif + + + @forelse ($bills as $bill) + + @if ($isConsumption) + + @else + + @endif + + @empty + + @endforelse + +
店铺名称金额(元)余额(元)交易类型投放日期结算日期流水号店铺名称金额(元)余额(元)交易类型商户订单号充值时间到账时间流水号状态备注
{{ $bill['store_name'] }}{{ number_format($bill['amount'], 2) }}{{ number_format($bill['balance'], 2) }}{{ $bill['transaction_type'] }}{{ $bill['investment_date'] }}{{ $bill['settlement_date'] }}{{ $bill['serial_number'] }}{{ $bill['store_name'] }}{{ number_format($bill['amount'], 2) }}{{ number_format($bill['balance'], 2) }}{{ $bill['transaction_type'] }}{{ $bill['order_number'] }}{{ $bill['recharge_time'] }}{{ $bill['arrival_time'] }}{{ $bill['serial_number'] }}{{ $bill['status'] }}{{ \Illuminate\Support\Str::limit($bill['notes'], 20) }}
暂无符合条件的账单
+
+ + @unless ($bills->isEmpty()) +
+ @endunless +
diff --git a/tests/Feature/Business/MarketingAccountBillsTabsTest.php b/tests/Feature/Business/MarketingAccountBillsTabsTest.php index 5f657dd7..dc5e45ef 100644 --- a/tests/Feature/Business/MarketingAccountBillsTabsTest.php +++ b/tests/Feature/Business/MarketingAccountBillsTabsTest.php @@ -38,16 +38,19 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase ->assertOk() ->assertSeeText('充值账单') ->assertSeeText('消耗账单') - ->assertSeeText('总存入金额(元)') - ->assertDontSeeText('非赠款消耗金额(元)'); + ->assertSeeText('金额(元)') + ->assertSeeText('充值时间') + ->assertDontSeeText('投放日期'); $this->loginAs() ->get(route('business.pages.show', array_merge($base, ['jd_ledger' => 'consumption']))) ->assertOk() ->assertSeeText('充值账单') ->assertSeeText('消耗账单') - ->assertSeeText('非赠款消耗金额(元)') - ->assertDontSeeText('总存入金额(元)'); + ->assertSeeText('金额(元)') + ->assertSeeText('投放日期') + ->assertSeeText('结算日期') + ->assertDontSeeText('充值时间'); } private function allowMarketingAccountBillsPage(): void diff --git a/tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php b/tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php new file mode 100644 index 00000000..717acbec --- /dev/null +++ b/tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php @@ -0,0 +1,79 @@ +calls[] = ['method' => 'scalar', 'sql' => $sql, 'bindings' => $bindings]; + + return 1; + } + + public function select(string $sql, array $bindings = []): array + { + $this->calls[] = ['method' => 'select', 'sql' => $sql, 'bindings' => $bindings]; + + return [[ + 'id' => 42, + 'shopName' => '测试京东店铺', + 'amount' => -12.345, + 'balanceAmount' => 98.765, + 'orderTypeName' => '快车扣费', + 'pk' => '流水-1', + 'createDate' => '2026-08-03 12:00:00', + 'updateTime' => '2026-08-03 12:01:00', + 'statusName' => '成功', + 'remark' => '备注', + 'creatTime' => '2026-08-03 12:00:00', + 'settleTime' => '2026-08-04 00:00:00', + 'invoiceApplyTypeName' => '推广费开票', + ]]; + } + + public function first(string $sql, array $bindings = []): ?array + { + $this->calls[] = ['method' => 'first', 'sql' => $sql, 'bindings' => $bindings]; + + return ['amount_total' => 12.345, 'balance_total' => 98.765, 'cnt' => 1]; + } + }; + + $factory = new class($query) extends UpstreamReadOnlyQueryFactory { + public function __construct(private readonly UpstreamReadOnlyQuery $query) + { + } + + public function forSource(string $source): UpstreamReadOnlyQuery + { + TestCase::assertSame('jiansun', $source); + + return $this->query; + } + }; + + $repository = new JdWalletBillRepository($factory); + $recharge = $repository->paginateRecharge(null, '2026-08-03', '2026-08-03', 1, 25); + $consumption = $repository->paginateConsumption(null, '2026-08-03', '2026-08-03', 1, 25); + $summary = $repository->summaryConsumption(null, '2026-08-03', '2026-08-03'); + + $this->assertSame(1, $recharge['total']); + $this->assertSame('2026-08-03 12:00:00', $recharge['rows'][0]['recharge_time']); + $this->assertSame('成功', $recharge['rows'][0]['status']); + $this->assertSame(12.35, $consumption['rows'][0]['amount']); + $this->assertSame('推广费开票', $consumption['rows'][0]['notes']); + $this->assertSame(12.35, $summary['amount_total']); + $this->assertStringContainsString('ABS(amount)', $query->calls[4]['sql']); + } +} -- 2.47.2 From 005d22169731c166458a9c4effef10f513ef66f7 Mon Sep 17 00:00:00 2001 From: chenxi Date: Thu, 6 Aug 2026 18:39:24 +0800 Subject: [PATCH 2/4] fix: align jd wallet pagination with pinduoduo --- .../partials/embedded-table.blade.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php b/resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php index 76e99f15..6bd0ec20 100644 --- a/resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php +++ b/resources/views/banking/marketing_jd_wallet_bills/partials/embedded-table.blade.php @@ -17,8 +17,17 @@ .jd-wallet-button-primary:hover { background: #6A5129; } .jd-wallet-button-secondary { border: 1px solid var(--vip-border, #D8D2C7); background: #fff; color: #171717; } .jd-wallet-button-secondary:hover { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-soft, #F7F0DE); } + .jd-wallet-pagination ul.pagination { display: flex; flex-direction: row; align-items: center; gap: 4px; margin: 0; padding: 0; list-style: none; } + .jd-wallet-pagination .page-item { margin: 0; } + .jd-wallet-pagination .page-link { display: inline-flex; min-width: 34px; height: 34px; align-items: center; justify-content: center; border: 1px solid var(--vip-border, #D8D2C7); border-radius: 8px; background: #fff; padding: 0 10px; color: #171717; font-size: 0.8125rem; text-decoration: none; } + .jd-wallet-pagination a.page-link:hover { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-soft, #F7F0DE); color: var(--vip-content-accent-dark, #856632); } + .jd-wallet-pagination .page-item.active .page-link { border-color: var(--vip-content-accent-dark, #856632); background: var(--vip-content-accent-dark, #856632); color: #fff; } + .jd-wallet-pagination .page-item.disabled .page-link { color: #595959; cursor: not-allowed; } .jd-wallet-pagination div.lg\:flex { flex-flow: row wrap; align-items: center; justify-content: center; gap: 12px; } .jd-wallet-pagination div.lg\:flex > div:first-child { display: none !important; } + .jd-wallet-pagination .pagination-xs { display: flex; width: 100%; justify-content: center; margin-top: 0; } + .jd-wallet-pagination .pagination-xs > nav, + .jd-wallet-pagination .pagination-xs > nav > ul { margin-right: auto; margin-left: auto; } @php -- 2.47.2 From 962d95fe05277734922bf2710df97638a1df68c5 Mon Sep 17 00:00:00 2001 From: chenxi Date: Fri, 7 Aug 2026 10:20:13 +0800 Subject: [PATCH 3/4] fix: rename marketing account bill tabs --- resources/views/business/pages/show.blade.php | 43 ++++++++----------- .../MarketingAccountBillsTabsTest.php | 7 ++- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/resources/views/business/pages/show.blade.php b/resources/views/business/pages/show.blade.php index 302737ca..0255e2aa 100644 --- a/resources/views/business/pages/show.blade.php +++ b/resources/views/business/pages/show.blade.php @@ -1,23 +1,24 @@ +@php + $isMarketingAccountBillsPage = ($section['slug'] ?? null) === 'billing-transactions' + && ($page['slug'] ?? null) === 'marketing-account-bills'; + $marketingAccountTabTitles = [ + 'taobao-alipay' => '淘系账户', + 'taobao-wechat' => '阿里巴巴账户', + 'jd-wallet' => '京东账户', + ]; + $activeContentTitle = $activeTab['title'] ?? ($page['title'] ?? $section['title']); + + if ($isMarketingAccountBillsPage && isset($marketingAccountTabTitles[$activeTab['slug'] ?? null])) { + $activeContentTitle = $marketingAccountTabTitles[$activeTab['slug']]; + } +@endphp + - {{ $activeTab['title'] ?? ($page['title'] ?? $section['title']) }} + {{ $activeContentTitle }} - @php - $isMarketingAccountBillsPage = ($section['slug'] ?? null) === 'billing-transactions' - && ($page['slug'] ?? null) === 'marketing-account-bills'; - $activeContentTitle = $activeTab['title'] ?? ($page['title'] ?? $section['title']); - - if ($isMarketingAccountBillsPage && ($activeTab['slug'] ?? null) === 'taobao-wechat') { - $activeContentTitle = 'Alibaba'; - } - - if ($isMarketingAccountBillsPage && ($activeTab['slug'] ?? null) === 'jd-wallet') { - $activeContentTitle = '京东'; - } - @endphp - @php @@ -41,38 +51,45 @@ $currentUrl = request()->url(); @endphp -
+
-
-
-
{{ $isConsumption ? '消耗金额合计' : '充值金额合计' }}(万元)
-
{{ number_format($amountWan, 2) }}
+
+
+
{{ $isConsumption ? '消耗金额合计' : '充值金额合计' }}(万元)
+
{{ number_format($amountWan, 2) }}
-
-
余额合计(万元)
-
{{ number_format($balanceWan, 2) }}
+
+
余额合计(万元)
+
{{ number_format($balanceWan, 2) }}
-
-
账单笔数
-
{{ number_format((int) ($summary['count'] ?? 0)) }}
+
+
账单笔数
+
{{ number_format((int) ($summary['count'] ?? 0)) }}
@if (! empty($error)) - + @endif -
+
@@ -94,7 +111,7 @@
- 清除 + 清除
共匹配 {{ $bills->total() }} 条
@@ -119,7 +136,7 @@ @endif @empty - 暂无符合条件的账单 + 当前筛选条件下没有账单,可调整筛选条件后重新查询。 @endforelse diff --git a/resources/views/business/pages/show.blade.php b/resources/views/business/pages/show.blade.php index 0255e2aa..c3bed1be 100644 --- a/resources/views/business/pages/show.blade.php +++ b/resources/views/business/pages/show.blade.php @@ -2,9 +2,8 @@ $isMarketingAccountBillsPage = ($section['slug'] ?? null) === 'billing-transactions' && ($page['slug'] ?? null) === 'marketing-account-bills'; $marketingAccountTabTitles = [ - 'taobao-alipay' => '淘系账户', - 'taobao-wechat' => '阿里巴巴账户', - 'jd-wallet' => '京东账户', + 'taobao-wechat' => 'Alibaba', + 'jd-wallet' => '京东', ]; $activeContentTitle = $activeTab['title'] ?? ($page['title'] ?? $section['title']); @@ -391,10 +390,20 @@ } -
$isMarketingAccountBillsPage, - ])> +
$isMarketingAccountBillsPage, + ]) + @if ($isMarketingAccountBillsPage) data-marketing-account-bills-workspace @endif + > + @if ($isMarketingAccountBillsPage) +

+ + @endif @if (is_string(session('success')))
{{ session('success') }}
@endif @@ -714,7 +723,11 @@ @endif
- @if (($page['slug'] ?? null) === 'store-receiving-bills') + @if ($isMarketingAccountBillsPage) + @push('scripts_start') + + @endpush + @elseif (($page['slug'] ?? null) === 'store-receiving-bills') @push('scripts_start') @endpush diff --git a/scripts/build-frontend.ps1 b/scripts/build-frontend.ps1 index ee4b4917..1dd4ee42 100644 --- a/scripts/build-frontend.ps1 +++ b/scripts/build-frontend.ps1 @@ -153,6 +153,7 @@ $jsCacheValid = Test-CacheFingerprint ` -Expected $jsFingerprint ` -RequiredOutputs @( (Join-Path $frontendOutputRoot 'js/auth/common.min.js'), + (Join-Path $frontendOutputRoot 'js/business/marketing-account-bills.min.js'), (Join-Path $frontendOutputRoot 'js/manifest.js'), (Join-Path $frontendOutputRoot 'js/vendor.js'), (Join-Path $cachedVendorPath 'apexcharts/apexcharts.min.js'), @@ -261,6 +262,7 @@ try { foreach ($output in @( 'public/js/auth/common.min.js', + 'public/js/business/marketing-account-bills.min.js', 'public/js/manifest.js', 'public/js/vendor.js', 'public/mix-manifest.json' diff --git a/tests/Feature/Business/MarketingAccountBillsTabsTest.php b/tests/Feature/Business/MarketingAccountBillsTabsTest.php index eb9c8080..be388a82 100644 --- a/tests/Feature/Business/MarketingAccountBillsTabsTest.php +++ b/tests/Feature/Business/MarketingAccountBillsTabsTest.php @@ -3,6 +3,11 @@ namespace Tests\Feature\Business; use App\Models\Auth\Permission; +use Mockery; +use Modules\EcommerceFinance\Repositories\JdWalletBillRepository; +use Modules\EcommerceFinance\Services\UpstreamQueryCache; +use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory; +use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; use Tests\Feature\FeatureTestCase; class MarketingAccountBillsTabsTest extends FeatureTestCase @@ -18,10 +23,10 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase 'tab' => 'taobao-wechat', ])) ->assertOk() - ->assertSeeText('淘系账户') - ->assertSeeText('阿里巴巴账户') - ->assertSeeText('京东账户') - ->assertDontSeeText('淘系支付宝账户') + ->assertSeeText('淘系支付宝账户') + ->assertSeeText('Alibaba') + ->assertSeeText('京东') + ->assertDontSeeText('淘系微信账户') ->assertDontSeeText('京东网银钱包账户') ->assertSee('class="my-6 business-page--marketing-account-bills"', false); } @@ -43,7 +48,9 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase ->assertSeeText('消耗账单') ->assertSeeText('金额(元)') ->assertSeeText('充值时间') - ->assertDontSeeText('投放日期'); + ->assertDontSeeText('投放日期') + ->assertSee('data-jd-wallet-panel', false) + ->assertSee('data-jd-wallet-navigation', false); $this->loginAs() ->get(route('business.pages.show', array_merge($base, ['jd_ledger' => 'consumption']))) @@ -56,6 +63,56 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase ->assertDontSeeText('充值时间'); } + public function testJdWalletColdStartRegistersEveryRequiredSnapshotScope(): void + { + $this->allowMarketingAccountBillsPage(); + $scopes = []; + $cache = Mockery::mock(UpstreamQueryCache::class); + $cache->shouldReceive('remember') + ->times(3) + ->andReturnUsing(function (string $scope, array $parameters, callable $resolver) use (&$scopes): array { + $scopes[] = $scope; + + if (count($scopes) === 1) { + throw new ServiceUnavailableHttpException(30, '首份共享快照正在准备。'); + } + + return match ($scope) { + 'jd-wallet-bills.recharge.summary' => [ + 'amount_total' => 0, + 'balance_total' => 0, + 'count' => 0, + ], + 'jd-wallet-bills.options' => [], + default => ['rows' => [], 'total' => 0], + }; + }); + + $factory = Mockery::mock(UpstreamQueryCacheFactory::class); + $factory->shouldReceive('forSource') + ->once() + ->with('jiansun') + ->andReturn($cache); + + $this->app->instance(UpstreamQueryCacheFactory::class, $factory); + $this->app->instance(JdWalletBillRepository::class, Mockery::mock(JdWalletBillRepository::class)); + + $this->loginAs() + ->get(route('business.pages.show', [ + 'section' => 'billing-transactions', + 'page' => 'marketing-account-bills', + 'tab' => 'jd-wallet', + ])) + ->assertOk() + ->assertSeeText('首份共享快照正在准备。'); + + $this->assertSame([ + 'jd-wallet-bills.recharge.list', + 'jd-wallet-bills.recharge.summary', + 'jd-wallet-bills.options', + ], $scopes); + } + private function allowMarketingAccountBillsPage(): void { $this->user->update(['locale' => 'zh-CN']); diff --git a/tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php b/tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php index 717acbec..ad6a7b36 100644 --- a/tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/JdWalletBillRepositoryTest.php @@ -75,5 +75,7 @@ class JdWalletBillRepositoryTest extends TestCase $this->assertSame('推广费开票', $consumption['rows'][0]['notes']); $this->assertSame(12.35, $summary['amount_total']); $this->assertStringContainsString('ABS(amount)', $query->calls[4]['sql']); + $this->assertStringContainsString('< DATEADD(day, 1, CAST(? AS date))', $query->calls[0]['sql']); + $this->assertSame(['2026-08-03 00:00:00', '2026-08-03'], $query->calls[0]['bindings']); } } diff --git a/tests/Unit/EcommerceFinance/JianSunSnapshotRefresherTest.php b/tests/Unit/EcommerceFinance/JianSunSnapshotRefresherTest.php index d35c3513..23cfebaa 100644 --- a/tests/Unit/EcommerceFinance/JianSunSnapshotRefresherTest.php +++ b/tests/Unit/EcommerceFinance/JianSunSnapshotRefresherTest.php @@ -7,6 +7,7 @@ use Modules\EcommerceFinance\Repositories\AlibabaBillRepository; use Modules\EcommerceFinance\Repositories\EdgeBandingRepository; use Modules\EcommerceFinance\Repositories\GoodsReconciliationRepository; use Modules\EcommerceFinance\Repositories\InventoryRepository; +use Modules\EcommerceFinance\Repositories\JdWalletBillRepository; use Modules\EcommerceFinance\Repositories\PinduoduoSpreadBillRepository; use Modules\EcommerceFinance\Repositories\ProductPriceRepository; use Modules\EcommerceFinance\Repositories\RawBoardPayablesRepository; @@ -217,6 +218,141 @@ class JianSunSnapshotRefresherTest extends TestCase $this->assertSame([['supplier' => '测试厂'], 2, 50], $rawBoardPayables->arguments); } + public function test_jd_wallet_scopes_are_available_to_background_refresh(): void + { + $jdWallet = new class extends JdWalletBillRepository { + public array $calls = []; + + public function __construct() + { + } + + public function paginateConsumption( + ?string $shopName = null, + ?string $fromDate = null, + ?string $toDate = null, + int $page = 1, + int $pageSize = 25, + ): array { + $this->calls['consumption.list'] = func_get_args(); + + return ['rows' => [], 'total' => 0]; + } + + public function paginateRecharge( + ?string $shopName = null, + ?string $fromDate = null, + ?string $toDate = null, + int $page = 1, + int $pageSize = 25, + ): array { + $this->calls['recharge.list'] = func_get_args(); + + return ['rows' => [], 'total' => 0]; + } + + public function summaryConsumption( + ?string $shopName = null, + ?string $fromDate = null, + ?string $toDate = null, + ): array { + $this->calls['consumption.summary'] = func_get_args(); + + return ['amount_total' => 0, 'balance_total' => 0, 'count' => 0]; + } + + public function summaryRecharge( + ?string $shopName = null, + ?string $fromDate = null, + ?string $toDate = null, + ): array { + $this->calls['recharge.summary'] = func_get_args(); + + return ['amount_total' => 0, 'balance_total' => 0, 'count' => 0]; + } + + public function shopOptions(): array + { + $this->calls['options'] = []; + + return ['测试京东店铺']; + } + }; + + $refresher = $this->refresher(jdWallet: $jdWallet); + $rechargeStatus = $refresher->refresh([ + 'scope' => 'jd-wallet-bills.recharge.list', + 'parameters' => [ + 'filters' => [ + 'store_name' => '测试京东店铺', + 'from' => '2026-08-01', + 'to' => '2026-08-10', + ], + 'page' => 2, + 'limit' => 50, + ], + ]); + $consumptionSummaryStatus = $refresher->refresh([ + 'scope' => 'jd-wallet-bills.consumption.summary', + 'parameters' => [ + 'filters' => [ + 'store_name' => '测试京东店铺', + 'from' => '2026-08-01', + 'to' => '2026-08-10', + ], + ], + ]); + $rechargeSummaryStatus = $refresher->refresh([ + 'scope' => 'jd-wallet-bills.recharge.summary', + 'parameters' => [ + 'filters' => [ + 'store_name' => '测试京东店铺', + 'from' => '2026-08-01', + 'to' => '2026-08-10', + ], + ], + ]); + $consumptionListStatus = $refresher->refresh([ + 'scope' => 'jd-wallet-bills.consumption.list', + 'parameters' => [ + 'filters' => [ + 'store_name' => '测试京东店铺', + 'from' => '2026-08-01', + 'to' => '2026-08-10', + ], + 'page' => 3, + 'limit' => 25, + ], + ]); + $optionsStatus = $refresher->refresh([ + 'scope' => 'jd-wallet-bills.options', + 'parameters' => [], + ]); + + $this->assertSame('fresh', $rechargeStatus); + $this->assertSame('fresh', $consumptionSummaryStatus); + $this->assertSame('fresh', $rechargeSummaryStatus); + $this->assertSame('fresh', $consumptionListStatus); + $this->assertSame('fresh', $optionsStatus); + $this->assertSame( + ['测试京东店铺', '2026-08-01', '2026-08-10', 2, 50], + $jdWallet->calls['recharge.list'], + ); + $this->assertSame( + ['测试京东店铺', '2026-08-01', '2026-08-10'], + $jdWallet->calls['consumption.summary'], + ); + $this->assertSame( + ['测试京东店铺', '2026-08-01', '2026-08-10'], + $jdWallet->calls['recharge.summary'], + ); + $this->assertSame( + ['测试京东店铺', '2026-08-01', '2026-08-10', 3, 25], + $jdWallet->calls['consumption.list'], + ); + $this->assertSame([], $jdWallet->calls['options']); + } + private function refresher( ?InventoryRepository $inventory = null, ?AlibabaBillRepository $alibaba = null, @@ -225,6 +361,7 @@ class JianSunSnapshotRefresherTest extends TestCase ?RawBoardPayablesRepository $rawBoardPayables = null, ?TmallTaobaoSpreadBillRepository $taobaoAlipay = null, ?SupplierPayableReportRepository $supplierPayableReports = null, + ?JdWalletBillRepository $jdWallet = null, ): JianSunSnapshotRefresher { return new JianSunSnapshotRefresher( new class extends JianSunQueryCache { @@ -251,6 +388,7 @@ class JianSunSnapshotRefresherTest extends TestCase $taobaoAlipay ?? $this->placeholder(TmallTaobaoSpreadBillRepository::class), $this->placeholder(SupplierPayableRepository::class), $supplierPayableReports ?? $this->placeholder(SupplierPayableReportRepository::class), + $jdWallet ?? $this->placeholder(JdWalletBillRepository::class), ); } diff --git a/webpack.mix.js b/webpack.mix.js index 13ebbb3b..259c7584 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -72,6 +72,7 @@ if (buildJavaScript) { // Business workspace(店铺管理 › 各子功能) .js('resources/assets/js/views/business/pages.js', 'public/js/business/pages.min.js') + .js('resources/assets/js/views/business/marketing-account-bills.js', 'public/js/business/marketing-account-bills.min.js') // Ecommerce Finance .js('resources/assets/js/views/ecommerce-finance/goods-reconciliations.js', 'public/js/ecommerce-finance/goods-reconciliations.min.js') -- 2.47.2