CaiWuXiTong/modules/EcommerceFinance/Repositories/BusinessInformationMirrorRepository.php
WuYongJie 75b12df493 feat: 完善财务报表字段查询与排序
合并店铺和主体经营报表的字段查询布局,补齐数值筛选、排序和镜像状态提示。
2026-09-24 20:23:41 +08:00

413 lines
18 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Repositories;
use Illuminate\Database\ConnectionInterface;
use InvalidArgumentException;
use Modules\EcommerceFinance\Contracts\BusinessMirrorDataset;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use RuntimeException;
class BusinessInformationMirrorRepository
{
private const DATASETS = [
'company-information.read' => '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<int,array<string,mixed>>, 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<string,string> $filters @return array<int,array<string,mixed>> */
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<array<string,mixed>>
*/
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<string,array<int,array<string,mixed>>> */
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<int,array<string,mixed>>, 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<int,array<string,mixed>>, 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<int,array<string,mixed>>, 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<string,string> $textColumns @param array<string,string> $exactColumns @param array<string,string> $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<int,array<string,mixed>>, 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);
}
}