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.

305 lines
17 KiB

2 weeks ago
# -*- coding: utf-8 -*-
"""Generate all crm-opportunity .bru files."""
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, write_folder_bru, bru_form, build_docs, env_json,
page_json, req_form_example, req_query_example,
)
# ---------- OpportunityDTO rows + JSON (shared) ----------
OPP_DTO_ROWS = [
('id', 'string', '商机ID(Long,序列化为 string)'),
('createTime', 'string', '创建时间 yyyy-MM-dd HH:mm:ss'),
('updateTime', 'string', '更新时间 yyyy-MM-dd HH:mm:ss'),
('oppName', 'string', '商机名称'),
('oppSource', 'string', '商机来源字典 code'),
('oppType', 'string', '商机类型字典 code'),
('industryCode', 'string', '行业字典 code'),
('bidForm', 'string', '招标形式字典 code'),
('localityType', 'string', '项目属地字典 code'),
('partyAClear', 'number', '甲方是否明确 0/1'),
('partyA', 'string', '甲方名称'),
('provinceCode', 'string', '省份国标 code'),
('cityCode', 'string', '市/区国标 code'),
('remark', 'string', '备注'),
('projectAmount', 'number', '项目金额(列表展示为「预计金额」)'),
('ownerUserId', 'string', '负责人ID(Long,序列化为 string)'),
('ownerNameSnapshot', 'string', '负责人姓名快照'),
('ownerDeptId', 'string', '负责人部门ID快照'),
('poolReason', 'string', '进入公海原因字典 code'),
('oppStatus', 'number', '商机状态(轴1:1待领取/2推进中/3暂缓中/4已关闭/5已转项目)'),
('currentStageId', 'string', '当前阶段节点ID(轴2)'),
('stageTemplateVersion', 'string', '阶段模板版本号冗余'),
('sourceLeadId', 'string', '来源线索ID'),
('primaryCustomerId', 'string', '主要意向客户ID'),
('primaryCustomerNameSnapshot', 'string', '主客户名快照(列表「意向客户」列)'),
('claimTime', 'string', '领取时刻'),
('lastValidFollowTime', 'string', '最近有效跟进时间'),
('oppSourceName', 'string', '商机来源名称(出参回显,字典快照)'),
('industryName', 'string', '行业名称(出参回显,字典快照)'),
('provinceName', 'string', '省份名称(出参回显)'),
('cityName', 'string', '市/区名称(出参回显)'),
('ownerDeptName', 'string', '负责人部门名称(出参回显)'),
('currentStageName', 'string', '当前工作节点名称(出参回显)'),
('stageStayDays', 'number', '节点停留时间(天,出参回显)'),
('nextStageName', 'string', '下一节点名称(出参回显,展示用建议下一步)'),
('lastFollowTime', 'string', '最近跟进时间(出参回显)'),
('schemeCardStatus', 'number', '方案卡状态(出参回显;无主客户卡=未创建0)'),
('schemeBudget', 'number', '方案预算(出参回显)'),
('followed', 'boolean', '当前用户是否已关注(出参回显)'),
('poolStageNameSnapshot', 'string', '入池前工作节点名称(出参回显,公海视图专属)'),
]
OPP_DTO_JSON_DETAIL = ( # for detail: data is one object
'{\n'
' "id": "1",\n'
' "createTime": "2024-01-01 12:00:00",\n'
' "updateTime": "2024-01-01 12:00:00",\n'
' "oppName": "示例商机",\n'
' "oppSource": "opp_source_01",\n'
' "oppType": "opp_type_01",\n'
' "industryCode": "industry_01",\n'
' "bidForm": "bid_01",\n'
' "localityType": "local_01",\n'
' "partyAClear": 1,\n'
' "partyA": "示例甲方",\n'
' "provinceCode": "110000",\n'
' "cityCode": "110100",\n'
' "remark": "备注示例",\n'
' "projectAmount": 1000000,\n'
' "ownerUserId": "1",\n'
' "ownerNameSnapshot": "张三",\n'
' "ownerDeptId": "1",\n'
' "poolReason": null,\n'
' "oppStatus": 2,\n'
' "currentStageId": "1",\n'
' "stageTemplateVersion": "v1",\n'
' "sourceLeadId": "1",\n'
' "primaryCustomerId": "1",\n'
' "primaryCustomerNameSnapshot": "示例客户",\n'
' "claimTime": "2024-01-01 12:00:00",\n'
' "lastValidFollowTime": "2024-01-02 10:00:00",\n'
' "oppSourceName": "线索转入",\n'
' "industryName": "示例行业",\n'
' "provinceName": "北京市",\n'
' "cityName": "北京市",\n'
' "ownerDeptName": "华北大区",\n'
' "currentStageName": "需求确认",\n'
' "stageStayDays": 3,\n'
' "nextStageName": "方案报价",\n'
' "lastFollowTime": "2024-01-02 10:00:00",\n'
' "schemeCardStatus": 1,\n'
' "schemeBudget": 800000,\n'
' "followed": true,\n'
' "poolStageNameSnapshot": null\n'
' }'
)
# ---------- folders ----------
print('== folders ==')
write_folder_bru('商机管理', '商机管理', 20)
write_folder_bru('销售机会', '销售机会', 21)
write_folder_bru('商机公海', '商机公海', 22)
for i, name in enumerate(['商机新建', '商机详情', '商机流转', '协作', '方案卡', '商机阶段', '详情子域'], start=10):
write_folder_bru(f'商机管理/{name}', name, i)
for i, name in enumerate(['最近访问', '我负责的', '我参与的', '我关注的'], start=10):
write_folder_bru(f'销售机会/{name}', name, i)
# ================================================================
# 商机新建
# ================================================================
print('== 商机新建 ==')
# --- 1. POST /api/opportunity 新建商机
params = [
('oppSource', 'string', '', '-', '商机来源字典 code(opp_source_01=线索转入触发关联线索必填)'),
('sourceLeadId', 'string', '条件', '-', '关联线索 id(Long,序列化为 string;oppSource=线索转入时必填)'),
('opportunityName', 'string', '', '-', '商机名称'),
('oppType', 'string', '', '-', '商机类型字典 code'),
('industryCode', 'string', '', '-', '行业字典 code'),
('bidForm', 'string', '', '-', '招标形式字典 code'),
('localityType', 'string', '', '-', '项目属地字典 code(本地/异地)'),
('partyAClear', 'number', '', '0', '甲方是否明确 0/1'),
('partyA', 'string', '', '-', '甲方'),
('customerId', 'string', '', '-', '主要意向客户 id(Long,序列化为 string;A4 未就绪前可空)'),
('customerName', 'string', '', '-', '主要意向客户名快照'),
('provinceCode', 'string', '', '-', '省国标 code'),
('cityCode', 'string', '', '-', '市国标 code'),
('remark', 'string', '', '-', '备注'),
]
kv = [('oppSource','opp_source_01'),('sourceLeadId','1'),('opportunityName','示例商机'),
('oppType','opp_type_01'),('industryCode','industry_01'),('bidForm','bid_01'),
('localityType','local_01'),('partyAClear','1'),('partyA','示例甲方'),
('customerId','1'),('customerName','示例客户'),('provinceCode','110000'),
('cityCode','110100'),('remark','备注示例')]
docs = build_docs(
'新建商机', 'POST', '/api/opportunity',
'商机侧新建入口(薄适配层,与线索侧转商机 port 并列的第二条建商机入口)。`oppSource=线索转入`(opp_source_01)时须选【关联线索】(下拉数据源见 `GET /api/opportunity/convertible-leads`);其余来源 `sourceLeadId` 可空。当前登录人自动为负责人/创建人。',
params,
req_form_example('POST', '/api/opportunity', kv),
'新建商机 ID(Long,序列化为 string)',
env_json('"1"'),
)
write_bru('商机管理/商机新建', '新建商机.bru',
bru_form(1, '新建商机', 'post', '/api/opportunity', kv, docs))
# --- 2. GET /api/opportunity/convertible-leads
docs = build_docs(
'可关联线索下拉候选', 'GET', '/api/opportunity/convertible-leads',
'新建商机弹窗中「关联线索」下拉数据源。返回当前登录人可关联的线索:状态 = 已领取 / 跟进中,且未被商机占用(未被任何商机的 source_lead_id 引用)。',
[],
req_query_example('GET', '/api/opportunity/convertible-leads', []),
[
('id', 'string', '线索 id(Long,序列化为 string,回填 sourceLeadId)'),
('leadName', 'string', '线索名称(下拉展示)'),
('phone', 'string', '联系电话(下拉辅助展示)'),
],
env_json('[\n {\n "id": "1",\n "leadName": "示例线索",\n "phone": "13800000000"\n }\n ]'),
)
write_bru('商机管理/商机新建', '可关联线索下拉.bru',
bru_form(2, '可关联线索下拉', 'get', '/api/opportunity/convertible-leads', [], docs))
# ================================================================
# 商机详情
# ================================================================
print('== 商机详情 ==')
docs = build_docs(
'商机详情', 'GET', '/api/opportunity/detail',
'商机详情页顶部信息 + 全量字段回显。返回 OpportunityDTO(含所有主表字段 + 出参回显字段:字典/部门/区划名、计算列、当前用户是否已关注等)。',
[('id', 'string', '', '-', '商机 ID(Long,序列化为 string)')],
req_query_example('GET', '/api/opportunity/detail', [('id', '1')]),
OPP_DTO_ROWS,
env_json(OPP_DTO_JSON_DETAIL),
)
write_bru('商机管理/商机详情', '商机详情.bru',
bru_form(1, '商机详情', 'get', '/api/opportunity/detail', [('id', '1')], docs))
edit_params = [
('id', 'string', '', '-', '商机 ID(Long,序列化为 string)'),
('oppName', 'string', '', '-', '商机名称'),
('oppSource', 'string', '', '-', '商机来源字典 code'),
('oppType', 'string', '', '-', '商机类型字典 code'),
('industryCode', 'string', '', '-', '行业字典 code'),
('bidForm', 'string', '', '-', '招标形式字典 code'),
('localityType', 'string', '', '-', '项目属地字典 code'),
('partyAClear', 'number', '', '-', '甲方是否明确 0/1'),
('partyA', 'string', '', '-', '甲方名称'),
('provinceCode', 'string', '', '-', '省份国标 code'),
('cityCode', 'string', '', '-', '市/区国标 code'),
('remark', 'string', '', '-', '备注'),
('projectAmount', 'number', '', '-', '项目金额'),
('primaryCustomerId', 'string', '', '-', '主要意向客户ID(Long,序列化为 string)'),
('primaryCustomerNameSnapshot', 'string', '', '-', '主客户名快照'),
]
edit_kv = [('id','1'),('oppName','示例商机'),('oppType','opp_type_01'),('industryCode','industry_01'),
('bidForm','bid_01'),('localityType','local_01'),('partyAClear','1'),('partyA','示例甲方'),
('provinceCode','110000'),('cityCode','110100'),('remark','备注示例'),
('projectAmount','1000000'),('primaryCustomerId','1'),('primaryCustomerNameSnapshot','示例客户')]
docs = build_docs(
'编辑商机', 'POST', '/api/opportunity/edit',
'商机详情页编辑保存(非终态可编辑;全量字段覆盖)。入参为 OpportunityDTO,仅列出常用可编辑字段;已关闭/已转项目等终态由服务层拒绝。',
edit_params,
req_form_example('POST', '/api/opportunity/edit', edit_kv),
None,
env_json('null'),
)
write_bru('商机管理/商机详情', '编辑商机.bru',
bru_form(2, '编辑商机', 'post', '/api/opportunity/edit', edit_kv, docs))
# ================================================================
# 商机流转 (Transition)
# ================================================================
print('== 商机流转 ==')
def transition_bru(seq, 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('商机管理/商机流转', filename,
bru_form(seq, name, 'post', path, kv, d))
# claim
transition_bru(1, '领取商机.bru', '领取商机', '/api/opportunity/claim',
[('id','string','','-','商机ID(Long,序列化为 string)'),
('ownerNameSnapshot','string','','-','负责人姓名快照(前端预填当前登录人姓名)'),
('ownerDeptId','string','','-','负责人部门ID快照(Long,序列化为 string;前端预填当前登录人部门)')],
[('id','1'),('ownerNameSnapshot','张三'),('ownerDeptId','1')],
'状态机 E1:待领取 → 推进中。当前登录人成为负责人,`claim_time` 落时刻。')
transition_bru(2, '批量领取.bru', '批量领取', '/api/opportunity/claim-batch',
[('ids','string[]','','-','商机ID列表(逗号分隔,Long序列化为 string)'),
('ownerNameSnapshot','string','','-','负责人姓名快照'),
('ownerDeptId','string','','-','负责人部门ID快照')],
[('ids','1,2,3'),('ownerNameSnapshot','张三'),('ownerDeptId','1')],
'状态机 E1 批量版。每条独立事务;单条失败不影响其他条。')
transition_bru(3, '分配商机.bru', '分配商机', '/api/opportunity/assign',
[('id','string','','-','商机ID(Long,序列化为 string)'),
('userId','string','','-','被分配的销售用户ID(Long,序列化为 string)'),
('ownerNameSnapshot','string','','-','负责人姓名快照'),
('ownerDeptId','string','','-','负责人部门ID快照')],
[('id','1'),('userId','2'),('ownerNameSnapshot','李四'),('ownerDeptId','1')],
'状态机 E2:待领取 → 推进中。指派他人为负责人(管理员/主管权限)。')
transition_bru(4, '批量分配.bru', '批量分配', '/api/opportunity/assign-batch',
[('ids','string[]','','-','商机ID列表(逗号分隔)'),
('userId','string','','-','被分配的销售用户ID'),
('ownerNameSnapshot','string','','-','负责人姓名快照'),
('ownerDeptId','string','','-','负责人部门ID快照')],
[('ids','1,2,3'),('userId','2'),('ownerNameSnapshot','李四'),('ownerDeptId','1')],
'状态机 E2 批量版。每条独立事务。')
transition_bru(5, '抛公海.bru', '抛公海', '/api/opportunity/release-pool',
[('id','string','','-','商机ID'),
('poolReason','string','','-','进入公海原因字典 code')],
[('id','1'),('poolReason','pool_reason_01')],
'状态机 E3:推进中 → 待领取。清空负责人,商机重回公海池。')
transition_bru(6, '暂缓.bru', '暂缓', '/api/opportunity/pause',
[('id','string','','-','商机ID'),
('pauseReason','string','','-','暂缓原因字典 code'),
('pauseExpectedRestartDate','string','','-','预计重启日期 yyyy-MM-dd'),
('pauseRemark','string','','-','暂缓备注')],
[('id','1'),('pauseReason','pause_reason_01'),('pauseExpectedRestartDate','2024-06-01'),('pauseRemark','等待客户预算落地')],
'状态机 E4:推进中 → 暂缓中。')
transition_bru(7, '取消暂缓.bru', '取消暂缓', '/api/opportunity/resume',
[('id','string','','-','商机ID')],
[('id','1')],
'状态机 E5:暂缓中 → 推进中。')
transition_bru(8, '关闭商机.bru', '关闭商机', '/api/opportunity/close',
[('id','string','','-','商机ID'),
('closeReason','string','','-','关闭原因字典 code'),
('closeRemark','string','','-','关闭备注')],
[('id','1'),('closeReason','close_reason_01'),('closeRemark','客户放弃项目')],
'状态机 E6:推进中/暂缓中 → 已关闭。')
transition_bru(9, '批量关闭.bru', '批量关闭', '/api/opportunity/close-batch',
[('ids','string[]','','-','商机ID列表(逗号分隔)'),
('closeReason','string','','-','关闭原因字典 code'),
('closeRemark','string','','-','关闭备注')],
[('ids','1,2,3'),('closeReason','close_reason_01'),('closeRemark','客户放弃项目')],
'状态机 E6 批量版。每条独立事务。')
transition_bru(10, '重启商机.bru', '重启商机', '/api/opportunity/reopen',
[('id','string','','-','商机ID'),
('reopenReason','string','','-','重启原因')],
[('id','1'),('reopenReason','客户重新启动项目')],
'状态机 E7:已关闭 → 推进中。')
transition_bru(11, '移交商机.bru', '移交商机', '/api/opportunity/handover',
[('id','string','','-','商机ID'),
('expectedOwnerUserId','string','','-','期望的当前负责人ID(乐观并发校验,防脏移交)'),
('newOwnerUserId','string','','-','新负责人用户ID'),
('ownerNameSnapshot','string','','-','新负责人姓名快照'),
('ownerDeptId','string','','-','新负责人部门ID快照')],
[('id','1'),('expectedOwnerUserId','1'),('newOwnerUserId','2'),('ownerNameSnapshot','李四'),('ownerDeptId','1')],
'状态机 E9:推进中/暂缓中自环换 owner。`expectedOwnerUserId` 提供乐观并发校验:与库中当前负责人不符则拒绝。原负责人保留在团队白名单(落入「我参与的」)。')
print('part1 done — 商机新建/详情/流转 完成')