'operations_replica.rpt.fys_companyInfo', 'store-information.read' => 'operations_replica.rpt.fys_shopInfo', 'trademark-information.read' => 'operations_replica.rpt.fys_TrademarkInfo', ]; private readonly ConnectionInterface $connection; public function __construct( private readonly BusinessMirrorDatasetRegistry $datasets, private readonly GenericBusinessMirrorRepository $mirror, ) { $this->connection = $mirror->connection(); } public function ready(string $scope): bool { return $this->mirror->ready($this->dataset($scope)); } /** @return array{data: array>, total: int} */ public function paginate(string $scope, int $page, int $perPage, array $filters = []): array { if (! $this->ready($scope)) { throw new RuntimeException('business_mirror_dataset_not_ready'); } $page = max($page, 1); $perPage = min(max($perPage, 1), 100); return match ($scope) { 'company-information.read' => $this->companies($page, $perPage, $filters), 'store-information.read' => $this->stores($page, $perPage, $filters), 'trademark-information.read' => $this->trademarks($page, $perPage, $filters), default => throw new InvalidArgumentException('未知的业务资料镜像范围:' . $scope), }; } /** @param array $filters @return array> */ public function allStores(array $filters = []): array { if (! $this->ready('store-information.read')) { throw new RuntimeException('business_mirror_dataset_not_ready'); } $query = $this->storeQuery()->orderByDesc('s.Id'); $this->applyStoreFilters($query, $filters); return $query ->get() ->map(static fn (object $row): array => (array) $row) ->all(); } /** * Return the stable store-information rows used to seed the receiving * account pages. The query stays on the shared business mirror and only * reads the approved shop/company fields. * * @return list> */ public function receivingAccountStores(): array { if (! $this->ready('store-information.read')) { throw new RuntimeException('business_mirror_dataset_not_ready'); } $latestCompanies = $this->connection->table('ops_fys_company_info') ->selectRaw('unifiedSocialCreditCode, MAX(Id) AS latest_id') ->whereNotNull('unifiedSocialCreditCode') ->where('unifiedSocialCreditCode', '<>', '') ->groupBy('unifiedSocialCreditCode'); return $this->connection->table('ops_fys_shop_info AS s') ->leftJoinSub($latestCompanies, 'company_keys', function ($join): void { $join->on('company_keys.unifiedSocialCreditCode', '=', 's.unifiedSocialCreditCode'); }) ->leftJoin('ops_fys_company_info AS c', 'c.Id', '=', 'company_keys.latest_id') ->selectRaw(<<<'SQL' s.Id AS id, s.sotreId AS store_id, s.shopName AS shop_name, s.erpShopName AS erp_store_name, s.unifiedSocialCreditCode AS unified_social_credit_code, c.businessLicenseName AS company_name, s.platform AS source_platform, s.shopState AS store_status, s.shopType AS store_type SQL) ->orderBy('s.Id') ->get() ->map(static fn (object $row): array => (array) $row) ->all(); } /** 店铺维度报表的店铺字段查询条件;日期左闭右开 [from, to)。 */ private function applyStoreFilters($query, array $filters): void { foreach ([ 'erp_store_name' => 's.erpShopName', 'store_name' => 's.shopName', 'store_id' => 's.sotreId', 'profit_center' => 's.responsibleDepartment', 'store_manager' => 's.shopHead', 'department_manager' => 's.customerDeptManager', ] as $key => $column) { $value = trim((string) ($filters[$key] ?? '')); if ($value !== '') { $query->where($column, 'like', '%' . addcslashes($value, '%_\\') . '%'); } } if (trim((string) ($filters['company_type'] ?? '')) !== '') { $query->where('c.businessLicenseType', '=', trim((string) $filters['company_type'])); } if (trim((string) ($filters['status'] ?? '')) !== '') { $query->where('s.shopState', '=', (string) $filters['status']); } if (trim((string) ($filters['opened_at_from'] ?? '')) !== '') { $query->where('s.openTime', '>=', $filters['opened_at_from']); } if (trim((string) ($filters['opened_at_to'] ?? '')) !== '') { $query->where('s.openTime', '<', $filters['opened_at_to']); } } /** @return array>> */ public function companiesByNames(array $names): array { if (! $this->ready('company-information.read')) { throw new RuntimeException('business_mirror_dataset_not_ready'); } $names = array_values(array_unique(array_filter(array_map('trim', $names)))); if ($names === []) { return []; } return $this->connection->table('ops_fys_company_info') ->whereIn('businessLicenseName', $names) ->orderByDesc('Id') ->get() ->map(static fn (object $row): array => (array) $row) ->groupBy(static fn (array $row): string => (string) $row['businessLicenseName']) ->map(static fn ($rows): array => $rows->values()->all()) ->all(); } /** @return array{data: array>, total: int} */ private function companies(int $page, int $perPage, array $filters): array { $query = $this->connection->table('ops_fys_company_info') ->selectRaw(<<<'SQL' Id AS id, unifiedSocialCreditCode AS unified_social_credit_code, businessLicenseName AS company_name, registeredCapital AS subscribed_capital, businessLicenseType AS company_type, IncorporationDate AS establishment_date, legalRepresentative AS legal_representative, registeredAddress AS registered_address, businessScope AS business_scope, accountSerialNumber AS basic_deposit_account_number, registeredCapitalVaildTime AS subscribed_at, companyStatus AS company_status, financialManager AS financial_officer, supervisor AS supervisor, taxCategoryAndType AS taxpayer_category, NULL AS shareholders SQL); $this->applyFieldFilters($query, $filters, [ 'unified_social_credit_code' => 'unifiedSocialCreditCode', 'company_name' => 'businessLicenseName', 'establishment_date' => 'IncorporationDate', 'registered_at' => 'IncorporationDate', 'legal_representative' => 'legalRepresentative', 'registered_address' => 'registeredAddress', 'city' => 'registeredAddress', 'business_scope' => 'businessScope', 'subscribed_at' => 'registeredCapitalVaildTime', 'financial_officer' => 'financialManager', 'billing_officer' => 'financialManager', 'supervisor' => 'supervisor', ], ['subscribed_capital' => 'registeredCapital'], [ 'company_type' => 'businessLicenseType', 'taxpayer_type' => 'taxCategoryAndType', 'taxpayer_category' => 'taxCategoryAndType', ]); $companyStatus = trim((string) ($filters['company_status'] ?? '')); if ($companyStatus !== '') { $statusCode = ['正常' => '0', '已注销' => '1'][$companyStatus] ?? $companyStatus; $query->where('companyStatus', '=', $statusCode); } $relatedPeople = mb_substr(trim((string) ($filters['related_people'] ?? '')), 0, 191); if ($relatedPeople !== '') { $like = '%' . addcslashes($relatedPeople, '%_\\') . '%'; $query->where(function ($query) use ($like): void { $query->where('legalRepresentative', 'like', $like) ->orWhere('supervisor', 'like', $like) ->orWhere('financialManager', 'like', $like); }); } return $this->page($query, $page, $perPage, 'Id', $filters, [ 'unified_social_credit_code' => 'unifiedSocialCreditCode', 'company_name' => 'businessLicenseName', 'taxpayer_type' => 'taxCategoryAndType', 'company_status' => 'companyStatus', 'subscribed_capital' => 'registeredCapital', 'registered_at' => 'IncorporationDate', 'legal_representative' => 'legalRepresentative', 'supervisor' => 'supervisor', 'billing_officer' => 'financialManager', 'registered_address' => 'registeredAddress', 'city' => 'registeredAddress', 'business_scope' => 'businessScope', 'basic_deposit_account' => 'accountSerialNumber', 'subscribed_at' => 'registeredCapitalVaildTime', ]); } /** @return array{data: array>, total: int} */ private function stores(int $page, int $perPage, array $filters): array { $query = $this->storeQuery(); $this->applyFieldFilters($query, $filters, [ 'store_id' => 's.sotreId', 'store_name' => 's.erpShopName', 'store_entity' => 'c.businessLicenseName', 'trademark' => 't.trademarkName', 'trademark_number' => 's.trademarkNumber', 'business_department' => 's.responsibleDepartment', 'store_manager' => 's.shopHead', 'department_manager' => 's.customerDeptManager', 'opened_at' => 's.openTime', 'renewed_at' => 's.renewalTime', ], [], ['platform' => 's.platform']); $storeStatus = trim((string) ($filters['store_status'] ?? '')); if ($storeStatus !== '') { $statusCode = ['正常' => '0', '关闭中' => '3', '已关闭' => '6'][$storeStatus] ?? $storeStatus; $query->where('s.shopState', '=', $statusCode); } $withdrawalAccount = trim((string) ($filters['withdrawal_account'] ?? '')); if ($withdrawalAccount === '已配置') { $query->whereNotNull('s.shopBankAccount')->where('s.shopBankAccount', '<>', ''); } elseif ($withdrawalAccount === '未配置') { $query->where(function ($query): void { $query->whereNull('s.shopBankAccount')->orWhere('s.shopBankAccount', '=', ''); }); } return $this->page($query, $page, $perPage, 's.Id', $filters, [ 'platform' => 's.platform', 'store_id' => 's.sotreId', 'store_name' => 's.erpShopName', 'store_entity' => 'c.businessLicenseName', 'store_status' => 's.shopState', 'trademark' => 't.trademarkName', 'trademark_number' => 's.trademarkNumber', 'business_department' => 's.responsibleDepartment', 'store_manager' => 's.shopHead', 'department_manager' => 's.customerDeptManager', 'opened_at' => 's.openTime', 'renewed_at' => 's.renewalTime', 'withdrawal_account' => 's.shopBankAccount', ]); } private function storeQuery() { $latestCompanies = $this->connection->table('ops_fys_company_info') ->selectRaw('unifiedSocialCreditCode, MAX(Id) AS latest_id') ->whereNotNull('unifiedSocialCreditCode') ->where('unifiedSocialCreditCode', '<>', '') ->groupBy('unifiedSocialCreditCode'); $query = $this->connection->table('ops_fys_shop_info AS s') ->leftJoinSub($latestCompanies, 'company_keys', function ($join): void { $join->on('company_keys.unifiedSocialCreditCode', '=', 's.unifiedSocialCreditCode'); }) ->leftJoin('ops_fys_company_info AS c', 'c.Id', '=', 'company_keys.latest_id') ->leftJoinSub( $this->connection->table('ops_fys_trademark_info AS trademark_keys') ->selectRaw('trademark_keys.trademarkNumber, MAX(trademark_keys.Id) AS latest_id') ->where('trademark_keys.isDelete', 0) ->whereNotNull('trademark_keys.trademarkNumber') ->where('trademark_keys.trademarkNumber', '<>', '') ->groupBy('trademark_keys.trademarkNumber'), 'trademark_keys', function ($join): void { $join->on('trademark_keys.trademarkNumber', '=', 's.trademarkNumber'); }, ) ->leftJoin('ops_fys_trademark_info AS t', 't.Id', '=', 'trademark_keys.latest_id') ->selectRaw(<<<'SQL' s.Id AS id, s.sotreId AS store_id, s.openTime AS opened_at, s.renewalTime AS renewed_at, s.erpShopName AS erp_store_name, s.shopName AS store_name, s.unifiedSocialCreditCode AS unified_social_credit_code, c.businessLicenseName AS company_name, c.businessLicenseType AS company_type, s.trademarkNumber AS trademark_number, t.trademarkName AS trademark_name, s.platform AS platform, s.responsibleDepartment AS profit_center, s.shopHead AS store_manager, s.customerDeptManager AS department_manager, s.shopState AS store_status, s.shopBankAccount AS withdrawal_account, s.shopType AS store_type, s.channelType AS channel_mode SQL); return $query; } /** @return array{data: array>, total: int} */ private function trademarks(int $page, int $perPage, array $filters): array { $query = $this->connection->table('ops_fys_trademark_info') ->where('isDelete', 0) ->selectRaw(<<<'SQL' Id AS id, trademarkNumber AS trademark_number, trademarkName AS trademark_name, internationalClassification AS trademark_type, trademarkStatus AS trademark_status, registrationDate AS registration_date, termOfValidity AS expiry_date, approvedCommodities AS approved_goods, transferenceUser AS final_holder, registrationAddress AS registered_address, custodyDepartment AS custody_department, remark AS notes SQL); $this->applyFieldFilters($query, $filters, [ 'trademark_number' => 'trademarkNumber', 'trademark_name' => 'trademarkName', 'registration_date' => 'registrationDate', 'expiry_date' => 'termOfValidity', 'international_classification' => 'internationalClassification', 'approved_goods' => 'approvedCommodities', 'final_holder' => 'transferenceUser', 'registered_address' => 'registrationAddress', 'custody_department' => 'custodyDepartment', ]); $status = trim((string) ($filters['trademark_status'] ?? '')); if ($status !== '') { $statusCode = ['未启用' => '0', '有效' => '1'][$status] ?? $status; $query->where('trademarkStatus', '=', $statusCode); } return $this->page($query, $page, $perPage, 'Id', $filters, [ 'trademark_name' => 'trademarkName', 'trademark_number' => 'trademarkNumber', 'trademark_status' => 'trademarkStatus', 'registration_date' => 'registrationDate', 'expiry_date' => 'termOfValidity', 'international_classification' => 'internationalClassification', 'approved_goods' => 'approvedCommodities', 'final_holder' => 'transferenceUser', 'registered_address' => 'registrationAddress', 'custody_department' => 'custodyDepartment', ]); } /** @param array $textColumns @param array $exactColumns @param array $enumColumns */ private function applyFieldFilters($query, array $filters, array $textColumns, array $exactColumns = [], array $enumColumns = []): void { foreach ($textColumns as $key => $column) { $value = mb_substr(trim((string) ($filters[$key] ?? '')), 0, 191); if ($value !== '') { $query->where($column, 'like', '%' . addcslashes($value, '%_\\') . '%'); } } foreach ($exactColumns as $key => $column) { $value = trim((string) ($filters[$key] ?? '')); if ($value !== '') { $query->where($column, '=', $value); } } foreach ($enumColumns as $key => $column) { $value = trim((string) ($filters[$key] ?? '')); if ($value !== '') { $query->where($column, '=', $value); } } foreach (['opened_at' => 's.openTime', 'renewed_at' => 's.renewalTime', 'registration_date' => 'registrationDate', 'expiry_date' => 'termOfValidity'] as $key => $column) { if (! array_key_exists($key, $textColumns)) continue; foreach (['_from' => '>=', '_to' => '<='] as $suffix => $operator) { $date = trim((string) ($filters[$key . $suffix] ?? '')); if (preg_match('/^\\d{4}-\\d{2}-\\d{2}$/D', $date)) $query->where($column, $operator, $date); } } } /** @return array{data: array>, total: int} */ private function page($query, int $page, int $perPage, string $orderColumn, array $filters = [], array $sortColumns = []): array { $total = (clone $query)->count(); $sortColumn = $sortColumns[$filters['_sort'] ?? ''] ?? null; $direction = ($filters['_direction'] ?? 'asc') === 'desc' ? 'desc' : 'asc'; if ($sortColumn !== null) { $query->orderBy($sortColumn, $direction)->orderByDesc($orderColumn); } else { $query->orderByDesc($orderColumn); } $rows = $query ->forPage($page, $perPage) ->get() ->map(static fn (object $row): array => (array) $row) ->all(); return ['data' => $rows, 'total' => $total]; } private function dataset(string $scope): BusinessMirrorDataset { $key = self::DATASETS[$scope] ?? null; if ($key === null) { throw new InvalidArgumentException('未知的业务资料镜像范围:' . $scope); } return $this->datasets->get($key); } }