WuYongJie 967b62ca2f feat: 数据逻辑配置页面支持在线维护
- 店铺主体商标数据逻辑配置新增编辑页:多商标分隔符、同日主体冲突口径、
  商标号缺失处理、爬虫断更开关与天数、四类预警责任人
- 店铺维度报表逻辑配置新增期初数据编辑页:114 家变更店铺可增删改,
  期初生效日随设置保存
- 规则与期初数据改存系统库 settings(扁平键),首次打开自动从
  config/store_report_initial_periods.php 固化,之后不再依赖代码文件
- 控制台任务通过 bootCompanyContext() 读取同一份公司级配置
- 新增迁移清理早期嵌套设置键
- 新增 2 个测试覆盖配置保存与期初店铺增删
2026-10-03 21:52:43 +08:00

136 lines
4.5 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Services;
use Modules\EcommerceFinance\Services\CrawlerDataGuard;
/**
* 「店铺主体商标数据逻辑配置」的可维护参数。
*
* 页面展示的规则目录仍由 ShopEntityTrademarkLogic 维护;这里保存负责人
* 可以调整的运行参数,读取时以设置值优先、代码默认值兜底。
*/
class DataLogicSettings
{
private const PREFIX = 'business_data_logic';
/** 扁平键:设置组件会按点号展开存储,不能嵌套数组。 */
public const KEYS = [
'crawl_priority' => self::PREFIX . '.crawl_priority',
'trademark_separator' => self::PREFIX . '.trademark_separator',
'same_day_conflict_policy' => self::PREFIX . '.same_day_conflict_policy',
'missing_trademark_action' => self::PREFIX . '.missing_trademark_action',
'stale_warning_enabled' => self::PREFIX . '.stale_warning_enabled',
'stale_after_days' => self::PREFIX . '.stale_after_days',
'owner_stale' => self::PREFIX . '.owner.stale',
'owner_subject_name' => self::PREFIX . '.owner.subject_name',
'owner_subject_conflict' => self::PREFIX . '.owner.subject_conflict',
'owner_trademark' => self::PREFIX . '.owner.trademark',
];
/**
* @return array<string,mixed>
*/
public static function defaults(): array
{
return [
'crawl_priority' => '1',
'trademark_separator' => '、',
'same_day_conflict_policy' => 'crawl_wins',
'missing_trademark_action' => 'warn_keep',
'stale_warning_enabled' => '1',
'stale_after_days' => '1',
'owner_stale' => '',
'owner_subject_name' => '',
'owner_subject_conflict' => '',
'owner_trademark' => '',
];
}
/**
* @return array<string,mixed>
*/
public static function current(): array
{
$values = [];
foreach (self::KEYS as $key => $settingKey) {
$value = setting($settingKey, null);
$values[$key] = is_array($value) ? implode('', $value) : (string) ($value ?? '');
}
return array_merge(self::defaults(), array_filter($values, static fn (string $value): bool => $value !== ''));
}
public static function value(string $key, mixed $default = null): mixed
{
$current = self::current();
return $current[$key] ?? ($default ?? (self::defaults()[$key] ?? null));
}
/** @param array<string,mixed> $values */
public static function save(array $values): void
{
$payload = [];
foreach (self::defaults() as $key => $default) {
if (! array_key_exists($key, $values) || ! isset(self::KEYS[$key])) {
continue;
}
$payload[self::KEYS[$key]] = mb_substr(trim((string) $values[$key]), 0, 191);
}
if ($payload !== []) {
setting($payload)->save();
}
}
public static function separator(): string
{
$value = trim((string) self::value('trademark_separator', '、'));
return $value === '' ? '、' : $value;
}
public static function crawlWinsOnConflict(): bool
{
return (string) self::value('same_day_conflict_policy', 'crawl_wins') === 'crawl_wins';
}
public static function staleWarningEnabled(): bool
{
return (string) self::value('stale_warning_enabled', '1') === '1';
}
public static function staleAfterDays(): int
{
return max((int) self::value('stale_after_days', 1), 1);
}
public static function ownerFor(string $warningType): string
{
$map = [
CrawlerDataGuard::TYPE_STALE => 'owner_stale',
CrawlerDataGuard::TYPE_SUBJECT_NAME => 'owner_subject_name',
CrawlerDataGuard::TYPE_SUBJECT_CONFLICT => 'owner_subject_conflict',
CrawlerDataGuard::TYPE_TRADEMARK_MISSING => 'owner_trademark',
];
$key = $map[$warningType] ?? null;
return $key === null ? '' : trim((string) self::value($key, ''));
}
/**
* 控制台命令没有公司上下文,设置组件默认按 company_id = null 读取。
* 这里切到第一个公司,保证命令和网页读到同一份配置。
*/
public static function bootCompanyContext(): void
{
try {
$company = \App\Models\Common\Company::query()->orderBy('id')->first();
$company?->makeCurrent();
} catch (\Throwable) {
// 读取失败时保持原上下文,使用代码默认值。
}
}
}