CaiWuXiTong/modules/EcommerceFinance/Repositories/DingPayableWorkflowMirrorRepository.php

200 lines
7.5 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\ConnectionInterface;
use Illuminate\Support\Facades\Schema;
use RuntimeException;
class DingPayableWorkflowMirrorRepository
{
public const DATASET_KEY = 'operations_replica.rpt.ding_payable_workflows';
public const PROCESS_CODE = 'PROC-39DD70DE-DAB2-4A5F-B8F1-0B0B68BD0C88';
private readonly ConnectionInterface $connection;
public function __construct(GenericBusinessMirrorRepository $mirror)
{
$this->connection = $mirror->connection();
}
public function ready(): bool
{
$schema = Schema::connection($this->connection->getName());
if (! $schema->hasTable('ding_payable_workflow_mirror') || ! $schema->hasTable('business_mirror_table_states')) {
return false;
}
$state = $this->connection->table('business_mirror_table_states')
->where('dataset_key', self::DATASET_KEY)->first();
return $state !== null && $state->initial_completed_at !== null;
}
/** @return array{data:list<array<string,mixed>>,total:int,status:string,last_synced_at:?string} */
public function paginate(array $filters, int $page, int $perPage): array
{
if (! $this->ready()) {
throw new RuntimeException('business_mirror_dataset_not_ready');
}
$page = max($page, 1);
$perPage = min(max($perPage, 1), 100);
$state = $this->connection->table('business_mirror_table_states')->where('dataset_key', self::DATASET_KEY)->first();
// 先在镜像库按固定业务关键词缩小候选流程,再解析 JSON;避免为每次页面请求
// 解码全部历史表单。关键词来自本任务的固定业务筛选,不是用户拼接 SQL。
// “仓”覆盖江西仓、重庆仓等实际用途写法;后续仍在解析明细时
// 同时校验类型和车费,避免仅因其他字段出现“仓”而误收流程。
$records = $this->connection->table('ding_payable_workflow_mirror')
->where('scheme_payload', 'like', '%快递运费%')
->where('scheme_payload', 'like', '%仓%')
->where('scheme_payload', 'like', '%车费%')
->orderByDesc('workflow_created_at')->orderByDesc('process_instance_id')->get();
$filtered = [];
foreach ($records as $record) {
$workflow = $this->decode((string) $record->workflow_payload, []);
if ((string) ($workflow['processCode'] ?? '') !== self::PROCESS_CODE) {
continue;
}
if (($filters['process_instance_id'] ?? '') !== ''
&& ! str_contains((string) ($workflow['processInstanceId'] ?? ''), (string) $filters['process_instance_id'])
&& ! str_contains((string) ($workflow['businessId'] ?? ''), (string) $filters['process_instance_id'])) {
continue;
}
if (! $this->matchesWorkflowFilters($workflow, $filters)) {
continue;
}
$scheme = $this->decode((string) $record->scheme_payload, []);
$forms = $this->formFields($scheme);
foreach ($this->detailRows($forms['应付明细'] ?? []) as $detail) {
$type = trim((string) ($detail['类型'] ?? ''));
$purpose = trim((string) ($detail['用途'] ?? ''));
if ($type !== '快递运费' || ! str_contains($purpose, '仓') || ! str_contains($purpose, '车费')) {
continue;
}
$filtered[] = [
'process_instance_id' => (string) ($workflow['processInstanceId'] ?? $record->process_instance_id),
'workflow' => $workflow,
'fields' => $forms,
'detail' => $detail,
'tasks' => $this->decode((string) ($record->task_payload ?? '[]'), []),
];
}
}
$total = count($filtered);
$data = array_slice($filtered, ($page - 1) * $perPage, $perPage);
return [
'data' => $data,
'total' => $total,
'status' => (string) ($state->status ?? 'unknown'),
'last_synced_at' => $state->last_succeeded_at ? (string) $state->last_succeeded_at : null,
];
}
/** @param array<string,mixed> $workflow @param array<string,mixed> $filters */
private function matchesWorkflowFilters(array $workflow, array $filters): bool
{
$createdFrom = trim((string) ($filters['created_from'] ?? ''));
if ($createdFrom !== '') {
$createdAt = $this->workflowDate($workflow['createAt'] ?? null);
if ($createdAt === null || $createdAt < $createdFrom) {
return false;
}
}
$finishedTo = trim((string) ($filters['finished_to'] ?? ''));
if ($finishedTo !== '') {
$finishTime = $this->workflowDate($workflow['finishTime'] ?? null);
if ($finishTime === null || $finishTime > $finishedTo) {
return false;
}
}
$requestedStatus = strtoupper(trim((string) ($filters['status'] ?? '')));
if ($requestedStatus !== '') {
$actualStatus = strtoupper(trim((string) ($workflow['status'] ?? '')));
$statusMatches = $requestedStatus === 'CANCELED'
? in_array($actualStatus, ['CANCELED', 'CANCELLED'], true)
: $actualStatus === $requestedStatus;
if (! $statusMatches) {
return false;
}
}
return true;
}
private function workflowDate(mixed $value): ?string
{
$date = substr(trim((string) ($value ?? '')), 0, 10);
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) === 1 ? $date : null;
}
/** @return array<string,mixed> */
private function formFields(array $rows): array
{
$fields = [];
foreach ($rows as $row) {
if (! is_array($row)) {
continue;
}
$name = trim((string) ($row['name'] ?? ''));
if ($name === '') {
continue;
}
$value = $this->decode((string) ($row['value'] ?? ''), $row['value'] ?? null);
$fields[$name] = $value;
}
return $fields;
}
/** @return list<array<string,mixed>> */
private function detailRows(mixed $value): array
{
if (! is_array($value)) {
return [];
}
$details = [];
foreach ($value as $row) {
if (! is_array($row) || ! isset($row['rowValue']) || ! is_array($row['rowValue'])) {
continue;
}
$detail = [];
foreach ($row['rowValue'] as $field) {
if (! is_array($field) || ! isset($field['label'])) {
continue;
}
$detail[(string) $field['label']] = $this->scalarValue($field['value'] ?? null);
}
if ($detail !== []) {
$details[] = $detail;
}
}
return $details;
}
private function scalarValue(mixed $value): mixed
{
if (is_scalar($value) || $value === null) {
return $value;
}
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}
private function decode(string $json, mixed $fallback): mixed
{
if ($json === '') {
return $fallback;
}
$decoded = json_decode($json, true);
return json_last_error() === JSON_ERROR_NONE ? $decoded : $fallback;
}
}