CaiWuXiTong/app/Business/UpstreamBusinessInformation.php

287 lines
9.6 KiB
PHP

<?php
namespace App\Business;
use Illuminate\Pagination\LengthAwarePaginator;
use InvalidArgumentException;
use Modules\EcommerceFinance\Repositories\BusinessInformationMirrorRepository;
use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
class UpstreamBusinessInformation
{
private const SOURCE = 'operations_replica';
public function __construct(
private readonly UpstreamQueryCacheFactory $caches,
private readonly BusinessInformationMirrorRepository $mirror,
) {}
public function paginate(string $section, string $page, int $perPage = 50): LengthAwarePaginator
{
$currentPage = max((int) request('page', 1), 1);
$perPage = min(max($perPage, 1), 100);
$key = "{$section}/{$page}";
$payload = match ($key) {
'company-management/basic-information' => $this->companyPage($currentPage, $perPage),
'store-management/basic-information' => $this->storePage($currentPage, $perPage),
'trademark-management/information' => $this->trademarkPage($currentPage, $perPage),
default => ['data' => [], 'total' => 0],
};
return new LengthAwarePaginator(
array_map(static fn (array $row) => (object) $row, $payload['data']),
(int) $payload['total'],
$perPage,
$currentPage,
[
'path' => request()->url(),
'query' => request()->query(),
],
);
}
/**
* 将已登记的经营分析副本库查询还原成不经过页面缓存的固定只读查询。
*
* 生产后台刷新器使用这个入口生成共享快照;页面仍然只能通过
* UpstreamQueryCache 读取快照,不能绕过缓存直接访问上游。
*
* @return array{data: array<int,array<string,mixed>>, total: int}
*/
public function snapshot(string $scope, array $parameters): array
{
$page = max((int) ($parameters['page'] ?? 1), 1);
$perPage = min(max((int) ($parameters['perPage'] ?? 50), 1), 100);
$query = new UpstreamReadOnlyQuery(self::SOURCE);
return match ($scope) {
'company-information.read' => $this->companySnapshot($query, $page, $perPage),
'store-information.read' => $this->storeSnapshot($query, $page, $perPage),
'trademark-information.read' => $this->trademarkSnapshot($query, $page, $perPage),
default => throw new InvalidArgumentException('未知的经营分析副本库快照类型:' . $scope),
};
}
private function companyPage(int $page, int $perPage): array
{
return $this->page(
'company-information.read',
$page,
$perPage,
fn () => $this->snapshot('company-information.read', [
'page' => $page,
'perPage' => $perPage,
]),
);
}
private function companySnapshot(UpstreamReadOnlyQuery $query, int $page, int $perPage): array
{
$offset = ($page - 1) * $perPage;
$rows = $query->select(<<<SQL
SELECT
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,
companyStatus AS company_status,
financialManager AS financial_officer,
supervisor AS supervisor,
taxCategoryAndType AS taxpayer_category,
NULL AS shareholders
FROM rpt.fys_companyInfo
ORDER BY Id DESC
OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY
SQL);
return [
'data' => array_map(fn (array $row) => $this->mapCompany($row), $rows),
'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_companyInfo'),
];
}
private function storePage(int $page, int $perPage): array
{
return $this->page(
'store-information.read',
$page,
$perPage,
fn () => $this->snapshot('store-information.read', [
'page' => $page,
'perPage' => $perPage,
]),
);
}
private function storeSnapshot(UpstreamReadOnlyQuery $query, int $page, int $perPage): array
{
$offset = ($page - 1) * $perPage;
$rows = $query->select(<<<SQL
SELECT
s.Id AS id,
s.openTime AS opened_at,
s.erpShopName AS erp_store_name,
s.unifiedSocialCreditCode AS unified_social_credit_code,
c.businessLicenseName AS company_name,
s.trademarkNumber AS trademark_number,
NULL AS trademark_name,
s.platform AS platform,
s.responsibleDepartment AS profit_center,
s.shopHead AS store_manager,
s.shopState AS store_status,
s.shopType AS store_type,
s.channelType AS channel_mode
FROM rpt.fys_shopInfo AS s
OUTER APPLY (
SELECT TOP 1 company.businessLicenseName
FROM rpt.fys_companyInfo AS company
WHERE company.unifiedSocialCreditCode = NULLIF(s.unifiedSocialCreditCode, '')
ORDER BY company.Id DESC
) AS c
ORDER BY s.Id DESC
OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY
SQL);
return [
'data' => array_map(fn (array $row) => $this->mapStore($row), $rows),
'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_shopInfo'),
];
}
private function trademarkPage(int $page, int $perPage): array
{
return $this->page(
'trademark-information.read',
$page,
$perPage,
fn () => $this->snapshot('trademark-information.read', [
'page' => $page,
'perPage' => $perPage,
]),
);
}
private function trademarkSnapshot(UpstreamReadOnlyQuery $query, int $page, int $perPage): array
{
$offset = ($page - 1) * $perPage;
$rows = $query->select(<<<SQL
SELECT
Id AS id,
trademarkName AS trademark_name,
internationalClassification AS trademark_type,
trademarkStatus AS trademark_status,
registrationDate AS registration_date,
termOfValidity AS expiry_date,
remark AS notes
FROM rpt.fys_TrademarkInfo
WHERE isDelete = 0
ORDER BY Id DESC
OFFSET {$offset} ROWS FETCH NEXT {$perPage} ROWS ONLY
SQL);
return [
'data' => array_map(fn (array $row) => $this->mapTrademark($row), $rows),
'total' => (int) $query->scalar('SELECT COUNT(*) FROM rpt.fys_TrademarkInfo WHERE isDelete = 0'),
];
}
private function page(string $scope, int $page, int $perPage, callable $resolver): array
{
if ($this->mirror->ready($scope)) {
return $this->normalize($scope, $this->mirror->paginate($scope, $page, $perPage));
}
$cache = $this->caches->forSource(self::SOURCE);
return $cache->remember($scope, compact('page', 'perPage'), function () use ($resolver) {
return $resolver(new UpstreamReadOnlyQuery(self::SOURCE));
});
}
/** @param array{data: array<int,array<string,mixed>>, total: int} $payload */
private function normalize(string $scope, array $payload): array
{
$mapper = match ($scope) {
'company-information.read' => fn (array $row): array => $this->mapCompany($row),
'store-information.read' => fn (array $row): array => $this->mapStore($row),
'trademark-information.read' => fn (array $row): array => $this->mapTrademark($row),
default => throw new InvalidArgumentException('未知的经营分析副本库资料类型:' . $scope),
};
return [
'data' => array_map($mapper, $payload['data']),
'total' => (int) $payload['total'],
];
}
private function mapCompany(array $row): array
{
$row = $this->toUtf8($row);
$row['establishment_date'] = $this->date($row['establishment_date'] ?? null);
$row['company_status'] = match ((string) ($row['company_status'] ?? '')) {
'0' => '正常',
'1' => '已注销',
default => $row['company_status'] ?? null,
};
return $row;
}
private function mapStore(array $row): array
{
$row = $this->toUtf8($row);
$row['opened_at'] = $this->date($row['opened_at'] ?? null);
$row['store_status'] = match ((string) ($row['store_status'] ?? '')) {
'0' => '正常',
'3' => '关闭中',
'6' => '已关闭',
default => $row['store_status'] ?? null,
};
return $row;
}
private function mapTrademark(array $row): array
{
$row = $this->toUtf8($row);
$row['registration_date'] = $this->date($row['registration_date'] ?? null);
$row['expiry_date'] = $this->date($row['expiry_date'] ?? null);
$row['trademark_status'] = match ((string) ($row['trademark_status'] ?? '')) {
'0' => '未启用',
'1' => '有效',
default => $row['trademark_status'] ?? null,
};
return $row;
}
private function toUtf8(array $row): array
{
foreach ($row as &$value) {
if (is_string($value) && ! mb_check_encoding($value, 'UTF-8')) {
$value = mb_convert_encoding($value, 'UTF-8', 'GB18030');
}
}
unset($value);
return $row;
}
private function date($value): ?string
{
if ($value === null || $value === '') {
return null;
}
return substr((string) $value, 0, 10);
}
}