接入淘系支付宝账单副本库全量数据

This commit is contained in:
chenxi 2026-09-19 01:49:17 +08:00
parent c3da871a73
commit dfc86f5d81
2 changed files with 59 additions and 105 deletions

View File

@ -28,11 +28,6 @@ class StoreReceivingAlipayBillMirrorRepository
/** @return array{data:array<int,array<string,mixed>>,total:int,current_page:int,last_page:int,per_page:int,summary:array{income:float,expense:float,balance:float,count:int},sync:array<string,mixed>} */
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<int,array<string,mixed>> */
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<int,array<string,mixed>> */
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<int,object> $rows @return array<string,object> */
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<int,object> $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<string,mixed> */
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));

View File

@ -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