CaiWuXiTong/modules/EcommerceFinance/Repositories/ShopInfoFlowMirrorRepository.php
renshen 885961084e 流程统计:接入店铺申请流程(开店/变更/关店)共享镜像
数据源:jiansun.dbo.fys_shopInfo_flow(申请单明细,4572 行,含店铺/平台/负责人/利润中心/商标/变更原因/关店原因等)
+ 中台库 dbo.flow_flow(按 flowId 关联审批状态与当前审批人),两个数据集已登记并完成首次全量。

- 新增 ShopInfoFlowMirrorRepository:左连接两套镜像,支持单据编号/申请人/申请日期/当前审批人/
  ERP店铺名称/店铺运营负责人/利润中心/商标号等文本筛选,流程类型、审批状态、平台为可搜索多选。
- 审批状态文案取自 OA 前端字典:0暂存/1流程中/2拒绝/3完成/4撤回/5待签署/6已签署/7待审批;
  流程类型:8店铺开店/9店铺变更/10店铺关店/12主体注销自动流程。
- 页面按目录表渲染(查询条件、筛选字段、导出),列与外部店铺申请页对齐。
- TableRegistry 登记为只读上游页,镜像未就绪时页面给提示而不是报错。
2026-10-08 14:38:16 +08:00

217 lines
9.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Modules\EcommerceFinance\Repositories;
use Illuminate\Database\Query\Builder;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use RuntimeException;
/**
* 财务经营风险管控 › 流程统计:店铺申请流程(开店 / 变更 / 关店)只读镜像。
*
* 表单字段来自 jiansun.dbo.fys_shopInfo_flow(聚水潭侧申请单,含店铺/主体/变更明细),
* 审批状态与当前审批人来自中台库的 OA 流程表 dbo.flow_flow(按 flowId 关联)。
*/
class ShopInfoFlowMirrorRepository
{
public const FORM_DATASET = 'jiansun.dbo.fys_shopInfo_flow';
public const FLOW_DATASET = 'js_data_middle_platform.dbo.flow_flow';
/** OA 流程类型(flowType)到页面文案。 */
public const FLOW_TYPES = [
8 => '店铺开店',
9 => '店铺变更',
10 => '店铺关店',
12 => '主体注销自动流程',
];
/** OA 审批状态(flow_flow.flowStatus),取值与文案取自 OA 前端。 */
public const APPROVAL_STATUSES = [
0 => '暂存',
1 => '流程中',
2 => '拒绝',
3 => '完成',
4 => '撤回',
5 => '待签署',
6 => '已签署',
7 => '待审批',
];
/** 店铺档案状态(fys_shopInfo.shopState 口径)。 */
private const SHOP_STATES = [0 => '正常', 3 => '关闭中', 6 => '已关闭'];
public function __construct(
private readonly BusinessMirrorDatasetRegistry $datasets,
private readonly GenericBusinessMirrorRepository $mirror,
) {
}
public function ready(): bool
{
try {
return $this->mirror->ready($this->datasets->get(self::FORM_DATASET));
} catch (\Throwable) {
return false;
}
}
/**
* @param array<string,mixed> $filters
* @return array{data:list<array<string,mixed>>,total:int}
*/
public function paginate(int $page, int $perPage, array $filters = []): array
{
if (! $this->ready()) {
throw new RuntimeException('shop_info_flow_mirror_not_ready');
}
$page = max($page, 1);
$perPage = min(max($perPage, 1), 100);
$connection = $this->mirror->connection();
$form = $this->datasets->get(self::FORM_DATASET)->targetTable();
$flow = $this->datasets->get(self::FLOW_DATASET)->targetTable();
$query = $connection->table($form . ' as f')
->leftJoin($flow . ' as o', 'o.flowId', '=', 'f.flowId');
$this->applyFilters($query, $filters);
$total = (clone $query)->count();
$rows = $query
->select([
'f.Id as id', 'f.flowId as flow_id', 'f.flowType as flow_type', 'f.applyUser as apply_user',
'f.applyTime as apply_time', 'f.applyDepartment as apply_department', 'f.remark as remark',
'f.platform as platform', 'f.responsibleDepartment as responsible_department',
'f.shopHead as shop_head', 'f.customerManager as customer_manager', 'f.shopType as shop_type',
'f.shopState as shop_state', 'f.shopName as shop_name', 'f.erpShopName as erp_shop_name',
'f.trademarkNumber as trademark_number', 'f.unifiedSocialCreditCode as subject_code',
'f.informationChangeType as information_change_type', 'f.changeReason as change_reason',
'f.changeSuccessful as change_successful', 'f.closeReason as close_reason',
'f.expectedClosingTime as expected_closing_time', 'f.refundType as refund_type',
'f.refundAmount as refund_amount', 'f.refundIssue as refund_issue',
'o.flowStatus as approval_status_code', 'o.approver as current_approver',
'o.subject as flow_subject', 'o.updateTime as flow_updated_at',
])
->orderByDesc('f.applyTime')->orderByDesc('f.Id')
->forPage($page, $perPage)
->get()
->map(fn (object $row): array => $this->mapRow($row))
->all();
return ['data' => $rows, 'total' => $total];
}
/** @param array<string,mixed> $filters */
private function applyFilters(Builder $query, array $filters): void
{
foreach ([
'flow_id' => 'f.flowId',
'apply_user' => 'f.applyUser',
'erp_shop_name' => 'f.erpShopName',
'shop_name' => 'f.shopName',
'shop_head' => 'f.shopHead',
'current_approver' => 'o.approver',
'responsible_department' => 'f.responsibleDepartment',
'trademark_number' => 'f.trademarkNumber',
] as $key => $column) {
$value = trim((string) ($filters[$key] ?? ''));
if ($value !== '') {
$query->where($column, 'like', '%' . addcslashes($value, '%_\\') . '%');
}
}
foreach ([['from', '>='], ['to', '<=']] as [$suffix, $operator]) {
$date = trim((string) ($filters['apply_' . $suffix] ?? ''));
if (preg_match('/^\d{4}-\d{2}-\d{2}$/D', $date)) {
$query->whereDate('f.applyTime', $operator, $date);
}
}
// 多选筛选:流程类型 / 审批状态 / 平台,多个取值取并集。
$flowTypes = $this->filterValues($filters['flow_type'] ?? '');
if ($flowTypes !== []) {
$codes = [];
foreach ($flowTypes as $label) {
$code = array_search($label, self::FLOW_TYPES, true);
if ($code !== false) {
$codes[] = (int) $code;
}
}
$query->whereIn('f.flowType', $codes === [] ? [-1] : $codes);
}
$statuses = $this->filterValues($filters['approval_status'] ?? '');
if ($statuses !== []) {
$codes = [];
foreach ($statuses as $label) {
$code = array_search($label, self::APPROVAL_STATUSES, true);
if ($code !== false) {
$codes[] = (int) $code;
}
}
$query->whereIn('o.flowStatus', $codes === [] ? [-1] : $codes);
}
$platforms = $this->filterValues($filters['platform'] ?? '');
if ($platforms !== []) {
$query->whereIn('f.platform', $platforms);
}
}
/** @return list<string> */
private function filterValues(mixed $value): array
{
$values = [];
foreach ((array) $value as $item) {
if (! is_scalar($item)) {
continue;
}
$item = trim((string) $item);
if ($item !== '') {
$values[$item] = true;
}
}
return array_keys($values);
}
/** @return array<string,mixed> */
private function mapRow(object $row): array
{
$flowType = (int) ($row->flow_type ?? 0);
$statusCode = $row->approval_status_code !== null ? (int) $row->approval_status_code : null;
return [
'id' => $row->id,
'flow_id' => (string) $row->flow_id,
'flow_type' => self::FLOW_TYPES[$flowType] ?? ('其它(' . $flowType . ')'),
'flow_subject' => (string) ($row->flow_subject ?? ''),
'approval_status' => $statusCode === null ? '' : (self::APPROVAL_STATUSES[$statusCode] ?? (string) $statusCode),
'current_approver' => (string) ($row->current_approver ?? ''),
'apply_user' => (string) ($row->apply_user ?? ''),
'apply_time' => (string) ($row->apply_time ?? ''),
'apply_department' => (string) ($row->apply_department ?? ''),
'remark' => (string) ($row->remark ?? ''),
'platform' => (string) ($row->platform ?? ''),
'responsible_department' => (string) ($row->responsible_department ?? ''),
'shop_head' => (string) ($row->shop_head ?? ''),
'customer_manager' => (string) ($row->customer_manager ?? ''),
'shop_type' => (string) ($row->shop_type ?? ''),
'shop_state' => $row->shop_state === null ? '' : (self::SHOP_STATES[(int) $row->shop_state] ?? (string) $row->shop_state),
'shop_name' => (string) ($row->shop_name ?? ''),
'erp_shop_name' => (string) ($row->erp_shop_name ?? ''),
'trademark_number' => (string) ($row->trademark_number ?? ''),
'subject_code' => (string) ($row->subject_code ?? ''),
'information_change_type' => $row->information_change_type === null ? '' : (string) $row->information_change_type,
'change_reason' => (string) ($row->change_reason ?? ''),
'change_successful' => $row->change_successful === null ? null : (bool) $row->change_successful,
'close_reason' => (string) ($row->close_reason ?? ''),
'expected_closing_time' => (string) ($row->expected_closing_time ?? ''),
'refund_type' => (string) ($row->refund_type ?? ''),
'refund_amount' => $row->refund_amount === null ? null : (float) $row->refund_amount,
'refund_issue' => (string) ($row->refund_issue ?? ''),
'flow_updated_at' => (string) ($row->flow_updated_at ?? ''),
];
}
}