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.
337 lines
16 KiB
337 lines
16 KiB
|
2 weeks ago
|
# -*- coding: utf-8 -*-
|
||
|
|
"""Part 2: 协作 / 方案卡 / 商机阶段 / 详情子域 / 商机分页(6副本)."""
|
||
|
|
import io, os, 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, page_json,
|
||
|
|
req_form_example, req_query_example,
|
||
|
|
)
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# 协作 (OpportunityCollabController)
|
||
|
|
# ================================================================
|
||
|
|
print('== 协作 ==')
|
||
|
|
|
||
|
|
def simple_null_post(seq, folder, filename, name, path, params, kv, desc):
|
||
|
|
d = build_docs(name, 'POST', path, desc, params,
|
||
|
|
req_form_example('POST', path, kv), None, env_json('null'))
|
||
|
|
write_bru(folder, filename, bru_form(seq, name, 'post', path, kv, d))
|
||
|
|
|
||
|
|
simple_null_post(1, '商机管理/协作', '关注商机.bru', '关注商机', '/api/opportunity/focus',
|
||
|
|
[('oppId', 'string', '是', '-', '商机ID(Long,序列化为 string)')],
|
||
|
|
[('oppId','1')],
|
||
|
|
'当前登录人关注该商机(详情页/列表关注按钮)。已关注再次调用幂等无副作用。')
|
||
|
|
|
||
|
|
simple_null_post(2, '商机管理/协作', '取关商机.bru', '取关商机', '/api/opportunity/unfocus',
|
||
|
|
[('oppId', 'string', '是', '-', '商机ID')],
|
||
|
|
[('oppId','1')],
|
||
|
|
'取消关注。未关注调用幂等。')
|
||
|
|
|
||
|
|
simple_null_post(3, '商机管理/协作', '批量关注.bru', '批量关注', '/api/opportunity/focus-batch',
|
||
|
|
[('ids', 'string[]', '是', '-', '商机ID列表(逗号分隔)')],
|
||
|
|
[('ids','1,2,3')],
|
||
|
|
'批量关注:逐条调 focus,每条幂等。')
|
||
|
|
|
||
|
|
simple_null_post(4, '商机管理/协作', '上报访问.bru', '上报访问', '/api/opportunity/view-touch',
|
||
|
|
[('oppId', 'string', '是', '-', '商机ID')],
|
||
|
|
[('oppId','1')],
|
||
|
|
'打开商机详情页时前端调用一次,写入访问记录用于「最近访问」视图排序。')
|
||
|
|
|
||
|
|
# 看板阶段汇总 - 返回类型 Result<?>(服务层 boardStageSummary 未定强类型返回结构,按数据类语义推导:阶段汇总列表)
|
||
|
|
board_summary_docs = build_docs(
|
||
|
|
'看板阶段汇总', 'POST', '/api/opportunity/board/stage-summary',
|
||
|
|
'销售机会看板视图:按阶段模板展开的每一节点,返回商机数量与金额汇总(用于阶段泳道头)。`stageTemplateId` 不传时按当前用户默认模板。',
|
||
|
|
[('stageTemplateId', 'string', '否', '-', '阶段模板版本ID(Long,序列化为 string)')],
|
||
|
|
req_form_example('POST', '/api/opportunity/board/stage-summary', [('stageTemplateId','1')]),
|
||
|
|
[
|
||
|
|
('stageId', 'string', '阶段节点ID'),
|
||
|
|
('stageName', 'string', '阶段名称'),
|
||
|
|
('count', 'number', '该阶段商机数量'),
|
||
|
|
('totalAmount', 'number', '该阶段项目金额合计'),
|
||
|
|
],
|
||
|
|
env_json('[\n {\n "stageId": "1",\n "stageName": "需求确认",\n "count": 3,\n "totalAmount": 3000000\n }\n ]'),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/协作', '看板阶段汇总.bru',
|
||
|
|
bru_form(5, '看板阶段汇总', 'post', '/api/opportunity/board/stage-summary',
|
||
|
|
[('stageTemplateId','1')], board_summary_docs))
|
||
|
|
|
||
|
|
# 看板阶段卡片
|
||
|
|
board_cards_docs = build_docs(
|
||
|
|
'看板阶段卡片', 'POST', '/api/opportunity/board/cards',
|
||
|
|
'销售机会看板视图:拉取指定阶段泳道的商机卡片列表(滚动/翻页用 offset+limit,非页码分页)。',
|
||
|
|
[('stageId', 'string', '是', '-', '阶段节点ID'),
|
||
|
|
('offset', 'number', '否', '0', '偏移量'),
|
||
|
|
('limit', 'number', '否', '20', '每次拉取条数')],
|
||
|
|
req_form_example('POST', '/api/opportunity/board/cards',
|
||
|
|
[('stageId','1'),('offset','0'),('limit','20')]),
|
||
|
|
[
|
||
|
|
('id', 'string', '商机ID'),
|
||
|
|
('oppName', 'string', '商机名称'),
|
||
|
|
('primaryCustomerNameSnapshot', 'string', '主客户名'),
|
||
|
|
('projectAmount', 'number', '项目金额'),
|
||
|
|
('ownerNameSnapshot', 'string', '负责人姓名'),
|
||
|
|
('stageStayDays', 'number', '当前阶段停留天数'),
|
||
|
|
('lastFollowTime', 'string', '最近跟进时间'),
|
||
|
|
],
|
||
|
|
env_json('[\n {\n "id": "1",\n "oppName": "示例商机",\n "primaryCustomerNameSnapshot": "示例客户",\n "projectAmount": 1000000,\n "ownerNameSnapshot": "张三",\n "stageStayDays": 3,\n "lastFollowTime": "2024-01-02 10:00:00"\n }\n ]'),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/协作', '看板阶段卡片.bru',
|
||
|
|
bru_form(6, '看板阶段卡片', 'post', '/api/opportunity/board/cards',
|
||
|
|
[('stageId','1'),('offset','0'),('limit','20')], board_cards_docs))
|
||
|
|
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# 方案卡 (OpportunitySchemeCardController)
|
||
|
|
# ================================================================
|
||
|
|
print('== 方案卡 ==')
|
||
|
|
|
||
|
|
SCHEME_CARD_ROWS = [
|
||
|
|
('id', 'string', '方案卡ID'),
|
||
|
|
('createTime', 'string', '创建时间'),
|
||
|
|
('updateTime', 'string', '更新时间'),
|
||
|
|
('oppId', 'string', '所属商机ID'),
|
||
|
|
('customerId', 'string', '关联客户ID'),
|
||
|
|
('customerNameSnapshot', 'string', '客户名快照'),
|
||
|
|
('templateId', 'string', '绑定模板版本行ID'),
|
||
|
|
('templateName', 'string', '模板名称'),
|
||
|
|
('templateVersion', 'string', '模板版本号'),
|
||
|
|
('cardStatus', 'number', '卡状态:1草稿 2已提交 3已推送'),
|
||
|
|
('editable', 'boolean', '可编辑标记(cardStatus<3 可编辑)'),
|
||
|
|
('schemeBudget', 'number', '方案预算金额'),
|
||
|
|
('biddingForm', 'string', '招标形式字典 code'),
|
||
|
|
('enterStage', 'string', '介入阶段字典 code'),
|
||
|
|
]
|
||
|
|
SCHEME_CARD_ONE = (
|
||
|
|
'{\n'
|
||
|
|
' "id": "1",\n'
|
||
|
|
' "createTime": "2024-01-01 12:00:00",\n'
|
||
|
|
' "updateTime": "2024-01-01 12:00:00",\n'
|
||
|
|
' "oppId": "1",\n'
|
||
|
|
' "customerId": "1",\n'
|
||
|
|
' "customerNameSnapshot": "示例客户",\n'
|
||
|
|
' "templateId": "1",\n'
|
||
|
|
' "templateName": "标准方案模板",\n'
|
||
|
|
' "templateVersion": "v1",\n'
|
||
|
|
' "cardStatus": 1,\n'
|
||
|
|
' "editable": true,\n'
|
||
|
|
' "schemeBudget": 800000,\n'
|
||
|
|
' "biddingForm": "bid_01",\n'
|
||
|
|
' "enterStage": "stage_01"\n'
|
||
|
|
' }'
|
||
|
|
)
|
||
|
|
|
||
|
|
docs = build_docs(
|
||
|
|
'方案卡列表', 'GET', '/api/opportunity/scheme-card/list',
|
||
|
|
'一商机多张方案卡分组展示。无卡时返回空数组,前端渲染「未创建」态。',
|
||
|
|
[('oppId','string','是','-','商机ID')],
|
||
|
|
req_query_example('GET','/api/opportunity/scheme-card/list',[('oppId','1')]),
|
||
|
|
SCHEME_CARD_ROWS,
|
||
|
|
env_json(f'[\n{SCHEME_CARD_ONE}\n ]'),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/方案卡', '方案卡列表.bru',
|
||
|
|
bru_form(1, '方案卡列表', 'get', '/api/opportunity/scheme-card/list',
|
||
|
|
[('oppId','1')], docs))
|
||
|
|
|
||
|
|
# 方案卡详情
|
||
|
|
SC_DETAIL_ROWS = SCHEME_CARD_ROWS + [
|
||
|
|
('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 = (
|
||
|
|
'{\n'
|
||
|
|
' "id": "1",\n'
|
||
|
|
' "createTime": "2024-01-01 12:00:00",\n'
|
||
|
|
' "updateTime": "2024-01-01 12:00:00",\n'
|
||
|
|
' "oppId": "1",\n'
|
||
|
|
' "customerId": "1",\n'
|
||
|
|
' "customerNameSnapshot": "示例客户",\n'
|
||
|
|
' "templateId": "1",\n'
|
||
|
|
' "templateName": "标准方案模板",\n'
|
||
|
|
' "templateVersion": "v1",\n'
|
||
|
|
' "cardStatus": 1,\n'
|
||
|
|
' "editable": true,\n'
|
||
|
|
' "schemeBudget": 800000,\n'
|
||
|
|
' "biddingForm": "bid_01",\n'
|
||
|
|
' "enterStage": "stage_01",\n'
|
||
|
|
' "fields": [\n'
|
||
|
|
' {\n'
|
||
|
|
' "seqNo": 1,\n'
|
||
|
|
' "fieldKey": "requirement_summary",\n'
|
||
|
|
' "fieldName": "需求概述",\n'
|
||
|
|
' "fieldType": "TEXT",\n'
|
||
|
|
' "isRequired": 1,\n'
|
||
|
|
' "fieldValue": "示例填写内容"\n'
|
||
|
|
' }\n'
|
||
|
|
' ]\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_ROWS,
|
||
|
|
env_json(SC_DETAIL_JSON),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/方案卡', '方案卡详情.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(新建方案卡传入 templateId 字段)'),
|
||
|
|
('templateName', 'string', '模板名称'),
|
||
|
|
('templateVersion', 'string', '模板版本号'),
|
||
|
|
],
|
||
|
|
env_json('[\n {\n "templateId": "1",\n "templateName": "标准方案模板",\n "templateVersion": "v1"\n }\n ]'),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/方案卡', '可选模板版本.bru',
|
||
|
|
bru_form(3, '可选模板版本', 'get', '/api/opportunity/scheme-card/selectable-templates',
|
||
|
|
[('oppId','1')], docs))
|
||
|
|
|
||
|
|
# 保存方案卡
|
||
|
|
save_params = [
|
||
|
|
('id','string','否','-','方案卡ID(编辑时必填,新建不传)'),
|
||
|
|
('oppId','string','是','-','所属商机ID'),
|
||
|
|
('customerId','string','是','-','关联客户ID(须为本商机的关联客户;编辑时不可更换)'),
|
||
|
|
('templateId','string','条件','-','绑定模板版本行ID(新建必填,编辑续用锁定版本可不传)'),
|
||
|
|
('schemeBudget','number','否','-','方案预算金额'),
|
||
|
|
('biddingForm','string','否','-','招标形式字典 code'),
|
||
|
|
('enterStage','string','否','-','介入阶段字典 code'),
|
||
|
|
('valuesJson','string','否','-','动态字段值 JSON 字符串(复杂字段 json-string-in-form 约定);格式:`[{"fieldKey":"...","fieldValue":"..."}]`'),
|
||
|
|
]
|
||
|
|
save_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` 表单字段上送,随保存全量替换值表。',
|
||
|
|
save_params, req_form_example('POST','/api/opportunity/scheme-card/save',save_kv),
|
||
|
|
'方案卡ID(Long,序列化为 string)',
|
||
|
|
env_json('"1"'),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/方案卡', '保存方案卡.bru',
|
||
|
|
bru_form(4, '保存方案卡', 'post', '/api/opportunity/scheme-card/save', save_kv, docs))
|
||
|
|
|
||
|
|
# 提交方案卡
|
||
|
|
simple_null_post(5, '商机管理/方案卡', '提交方案卡.bru', '提交方案卡', '/api/opportunity/scheme-card/submit',
|
||
|
|
[('id','string','是','-','方案卡ID')],
|
||
|
|
[('id','1')],
|
||
|
|
'点击「确认」按钮 → 卡状态由 草稿 变为 已提交;提交前校验模板必填字段。')
|
||
|
|
|
||
|
|
|
||
|
|
# ================================================================
|
||
|
|
# 商机阶段 (OpportunityStageController)
|
||
|
|
# ================================================================
|
||
|
|
print('== 商机阶段 ==')
|
||
|
|
|
||
|
|
STAGE_NODE_ROWS = [
|
||
|
|
('oppId', 'string', '商机ID'),
|
||
|
|
('oppStatus', 'number', '商机状态(轴1)'),
|
||
|
|
('stageTemplateId', 'string', '绑定阶段模板版本行ID'),
|
||
|
|
('stageTemplateVersion', 'string', '阶段模板版本号'),
|
||
|
|
('currentStageId', 'string', '当前阶段节点ID'),
|
||
|
|
('currentWorkGoal', 'string', '当前节点工作目标(推进指引区展示)'),
|
||
|
|
('nodes', 'array<object>', '有序节点列表(按 seq_no 升序,末位为固定节点「已转项目」)'),
|
||
|
|
('nodes[].nodeId', 'string', '节点ID'),
|
||
|
|
('nodes[].seqNo', 'number', '阶段序号'),
|
||
|
|
('nodes[].stageDictCode', 'string', '阶段字典值'),
|
||
|
|
('nodes[].customNodeName', 'string', '自定义节点名称(空则前端带字典名)'),
|
||
|
|
('nodes[].workGoal', 'string', '阶段工作目标'),
|
||
|
|
('nodes[].isFixed', 'number', '固定节点:1固定(已转项目)0普通'),
|
||
|
|
('nodes[].nodeState', 'string', '进度状态:COMPLETED已完成 / CURRENT当前 / PENDING未到达'),
|
||
|
|
]
|
||
|
|
STAGE_PROGRESS_JSON = (
|
||
|
|
'{\n'
|
||
|
|
' "oppId": "1",\n'
|
||
|
|
' "oppStatus": 2,\n'
|
||
|
|
' "stageTemplateId": "1",\n'
|
||
|
|
' "stageTemplateVersion": "v1",\n'
|
||
|
|
' "currentStageId": "2",\n'
|
||
|
|
' "currentWorkGoal": "确认客户预算与决策链",\n'
|
||
|
|
' "nodes": [\n'
|
||
|
|
' {\n'
|
||
|
|
' "nodeId": "1",\n'
|
||
|
|
' "seqNo": 1,\n'
|
||
|
|
' "stageDictCode": "stage_01",\n'
|
||
|
|
' "customNodeName": null,\n'
|
||
|
|
' "workGoal": "初步接触",\n'
|
||
|
|
' "isFixed": 0,\n'
|
||
|
|
' "nodeState": "COMPLETED"\n'
|
||
|
|
' }\n'
|
||
|
|
' ]\n'
|
||
|
|
' }'
|
||
|
|
)
|
||
|
|
docs = build_docs(
|
||
|
|
'阶段推进指引', 'GET', '/api/opportunity/stage/progress',
|
||
|
|
'商机概览页三态进度展示:已完成 / 当前 / 未完成 + 当前节点工作目标。按商机绑定的模板版本(锁版本,发新版不迁移)展开有序节点;节点展示名 = customNodeName 优先,空则前端按 stageDictCode 带字典名。',
|
||
|
|
[('oppId','string','是','-','商机ID')],
|
||
|
|
req_query_example('GET','/api/opportunity/stage/progress',[('oppId','1')]),
|
||
|
|
STAGE_NODE_ROWS,
|
||
|
|
env_json(STAGE_PROGRESS_JSON),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/商机阶段', '阶段推进指引.bru',
|
||
|
|
bru_form(1, '阶段推进指引', 'get', '/api/opportunity/stage/progress',
|
||
|
|
[('oppId','1')], docs))
|
||
|
|
|
||
|
|
simple_null_post(2, '商机管理/商机阶段', '切换阶段.bru', '切换阶段', '/api/opportunity/stage/switch',
|
||
|
|
[('oppId','string','是','-','商机ID'),
|
||
|
|
('toStageId','string','是','-','目标阶段节点ID(须属商机绑定的模板版本且非固定节点)'),
|
||
|
|
('remark','string','否','-','推进说明')],
|
||
|
|
[('oppId','1'),('toStageId','3'),('remark','客户已确认预算')],
|
||
|
|
'自由切换阶段:允许连跳 / 回退。「已转项目」固定节点不可手动切(由业务流程触发)。')
|
||
|
|
|
||
|
|
# 阶段历史
|
||
|
|
STAGE_HIST_ROWS = [
|
||
|
|
('id','string','历史记录ID'),
|
||
|
|
('createTime','string','创建时间'),
|
||
|
|
('updateTime','string','更新时间'),
|
||
|
|
('oppId','string','商机ID'),
|
||
|
|
('fromStageId','string','原阶段节点ID'),
|
||
|
|
('toStageId','string','目标阶段节点ID'),
|
||
|
|
('operatorUserId','string','操作人ID(系统动作为 0)'),
|
||
|
|
('operateTime','string','操作时间'),
|
||
|
|
('remark','string','推进说明'),
|
||
|
|
]
|
||
|
|
STAGE_HIST_JSON = (
|
||
|
|
'[\n'
|
||
|
|
' {\n'
|
||
|
|
' "id": "1",\n'
|
||
|
|
' "createTime": "2024-01-01 12:00:00",\n'
|
||
|
|
' "updateTime": "2024-01-01 12:00:00",\n'
|
||
|
|
' "oppId": "1",\n'
|
||
|
|
' "fromStageId": "1",\n'
|
||
|
|
' "toStageId": "2",\n'
|
||
|
|
' "operatorUserId": "1",\n'
|
||
|
|
' "operateTime": "2024-01-01 12:00:00",\n'
|
||
|
|
' "remark": "推进说明示例"\n'
|
||
|
|
' }\n'
|
||
|
|
' ]'
|
||
|
|
)
|
||
|
|
docs = build_docs(
|
||
|
|
'阶段推进历史', 'GET', '/api/opportunity/stage/history',
|
||
|
|
'按操作时间倒序返回该商机的全部阶段推进记录(人工推进 + 系统动作合并展示)。',
|
||
|
|
[('oppId','string','是','-','商机ID')],
|
||
|
|
req_query_example('GET','/api/opportunity/stage/history',[('oppId','1')]),
|
||
|
|
STAGE_HIST_ROWS,
|
||
|
|
env_json(STAGE_HIST_JSON),
|
||
|
|
)
|
||
|
|
write_bru('商机管理/商机阶段', '阶段推进历史.bru',
|
||
|
|
bru_form(3, '阶段推进历史', 'get', '/api/opportunity/stage/history',
|
||
|
|
[('oppId','1')], docs))
|
||
|
|
|
||
|
|
print('part2 done — 协作/方案卡/商机阶段 完成')
|