diff --git a/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php b/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php index c7c74b07..eb51b30d 100644 --- a/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/StoreReceivingAlipayBillMirrorRepository.php @@ -28,11 +28,6 @@ class StoreReceivingAlipayBillMirrorRepository /** @return array{data:array>,total:int,current_page:int,last_page:int,per_page:int,summary:array{income:float,expense:float,balance:float,count:int},sync:array} */ public function paginate(array $filters, string $sort, string $direction, int $page, int $perPage): array { - $companyId = (int) (company_id() ?: 1); - if ($this->hasImportedRows($companyId)) { - return $this->paginateImported($companyId, $filters, $sort, $direction, $page, $perPage); - } - return $this->paginateChannel($filters, $sort, $direction, $page, $perPage, 'alipayCSV', 'taobao', self::DATASET, true); } @@ -96,8 +91,9 @@ class StoreReceivingAlipayBillMirrorRepository ); $shopNames = $this->shopNames($billRows); $productNames = $mapping === 'taobao' ? $this->productNames($billRows) : []; + $importedRows = $mapping === 'taobao' ? $this->importedRows($billRows) : []; $rows = collect($billRows) - ->map(fn (object $row): array => $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping, $productNames)) + ->map(fn (object $row): array => $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping, $productNames, $importedRows)) ->all(); $income = $summary['income']; $expense = $summary['expense']; @@ -124,99 +120,9 @@ class StoreReceivingAlipayBillMirrorRepository /** @return iterable> */ public function exportRows(array $filters, string $sort = 'occurred_at', string $direction = 'asc'): iterable { - $companyId = (int) (company_id() ?: 1); - if ($this->hasImportedRows($companyId)) { - return $this->exportImportedRows($companyId, $filters, $sort, $direction); - } - return $this->exportChannelRows($filters, $sort, $direction, 'alipayCSV', 'taobao', self::DATASET, true); } - private function hasImportedRows(int $companyId): bool - { - return Schema::hasTable('ec_alipay_bills') - && DB::table('ec_alipay_bills')->where('company_id', $companyId)->whereNull('deleted_at')->exists(); - } - - private function paginateImported(int $companyId, array $filters, string $sort, string $direction, int $page, int $perPage): array - { - $page = max($page, 1); - $perPage = min(max($perPage, 1), 100); - $direction = $direction === 'asc' ? 'asc' : 'desc'; - $query = $this->importedQuery($companyId, $filters); - $total = (clone $query)->count(); - $summary = (clone $query)->selectRaw( - 'COALESCE(SUM(income), 0) AS income, COALESCE(SUM(expense), 0) AS expense' - )->first(); - $income = round((float) ($summary->income ?? 0), 2); - $expense = round((float) ($summary->expense ?? 0), 2); - $sortColumn = in_array($sort, ['record_code', 'occurred_at', 'income', 'expense', 'balance'], true) - ? $sort : 'occurred_at'; - $rows = $query->orderBy($sortColumn, $direction)->orderBy('id', $direction) - ->forPage($page, $perPage)->get()->map(fn (object $row): array => $this->mapImportedRow($row))->all(); - - return [ - 'data' => $rows, - 'total' => $total, - 'current_page' => $page, - 'last_page' => max((int) ceil($total / $perPage), 1), - 'per_page' => $perPage, - 'summary' => [ - 'income' => $income, - 'expense' => $expense, - 'balance' => round($income - $expense, 2), - 'count' => $total, - ], - 'sync' => ['status' => 'attachment-import', 'last_succeeded_at' => null], - ]; - } - - private function exportImportedRows(int $companyId, array $filters, string $sort, string $direction): iterable - { - $direction = $direction === 'desc' ? 'desc' : 'asc'; - $sortColumn = in_array($sort, ['record_code', 'occurred_at', 'income', 'expense', 'balance'], true) - ? $sort : 'occurred_at'; - foreach ($this->importedQuery($companyId, $filters)->orderBy($sortColumn, $direction) - ->orderBy('id', $direction)->cursor() as $row) { - yield $this->mapImportedRow($row); - } - } - - private function importedQuery(int $companyId, array $filters): Builder - { - $query = DB::table('ec_alipay_bills')->where('company_id', $companyId)->whereNull('deleted_at'); - if (($filters['store_name'] ?? '') !== '') $query->where('store_name', 'like', '%' . $this->escapeLike((string) $filters['store_name']) . '%'); - if (($filters['bill_item'] ?? '') !== '') $query->where('bill_item', 'like', '%' . $this->escapeLike((string) $filters['bill_item']) . '%'); - if (($filters['business_type'] ?? '') !== '') $query->where('business_type', $filters['business_type']); - if (($filters['start'] ?? '') !== '') $query->where('occurred_at', '>=', $filters['start'] . ' 00:00:00'); - if (($filters['end'] ?? '') !== '') $query->where('occurred_at', '<', date('Y-m-d', strtotime($filters['end'] . ' +1 day')) . ' 00:00:00'); - if (($filters['source_channel'] ?? '') !== '') $query->whereRaw('1 = 0'); - if (($filters['keyword'] ?? '') !== '') { - $value = '%' . $this->escapeLike((string) $filters['keyword']) . '%'; - $query->where(function (Builder $query) use ($value): void { - foreach (['record_code', 'bill_item', 'account_order_no', 'business_serial_no', 'merchant_order_no', 'business_base_order_no', 'goods_name', 'online_order_no', 'business_order_no'] as $column) { - $query->orWhere($column, 'like', $value); - } - }); - } - return $query; - } - - private function mapImportedRow(object $row): array - { - $fields = [ - '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', - ]; - $mapped = ['id' => (string) $row->id]; - foreach ($fields as $field) $mapped[$field] = $row->{$field} ?? null; - foreach (['income', 'expense', 'balance'] as $field) $mapped[$field] = (float) ($mapped[$field] ?? 0); - return $mapped; - } - /** @return iterable> */ public function exportChannelRows( array $filters, @@ -337,8 +243,9 @@ class StoreReceivingAlipayBillMirrorRepository $rows = $batch->select('b.*')->orderBy($sortColumn, $direction) ->orderBy('b.Id', $direction)->limit(1000)->get(); $productNames = $mapping === 'taobao' ? $this->productNames($rows->all()) : []; + $importedRows = $mapping === 'taobao' ? $this->importedRows($rows->all()) : []; foreach ($rows as $row) { - yield $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping, $productNames); + yield $this->mapRowFor($row, $shopNames[(string) $row->shop_id] ?? null, $mapping, $productNames, $importedRows); } $last = $rows->last(); } while ($rows->count() === 1000); @@ -609,6 +516,25 @@ class StoreReceivingAlipayBillMirrorRepository ->all(); } + /** @param array $rows @return array */ + private function importedRows(array $rows): array + { + if (! Schema::hasTable('ec_alipay_bills')) { + return []; + } + + $recordCodes = collect($rows)->pluck('p_auto_id')->filter()->unique()->values()->all(); + if ($recordCodes === []) { + return []; + } + + return DB::table('ec_alipay_bills') + ->where('company_id', (int) (company_id() ?: 1)) + ->whereNull('deleted_at') + ->whereIn('record_code', $recordCodes) + ->get()->keyBy(fn (object $row): string => (string) $row->record_code)->all(); + } + /** @param array $rows */ public function mapDouyinRows(array $rows): array { @@ -616,13 +542,13 @@ class StoreReceivingAlipayBillMirrorRepository return array_map(fn (object $row): array => $this->mapRowFor($row, $shops[(string) $row->shop_id] ?? null, 'douyin'), $rows); } - private function mapRow(object $row, ?string $shopName, array $productNames = []): array + private function mapRow(object $row, ?string $shopName, array $productNames = [], array $importedRows = []): array { $amount = abs((float) ($row->amount ?? 0)); $income = (string) ($row->inout_type ?? '') === '1' ? $amount : 0.0; $expense = (string) ($row->inout_type ?? '') === '-1' ? $amount : 0.0; - return [ + $mapped = [ 'id' => (string) $row->Id, 'record_code' => (string) ($row->p_auto_id ?: $row->Id), 'store_name' => $shopName, @@ -650,10 +576,29 @@ class StoreReceivingAlipayBillMirrorRepository 'updated_at' => $this->dateTime($row->updateTime), 'modifier' => '', ]; + + $imported = $importedRows[$mapped['record_code']] ?? null; + if ($imported === null) { + return $mapped; + } + + foreach ([ + 'custom_remark', 'alipay_account', 'account_order_no', 'business_serial_no', + 'merchant_order_no', 'business_base_order_no', 'counterparty_account', 'balance', + 'business_type', 'remark', 'business_description', 'business_bill_source', + 'business_order_no', 'modifier', + ] as $field) { + $mapped[$field] = $imported->{$field} ?? $mapped[$field]; + } + if ($mapped['goods_name'] === null || $mapped['goods_name'] === '') { + $mapped['goods_name'] = $imported->goods_name ?? null; + } + + return $mapped; } /** @return array */ - private function mapRowFor(object $row, ?string $shopName, string $mapping, array $productNames = []): array + private function mapRowFor(object $row, ?string $shopName, string $mapping, array $productNames = [], array $importedRows = []): array { if ($mapping === 'pinduoduo') { return $this->mapPinduoduoRow($row, $shopName); @@ -664,7 +609,7 @@ class StoreReceivingAlipayBillMirrorRepository } if ($mapping !== 'douyin') { - return $this->mapRow($row, $shopName, $productNames); + return $this->mapRow($row, $shopName, $productNames, $importedRows); } $amount = abs((float) ($row->amount ?? 0)); diff --git a/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php b/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php index a85b67bc..f6c4c46a 100644 --- a/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/StoreReceivingAlipayBillMirrorRepositoryTest.php @@ -108,10 +108,17 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase $this->repository->paginate([], 'occurred_at', 'desc', 1, 10); } - public function test_imported_attachment_rows_replace_the_mirror_for_the_taobao_page(): void + public function test_imported_attachment_rows_enrich_matching_mirror_rows_for_the_taobao_page(): void { + $this->markReady(); + DB::connection('business_mirror')->table('ops_jst_shop_info')->insert([ + 'Id' => 10, 'shop_id' => 7, 'shop_name' => '天猫-经营店', 'shop_site' => '淘宝天猫', + ]); + DB::connection('business_mirror')->table('ops_jst_original_bill_info')->insert( + $this->row(1, 7, '2026-08-01 09:00:00', '1', 100.25), + ); DB::table('ec_alipay_bills')->insert([ - 'company_id' => 1, 'record_code' => 'ATTACHMENT-1', 'store_name' => '天猫-附件店铺', + 'company_id' => 1, 'record_code' => 'P-1', 'store_name' => '天猫-附件店铺', 'custom_remark' => '模板数据', 'bill_item' => '交易收款', 'alipay_account' => '20880001', 'account_order_no' => 'ACCOUNT-1', 'business_serial_no' => 'SERIAL-1', 'merchant_order_no' => 'MERCHANT-1', 'business_base_order_no' => 'BASE-1', @@ -127,10 +134,12 @@ class StoreReceivingAlipayBillMirrorRepositoryTest extends TestCase $result = $this->repository->paginate([], 'occurred_at', 'desc', 1, 10); $this->assertSame(1, $result['total']); - $this->assertSame('ATTACHMENT-1', $result['data'][0]['record_code']); + $this->assertSame('P-1', $result['data'][0]['record_code']); + $this->assertSame('天猫-经营店', $result['data'][0]['store_name']); $this->assertSame('20880001', $result['data'][0]['alipay_account']); $this->assertSame(300.50, $result['data'][0]['balance']); - $this->assertSame('attachment-import', $result['sync']['status']); + $this->assertSame(100.25, $result['summary']['income']); + $this->assertSame('succeeded', $result['sync']['status']); } public function test_export_uses_the_same_filters_and_mapping_as_the_page(): void