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.
 
 
 
 
 
 

336 lines
16 KiB

# -*- 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 — 协作/方案卡/商机阶段 完成')