diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmMailingApplyController.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmMailingApplyController.java new file mode 100644 index 00000000..e64fbcf6 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/controller/CrmMailingApplyController.java @@ -0,0 +1,144 @@ +package org.dromara.oa.crm.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +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.oa.crm.domain.bo.CrmMailingApplyBo; +import org.dromara.oa.crm.domain.vo.CrmMailingApplyVo; +import org.dromara.oa.crm.service.ICrmMailingApplyService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 邮寄申请 + * 前端访问路由地址为:/oa/crm/crmMailingApply + * + * @author Yinq + * @date 2025-12-12 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/crm/crmMailingApply") +public class CrmMailingApplyController extends BaseController { + + private final ICrmMailingApplyService crmMailingApplyService; + + /** + * 查询邮寄申请列表 + */ + @SaCheckPermission("oa/crm:crmMailingApply:list") + @GetMapping("/list") + public TableDataInfo list(CrmMailingApplyBo bo, PageQuery pageQuery) { + return crmMailingApplyService.queryPageList(bo, pageQuery); + } + + /** + * 导出邮寄申请列表 + */ + @SaCheckPermission("oa/crm:crmMailingApply:export") + @Log(title = "邮寄申请", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(CrmMailingApplyBo bo, HttpServletResponse response) { + List list = crmMailingApplyService.queryList(bo); + ExcelUtil.exportExcel(list, "邮寄申请", CrmMailingApplyVo.class, response); + } + + /** + * 获取邮寄申请详细信息 + * + * @param mailingApplyId 主键 + */ + @SaCheckPermission("oa/crm:crmMailingApply:query") + @GetMapping("/{mailingApplyId}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable("mailingApplyId") Long mailingApplyId) { + return R.ok(crmMailingApplyService.queryById(mailingApplyId)); + } + + /** + * 新增邮寄申请 + */ + @SaCheckPermission("oa/crm:crmMailingApply:add") + @Log(title = "邮寄申请", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody CrmMailingApplyBo bo) { + crmMailingApplyService.insertByBo(bo); + return R.ok(crmMailingApplyService.queryById(bo.getMailingApplyId())); + } + + /** + * 修改邮寄申请 + */ + @SaCheckPermission("oa/crm:crmMailingApply:edit") + @Log(title = "邮寄申请", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody CrmMailingApplyBo bo) { + return toAjax(crmMailingApplyService.updateByBo(bo)); + } + + /** + * 删除邮寄申请 + * + * @param mailingApplyIds 主键串 + */ + @SaCheckPermission("oa/crm:crmMailingApply:remove") + @Log(title = "邮寄申请", businessType = BusinessType.DELETE) + @DeleteMapping("/{mailingApplyIds}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable("mailingApplyIds") Long[] mailingApplyIds) { + return toAjax(crmMailingApplyService.deleteWithValidByIds(List.of(mailingApplyIds), true)); + } + + /** + * 下拉框查询邮寄申请列表 + */ + @GetMapping("/getCrmMailingApplyList") + public R> getCrmMailingApplyList(CrmMailingApplyBo bo) { + List list = crmMailingApplyService.queryList(bo); + return R.ok(list); + } + + /** + * 提交邮寄申请并发起审批流程 + */ + @SaCheckPermission("oa/crm:crmMailingApply:add") + @Log(title = "邮寄申请", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/submitAndFlowStart") + public R mailingApplySubmitAndFlowStart(@RequestBody CrmMailingApplyBo bo) { + return R.ok(crmMailingApplyService.mailingApplySubmitAndFlowStart(bo)); + } + + /** + * 更新签收时间 + */ + @SaCheckPermission("oa/crm:crmMailingApply:edit") + @Log(title = "邮寄申请", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/signTime") + public R updateSignTime(@RequestBody CrmMailingApplyBo bo) { + if (bo == null || bo.getMailingApplyId() == null) { + throw new ServiceException("邮寄申请ID不能为空"); + } + return R.ok(crmMailingApplyService.updateSignTime(bo.getMailingApplyId(), bo.getSignTime())); + } + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmMailingApply.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmMailingApply.java new file mode 100644 index 00000000..7d1d9ea0 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/CrmMailingApply.java @@ -0,0 +1,150 @@ +package org.dromara.oa.crm.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.math.BigDecimal; +import java.util.Date; + +/** + * 邮寄申请对象 crm_mailing_apply + * + * @author Yinq + * @date 2025-12-12 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("crm_mailing_apply") +public class CrmMailingApply extends TenantEntity { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 邮寄申请ID + */ + @TableId(value = "mailing_apply_id", type = IdType.ASSIGN_ID) + private Long mailingApplyId; + + /** + * 邮寄申请编号(自动生成) + */ + private String mailingApplyCode; + + /** + * 申请日期 + */ + private Date applicationDate; + + /** + * 经手人ID + */ + private Long handlerId; + + /** + * 经手人姓名 + */ + private String handlerName; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 省份 + */ + private String province; + + /** + * 重量(单位:kg) + */ + private BigDecimal weight; + + /** + * 邮寄类型(1寄付-邮费超过80元 2寄付-顺丰特快 3到付 4所有非月结邮寄) + */ + private String mailingType; + + /** + * 邮寄费用(元) + */ + private BigDecimal mailingFee; + + /** + * 快递单号 + */ + private String expressNo; + + /** + * 邮寄物品信息及申请事由 + */ + private String itemInfo; + + /** + * 项目ID + */ + private Long projectId; + + /** + * 项目号 + */ + private String projectCode; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 附件ID + */ + private String ossId; + + /** + * 申请状态(1暂存 2审批中 3已审批 4作废) + */ + private String mailingApplyStatus; + + /** + * 流程状态 + */ + private String flowStatus; + + /** + * 物流状态(1运输中 2派送中 3已签收) + */ + private String logisticsStatus; + + /** + * 邮寄时间 + */ + private Date mailingTime; + + /** + * 签收时间 + */ + private Date signTime; + + /** + * 备注 + */ + private String remark; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private String delFlag; + + /** + * 部门名称(连表查询字段) + */ + @TableField(exist = false) + private String deptName; + + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmMailingApplyBo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmMailingApplyBo.java new file mode 100644 index 00000000..cda9b876 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/bo/CrmMailingApplyBo.java @@ -0,0 +1,186 @@ +package org.dromara.oa.crm.domain.bo; + +import cn.hutool.core.util.ObjectUtil; +import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.oa.crm.domain.CrmMailingApply; +import org.dromara.workflow.api.domain.RemoteFlowInstanceBizExt; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + + +/** + * 邮寄申请业务对象 crm_mailing_apply + * + * @author Yinq + * @date 2025-12-12 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CrmMailingApply.class, reverseConvertGenerate = false) +public class CrmMailingApplyBo extends BaseEntity { + + /** + * 邮寄申请ID + */ + @NotNull(message = "邮寄申请ID不能为空", groups = { EditGroup.class }) + private Long mailingApplyId; + + /** + * 邮寄申请编号(自动生成) + */ + @NotBlank(groups = AddGroup.class) + private String mailingApplyCode; + + /** + * 申请日期 + */ + @NotNull(message = "申请日期不能为空", groups = { AddGroup.class, EditGroup.class }) + private Date applicationDate; + + /** + * 经手人ID + */ + @NotNull(message = "经手人ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long handlerId; + + /** + * 经手人姓名 + */ + private String handlerName; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 省份 + */ + @NotBlank(message = "省份不能为空", groups = { AddGroup.class, EditGroup.class }) + private String province; + + /** + * 重量(单位:kg) + */ + @NotNull(message = "重量(单位:kg)不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal weight; + + /** + * 邮寄类型(1寄付-邮费超过80元 2寄付-顺丰特快 3到付 4所有非月结邮寄) + */ + @NotBlank(message = "邮寄类型不能为空", groups = { AddGroup.class, EditGroup.class }) + private String mailingType; + + /** + * 邮寄费用(元) + */ + @NotNull(message = "邮寄费用(元)不能为空", groups = { AddGroup.class, EditGroup.class }) + private BigDecimal mailingFee; + + /** + * 快递单号 + */ + private String expressNo; + + /** + * 邮寄物品信息及申请事由 + */ + private String itemInfo; + + /** + * 项目ID + */ + private Long projectId; + + /** + * 项目号 + */ + private String projectCode; + + /** + * 项目名称 + */ + private String projectName; + + /** + * 附件ID + */ + private String ossId; + + /** + * 申请状态(1暂存 2审批中 3已审批 4作废) + */ + private String mailingApplyStatus; + + /** + * 流程状态 + */ + private String flowStatus; + + /** + * 物流状态(1运输中 2派送中 3已签收) + */ + private String logisticsStatus; + + /** + * 邮寄时间 + */ + private Date mailingTime; + + /** + * 签收时间 + */ + private Date signTime; + + /** + * 备注 + */ + private String remark; + + /** + * 流程编码 + */ + private String flowCode; + + /** + * 办理人(可不填 用于覆盖当前节点办理人) + */ + private String handler; + + /** + * 流程变量,前端会提交一个元素{'entity': {业务详情数据对象}} + */ + private Map variables; + + /** + * 流程业务扩展信息 + */ + private RemoteFlowInstanceBizExt bizExt; + + public Map getVariables() { + if (variables == null) { + return new HashMap<>(16); + } + variables.entrySet().removeIf(entry -> Objects.isNull(entry.getValue())); + return variables; + } + + public RemoteFlowInstanceBizExt getBizExt() { + if (ObjectUtil.isNull(bizExt)) { + bizExt = new RemoteFlowInstanceBizExt(); + } + return bizExt; + } + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmMailingApplyVo.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmMailingApplyVo.java new file mode 100644 index 00000000..a062e595 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/domain/vo/CrmMailingApplyVo.java @@ -0,0 +1,177 @@ +package org.dromara.oa.crm.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.oa.crm.domain.CrmMailingApply; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + + + +/** + * 邮寄申请视图对象 crm_mailing_apply + * + * @author Yinq + * @date 2025-12-12 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = CrmMailingApply.class) +public class CrmMailingApplyVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 邮寄申请ID + */ + @ExcelProperty(value = "邮寄申请ID") + private Long mailingApplyId; + + /** + * 邮寄申请编号(自动生成) + */ + @ExcelProperty(value = "邮寄申请编号", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "自=动生成") + private String mailingApplyCode; + + /** + * 申请日期 + */ + @ExcelProperty(value = "申请日期") + private Date applicationDate; + + /** + * 经手人ID + */ + @ExcelProperty(value = "经手人ID") + private Long handlerId; + + /** + * 经手人姓名 + */ + @ExcelProperty(value = "经手人姓名") + private String handlerName; + + /** + * 部门ID + */ + @ExcelProperty(value = "部门ID") + private Long deptId; + + /** + * 省份 + */ + @ExcelProperty(value = "省份") + private String province; + + /** + * 重量(单位:kg) + */ + @ExcelProperty(value = "重量", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "单=位:kg") + private BigDecimal weight; + + /** + * 邮寄类型(1寄付-邮费超过80元 2寄付-顺丰特快 3到付 4所有非月结邮寄) + */ + @ExcelProperty(value = "邮寄类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "mailing_type") + private String mailingType; + + /** + * 邮寄费用(元) + */ + @ExcelProperty(value = "邮寄费用", converter = ExcelDictConvert.class) + @ExcelDictFormat(readConverterExp = "元=") + private BigDecimal mailingFee; + + /** + * 快递单号 + */ + @ExcelProperty(value = "快递单号") + private String expressNo; + + /** + * 邮寄物品信息及申请事由 + */ + @ExcelProperty(value = "邮寄物品信息及申请事由") + private String itemInfo; + + /** + * 项目ID + */ + @ExcelProperty(value = "项目ID") + private Long projectId; + + /** + * 项目号 + */ + @ExcelProperty(value = "项目号") + private String projectCode; + + /** + * 项目名称 + */ + @ExcelProperty(value = "项目名称") + private String projectName; + + /** + * 附件ID + */ + @ExcelProperty(value = "附件ID") + private String ossId; + + /** + * 申请状态(1暂存 2审批中 3已审批 4作废) + */ + @ExcelProperty(value = "申请状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "mailing_apply_status") + private String mailingApplyStatus; + + /** + * 流程状态 + */ + @ExcelProperty(value = "流程状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "flow_status") + private String flowStatus; + + /** + * 物流状态(1运输中 2派送中 3已签收) + */ + @ExcelProperty(value = "物流状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "logistics_status") + private String logisticsStatus; + + /** + * 邮寄时间 + */ + @ExcelProperty(value = "邮寄时间") + private Date mailingTime; + + /** + * 签收时间 + */ + @ExcelProperty(value = "签收时间") + private Date signTime; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * 部门名称(连表查询字段) + */ + @ExcelProperty(value = "部门名称") + private String deptName; + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/mapper/CrmMailingApplyMapper.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/mapper/CrmMailingApplyMapper.java new file mode 100644 index 00000000..a7d4b8fe --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/mapper/CrmMailingApplyMapper.java @@ -0,0 +1,124 @@ +package org.dromara.oa.crm.mapper; + +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import org.apache.ibatis.annotations.Param; +import org.dromara.common.mybatis.annotation.DataColumn; +import org.dromara.common.mybatis.annotation.DataPermission; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.dromara.oa.crm.domain.CrmMailingApply; +import org.dromara.oa.crm.domain.vo.CrmMailingApplyVo; + +import java.util.Collection; +import java.util.List; + +/** + * 邮寄申请Mapper接口 + * + * @author Yinq + * @date 2025-12-12 + */ +public interface CrmMailingApplyMapper extends BaseMapperPlus { + + /** + * 查询邮寄申请列表 + * + * @param page 分页 + * @param queryWrapper 条件 + * @return 邮寄申请集合 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "t.create_dept"), + @DataColumn(key = "userName", value = "t.create_by") + }) + Page selectCustomCrmMailingApplyVoList(@Param("page") Page page, @Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper); + + /** + * 查询邮寄申请列表 + * + * @param queryWrapper 条件 + * @return 邮寄申请集合 + */ + @DataPermission({ + @DataColumn(key = "deptName", value = "t.create_dept"), + @DataColumn(key = "userName", value = "t.create_by") + }) + List selectCustomCrmMailingApplyVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper queryWrapper); + + /** + * 根据ID查询邮寄申请详情 + * + * @param mailingApplyId 主键ID + * @return 邮寄申请对象 + */ + CrmMailingApplyVo selectCustomCrmMailingApplyVoById(@Param("mailingApplyId") Long mailingApplyId); + + /** + * 根据ID列表批量查询邮寄申请 + * + * @param ids ID集合 + * @return 邮寄申请集合 + */ + List selectCustomCrmMailingApplyVoByIds(@Param("ids") Collection ids); + + /** + * 统计邮寄申请记录数 + * + * @param queryWrapper 查询条件 + * @return 记录总数 + */ + Long countCustomCrmMailingApply(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 分页查询邮寄申请(自定义条件) + * + * @param page 分页对象 + * @param queryWrapper 查询条件 + * @return 分页结果 + */ + Page selectCustomCrmMailingApplyVoPage(@Param("page") Page page, @Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 批量插入邮寄申请 + * + * @param list 邮寄申请对象集合 + * @return 影响行数 + */ + int batchInsertCrmMailingApply(@Param("list") List list); + + /** + * 批量更新邮寄申请 + * + * @param list 邮寄申请对象集合 + * @return 影响行数 + */ + int batchUpdateCrmMailingApply(@Param("list") List list); + + /** + * 根据自定义条件删除邮寄申请 + * + * @param queryWrapper 删除条件 + * @return 影响行数 + */ + int deleteCustomCrmMailingApply(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + /** + * 根据ID列表批量删除邮寄申请 + * + * @param ids ID集合 + * @return 影响行数 + */ + int deleteCustomCrmMailingApplyByIds(@Param("ids") Collection ids); + + /** + * 检查邮寄申请是否存在 + * + * @param queryWrapper 查询条件 + * @return 是否存在 + */ + Boolean existsCrmMailingApply(@Param(Constants.WRAPPER) Wrapper queryWrapper); + + +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/ICrmMailingApplyService.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/ICrmMailingApplyService.java new file mode 100644 index 00000000..365b75c5 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/ICrmMailingApplyService.java @@ -0,0 +1,86 @@ +package org.dromara.oa.crm.service; + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.oa.crm.domain.bo.CrmMailingApplyBo; +import org.dromara.oa.crm.domain.vo.CrmMailingApplyVo; + +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * 邮寄申请Service接口 + * + * @author Yinq + * @date 2025-12-12 + */ +public interface ICrmMailingApplyService { + + /** + * 查询邮寄申请 + * + * @param mailingApplyId 主键 + * @return 邮寄申请 + */ + CrmMailingApplyVo queryById(Long mailingApplyId); + + /** + * 分页查询邮寄申请列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 邮寄申请分页列表 + */ + TableDataInfo queryPageList(CrmMailingApplyBo bo, PageQuery pageQuery); + + /** + * 查询符合条件的邮寄申请列表 + * + * @param bo 查询条件 + * @return 邮寄申请列表 + */ + List queryList(CrmMailingApplyBo bo); + + /** + * 新增邮寄申请 + * + * @param bo 邮寄申请 + * @return 是否新增成功 + */ + Boolean insertByBo(CrmMailingApplyBo bo); + + /** + * 修改邮寄申请 + * + * @param bo 邮寄申请 + * @return 是否修改成功 + */ + Boolean updateByBo(CrmMailingApplyBo bo); + + /** + * 校验并批量删除邮寄申请信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 提交邮寄申请并发起审批流程 + * + * @param bo 邮寄申请 + * @return 邮寄申请VO + */ + CrmMailingApplyVo mailingApplySubmitAndFlowStart(CrmMailingApplyBo bo); + + /** + * 更新签收时间 + * + * @param mailingApplyId 邮寄申请ID + * @param signTime 签收时间 + * @return 邮寄申请VO + */ + CrmMailingApplyVo updateSignTime(Long mailingApplyId, Date signTime); +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmMailingApplyServiceImpl.java b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmMailingApplyServiceImpl.java new file mode 100644 index 00000000..06c9f735 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/java/org/dromara/oa/crm/service/impl/CrmMailingApplyServiceImpl.java @@ -0,0 +1,333 @@ +package org.dromara.oa.crm.service.impl; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.github.yulichang.toolkit.JoinWrappers; +import com.github.yulichang.wrapper.MPJLambdaWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.apache.seata.spring.annotation.GlobalTransactional; +import org.dromara.common.core.enums.BusinessStatusEnum; +import org.dromara.common.core.enums.OAStatusEnum; +import org.dromara.common.core.exception.ServiceException; +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.common.satoken.utils.LoginHelper; +import org.dromara.common.tenant.helper.TenantHelper; +import org.dromara.oa.crm.domain.CrmMailingApply; +import org.dromara.oa.crm.domain.bo.CrmMailingApplyBo; +import org.dromara.oa.crm.domain.vo.CrmMailingApplyVo; +import org.dromara.oa.crm.mapper.CrmMailingApplyMapper; +import org.dromara.oa.crm.service.ICrmMailingApplyService; +import org.dromara.workflow.api.RemoteWorkflowService; +import org.dromara.workflow.api.domain.RemoteStartProcess; +import org.dromara.workflow.api.event.ProcessEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * 邮寄申请Service业务层处理 + * + * @author Yinq + * @date 2025-12-12 + */ +@RequiredArgsConstructor +@Service +@Slf4j +public class CrmMailingApplyServiceImpl implements ICrmMailingApplyService { + + private final CrmMailingApplyMapper baseMapper; + + @DubboReference(timeout = 30000) + private RemoteWorkflowService remoteWorkflowService; + + /** + * 查询邮寄申请 + * + * @param mailingApplyId 主键 + * @return 邮寄申请 + */ + @Override + public CrmMailingApplyVo queryById(Long mailingApplyId){ + return baseMapper.selectCustomCrmMailingApplyVoById(mailingApplyId); + } + + /** + * 分页查询邮寄申请列表 + * + * @param bo 查询条件 + * @param pageQuery 分页参数 + * @return 邮寄申请分页列表 + */ + @Override + public TableDataInfo queryPageList(CrmMailingApplyBo bo, PageQuery pageQuery) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectCustomCrmMailingApplyVoList(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询符合条件的邮寄申请列表 + * + * @param bo 查询条件 + * @return 邮寄申请列表 + */ + @Override + public List queryList(CrmMailingApplyBo bo) { + MPJLambdaWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectCustomCrmMailingApplyVoList(lqw); + } + + private MPJLambdaWrapper buildQueryWrapper(CrmMailingApplyBo bo) { + Map params = bo.getParams(); + MPJLambdaWrapper lqw = JoinWrappers.lambda(CrmMailingApply.class) + .selectAll(CrmMailingApply.class) + .eq(CrmMailingApply::getDelFlag, "0") + .eq(StringUtils.isNotBlank(bo.getMailingApplyCode()), CrmMailingApply::getMailingApplyCode, bo.getMailingApplyCode()) + .eq(bo.getApplicationDate() != null, CrmMailingApply::getApplicationDate, bo.getApplicationDate()) + .eq(bo.getHandlerId() != null, CrmMailingApply::getHandlerId, bo.getHandlerId()) + .like(StringUtils.isNotBlank(bo.getHandlerName()), CrmMailingApply::getHandlerName, bo.getHandlerName()) + .eq(bo.getDeptId() != null, CrmMailingApply::getDeptId, bo.getDeptId()) + .eq(StringUtils.isNotBlank(bo.getProvince()), CrmMailingApply::getProvince, bo.getProvince()) + .eq(bo.getWeight() != null, CrmMailingApply::getWeight, bo.getWeight()) + .eq(StringUtils.isNotBlank(bo.getMailingType()), CrmMailingApply::getMailingType, bo.getMailingType()) + .eq(bo.getMailingFee() != null, CrmMailingApply::getMailingFee, bo.getMailingFee()) + .eq(StringUtils.isNotBlank(bo.getExpressNo()), CrmMailingApply::getExpressNo, bo.getExpressNo()) + .eq(StringUtils.isNotBlank(bo.getItemInfo()), CrmMailingApply::getItemInfo, bo.getItemInfo()) + .eq(bo.getProjectId() != null, CrmMailingApply::getProjectId, bo.getProjectId()) + .eq(StringUtils.isNotBlank(bo.getProjectCode()), CrmMailingApply::getProjectCode, bo.getProjectCode()) + .like(StringUtils.isNotBlank(bo.getProjectName()), CrmMailingApply::getProjectName, bo.getProjectName()) + .eq(StringUtils.isNotBlank(bo.getOssId()), CrmMailingApply::getOssId, bo.getOssId()) + .eq(StringUtils.isNotBlank(bo.getMailingApplyStatus()), CrmMailingApply::getMailingApplyStatus, bo.getMailingApplyStatus()) + .eq(StringUtils.isNotBlank(bo.getFlowStatus()), CrmMailingApply::getFlowStatus, bo.getFlowStatus()) + .eq(StringUtils.isNotBlank(bo.getLogisticsStatus()), CrmMailingApply::getLogisticsStatus, bo.getLogisticsStatus()) + .eq(bo.getMailingTime() != null, CrmMailingApply::getMailingTime, bo.getMailingTime()) + .eq(bo.getSignTime() != null, CrmMailingApply::getSignTime, bo.getSignTime()); + return lqw; + } + + /** + * 新增邮寄申请 + * + * @param bo 邮寄申请 + * @return 是否新增成功 + */ + @Override + public Boolean insertByBo(CrmMailingApplyBo bo) { + CrmMailingApply add = MapstructUtils.convert(bo, CrmMailingApply.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setMailingApplyId(add.getMailingApplyId()); + } + return flag; + } + + /** + * 修改邮寄申请 + * P0修复:仅草稿状态可修改,防止越权修改已提交/审批中的单据 + * + * @param bo 邮寄申请 + * @return 是否修改成功 + */ + @Override + public Boolean updateByBo(CrmMailingApplyBo bo) { + // P0修复:校验仅草稿状态可修改 + CrmMailingApply existing = baseMapper.selectById(bo.getMailingApplyId()); + if (existing == null) { + throw new ServiceException("邮寄申请不存在"); + } + if (StringUtils.isNotBlank(bo.getMailingApplyCode()) + && !Objects.equals(existing.getMailingApplyCode(), bo.getMailingApplyCode())) { + throw new ServiceException("邮寄申请编号不允许修改"); + } + if (!"1".equals(existing.getMailingApplyStatus())) { + throw new ServiceException("只有草稿状态的邮寄申请才能修改"); + } + CrmMailingApply update = MapstructUtils.convert(bo, CrmMailingApply.class); + // 前端生成编号,后端必须防止被置空或篡改 + update.setMailingApplyCode(existing.getMailingApplyCode()); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(CrmMailingApply entity){ + if (StringUtils.isBlank(entity.getMailingApplyCode())) { + throw new ServiceException("邮寄申请编号不能为空"); + } + Boolean exists = baseMapper.existsCrmMailingApply( + Wrappers.lambdaQuery(CrmMailingApply.class) + .eq(CrmMailingApply::getDelFlag, "0") + .eq(entity.getTenantId() != null, CrmMailingApply::getTenantId, entity.getTenantId()) + .eq(CrmMailingApply::getMailingApplyCode, entity.getMailingApplyCode()) + .ne(entity.getMailingApplyId() != null, CrmMailingApply::getMailingApplyId, entity.getMailingApplyId()) + ); + if (Boolean.TRUE.equals(exists)) { + throw new ServiceException("邮寄申请编号已存在,请重新生成"); + } + } + + /** + * 校验并批量删除邮寄申请信息 + * + * @param ids 待删除的主键集合 + * @param isValid 是否进行有效性校验 + * @return 是否删除成功 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + // 校验只能删除草稿状态的邮寄申请 + for (Long id : ids) { + CrmMailingApply apply = baseMapper.selectById(id); + if (apply != null && !"1".equals(apply.getMailingApplyStatus())) { + throw new ServiceException("只能删除草稿状态的邮寄申请"); + } + } + } + return baseMapper.deleteByIds(ids) > 0; + } + + /** + * 提交邮寄申请并发起审批流程 + * P0修复:提交前先更新业务状态为"审批中",避免状态窗口期被删除/修改 + */ + @Override + @GlobalTransactional(rollbackFor = Exception.class) + public CrmMailingApplyVo mailingApplySubmitAndFlowStart(CrmMailingApplyBo bo) { + if (StringUtils.isBlank(bo.getExpressNo())) { + throw new ServiceException("提交审批前必须填写快递单号"); + } + if (bo.getMailingTime() == null) { + throw new ServiceException("提交审批前必须填写邮寄时间"); + } + if (StringUtils.isBlank(bo.getLogisticsStatus())) { + throw new ServiceException("提交审批前必须选择物流状态"); + } + + // 兼容性处理:如果前端未回传编号且数据库已有编号,则沿用旧值,避免误判“编号为空” + if (bo.getMailingApplyId() != null && StringUtils.isBlank(bo.getMailingApplyCode())) { + CrmMailingApply existing = baseMapper.selectById(bo.getMailingApplyId()); + if (existing == null) { + throw new ServiceException("邮寄申请不存在"); + } + bo.setMailingApplyCode(existing.getMailingApplyCode()); + } + CrmMailingApply add = MapstructUtils.convert(bo, CrmMailingApply.class); + validEntityBeforeSave(add); + + // P0修复:提交前先设置状态为审批中,避免状态窗口期 + bo.setMailingApplyStatus(OAStatusEnum.APPROVING.getStatus()); + bo.setFlowStatus(BusinessStatusEnum.WAITING.getStatus()); + + if (StringUtils.isNull(bo.getMailingApplyId())) { + // 新增时直接插入(不走updateByBo的草稿校验) + CrmMailingApply insert = MapstructUtils.convert(bo, CrmMailingApply.class); + baseMapper.insert(insert); + bo.setMailingApplyId(insert.getMailingApplyId()); + } else { + // 修改时需校验原状态为草稿 + CrmMailingApply existing = baseMapper.selectById(bo.getMailingApplyId()); + if (existing == null) { + throw new ServiceException("邮寄申请不存在"); + } + if (StringUtils.isNotBlank(bo.getMailingApplyCode()) + && !Objects.equals(existing.getMailingApplyCode(), bo.getMailingApplyCode())) { + throw new ServiceException("邮寄申请编号不允许修改"); + } + if (!"1".equals(existing.getMailingApplyStatus())) { + throw new ServiceException("只有草稿状态的邮寄申请才能提交"); + } + CrmMailingApply update = MapstructUtils.convert(bo, CrmMailingApply.class); + // 前端生成编号,后端必须防止被置空或篡改 + update.setMailingApplyCode(existing.getMailingApplyCode()); + baseMapper.updateById(update); + } + + // 后端发起需要忽略权限 + bo.getVariables().put("ignore", true); + RemoteStartProcess startProcess = new RemoteStartProcess(); + startProcess.setBusinessId(bo.getMailingApplyId().toString()); + startProcess.setFlowCode(bo.getFlowCode()); + startProcess.setVariables(bo.getVariables()); + startProcess.setBizExt(bo.getBizExt()); + bo.getBizExt().setBusinessId(startProcess.getBusinessId()); + boolean flagOne = remoteWorkflowService.startCompleteTask(startProcess); + if (!flagOne) { + throw new ServiceException("流程发起异常"); + } + return queryById(bo.getMailingApplyId()); + } + + @Override + public CrmMailingApplyVo updateSignTime(Long mailingApplyId, java.util.Date signTime) { + CrmMailingApply entity = baseMapper.selectById(mailingApplyId); + if (entity == null || !"0".equals(entity.getDelFlag())) { + throw new ServiceException("邮寄申请不存在"); + } + Long userId = LoginHelper.getUserId(); + // 业务约束:签收时间允许“随时更新”,但仍需最小权限控制(创建人/经手人/管理员),避免任意用户篡改。 + if (!LoginHelper.isSuperAdmin() + && !Objects.equals(userId, entity.getCreateBy()) + && !Objects.equals(userId, entity.getHandlerId())) { + throw new ServiceException("无权限更新签收时间"); + } + CrmMailingApply update = new CrmMailingApply(); + update.setMailingApplyId(mailingApplyId); + update.setSignTime(signTime); + // 业务闭环:签收时间一旦确认,物流状态同步置为“已签收” + if (signTime != null) { + update.setLogisticsStatus("3"); + } + baseMapper.updateById(update); + return queryById(mailingApplyId); + } + + /** + * 流程监听器:邮寄申请流程状态变化时更新业务状态 + * 流程编码:CRMMA (CRM Mailing Apply) + */ + @EventListener(condition = "#processEvent.flowCode == 'CRMMA'") + public void processHandler(ProcessEvent processEvent) { + TenantHelper.dynamic(processEvent.getTenantId(), () -> { + log.info("邮寄申请流程监听器执行:{}", processEvent.toString()); + CrmMailingApply mailingApply = baseMapper.selectById(Convert.toLong(processEvent.getBusinessId())); + if (mailingApply == null) { + log.warn("邮寄申请不存在,businessId={}", processEvent.getBusinessId()); + return; + } + mailingApply.setFlowStatus(processEvent.getStatus()); + Map params = processEvent.getParams(); + if (MapUtil.isNotEmpty(params)) { + // 办理人 + String handler = Convert.toStr(params.get("handler")); + } + // 根据流程状态更新业务状态 + if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.WAITING.getStatus())) { + mailingApply.setMailingApplyStatus(OAStatusEnum.APPROVING.getStatus()); + } else if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.FINISH.getStatus())) { + mailingApply.setMailingApplyStatus(OAStatusEnum.COMPLETED.getStatus()); + } else if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.INVALID.getStatus()) + || Objects.equals(processEvent.getStatus(), BusinessStatusEnum.TERMINATION.getStatus())) { + mailingApply.setMailingApplyStatus(OAStatusEnum.INVALID.getStatus()); + } else if (Objects.equals(processEvent.getStatus(), BusinessStatusEnum.BACK.getStatus()) + || Objects.equals(processEvent.getStatus(), BusinessStatusEnum.CANCEL.getStatus())) { + mailingApply.setMailingApplyStatus(OAStatusEnum.DRAFT.getStatus()); + } + baseMapper.updateById(mailingApply); + }); + } +} diff --git a/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/crm/CrmMailingApplyMapper.xml b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/crm/CrmMailingApplyMapper.xml new file mode 100644 index 00000000..c450afc4 --- /dev/null +++ b/ruoyi-modules/ruoyi-oa/src/main/resources/mapper/oa/crm/CrmMailingApplyMapper.xml @@ -0,0 +1,294 @@ + + + + + + + + + + + + + + + + + + + + + + + insert into crm_mailing_apply( + mailing_apply_code, + + tenant_id, + + application_date, + + handler_id, + + handler_name, + + dept_id, + + province, + + weight, + + mailing_type, + + mailing_fee, + + express_no, + + item_info, + + project_id, + + project_code, + + project_name, + + oss_id, + + mailing_apply_status, + + flow_status, + + logistics_status, + + mailing_time, + + sign_time, + + remark, + + del_flag, + + create_dept, + + create_by, + + create_time, + + update_by, + + update_time + + ) + values + + ( + #{item.mailingApplyCode}, + + #{item.tenantId}, + + #{item.applicationDate}, + + #{item.handlerId}, + + #{item.handlerName}, + + #{item.deptId}, + + #{item.province}, + + #{item.weight}, + + #{item.mailingType}, + + #{item.mailingFee}, + + #{item.expressNo}, + + #{item.itemInfo}, + + #{item.projectId}, + + #{item.projectCode}, + + #{item.projectName}, + + #{item.ossId}, + + #{item.mailingApplyStatus}, + + #{item.flowStatus}, + + #{item.logisticsStatus}, + + #{item.mailingTime}, + + #{item.signTime}, + + #{item.remark}, + + #{item.delFlag}, + + #{item.createDept}, + + #{item.createBy}, + + #{item.createTime}, + + #{item.updateBy}, + + #{item.updateTime} + + ) + + + + + + + update crm_mailing_apply t + + + t.mailing_apply_code = #{item.mailingApplyCode}, + + + t.tenant_id = #{item.tenantId}, + + + t.application_date = #{item.applicationDate}, + + + t.handler_id = #{item.handlerId}, + + + t.handler_name = #{item.handlerName}, + + + t.dept_id = #{item.deptId}, + + + t.province = #{item.province}, + + + t.weight = #{item.weight}, + + + t.mailing_type = #{item.mailingType}, + + + t.mailing_fee = #{item.mailingFee}, + + + t.express_no = #{item.expressNo}, + + + t.item_info = #{item.itemInfo}, + + + t.project_id = #{item.projectId}, + + + t.project_code = #{item.projectCode}, + + + t.project_name = #{item.projectName}, + + + t.oss_id = #{item.ossId}, + + + t.mailing_apply_status = #{item.mailingApplyStatus}, + + + t.flow_status = #{item.flowStatus}, + + + t.logistics_status = #{item.logisticsStatus}, + + + t.mailing_time = #{item.mailingTime}, + + + t.sign_time = #{item.signTime}, + + + t.remark = #{item.remark}, + + + t.del_flag = #{item.delFlag}, + + + t.create_dept = #{item.createDept}, + + + t.create_by = #{item.createBy}, + + + t.create_time = #{item.createTime}, + + + t.update_by = #{item.updateBy}, + + + t.update_time = #{item.updateTime} + + + where t.mailing_apply_id = #{item.mailingApplyId} + + + + + + delete from crm_mailing_apply t + ${ew.getCustomSqlSegment} + + + + + delete from crm_mailing_apply t + where t.mailing_apply_id in + + #{id} + + + + + + + + diff --git a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/word/WmsShippingDetailTablePolicy.java b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/word/WmsShippingDetailTablePolicy.java index bd100b77..034fa90b 100644 --- a/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/word/WmsShippingDetailTablePolicy.java +++ b/ruoyi-modules/ruoyi-wms/src/main/java/org/dromara/wms/word/WmsShippingDetailTablePolicy.java @@ -19,10 +19,20 @@ import java.util.List; * 基于 poi-tl 的 DynamicTableRenderPolicy,用于在 Word 模板中动态渲染发货单明细表格。 * 模板中只需放置一个占位符 {{detailsTable}},本策略会自动删除模板行并插入实际明细数据。 *

- * 使用方式: - * 1) Word 模板中画一个 5 列表格(序号/名称/数量/单位/备注),表头行 + 一行模板行(含 {{detailsTable}}) - * 2) Java 代码中将明细数据转换为 List,放入 data.put("detailsTable", rows) - * 3) 使用 Configure.builder().customPolicy("detailsTable", new WmsShippingDetailTablePolicy()).build() 绑定策略 + * 使用方式(与当前 Controller 实现保持一致): + * 1) Word 模板中画一个 5 列表格(序号/名称/数量/单位/备注),至少包含“表头行 + 模板行”。 + * 在“模板行”的任意单元格中放置占位符 {{detailsTable}}。 + * 2) Service 侧将明细数据转换为 {@code List},并放入 data:{@code data.put("detailsTable", rows)}。 + * 3) Controller 侧使用 {@code Configure.builder().bind("detailsTable", new WmsShippingDetailTablePolicy()).build()} 绑定策略, + * 再调用 {@code WordTemplateUtil.renderToResponse(..., config, response)} 输出。 + *

+ * 注意事项: + * - 本策略会删除模板行并插入新行,因此模板行不建议做复杂单元格合并,否则插入行结构可能与预期不一致。 + * - 当 rows 为空或为 null 时:会删除模板行并保留表头(避免用户看到占位符)。 + *

+ * 常见问题: + * - 表格不渲染/仍显示 {{detailsTable}}:检查占位符名称与 bind key、data key 是否一致;并确认使用了带 Configure 的导出方法。 + * - ClassCastException:传入的 data 必须是 {@code List},不要传 {@code List>}。 * * @author Yinq * @date 2025-12-11