Parcourir la source

拜访管理代码

沐梦. il y a 1 mois
Parent
commit
1105bab351
30 fichiers modifiés avec 2882 ajouts et 0 suppressions
  1. 72 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CrmVisitPlanController.java
  2. 73 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CrmVisitRoutineController.java
  3. 72 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/FollowUpLogController.java
  4. 67 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/SalesAnnualFinalizationController.java
  5. 170 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/FollowUpLog.java
  6. 300 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/SalesAnnualFinalization.java
  7. 45 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/VisitPlan.java
  8. 147 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/VisitRoutine.java
  9. 28 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CrmVisitPlanBo.java
  10. 137 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/FollowUpLogBo.java
  11. 295 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/SalesAnnualFinalizationBo.java
  12. 58 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/VisitRoutineBo.java
  13. 43 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CrmVisitPlanVo.java
  14. 163 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/FollowUpLogVo.java
  15. 386 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/SalesAnnualFinalizationVo.java
  16. 75 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/VisitRoutineVo.java
  17. 13 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CrmVisitPlanMapper.java
  18. 12 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CrmVisitRoutineMapper.java
  19. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/FollowUpLogMapper.java
  20. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/SalesAnnualFinalizationMapper.java
  21. 28 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICrmVisitPlanService.java
  22. 27 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICrmVisitRoutineService.java
  23. 49 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IFollowUpLogService.java
  24. 9 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IOperationLogService.java
  25. 48 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ISalesAnnualFinalizationService.java
  26. 126 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CrmVisitPlanServiceImpl.java
  27. 104 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CrmVisitRoutineServiceImpl.java
  28. 147 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/FollowUpLogServiceImpl.java
  29. 8 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/OperationLogServiceImpl.java
  30. 150 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/SalesAnnualFinalizationServiceImpl.java

+ 72 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CrmVisitPlanController.java

@@ -0,0 +1,72 @@
+package org.dromara.customer.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.bo.CrmVisitPlanBo;
+import org.dromara.customer.domain.vo.CrmVisitPlanVo;
+import org.dromara.customer.service.ICrmVisitPlanService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * 拜访计划
+ * @author tys
+ */
+@SaIgnore
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/crm/visit/plan")
+public class CrmVisitPlanController extends BaseController {
+
+    private final ICrmVisitPlanService crmVisitPlanService;
+
+    /**
+     * 查询拜访计划分页列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<CrmVisitPlanVo> list(CrmVisitPlanBo bo, PageQuery pageQuery) {
+        return crmVisitPlanService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取拜访计划详细信息
+     */
+    @GetMapping("/{id}")
+    public R<CrmVisitPlanVo> getInfo(@PathVariable Long id) {
+        return R.ok(crmVisitPlanService.queryById(id));
+    }
+
+    /**
+     * 新增拜访计划
+     */
+    @PostMapping
+    public R<Void> add(@RequestBody CrmVisitPlanBo bo) {
+        return toAjax(crmVisitPlanService.insertByBo(bo));
+    }
+
+    /**
+     * 修改拜访计划
+     */
+    @PutMapping
+    public R<Void> edit(@RequestBody CrmVisitPlanBo bo) {
+        return toAjax(crmVisitPlanService.updateByBo(bo));
+    }
+
+    /**
+     * 删除拜访计划
+     */
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(crmVisitPlanService.deleteWithValidByIds(Arrays.asList(ids), true));
+    }
+
+}
+
+

+ 73 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CrmVisitRoutineController.java

@@ -0,0 +1,73 @@
+package org.dromara.customer.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.bo.VisitRoutineBo;
+import org.dromara.customer.domain.vo.VisitRoutineVo;
+import org.dromara.customer.service.ICrmVisitRoutineService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * 拜访日程Controller
+ *
+ * @author yoe
+ */
+@SaIgnore
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/crm/visit/routine")
+public class CrmVisitRoutineController extends BaseController {
+
+    private final ICrmVisitRoutineService crmVisitRoutineService;
+
+    /**
+     * 查询拜访日程分页列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<VisitRoutineVo> list(VisitRoutineBo bo, PageQuery pageQuery) {
+        return crmVisitRoutineService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取拜访日程详细信息
+     */
+    @GetMapping("/{id}")
+    public R<VisitRoutineVo> getInfo(@PathVariable Long id) {
+        return R.ok(crmVisitRoutineService.queryById(id));
+    }
+
+    /**
+     * 新增拜访日程
+     */
+    @PostMapping
+    public R<Void> add(@RequestBody VisitRoutineBo bo) {
+        return toAjax(crmVisitRoutineService.insertByBo(bo));
+    }
+
+    /**
+     * 修改拜访日程
+     */
+    @PutMapping
+    public R<Void> edit(@RequestBody VisitRoutineBo bo) {
+        return toAjax(crmVisitRoutineService.updateByBo(bo));
+    }
+
+    /**
+     * 删除拜访日程
+     */
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(crmVisitRoutineService.deleteWithValidByIds(Arrays.asList(ids), true));
+    }
+
+}
+
+

+ 72 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/FollowUpLogController.java

@@ -0,0 +1,72 @@
+package org.dromara.customer.controller;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.bo.FollowUpLogBo;
+import org.dromara.customer.domain.vo.FollowUpLogVo;
+import org.dromara.customer.service.IFollowUpLogService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * 跟进记录Controller
+ *
+ * @author yoe
+ * @date 2026-04-20
+ */
+@SaIgnore
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/crm/followup/log")
+public class FollowUpLogController extends BaseController {
+
+    private final IFollowUpLogService followUpLogService;
+
+    /**
+     * 查询跟进记录分页列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<FollowUpLogVo> list(FollowUpLogBo bo, PageQuery pageQuery) {
+        return followUpLogService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取跟进记录详细信息
+     */
+    @GetMapping("/{id}")
+    public R<FollowUpLogVo> getInfo(@PathVariable Long id) {
+        return R.ok(followUpLogService.queryById(id));
+    }
+
+    /**
+     * 新增跟进记录
+     */
+    @PostMapping
+    public R<Void> add(@RequestBody FollowUpLogBo bo) {
+        return toAjax(followUpLogService.insertByBo(bo));
+    }
+
+    /**
+     * 修改跟进记录
+     */
+    @PutMapping
+    public R<Void> edit(@RequestBody FollowUpLogBo bo) {
+        return toAjax(followUpLogService.updateByBo(bo));
+    }
+
+    /**
+     * 删除跟进记录
+     */
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(followUpLogService.deleteWithValidByIds(Arrays.asList(ids), true));
+    }
+
+}

+ 67 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/SalesAnnualFinalizationController.java

@@ -0,0 +1,67 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.bo.SalesAnnualFinalizationBo;
+import org.dromara.customer.domain.vo.SalesAnnualFinalizationVo;
+import org.dromara.customer.service.ISalesAnnualFinalizationService;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * 年度入围项目
+ *
+ * @author Antigravity
+ * @date 2026-04-18
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/annualFinalization")
+public class SalesAnnualFinalizationController extends BaseController {
+
+    private final ISalesAnnualFinalizationService salesAnnualFinalizationService;
+
+    /**
+     * 查询年度入围项目列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<SalesAnnualFinalizationVo> list(SalesAnnualFinalizationBo bo, PageQuery pageQuery) {
+        return salesAnnualFinalizationService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取年度入围项目详细信息
+     */
+    @GetMapping("/{id}")
+    public R<SalesAnnualFinalizationVo> getInfo(@PathVariable Long id) {
+        return R.ok(salesAnnualFinalizationService.queryById(id));
+    }
+
+    /**
+     * 新增年度入围项目
+     */
+    @PostMapping
+    public R<Void> add(@RequestBody SalesAnnualFinalizationBo bo) {
+        return toAjax(salesAnnualFinalizationService.insertByBo(bo));
+    }
+
+    /**
+     * 修改年度入围项目
+     */
+    @PutMapping
+    public R<Void> edit(@RequestBody SalesAnnualFinalizationBo bo) {
+        return toAjax(salesAnnualFinalizationService.updateByBo(bo));
+    }
+
+    /**
+     * 删除年度入围项目
+     */
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(salesAnnualFinalizationService.deleteWithValidByIds(Arrays.asList(ids), true));
+    }
+}

+ 170 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/FollowUpLog.java

@@ -0,0 +1,170 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 跟进记录对象
+ *
+ * @author tys
+ * @date 2026-04-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("followuplog")
+public class FollowUpLog extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键,自增ID
+     */
+    @TableId(value = "Id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 记录编号
+     */
+    @TableField("RecordsNo")
+    private String recordsNo;
+
+    /**
+     * 数据类型
+     */
+    @TableField("DataType")
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    @TableField("ObjectNo")
+    private String objectNo;
+
+    /**
+     * 客户编号
+     */
+    @TableField("CustomerNo")
+    private String customerNo;
+
+    /**
+     * 客户名称
+     */
+    @TableField("CustomerName")
+    private String customerName;
+
+    /**
+     * 目标对象编号
+     */
+    @TableField("GoalObjectNo")
+    private String goalObjectNo;
+
+    /**
+     * 目标对象
+     */
+    @TableField("GoalObject")
+    private String goalObject;
+
+    /**
+     * 部门
+     */
+    @TableField("Department")
+    private String department;
+
+    /**
+     * 行业
+     */
+    @TableField("Profession")
+    private String profession;
+
+    /**
+     * 业务员
+     */
+    @TableField("Salesman")
+    private String salesman;
+
+    /**
+     * 拜访人编号
+     */
+    @TableField("VisitorNo")
+    private Long visitorNo;
+
+    /**
+     * 拜访人
+     */
+    @TableField("Visitor")
+    private String visitor;
+
+    /**
+     * 跟进人编号
+     */
+    @TableField("FollowPeopleNo")
+    private String followPeopleNo;
+
+    /**
+     * 跟进人姓名
+     */
+    @TableField("FollowPeopleName")
+    private String followPeopleName;
+
+    /**
+     * 拜访类型代码
+     */
+    @TableField("CallTypeCode")
+    private String callTypeCode;
+
+    /**
+     * 拜访日期
+     */
+    @TableField("CallDate")
+    private Date callDate;
+
+    /**
+     * 下次拜访日期
+     */
+    @TableField("NextCallDate")
+    private Date nextCallDate;
+
+    /**
+     * 拜访目的
+     */
+    @TableField("CallAim")
+    private String callAim;
+
+    /**
+     * 跟进情况
+     */
+    @TableField("FollowUpCondition")
+    private String followUpCondition;
+
+    /**
+     * 记录图片
+     */
+    @TableField("RecordPicture")
+    private String recordPicture;
+
+    /**
+     * 文件编号
+     */
+    @TableField("FileNo")
+    private String fileNo;
+
+    /**
+     * 创建部门
+     */
+    @TableField(exist = false)
+    private Long createDept;
+
+    /**
+     * 是否删除(0-否,1-是)
+     */
+    @TableLogic(value = "0", delval = "1")
+    @TableField("IsDelete")
+    private Integer isDelete = 0;
+
+}

+ 300 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/SalesAnnualFinalization.java

@@ -0,0 +1,300 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 年度入围项目对象 salesannualfinalization
+ *
+ * @author tys
+ * @date 2026-04-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("salesannualfinalization")
+public class SalesAnnualFinalization extends TenantEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "Id")
+    private Long id;
+
+    /**
+     * 项目编号
+     */
+    @TableField(value = "ProjectNo")
+    private String projectNo;
+
+    /**
+     * 公司编号
+     */
+    @TableField(value = "CompanyNo")
+    private String companyNo;
+
+    /**
+     * 项目名称
+     */
+    @TableField(value = "ProjectName")
+    private String projectName;
+
+    /**
+     * 项目状态
+     */
+    @TableField(value = "ProjectStatus")
+    private Integer projectStatus;
+
+    /**
+     * 业务类型
+     */
+    @TableField(value = "BusinessType")
+    private Integer businessType;
+
+    /**
+     * 项目级别
+     */
+    @TableField(value = "ProjectLevel")
+    private Integer projectLevel;
+
+    /**
+     * 客户编号
+     */
+    @TableField(value = "CustomNo")
+    private String customNo;
+
+    /**
+     * 客户名称
+     */
+    @TableField(value = "CustomName")
+    private String customName;
+
+    /**
+     * 行业
+     */
+    @TableField(value = "Profession")
+    private Integer profession;
+
+    /**
+     * 部门编号
+     */
+    @TableField(value = "DeptNo")
+    private String deptNo;
+
+    /**
+     * 业务员
+     */
+    @TableField(value = "Salesman")
+    private String salesman;
+
+    /**
+     * 客服
+     */
+    @TableField(value = "CustomService")
+    private String customService;
+
+    /**
+     * 签约日期
+     */
+    @TableField(value = "SignDate")
+    private Date signDate;
+
+    /**
+     * 项目金额
+     */
+    @TableField(value = "Amount")
+    private BigDecimal amount;
+
+    /**
+     * 入围费
+     */
+    @TableField(value = "EntryFee")
+    private BigDecimal entryFee;
+
+    /**
+     * 投标保证金
+     */
+    @TableField(value = "BidBond")
+    private BigDecimal bidBond;
+
+    /**
+     * 保证金状态
+     */
+    @TableField(value = "BidBondStatus")
+    private Integer bidBondStatus;
+
+    /**
+     * 招标日期
+     */
+    @TableField(value = "TenderDate")
+    private Date tenderDate;
+
+    /**
+     * 中标率
+     */
+    @TableField(value = "WinningRate")
+    private Double winningRate;
+
+    /**
+     * 报名截止日期
+     */
+    @TableField(value = "SignUpDeadline")
+    private Date signUpDeadline;
+
+    /**
+     * 投标截止日期
+     */
+    @TableField(value = "TenderDeadline")
+    private Date tenderDeadline;
+
+    /**
+     * 标准周期
+     */
+    @TableField(value = "StandardPeriod")
+    private Double standardPeriod;
+
+    /**
+     * 入围类型
+     */
+    @TableField(value = "ShortlistedType")
+    private Integer shortlistedType;
+
+    /**
+     * 招标类型
+     */
+    @TableField(value = "BiddingType")
+    private Integer biddingType;
+
+    /**
+     * 招标代理机构
+     */
+    @TableField(value = "BiddingAgency")
+    private String biddingAgency;
+
+    /**
+     * 代理联系人
+     */
+    @TableField(value = "AgencyContact")
+    private String agencyContact;
+
+    /**
+     * 项目描述
+     */
+    @TableField(value = "ProjectDesc")
+    private String projectDesc;
+
+    /**
+     * 负责人ID
+     */
+    @TableField(value = "Leader")
+    private Long leader;
+
+    /**
+     * 负责人名称
+     */
+    @TableField(value = "LeaderName")
+    private String leaderName;
+
+    /**
+     * 文件编号
+     */
+    @TableField(value = "FileNo")
+    private String fileNo;
+
+    /**
+     * 产品支持
+     */
+    @TableField(value = "ProductSupport")
+    private String productSupport;
+
+    /**
+     * 是否删除 0=否 1=是
+     */
+    @TableLogic
+    @TableField(value = "IsDelete")
+    private Integer isDelete;
+
+
+    /**
+     * 项目进度
+     */
+    @TableField(value = "ProjectSchedule")
+    private String projectSchedule;
+
+    /**
+     * 投标周期类型
+     */
+    @TableField(value = "BidPeriodType")
+    private Integer bidPeriodType;
+
+    /**
+     * 下次招标时间
+     */
+    @TableField(value = "NextBiddingTime")
+    private Date nextBiddingTime;
+
+    /**
+     * 入围类型 (FinalizationType)
+     */
+    @TableField(value = "FinalizationType")
+    private Integer finalizationType;
+
+    /**
+     * 提前通知天数
+     */
+    @TableField(value = "NoticeAdvanceDays")
+    private Integer noticeAdvanceDays;
+
+    /**
+     * 是否提前通知
+     */
+    @TableField(value = "IsNoticeAdvance")
+    private Integer isNoticeAdvance;
+
+    /**
+     * 项目等级
+     */
+    @TableField(value = "ProjectGrade")
+    private String projectGrade;
+
+    /**
+     * 条件
+     */
+    @TableField(value = "`Condition`")
+    private String condition;
+
+    /**
+     * 招标链接
+     */
+    @TableField(value = "BiddingLink")
+    private String biddingLink;
+
+    /**
+     * 平台名称
+     */
+    @TableField(value = "PlatformName")
+    private String platformName;
+
+    /**
+     * 平台链接
+     */
+    @TableField(value = "PlatformLink")
+    private String platformLink;
+
+    /**
+     * 服务时间
+     */
+    @TableField(value = "ServiceTime")
+    private String serviceTime;
+
+}
+

+ 45 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/VisitPlan.java

@@ -0,0 +1,45 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 拜访计划
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("callonplan")
+public class VisitPlan extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "Id")
+    private Long id;
+
+    @TableField("PlanNo")
+    private String planNo;
+
+    @TableField("CallPeopleNo")
+    private Long visitorId;
+
+    @TableField("CallPeopleName")
+    private String visitorName;
+
+    @TableField("StartDate")
+    private Date startTime;
+
+    @TableField("EndDate")
+    private Date endTime;
+
+    @TableField("Status")
+    private String status;
+
+}

+ 147 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/VisitRoutine.java

@@ -0,0 +1,147 @@
+package org.dromara.customer.domain;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * 拜访日程对象
+ *
+ * @author tys
+ * @date 2026-04-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("callonschedule")
+public class VisitRoutine extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID,自增
+     */
+    @TableId(value = "Id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 计划编号,关联拜访计划表
+     */
+    @TableField("PlanNo")
+    private String planNo;
+
+    /**
+     * 日程编号,唯一标识
+     */
+    @TableField("ScheduleNo")
+    private String scheduleNo;
+
+    /**
+     * 关联类型
+     */
+    @TableField("RelevanceType")
+    private Integer relevanceType;
+
+    /**
+     * 对象编号
+     */
+    @TableField("ObjectNo")
+    private String objectNo;
+
+    /**
+     * 对象名称
+     */
+    @TableField("ObjectName")
+    private String objectName;
+
+    /**
+     * 客户编号
+     */
+    @TableField("CustomerNo")
+    private String customerNo;
+
+    /**
+     * 客户名称
+     */
+    @TableField("CustomerName")
+    private String customerName;
+
+    /**
+     * 拜访人员编号
+     */
+    @TableField("CallPeopleNo")
+    private Long callPeopleNo;
+
+    /**
+     * 拜访人员姓名
+     */
+    @TableField("CallPeopleName")
+    private String callPeopleName;
+
+    /**
+     * 拜访日期
+     */
+    @TableField("CallDate")
+    private Date callDate;
+
+    /**
+     * 跟进人员编号
+     */
+    @TableField("FollowPeopleNo")
+    private String followPeopleNo;
+
+    /**
+     * 跟进人员姓名
+     */
+    @TableField("FollowPeopleName")
+    private String followPeopleName;
+
+    /**
+     * 紧要级别
+     */
+    @ExcelProperty(value = "紧要级别", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "1=一般,2=重要,3=紧急")
+    @TableField("ImportantLevel")
+    private Integer importantLevel;
+
+    /**
+     * 拜访目的
+     */
+    @TableField("PurposeVisit")
+    private String purposeVisit;
+
+    /**
+     * 日程状态
+     */
+    @ExcelProperty(value = "日程状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=未执行,1=已执行,2=放弃执行")
+    @TableField("ScheduleStatus")
+    private Integer scheduleStatus;
+
+    /**
+     * 实际执行时间
+     */
+    @TableField("ExecuteTime")
+    private Date executeTime;
+
+    /**
+     * 未执行原因
+     */
+    @TableField("UnExecuteReason")
+    private String unExecuteReason;
+
+    /**
+     * 是否删除 (0:否, 1:是)
+     */
+    @TableLogic(value = "0", delval = "1")
+    @TableField("IsDelete")
+    private Integer isDelete;
+
+}

+ 28 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CrmVisitPlanBo.java

@@ -0,0 +1,28 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.customer.domain.VisitPlan;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = VisitPlan.class)
+public class CrmVisitPlanBo extends BaseEntity {
+
+    private Long id;
+    private String planNo;
+    private Long visitorId;
+    private String visitorName;
+    private Date startTime;
+    private Date endTime;
+    private String status;
+
+    /** 子表日程明细 */
+    private List<VisitRoutineBo> detailList;
+
+}

+ 137 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/FollowUpLogBo.java

@@ -0,0 +1,137 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.customer.domain.FollowUpLog;
+
+import java.util.Date;
+
+/**
+ * 跟进记录业务对象 followuplog
+ *
+ * @author yoe
+ * @date 2026-04-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = FollowUpLog.class, reverseConvertGenerate = true)
+public class FollowUpLogBo extends BaseEntity {
+
+    /**
+     * 主键,自增ID
+     */
+    private Long id;
+
+    /**
+     * 记录编号
+     */
+    private String recordsNo;
+
+    /**
+     * 数据类型
+     */
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    private String objectNo;
+
+    /**
+     * 客户编号
+     */
+    private String customerNo;
+
+    /**
+     * 客户名称
+     */
+    private String customerName;
+
+    /**
+     * 目标对象编号
+     */
+    private String goalObjectNo;
+
+    /**
+     * 目标对象
+     */
+    private String goalObject;
+
+    /**
+     * 部门
+     */
+    private String department;
+
+    /**
+     * 行业
+     */
+    private String profession;
+
+    /**
+     * 业务员
+     */
+    private String salesman;
+
+    /**
+     * 拜访人编号
+     */
+    private Long visitorNo;
+
+    /**
+     * 拜访人
+     */
+    private String visitor;
+
+    /**
+     * 跟进人编号
+     */
+    private String followPeopleNo;
+
+    /**
+     * 跟进人姓名
+     */
+    private String followPeopleName;
+
+    /**
+     * 拜访类型代码
+     */
+    private String callTypeCode;
+
+    /**
+     * 拜访日期
+     */
+    private Date callDate;
+
+    /**
+     * 下次拜访日期
+     */
+    private Date nextCallDate;
+
+    /**
+     * 拜访目的
+     */
+    private String callAim;
+
+    /**
+     * 跟进情况
+     */
+    private String followUpCondition;
+
+    /**
+     * 记录图片
+     */
+    private String recordPicture;
+
+    /**
+     * 文件编号
+     */
+    private String fileNo;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+
+}

+ 295 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/SalesAnnualFinalizationBo.java

@@ -0,0 +1,295 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.customer.domain.SalesAnnualFinalization;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 年度入围项目业务对象 salesannualfinalization
+ *
+ * @author tys
+ * @date 2026-04-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = SalesAnnualFinalization.class, reverseConvertGenerate = true)
+@Schema(description = "年度入围项目业务对象")
+public class SalesAnnualFinalizationBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    @Schema(description = "主键ID")
+    private Long id;
+
+    /**
+     * 项目编号
+     */
+    @Schema(description = "项目编号")
+    private String projectNo;
+
+    /**
+     * 公司编号
+     */
+    @Schema(description = "公司编号")
+    private String companyNo;
+
+    /**
+     * 项目名称
+     */
+    @Schema(description = "项目名称")
+    private String projectName;
+
+    /**
+     * 项目状态
+     */
+    @Schema(description = "项目状态")
+    private Integer projectStatus;
+
+    /**
+     * 业务类型
+     */
+    @Schema(description = "业务类型")
+    private Integer businessType;
+
+    /**
+     * 项目级别
+     */
+    @Schema(description = "项目级别")
+    private Integer projectLevel;
+
+    /**
+     * 客户编号
+     */
+    @Schema(description = "客户编号")
+    private String customNo;
+
+    /**
+     * 客户名称
+     */
+    @Schema(description = "客户名称")
+    private String customName;
+
+    /**
+     * 行业
+     */
+    @Schema(description = "行业")
+    private Integer profession;
+
+    /**
+     * 部门编号
+     */
+    @Schema(description = "部门编号")
+    private String deptNo;
+
+    /**
+     * 业务员
+     */
+    @Schema(description = "业务员")
+    private String salesman;
+
+    /**
+     * 客服
+     */
+    @Schema(description = "客服")
+    private String customService;
+
+    /**
+     * 签约日期
+     */
+    @Schema(description = "签约日期")
+    private Date signDate;
+
+    /**
+     * 项目金额
+     */
+    @Schema(description = "项目金额")
+    private BigDecimal amount;
+
+    /**
+     * 入围费
+     */
+    @Schema(description = "入围费")
+    private BigDecimal entryFee;
+
+    /**
+     * 投标保证金
+     */
+    @Schema(description = "投标保证金")
+    private BigDecimal bidBond;
+
+    /**
+     * 保证金状态
+     */
+    @Schema(description = "保证金状态")
+    private Integer bidBondStatus;
+
+    /**
+     * 招标日期
+     */
+    @Schema(description = "招标日期")
+    private Date tenderDate;
+
+    /**
+     * 中标率
+     */
+    @Schema(description = "中标率")
+    private Double winningRate;
+
+    /**
+     * 报名截止日期
+     */
+    @Schema(description = "报名截止日期")
+    private Date signUpDeadline;
+
+    /**
+     * 投标截止日期
+     */
+    @Schema(description = "投标截止日期")
+    private Date tenderDeadline;
+
+    /**
+     * 标准周期
+     */
+    @Schema(description = "标准周期")
+    private Double standardPeriod;
+
+    /**
+     * 入围类型
+     */
+    @Schema(description = "入围类型")
+    private Integer shortlistedType;
+
+    /**
+     * 招标类型
+     */
+    @Schema(description = "招标类型")
+    private Integer biddingType;
+
+    /**
+     * 招标代理机构
+     */
+    @Schema(description = "招标代理机构")
+    private String biddingAgency;
+
+    /**
+     * 代理联系人
+     */
+    @Schema(description = "代理联系人")
+    private String agencyContact;
+
+    /**
+     * 项目描述
+     */
+    @Schema(description = "项目描述")
+    private String projectDesc;
+
+    /**
+     * 负责人ID
+     */
+    @Schema(description = "负责人ID")
+    private Long leader;
+
+    /**
+     * 负责人名称
+     */
+    @Schema(description = "负责人名称")
+    private String leaderName;
+
+    /**
+     * 文件编号
+     */
+    @Schema(description = "文件编号")
+    private String fileNo;
+
+    /**
+     * 产品支持
+     */
+    @Schema(description = "产品支持")
+    private String productSupport;
+
+    /**
+     * 创建组织ID
+     */
+    @Schema(description = "创建组织ID")
+    private Long createOrgId;
+
+    /**
+     * 项目进度
+     */
+    @Schema(description = "项目进度")
+    private String projectSchedule;
+
+    /**
+     * 投标周期类型
+     */
+    @Schema(description = "投标周期类型")
+    private Integer bidPeriodType;
+
+    /**
+     * 下次招标时间
+     */
+    @Schema(description = "下次招标时间")
+    private Date nextBiddingTime;
+
+    /**
+     * 入围类型 (FinalizationType)
+     */
+    @Schema(description = "入围类型")
+    private Integer finalizationType;
+
+    /**
+     * 提前通知天数
+     */
+    @Schema(description = "提前通知天数")
+    private Integer noticeAdvanceDays;
+
+    /**
+     * 是否提前通知
+     */
+    @Schema(description = "是否提前通知")
+    private Integer isNoticeAdvance;
+
+    /**
+     * 项目等级
+     */
+    @Schema(description = "项目等级")
+    private String projectGrade;
+
+    /**
+     * 条件
+     */
+    @Schema(description = "条件")
+    private String condition;
+
+    /**
+     * 招标链接
+     */
+    @Schema(description = "招标链接")
+    private String biddingLink;
+
+    /**
+     * 平台名称
+     */
+    @Schema(description = "平台名称")
+    private String platformName;
+
+    /**
+     * 平台链接
+     */
+    @Schema(description = "平台链接")
+    private String platformLink;
+
+    /**
+     * 服务时间
+     */
+    @Schema(description = "服务时间")
+    private String serviceTime;
+
+}

+ 58 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/VisitRoutineBo.java

@@ -0,0 +1,58 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.customer.domain.VisitRoutine;
+
+import java.util.Date;
+
+/**
+ * 拜访日程业务对象 callonschedule
+ *
+ * @author tys
+ * @date 2026-04-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = VisitRoutine.class, reverseConvertGenerate = true)
+public class VisitRoutineBo extends BaseEntity {
+
+    private Long id;
+
+    private String planNo;
+
+    private String scheduleNo;
+
+    private Integer relevanceType;
+
+    private String objectNo;
+
+    private String objectName;
+
+    private String customerNo;
+
+    private String customerName;
+
+    private Long callPeopleNo;
+
+    private String callPeopleName;
+
+    private Date callDate;
+
+    private String followPeopleNo;
+
+    private String followPeopleName;
+
+    private Integer importantLevel;
+
+    private String purposeVisit;
+
+    private Integer scheduleStatus;
+
+    private Date executeTime;
+
+    private String unExecuteReason;
+
+}

+ 43 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CrmVisitPlanVo.java

@@ -0,0 +1,43 @@
+package org.dromara.customer.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.customer.domain.VisitPlan;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@AutoMapper(target = VisitPlan.class)
+public class CrmVisitPlanVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String planNo;
+    private Long visitorId;
+    private String visitorName;
+    private Date startTime;
+    private Date endTime;
+    private String status;
+    private Date createTime;
+
+    private Long createBy;
+
+    @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
+    private String createByName;
+
+    private Long updateBy;
+
+    @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy")
+    private String updateByName;
+
+    private Date updateTime;
+
+    /** 子表日程明细 */
+    private List<VisitRoutineVo> detailList;
+
+}

+ 163 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/FollowUpLogVo.java

@@ -0,0 +1,163 @@
+package org.dromara.customer.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.customer.domain.FollowUpLog;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 跟进记录视图对象 followuplog
+ *
+ * @author yoe
+ * @date 2026-04-20
+ */
+@Data
+@AutoMapper(target = FollowUpLog.class)
+public class FollowUpLogVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键,自增ID
+     */
+    private Long id;
+
+    /**
+     * 记录编号
+     */
+    private String recordsNo;
+
+    /**
+     * 数据类型
+     */
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    private String objectNo;
+
+    /**
+     * 客户编号
+     */
+    private String customerNo;
+
+    /**
+     * 客户名称
+     */
+    private String customerName;
+
+    /**
+     * 目标对象编号
+     */
+    private String goalObjectNo;
+
+    /**
+     * 目标对象
+     */
+    private String goalObject;
+
+    /**
+     * 部门
+     */
+    private String department;
+
+    /**
+     * 行业
+     */
+    private String profession;
+
+    /**
+     * 业务员
+     */
+    private String salesman;
+
+    /**
+     * 拜访人编号
+     */
+    private Long visitorNo;
+
+    /**
+     * 拜访人
+     */
+    private String visitor;
+
+    /**
+     * 跟进人编号
+     */
+    private String followPeopleNo;
+
+    /**
+     * 跟进人姓名
+     */
+    private String followPeopleName;
+
+    /**
+     * 拜访类型代码
+     */
+    private String callTypeCode;
+
+    /**
+     * 拜访日期
+     */
+    private Date callDate;
+
+    /**
+     * 下次拜访日期
+     */
+    private Date nextCallDate;
+
+    /**
+     * 拜访目的
+     */
+    private String callAim;
+
+    /**
+     * 跟进情况
+     */
+    private String followUpCondition;
+
+    /**
+     * 记录图片
+     */
+    private String recordPicture;
+
+    /**
+     * 文件编号
+     */
+    private String fileNo;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+
+    /**
+     * 创建人
+     */
+    @Translation(type = TransConstant.USER_ID_TO_NAME)
+    private Long createBy;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 更新人
+     */
+    @Translation(type = TransConstant.USER_ID_TO_NAME)
+    private Long updateBy;
+
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+
+}

+ 386 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/SalesAnnualFinalizationVo.java

@@ -0,0 +1,386 @@
+package org.dromara.customer.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.customer.domain.SalesAnnualFinalization;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 年度入围项目视图对象 salesannualfinalization
+ *
+ * @author tys
+ * @date 2026-04-18
+ */
+@Data
+@AutoMapper(target = SalesAnnualFinalization.class)
+@ExcelIgnoreUnannotated
+@Schema(description = "年度入围项目视图对象")
+public class SalesAnnualFinalizationVo {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    @Schema(description = "主键ID")
+    private Long id;
+
+    /**
+     * 项目编号
+     */
+    @ExcelProperty(value = "项目编号")
+    @Schema(description = "项目编号")
+    private String projectNo;
+
+    /**
+     * 公司编号
+     */
+    @ExcelProperty(value = "公司编号")
+    @Schema(description = "公司编号")
+    private String companyNo;
+
+    /**
+     * 项目名称
+     */
+    @ExcelProperty(value = "项目名称")
+    @Schema(description = "项目名称")
+    private String projectName;
+
+    /**
+     * 项目状态
+     */
+    @ExcelProperty(value = "项目状态")
+    @Schema(description = "项目状态")
+    private Integer projectStatus;
+
+    /**
+     * 业务类型
+     */
+    @ExcelProperty(value = "业务类型")
+    @Schema(description = "业务类型")
+    private Integer businessType;
+
+    /**
+     * 项目级别
+     */
+    @ExcelProperty(value = "项目级别")
+    @Schema(description = "项目级别")
+    private Integer projectLevel;
+
+    /**
+     * 客户编号
+     */
+    @ExcelProperty(value = "客户编号")
+    @Schema(description = "客户编号")
+    private String customNo;
+
+    /**
+     * 客户名称
+     */
+    @ExcelProperty(value = "客户名称")
+    @Schema(description = "客户名称")
+    private String customName;
+
+    /**
+     * 行业
+     */
+    @ExcelProperty(value = "行业")
+    @Schema(description = "行业")
+    private Integer profession;
+
+    /**
+     * 部门编号
+     */
+    @ExcelProperty(value = "部门编号")
+    @Schema(description = "部门编号")
+    private String deptNo;
+
+    /**
+     * 业务员
+     */
+    @ExcelProperty(value = "业务员")
+    @Schema(description = "业务员")
+    private String salesman;
+
+    /**
+     * 客服
+     */
+    @ExcelProperty(value = "客服")
+    @Schema(description = "客服")
+    private String customService;
+
+    /**
+     * 签约日期
+     */
+    @ExcelProperty(value = "签约日期")
+    @Schema(description = "签约日期")
+    private Date signDate;
+
+    /**
+     * 项目金额
+     */
+    @ExcelProperty(value = "项目金额")
+    @Schema(description = "项目金额")
+    private BigDecimal amount;
+
+    /**
+     * 入围费
+     */
+    @ExcelProperty(value = "入围费")
+    @Schema(description = "入围费")
+    private BigDecimal entryFee;
+
+    /**
+     * 投标保证金
+     */
+    @ExcelProperty(value = "投标保证金")
+    @Schema(description = "投标保证金")
+    private BigDecimal bidBond;
+
+    /**
+     * 保证金状态
+     */
+    @ExcelProperty(value = "保证金状态")
+    @Schema(description = "保证金状态")
+    private Integer bidBondStatus;
+
+    /**
+     * 招标日期
+     */
+    @ExcelProperty(value = "招标日期")
+    @Schema(description = "招标日期")
+    private Date tenderDate;
+
+    /**
+     * 中标率
+     */
+    @ExcelProperty(value = "中标率")
+    @Schema(description = "中标率")
+    private Double winningRate;
+
+    /**
+     * 报名截止日期
+     */
+    @ExcelProperty(value = "报名截止日期")
+    @Schema(description = "报名截止日期")
+    private Date signUpDeadline;
+
+    /**
+     * 投标截止日期
+     */
+    @ExcelProperty(value = "投标截止日期")
+    @Schema(description = "投标截止日期")
+    private Date tenderDeadline;
+
+    /**
+     * 标准周期
+     */
+    @ExcelProperty(value = "标准周期")
+    @Schema(description = "标准周期")
+    private Double standardPeriod;
+
+    /**
+     * 入围类型
+     */
+    @ExcelProperty(value = "入围类型")
+    @Schema(description = "入围类型")
+    private Integer shortlistedType;
+
+    /**
+     * 招标类型
+     */
+    @ExcelProperty(value = "招标类型")
+    @Schema(description = "招标类型")
+    private Integer biddingType;
+
+    /**
+     * 招标代理机构
+     */
+    @ExcelProperty(value = "招标代理机构")
+    @Schema(description = "招标代理机构")
+    private String biddingAgency;
+
+    /**
+     * 代理联系人
+     */
+    @ExcelProperty(value = "代理联系人")
+    @Schema(description = "代理联系人")
+    private String agencyContact;
+
+    /**
+     * 项目描述
+     */
+    @ExcelProperty(value = "项目描述")
+    @Schema(description = "项目描述")
+    private String projectDesc;
+
+    /**
+     * 负责人ID
+     */
+    @ExcelProperty(value = "负责人ID")
+    @Schema(description = "负责人ID")
+    private Long leader;
+
+    /**
+     * 负责人名称
+     */
+    @ExcelProperty(value = "负责人名称")
+    @Schema(description = "负责人名称")
+    private String leaderName;
+
+    /**
+     * 文件编号
+     */
+    @ExcelProperty(value = "文件编号")
+    @Schema(description = "文件编号")
+    private String fileNo;
+
+    /**
+     * 产品支持
+     */
+    @ExcelProperty(value = "产品支持")
+    @Schema(description = "产品支持")
+    private String productSupport;
+
+    /**
+     * 创建组织ID
+     */
+    @ExcelProperty(value = "创建组织ID")
+    @Schema(description = "创建组织ID")
+    private Long createOrgId;
+
+    /**
+     * 创建者
+     */
+    @ExcelProperty(value = "创建者")
+    @Schema(description = "创建者")
+    private Long createBy;
+
+    /**
+     * 创建者姓名
+     */
+    @Schema(description = "创建者姓名")
+    @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
+    private String createByName;
+
+    /**
+     * 创建时间
+     */
+    @ExcelProperty(value = "创建时间")
+    @Schema(description = "创建时间")
+    private Date createTime;
+
+    /**
+     * 更新者
+     */
+    @ExcelProperty(value = "更新者")
+    @Schema(description = "更新者")
+    private Long updateBy;
+
+    /**
+     * 更新者姓名
+     */
+    @Schema(description = "更新者姓名")
+    @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy")
+    private String updateByName;
+
+    /**
+     * 更新时间
+     */
+    @ExcelProperty(value = "更新时间")
+    @Schema(description = "更新时间")
+    private Date updateTime;
+
+    /**
+     * 项目进度
+     */
+    @ExcelProperty(value = "项目进度")
+    @Schema(description = "项目进度")
+    private String projectSchedule;
+
+    /**
+     * 投标周期类型
+     */
+    @ExcelProperty(value = "投标周期类型")
+    @Schema(description = "投标周期类型")
+    private Integer bidPeriodType;
+
+    /**
+     * 下次招标时间
+     */
+    @ExcelProperty(value = "下次招标时间")
+    @Schema(description = "下次招标时间")
+    private Date nextBiddingTime;
+
+    /**
+     * 入围类型 (FinalizationType)
+     */
+    @ExcelProperty(value = "入围类型")
+    @Schema(description = "入围类型")
+    private Integer finalizationType;
+
+    /**
+     * 提前通知天数
+     */
+    @ExcelProperty(value = "提前通知天数")
+    @Schema(description = "提前通知天数")
+    private Integer noticeAdvanceDays;
+
+    /**
+     * 是否提前通知
+     */
+    @ExcelProperty(value = "是否提前通知")
+    @Schema(description = "是否提前通知")
+    private Integer isNoticeAdvance;
+
+    /**
+     * 项目等级
+     */
+    @ExcelProperty(value = "项目等级")
+    @Schema(description = "项目等级")
+    private String projectGrade;
+
+    /**
+     * 条件
+     */
+    @ExcelProperty(value = "条件")
+    @Schema(description = "条件")
+    private String condition;
+
+    /**
+     * 招标链接
+     */
+    @ExcelProperty(value = "招标链接")
+    @Schema(description = "招标链接")
+    private String biddingLink;
+
+    /**
+     * 平台名称
+     */
+    @ExcelProperty(value = "平台名称")
+    @Schema(description = "平台名称")
+    private String platformName;
+
+    /**
+     * 平台链接
+     */
+    @ExcelProperty(value = "平台链接")
+    @Schema(description = "平台链接")
+    private String platformLink;
+
+    /**
+     * 服务时间
+     */
+    @ExcelProperty(value = "服务时间")
+    @Schema(description = "服务时间")
+    private String serviceTime;
+
+}

+ 75 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/VisitRoutineVo.java

@@ -0,0 +1,75 @@
+package org.dromara.customer.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import org.dromara.customer.domain.VisitRoutine;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 拜访日程视图对象 callonschedule
+ *
+ * @author yoe
+ * @date 2026-04-20
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = VisitRoutine.class)
+public class VisitRoutineVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    private String planNo;
+
+    private String scheduleNo;
+
+    private Integer relevanceType;
+
+    private String objectNo;
+
+    private String objectName;
+
+    private String customerNo;
+
+    private String customerName;
+
+    private Long callPeopleNo;
+
+    private String callPeopleName;
+
+    private Date callDate;
+
+    private String followPeopleNo;
+
+    private String followPeopleName;
+
+    @ExcelProperty(value = "紧要级别", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "1=一般,2=重要,3=紧急")
+    private Integer importantLevel;
+
+    @ExcelProperty(value = "拜访目的")
+    private String purposeVisit;
+
+    @ExcelProperty(value = "日程状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=未执行,1=已执行,2=放弃执行")
+    private Integer scheduleStatus;
+
+    @ExcelProperty(value = "实际执行时间")
+    private Date executeTime;
+
+    @ExcelProperty(value = "未执行原因")
+    private String unExecuteReason;
+
+    @ExcelProperty(value = "创建时间")
+    private Date createTime;
+
+}

+ 13 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CrmVisitPlanMapper.java

@@ -0,0 +1,13 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.VisitPlan;
+import org.dromara.customer.domain.vo.CrmVisitPlanVo;
+
+/**
+ * 拜访计划Mapper接口
+ * @author tys
+ */
+public interface CrmVisitPlanMapper extends BaseMapperPlus<VisitPlan, CrmVisitPlanVo> {
+
+}

+ 12 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CrmVisitRoutineMapper.java

@@ -0,0 +1,12 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.VisitRoutine;
+import org.dromara.customer.domain.vo.VisitRoutineVo;
+
+/**
+ * 拜访日程Mapper接口
+ */
+public interface CrmVisitRoutineMapper extends BaseMapperPlus<VisitRoutine, VisitRoutineVo> {
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/FollowUpLogMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.FollowUpLog;
+import org.dromara.customer.domain.vo.FollowUpLogVo;
+
+/**
+ * 跟进记录Mapper接口
+ *
+ * @author yoe
+ * @date 2026-04-20
+ */
+public interface FollowUpLogMapper extends BaseMapperPlus<FollowUpLog, FollowUpLogVo> {
+
+}

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/SalesAnnualFinalizationMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.SalesAnnualFinalization;
+import org.dromara.customer.domain.vo.SalesAnnualFinalizationVo;
+
+/**
+ * 年度入围项目 Mapper 接口
+ *
+ * @author tys
+ * @date 2026-04-18
+ */
+public interface SalesAnnualFinalizationMapper extends BaseMapperPlus<SalesAnnualFinalization, SalesAnnualFinalizationVo> {
+
+}

+ 28 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICrmVisitPlanService.java

@@ -0,0 +1,28 @@
+package org.dromara.customer.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.bo.CrmVisitPlanBo;
+import org.dromara.customer.domain.vo.CrmVisitPlanVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 拜访计划Service接口
+ * @author tys
+ */
+public interface ICrmVisitPlanService {
+
+    CrmVisitPlanVo queryById(Long id);
+
+    TableDataInfo<CrmVisitPlanVo> queryPageList(CrmVisitPlanBo bo, PageQuery pageQuery);
+
+    List<CrmVisitPlanVo> queryList(CrmVisitPlanBo bo);
+
+    Boolean insertByBo(CrmVisitPlanBo bo);
+
+    Boolean updateByBo(CrmVisitPlanBo bo);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 27 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICrmVisitRoutineService.java

@@ -0,0 +1,27 @@
+package org.dromara.customer.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.bo.VisitRoutineBo;
+import org.dromara.customer.domain.vo.VisitRoutineVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 拜访日程Service接口
+ */
+public interface ICrmVisitRoutineService {
+
+    VisitRoutineVo queryById(Long id);
+
+    TableDataInfo<VisitRoutineVo> queryPageList(VisitRoutineBo bo, PageQuery pageQuery);
+
+    List<VisitRoutineVo> queryList(VisitRoutineBo bo);
+
+    Boolean insertByBo(VisitRoutineBo bo);
+
+    Boolean updateByBo(VisitRoutineBo bo);
+
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 49 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IFollowUpLogService.java

@@ -0,0 +1,49 @@
+package org.dromara.customer.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.bo.FollowUpLogBo;
+import org.dromara.customer.domain.vo.FollowUpLogVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 跟进记录Service接口
+ *
+ * @author yoe
+ * @date 2026-04-20
+ */
+public interface IFollowUpLogService {
+
+    /**
+     * 查询跟进记录
+     */
+    FollowUpLogVo queryById(Long id);
+
+    /**
+     * 查询跟进记录分页列表
+     */
+    TableDataInfo<FollowUpLogVo> queryPageList(FollowUpLogBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询跟进记录列表
+     */
+    List<FollowUpLogVo> queryList(FollowUpLogBo bo);
+
+    /**
+     * 新增跟进记录
+     */
+    Boolean insertByBo(FollowUpLogBo bo);
+
+    /**
+     * 修改跟进记录
+     */
+    Boolean updateByBo(FollowUpLogBo bo);
+
+    /**
+     * 批量删除跟进记录
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+}

+ 9 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IOperationLogService.java

@@ -38,4 +38,13 @@ public interface IOperationLogService {
      * @param targetObject  目标对象
      */
     void recordLog(Integer dataType, String objectNo, Integer actionType, Integer actionModule, String actionDetails, String targetObject);
+
+
+    /**
+     * 异步记录操作日志
+     * @param planId 拜访计划ID
+     * @param type 操作类型
+     * @param content 操作明细
+     */
+    void asyncRecordLog(Long planId, String type, String content);
 }

+ 48 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ISalesAnnualFinalizationService.java

@@ -0,0 +1,48 @@
+package org.dromara.customer.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.bo.SalesAnnualFinalizationBo;
+import org.dromara.customer.domain.vo.SalesAnnualFinalizationVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 年度入围项目 Service 接口
+ *
+ * @author Antigravity
+ * @date 2026-04-18
+ */
+public interface ISalesAnnualFinalizationService {
+
+    /**
+     * 查询年度入围项目列表
+     */
+    TableDataInfo<SalesAnnualFinalizationVo> queryPageList(SalesAnnualFinalizationBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询年度入围项目列表
+     */
+    List<SalesAnnualFinalizationVo> queryList(SalesAnnualFinalizationBo bo);
+
+    /**
+     * 查询年度入围项目详情
+     */
+    SalesAnnualFinalizationVo queryById(Long id);
+
+    /**
+     * 新增年度入围项目
+     */
+    Boolean insertByBo(SalesAnnualFinalizationBo bo);
+
+    /**
+     * 修改年度入围项目
+     */
+    Boolean updateByBo(SalesAnnualFinalizationBo bo);
+
+    /**
+     * 批量删除年度入围项目
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 126 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CrmVisitPlanServiceImpl.java

@@ -0,0 +1,126 @@
+package org.dromara.customer.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.VisitPlan;
+import org.dromara.customer.domain.bo.CrmVisitPlanBo;
+import org.dromara.customer.domain.bo.VisitRoutineBo;
+import org.dromara.customer.domain.vo.CrmVisitPlanVo;
+import org.dromara.customer.mapper.CrmVisitPlanMapper;
+import org.dromara.customer.service.ICrmVisitPlanService;
+import org.dromara.customer.service.ICrmVisitRoutineService;
+import org.dromara.customer.service.IOperationLogService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 拜访计划Service业务层处理
+ */
+@RequiredArgsConstructor
+@Service
+public class CrmVisitPlanServiceImpl implements ICrmVisitPlanService {
+
+    private final CrmVisitPlanMapper baseMapper;
+    private final ICrmVisitRoutineService crmVisitRoutineService;
+    private final IOperationLogService operateLogService;
+
+    @Override
+    public CrmVisitPlanVo queryById(Long id) {
+        CrmVisitPlanVo vo = baseMapper.selectVoById(id);
+        if (vo != null) {
+            VisitRoutineBo query = new VisitRoutineBo();
+            query.setPlanNo(vo.getPlanNo());
+            vo.setDetailList(crmVisitRoutineService.queryList(query));
+        }
+        return vo;
+    }
+
+    @Override
+    public TableDataInfo<CrmVisitPlanVo> queryPageList(CrmVisitPlanBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<VisitPlan> lqw = buildQueryWrapper(bo);
+        Page<CrmVisitPlanVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<CrmVisitPlanVo> queryList(CrmVisitPlanBo bo) {
+        LambdaQueryWrapper<VisitPlan> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<VisitPlan> buildQueryWrapper(CrmVisitPlanBo bo) {
+        LambdaQueryWrapper<VisitPlan> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getPlanNo()), VisitPlan::getPlanNo, bo.getPlanNo());
+        lqw.eq(bo.getVisitorId() != null, VisitPlan::getVisitorId, bo.getVisitorId());
+        lqw.like(StringUtils.isNotBlank(bo.getVisitorName()), VisitPlan::getVisitorName, bo.getVisitorName());
+        lqw.eq(bo.getStartTime() != null, VisitPlan::getStartTime, bo.getStartTime());
+        lqw.eq(bo.getEndTime() != null, VisitPlan::getEndTime, bo.getEndTime());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), VisitPlan::getStatus, bo.getStatus());
+        lqw.orderByDesc(VisitPlan::getId);
+        return lqw;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public Boolean insertByBo(CrmVisitPlanBo bo) {
+        if (StringUtils.isBlank(bo.getPlanNo())) {
+            bo.setPlanNo(DateUtil.format(new java.util.Date(), "yyyyMMdd") + RandomUtil.randomNumbers(4));
+        }
+        bo.setStatus("0"); // 默认待审核
+        VisitPlan add = MapstructUtils.convert(bo, VisitPlan.class);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            operateLogService.asyncRecordLog(add.getId(), "新建计划", "创建了拜访计划及任务");
+            bo.setId(add.getId());
+            insertDetails(bo);
+        }
+        return flag;
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public Boolean updateByBo(CrmVisitPlanBo bo) {
+        VisitPlan update = MapstructUtils.convert(bo,VisitPlan.class);
+        boolean flag = baseMapper.updateById(update) > 0;
+        if (flag) {
+            operateLogService.asyncRecordLog(update.getId(), "编辑计划", "修改了拜访计划及日程分布");
+
+            // 删除原有的明细并重新插入
+            List<Long> delIds = crmVisitRoutineService.queryList(new VisitRoutineBo(){{ setPlanNo(bo.getPlanNo()); }})
+                    .stream().map(org.dromara.customer.domain.vo.VisitRoutineVo::getId).toList();
+            if (CollUtil.isNotEmpty(delIds)) {
+                crmVisitRoutineService.deleteWithValidByIds(delIds, false);
+            }
+            insertDetails(bo);
+        }
+        return flag;
+    }
+
+    private void insertDetails(CrmVisitPlanBo bo) {
+        if (CollUtil.isNotEmpty(bo.getDetailList())) {
+            for (VisitRoutineBo detail : bo.getDetailList()) {
+                detail.setPlanNo(bo.getPlanNo());
+                detail.setCallPeopleNo(bo.getVisitorId());
+                detail.setCallPeopleName(bo.getVisitorName());
+                crmVisitRoutineService.insertByBo(detail);
+            }
+        }
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 104 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CrmVisitRoutineServiceImpl.java

@@ -0,0 +1,104 @@
+package org.dromara.customer.service.impl;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.VisitRoutine;
+import org.dromara.customer.domain.bo.VisitRoutineBo;
+import org.dromara.customer.domain.vo.VisitRoutineVo;
+import org.dromara.customer.mapper.CrmVisitRoutineMapper;
+import org.dromara.customer.service.ICrmVisitRoutineService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+@RequiredArgsConstructor
+@Service
+public class CrmVisitRoutineServiceImpl implements ICrmVisitRoutineService {
+
+    private final CrmVisitRoutineMapper baseMapper;
+
+    @Override
+    public VisitRoutineVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    @Override
+    public TableDataInfo<VisitRoutineVo> queryPageList(VisitRoutineBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<VisitRoutine> lqw = buildQueryWrapper(bo);
+        Page<VisitRoutineVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    @Override
+    public List<VisitRoutineVo> queryList(VisitRoutineBo bo) {
+        LambdaQueryWrapper<VisitRoutine> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<VisitRoutine> buildQueryWrapper(VisitRoutineBo bo) {
+        LambdaQueryWrapper<VisitRoutine> lqw = Wrappers.lambdaQuery();
+        lqw.like(StringUtils.isNotBlank(bo.getPlanNo()), VisitRoutine::getPlanNo, bo.getPlanNo());
+        lqw.like(StringUtils.isNotBlank(bo.getScheduleNo()), VisitRoutine::getScheduleNo, bo.getScheduleNo());
+        lqw.eq(bo.getRelevanceType() != null, VisitRoutine::getRelevanceType, bo.getRelevanceType());
+        lqw.like(StringUtils.isNotBlank(bo.getCustomerName()), VisitRoutine::getCustomerName, bo.getCustomerName());
+        lqw.like(StringUtils.isNotBlank(bo.getCallPeopleName()), VisitRoutine::getCallPeopleName, bo.getCallPeopleName());
+        lqw.eq(bo.getCallDate() != null, VisitRoutine::getCallDate, bo.getCallDate());
+        lqw.eq(bo.getImportantLevel() != null, VisitRoutine::getImportantLevel, bo.getImportantLevel());
+        lqw.eq(bo.getScheduleStatus() != null, VisitRoutine::getScheduleStatus, bo.getScheduleStatus());
+        return lqw;
+    }
+
+    @Override
+    public Boolean insertByBo(VisitRoutineBo bo) {
+        if (StringUtils.isBlank(bo.getScheduleNo())) {
+            bo.setScheduleNo(DateUtil.format(new java.util.Date(), "yyyyMMdd") + RandomUtil.randomNumbers(4));
+        }
+        if (bo.getScheduleStatus() == null) {
+            bo.setScheduleStatus(0);
+        }
+        // 补充关联信息
+        if (bo.getRelevanceType() != null && bo.getRelevanceType() == 0) {
+            if (StringUtils.isBlank(bo.getObjectName())) {
+                bo.setObjectName(bo.getCustomerName());
+            }
+            if (StringUtils.isBlank(bo.getObjectNo())) {
+                bo.setObjectNo(bo.getCustomerNo());
+            }
+        }
+        VisitRoutine add = MapstructUtils.convert(bo, VisitRoutine.class);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    @Override
+    public Boolean updateByBo(VisitRoutineBo bo) {
+        // 补充关联信息
+        if (bo.getRelevanceType() != null && bo.getRelevanceType() == 0) {
+            if (StringUtils.isBlank(bo.getObjectName())) {
+                bo.setObjectName(bo.getCustomerName());
+            }
+            if (StringUtils.isBlank(bo.getObjectNo())) {
+                bo.setObjectNo(bo.getCustomerNo());
+            }
+        }
+        VisitRoutine update = MapstructUtils.convert(bo, VisitRoutine.class);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 147 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/FollowUpLogServiceImpl.java

@@ -0,0 +1,147 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.domain.FollowUpLog;
+import org.dromara.customer.domain.bo.FollowUpLogBo;
+import org.dromara.customer.domain.vo.CustomerInfoVo;
+import org.dromara.customer.domain.vo.FollowUpLogVo;
+import org.dromara.customer.mapper.CustomerInfoMapper;
+import org.dromara.customer.mapper.FollowUpLogMapper;
+import org.dromara.customer.service.IFollowUpLogService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 跟进记录Service业务层处理
+ *
+ * @author yoe
+ * @date 2026-04-20
+ */
+@RequiredArgsConstructor
+@Service
+public class FollowUpLogServiceImpl implements IFollowUpLogService {
+
+    private final FollowUpLogMapper baseMapper;
+    private final CustomerInfoMapper customerInfoMapper;
+
+    /**
+     * 查询跟进记录
+     */
+    @Override
+    public FollowUpLogVo queryById(Long id) {
+        FollowUpLogVo vo = baseMapper.selectVoById(id);
+        if (vo != null && StringUtils.isNotBlank(vo.getCustomerNo())) {
+            // 如果部门、行业或负责人为空,尝试从客户基础信息中获取真实数据
+            if (StringUtils.isBlank(vo.getDepartment()) || StringUtils.isBlank(vo.getProfession()) || StringUtils.isBlank(vo.getSalesman())) {
+                CustomerInfoVo customer = customerInfoMapper.selectVoOne(
+                    Wrappers.<org.dromara.customer.domain.CustomerInfo>lambdaQuery()
+                        .eq(CustomerInfo::getCustomerNo, vo.getCustomerNo())
+                );
+                if (customer != null) {
+                    if (StringUtils.isBlank(vo.getProfession())) {
+                        vo.setProfession(customer.getIndustryCategory());
+                    }
+                    // 从关联的销售信息中获取部门和负责人
+                    org.dromara.customer.domain.vo.CustomerSalesInfoVo salesInfo = customer.getCustomerSalesInfoVo();
+                    if (salesInfo != null) {
+                        if (StringUtils.isBlank(vo.getDepartment())) {
+                            vo.setDepartment(salesInfo.getBelongingDepartment());
+                        }
+                        if (StringUtils.isBlank(vo.getSalesman())) {
+                            vo.setSalesman(salesInfo.getSalesPerson());
+                        }
+                    }
+                }
+            }
+        }
+        return vo;
+    }
+
+    /**
+     * 查询跟进记录分页列表
+     */
+    @Override
+    public TableDataInfo<FollowUpLogVo> queryPageList(FollowUpLogBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<FollowUpLog> lqw = buildQueryWrapper(bo);
+        Page<FollowUpLogVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询跟进记录列表
+     */
+    @Override
+    public List<FollowUpLogVo> queryList(FollowUpLogBo bo) {
+        LambdaQueryWrapper<FollowUpLog> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<FollowUpLog> buildQueryWrapper(FollowUpLogBo bo) {
+        LambdaQueryWrapper<FollowUpLog> lqw = Wrappers.lambdaQuery();
+        lqw.eq(StringUtils.isNotBlank(bo.getRecordsNo()), FollowUpLog::getRecordsNo, bo.getRecordsNo());
+        lqw.eq(bo.getDataType() != null, FollowUpLog::getDataType, bo.getDataType());
+        lqw.eq(StringUtils.isNotBlank(bo.getObjectNo()), FollowUpLog::getObjectNo, bo.getObjectNo());
+        lqw.eq(StringUtils.isNotBlank(bo.getCustomerNo()), FollowUpLog::getCustomerNo, bo.getCustomerNo());
+        lqw.like(StringUtils.isNotBlank(bo.getCustomerName()), FollowUpLog::getCustomerName, bo.getCustomerName());
+        lqw.like(StringUtils.isNotBlank(bo.getVisitor()), FollowUpLog::getVisitor, bo.getVisitor());
+        lqw.like(StringUtils.isNotBlank(bo.getFollowPeopleName()), FollowUpLog::getFollowPeopleName, bo.getFollowPeopleName());
+        lqw.eq(StringUtils.isNotBlank(bo.getCallTypeCode()), FollowUpLog::getCallTypeCode, bo.getCallTypeCode());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), FollowUpLog::getPlatformCode, bo.getPlatformCode());
+        // 增加排重和基础过滤
+        lqw.eq(FollowUpLog::getIsDelete, 0);
+        lqw.orderByDesc(FollowUpLog::getId);
+        return lqw;
+    }
+
+    /**
+     * 新增跟进记录
+     */
+    @Override
+    public Boolean insertByBo(FollowUpLogBo bo) {
+        FollowUpLog add = MapstructUtils.convert(bo, FollowUpLog.class);
+        if (StringUtils.isBlank(add.getRecordsNo())) {
+            add.setRecordsNo(cn.hutool.core.date.DateUtil.format(new java.util.Date(), "yyyyMMdd") + cn.hutool.core.util.RandomUtil.randomNumbers(4));
+        }
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改跟进记录
+     */
+    @Override
+    public Boolean updateByBo(FollowUpLogBo bo) {
+        FollowUpLog update = MapstructUtils.convert(bo, FollowUpLog.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(FollowUpLog entity) {
+        // TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 批量删除跟进记录
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 8 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/OperationLogServiceImpl.java

@@ -12,6 +12,7 @@ import org.dromara.customer.domain.bo.OperationLogBo;
 import org.dromara.customer.domain.vo.OperationLogVo;
 import org.dromara.customer.mapper.OperationLogMapper;
 import org.dromara.customer.service.IOperationLogService;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.util.Collection;
@@ -81,4 +82,11 @@ public class OperationLogServiceImpl implements IOperationLogService {
         log.setPlatformCode("crm");
         baseMapper.insert(log);
     }
+
+
+    @Async
+    @Override
+    public void asyncRecordLog(Long planId, String type, String content) {
+        return;
+    }
 }

+ 150 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/SalesAnnualFinalizationServiceImpl.java

@@ -0,0 +1,150 @@
+package org.dromara.customer.service.impl;
+
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.SequenceUtils;
+import org.dromara.customer.domain.SalesAnnualFinalization;
+import org.dromara.customer.domain.bo.SalesAnnualFinalizationBo;
+import org.dromara.customer.domain.vo.SalesAnnualFinalizationVo;
+import org.dromara.customer.mapper.SalesAnnualFinalizationMapper;
+import org.dromara.customer.service.IOperationLogService;
+import org.dromara.customer.service.ISalesAnnualFinalizationService;
+import org.springframework.stereotype.Service;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 年度入围项目 Service 业务层处理
+ *
+ * @author tys
+ * @date 2026-04-18
+ */
+@RequiredArgsConstructor
+@Service
+public class SalesAnnualFinalizationServiceImpl implements ISalesAnnualFinalizationService {
+
+    private static final String PROJECT_NO_KEY = "annual_finalization:project_no";
+
+    private final SalesAnnualFinalizationMapper baseMapper;
+    private final IOperationLogService operationLogService;
+
+    /**
+     * 查询年度入围项目列表
+     */
+    @Override
+    public TableDataInfo<SalesAnnualFinalizationVo> queryPageList(SalesAnnualFinalizationBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<SalesAnnualFinalization> lqw = buildQueryWrapper(bo);
+        Page<SalesAnnualFinalizationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询年度入围项目列表
+     */
+    @Override
+    public List<SalesAnnualFinalizationVo> queryList(SalesAnnualFinalizationBo bo) {
+        LambdaQueryWrapper<SalesAnnualFinalization> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<SalesAnnualFinalization> buildQueryWrapper(SalesAnnualFinalizationBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<SalesAnnualFinalization> lqw = Wrappers.lambdaQuery();
+        lqw.like(StrUtil.isNotBlank(bo.getProjectName()), SalesAnnualFinalization::getProjectName, bo.getProjectName());
+        lqw.eq(StrUtil.isNotBlank(bo.getCompanyNo()), SalesAnnualFinalization::getCompanyNo, bo.getCompanyNo());
+        lqw.like(StrUtil.isNotBlank(bo.getCustomName()), SalesAnnualFinalization::getCustomName, bo.getCustomName());
+        lqw.eq(bo.getLeader() != null, SalesAnnualFinalization::getLeader, bo.getLeader());
+        lqw.eq(StrUtil.isNotBlank(bo.getDeptNo()), SalesAnnualFinalization::getDeptNo, bo.getDeptNo());
+        lqw.eq(bo.getProjectStatus() != null, SalesAnnualFinalization::getProjectStatus, bo.getProjectStatus());
+        lqw.eq(bo.getProjectLevel() != null, SalesAnnualFinalization::getProjectLevel, bo.getProjectLevel());
+        lqw.eq(bo.getBusinessType() != null, SalesAnnualFinalization::getBusinessType, bo.getBusinessType());
+        lqw.eq(StrUtil.isNotBlank(bo.getProductSupport()), SalesAnnualFinalization::getProductSupport, bo.getProductSupport());
+        return lqw;
+    }
+
+    /**
+     * 查询年度入围项目详情
+     */
+    @Override
+    public SalesAnnualFinalizationVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 新增年度入围项目
+     */
+    @Override
+    public Boolean insertByBo(SalesAnnualFinalizationBo bo) {
+        SalesAnnualFinalization add = BeanUtil.toBean(bo, SalesAnnualFinalization.class);
+        validEntityBeforeSave(add);
+
+        if (add.getProjectNo() == null || add.getProjectNo().isEmpty()) {
+            add.setProjectNo(SequenceUtils.nextPaddedIdStr(PROJECT_NO_KEY, Duration.ofDays(3650), 6));
+        }
+        // 恢复默认值赋值,因为当前连接的数据库仍然提示没有默认值
+        if (add.getBiddingType() == null) {
+            add.setBiddingType(1);
+        }
+        if (add.getProjectStatus() == null) {
+            add.setProjectStatus(1);
+        }
+        if (add.getBidBondStatus() == null) {
+            add.setBidBondStatus(0);
+        }
+        if (add.getIsNoticeAdvance() == null) {
+            add.setIsNoticeAdvance(0);
+        }
+
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+            operationLogService.recordLog(2, String.valueOf(add.getId()), 1, null, "年度入围", add.getProjectName());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改年度入围项目
+     */
+    @Override
+    public Boolean updateByBo(SalesAnnualFinalizationBo bo) {
+        SalesAnnualFinalization oldData = baseMapper.selectById(bo.getId());
+        SalesAnnualFinalization update = BeanUtil.toBean(bo, SalesAnnualFinalization.class);
+        validEntityBeforeSave(update);
+        boolean flag = baseMapper.updateById(update) > 0;
+        if (flag && oldData != null) {
+            if (!ObjectUtil.equals(oldData.getProjectStatus(), bo.getProjectStatus())) {
+                operationLogService.recordLog(2, String.valueOf(bo.getId()), 2, null, "项目状态", String.valueOf(bo.getProjectStatus()));
+            }
+        }
+        return flag;
+    }
+
+    /**
+     * 保存前校验
+     */
+    private void validEntityBeforeSave(SalesAnnualFinalization entity) {
+        // 在这里添加保存前的校验逻辑
+    }
+
+    /**
+     * 批量删除年度入围项目
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if (isValid) {
+            // 这里可以添加逻辑,校验是否允许删除
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}