You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
684 lines
34 KiB
684 lines
34 KiB
# -*- coding: utf-8 -*-
|
|
"""票 11 t11-4 — gen-rules-views.py:生成规则族 25 + 自定义视图 4 新 .bru(真实示例落地)。
|
|
|
|
数据源:.scratch/opportunity-e2e/specimens.json(32 标本 = 30 首采 + stage/scheme versions 补采 20260829)。
|
|
落点:D:\\code\\crm-api-docs\\商机模块\\商机管理\\商机规则\\{公海规则,阶段模板,方案卡模板}\\
|
|
D:\\code\\crm-api-docs\\商机模块\\销售机会\\自定义视图\\
|
|
目录落点裁决(handoff §3.1):源码 @Tag 是「商机管理/商机规则」「销售机会/自定义视图」,
|
|
遵循现有集合树(顶层「商机模块/」)避免顶层重复文件夹。
|
|
|
|
纪律(handoff §3.5 / bruno-sync SKILL):
|
|
- UTF-8 无 BOM、\\n 换行;幂等:目标文件已存在则跳过
|
|
- docs 块所有行 2 空格缩进;首行 `# 动作语义名`、第二行 `METHOD 路径`(对账钥匙勿动)
|
|
- 节顺序:说明段 → ## 请求参数 → ## 请求示例 → ## 响应 data 结构 → ## 响应示例
|
|
- 有标本:`// 实测响应摘录(20260828 · 用户X · e2e 种子库;数组字段只示第 1 条)`
|
|
无标本(写操作):`// 示例数据,字段结构以上表为准,非真实返回`
|
|
- Result<Void> 写操作:「响应 data 结构」段写 null(照 商机模块/状态流转/领取商机.bru 惯例)
|
|
- folder.bru 无 type、无 tags;seq 动态取同父目录现有 folder.bru max+10
|
|
"""
|
|
import io
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
FORCE = '--force' in sys.argv
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
DOCS_ROOT = r'D:\code\crm-api-docs'
|
|
MOD_ROOT = os.path.join(DOCS_ROOT, '商机模块')
|
|
SP = json.load(open(os.path.join(HERE, 'specimens.json'), encoding='utf-8'))
|
|
|
|
# ---------- 真实值(全部从标本现取,不硬编码) ----------
|
|
POOL_ID = SP['pool_detail']['params']['id'] # 748507245062389760
|
|
SCHEME_ID = SP['scheme_detail']['params']['id'] # 748508098259320832
|
|
STAGE_ID = SP['stage_detail']['params']['id'] # 747831302652166144
|
|
_seed_view = next(v for v in SP['view_list']['resp']['data'] if '测试视图' in v['name'])
|
|
VIEW_ID = _seed_view['viewId']
|
|
_fdefs = SP['field_defs']['resp']['data']
|
|
FK0, FK1 = _fdefs[0]['fieldKey'], _fdefs[1]['fieldKey'] # customer_name / customer_type
|
|
|
|
MECH = '// 示例数据,字段结构以上表为准,非真实返回'
|
|
|
|
|
|
def measured(key):
|
|
return '// 实测响应摘录(20260828 · 用户%s · e2e 种子库;数组字段只示第 1 条)' % SP[key]['who']
|
|
|
|
|
|
def j2(obj):
|
|
return json.dumps(obj, ensure_ascii=False, indent=2)
|
|
|
|
|
|
def env(data):
|
|
return {'code': 0, 'success': True, 'message': 'success', 'data': data}
|
|
|
|
|
|
def slim_page(key):
|
|
d = SP[key]['resp']['data']
|
|
return dict(d, content=d['content'][:1])
|
|
|
|
|
|
def slim_list(key):
|
|
return SP[key]['resp']['data'][:1]
|
|
|
|
|
|
def table(rows, headers):
|
|
out = ['| ' + ' | '.join(headers) + ' |',
|
|
'| ' + ' | '.join(['---'] * len(headers)) + ' |']
|
|
out += ['| ' + ' | '.join(r) + ' |' for r in rows]
|
|
return out
|
|
|
|
|
|
def prefix_rows(rows, p):
|
|
return [(p + n, t, d) for n, t, d in rows]
|
|
|
|
|
|
# ---------- 响应 data 结构字段表(源码 DTO/实体逐字段) ----------
|
|
PAGE_SHELL_ROWS = [
|
|
('total', 'number', '总条数'),
|
|
('size', 'number', '每页条数'),
|
|
('current', 'number', '当前页码'),
|
|
('pages', 'number', '总页数'),
|
|
('empty', 'boolean', '是否为空'),
|
|
]
|
|
|
|
POOL_DTO_ROWS = [
|
|
('id', 'string', '版本行 ID(Long 序列化为 string)'),
|
|
('createTime', 'string', '创建时间'),
|
|
('updateTime', 'string', '更新时间'),
|
|
('ruleCode', 'string', '规则编码(系统生成,同规则多版本共用)'),
|
|
('ruleName', 'string', '规则名称'),
|
|
('versionNo', 'string', '版本号(系统生成只读:V1.0/V1.1…)'),
|
|
('status', 'number', '状态:1草稿 2发布中 3已停用'),
|
|
('applyScope', 'number', '适用范围:1所有部门通用 2对应部门专用'),
|
|
('isDefault', 'number', '是否默认:1是 0否(仅通用可设)'),
|
|
('allowManualPool', 'number', '允许负责人手动转入公海:1是 0否'),
|
|
('autoRecycleEnabled', 'number', '启用自动回收:1是 0否'),
|
|
('recycleDays', 'number', '自动回收天数(启用回收时必填,≥1;未启用为 null)'),
|
|
('allowFreeClaim', 'number', '允许自由领取:1是 0否'),
|
|
('recycleRemindEnabled', 'number', '自动回收前提醒:1是 0否(仅回收启用时可开)'),
|
|
('remindDays', 'number', '提前提醒天数(启用提醒时必填,≥1 且 < 回收天数)'),
|
|
('ruleDesc', 'string', '规则说明'),
|
|
('deptIds', 'array', '适用部门 ID 列表(Long 序列化为 string;applyScope=1 时为空数组)'),
|
|
('deptNames', 'array', '适用部门名称列表(出参回显)'),
|
|
]
|
|
|
|
STAGE_DTO_ROWS = [
|
|
('id', 'string', '版本行 ID(Long 序列化为 string)'),
|
|
('createTime', 'string', '创建时间'),
|
|
('updateTime', 'string', '更新时间'),
|
|
('templateCode', 'string', '模板编码(系统生成,同模板多版本共用)'),
|
|
('templateName', 'string', '模板名称'),
|
|
('versionNo', 'string', '版本号(系统生成只读:V1.0/V1.1…)'),
|
|
('status', 'number', '状态:1草稿 2发布中 3已停用'),
|
|
('applyScope', 'number', '适用范围:1所有商机 2指定部门'),
|
|
('isDefault', 'number', '是否默认:1是 0否(仅所有商机范围可设)'),
|
|
('templateDesc', 'string', '模板说明'),
|
|
('deptIds', 'array', '适用部门 ID 列表(Long 序列化为 string;applyScope=1 时为空数组)'),
|
|
('deptNames', 'array', '适用部门名称列表(出参回显)'),
|
|
('nodes', 'array', '阶段节点有序列表(seqNo 按列表顺序重算;末位固定节点系统保证)'),
|
|
('nodes[].id', 'string', '节点 ID(出参回显)'),
|
|
('nodes[].seqNo', 'number', '阶段序号'),
|
|
('nodes[].stageDictCode', 'string', '阶段字典值(引用「商机阶段」字典分组项)'),
|
|
('nodes[].customNodeName', 'string', '自定义节点名称(空则带入字典名)'),
|
|
('nodes[].workGoal', 'string', '阶段工作目标'),
|
|
('nodes[].isFixed', 'number', '固定节点:1固定(已转项目)0普通(出参回显,入参忽略)'),
|
|
]
|
|
|
|
SCHEME_DTO_ROWS = [
|
|
('id', 'string', '版本行 ID(Long 序列化为 string)'),
|
|
('createTime', 'string', '创建时间'),
|
|
('updateTime', 'string', '更新时间'),
|
|
('templateCode', 'string', '模板编码(系统生成,同模板多版本共用)'),
|
|
('templateName', 'string', '模板名称'),
|
|
('versionNo', 'string', '版本号(系统生成只读:V1.0/V1.1…)'),
|
|
('status', 'number', '状态:1草稿 2发布中 3已停用'),
|
|
('applyScope', 'number', '适用范围:1所有部门通用 2对应部门专用'),
|
|
('isDefault', 'number', '是否默认:1是 0否(仅所有部门通用可设)'),
|
|
('templateDesc', 'string', '模板说明'),
|
|
('deptIds', 'array', '适用部门 ID 列表(Long 序列化为 string;applyScope=1 时为空数组)'),
|
|
('deptNames', 'array', '适用部门名称列表(出参回显)'),
|
|
('fields', 'array', '字段项有序列表(seqNo 按列表顺序重算)'),
|
|
('fields[].id', 'string', '字段项 ID(出参回显)'),
|
|
('fields[].seqNo', 'number', '序号'),
|
|
('fields[].fieldKey', 'string', '字段键(引用系统字段定义)'),
|
|
('fields[].fieldName', 'string', '展示字段名(系统字段快照回显;入参忽略)'),
|
|
('fields[].fieldType', 'string', '字段类型(系统字段快照回显;入参忽略)'),
|
|
('fields[].isVisible', 'number', '显示开关:1显示 0不显示(关闭则不出现在填写页且清必填)'),
|
|
('fields[].isRequired', 'number', '必填:1必填 0非必填(仅显示时可为 1)'),
|
|
]
|
|
|
|
VER_BASE_ROWS = [
|
|
('id', 'string', '版本行 ID(Long 序列化为 string)'),
|
|
('creatorId', 'string', '创建人 ID(实体直出内部字段;D-19 P2 已登记待收敛)'),
|
|
('createTime', 'string', '创建时间'),
|
|
('updaterId', 'string', '更新人 ID(实体直出内部字段)'),
|
|
('updateTime', 'string', '更新时间'),
|
|
('deleted', 'boolean', '逻辑删除标记(实体直出,正常恒为 false)'),
|
|
]
|
|
|
|
POOL_VER_ROWS = VER_BASE_ROWS + [
|
|
('ruleCode', 'string', '规则编码'),
|
|
('ruleName', 'string', '规则名称'),
|
|
('versionNo', 'string', '版本号'),
|
|
('status', 'number', '状态:1草稿 2发布中 3已停用'),
|
|
('applyScope', 'number', '适用范围:1所有部门通用 2对应部门专用'),
|
|
('isDefault', 'number', '是否默认:1是 0否'),
|
|
('allowManualPool', 'number', '允许负责人手动转入公海:1是 0否'),
|
|
('autoRecycleEnabled', 'number', '启用自动回收:1是 0否'),
|
|
('recycleDays', 'number', '自动回收天数'),
|
|
('allowFreeClaim', 'number', '允许自由领取:1是 0否'),
|
|
('recycleRemindEnabled', 'number', '自动回收前提醒:1是 0否'),
|
|
('remindDays', 'number', '提前提醒天数'),
|
|
('ruleDesc', 'string', '规则说明'),
|
|
]
|
|
|
|
STAGE_VER_ROWS = VER_BASE_ROWS + [
|
|
('templateCode', 'string', '模板编码'),
|
|
('templateName', 'string', '模板名称'),
|
|
('versionNo', 'string', '版本号'),
|
|
('status', 'number', '状态:1草稿 2发布中 3已停用'),
|
|
('applyScope', 'number', '适用范围:1所有商机 2指定部门'),
|
|
('isDefault', 'number', '是否默认:1是 0否'),
|
|
('templateDesc', 'string', '模板说明'),
|
|
]
|
|
|
|
FIELD_DEF_ROWS = [
|
|
('id', 'string', '字段定义 ID'),
|
|
('createTime', 'string', '创建时间'),
|
|
('updateTime', 'string', '更新时间'),
|
|
('fieldKey', 'string', '字段键(模板字段项引用它)'),
|
|
('fieldName', 'string', '字段名(展示名)'),
|
|
('fieldType', 'string', '类型:RELATED_OBJECT/SINGLE_SELECT/AUTOFILL/YES_NO/ATTACHMENT/AMOUNT/TEXT_SINGLE/TEXT_MULTI/DATE'),
|
|
('optionSource', 'string', '选项来源(单选下拉类:字典分组 code)'),
|
|
('refTarget', 'string', '关联对象指向(关联对象类:目标实体语义,如 customer/contact)'),
|
|
('autofillSource', 'string', '自动回填来源(自动回填类:来源字段语义)'),
|
|
('defaultValue', 'string', '默认值'),
|
|
('isDeletable', 'number', '是否可删除:1可删 0内置不可删'),
|
|
]
|
|
|
|
VIEW_ROWS = [
|
|
('viewId', 'string', '视图 ID(服务端生成;save 新建时返回,编辑/删除/设默认须回传)'),
|
|
('name', 'string', '检索名称(用户填)'),
|
|
('conditions', 'array', '检索条件列表(本期前端只提交 1 组)'),
|
|
('conditions[].field', 'string', '字段 key(业务方字段池内的稳定 code)'),
|
|
('conditions[].operator', 'string', '操作符(业务方自定义 code,如 eq 等于 / like 包含)'),
|
|
('conditions[].value', 'string', '条件值(为空/不为空操作符时可为 null)'),
|
|
('sortField', 'string', '排序字段 key(可空)'),
|
|
('sortDirection', 'string', '排序方向 asc/desc(可空)'),
|
|
('isDefault', 'boolean', '是否默认视图(单值互斥)'),
|
|
('seqNo', 'number', '视图在列表中的排序序号'),
|
|
]
|
|
|
|
# ---------- 请求参数表 ----------
|
|
PAGE_IN_BASE = [
|
|
('current', 'number', '否', '1', '当前页码,从 1 开始'),
|
|
('size', 'number', '否', '10', '每页条数,上限 500'),
|
|
('keyword', 'string', '否', '-', 'KW_NOTE'),
|
|
('orderBy', 'string', '否', '-', '排序字段(驼峰或下划线均可,如 createTime)'),
|
|
('asc', 'boolean', '否', 'false', '是否升序(默认 false,新数据在前)'),
|
|
]
|
|
|
|
POOL_SAVE_IN = [
|
|
('id', 'string', '否', '-', '编辑草稿时传版本行 ID;新建不传'),
|
|
('ruleName', 'string', '是', '-', '规则名称'),
|
|
('applyScope', 'number', '是', '-', '适用范围:1所有部门通用 2对应部门专用'),
|
|
('isDefault', 'number', '否', '0', '是否默认:1是 0否(仅通用可设)'),
|
|
('allowManualPool', 'number', '否', '1', '允许负责人手动转入公海:1是 0否'),
|
|
('autoRecycleEnabled', 'number', '否', '1', '启用自动回收:1是 0否;=0 时 recycleRemindEnabled 须显式传 0(省略按开启处理)'),
|
|
('recycleDays', 'number', '条件', '-', '自动回收天数(启用回收时必填,≥1)'),
|
|
('allowFreeClaim', 'number', '否', '1', '允许自由领取:1是 0否'),
|
|
('recycleRemindEnabled', 'number', '条件', '0', '自动回收前提醒:1是 0否(仅回收启用时可开)'),
|
|
('remindDays', 'number', '条件', '-', '提前提醒天数(启用提醒时必填,≥1 且 < 回收天数)'),
|
|
('ruleDesc', 'string', '否', '-', '规则说明'),
|
|
('deptIds', 'string', '条件', '-', '适用部门 ID 列表,逗号分隔(applyScope=2 时必填至少一个)'),
|
|
]
|
|
|
|
STAGE_SAVE_IN = [
|
|
('id', 'string', '否', '-', '编辑草稿时传版本行 ID;新建不传'),
|
|
('templateName', 'string', '是', '-', '模板名称'),
|
|
('applyScope', 'number', '是', '-', '适用范围:1所有商机 2指定部门'),
|
|
('isDefault', 'number', '否', '0', '是否默认:1是 0否(仅所有商机范围可设)'),
|
|
('templateDesc', 'string', '否', '-', '模板说明'),
|
|
('deptIds', 'string', '条件', '-', '适用部门 ID 列表,逗号分隔(applyScope=2 时必填至少一个)'),
|
|
('nodes[N].stageDictCode', 'string', '是', '-', '第 N 个节点的阶段字典值(N 从 0 递增的索引式表单绑定,引用「商机阶段」字典分组项)'),
|
|
('nodes[N].customNodeName', 'string', '否', '-', '自定义节点名称(空则带入字典名)'),
|
|
('nodes[N].workGoal', 'string', '否', '-', '阶段工作目标'),
|
|
]
|
|
|
|
SCHEME_SAVE_IN = [
|
|
('id', 'string', '否', '-', '编辑草稿时传版本行 ID;新建不传'),
|
|
('templateName', 'string', '是', '-', '模板名称'),
|
|
('applyScope', 'number', '是', '-', '适用范围:1所有部门通用 2对应部门专用'),
|
|
('isDefault', 'number', '否', '0', '是否默认:1是 0否(仅所有部门通用可设)'),
|
|
('templateDesc', 'string', '否', '-', '模板说明'),
|
|
('deptIds', 'string', '条件', '-', '适用部门 ID 列表,逗号分隔(applyScope=2 时必填至少一个)'),
|
|
('fields[N].fieldKey', 'string', '是', '-', '第 N 个字段项的字段键(索引式表单绑定,必须取「系统字段库列表」实测 fieldKey,非自由造)'),
|
|
('fields[N].isVisible', 'number', '否', '1', '显示开关:1显示 0不显示(关闭则不出现在填写页且清必填)'),
|
|
('fields[N].isRequired', 'number', '否', '0', '必填:1必填 0非必填(仅显示时可为 1)'),
|
|
]
|
|
|
|
# ---------- 写操作实测构造(e2e-rules.py / seed-opportunity.py 实测通过口径) ----------
|
|
POOL_SAVE_PAIRS = [
|
|
('ruleName', 'e2e-演示-公海规则'),
|
|
('applyScope', '1'),
|
|
('isDefault', '0'),
|
|
('allowManualPool', '1'),
|
|
('autoRecycleEnabled', '1'),
|
|
('recycleDays', '30'),
|
|
('allowFreeClaim', '1'),
|
|
('recycleRemindEnabled', '1'),
|
|
('remindDays', '7'),
|
|
('ruleDesc', '票11演示'),
|
|
]
|
|
|
|
STAGE_SAVE_PAIRS = [
|
|
('templateName', 'e2e-演示-阶段模板'),
|
|
('applyScope', '1'),
|
|
('isDefault', '0'),
|
|
('templateDesc', '票11演示'),
|
|
('nodes[0].stageDictCode', 'OPP_STAGE_01'),
|
|
('nodes[0].workGoal', '需求确认'),
|
|
('nodes[1].stageDictCode', 'OPP_STAGE_02'),
|
|
('nodes[1].customNodeName', '方案深化'),
|
|
]
|
|
|
|
SCHEME_SAVE_PAIRS = [
|
|
('templateName', 'e2e-演示-方案卡模板'),
|
|
('applyScope', '1'),
|
|
('isDefault', '0'),
|
|
('templateDesc', '票11演示'),
|
|
('fields[0].fieldKey', FK0),
|
|
('fields[0].isVisible', '1'),
|
|
('fields[0].isRequired', '1'),
|
|
('fields[1].fieldKey', FK1),
|
|
('fields[1].isVisible', '1'),
|
|
('fields[1].isRequired', '0'),
|
|
]
|
|
|
|
VIEW_SAVE_OBJ = {
|
|
'name': 'e2e-B-测试视图-我的推进',
|
|
'conditions': [
|
|
{'field': 'opportunityName', 'operator': 'like', 'value': 'e2e-'},
|
|
{'field': 'oppStatus', 'operator': 'eq', 'value': '2'},
|
|
],
|
|
'sortField': 'createTime',
|
|
'sortDirection': 'desc',
|
|
'isDefault': False,
|
|
'seqNo': 99,
|
|
}
|
|
|
|
# ---------- .bru 组装 ----------
|
|
CREATED, SKIPPED, REWRITTEN = [], [], []
|
|
# 仅限本脚本生成的 4 个新目录内允许 --force 重写(防误伤既有集合文件)
|
|
OWN_DIRS = ('商机规则', '自定义视图')
|
|
|
|
|
|
def write_file(path, content):
|
|
rel = os.path.relpath(path, DOCS_ROOT)
|
|
exists = os.path.exists(path)
|
|
if exists and not (FORCE and any(seg in rel for seg in OWN_DIRS)):
|
|
SKIPPED.append(rel)
|
|
return
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
with open(path, 'w', encoding='utf-8', newline='\n') as f:
|
|
f.write(content)
|
|
(CREATED if not exists else REWRITTEN).append(rel)
|
|
|
|
|
|
def next_seq(parent):
|
|
mx = 0
|
|
if os.path.isdir(parent):
|
|
for d in sorted(os.listdir(parent)):
|
|
fp = os.path.join(parent, d, 'folder.bru')
|
|
if os.path.isfile(fp):
|
|
m = re.search(r'seq:\s*(\d+)', open(fp, encoding='utf-8').read())
|
|
if m:
|
|
mx = max(mx, int(m.group(1)))
|
|
return mx + 10
|
|
|
|
|
|
def write_folder(parent, name):
|
|
seq = next_seq(parent)
|
|
write_file(os.path.join(parent, name, 'folder.bru'),
|
|
'meta {\n name: %s\n seq: %d\n}\n' % (name, seq))
|
|
|
|
|
|
def qblock(pairs):
|
|
return '\n'.join(['params:query {'] + [' %s: %s' % kv for kv in pairs] + ['}'])
|
|
|
|
|
|
def fblock(pairs):
|
|
return '\n'.join(['body:form-urlencoded {'] + [' %s: %s' % kv for kv in pairs] + ['}'])
|
|
|
|
|
|
def jblock(json_text):
|
|
return '\n'.join(['body:json {'] + [' ' + l for l in json_text.splitlines()] + ['}'])
|
|
|
|
|
|
def make_bru(name, seq, method, path, transport, param_text, docs_text):
|
|
body = {'GET': 'none', 'FORM': 'formUrlEncoded', 'JSON': 'json'}[transport]
|
|
verb = 'get' if method == 'GET' else 'post'
|
|
L = ['meta {', ' name: ' + name, ' type: http', ' seq: ' + str(seq),
|
|
' tags: [', ' generated', ' ]', '}', '',
|
|
verb + ' {', ' url: {{baseUrl}}' + path, ' body: ' + body,
|
|
' auth: inherit', '}', '']
|
|
if param_text:
|
|
L += [param_text, '']
|
|
L += [docs_text]
|
|
return '\n'.join(L) + '\n'
|
|
|
|
|
|
def make_docs(title, key, intro, req_md, ex_md, struct_md, resp_comment, resp_json):
|
|
struct_lines = struct_md if isinstance(struct_md, list) else [struct_md]
|
|
L = ['# ' + title, '`' + key + '`']
|
|
L += list(intro)
|
|
L += ['', '## 请求参数', ''] + list(req_md)
|
|
L += ['', '## 请求示例', ''] + list(ex_md)
|
|
L += ['', '## 响应 data 结构', ''] + struct_lines
|
|
L += ['', '## 响应示例', '', resp_comment, '```json']
|
|
L += resp_json.splitlines()
|
|
L += ['```']
|
|
return 'docs {\n' + '\n'.join((' ' + l) if l else '' for l in L) + '\n}'
|
|
|
|
|
|
def ex_form(method, path, pairs):
|
|
qs = '&'.join('%s=%s' % kv for kv in pairs)
|
|
return ['请求头:Authorization: Bearer {{token}}',
|
|
'Content-Type: application/x-www-form-urlencoded',
|
|
'', method + ' ' + path, qs]
|
|
|
|
|
|
def ex_get(path, pairs):
|
|
qs = '&'.join('%s=%s' % kv for kv in pairs)
|
|
return ['请求头:Authorization: Bearer {{token}}', '',
|
|
'GET ' + path + '?' + qs]
|
|
|
|
|
|
def ex_json(path, query_pairs, json_text):
|
|
qs = ('?' + '&'.join('%s=%s' % kv for kv in query_pairs)) if query_pairs else ''
|
|
L = ['请求头:Authorization: Bearer {{token}}', '',
|
|
'POST ' + path + qs, 'Content-Type: application/json', '', '```json']
|
|
L += json_text.splitlines()
|
|
L += ['```']
|
|
return L
|
|
|
|
|
|
def page_struct_rows(biz_rows, row_noun):
|
|
return ([('content', 'array', 'PageResult.content ' + row_noun)]
|
|
+ prefix_rows(biz_rows, 'content[].') + PAGE_SHELL_ROWS)
|
|
|
|
|
|
def struct_table(rows):
|
|
return table(rows, ('字段', '类型', '说明'))
|
|
|
|
|
|
# ---------- 规则三族生成 ----------
|
|
def build_family(cfg):
|
|
sub, base, title = cfg['sub'], cfg['base'], cfg['title']
|
|
tpl_id = cfg['tpl_id']
|
|
parent = os.path.join(MOD_ROOT, '商机管理', '商机规则')
|
|
write_folder(parent, sub)
|
|
fdir = os.path.join(parent, sub)
|
|
|
|
def emit(name, seq, method, path, transport, param_text, key_line,
|
|
intro, req_md, ex_md, struct_md, resp_data, resp_key):
|
|
comment = measured(resp_key) if resp_key else MECH
|
|
docs = make_docs(name, key_line, intro, req_md, ex_md, struct_md,
|
|
comment, j2(env(resp_data)))
|
|
write_file(os.path.join(fdir, name + '.bru'),
|
|
make_bru(name, seq, method, path, transport, param_text, docs))
|
|
|
|
code_col = cfg['code_col']
|
|
page_rows = [r if r[4] != 'KW_NOTE' else (r[0], r[1], r[2], r[3], cfg['keyword_note'])
|
|
for r in PAGE_IN_BASE]
|
|
page_rows += [
|
|
('applyScope', 'number', '否', '-', '适用范围筛选:' + cfg['scope_desc']),
|
|
('status', 'number', '否', '-', '状态筛选:1草稿 2发布中 3已停用'),
|
|
]
|
|
id_only = lambda desc: [('id', 'string', '是', '-', desc)]
|
|
|
|
# 1 分页
|
|
emit(title + '分页', 1, 'POST', base + '/page', 'FORM',
|
|
fblock([('current', '1'), ('size', '10')]),
|
|
'POST ' + base + '/page',
|
|
['%s配置列表,按版本维度展示(同 %s 草稿/发布中/停用各占一行)。' % (title, code_col),
|
|
cfg['keyword_note'] + '。'],
|
|
table(page_rows, ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_form('POST', base + '/page', [('current', '1'), ('size', '10')]),
|
|
struct_table(page_struct_rows(cfg['biz_rows'], cfg['row_noun'])),
|
|
slim_page(cfg['page_key']), cfg['page_key'])
|
|
|
|
# 2 详情
|
|
emit(title + '详情', 2, 'GET', base + '/detail', 'GET',
|
|
qblock([('id', tpl_id)]),
|
|
'GET ' + base + '/detail',
|
|
['%s详情(含适用部门%s)。' % (title, cfg['detail_extra'])],
|
|
table(id_only('版本行 ID'), ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_get(base + '/detail', [('id', tpl_id)]),
|
|
struct_table(cfg['biz_rows']),
|
|
SP[cfg['detail_key']]['resp']['data'], cfg['detail_key'])
|
|
|
|
seq = 3
|
|
if cfg.get('field_defs'):
|
|
# 方案卡模板专属:系统字段库列表
|
|
emit('系统字段库列表', seq, 'GET', base + '/field-defs', 'GET',
|
|
None, 'GET ' + base + '/field-defs',
|
|
['系统字段库列表:模板字段项 fieldKey 的合法取值源(只读,无参数)。'],
|
|
['无参数。'],
|
|
ex_get(base + '/field-defs', []),
|
|
struct_table(FIELD_DEF_ROWS),
|
|
slim_list('field_defs'), 'field_defs')
|
|
seq += 1
|
|
|
|
# 保存草稿
|
|
emit('保存%s草稿' % title, seq, 'POST', base + '/save-draft', 'FORM',
|
|
fblock(cfg['save_pairs']), 'POST ' + base + '/save-draft',
|
|
['保存草稿:新建 / 原位编辑草稿 / 从发布中停用生成新草稿(三态复用)。']
|
|
+ cfg['save_extra'],
|
|
table(cfg['save_in_rows'], ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_form('POST', base + '/save-draft', cfg['save_pairs']),
|
|
'null', None, None)
|
|
seq += 1
|
|
|
|
# 发布
|
|
pub_rows = list(cfg['save_in_rows'])
|
|
pub_rows[0] = ('id', 'string', '是', '-', '须传 save-draft 产出的草稿版本行 ID')
|
|
pub_pairs = [('id', '1')] + list(cfg['save_pairs'])
|
|
emit('发布' + title, seq, 'POST', base + '/publish', 'FORM',
|
|
fblock(pub_pairs), 'POST ' + base + '/publish',
|
|
['保存并发布:原发布中转停用、原默认取消默认、部门覆盖校验。',
|
|
'⚠ 发布将顶替同编码发布中版本;id 须替换为 save-draft 产出的草稿 id,'
|
|
'严禁对种子「%s」直接执行。' % cfg['seed']],
|
|
table(pub_rows, ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_form('POST', base + '/publish', pub_pairs),
|
|
'null', None, None)
|
|
seq += 1
|
|
|
|
# 复制
|
|
emit('复制' + title, seq, 'POST', base + '/copy', 'FORM',
|
|
fblock([('id', tpl_id)]), 'POST ' + base + '/copy',
|
|
['复制:独立新%s(新编码 + V1.0 草稿%s)。' % (title, cfg['copy_extra']),
|
|
'对发布中示例 id 照抄 = 新建草稿副本(无害),可用「删除%s」练手清掉。' % title],
|
|
table(id_only('被复制版本行 ID'), ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_form('POST', base + '/copy', [('id', tpl_id)]),
|
|
'string', '1', None)
|
|
seq += 1
|
|
|
|
# 停用
|
|
emit('停用' + title, seq, 'POST', base + '/disable', 'FORM',
|
|
fblock([('id', '1')]), 'POST ' + base + '/disable',
|
|
['停用:仅发布中可停用。',
|
|
'⚠ id 须替换为发布中版本行 id(勿误停种子「%s」)。' % cfg['seed']],
|
|
table(id_only('发布中版本行 ID'), ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_form('POST', base + '/disable', [('id', '1')]),
|
|
'null', None, None)
|
|
seq += 1
|
|
|
|
# 删除
|
|
emit('删除' + title, seq, 'POST', base + '/delete', 'FORM',
|
|
fblock([('id', '1')]), 'POST ' + base + '/delete',
|
|
[cfg['delete_note'],
|
|
'⚠ id 须替换为草稿 id(可先用「复制%s」造一条草稿练手)。' % title],
|
|
table(id_only('草稿版本行 ID'), ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_form('POST', base + '/delete', [('id', '1')]),
|
|
'null', None, None)
|
|
seq += 1
|
|
|
|
# 版本列表
|
|
emit(title + '版本列表', seq, 'GET', base + '/versions', 'GET',
|
|
qblock([('id', tpl_id)]), 'GET ' + base + '/versions',
|
|
['同族版本列表:按 %s 查所有版本。' % code_col,
|
|
'实体直出(无 DTO 包装),不含%s子表。' % cfg['ver_missing']],
|
|
table(id_only('任一版本行 ID(按其编码查全族)'), ('参数', '类型', '必填', '默认', '说明')),
|
|
ex_get(base + '/versions', [('id', tpl_id)]),
|
|
struct_table(cfg['ver_rows']),
|
|
slim_list(cfg['versions_key']), cfg['versions_key'])
|
|
|
|
|
|
FAMILIES = [
|
|
dict(
|
|
sub='公海规则', title='公海规则', base='/api/rule/opp-pool-rule',
|
|
code_col='ruleCode', row_noun='规则版本行列表',
|
|
page_key='pool_page', detail_key='pool_detail', versions_key='pool_versions',
|
|
tpl_id=POOL_ID, seed='e2e-seed公海规则(POOL_RULE_01)',
|
|
scope_desc='1所有部门通用 2对应部门专用', keyword_note='模糊搜索(匹配规则名称/规则编码/版本号)',
|
|
detail_extra='',
|
|
biz_rows=POOL_DTO_ROWS, ver_rows=POOL_VER_ROWS,
|
|
save_pairs=POOL_SAVE_PAIRS, save_in_rows=POOL_SAVE_IN,
|
|
save_extra=['V-CONFIG 语义:同 ruleCode 至多一草稿一发布中;'
|
|
'autoRecycleEnabled=0 时 recycleRemindEnabled 须显式传 0(省略按开启处理)。'],
|
|
copy_extra='', delete_note='删除:仅草稿可删。',
|
|
ver_missing=' deptIds/deptNames ',
|
|
),
|
|
dict(
|
|
sub='阶段模板', title='阶段模板', base='/api/rule/opp-stage-template',
|
|
code_col='templateCode', row_noun='模板版本行列表',
|
|
page_key='stage_page', detail_key='stage_detail', versions_key='stage_versions',
|
|
tpl_id=STAGE_ID, seed='全公司通用阶段模板(OPP_STAGE_TPL_01)',
|
|
scope_desc='1所有商机 2指定部门', keyword_note='模糊搜索(匹配模板名称/模板编码/版本号)',
|
|
detail_extra=' + 阶段节点有序列表',
|
|
biz_rows=STAGE_DTO_ROWS, ver_rows=STAGE_VER_ROWS,
|
|
save_pairs=STAGE_SAVE_PAIRS, save_in_rows=STAGE_SAVE_IN,
|
|
save_extra=['节点为索引式表单绑定(nodes[0].xxx = 第 1 个节点…),节点全量替换;',
|
|
'末位固定节点 OPP_STAGE_05(已转项目)系统保证,入参不可伪造(伪造被 64000 类校验拒绝)。'],
|
|
copy_extra=',节点随复制', delete_note='删除:仅草稿可删(级联删节点与适用部门)。',
|
|
ver_missing=' nodes/deptIds/deptNames ',
|
|
),
|
|
dict(
|
|
sub='方案卡模板', title='方案卡模板', base='/api/rule/opp-scheme-template',
|
|
code_col='templateCode', row_noun='模板版本行列表',
|
|
page_key='scheme_page', detail_key='scheme_detail', versions_key='scheme_versions',
|
|
tpl_id=SCHEME_ID, seed='全公司通用方案卡模板(OPP_SCHEME_TPL_04)',
|
|
scope_desc='1所有部门通用 2对应部门专用', keyword_note='模糊搜索(匹配模板名称/模板编码/版本号)',
|
|
detail_extra=' + 字段项有序列表',
|
|
biz_rows=SCHEME_DTO_ROWS, ver_rows=STAGE_VER_ROWS,
|
|
save_pairs=SCHEME_SAVE_PAIRS, save_in_rows=SCHEME_SAVE_IN,
|
|
save_extra=['字段项为索引式表单绑定(fields[0].xxx = 第 1 个字段项…),字段项全量替换,'
|
|
'fieldKey 必须取「系统字段库列表」实测值,非自由造;',
|
|
'fieldName/fieldType 入参忽略(取系统字段快照回显),isVisible=0 会清必填。'],
|
|
copy_extra=',字段配置随复制', delete_note='删除:仅草稿可删(级联删字段项与适用部门)。',
|
|
ver_missing=' fields/deptIds/deptNames ',
|
|
field_defs=True,
|
|
),
|
|
]
|
|
|
|
# ---------- 自定义视图生成 ----------
|
|
def build_views():
|
|
parent = os.path.join(MOD_ROOT, '销售机会')
|
|
write_folder(parent, '自定义视图')
|
|
fdir = os.path.join(parent, '自定义视图')
|
|
base = '/api/preference/view'
|
|
|
|
def emit(name, seq, method, path, transport, param_text, key_line,
|
|
intro, req_md, ex_md, struct_md, resp_data, resp_key):
|
|
comment = measured(resp_key) if resp_key else MECH
|
|
docs = make_docs(name, key_line, intro, req_md, ex_md, struct_md,
|
|
comment, j2(env(resp_data)))
|
|
write_file(os.path.join(fdir, name + '.bru'),
|
|
make_bru(name, seq, method, path, transport, param_text, docs))
|
|
|
|
param_table = lambda rows: table(rows, ('参数', '类型', '必填', '默认', '说明'))
|
|
|
|
# 1 列表
|
|
emit('自定义视图列表', 1, 'GET', base + '/list', 'GET',
|
|
qblock([('scopeKey', 'opportunity')]), 'GET ' + base + '/list',
|
|
['当前用户在某 scope 下的自建视图(内置视图不走本接口),按 seqNo 排序。',
|
|
'出参不回显 scopeKey;商机域实测 scopeKey=opportunity。'],
|
|
param_table([('scopeKey', 'string', '是', '-', '视图域标识;商机域实测传 opportunity')]),
|
|
ex_get(base + '/list', [('scopeKey', 'opportunity')]),
|
|
struct_table(VIEW_ROWS),
|
|
slim_list('view_list'), 'view_list')
|
|
|
|
# 2 保存(query scopeKey + body:json)
|
|
vjson = j2(VIEW_SAVE_OBJ)
|
|
emit('保存自定义视图', 2, 'POST', base + '/save', 'JSON',
|
|
qblock([('scopeKey', 'opportunity')]) + '\n\n' + jblock(vjson),
|
|
'POST ' + base + '/save',
|
|
['保存自定义视图:viewId 空=新建,非空=覆盖编辑(须回传原 viewId);'
|
|
'isDefault 单值互斥(设默认自动取消原默认)。',
|
|
'scopeKey 走 query 参数,JSON body 才是视图本体;商机域实测 scopeKey=opportunity。'],
|
|
param_table([
|
|
('scopeKey', 'string', '是', '-', 'query 参数:视图域标识(opportunity)'),
|
|
('viewId', 'string', '否', '-', 'JSON body:空=新建;非空=覆盖编辑'),
|
|
('name', 'string', '是', '-', 'JSON body:检索名称(用户填)'),
|
|
('conditions', 'array', '否', '-', 'JSON body:检索条件列表(本期前端只提交 1 组)'),
|
|
('conditions[].field', 'string', '条件', '-', '字段 key(商机字段池稳定 code,如 opportunityName)'),
|
|
('conditions[].operator', 'string', '条件', '-', '操作符(如 eq 等于 / like 包含)'),
|
|
('conditions[].value', 'string', '条件', '-', '条件值'),
|
|
('sortField', 'string', '否', '-', 'JSON body:排序字段 key(可空)'),
|
|
('sortDirection', 'string', '否', '-', 'JSON body:排序方向 asc/desc(可空)'),
|
|
('isDefault', 'boolean', '否', 'false', 'JSON body:是否默认视图(单值互斥)'),
|
|
('seqNo', 'number', '否', '-', 'JSON body:列表排序序号'),
|
|
]),
|
|
ex_json(base + '/save', [('scopeKey', 'opportunity')], vjson),
|
|
struct_table([('viewId', 'string', '新建或覆盖后视图的 viewId(服务端生成)')]),
|
|
VIEW_ID, None)
|
|
|
|
# 3 删除 / 4 设为默认
|
|
del_pairs = [('scopeKey', 'opportunity'), ('viewId', '1')]
|
|
for name, path, intro in [
|
|
('删除自定义视图', base + '/delete',
|
|
['删除本人名下自定义视图。',
|
|
'⚠ viewId 须替换为本人视图 id(「自定义视图列表」可查;勿对他人/种子视图执行)。']),
|
|
('设为默认视图', base + '/set-default',
|
|
['图钉:设为该 scope 的默认视图(单值互斥)。',
|
|
'⚠ viewId 须替换为本人视图 id(「自定义视图列表」可查;勿对他人/种子视图执行)。']),
|
|
]:
|
|
emit(name, 3 if path.endswith('delete') else 4, 'POST', path, 'FORM',
|
|
fblock(del_pairs), 'POST ' + path,
|
|
intro,
|
|
param_table([
|
|
('scopeKey', 'string', '是', '-', '视图域标识(opportunity)'),
|
|
('viewId', 'string', '是', '-', '视图 ID(须替换为本人视图 id)'),
|
|
]),
|
|
ex_form('POST', path, del_pairs),
|
|
'null', None, None)
|
|
|
|
|
|
# ---------- 主流程 ----------
|
|
if __name__ == '__main__':
|
|
for cfg in FAMILIES:
|
|
build_family(cfg)
|
|
build_views()
|
|
|
|
print('created:', len(CREATED), ' rewritten:', len(REWRITTEN), ' skipped:', len(SKIPPED))
|
|
for rel in CREATED:
|
|
print(' +', rel)
|
|
for rel in REWRITTEN:
|
|
print(' ~', rel, '(--force 重写)')
|
|
for rel in SKIPPED:
|
|
print(' =', rel, '(已存在,跳过)')
|
|
|
|
bom = []
|
|
for rel in CREATED + REWRITTEN:
|
|
with open(os.path.join(DOCS_ROOT, rel), 'rb') as f:
|
|
if f.read(3) == b'\xef\xbb\xbf':
|
|
bom.append(rel)
|
|
if bom:
|
|
print('!! BOM 文件:', bom)
|
|
sys.exit(1)
|
|
total = sum(1 for dp, dn, fn in os.walk(MOD_ROOT)
|
|
for x in fn if x.endswith('.bru') and x != 'folder.bru')
|
|
print('商机模块 .bru 接口文件总数:', total)
|
|
|