feat(api): A4 数据字典与系统配置后端
背景:A4 需要字典三层合并、配置单一来源与阈值同源。\n改动:新增 catalog 模块(类型/条目/三层合并/编码不可改/快照工具);platform 新增 SystemConfigService/Controller、NamingService 与配置版本历史;roles/users/customers/customer-fields 改为读 system_configs 上限。\n影响面:新增 13 个 REST 端点与 4 个模块注入;system_configs 写入走事务/乐观锁/版本历史;OpenAPI 快照更新。\n回滚:回滚本提交并恢复 shared 契约;新增表可保留。\n验证:api typecheck/build;单测 137;集成 58;OpenAPI diff 无破坏性变更。
This commit is contained in:
1 parent
cc6e84cdbc
commit
e4330a43e4
25 files changed
+4189
-30
No files matched your search
@@ -0,0 +1,133 @@
|
||||
import { dictItemSnapshotSchema } from '@lingxi/shared';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
mapDictGroup,
|
||||
mapDictKind,
|
||||
mapDictStatus,
|
||||
toDictItem,
|
||||
toDictSnapshot,
|
||||
toDictTypeSummary,
|
||||
} from './dict.mapper';
|
||||
|
||||
describe('dict.mapper · Prisma 行 -> 共享 DTO', () => {
|
||||
it('类型统计与枚举转换:group / kind / status 映射为契约小写值', () => {
|
||||
const summary = toDictTypeSummary(
|
||||
{
|
||||
code: 'script_direction',
|
||||
name: '脚本方向',
|
||||
groupKey: 'content',
|
||||
kind: 'LIST',
|
||||
description: '脚本与素材的结构化方向',
|
||||
industryScoped: true,
|
||||
builtin: true,
|
||||
status: 'ACTIVE',
|
||||
sortOrder: 60,
|
||||
updatedAt: new Date('2026-11-04T02:00:00.000Z'),
|
||||
updatedBy: '江岚',
|
||||
},
|
||||
{ itemCount: 11, activeItemCount: 10, industryCount: 2 },
|
||||
);
|
||||
expect(summary).toEqual({
|
||||
code: 'script_direction',
|
||||
name: '脚本方向',
|
||||
group: 'content',
|
||||
kind: 'list',
|
||||
description: '脚本与素材的结构化方向',
|
||||
industryScoped: true,
|
||||
builtin: true,
|
||||
status: 'active',
|
||||
sortOrder: 60,
|
||||
itemCount: 11,
|
||||
activeItemCount: 10,
|
||||
industryCount: 2,
|
||||
updatedAt: '2026-11-04T02:00:00.000Z',
|
||||
updatedBy: '江岚',
|
||||
});
|
||||
});
|
||||
|
||||
it('库内异常分组回退 content;kind / status 只映射两个分支', () => {
|
||||
expect(mapDictGroup('unknown_group')).toBe('content');
|
||||
expect(mapDictGroup('quality')).toBe('quality');
|
||||
expect(mapDictKind('TREE')).toBe('tree');
|
||||
expect(mapDictKind('LIST')).toBe('list');
|
||||
expect(mapDictStatus('ACTIVE')).toBe('active');
|
||||
expect(mapDictStatus('DISABLED')).toBe('disabled');
|
||||
});
|
||||
|
||||
it('条目映射:source / overridden / parentName / industryPath / refCount=0', () => {
|
||||
const item = toDictItem(
|
||||
{
|
||||
id: '01HZZZZZZZZZZZZZZZZZZZZZZZ',
|
||||
code: 'seeding',
|
||||
name: '种草短视频',
|
||||
parentCode: null,
|
||||
industryId: 'ec',
|
||||
sortOrder: 40,
|
||||
status: 'ACTIVE',
|
||||
remark: '父行业覆盖通用层',
|
||||
updatedAt: new Date('2026-11-04T02:00:00.000Z'),
|
||||
updatedBy: '江岚',
|
||||
},
|
||||
{
|
||||
typeCode: 'script_direction',
|
||||
source: 'parent',
|
||||
overridden: true,
|
||||
parentName: null,
|
||||
industryPath: '电商',
|
||||
},
|
||||
);
|
||||
expect(item).toMatchObject({
|
||||
typeCode: 'script_direction',
|
||||
code: 'seeding',
|
||||
name: '种草短视频',
|
||||
parentName: null,
|
||||
industryId: 'ec',
|
||||
industryPath: '电商',
|
||||
status: 'active',
|
||||
source: 'parent',
|
||||
overridden: true,
|
||||
refCount: 0,
|
||||
updatedAt: '2026-11-04T02:00:00.000Z',
|
||||
updatedBy: '江岚',
|
||||
});
|
||||
});
|
||||
|
||||
it('快照 R-10:先取快照再改字典名称,快照保留填写时名称', () => {
|
||||
const item = toDictItem(
|
||||
{
|
||||
id: '01HZZZZZZZZZZZZZZZZZZZZZZZ',
|
||||
code: 'oral',
|
||||
name: '口播',
|
||||
parentCode: null,
|
||||
industryId: null,
|
||||
sortOrder: 10,
|
||||
status: 'ACTIVE',
|
||||
remark: '',
|
||||
updatedAt: new Date('2026-11-04T02:00:00.000Z'),
|
||||
updatedBy: '江岚',
|
||||
},
|
||||
{
|
||||
typeCode: 'script_direction',
|
||||
source: 'generic',
|
||||
overridden: false,
|
||||
parentName: null,
|
||||
industryPath: null,
|
||||
},
|
||||
);
|
||||
const snapshot = toDictSnapshot(item, '2026-11-04T10:00:00.000Z');
|
||||
// 模拟字典改名 / 停用(历史引用方不感知)
|
||||
const renamed = { ...item, name: '口播(改名后)', status: 'disabled' as const };
|
||||
expect(renamed.name).not.toBe(snapshot.name);
|
||||
expect(snapshot.name).toBe('口播');
|
||||
expect(snapshot).toEqual({
|
||||
typeCode: 'script_direction',
|
||||
code: 'oral',
|
||||
name: '口播',
|
||||
parentCode: null,
|
||||
industryId: null,
|
||||
capturedAt: '2026-11-04T10:00:00.000Z',
|
||||
});
|
||||
expect(dictItemSnapshotSchema.parse(snapshot)).toEqual(snapshot);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user