fix(stats/self_input): 渠道字典、财务脱敏与同日同渠道全局唯一

自媒体来源改推广渠道字典;非白名单隐藏账户消耗/ROI;部门展示完整路径;业绩与消耗按日期+渠道全局去重。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-22 12:02:12 +08:00
co-authored by Cursor
parent bba989c0af
commit e4f181e4fe
13 changed files with 277 additions and 65 deletions
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace app\adminapi\logic\stats;
use app\common\logic\BaseLogic;
use app\common\model\dict\DictData;
use app\common\model\stats\PersonalAccountCost;
use app\common\model\stats\PersonalYeji;
@@ -75,6 +76,15 @@ class SelfInputLogic extends BaseLogic
}
$summary = self::finalizeMetrics($summaryBase);
$canViewFinance = self::canViewAllSelfInputStats($adminInfo);
if (!$canViewFinance) {
$summary = self::maskFinanceFields($summary);
foreach ($lists as &$item) {
$item = self::maskFinanceFields($item);
}
unset($item);
}
return [
'summary' => $summary,
@@ -85,39 +95,56 @@ class SelfInputLogic extends BaseLogic
'extend' => [
'summary' => $summary,
'date_range' => [$startDate, $endDate],
'can_view_finance' => $canViewFinance,
],
];
}
/**
* 从已录入的业绩/账户消耗中提取去重后的自媒体来源(与列表数据权限一致)
* 财务相关字段(账户消耗 / 现金成本 / ROI):非豁免角色不可见,剔除字段避免被嗅探
*
* @return string[]
* @param array<string, mixed> $entity
* @return array<string, mixed>
*/
private static function maskFinanceFields(array $entity): array
{
foreach (['account_cost', 'cash_cost', 'roi'] as $key) {
unset($entity[$key]);
}
return $entity;
}
/**
* 自媒体来源选项:来自字典「推广渠道」(type_value=channels),按 sort/id 排序。
* 用 dict_data.name 作为存储值(与历史录入兼容;保留 value 仅作展示标识)。
*
* @return array<int, array{name: string, value: string}>
*/
public static function mediaSourceOptions(int $adminId, array $adminInfo): array
{
$effectiveAdminIds = self::resolveEffectiveAdminIds($adminId, $adminInfo, 0);
if ($effectiveAdminIds === []) {
return [];
}
unset($adminId, $adminInfo);
$map = [];
foreach ([PersonalYeji::class, PersonalAccountCost::class] as $modelClass) {
$query = $modelClass::where('media_source', '<>', '');
if ($effectiveAdminIds !== null) {
$query->whereIn('creator_id', $effectiveAdminIds);
}
$rows = $query->group('media_source')->column('media_source');
foreach ($rows as $raw) {
$norm = self::normalizeMediaSource((string) $raw);
if ($norm !== '') {
$map[$norm] = true;
}
}
}
$rows = DictData::where('type_value', 'channels')
->where('status', 1)
->order(['sort' => 'desc', 'id' => 'asc'])
->field('name, value')
->select()
->toArray();
$list = array_keys($map);
sort($list, SORT_STRING);
$list = [];
$seen = [];
foreach ($rows as $row) {
$name = self::normalizeMediaSource((string) ($row['name'] ?? ''));
if ($name === '' || isset($seen[$name])) {
continue;
}
$seen[$name] = true;
$list[] = [
'name' => $name,
'value' => (string) ($row['value'] ?? ''),
];
}
return $list;
}