From bc9e530d281236b53435e6016a5dc770b53cfa42 Mon Sep 17 00:00:00 2001 From: Yangk Date: Thu, 19 Mar 2026 16:15:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(oa):=20=E6=B7=BB=E5=8A=A0=E5=87=BA?= =?UTF-8?q?=E5=B7=AE=E7=94=B3=E8=AF=B7=E6=98=8E=E7=BB=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=B9=B6=E9=87=8D=E6=9E=84=E4=B8=BA=E4=B8=BB=E5=AD=90=E8=A1=A8?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增出差申请明细实体类、业务对象、视图对象及映射器 - 添加出差申请明细控制器和服务实现 - 重构出差申请主表字段:移除exchangeProcess字段,将feedback字段重命名为exchangeFeedback - 在出差申请主表中添加明细列表关联关系 - 实现出差申请与明细的级联查询、保存、更新和删除功能 - 添加事务注解确保数据一致性操作 --- .../CrmBusinessTripDetailsController.java | 116 ++++++++++++++ .../oa/crm/domain/CrmBusinessTripApply.java | 15 +- .../oa/crm/domain/CrmBusinessTripDetails.java | 109 +++++++++++++ .../crm/domain/bo/CrmBusinessTripApplyBo.java | 13 +- .../domain/bo/CrmBusinessTripDetailsBo.java | 102 ++++++++++++ .../crm/domain/vo/CrmBusinessTripApplyVo.java | 16 +- .../domain/vo/CrmBusinessTripDetailsVo.java | 136 ++++++++++++++++ .../mapper/CrmBusinessTripDetailsMapper.java | 37 +++++ .../ICrmBusinessTripDetailsService.java | 69 +++++++++ .../impl/CrmBusinessTripApplyServiceImpl.java | 99 +++++++++++- .../CrmBusinessTripDetailsServiceImpl.java | 145 ++++++++++++++++++ .../oa/crm/CrmBusinessTripDetailsMapper.xml | 14 ++ 12 files changed, 842 insertions(+), 29 deletions(-) create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmBusinessTripDetailsController.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripDetails.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripDetailsBo.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripDetailsVo.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/mapper/CrmBusinessTripDetailsMapper.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/ICrmBusinessTripDetailsService.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripDetailsServiceImpl.java create mode 100644 ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/crm/CrmBusinessTripDetailsMapper.xml diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmBusinessTripDetailsController.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmBusinessTripDetailsController.java new file mode 100644 index 00000000..d872694f --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmBusinessTripDetailsController.java @@ -0,0 +1,116 @@ +package org.dromara.oa.crm.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.oa.crm.domain.vo.CrmBusinessTripDetailsVo; +import org.dromara.oa.crm.domain.bo.CrmBusinessTripDetailsBo; +import org.dromara.oa.crm.service.ICrmBusinessTripDetailsService; +import org.dromara.common.mybatis.core.page.TableDataInfo; + +/** + * 出差申请明细 + * 前端访问路由地址为:/oa/crm/businessTripDetails + * + * @author Yangk + * @date 2026-03-17 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/businessTripDetails") +public class CrmBusinessTripDetailsController extends BaseController { + + private final ICrmBusinessTripDetailsService crmBusinessTripDetailsService; + + /** + * 查询出差申请明细列表 + */ + @SaCheckPermission("oa/crm:businessTripDetails:list") + @GetMapping("/list") + public TableDataInfo list(CrmBusinessTripDetailsBo bo, PageQuery pageQuery) { + return crmBusinessTripDetailsService.queryPageList(bo, pageQuery); + } + + /** + * 导出出差申请明细列表 + */ + @SaCheckPermission("oa/crm:businessTripDetails:export") + @Log(title = "出差申请明细", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(CrmBusinessTripDetailsBo bo, HttpServletResponse response) { + List list = crmBusinessTripDetailsService.queryList(bo); + ExcelUtil.exportExcel(list, "出差申请明细", CrmBusinessTripDetailsVo.class, response); + } + + /** + * 获取出差申请明细详细信息 + * + * @param tripDetailsId 主键 + */ + @SaCheckPermission("oa/crm:businessTripDetails:query") + @GetMapping("/{tripDetailsId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable("tripDetailsId") Long tripDetailsId) { + return R.ok(crmBusinessTripDetailsService.queryById(tripDetailsId)); + } + + /** + * 新增出差申请明细 + */ + @SaCheckPermission("oa/crm:businessTripDetails:add") + @Log(title = "出差申请明细", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody CrmBusinessTripDetailsBo bo) { + return toAjax(crmBusinessTripDetailsService.insertByBo(bo)); + } + + /** + * 修改出差申请明细 + */ + @SaCheckPermission("oa/crm:businessTripDetails:edit") + @Log(title = "出差申请明细", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody CrmBusinessTripDetailsBo bo) { + return toAjax(crmBusinessTripDetailsService.updateByBo(bo)); + } + + /** + * 删除出差申请明细 + * + * @param tripDetailsIds 主键串 + */ + @SaCheckPermission("oa/crm:businessTripDetails:remove") + @Log(title = "出差申请明细", businessType = BusinessType.DELETE) + @DeleteMapping("/{tripDetailsIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable("tripDetailsIds") Long[] tripDetailsIds) { + return toAjax(crmBusinessTripDetailsService.deleteWithValidByIds(List.of(tripDetailsIds), true)); + } + + /** + * 下拉框查询出差申请明细列表 + */ + @GetMapping("/getCrmBusinessTripDetailsList") + public R> getCrmBusinessTripDetailsList(CrmBusinessTripDetailsBo bo) { + List list = crmBusinessTripDetailsService.queryList(bo); + return R.ok(list); + } + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripApply.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripApply.java index 317a9c3a..aba1ee26 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripApply.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripApply.java @@ -109,20 +109,16 @@ public class CrmBusinessTripApply extends TenantEntity { */ private String exchangePurpose; - /** - * 交流过程简述 - */ - private String exchangeProcess; - /** * 会议/展会名称 */ private String meetingName; /** - * 结果反馈 + * 交流过程与反馈 */ - private String feedback; + @TableField("feedback") + private String exchangeFeedback; /** * 申请状态(1暂存 2审批中 3已审批 4作废) @@ -150,5 +146,10 @@ public class CrmBusinessTripApply extends TenantEntity { @TableLogic private String delFlag; + /** + * 出差申请明细列表 + */ + @TableField(exist = false) + private java.util.List crmBusinessTripDetailsList; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripDetails.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripDetails.java new file mode 100644 index 00000000..f099abd6 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmBusinessTripDetails.java @@ -0,0 +1,109 @@ +package org.dromara.oa.crm.domain; + +import org.dromara.common.tenant.core.TenantEntity; +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; + +/** + * 出差申请明细对象 crm_business_trip_details + * + * @author Yangk + * @date 2026-03-17 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("crm_business_trip_details") +public class CrmBusinessTripDetails extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 申请明细ID + */ + @TableId(value = "trip_details_id", type = IdType.AUTO) + private Long tripDetailsId; + + /** + * 出差申请ID + */ + private Long tripId; + + /** + * 行程顺序(行程1) + */ + private Long itineraryNumber; + + /** + * 出差地点 + */ + private String tripLocation; + + /** + * 开始日期 + */ + private Date startTime; + + /** + * 结束日期 + */ + private Date endTime; + + /** + * 时长(天) + */ + private Long durationDays; + + /** + * 项目ID + */ + private Long projectId; + + /** + * 客户ID + */ + private Long customerId; + + /** + * 会议/展会名称 + */ + private String meetingName; + + /** + * 交流目的 + */ + private String exchangePurpose; + + /** + * 交流过程与反馈 + */ + private String exchangeFeedback; + + /** + * 出差事由 + */ + private String tripReason; + + /** + * 备注 + */ + private String remark; + + /** + * 附件ID(多个用逗号分隔) + */ + private String ossId; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripApplyBo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripApplyBo.java index 590196ea..c821d0e4 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripApplyBo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripApplyBo.java @@ -112,20 +112,15 @@ public class CrmBusinessTripApplyBo extends BaseEntity { */ private String exchangePurpose; - /** - * 交流过程简述 - */ - private String exchangeProcess; - /** * 会议/展会名称 */ private String meetingName; /** - * 结果反馈 + * 交流过程与反馈 */ - private String feedback; + private String exchangeFeedback; /** * 申请状态(1暂存 2审批中 3已审批 4作废) @@ -166,5 +161,9 @@ public class CrmBusinessTripApplyBo extends BaseEntity { * 导出时指定的ID列表(逗号分隔) */ private String tripIds; + /** + * 出差申请明细列表 + */ + private java.util.List crmBusinessTripDetailsList; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripDetailsBo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripDetailsBo.java new file mode 100644 index 00000000..c4cd3cfe --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmBusinessTripDetailsBo.java @@ -0,0 +1,102 @@ +package org.dromara.oa.crm.domain.bo; + +import org.dromara.oa.crm.domain.CrmBusinessTripDetails; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 出差申请明细业务对象 crm_business_trip_details + * + * @author Yangk + * @date 2026-03-17 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CrmBusinessTripDetails.class, reverseConvertGenerate = false) +public class CrmBusinessTripDetailsBo extends BaseEntity { + + /** + * 申请明细ID + */ + @NotNull(message = "申请明细ID不能为空", groups = { EditGroup.class }) + private Long tripDetailsId; + + /** + * 出差申请ID + */ + private Long tripId; + + /** + * 行程顺序(行程1) + */ + private Long itineraryNumber; + + /** + * 出差地点 + */ + private String tripLocation; + + /** + * 开始日期 + */ + private Date startTime; + + /** + * 结束日期 + */ + private Date endTime; + + /** + * 时长(天) + */ + private Long durationDays; + + /** + * 项目ID + */ + private Long projectId; + + /** + * 客户ID + */ + private Long customerId; + + /** + * 会议/展会名称 + */ + private String meetingName; + + /** + * 交流目的 + */ + private String exchangePurpose; + + /** + * 交流过程与反馈 + */ + private String exchangeFeedback; + + /** + * 出差事由 + */ + private String tripReason; + + /** + * 备注 + */ + private String remark; + + /** + * 附件ID(多个用逗号分隔) + */ + private String ossId; + + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripApplyVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripApplyVo.java index 422ad429..f973a36f 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripApplyVo.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripApplyVo.java @@ -148,12 +148,6 @@ public class CrmBusinessTripApplyVo implements Serializable { @ExcelProperty(value = "交流目的") private String exchangePurpose; - /** - * 交流过程简述 - */ - @ExcelProperty(value = "交流过程简述") - private String exchangeProcess; - /** * 会议/展会名称 */ @@ -161,10 +155,10 @@ public class CrmBusinessTripApplyVo implements Serializable { private String meetingName; /** - * 结果反馈 + * 交流过程与反馈 */ - @ExcelProperty(value = "结果反馈") - private String feedback; + @ExcelProperty(value = "交流过程与反馈") + private String exchangeFeedback; /** * 申请状态(1暂存 2审批中 3已审批 4作废) @@ -191,5 +185,9 @@ public class CrmBusinessTripApplyVo implements Serializable { @ExcelProperty(value = "附件ID", converter = ExcelDictConvert.class) @ExcelDictFormat(readConverterExp = "多=个用逗号分隔") private String ossId; + /** + * 出差申请明细列表 + */ + private java.util.List crmBusinessTripDetailsList; } diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripDetailsVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripDetailsVo.java new file mode 100644 index 00000000..d17a67ca --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmBusinessTripDetailsVo.java @@ -0,0 +1,136 @@ +package org.dromara.oa.crm.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; +import org.dromara.oa.crm.domain.CrmBusinessTripDetails; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + + + +/** + * 出差申请明细视图对象 crm_business_trip_details + * + * @author Yangk + * @date 2026-03-17 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = CrmBusinessTripDetails.class) +public class CrmBusinessTripDetailsVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 申请明细ID + */ + @ExcelProperty(value = "申请明细ID") + private Long tripDetailsId; + + /** + * 出差申请ID + */ + @ExcelProperty(value = "出差申请ID") + private Long tripId; + + /** + * 行程顺序(行程1) + */ + @ExcelProperty(value = "行程顺序(行程1)") + private Long itineraryNumber; + + /** + * 出差地点 + */ + @ExcelProperty(value = "出差地点") + private String tripLocation; + + /** + * 开始日期 + */ + @ExcelProperty(value = "开始日期") + private Date startTime; + + /** + * 结束日期 + */ + @ExcelProperty(value = "结束日期") + private Date endTime; + + /** + * 时长(天) + */ + @ExcelProperty(value = "时长(天)") + private Long durationDays; + + /** + * 项目ID + */ + @ExcelProperty(value = "项目ID") + private Long projectId; + + /** + * 客户ID + */ + @ExcelProperty(value = "客户ID") + private Long customerId; + + /** + * 会议/展会名称 + */ + @ExcelProperty(value = "会议/展会名称") + private String meetingName; + + /** + * 交流目的 + */ + @ExcelProperty(value = "交流目的", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "exchange_purpose") + private String exchangePurpose; + + /** + * 交流过程与反馈 + */ + @ExcelProperty(value = "交流过程与反馈") + private String exchangeFeedback; + + /** + * 出差事由 + */ + @ExcelProperty(value = "出差事由") + private String tripReason; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 附件ID(多个用逗号分隔) + */ + @ExcelProperty(value = "附件ID", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "多=个用逗号分隔") + private String ossId; + + + /** + * 项目名称 + */ + private String projectName; + + /** + * 项目号 + */ + private String projectCode; + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/mapper/CrmBusinessTripDetailsMapper.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/mapper/CrmBusinessTripDetailsMapper.java new file mode 100644 index 00000000..9daee6a8 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/mapper/CrmBusinessTripDetailsMapper.java @@ -0,0 +1,37 @@ +package org.dromara.oa.crm.mapper; + +import java.util.List; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.dromara.oa.crm.domain.CrmBusinessTripDetails; +import org.dromara.oa.crm.domain.vo.CrmBusinessTripDetailsVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 出差申请明细Mapper接口 + * + * @author Yangk + * @date 2026-03-17 + */ +public interface CrmBusinessTripDetailsMapper extends BaseMapperPlus { + + /** + * 查询出差申请明细列表 + * + * @param page 分页 + * @param queryWrapper 条件 + * @return 出差申请明细集合 + */ + public Page selectCustomCrmBusinessTripDetailsVoList(@Param("page") Page page, @Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper); + + /** + * 查询出差申请明细列表 + * + * @param queryWrapper 条件 + * @return 出差申请明细集合 + */ + public List selectCustomCrmBusinessTripDetailsVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper); + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/ICrmBusinessTripDetailsService.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/ICrmBusinessTripDetailsService.java new file mode 100644 index 00000000..b94cea88 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/ICrmBusinessTripDetailsService.java @@ -0,0 +1,69 @@ +package org.dromara.oa.crm.service; + +import org.dromara.oa.crm.domain.CrmBusinessTripDetails; +import org.dromara.oa.crm.domain.vo.CrmBusinessTripDetailsVo; +import org.dromara.oa.crm.domain.bo.CrmBusinessTripDetailsBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 出差申请明细Service接口 + * + * @author Yangk + * @date 2026-03-17 + */ +public interface ICrmBusinessTripDetailsService { + + /** + * 查询出差申请明细 + * + * @param tripDetailsId 主键 + * @return 出差申请明细 + */ + CrmBusinessTripDetailsVo queryById(Long tripDetailsId); + + /** + * 分页查询出差申请明细列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 出差申请明细分页列表 + */ + TableDataInfo queryPageList(CrmBusinessTripDetailsBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的出差申请明细列表 + * + * @param bo 查询条件 + * @return 出差申请明细列表 + */ + List queryList(CrmBusinessTripDetailsBo bo); + + /** + * 新增出差申请明细 + * + * @param bo 出差申请明细 + * @return 是否新增成功 + */ + Boolean insertByBo(CrmBusinessTripDetailsBo bo); + + /** + * 修改出差申请明细 + * + * @param bo 出差申请明细 + * @return 是否修改成功 + */ + Boolean updateByBo(CrmBusinessTripDetailsBo bo); + + /** + * 校验并批量删除出差申请明细信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripApplyServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripApplyServiceImpl.java index e776c51c..97aa54e9 100644 --- a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripApplyServiceImpl.java +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripApplyServiceImpl.java @@ -9,14 +9,19 @@ import com.github.yulichang.toolkit.JoinWrappers; import com.github.yulichang.wrapper.MPJLambdaWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; +import org.dromara.oa.crm.domain.CrmBusinessTripDetails; +import org.dromara.oa.crm.domain.bo.CrmBusinessTripDetailsBo; import org.springframework.stereotype.Service; import org.dromara.oa.crm.domain.bo.CrmBusinessTripApplyBo; import org.dromara.oa.crm.domain.vo.CrmBusinessTripApplyVo; +import org.dromara.oa.crm.domain.vo.CrmBusinessTripDetailsVo; import org.dromara.oa.crm.domain.CrmBusinessTripApply; import org.dromara.oa.crm.mapper.CrmBusinessTripApplyMapper; import org.dromara.oa.crm.service.ICrmBusinessTripApplyService; import org.dromara.oa.erp.domain.ErpProjectInfo; import org.dromara.oa.crm.domain.CrmCustomerInfo; +import org.dromara.oa.erp.mapper.ErpProjectInfoMapper; +import org.dromara.oa.crm.mapper.CrmBusinessTripDetailsMapper; import java.util.List; import java.util.Map; @@ -49,6 +54,8 @@ import cn.hutool.core.convert.Convert; public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplyService { private final CrmBusinessTripApplyMapper baseMapper; + private final CrmBusinessTripDetailsMapper detailsMapper; + private final ErpProjectInfoMapper erpProjectInfoMapper; @DubboReference(timeout = 30000) private RemoteWorkflowService remoteWorkflowService; @@ -72,7 +79,20 @@ public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplySer .leftJoin(ErpProjectInfo.class, ErpProjectInfo::getProjectId, CrmBusinessTripApply::getProjectId) .leftJoin(CrmCustomerInfo.class, CrmCustomerInfo::getCustomerId, CrmBusinessTripApply::getCustomerId) .eq(CrmBusinessTripApply::getTripId, tripId); - return baseMapper.selectJoinOne(CrmBusinessTripApplyVo.class, lqw); + CrmBusinessTripApplyVo vo = baseMapper.selectJoinOne(CrmBusinessTripApplyVo.class, lqw); + if (vo != null) { + MPJLambdaWrapper detailLqw = JoinWrappers.lambda(CrmBusinessTripDetails.class) + .selectAll(CrmBusinessTripDetails.class) + .selectAs(ErpProjectInfo::getProjectName, CrmBusinessTripDetailsVo::getProjectName) + .selectAs(ErpProjectInfo::getProjectCode, CrmBusinessTripDetailsVo::getProjectCode) + .leftJoin(ErpProjectInfo.class, ErpProjectInfo::getProjectId, CrmBusinessTripDetails::getProjectId) + .eq(CrmBusinessTripDetails::getTripId, tripId) + .orderByAsc(CrmBusinessTripDetails::getItineraryNumber); + List detailsVos = detailsMapper.selectJoinList(CrmBusinessTripDetailsVo.class, + detailLqw); + vo.setCrmBusinessTripDetailsList(detailsVos); + } + return vo; } /** @@ -136,11 +156,10 @@ public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplySer bo.getBusinessDirection()) .eq(StringUtils.isNotBlank(bo.getExchangePurpose()), CrmBusinessTripApply::getExchangePurpose, bo.getExchangePurpose()) - .eq(StringUtils.isNotBlank(bo.getExchangeProcess()), CrmBusinessTripApply::getExchangeProcess, - bo.getExchangeProcess()) .like(StringUtils.isNotBlank(bo.getMeetingName()), CrmBusinessTripApply::getMeetingName, bo.getMeetingName()) - .eq(StringUtils.isNotBlank(bo.getFeedback()), CrmBusinessTripApply::getFeedback, bo.getFeedback()) + .eq(StringUtils.isNotBlank(bo.getExchangeFeedback()), CrmBusinessTripApply::getExchangeFeedback, + bo.getExchangeFeedback()) .eq(StringUtils.isNotBlank(bo.getTripStatus()), CrmBusinessTripApply::getTripStatus, bo.getTripStatus()) .eq(StringUtils.isNotBlank(bo.getFlowStatus()), CrmBusinessTripApply::getFlowStatus, bo.getFlowStatus()) .eq(StringUtils.isNotBlank(bo.getOssId()), CrmBusinessTripApply::getOssId, bo.getOssId()) @@ -155,6 +174,7 @@ public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplySer * @return 是否新增成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean insertByBo(CrmBusinessTripApplyBo bo) { CrmBusinessTripApply add = MapstructUtils.convert(bo, CrmBusinessTripApply.class); validEntityBeforeSave(add); @@ -167,6 +187,17 @@ public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplySer if (flag) { bo.setTripId(add.getTripId()); bo.setApplyCode(add.getApplyCode()); + // 插入行程明细 + if (bo.getCrmBusinessTripDetailsList() != null && !bo.getCrmBusinessTripDetailsList().isEmpty()) { + List detailsList = MapstructUtils.convert(bo.getCrmBusinessTripDetailsList(), + CrmBusinessTripDetails.class); + long order = 1L; + for (CrmBusinessTripDetails detail : detailsList) { + detail.setTripId(add.getTripId()); + detail.setItineraryNumber(order++); + detailsMapper.insert(detail); + } + } } return flag; } @@ -178,10 +209,32 @@ public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplySer * @return 是否修改成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean updateByBo(CrmBusinessTripApplyBo bo) { CrmBusinessTripApply update = MapstructUtils.convert(bo, CrmBusinessTripApply.class); validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; + boolean flag = baseMapper.updateById(update) > 0; + if (flag) { + // 如果前端传了明细列表才进行全量覆盖替换(防止局部更新时不小心清空单据明细) + if (bo.getCrmBusinessTripDetailsList() != null) { + // 删除旧明细 + detailsMapper.delete(Wrappers.lambdaQuery() + .eq(CrmBusinessTripDetails::getTripId, update.getTripId())); + // 重新插入新明细 + if (!bo.getCrmBusinessTripDetailsList().isEmpty()) { + List detailsList = MapstructUtils.convert(bo.getCrmBusinessTripDetailsList(), + CrmBusinessTripDetails.class); + long order = 1L; + for (CrmBusinessTripDetails detail : detailsList) { + detail.setTripDetailsId(null); // 清除前端带过来的主键,防止与软删除的旧记录冲突 + detail.setTripId(update.getTripId()); + detail.setItineraryNumber(order++); + detailsMapper.insert(detail); + } + } + } + } + return flag; } /** @@ -199,11 +252,20 @@ public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplySer * @return 是否删除成功 */ @Override + @Transactional(rollbackFor = Exception.class) public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if (isValid) { // TODO 做一些业务上的校验,判断是否需要校验 } - return baseMapper.deleteByIds(ids) > 0; + boolean flag = baseMapper.deleteByIds(ids) > 0; + if (flag) { + // 批量级联删除子表明细 + for (Long tripId : ids) { + detailsMapper.delete(Wrappers.lambdaQuery() + .eq(CrmBusinessTripDetails::getTripId, tripId)); + } + } + return flag; } /** @@ -229,6 +291,31 @@ public class CrmBusinessTripApplyServiceImpl implements ICrmBusinessTripApplySer if (StringUtils.isNotBlank(bo.getTripType())) { variables.put("tripType", bo.getTripType()); } + if (bo.getApplicantId() != null) { + variables.put("applicantId", bo.getApplicantId().toString()); + } + + // --- 自动提取所有对应项目的第一顺位项目经理合并为多重审批人 --- + if ("1".equals(bo.getTripType()) && bo.getCrmBusinessTripDetailsList() != null + && !bo.getCrmBusinessTripDetailsList().isEmpty()) { + java.util.List projectIds = bo.getCrmBusinessTripDetailsList().stream() + .map(CrmBusinessTripDetailsBo::getProjectId) + .filter(java.util.Objects::nonNull) + .collect(java.util.stream.Collectors.toList()); + if (!projectIds.isEmpty()) { + java.util.List projects = erpProjectInfoMapper.selectList( + Wrappers.lambdaQuery().in(ErpProjectInfo::getProjectId, projectIds)); + String managerIds = projects.stream() + .map(ErpProjectInfo::getManagerId) + .filter(java.util.Objects::nonNull) + .map(String::valueOf) + .distinct() // 去重 + .collect(java.util.stream.Collectors.joining(",")); + if (StringUtils.isNotBlank(managerIds)) { + variables.put("approverId", managerIds); + } + } + } RemoteStartProcess startProcess = new RemoteStartProcess(); startProcess.setBusinessId(bo.getTripId().toString()); diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripDetailsServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripDetailsServiceImpl.java new file mode 100644 index 00000000..4d47c781 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmBusinessTripDetailsServiceImpl.java @@ -0,0 +1,145 @@ +package org.dromara.oa.crm.service.impl; + +import org.dromara.common.core.utils.MapstructUtils; +import org.dromara.common.core.utils.StringUtils; + import org.dromara.common.mybatis.core.page.TableDataInfo; + import org.dromara.common.mybatis.core.page.PageQuery; + import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.yulichang.toolkit.JoinWrappers; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.dromara.oa.crm.domain.bo.CrmBusinessTripDetailsBo; +import org.dromara.oa.crm.domain.vo.CrmBusinessTripDetailsVo; +import org.dromara.oa.crm.domain.CrmBusinessTripDetails; +import org.dromara.oa.crm.mapper.CrmBusinessTripDetailsMapper; +import org.dromara.oa.crm.service.ICrmBusinessTripDetailsService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 出差申请明细Service业务层处理 + * + * @author Yangk + * @date 2026-03-17 + */ +@RequiredArgsConstructor +@Service +public class CrmBusinessTripDetailsServiceImpl implements ICrmBusinessTripDetailsService { + + private final CrmBusinessTripDetailsMapper baseMapper; + + /** + * 查询出差申请明细 + * + * @param tripDetailsId 主键 + * @return 出差申请明细 + */ + @Override + public CrmBusinessTripDetailsVo queryById(Long tripDetailsId){ + return baseMapper.selectVoById(tripDetailsId); + } + + /** + * 分页查询出差申请明细列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 出差申请明细分页列表 + */ + @Override + public TableDataInfo queryPageList(CrmBusinessTripDetailsBo bo, PageQuery pageQuery) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的出差申请明细列表 + * + * @param bo 查询条件 + * @return 出差申请明细列表 + */ + @Override + public List queryList(CrmBusinessTripDetailsBo bo) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private MPJLambdaWrapper buildQueryWrapper(CrmBusinessTripDetailsBo bo) { + Map params = bo.getParams(); + MPJLambdaWrapper lqw = JoinWrappers.lambda(CrmBusinessTripDetails.class) + .selectAll(CrmBusinessTripDetails.class) + .eq(CrmBusinessTripDetails::getDelFlag, "0") + .eq(bo.getTripId() != null, CrmBusinessTripDetails::getTripId, bo.getTripId()) + .eq(bo.getItineraryNumber() != null, CrmBusinessTripDetails::getItineraryNumber, bo.getItineraryNumber()) + .eq(StringUtils.isNotBlank(bo.getTripLocation()), CrmBusinessTripDetails::getTripLocation, bo.getTripLocation()) + .eq(bo.getStartTime() != null, CrmBusinessTripDetails::getStartTime, bo.getStartTime()) + .eq(bo.getEndTime() != null, CrmBusinessTripDetails::getEndTime, bo.getEndTime()) + .eq(bo.getDurationDays() != null, CrmBusinessTripDetails::getDurationDays, bo.getDurationDays()) + .eq(bo.getProjectId() != null, CrmBusinessTripDetails::getProjectId, bo.getProjectId()) + .eq(bo.getCustomerId() != null, CrmBusinessTripDetails::getCustomerId, bo.getCustomerId()) + .like(StringUtils.isNotBlank(bo.getMeetingName()), CrmBusinessTripDetails::getMeetingName, bo.getMeetingName()) + .eq(StringUtils.isNotBlank(bo.getExchangePurpose()), CrmBusinessTripDetails::getExchangePurpose, bo.getExchangePurpose()) + .eq(StringUtils.isNotBlank(bo.getExchangeFeedback()), CrmBusinessTripDetails::getExchangeFeedback, bo.getExchangeFeedback()) + .eq(StringUtils.isNotBlank(bo.getTripReason()), CrmBusinessTripDetails::getTripReason, bo.getTripReason()) + .eq(StringUtils.isNotBlank(bo.getOssId()), CrmBusinessTripDetails::getOssId, bo.getOssId()) +; + return lqw; + } + + /** + * 新增出差申请明细 + * + * @param bo 出差申请明细 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(CrmBusinessTripDetailsBo bo) { + CrmBusinessTripDetails add = MapstructUtils.convert(bo, CrmBusinessTripDetails.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setTripDetailsId(add.getTripDetailsId()); + } + return flag; + } + + /** + * 修改出差申请明细 + * + * @param bo 出差申请明细 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(CrmBusinessTripDetailsBo bo) { + CrmBusinessTripDetails update = MapstructUtils.convert(bo, CrmBusinessTripDetails.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(CrmBusinessTripDetails entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 校验并批量删除出差申请明细信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteByIds(ids) > 0; + } +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/crm/CrmBusinessTripDetailsMapper.xml b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/crm/CrmBusinessTripDetailsMapper.xml new file mode 100644 index 00000000..b914e8e6 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/crm/CrmBusinessTripDetailsMapper.xml @@ -0,0 +1,14 @@ + + + + + + + + +