# -*- coding: utf-8 -*- """第3批:新建商机(2)+ 状态流转(11)""" 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 # ── 新建商机 ───────────────────────────────────────────────────────── print('== 新建商机 ==') CREATE_PARAMS = [ ('oppName', 'string', '是', '-', '商机名称'), ('oppSource', 'string', '是', '-', '商机来源字典 code'), ('oppType', 'string', '是', '-', '商机类型字典 code'), ('industryCode', 'string', '否', '-', '行业字典 code'), ('bidForm', 'string', '否', '-', '招标形式字典 code'), ('localityType', 'string', '否', '-', '项目属地字典 code'), ('partyAClear', 'number', '否', '0', '甲方是否明确:1是 0否'), ('partyA', 'string', '否', '-', '甲方名称(partyAClear=1 时建议填)'), ('provinceCode', 'string', '否', '-', '省份国标 code'), ('cityCode', 'string', '否', '-', '市/区国标 code'), ('projectAmount', 'number', '否', '-', '预计项目金额'), ('ownerUserId', 'string', '否', '-', '负责人用户 ID(不传则默认当前登录人)'), ('sourceLeadId', 'string', '否', '-', '来源线索 ID(从线索转商机时带入)'), ('primaryCustomerId','string','否','-', '主要意向客户 ID'), ('remark', 'string', '否', '-', '备注'), ] CREATE_KV = [ ('oppName','示例商机'),('oppSource','source_01'),('oppType','type_01'), ('industryCode','ind_01'),('projectAmount','1000000'),('primaryCustomerId','1'), ] docs = build_docs( '新建商机', 'POST', '/api/opportunity', '顶部「新建商机」按钮弹窗「确认」触发。新建后商机初始状态由系统判断:若当前用户有领取权限则直接进入「推进中」,否则进入「待领取」公海池。\n' '阶段模板绑定:按负责人部门匹配发布中模板版本自动绑定(不传 ownerUserId 按当前用户部门)。', CREATE_PARAMS, req_form_example('POST', '/api/opportunity', CREATE_KV), '新建商机 ID(Long,序列化为 string)', env_json('"1"'), ) write_bru('商机模块/新建商机', '新建商机.bru', bru_form(1, '新建商机', 'post', '/api/opportunity', CREATE_KV, docs)) docs = build_docs( '可关联线索下拉', 'GET', '/api/opportunity/convertible-leads', '新建商机弹窗「来源线索」下拉数据源:返回当前用户可关联的线索列表(已转商机的线索不再出现)。', [], req_query_example('GET', '/api/opportunity/convertible-leads', []), [('id','string','线索 ID'),('leadName','string','线索名称'),('phone','string','联系电话(脱敏)')], env_json('[\n {"id":"1","leadName":"示例线索","phone":"138****0001"}\n ]'), ) write_bru('商机模块/新建商机', '可关联线索下拉.bru', bru_form(2, '可关联线索下拉', 'get', '/api/opportunity/convertible-leads', [], docs)) # ── 状态流转 ────────────────────────────────────────────────────────── print('== 状态流转 ==') def null_post(seq, 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('商机模块/状态流转', f'{name}.bru', bru_form(seq, name, 'post', path, kv, d)) null_post(1, '领取商机', '/api/opportunity/claim', [('id','string','是','-','商机 ID'), ('ownerNameSnapshot','string','是','-','领取人姓名快照(冗余存储防后续改名)'), ('ownerDeptId','string','是','-','领取人部门 ID')], [('id','1'),('ownerNameSnapshot','张三'),('ownerDeptId','1')], '公海池商机「领取」(E1):`oppStatus: 1待领取 → 2推进中`,写入领取人快照,绑定阶段模板。') null_post(2, '批量领取', '/api/opportunity/claim-batch', [('ids','string','是','-','商机 ID 列表,逗号分隔'), ('ownerNameSnapshot','string','是','-','领取人姓名快照'), ('ownerDeptId','string','是','-','领取人部门 ID')], [('ids','1,2'),('ownerNameSnapshot','张三'),('ownerDeptId','1')], '批量领取:逐条执行领取逻辑,每条成功/失败独立记录;返回 null 前端轮询刷新列表。') null_post(3, '分配商机', '/api/opportunity/assign', [('id','string','是','-','商机 ID'), ('toUserId','string','是','-','被分配人用户 ID'), ('toUserNameSnapshot','string','是','-','被分配人姓名快照'), ('toDeptId','string','是','-','被分配人部门 ID')], [('id','1'),('toUserId','2'),('toUserNameSnapshot','李四'),('toDeptId','1')], '管理员分配商机(E2):公海或推进中商机均可分配,负责人变更,记操作日志。') null_post(4, '批量分配', '/api/opportunity/assign-batch', [('ids','string','是','-','商机 ID 列表,逗号分隔'), ('toUserId','string','是','-','被分配人用户 ID'), ('toUserNameSnapshot','string','是','-','被分配人姓名快照'), ('toDeptId','string','是','-','被分配人部门 ID')], [('ids','1,2'),('toUserId','2'),('toUserNameSnapshot','李四'),('toDeptId','1')], '批量分配:逐条执行分配逻辑,独立成功/失败。') null_post(5, '抛公海', '/api/opportunity/release-pool', [('id','string','是','-','商机 ID'), ('poolReason','string','是','-','进入公海原因字典 code')], [('id','1'),('poolReason','reason_01')], '手动抛公海(E3):`oppStatus: 2推进中/3暂缓中 → 1待领取`,清空负责人快照,记入池原因。') null_post(6, '暂缓', '/api/opportunity/pause', [('id','string','是','-','商机 ID'), ('remark','string','否','-','暂缓原因说明')], [('id','1'),('remark','客户内部调整,暂缓推进')], '暂缓商机(E4):`oppStatus: 2推进中 → 3暂缓中`,商机暂停推进但不进公海,阶段轴 2 冻结。') null_post(7, '取消暂缓', '/api/opportunity/resume', [('id','string','是','-','商机 ID')], [('id','1')], '取消暂缓(E5):`oppStatus: 3暂缓中 → 2推进中`,恢复阶段轴 2 计时。') null_post(8, '关闭商机', '/api/opportunity/close', [('id','string','是','-','商机 ID'), ('closeReason','string','是','-','关闭原因字典 code'), ('remark','string','否','-','关闭说明')], [('id','1'),('closeReason','reason_close_01'),('remark','竞争失败')], '关闭商机(E6):`oppStatus → 4已关闭`,终态不可逆(只能「重启」回推进中)。') null_post(9, '批量关闭', '/api/opportunity/close-batch', [('ids','string','是','-','商机 ID 列表,逗号分隔'), ('closeReason','string','是','-','关闭原因字典 code'), ('remark','string','否','-','关闭说明')], [('ids','1,2'),('closeReason','reason_close_01')], '批量关闭:逐条执行关闭逻辑,独立成功/失败。') null_post(10, '重启商机', '/api/opportunity/reopen', [('id','string','是','-','商机 ID')], [('id','1')], '重启商机(E7):`oppStatus: 4已关闭 → 2推进中`,重新激活商机,阶段轴 2 继续计时。') null_post(11, '移交商机', '/api/opportunity/handover', [('id','string','是','-','商机 ID'), ('toUserId','string','是','-','接收人用户 ID'), ('toUserNameSnapshot','string','是','-','接收人姓名快照'), ('toDeptId','string','是','-','接收人部门 ID'), ('expectedOwnerUserId','string','是','-','乐观锁:当前负责人 ID(防脏移交;若移交前已被他人移交则拒绝)')], [('id','1'),('toUserId','2'),('toUserNameSnapshot','李四'),('toDeptId','1'),('expectedOwnerUserId','1')], '移交商机(E9):负责人主动将商机转给他人,`expectedOwnerUserId` 做乐观并发校验防脏移交。') print('batch3 done')