Browse Source
§7.4–7.7 四张表实体 + Mapper 骨架(对齐票 03 粒度,只落结构不接线): - OpportunityFollow 跟进记录:无草稿态,软删+delete_key,支持删除不回退锚点,本期不做编辑 - OpportunityAttachment 统一附件表:biz_type+biz_id 定位(AttachmentBizType 枚举 OPP/FOLLOW_UP/SITE_SURVEY/SCHEME_CARD),文件本体走 crm-file,软删+delete_key; 是票 07/08 的附件契约落点 - OpportunityWorkPlan 工作计划:结构 only,plan_status 0/1,精确态枚举与联动归 A1X - OpportunityOplog 操作日志:字段级审计,双维度 OplogKind(FIELD_CHANGE/ROW_ADD/ ROW_DELETE)+ OplogLogType(7 类,SYSTEM_INTERNAL 记库不显),只增不删无 delete_key, 带 biz_ref + snapshotJson 测试:OpportunityChildTablesMapperTest(表绑定+列映射+逻辑删)+ EnumTest(值域), crm-opportunity 100→108 全绿。CONTEXT/底稿落地记录回填。 接线待后续:跟进/附件读写 Service/Controller、统一审计切面写 oplog、 last_valid_follow_time 联动、crm-dict 跟进方式/结果标签/资料类型分组master
15 changed files with 584 additions and 1 deletions
@ -0,0 +1,63 @@ |
|||
package com.crm.opportunity.domain.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.crm.base.domain.entity.BaseEntity; |
|||
import jakarta.persistence.Column; |
|||
import jakarta.persistence.Entity; |
|||
import jakarta.persistence.Index; |
|||
import jakarta.persistence.Table; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.hibernate.annotations.Comment; |
|||
|
|||
/** |
|||
* 统一附件子表(票 09 §7.5 / 原型 A3-1-1-1-5,文件本体复用 crm-file) |
|||
* |
|||
* <p>一张附件表 + {@code biz_type}+{@code biz_id} 定位业务对象,不为每个子表各建附件表 |
|||
* (票 07/08 的附件契约落点)。附件归属商机(无孤立附件硬约束):{@code biz_type=OPP} 时 |
|||
* {@code biz_id}=opp_id 或空,其余类型 {@code biz_id}=对应子表业务对象 id |
|||
* ({@link com.crm.opportunity.domain.enums.AttachmentBizType})。</p> |
|||
* |
|||
* <p>数量上限:仅跟进附件限 10 个/条(原型明画,应用层校验);其余 biz_type 不设业务上限, |
|||
* 由 crm-file 层统一管控(grill 补拍 20260821)。本表存资料类型/说明/上传人/时间, |
|||
* 文件本体走 crm-file。软删 + {@code delete_key}(对齐子表族通用约定)。</p> |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@TableName("opportunity_attachment") |
|||
@Entity |
|||
@Table(name = "opportunity_attachment", |
|||
indexes = { |
|||
@Index(name = "idx_oa_opp", columnList = "opp_id"), |
|||
@Index(name = "idx_oa_biz", columnList = "biz_type,biz_id") |
|||
}) |
|||
public class OpportunityAttachment extends BaseEntity { |
|||
|
|||
@Comment("所属商机ID(opportunity.id,附件恒归属商机,无孤立附件)") |
|||
@Column(columnDefinition = "bigint not null comment '所属商机ID'") |
|||
private Long oppId; |
|||
|
|||
@Comment("业务归属类型:OPP/FOLLOW_UP/SITE_SURVEY/SCHEME_CARD(AttachmentBizType)") |
|||
@Column(columnDefinition = "varchar(32) not null comment '业务归属类型'") |
|||
private String bizType; |
|||
|
|||
@Comment("业务对象ID(跟进/勘察/方案卡 id;biz_type=OPP 时=opp_id 或空)") |
|||
@Column(columnDefinition = "bigint comment '业务对象ID'") |
|||
private Long bizId; |
|||
|
|||
@Comment("资料类型字典 code(现场资料/设计清单·图纸/客户需求资料…,crm-dict「商机资料类型」分组)") |
|||
@Column(columnDefinition = "varchar(32) comment '资料类型字典code'") |
|||
private String materialType; |
|||
|
|||
@Comment("文件ID(引用 crm-file 文件本体)") |
|||
@Column(columnDefinition = "bigint not null comment '文件ID(crm-file)'") |
|||
private Long fileId; |
|||
|
|||
@Comment("附件说明(选填)") |
|||
@Column(columnDefinition = "varchar(500) comment '附件说明'") |
|||
private String remark; |
|||
|
|||
@Comment("软删除复用键:存活时固定 0,逻辑删除时写为主键 id") |
|||
@Column(columnDefinition = "bigint not null default 0 comment '软删除复用键'") |
|||
private Long deleteKey; |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
package com.crm.opportunity.domain.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.crm.base.domain.entity.BaseEntity; |
|||
import jakarta.persistence.Column; |
|||
import jakarta.persistence.Entity; |
|||
import jakarta.persistence.Index; |
|||
import jakarta.persistence.Table; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.hibernate.annotations.Comment; |
|||
|
|||
import java.time.LocalDateTime; |
|||
|
|||
/** |
|||
* 跟进记录子表(票 09 §7.4 / 原型 A3-1-1-1-6,写跟进 A3-1-1-2-2) |
|||
* |
|||
* <p>销售过程痕迹:写跟进弹窗「确认」即校验 + 提交,无草稿态(异于线索反馈记录)。 |
|||
* 保存跟进 → 刷新商机「最近跟进」 + {@code last_valid_follow_time}(回收锚点刷新点之一, |
|||
* 票 05 联动)。与阶段推进**不联动**(写跟进不自动推阶段,票 06)。</p> |
|||
* |
|||
* <p>删除口径(grill 拍板 20260821):**支持删除**,删除记一条 ROW_DELETE oplog;但 |
|||
* {@code last_valid_follow_time} 删除后保持不变(不回退重算,防「写了又删」刷锚点); |
|||
* 「最近跟进」计算列按删除后最新留存行自然变化。**编辑本期不做**(写错以删除 + 补写修正, |
|||
* 待确认清单 P2-9)。软删 + {@code delete_key} 复用键(对齐子表族通用约定)。</p> |
|||
* |
|||
* <p>附件走统一附件表 {@code opportunity_attachment}({@code biz_type=FOLLOW_UP},最多 10 个/条)。</p> |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@TableName("opportunity_follow") |
|||
@Entity |
|||
@Table(name = "opportunity_follow", |
|||
indexes = { |
|||
@Index(name = "idx_of_opp", columnList = "opp_id"), |
|||
@Index(name = "idx_of_customer", columnList = "customer_id") |
|||
}) |
|||
public class OpportunityFollow extends BaseEntity { |
|||
|
|||
@Comment("所属商机ID(opportunity.id)") |
|||
@Column(columnDefinition = "bigint not null comment '所属商机ID'") |
|||
private Long oppId; |
|||
|
|||
@Comment("跟进内容(必填,长文本)") |
|||
@Column(columnDefinition = "text not null comment '跟进内容'") |
|||
private String followContent; |
|||
|
|||
@Comment("跟进时间(必填,默认当前时间可改)") |
|||
@Column(columnDefinition = "datetime not null comment '跟进时间'") |
|||
private LocalDateTime followTime; |
|||
|
|||
@Comment("跟进方式字典 code(电话沟通/上门拜访/微信对接/线上会议/展会沟通,crm-dict「跟进方式」分组)") |
|||
@Column(columnDefinition = "varchar(32) not null comment '跟进方式字典code'") |
|||
private String followWay; |
|||
|
|||
@Comment("关联客户ID(必填,默认带出商机关联客户,引用 opportunity_customer.customer_id)") |
|||
@Column(columnDefinition = "bigint not null comment '关联客户ID'") |
|||
private Long customerId; |
|||
|
|||
@Comment("客户联系人ID(选填,引用 A4 联系人,未建)") |
|||
@Column(columnDefinition = "bigint comment '客户联系人ID'") |
|||
private Long contactId; |
|||
|
|||
@Comment("跟进结果标签字典 code(选填,暂无进展/需求确认/方案推进…,crm-dict「跟进结果标签」分组)") |
|||
@Column(columnDefinition = "varchar(32) comment '跟进结果标签字典code'") |
|||
private String resultTag; |
|||
|
|||
@Comment("计划下次跟进时间(选填,不得早于本次跟进时间,应用层校验;可生成后续提醒/工作计划)") |
|||
@Column(columnDefinition = "datetime comment '计划下次跟进时间'") |
|||
private LocalDateTime nextFollowTime; |
|||
|
|||
@Comment("软删除复用键:存活时固定 0,逻辑删除时写为主键 id") |
|||
@Column(columnDefinition = "bigint not null default 0 comment '软删除复用键'") |
|||
private Long deleteKey; |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
package com.crm.opportunity.domain.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.crm.base.domain.entity.BaseEntity; |
|||
import jakarta.persistence.Column; |
|||
import jakarta.persistence.Entity; |
|||
import jakarta.persistence.Index; |
|||
import jakarta.persistence.Table; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.hibernate.annotations.Comment; |
|||
|
|||
import java.time.LocalDateTime; |
|||
|
|||
/** |
|||
* 操作日志子表(票 09 §7.7 / 原型 A3-1-1-1-9):字段级变更审计 + 双维度分类 |
|||
* |
|||
* <p>异于线索 {@code lead_history}(动作枚举 + JSON detail):商机操作日志走**字段级审计模型** |
|||
* ——实体对象 / 字段名 / 旧值 / 新值 / 操作说明 / 来源(系统自动 vs 人工)。一条日志由两个正交 |
|||
* 枚举描述(grill 拍板 20260821):{@code op_kind} 记录形态(怎么存,见 |
|||
* {@link com.crm.opportunity.domain.enums.OplogKind})+ {@code log_type} 业务类别 |
|||
* (决定前端日志页是否展示,见 {@link com.crm.opportunity.domain.enums.OplogLogType})。</p> |
|||
* |
|||
* <p>记/不记口径(P1-5 + grill):默认全记 + 短黑名单排除(技术字段 create_time/update_time/ |
|||
* version、无业务含义的系统自动刷新 last_valid_follow_time → 连 SYSTEM_INTERNAL 都不落)。 |
|||
* 状态/阶段流转照记,与专用快照表 {@code opportunity_status_history}/{@code opportunity_stage_history} |
|||
* 并存不重复(快照=流转事实,日志=统一审计视图)。**只增不删**(审计轨迹,无 delete_key); |
|||
* 普通用户只读,支持按时间范围查询 + 导出。写入点由统一审计切面驱动(接线归后续)。</p> |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@TableName("opportunity_oplog") |
|||
@Entity |
|||
@Table(name = "opportunity_oplog", |
|||
indexes = { |
|||
@Index(name = "idx_ool_opp_time", columnList = "opp_id,op_time"), |
|||
@Index(name = "idx_ool_log_type", columnList = "log_type") |
|||
}) |
|||
public class OpportunityOplog extends BaseEntity { |
|||
|
|||
@Comment("所属商机ID(opportunity.id)") |
|||
@Column(columnDefinition = "bigint not null comment '所属商机ID'") |
|||
private Long oppId; |
|||
|
|||
@Comment("记录形态:FIELD_CHANGE/ROW_ADD/ROW_DELETE(OplogKind,决定 old/new/biz_ref 语义)") |
|||
@Column(columnDefinition = "varchar(32) not null comment '记录形态'") |
|||
private String opKind; |
|||
|
|||
@Comment("业务类别:FIELD_CHANGE/STATUS_FLOW/STAGE_FLOW/ATTACHMENT/SCHEME_PUSH/ROW_CHANGE/SYSTEM_INTERNAL(OplogLogType,决定前端是否展示)") |
|||
@Column(columnDefinition = "varchar(32) not null comment '业务类别'") |
|||
private String logType; |
|||
|
|||
@Comment("变更实体对象(哪张表/哪个业务对象变更)") |
|||
@Column(columnDefinition = "varchar(64) comment '变更实体对象'") |
|||
private String entityName; |
|||
|
|||
@Comment("变更字段名(op_kind=FIELD_CHANGE 时有意义)") |
|||
@Column(columnDefinition = "varchar(64) comment '变更字段名'") |
|||
private String fieldName; |
|||
|
|||
@Comment("旧值(ROW_ADD 时空)") |
|||
@Column(columnDefinition = "varchar(1000) comment '旧值'") |
|||
private String oldValue; |
|||
|
|||
@Comment("新值(ROW_DELETE 时空)") |
|||
@Column(columnDefinition = "varchar(1000) comment '新值'") |
|||
private String newValue; |
|||
|
|||
@Comment("被操作行业务标识(ROW_ADD/ROW_DELETE 时的可读标识,如客户名,配合快照摘要)") |
|||
@Column(columnDefinition = "varchar(200) comment '被操作行业务标识'") |
|||
private String bizRef; |
|||
|
|||
@Comment("整行快照摘要 JSON(ROW_DELETE 保留被删行关键内容,供轨迹回读)") |
|||
@Column(columnDefinition = "text comment '整行快照摘要JSON'") |
|||
private String snapshotJson; |
|||
|
|||
@Comment("操作说明") |
|||
@Column(columnDefinition = "varchar(500) comment '操作说明'") |
|||
private String opDesc; |
|||
|
|||
@Comment("操作来源:SYSTEM 系统自动 / MANUAL 人工") |
|||
@Column(columnDefinition = "varchar(16) not null comment '操作来源:SYSTEM/MANUAL'") |
|||
private String opSource; |
|||
|
|||
@Comment("操作时间") |
|||
@Column(columnDefinition = "datetime not null comment '操作时间'") |
|||
private LocalDateTime opTime; |
|||
|
|||
@Comment("操作人ID(系统自动时可空)") |
|||
@Column(columnDefinition = "bigint comment '操作人ID'") |
|||
private Long opUserId; |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
package com.crm.opportunity.domain.entity; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import com.crm.base.domain.entity.BaseEntity; |
|||
import jakarta.persistence.Column; |
|||
import jakarta.persistence.Entity; |
|||
import jakarta.persistence.Index; |
|||
import jakarta.persistence.Table; |
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.hibernate.annotations.Comment; |
|||
|
|||
import java.time.LocalDateTime; |
|||
|
|||
/** |
|||
* 工作计划子表(票 09 §7.6 / 原型 A3-1-1-1-7,**结构 only**) |
|||
* |
|||
* <p>🅿️ 工作计划 = 独立业务模块 A1X,本期挂起(产品拍板 P1-4)。本商机模块**只保留子表结构与 |
|||
* 归属契约**(多条 / 归属商机 / 内容 / deadline / plan_status / finish_time);**精确态枚举、 |
|||
* 状态机、与「下次跟进时间生成工作计划」的联动、Tab 读中央模块** 全归 A1X 模块定,不在本期决策。</p> |
|||
* |
|||
* <p>{@code plan_status}:未完成 0 / 已完成 1;逾期 = 未完成且 {@code now > deadline},实时算不落库; |
|||
* 「剩余时间」由 deadline 实时算展示不落库。软删 + {@code delete_key}(对齐子表族通用约定)。</p> |
|||
*/ |
|||
@Data |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@TableName("opportunity_work_plan") |
|||
@Entity |
|||
@Table(name = "opportunity_work_plan", |
|||
indexes = { |
|||
@Index(name = "idx_owp_opp", columnList = "opp_id") |
|||
}) |
|||
public class OpportunityWorkPlan extends BaseEntity { |
|||
|
|||
@Comment("所属商机ID(opportunity.id)") |
|||
@Column(columnDefinition = "bigint not null comment '所属商机ID'") |
|||
private Long oppId; |
|||
|
|||
@Comment("计划内容") |
|||
@Column(columnDefinition = "varchar(1000) not null comment '计划内容'") |
|||
private String planContent; |
|||
|
|||
@Comment("截止时间(判临期/逾期,逾期实时算不落库)") |
|||
@Column(columnDefinition = "datetime comment '截止时间'") |
|||
private LocalDateTime deadline; |
|||
|
|||
@Comment("计划状态:0未完成 1已完成(逾期=未完成且now>deadline实时算;精确态枚举归A1X模块)") |
|||
@Column(columnDefinition = "tinyint not null default 0 comment '计划状态:0未完成 1已完成'") |
|||
private Integer planStatus; |
|||
|
|||
@Comment("标记完成时间") |
|||
@Column(columnDefinition = "datetime comment '标记完成时间'") |
|||
private LocalDateTime finishTime; |
|||
|
|||
@Comment("软删除复用键:存活时固定 0,逻辑删除时写为主键 id") |
|||
@Column(columnDefinition = "bigint not null default 0 comment '软删除复用键'") |
|||
private Long deleteKey; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
package com.crm.opportunity.domain.enums; |
|||
|
|||
import com.crm.base.domain.enums.HasValueEnum; |
|||
import lombok.Getter; |
|||
import lombok.RequiredArgsConstructor; |
|||
|
|||
/** |
|||
* 商机附件业务归属类型(票 09 §7.5 统一附件表 biz_type) |
|||
* |
|||
* <p>一张 {@code opportunity_attachment} 表承载所有子表族附件,靠 {@code biz_type}+{@code biz_id} |
|||
* 定位业务对象,不为每个子表各建附件表(票 07/08 的附件契约落点)。附件恒归属商机 |
|||
* (无孤立附件硬约束):{@code biz_type=OPP} 时 {@code biz_id} = opp_id 或空,其余类型 |
|||
* {@code biz_id} = 对应子表业务对象 id。</p> |
|||
*/ |
|||
@Getter |
|||
@RequiredArgsConstructor |
|||
public enum AttachmentBizType implements HasValueEnum<String> { |
|||
|
|||
/** 商机通用资料(不挂具体子表,biz_id=opp_id 或空) */ |
|||
OPP("OPP", "商机通用"), |
|||
|
|||
/** 跟进记录附件(biz_id=opportunity_follow.id,最多 10 个/条,PRD §7.5) */ |
|||
FOLLOW_UP("FOLLOW_UP", "跟进记录"), |
|||
|
|||
/** 现场勘察附件(biz_id=勘察记录 id,票 08) */ |
|||
SITE_SURVEY("SITE_SURVEY", "现场勘察"), |
|||
|
|||
/** 方案卡附件(biz_id=opportunity_scheme_card.id,票 07) */ |
|||
SCHEME_CARD("SCHEME_CARD", "方案卡"); |
|||
|
|||
private final String value; |
|||
private final String label; |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
package com.crm.opportunity.domain.enums; |
|||
|
|||
import com.crm.base.domain.enums.HasValueEnum; |
|||
import lombok.Getter; |
|||
import lombok.RequiredArgsConstructor; |
|||
|
|||
/** |
|||
* 操作日志记录形态(票 09 §7.7 双维度模型之一,「怎么存」) |
|||
* |
|||
* <p>与 {@link OplogLogType}(业务类别,「决定前端是否展示」)正交:{@code op_kind} 描述一条 |
|||
* 日志的物理形态——字段级旧新值 / 整行新增 / 整行删除,决定 {@code old_value}/{@code new_value}/ |
|||
* {@code biz_ref} 哪些有意义。商机操作日志走字段级审计模型,异于线索 {@code lead_history} |
|||
* 的动作枚举 + JSON 模型(PRD §7.7)。</p> |
|||
*/ |
|||
@Getter |
|||
@RequiredArgsConstructor |
|||
public enum OplogKind implements HasValueEnum<String> { |
|||
|
|||
/** 字段变更:有旧值与新值(field_name / old_value / new_value 均有意义) */ |
|||
FIELD_CHANGE("FIELD_CHANGE", "字段变更"), |
|||
|
|||
/** 整行新增:无旧值(如新增关联客户/团队成员,new_value 或快照摘要记内容) */ |
|||
ROW_ADD("ROW_ADD", "整行新增"), |
|||
|
|||
/** 整行删除:无新值,带 biz_ref 被删行业务标识 + 快照摘要 JSON(可读为「删除关联客户:上海XX公司」) */ |
|||
ROW_DELETE("ROW_DELETE", "整行删除"); |
|||
|
|||
private final String value; |
|||
private final String label; |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
package com.crm.opportunity.domain.enums; |
|||
|
|||
import com.crm.base.domain.enums.HasValueEnum; |
|||
import lombok.Getter; |
|||
import lombok.RequiredArgsConstructor; |
|||
|
|||
/** |
|||
* 操作日志业务类别(票 09 §7.7 双维度模型之一,「决定前端日志页是否展示」) |
|||
* |
|||
* <p>与 {@link OplogKind}(记录形态,「怎么存」)正交:{@code log_type} 对应原型 line 1745 |
|||
* 给出的行为类别,决定该条日志在前端日志页是否可见。除 {@link #SYSTEM_INTERNAL}(记库、 |
|||
* 前端过滤不显)外均对终端用户可见。枚举**不含浏览/关注**——那两类为纯行为流水,走物理硬删 + |
|||
* logback INFO,不入 oplog(PRD §7.8/7.9)。状态/阶段流转另有专用快照表 |
|||
* {@code opportunity_status_history}/{@code opportunity_stage_history},与本通用审计并存不重复。</p> |
|||
*/ |
|||
@Getter |
|||
@RequiredArgsConstructor |
|||
public enum OplogLogType implements HasValueEnum<String> { |
|||
|
|||
/** 字段变更(前端可见) */ |
|||
FIELD_CHANGE("FIELD_CHANGE", "字段变更"), |
|||
|
|||
/** 状态流转:opp_status 迁移(前端可见;快照另存 opportunity_status_history) */ |
|||
STATUS_FLOW("STATUS_FLOW", "状态流转"), |
|||
|
|||
/** 阶段流转:current_stage 切换(前端可见;快照另存 opportunity_stage_history) */ |
|||
STAGE_FLOW("STAGE_FLOW", "阶段流转"), |
|||
|
|||
/** 附件上传(前端可见) */ |
|||
ATTACHMENT("ATTACHMENT", "附件上传"), |
|||
|
|||
/** 方案推送(前端可见) */ |
|||
SCHEME_PUSH("SCHEME_PUSH", "方案推送"), |
|||
|
|||
/** 子表增删(前端可见;如关联客户/团队成员整行增删) */ |
|||
ROW_CHANGE("ROW_CHANGE", "子表增删"), |
|||
|
|||
/** 系统内部:有排查价值但不给终端用户看(记库、前端日志页过滤不显;如 executeRecycle 系统侧写) */ |
|||
SYSTEM_INTERNAL("SYSTEM_INTERNAL", "系统内部"); |
|||
|
|||
private final String value; |
|||
private final String label; |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package com.crm.opportunity.mapper; |
|||
|
|||
import com.crm.base.mapper.CrmBaseMapper; |
|||
import com.crm.opportunity.domain.entity.OpportunityAttachment; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
|
|||
@Mapper |
|||
public interface OpportunityAttachmentMapper extends CrmBaseMapper<OpportunityAttachment> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package com.crm.opportunity.mapper; |
|||
|
|||
import com.crm.base.mapper.CrmBaseMapper; |
|||
import com.crm.opportunity.domain.entity.OpportunityFollow; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
|
|||
@Mapper |
|||
public interface OpportunityFollowMapper extends CrmBaseMapper<OpportunityFollow> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package com.crm.opportunity.mapper; |
|||
|
|||
import com.crm.base.mapper.CrmBaseMapper; |
|||
import com.crm.opportunity.domain.entity.OpportunityOplog; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
|
|||
@Mapper |
|||
public interface OpportunityOplogMapper extends CrmBaseMapper<OpportunityOplog> { |
|||
} |
|||
@ -0,0 +1,9 @@ |
|||
package com.crm.opportunity.mapper; |
|||
|
|||
import com.crm.base.mapper.CrmBaseMapper; |
|||
import com.crm.opportunity.domain.entity.OpportunityWorkPlan; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
|
|||
@Mapper |
|||
public interface OpportunityWorkPlanMapper extends CrmBaseMapper<OpportunityWorkPlan> { |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
package com.crm.opportunity.domain.enums; |
|||
|
|||
import com.crm.base.utils.EnumUtils; |
|||
import org.junit.jupiter.api.DisplayName; |
|||
import org.junit.jupiter.api.Test; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
|
|||
/** |
|||
* 商机子表族枚举值域单测(票 09):附件业务类型 + 操作日志双维度分类。 |
|||
*/ |
|||
@DisplayName("商机子表族枚举(票 09)") |
|||
class OpportunityChildTablesEnumTest { |
|||
|
|||
@Test |
|||
@DisplayName("附件业务类型 4 值:OPP/FOLLOW_UP/SITE_SURVEY/SCHEME_CARD") |
|||
void attachmentBizType_domain() { |
|||
assertThat(AttachmentBizType.values()).hasSize(4); |
|||
assertThat(AttachmentBizType.FOLLOW_UP.getValue()).isEqualTo("FOLLOW_UP"); |
|||
assertThat(AttachmentBizType.SCHEME_CARD.getLabel()).isEqualTo("方案卡"); |
|||
assertThat(EnumUtils.getByValue(AttachmentBizType.class, "SITE_SURVEY")) |
|||
.isEqualTo(AttachmentBizType.SITE_SURVEY); |
|||
assertThat(EnumUtils.getByValue(AttachmentBizType.class, "NOPE")).isNull(); |
|||
} |
|||
|
|||
@Test |
|||
@DisplayName("操作日志记录形态 3 值:FIELD_CHANGE/ROW_ADD/ROW_DELETE") |
|||
void oplogKind_domain() { |
|||
assertThat(OplogKind.values()).hasSize(3); |
|||
assertThat(OplogKind.ROW_DELETE.getValue()).isEqualTo("ROW_DELETE"); |
|||
assertThat(EnumUtils.getByValue(OplogKind.class, "FIELD_CHANGE")) |
|||
.isEqualTo(OplogKind.FIELD_CHANGE); |
|||
} |
|||
|
|||
@Test |
|||
@DisplayName("操作日志业务类别 7 值含 SYSTEM_INTERNAL,不含浏览/关注") |
|||
void oplogLogType_domain() { |
|||
assertThat(OplogLogType.values()).hasSize(7); |
|||
assertThat(EnumUtils.getByValue(OplogLogType.class, "SYSTEM_INTERNAL")) |
|||
.isEqualTo(OplogLogType.SYSTEM_INTERNAL); |
|||
assertThat(EnumUtils.getByValue(OplogLogType.class, "VIEW")).isNull(); |
|||
assertThat(EnumUtils.getByValue(OplogLogType.class, "FOCUS")).isNull(); |
|||
} |
|||
} |
|||
@ -0,0 +1,91 @@ |
|||
package com.crm.opportunity.mapper; |
|||
|
|||
import com.baomidou.mybatisplus.core.MybatisConfiguration; |
|||
import com.baomidou.mybatisplus.core.metadata.TableInfo; |
|||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; |
|||
import com.crm.base.mapper.CrmBaseMapper; |
|||
import com.crm.opportunity.domain.entity.OpportunityAttachment; |
|||
import com.crm.opportunity.domain.entity.OpportunityFollow; |
|||
import com.crm.opportunity.domain.entity.OpportunityOplog; |
|||
import com.crm.opportunity.domain.entity.OpportunityWorkPlan; |
|||
import org.apache.ibatis.annotations.Mapper; |
|||
import org.apache.ibatis.builder.MapperBuilderAssistant; |
|||
import org.junit.jupiter.api.BeforeAll; |
|||
import org.junit.jupiter.api.DisplayName; |
|||
import org.junit.jupiter.api.Test; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
|
|||
/** |
|||
* 商机子表族 Mapper 骨架单测(票 09 §7.4–7.7:跟进 / 附件 / 工作计划 / 操作日志)。 |
|||
* <p>验证四张表 MyBatis-Plus 运行时表绑定(表名 + 驼峰列映射 + 逻辑删除位), |
|||
* 同构 {@link OpportunityMapperTest}。跟进/附件/工作计划走软删(deleted + delete_key), |
|||
* 操作日志只增不删(有 deleted 逻辑删列但无 delete_key)。</p> |
|||
*/ |
|||
@DisplayName("商机子表族 Mapper 骨架(票 09)") |
|||
class OpportunityChildTablesMapperTest { |
|||
|
|||
@BeforeAll |
|||
static void initTableInfo() { |
|||
MapperBuilderAssistant assistant = |
|||
new MapperBuilderAssistant(new MybatisConfiguration(), ""); |
|||
TableInfoHelper.initTableInfo(assistant, OpportunityFollow.class); |
|||
TableInfoHelper.initTableInfo(assistant, OpportunityAttachment.class); |
|||
TableInfoHelper.initTableInfo(assistant, OpportunityWorkPlan.class); |
|||
TableInfoHelper.initTableInfo(assistant, OpportunityOplog.class); |
|||
} |
|||
|
|||
@Test |
|||
@DisplayName("四 Mapper 均继承 CrmBaseMapper + 带 @Mapper 注解(可被扫描)") |
|||
void mappersWellFormed() { |
|||
for (Class<?> mapper : new Class<?>[]{ |
|||
OpportunityFollowMapper.class, OpportunityAttachmentMapper.class, |
|||
OpportunityWorkPlanMapper.class, OpportunityOplogMapper.class}) { |
|||
assertThat(CrmBaseMapper.class).isAssignableFrom(mapper); |
|||
assertThat(mapper.getAnnotation(Mapper.class)).isNotNull(); |
|||
} |
|||
} |
|||
|
|||
@Test |
|||
@DisplayName("跟进表:绑定 opportunity_follow,driver 列映射 follow_way↔followWay + 软删复用键") |
|||
void followTableInfo() { |
|||
TableInfo info = TableInfoHelper.getTableInfo(OpportunityFollow.class); |
|||
assertThat(info.getTableName()).isEqualTo("opportunity_follow"); |
|||
assertThat(info.getFieldList()).anyMatch(f -> |
|||
"follow_way".equals(f.getColumn()) && "followWay".equals(f.getProperty())); |
|||
assertThat(info.getLogicDeleteFieldInfo()).isNotNull(); |
|||
assertThat(info.getFieldList()).anyMatch(f -> "delete_key".equals(f.getColumn())); |
|||
} |
|||
|
|||
@Test |
|||
@DisplayName("附件表:绑定 opportunity_attachment,列映射 biz_type↔bizType / file_id↔fileId") |
|||
void attachmentTableInfo() { |
|||
TableInfo info = TableInfoHelper.getTableInfo(OpportunityAttachment.class); |
|||
assertThat(info.getTableName()).isEqualTo("opportunity_attachment"); |
|||
assertThat(info.getFieldList()).anyMatch(f -> |
|||
"biz_type".equals(f.getColumn()) && "bizType".equals(f.getProperty())); |
|||
assertThat(info.getFieldList()).anyMatch(f -> |
|||
"file_id".equals(f.getColumn()) && "fileId".equals(f.getProperty())); |
|||
} |
|||
|
|||
@Test |
|||
@DisplayName("工作计划表:绑定 opportunity_work_plan,列映射 plan_status↔planStatus") |
|||
void workPlanTableInfo() { |
|||
TableInfo info = TableInfoHelper.getTableInfo(OpportunityWorkPlan.class); |
|||
assertThat(info.getTableName()).isEqualTo("opportunity_work_plan"); |
|||
assertThat(info.getFieldList()).anyMatch(f -> |
|||
"plan_status".equals(f.getColumn()) && "planStatus".equals(f.getProperty())); |
|||
} |
|||
|
|||
@Test |
|||
@DisplayName("操作日志表:绑定 opportunity_oplog,双维度列 op_kind/log_type,只增不删无 delete_key") |
|||
void oplogTableInfo() { |
|||
TableInfo info = TableInfoHelper.getTableInfo(OpportunityOplog.class); |
|||
assertThat(info.getTableName()).isEqualTo("opportunity_oplog"); |
|||
assertThat(info.getFieldList()).anyMatch(f -> |
|||
"op_kind".equals(f.getColumn()) && "opKind".equals(f.getProperty())); |
|||
assertThat(info.getFieldList()).anyMatch(f -> |
|||
"log_type".equals(f.getColumn()) && "logType".equals(f.getProperty())); |
|||
assertThat(info.getFieldList()).noneMatch(f -> "delete_key".equals(f.getColumn())); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue