connection = $databases->connection( (string) config('upstream_mirrors.connection', 'business_mirror'), ); } /** * @return array{ * date:string,total:int,receiving_bill:int,jushuitan:int, * replaced:int,source_errors:list * } */ public function sync(string $balanceDate): array { $date = CarbonImmutable::createFromFormat('!Y-m-d', $balanceDate); if (! $date instanceof CarbonImmutable || $date->format('Y-m-d') !== $balanceDate) { throw new InvalidArgumentException('店铺余额日期必须使用 YYYY-MM-DD 格式。'); } $rows = []; $sourceErrors = []; $receivingBillCount = 0; foreach ([FinanceBillBalanceRepository::SCOPE_TAOBAO, FinanceBillBalanceRepository::SCOPE_JD_WALLET] as $scope) { try { foreach ($this->billBalances->latestBalances($scope, $balanceDate) as $source) { $key = $this->key( (string) ($source['source_platform'] ?? ''), (string) ($source['store_name'] ?? ''), ); if ($key === '') { continue; } $rows[$key] = $this->snapshotRow( $balanceDate, self::DATA_SOURCE_RECEIVING_BILL, $key, $source, ); $receivingBillCount++; } } catch (Throwable $exception) { report($exception); $sourceErrors[] = $scope . ':' . $exception->getMessage(); } } $legacy = $this->connection->table(JushuitanShopBalanceRepository::TABLE) ->where('balance_date', $balanceDate) ->where('source_scope', '<>', self::SOURCE_SCOPE) ->orderByRaw("CASE WHEN data_source = 'jushuitan' THEN 0 ELSE 1 END") ->orderBy('id') ->get(); $jushuitanCount = 0; foreach ($legacy as $source) { $key = $this->key( (string) ($source->source_platform ?? ''), (string) ($source->store_name ?? ''), ); if ($key === '' || isset($rows[$key])) { continue; } $dataSource = (string) ($source->data_source ?? JushuitanShopBalanceRepository::DATA_SOURCE_JUSHUITAN); $rows[$key] = $this->snapshotRow($balanceDate, $dataSource, $key, (array) $source); if ($dataSource === JushuitanShopBalanceRepository::DATA_SOURCE_JUSHUITAN) { $jushuitanCount++; } } $rows = array_values($rows); $existing = $this->connection->table(JushuitanShopBalanceRepository::TABLE) ->where('balance_date', $balanceDate) ->where('source_scope', self::SOURCE_SCOPE) ->count(); if ($rows === []) { return [ 'date' => $balanceDate, 'total' => 0, 'receiving_bill' => 0, 'jushuitan' => 0, 'replaced' => $existing, 'source_errors' => $sourceErrors, ]; } $this->connection->transaction(function () use ($balanceDate, $rows): void { $this->connection->table(JushuitanShopBalanceRepository::TABLE) ->where('balance_date', $balanceDate) ->where('source_scope', self::SOURCE_SCOPE) ->delete(); foreach (array_chunk($rows, 500) as $chunk) { $this->connection->table(JushuitanShopBalanceRepository::TABLE)->insert($chunk); } }); return [ 'date' => $balanceDate, 'total' => count($rows), 'receiving_bill' => $receivingBillCount, 'jushuitan' => $jushuitanCount, 'replaced' => $existing, 'source_errors' => $sourceErrors, ]; } /** * @param array $source * @return array */ private function snapshotRow(string $balanceDate, string $dataSource, string $key, array $source): array { $now = now(); $storeName = trim((string) ($source['store_name'] ?? '')); $normalized = trim((string) ($source['normalized_store_name'] ?? '')); if ($normalized === '') { $normalized = JushuitanShopBalanceRepository::normalizeStoreName($storeName); } $sourceFileName = trim((string) ($source['source_file_name'] ?? '')); if ($sourceFileName === '') { $sourceFileName = match ($dataSource) { self::DATA_SOURCE_RECEIVING_BILL => '财务系统/店铺收款账户账单', JushuitanShopBalanceRepository::DATA_SOURCE_FINANCE => '财务系统账单补录', default => '聚水潭店铺余额', }; } return [ 'balance_date' => $balanceDate, 'source_scope' => self::SOURCE_SCOPE, 'data_source' => $dataSource, 'source_platform' => trim((string) ($source['source_platform'] ?? '')) ?: null, 'store_name' => $storeName, 'normalized_store_name' => $normalized, 'platform_account_id' => trim((string) ($source['platform_account_id'] ?? '')) ?: null, 'balance' => round((float) ($source['balance'] ?? 0), 6), 'source_row_key' => hash('sha256', implode("\0", [ self::SOURCE_SCOPE, $dataSource, $key, ])), 'source_file_name' => mb_substr($sourceFileName, 0, 255), 'source_sha256' => hash('sha256', implode("\0", [ $balanceDate, $dataSource, $key, (string) ($source['occurred_at'] ?? ''), number_format((float) ($source['balance'] ?? 0), 6, '.', ''), ])), 'imported_at' => $now, 'created_at' => $now, 'updated_at' => $now, ]; } private function key(string $sourcePlatform, string $storeName): string { $storeName = trim($storeName); if ($storeName === '') { return ''; } $platform = JushuitanShopBalanceRepository::platformKey($sourcePlatform) ?? mb_strtolower(trim($sourcePlatform)); $normalized = JushuitanShopBalanceRepository::normalizeStoreName($storeName); return $normalized === '' ? '' : $platform . "\0" . $normalized; } public static function sourceLabel(?string $dataSource, ?string $sourceFileName = null): string { return match ($dataSource) { self::DATA_SOURCE_RECEIVING_BILL => str_replace( '财务系统/店铺收款账户账单/', '店铺收款账户账单 · ', trim((string) $sourceFileName) ?: '财务系统/店铺收款账户账单', ), JushuitanShopBalanceRepository::DATA_SOURCE_FINANCE => '财务系统账单补录', JushuitanShopBalanceRepository::DATA_SOURCE_JUSHUITAN => '聚水潭店铺余额', default => trim((string) $sourceFileName) ?: '未标明来源', }; } }