|null */ public function rows(): ?array { try { $stores = $this->storeInformation->allStores(); } catch (Throwable $exception) { report($exception); return null; } $openStores = array_values(array_filter( $stores, static fn (array $store): bool => in_array( (string) ($store['store_status'] ?? ''), ['0', '正常'], true, ) && trim((string) ($store['erp_store_name'] ?? '')) !== '', )); if ($openStores === []) { return []; } try { $periods = $this->periods->build($openStores); } catch (Throwable $exception) { report($exception); return null; } $financeShops = $this->financeShops(); $accountMaster = $this->accountMaster(); $ownership = $this->storeOwnership(); $rows = []; foreach ($openStores as $store) { $erpStoreName = trim((string) ($store['erp_store_name'] ?? '')); $companyName = null; foreach ($periods[$erpStoreName] ?? [] as $period) { // 报表把失效时间为空的最后一个期间显示为「存续中」。 if (($period['expires_at'] ?? null) === null) { $companyName = $this->nullableString($period['company_name'] ?? null); } } $financeShop = $financeShops['exact'][$erpStoreName] ?? null; if ($financeShop === null) { $normalized = JushuitanShopBalanceRepository::normalizeStoreName($erpStoreName); $financeShop = $financeShops['normalized'][$normalized] ?? null; } $storePayoutAccount = $this->nullableString($store['withdrawal_account'] ?? null); $financePayoutAccount = $this->nullableString($financeShop->payout_account ?? null); $account = $this->matchAccount( $storePayoutAccount ?? $financePayoutAccount, $this->nullableString($financeShop->account_short ?? null), $accountMaster, ); $storeManager = $this->nullableString($store['store_manager'] ?? null); $departmentManager = $this->nullableString($store['department_manager'] ?? null); $ownershipMatch = $ownership[StoreAccountMirrorRepository::storeNameKey($erpStoreName)] ?? null; if ($ownershipMatch !== null) { if (trim((string) ($ownershipMatch['operation_owner'] ?? '')) !== '') { $storeManager = trim((string) $ownershipMatch['operation_owner']); } if (trim((string) ($ownershipMatch['operation_department_head'] ?? '')) !== '') { $departmentManager = trim((string) $ownershipMatch['operation_department_head']); } } $rows[] = [ 'id' => (int) ($store['id'] ?? 0), 'account_match_key' => $this->normalizeAccountValue($storePayoutAccount ?? $financePayoutAccount), 'platform' => $this->nullableString($store['platform'] ?? null), 'shop_name' => $erpStoreName, 'company_name' => $companyName, 'account_short' => $account['account_short'] ?? null, 'status' => '在营', 'store_manager' => $storeManager, 'department_manager' => $departmentManager, 'payout_account' => $this->nullableString($account['account_no'] ?? null), 'shop_balance' => round((float) ($financeShop->shop_balance ?? 0), 2), 'shop_balance_date' => $this->nullableString($financeShop->shop_balance_date ?? null), 'remark' => $this->nullableString($financeShop->remark ?? null), ]; } return $this->propagateAccountLinks($rows); } /** * 与店铺维度报表一致:店铺账号镜像有值时覆盖店铺资料中的负责人字段。 * * @return array */ private function storeOwnership(): array { try { return $this->storeAccounts->ownershipByStoreName(); } catch (Throwable) { return []; } } /** @return array{exact:array,normalized:array} */ private function financeShops(): array { $connection = $this->databases->connection( (string) config('upstream_mirrors.connection', 'business_mirror'), ); if (! Schema::connection($connection->getName())->hasTable('fin_shops')) { return ['exact' => [], 'normalized' => []]; } $rows = $connection->table('fin_shops')->get(); $exact = []; $normalizedGroups = []; foreach ($rows as $row) { $name = trim((string) ($row->shop_name ?? '')); if ($name === '') { continue; } $exact[$name] = $row; $normalizedGroups[JushuitanShopBalanceRepository::normalizeStoreName($name)][] = $row; } return [ 'exact' => $exact, // 规范化名称重复时置空,避免跨平台误匹配。 'normalized' => array_map( static fn (array $group): ?object => count($group) === 1 ? $group[0] : null, $normalizedGroups, ), ]; } /** * @return array{ * by_account_no:array, * by_short:array * } */ private function accountMaster(): array { $connection = $this->databases->connection( (string) config('upstream_mirrors.connection', 'business_mirror'), ); if (! Schema::connection($connection->getName())->hasTable('fin_accounts')) { return ['by_account_no' => [], 'by_short' => []]; } $accounts = $connection->table('fin_accounts') ->get(['account_short', 'account_no', 'bank_name', 'account_type']) ->filter(function (object $account): bool { $label = (string) ($account->account_type ?? '') . ' ' . (string) ($account->bank_name ?? ''); return mb_stripos($label, '网商银行') === false && mb_stripos($label, '支付宝') === false && trim((string) ($account->account_short ?? '')) !== ''; }); $byAccountNo = []; foreach ($accounts->groupBy( fn (object $account): string => $this->normalizeAccountValue($account->account_no ?? null), ) as $key => $group) { $identities = $group ->map(fn (object $account): string => $this->accountIdentity($account)) ->unique(); if ($key !== '' && $identities->count() === 1) { $byAccountNo[$key] = $this->accountFields($group->first()); } } $byShort = []; foreach ($accounts->groupBy( static fn (object $account): string => trim((string) ($account->account_short ?? '')), ) as $short => $group) { if ($short !== '' && $group->count() === 1) { $byShort[$short] = $this->accountFields($group->first()); } } return [ 'by_account_no' => $byAccountNo, 'by_short' => $byShort, ]; } /** * @param array{ * by_account_no:array, * by_short:array * } $accountMaster * @return array{account_short:string,account_no:?string}|null */ private function matchAccount( ?string $payoutAccount, ?string $existingShort, array $accountMaster, ): ?array { $accountNo = $this->normalizeAccountValue($payoutAccount); if ($accountNo !== '' && isset($accountMaster['by_account_no'][$accountNo])) { return $accountMaster['by_account_no'][$accountNo]; } $existingShort = trim((string) $existingShort); return $existingShort !== '' && isset($accountMaster['by_short'][$existingShort]) ? $accountMaster['by_short'][$existingShort] : null; } private function normalizeAccountValue(mixed $value): string { return preg_replace('/[^a-z0-9]+/i', '', mb_strtolower(trim((string) $value))) ?? ''; } /** * 同一回款账号已在其他店铺匹配到唯一账户总表记录时,可安全复用其 * 账户简称和账号;同账号出现多个账户记录时视为歧义,不传播。 * * @param list> $rows * @return list> */ private function propagateAccountLinks(array $rows): array { $knownGroups = []; foreach ($rows as $row) { $payoutKey = (string) ($row['account_match_key'] ?? ''); $accountShort = trim((string) ($row['account_short'] ?? '')); if ($payoutKey !== '' && $accountShort !== '') { $knownGroups[$payoutKey][] = [ 'account_short' => $accountShort, 'account_no' => $row['payout_account'] ?? null, ]; } } $knownAccounts = []; foreach ($knownGroups as $payoutKey => $accounts) { $uniqueAccounts = []; foreach ($accounts as $account) { $identity = $this->normalizeAccountValue($account['account_no'] ?? null) . "\0" . trim((string) ($account['account_short'] ?? '')); $uniqueAccounts[$identity] = $account; } if (count($uniqueAccounts) === 1) { $knownAccounts[$payoutKey] = reset($uniqueAccounts); } } foreach ($rows as &$row) { if (trim((string) ($row['account_short'] ?? '')) !== '') { continue; } $payoutKey = (string) ($row['account_match_key'] ?? ''); if ($payoutKey !== '' && isset($knownAccounts[$payoutKey])) { $row['account_short'] = $knownAccounts[$payoutKey]['account_short']; $row['payout_account'] = $knownAccounts[$payoutKey]['account_no']; } } unset($row); foreach ($rows as &$row) { unset($row['account_match_key']); } unset($row); return $rows; } /** @return array{account_short:string,account_no:?string} */ private function accountFields(object $account): array { return [ 'account_short' => trim((string) ($account->account_short ?? '')), 'account_no' => $this->nullableString($account->account_no ?? null), ]; } private function accountIdentity(object $account): string { return $this->normalizeAccountValue($account->account_no ?? null) . "\0" . trim((string) ($account->account_short ?? '')); } private function nullableString(mixed $value): ?string { $value = trim((string) $value); return $value === '' ? null : $value; } }