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.
 
 
 
 
 

253 lines
20 KiB

# -*- coding: utf-8 -*-
"""第5批:商机详情各 Tab(21 个)"""
import io, sys
sys.path.insert(0, 'D:/code/crm-backend-matt/.scratch')
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
from bru_lib import write_bru, bru_form, build_docs, env_json, req_form_example, req_query_example
BASE = '商机模块/商机详情'
# ── 客户 Tab ──────────────────────────────────────────────────────────
print('== 客户 ==')
CUSTOMER_RESP = [
('id','string','关联行 ID'),
('opportunityId','string','所属商机 ID'),
('customerId','string','客户 ID'),
('customerNameSnapshot','string','客户名快照'),
('customerRole','string','客户角色字典 code'),
('isPrimaryIntended','number','是否主要意向客户:1是 0否'),
]
docs = build_docs(
'客户列表', 'GET', '/api/opportunity/customer/list',
'商机详情「客户」Tab:返回全部关联客户,其中恰好一个 `isPrimaryIntended=1` 为主要意向客户。',
[('oppId','string','','-','商机 ID')],
req_query_example('GET','/api/opportunity/customer/list',[('oppId','1')]),
CUSTOMER_RESP,
env_json('[\n {"id":"1","opportunityId":"1","customerId":"1","customerNameSnapshot":"示例客户","customerRole":"role_01","isPrimaryIntended":1}\n ]'),
)
write_bru(f'{BASE}/客户', '客户列表.bru', bru_form(1,'客户列表','get','/api/opportunity/customer/list',[('oppId','1')],docs))
# ── 跟进 Tab ──────────────────────────────────────────────────────────
print('== 跟进 ==')
FOLLOW_RESP = [
('id','string','跟进记录 ID'),
('createTime','string','创建时间(写入时间)'),
('oppId','string','所属商机 ID'),
('followContent','string','跟进内容'),
('followTime','string','跟进时间'),
('followWay','string','跟进方式字典 code'),
('customerId','string','关联客户 ID'),
('contactId','string','客户联系人 ID(可空)'),
('resultTag','string','跟进结果标签字典 code(可空)'),
('nextFollowTime','string','计划下次跟进时间(可空)'),
]
FOLLOW_ONE = '{\n "id":"1","createTime":"2024-01-01 12:00:00","oppId":"1","followContent":"已电话沟通预算","followTime":"2024-01-01 12:00:00","followWay":"way_01","customerId":"1","contactId":"1","resultTag":"tag_01","nextFollowTime":"2024-01-05 12:00:00"\n }'
FOLLOW_PAGE = env_json('{\n "content":[\n' + FOLLOW_ONE + '\n ],"total":1,"size":10,"current":1,"pages":1,"empty":false\n }')
docs = build_docs(
'跟进分页','POST','/api/opportunity/follow/page',
'商机详情「跟进」Tab:分页拉取跟进记录,默认按创建时间倒序。',
[('oppId','string','','-','商机 ID'),('pageNum','number','','1','页码'),('pageSize','number','','10','每页条数')],
req_form_example('POST','/api/opportunity/follow/page',[('oppId','1'),('pageNum','1'),('pageSize','10')]),
[('content','array','跟进记录数组'),('total','number','总条数'),('size','number','每页条数'),('current','number','当前页'),('pages','number','总页数'),('empty','boolean','是否为空')],
FOLLOW_PAGE,
)
write_bru(f'{BASE}/跟进','跟进分页.bru',bru_form(1,'跟进分页','post','/api/opportunity/follow/page',[('oppId','1'),('pageNum','1'),('pageSize','10')],docs))
ADD_FOLLOW_KV=[('oppId','1'),('followContent','已电话沟通预算'),('followTime','2024-01-01 12:00:00'),('followWay','way_01'),('customerId','1')]
docs = build_docs(
'新增跟进','POST','/api/opportunity/follow/add',
'写跟进弹窗「确认」提交。保存后刷新商机「最近跟进」与 `lastValidFollowTime`(回收锚点)。与阶段推进不联动。',
[('oppId','string','','-','商机 ID'),('followContent','string','','-','跟进内容'),
('followTime','string','','-','跟进时间 yyyy-MM-dd HH:mm:ss'),('followWay','string','','-','跟进方式字典 code'),
('customerId','string','','-','关联客户 ID'),('contactId','string','','-','联系人 ID'),
('resultTag','string','','-','跟进结果标签字典 code'),('nextFollowTime','string','','-','计划下次跟进时间')],
req_form_example('POST','/api/opportunity/follow/add',ADD_FOLLOW_KV),
'新增跟进记录 ID(Long,序列化为 string)', env_json('"1"'),
)
write_bru(f'{BASE}/跟进','新增跟进.bru',bru_form(2,'新增跟进','post','/api/opportunity/follow/add',ADD_FOLLOW_KV,docs))
docs = build_docs(
'删除跟进','POST','/api/opportunity/follow/delete',
'删除跟进记录。`lastValidFollowTime` 删除后不回退重算(防「写了又删」刷锚点)。',
[('id','string','','-','跟进记录 ID')],
req_form_example('POST','/api/opportunity/follow/delete',[('id','1')]),
None, env_json('null'),
)
write_bru(f'{BASE}/跟进','删除跟进.bru',bru_form(3,'删除跟进','post','/api/opportunity/follow/delete',[('id','1')],docs))
# ── 勘察 Tab ──────────────────────────────────────────────────────────
print('== 勘察 ==')
SURVEY_RESP=[
('id','string','勘察记录 ID'),('createTime','string','创建时间'),
('oppId','string','所属商机 ID'),('surveyDate','string','现场勘察日期 yyyy-MM-dd'),
('engineerUserId','string','主要工程师用户 ID'),('applyWay','string','申请方式字典 code'),
('surveySeq','string','勘察次数字典 code'),('applyNo','string','申请单号(可空)'),
('surveyDesc','string','勘察补充说明(可空)'),
]
SURVEY_ONE='{\n "id":"1","createTime":"2024-01-01 12:00:00","oppId":"1","surveyDate":"2024-01-15","engineerUserId":"1","applyWay":"apply_01","surveySeq":"seq_01","applyNo":"SS001","surveyDesc":"现场符合要求"\n }'
SURVEY_PAGE=env_json('{\n "content":[\n'+SURVEY_ONE+'\n ],"total":1,"size":10,"current":1,"pages":1,"empty":false\n }')
docs=build_docs('勘察分页','POST','/api/opportunity/site-survey/page',
'商机详情「勘察」Tab:分页拉取现场勘察记录(多次勘察保留历史)。',
[('oppId','string','','-','商机 ID'),('pageNum','number','','1','页码'),('pageSize','number','','10','每页条数')],
req_form_example('POST','/api/opportunity/site-survey/page',[('oppId','1'),('pageNum','1'),('pageSize','10')]),
[('content','array','勘察记录数组'),('total','number','总条数'),('size','number','每页条数'),('current','number','当前页'),('pages','number','总页数'),('empty','boolean','是否为空')],
SURVEY_PAGE)
write_bru(f'{BASE}/勘察','勘察分页.bru',bru_form(1,'勘察分页','post','/api/opportunity/site-survey/page',[('oppId','1'),('pageNum','1'),('pageSize','10')],docs))
ADD_SURVEY_KV=[('oppId','1'),('surveyDate','2024-01-15'),('engineerUserId','1'),('applyWay','apply_01'),('surveySeq','seq_01')]
docs=build_docs('新增勘察','POST','/api/opportunity/site-survey/add',
'新建现场勘察记录。附件走统一附件表(bizType=SITE_SURVEY,bizId=本记录 id)。',
[('oppId','string','','-','商机 ID'),('surveyDate','string','','-','勘察日期 yyyy-MM-dd'),
('engineerUserId','string','','-','主要工程师用户 ID'),('applyWay','string','','-','申请方式字典 code'),
('surveySeq','string','','-','勘察次数字典 code'),('applyNo','string','','-','申请单号'),('surveyDesc','string','','-','补充说明')],
req_form_example('POST','/api/opportunity/site-survey/add',ADD_SURVEY_KV),
'新增勘察记录 ID', env_json('"1"'))
write_bru(f'{BASE}/勘察','新增勘察.bru',bru_form(2,'新增勘察','post','/api/opportunity/site-survey/add',ADD_SURVEY_KV,docs))
docs=build_docs('删除勘察','POST','/api/opportunity/site-survey/delete','逻辑删除勘察记录。',
[('id','string','','-','勘察记录 ID')],
req_form_example('POST','/api/opportunity/site-survey/delete',[('id','1')]),
None,env_json('null'))
write_bru(f'{BASE}/勘察','删除勘察.bru',bru_form(3,'删除勘察','post','/api/opportunity/site-survey/delete',[('id','1')],docs))
# ── 附件 Tab ──────────────────────────────────────────────────────────
print('== 附件 ==')
ATT_RESP=[
('id','string','附件行 ID'),('createTime','string','上传时间'),
('oppId','string','所属商机 ID'),('bizType','string','业务归属:OPP/FOLLOW_UP/SITE_SURVEY/SCHEME_CARD'),
('bizId','string','业务对象 ID'),('materialType','string','资料类型字典 code'),
('fileId','string','文件 ID(引用 crm-file)'),('remark','string','附件说明'),
]
docs=build_docs('附件列表','GET','/api/opportunity/attachment/list',
'商机详情「附件」Tab:按 `bizType` 过滤;不传则返回该商机全部附件。',
[('oppId','string','','-','商机 ID'),('bizType','string','','-','业务归属类型过滤')],
req_query_example('GET','/api/opportunity/attachment/list',[('oppId','1'),('bizType','OPP')]),
ATT_RESP,
env_json('[\n {"id":"1","createTime":"2024-01-01 12:00:00","oppId":"1","bizType":"OPP","bizId":"1","materialType":"mat_01","fileId":"1","remark":"客户需求文档"}\n ]'))
write_bru(f'{BASE}/附件','附件列表.bru',bru_form(1,'附件列表','get','/api/opportunity/attachment/list',[('oppId','1'),('bizType','OPP')],docs))
ADD_ATT_KV=[('oppId','1'),('fileId','1'),('bizType','OPP'),('bizId','1'),('materialType','mat_01')]
docs=build_docs('新增附件','POST','/api/opportunity/attachment/add',
'将已上传到 crm-file 的文件挂到商机某业务对象下。跟进附件(bizType=FOLLOW_UP)应用层限 10 个/条。',
[('oppId','string','','-','商机 ID'),('fileId','string','','-','文件 ID(先调 crm-file 上传获取)'),
('bizType','string','','-','业务归属类型'),('bizId','string','','-','业务对象 ID'),
('materialType','string','','-','资料类型字典 code'),('remark','string','','-','附件说明')],
req_form_example('POST','/api/opportunity/attachment/add',ADD_ATT_KV),
'新增附件行 ID',env_json('"1"'))
write_bru(f'{BASE}/附件','新增附件.bru',bru_form(2,'新增附件','post','/api/opportunity/attachment/add',ADD_ATT_KV,docs))
docs=build_docs('删除附件','POST','/api/opportunity/attachment/delete',
'逻辑删除附件行;文件本体不动,由 crm-file 侧独立管理生命周期。',
[('id','string','','-','附件行 ID')],
req_form_example('POST','/api/opportunity/attachment/delete',[('id','1')]),
None,env_json('null'))
write_bru(f'{BASE}/附件','删除附件.bru',bru_form(3,'删除附件','post','/api/opportunity/attachment/delete',[('id','1')],docs))
# ── 团队 Tab ──────────────────────────────────────────────────────────
print('== 团队 ==')
TEAM_RESP=[
('id','string','团队成员行 ID'),('createTime','string','加入团队时间'),
('opportunityId','string','所属商机 ID'),('userId','string','成员用户 ID'),
('userNameSnapshot','string','成员姓名快照'),
('projectRole','string','项目角色字典 code(纯标签,不带权限)'),
('permission','number','行级权限档:1只读 2可写跟进 3可推进节点 4读写'),
]
docs=build_docs('团队成员列表','GET','/api/opportunity/team/list',
'商机详情「团队」Tab:返回全部团队成员(含负责人)。团队白名单绕开 DataScope 部门天花板,为「我参与的」视图与详情读授权提供依据。',
[('oppId','string','','-','商机 ID')],
req_query_example('GET','/api/opportunity/team/list',[('oppId','1')]),
TEAM_RESP,
env_json('[\n {"id":"1","createTime":"2024-01-01 12:00:00","opportunityId":"1","userId":"1","userNameSnapshot":"张三","projectRole":"role_owner","permission":4}\n ]'))
write_bru(f'{BASE}/团队','团队成员列表.bru',bru_form(1,'团队成员列表','get','/api/opportunity/team/list',[('oppId','1')],docs))
# ── 日志 Tab ──────────────────────────────────────────────────────────
print('== 日志 ==')
OPLOG_RESP=[
('id','string','日志 ID'),('createTime','string','创建时间'),
('oppId','string','商机 ID'),
('opKind','string','记录形态:FIELD_CHANGE / ROW_ADD / ROW_DELETE'),
('logType','string','业务类别:FIELD_CHANGE/STATUS_FLOW/STAGE_FLOW/ATTACHMENT/SCHEME_PUSH/ROW_CHANGE/SYSTEM_INTERNAL'),
('entityName','string','变更实体对象'),
('fieldName','string','变更字段名(FIELD_CHANGE 时有意义)'),
('oldValue','string','旧值'),('newValue','string','新值'),
('bizRef','string','被操作行业务标识(ROW_ADD/ROW_DELETE 时的可读标识)'),
('opDesc','string','操作说明'),
('opSource','string','操作来源:SYSTEM / MANUAL'),
('opTime','string','操作时间'),('opUserId','string','操作人 ID'),
]
OPLOG_ONE='{\n "id":"1","createTime":"2024-01-01 12:00:00","oppId":"1","opKind":"FIELD_CHANGE","logType":"FIELD_CHANGE","entityName":"opportunity","fieldName":"projectAmount","oldValue":"500000","newValue":"800000","bizRef":null,"opDesc":"修改项目金额","opSource":"MANUAL","opTime":"2024-01-01 12:00:00","opUserId":"1"\n }'
OPLOG_PAGE=env_json('{\n "content":[\n'+OPLOG_ONE+'\n ],"total":1,"size":10,"current":1,"pages":1,"empty":false\n }')
docs=build_docs('操作日志分页','POST','/api/opportunity/oplog/page',
'商机详情「日志」Tab:字段级审计日志,按操作时间倒序。只增不删。',
[('oppId','string','','-','商机 ID'),('pageNum','number','','1','页码'),('pageSize','number','','10','每页条数')],
req_form_example('POST','/api/opportunity/oplog/page',[('oppId','1'),('pageNum','1'),('pageSize','10')]),
[('content','array','日志记录数组'),('total','number','总条数'),('size','number','每页条数'),('current','number','当前页'),('pages','number','总页数'),('empty','boolean','是否为空')],
OPLOG_PAGE)
write_bru(f'{BASE}/日志','操作日志分页.bru',bru_form(1,'操作日志分页','post','/api/opportunity/oplog/page',[('oppId','1'),('pageNum','1'),('pageSize','10')],docs))
# ── 方案卡 Tab ────────────────────────────────────────────────────────
print('== 方案卡 ==')
SC_RESP=[
('id','string','方案卡 ID'),('createTime','string','创建时间'),
('oppId','string','所属商机 ID'),('customerId','string','关联客户 ID'),
('customerNameSnapshot','string','客户名快照'),
('templateId','string','绑定模板版本行 ID'),('templateName','string','模板名称'),
('cardStatus','number','卡状态:1草稿 2已提交 3已推送'),
('editable','boolean','可编辑标记(cardStatus<3 可编辑)'),
('schemeBudget','number','方案预算金额'),
('biddingForm','string','招标形式字典 code'),('enterStage','string','介入阶段字典 code'),
]
SC_ONE='{\n "id":"1","createTime":"2024-01-01 12:00:00","oppId":"1","customerId":"1","customerNameSnapshot":"示例客户","templateId":"1","templateName":"标准方案模板","cardStatus":1,"editable":true,"schemeBudget":800000,"biddingForm":"bid_01","enterStage":"stage_01"\n }'
docs=build_docs('方案卡列表','GET','/api/opportunity/scheme-card/list',
'商机详情「方案卡」Tab:一商机多张方案卡分组展示。无卡时返回空数组。',
[('oppId','string','','-','商机 ID')],
req_query_example('GET','/api/opportunity/scheme-card/list',[('oppId','1')]),
SC_RESP, env_json('[\n'+SC_ONE+'\n ]'))
write_bru(f'{BASE}/方案卡','方案卡列表.bru',bru_form(1,'方案卡列表','get','/api/opportunity/scheme-card/list',[('oppId','1')],docs))
SC_DETAIL_RESP=SC_RESP+[
('fields','array<object>','字段项有序列表'),
('fields[].seqNo','number','序号'),('fields[].fieldKey','string','字段键'),
('fields[].fieldName','string','字段名'),('fields[].fieldType','string','字段类型'),
('fields[].isRequired','number','必填:1是 0否'),('fields[].fieldValue','string','已填值'),
]
SC_DETAIL_JSON=env_json('{\n "id":"1","createTime":"2024-01-01 12:00:00","oppId":"1","customerId":"1","customerNameSnapshot":"示例客户","templateId":"1","templateName":"标准方案模板","cardStatus":1,"editable":true,"schemeBudget":800000,"biddingForm":"bid_01","enterStage":"stage_01","fields":[{"seqNo":1,"fieldKey":"requirement_summary","fieldName":"需求概述","fieldType":"TEXT","isRequired":1,"fieldValue":"示例填写内容"}]\n }')
docs=build_docs('方案卡详情','GET','/api/opportunity/scheme-card/detail',
'按绑定模板版本回显字段项有序列表 + 已填值。结构化字段(招标形式/介入阶段/预算)从主表回填,非结构化字段从值表回填。',
[('id','string','','-','方案卡 ID')],
req_query_example('GET','/api/opportunity/scheme-card/detail',[('id','1')]),
SC_DETAIL_RESP, SC_DETAIL_JSON)
write_bru(f'{BASE}/方案卡','方案卡详情.bru',bru_form(2,'方案卡详情','get','/api/opportunity/scheme-card/detail',[('id','1')],docs))
docs=build_docs('可选模板版本','GET','/api/opportunity/scheme-card/selectable-templates',
'新建方案卡「选模板」下拉数据源:发布中且适用范围命中商机所属部门的模板版本。空列表 = 建卡入口置灰。',
[('oppId','string','','-','商机 ID')],
req_query_example('GET','/api/opportunity/scheme-card/selectable-templates',[('oppId','1')]),
[('templateId','string','模板版本行 ID'),('templateName','string','模板名称'),('templateVersion','string','模板版本号')],
env_json('[\n {"templateId":"1","templateName":"标准方案模板","templateVersion":"v1"}\n ]'))
write_bru(f'{BASE}/方案卡','可选模板版本.bru',bru_form(3,'可选模板版本','get','/api/opportunity/scheme-card/selectable-templates',[('oppId','1')],docs))
SAVE_SC_KV=[('oppId','1'),('customerId','1'),('templateId','1'),('schemeBudget','800000'),('biddingForm','bid_01'),('enterStage','stage_01'),('valuesJson','[{"fieldKey":"requirement_summary","fieldValue":"示例填写内容"}]')]
docs=build_docs('保存方案卡','POST','/api/opportunity/scheme-card/save',
'新建草稿/编辑保存。新建须传 `templateId`;编辑时模板版本已锁不重传。动态字段值以 `valuesJson` 表单字段上送(json-string-in-form 约定),随保存全量替换值表。',
[('id','string','','-','方案卡 ID(编辑时必填,新建不传)'),
('oppId','string','','-','商机 ID'),('customerId','string','','-','关联客户 ID'),
('templateId','string','条件','-','绑定模板版本行 ID(新建必填)'),
('schemeBudget','number','','-','方案预算金额'),
('biddingForm','string','','-','招标形式字典 code'),('enterStage','string','','-','介入阶段字典 code'),
('valuesJson','string','','-','动态字段值 JSON 字符串:`[{"fieldKey":"...","fieldValue":"..."}]`')],
req_form_example('POST','/api/opportunity/scheme-card/save',SAVE_SC_KV),
'方案卡 ID(Long,序列化为 string)',env_json('"1"'))
write_bru(f'{BASE}/方案卡','保存方案卡.bru',bru_form(4,'保存方案卡','post','/api/opportunity/scheme-card/save',SAVE_SC_KV,docs))
docs=build_docs('提交方案卡','POST','/api/opportunity/scheme-card/submit',
'点击「确认」→ 卡状态由草稿变为已提交;提交前校验模板必填字段。',
[('id','string','','-','方案卡 ID')],
req_form_example('POST','/api/opportunity/scheme-card/submit',[('id','1')]),
None,env_json('null'))
write_bru(f'{BASE}/方案卡','提交方案卡.bru',bru_form(5,'提交方案卡','post','/api/opportunity/scheme-card/submit',[('id','1')],docs))
print('batch5 done')