feat(crm/crmMailingApply): 新增并实现邮寄申请模块业务逻辑

- 添加邮件申请实体类 CrmMailingApply,定义完整字段及逻辑删除标志
- 新增业务对象 CrmMailingApplyBo,支持校验注解和自动映射
- 实现 CrmMailingApplyController,提供增删改查及审批流程接口
- 编写 Mapper 接口及 XML 映射,支持复杂查询和批量操作
- 完成业务层 CrmMailingApplyServiceImpl,含分页查询、条件构造、数据校验
- 集成工作流启动与监听,确保审批流程正常触发与流程变量处理
- 对发货单明细表格渲染策略文档注释进行详细补充说明
- 增加请求参数校验及防重提交注解保障接口安全与幂等性
dev
zangch@mesnac.com 2 months ago
parent 5e9c90e3bf
commit bb41043301

@ -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<CrmMailingApplyVo> 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<CrmMailingApplyVo> list = crmMailingApplyService.queryList(bo);
ExcelUtil.exportExcel(list, "邮寄申请", CrmMailingApplyVo.class, response);
}
/**
*
*
* @param mailingApplyId
*/
@SaCheckPermission("oa/crm:crmMailingApply:query")
@GetMapping("/{mailingApplyId}")
public R<CrmMailingApplyVo> 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<CrmMailingApplyVo> 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<Void> 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<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable("mailingApplyIds") Long[] mailingApplyIds) {
return toAjax(crmMailingApplyService.deleteWithValidByIds(List.of(mailingApplyIds), true));
}
/**
*
*/
@GetMapping("/getCrmMailingApplyList")
public R<List<CrmMailingApplyVo>> getCrmMailingApplyList(CrmMailingApplyBo bo) {
List<CrmMailingApplyVo> list = crmMailingApplyService.queryList(bo);
return R.ok(list);
}
/**
*
*/
@SaCheckPermission("oa/crm:crmMailingApply:add")
@Log(title = "邮寄申请", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/submitAndFlowStart")
public R<CrmMailingApplyVo> 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<CrmMailingApplyVo> updateSignTime(@RequestBody CrmMailingApplyBo bo) {
if (bo == null || bo.getMailingApplyId() == null) {
throw new ServiceException("邮寄申请ID不能为空");
}
return R.ok(crmMailingApplyService.updateSignTime(bo.getMailingApplyId(), bo.getSignTime()));
}
}

@ -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;
}

@ -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<String, Object> variables;
/**
*
*/
private RemoteFlowInstanceBizExt bizExt;
public Map<String, Object> 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;
}
}

@ -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;
}

@ -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<CrmMailingApply, CrmMailingApplyVo> {
/**
*
*
* @param page
* @param queryWrapper
* @return
*/
@DataPermission({
@DataColumn(key = "deptName", value = "t.create_dept"),
@DataColumn(key = "userName", value = "t.create_by")
})
Page<CrmMailingApplyVo> selectCustomCrmMailingApplyVoList(@Param("page") Page<CrmMailingApplyVo> page, @Param(Constants.WRAPPER) MPJLambdaWrapper<CrmMailingApply> queryWrapper);
/**
*
*
* @param queryWrapper
* @return
*/
@DataPermission({
@DataColumn(key = "deptName", value = "t.create_dept"),
@DataColumn(key = "userName", value = "t.create_by")
})
List<CrmMailingApplyVo> selectCustomCrmMailingApplyVoList(@Param(Constants.WRAPPER) MPJLambdaWrapper<CrmMailingApply> queryWrapper);
/**
* ID
*
* @param mailingApplyId ID
* @return
*/
CrmMailingApplyVo selectCustomCrmMailingApplyVoById(@Param("mailingApplyId") Long mailingApplyId);
/**
* ID
*
* @param ids ID
* @return
*/
List<CrmMailingApplyVo> selectCustomCrmMailingApplyVoByIds(@Param("ids") Collection<Long> ids);
/**
*
*
* @param queryWrapper
* @return
*/
Long countCustomCrmMailingApply(@Param(Constants.WRAPPER) Wrapper<CrmMailingApply> queryWrapper);
/**
*
*
* @param page
* @param queryWrapper
* @return
*/
Page<CrmMailingApplyVo> selectCustomCrmMailingApplyVoPage(@Param("page") Page<CrmMailingApplyVo> page, @Param(Constants.WRAPPER) Wrapper<CrmMailingApply> queryWrapper);
/**
*
*
* @param list
* @return
*/
int batchInsertCrmMailingApply(@Param("list") List<CrmMailingApply> list);
/**
*
*
* @param list
* @return
*/
int batchUpdateCrmMailingApply(@Param("list") List<CrmMailingApply> list);
/**
*
*
* @param queryWrapper
* @return
*/
int deleteCustomCrmMailingApply(@Param(Constants.WRAPPER) Wrapper<CrmMailingApply> queryWrapper);
/**
* ID
*
* @param ids ID
* @return
*/
int deleteCustomCrmMailingApplyByIds(@Param("ids") Collection<Long> ids);
/**
*
*
* @param queryWrapper
* @return
*/
Boolean existsCrmMailingApply(@Param(Constants.WRAPPER) Wrapper<CrmMailingApply> queryWrapper);
}

@ -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<CrmMailingApplyVo> queryPageList(CrmMailingApplyBo bo, PageQuery pageQuery);
/**
*
*
* @param bo
* @return
*/
List<CrmMailingApplyVo> 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<Long> ids, Boolean isValid);
/**
*
*
* @param bo
* @return VO
*/
CrmMailingApplyVo mailingApplySubmitAndFlowStart(CrmMailingApplyBo bo);
/**
*
*
* @param mailingApplyId ID
* @param signTime
* @return VO
*/
CrmMailingApplyVo updateSignTime(Long mailingApplyId, Date signTime);
}

@ -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<CrmMailingApplyVo> queryPageList(CrmMailingApplyBo bo, PageQuery pageQuery) {
MPJLambdaWrapper<CrmMailingApply> lqw = buildQueryWrapper(bo);
Page<CrmMailingApplyVo> result = baseMapper.selectCustomCrmMailingApplyVoList(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
/**
*
*
* @param bo
* @return
*/
@Override
public List<CrmMailingApplyVo> queryList(CrmMailingApplyBo bo) {
MPJLambdaWrapper<CrmMailingApply> lqw = buildQueryWrapper(bo);
return baseMapper.selectCustomCrmMailingApplyVoList(lqw);
}
private MPJLambdaWrapper<CrmMailingApply> buildQueryWrapper(CrmMailingApplyBo bo) {
Map<String, Object> params = bo.getParams();
MPJLambdaWrapper<CrmMailingApply> 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<Long> 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<String, Object> 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);
});
}
}

@ -0,0 +1,294 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.oa.crm.mapper.CrmMailingApplyMapper">
<resultMap type="org.dromara.oa.crm.domain.vo.CrmMailingApplyVo" id="CrmMailingApplyResult">
</resultMap>
<select id="selectCustomCrmMailingApplyVoList" resultMap="CrmMailingApplyResult">
select t.mailing_apply_id, t.mailing_apply_code, t.tenant_id, t.application_date, t.handler_id, t.handler_name,
t.dept_id, d.dept_name, t.province, t.weight, t.mailing_type, t.mailing_fee, t.express_no, t.item_info,
t.project_id, t.project_code, t.project_name, t.oss_id, t.mailing_apply_status, t.flow_status,
t.logistics_status, t.mailing_time, t.sign_time, t.remark, t.del_flag, t.create_dept, t.create_by,
t.create_time, t.update_by, t.update_time
from crm_mailing_apply t
left join sys_dept d on t.dept_id = d.dept_id
${ew.getCustomSqlSegment}
</select>
<!-- 根据ID查询详情 -->
<select id="selectCustomCrmMailingApplyVoById" resultMap="CrmMailingApplyResult">
select t.mailing_apply_id, t.mailing_apply_code, t.tenant_id, t.application_date, t.handler_id, t.handler_name,
t.dept_id, d.dept_name, t.province, t.weight, t.mailing_type, t.mailing_fee, t.express_no, t.item_info,
t.project_id, t.project_code, t.project_name, t.oss_id, t.mailing_apply_status, t.flow_status,
t.logistics_status, t.mailing_time, t.sign_time, t.remark, t.del_flag, t.create_dept, t.create_by,
t.create_time, t.update_by, t.update_time
from crm_mailing_apply t
left join sys_dept d on t.dept_id = d.dept_id
where t.mailing_apply_id = #{mailingApplyId} and t.del_flag = '0'
</select>
<!-- 批量查询 - 根据ID列表 -->
<select id="selectCustomCrmMailingApplyVoByIds" resultMap="CrmMailingApplyResult">
select t.mailing_apply_id, t.mailing_apply_code, t.tenant_id, t.application_date, t.handler_id, t.handler_name, t.dept_id, t.province, t.weight, t.mailing_type, t.mailing_fee, t.express_no, t.item_info, t.project_id, t.project_code, t.project_name, t.oss_id, t.mailing_apply_status, t.flow_status, t.logistics_status, t.mailing_time, t.sign_time, t.remark, t.del_flag, t.create_dept, t.create_by, t.create_time, t.update_by, t.update_time
from crm_mailing_apply t
where t.del_flag = '0' and t.mailing_apply_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- 统计查询 -->
<select id="countCustomCrmMailingApply" resultType="java.lang.Long">
select count(1) from crm_mailing_apply t
${ew.getCustomSqlSegment}
</select>
<!-- 分页查询(带自定义条件) -->
<select id="selectCustomCrmMailingApplyVoPage" resultMap="CrmMailingApplyResult">
select t.mailing_apply_id, t.mailing_apply_code, t.tenant_id, t.application_date, t.handler_id, t.handler_name, t.dept_id, t.province, t.weight, t.mailing_type, t.mailing_fee, t.express_no, t.item_info, t.project_id, t.project_code, t.project_name, t.oss_id, t.mailing_apply_status, t.flow_status, t.logistics_status, t.mailing_time, t.sign_time, t.remark, t.del_flag, t.create_dept, t.create_by, t.create_time, t.update_by, t.update_time
from crm_mailing_apply t
${ew.getCustomSqlSegment}
</select>
<!-- 批量插入 -->
<insert id="batchInsertCrmMailingApply">
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
<foreach collection="list" item="item" separator=",">
(
#{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}
)
</foreach>
</insert>
<!-- 批量更新 -->
<update id="batchUpdateCrmMailingApply">
<foreach collection="list" item="item" separator=";">
update crm_mailing_apply t
<set>
<if test="item.mailingApplyCode != null and item.mailingApplyCode != ''">
t.mailing_apply_code = #{item.mailingApplyCode},
</if>
<if test="item.tenantId != null and item.tenantId != ''">
t.tenant_id = #{item.tenantId},
</if>
<if test="item.applicationDate != null">
t.application_date = #{item.applicationDate},
</if>
<if test="item.handlerId != null">
t.handler_id = #{item.handlerId},
</if>
<if test="item.handlerName != null and item.handlerName != ''">
t.handler_name = #{item.handlerName},
</if>
<if test="item.deptId != null">
t.dept_id = #{item.deptId},
</if>
<if test="item.province != null and item.province != ''">
t.province = #{item.province},
</if>
<if test="item.weight != null">
t.weight = #{item.weight},
</if>
<if test="item.mailingType != null and item.mailingType != ''">
t.mailing_type = #{item.mailingType},
</if>
<if test="item.mailingFee != null">
t.mailing_fee = #{item.mailingFee},
</if>
<if test="item.expressNo != null and item.expressNo != ''">
t.express_no = #{item.expressNo},
</if>
<if test="item.itemInfo != null and item.itemInfo != ''">
t.item_info = #{item.itemInfo},
</if>
<if test="item.projectId != null">
t.project_id = #{item.projectId},
</if>
<if test="item.projectCode != null and item.projectCode != ''">
t.project_code = #{item.projectCode},
</if>
<if test="item.projectName != null and item.projectName != ''">
t.project_name = #{item.projectName},
</if>
<if test="item.ossId != null and item.ossId != ''">
t.oss_id = #{item.ossId},
</if>
<if test="item.mailingApplyStatus != null and item.mailingApplyStatus != ''">
t.mailing_apply_status = #{item.mailingApplyStatus},
</if>
<if test="item.flowStatus != null and item.flowStatus != ''">
t.flow_status = #{item.flowStatus},
</if>
<if test="item.logisticsStatus != null and item.logisticsStatus != ''">
t.logistics_status = #{item.logisticsStatus},
</if>
<if test="item.mailingTime != null">
t.mailing_time = #{item.mailingTime},
</if>
<if test="item.signTime != null">
t.sign_time = #{item.signTime},
</if>
<if test="item.remark != null and item.remark != ''">
t.remark = #{item.remark},
</if>
<if test="item.delFlag != null and item.delFlag != ''">
t.del_flag = #{item.delFlag},
</if>
<if test="item.createDept != null">
t.create_dept = #{item.createDept},
</if>
<if test="item.createBy != null">
t.create_by = #{item.createBy},
</if>
<if test="item.createTime != null">
t.create_time = #{item.createTime},
</if>
<if test="item.updateBy != null">
t.update_by = #{item.updateBy},
</if>
<if test="item.updateTime != null">
t.update_time = #{item.updateTime}
</if>
</set>
where t.mailing_apply_id = #{item.mailingApplyId}
</foreach>
</update>
<!-- 根据自定义条件删除 -->
<delete id="deleteCustomCrmMailingApply">
delete from crm_mailing_apply t
${ew.getCustomSqlSegment}
</delete>
<!-- 根据ID列表批量删除 -->
<delete id="deleteCustomCrmMailingApplyByIds">
delete from crm_mailing_apply t
where t.mailing_apply_id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 检查是否存在 -->
<select id="existsCrmMailingApply" resultType="java.lang.Boolean">
select count(1) > 0 from crm_mailing_apply t
${ew.getCustomSqlSegment}
</select>
</mapper>

@ -19,10 +19,20 @@ import java.util.List;
* poi-tl DynamicTableRenderPolicy Word
* {{detailsTable}}
* <p>
* 使
* 1) Word 5 //// + {{detailsTable}}
* 2) Java List<RowRenderData> data.put("detailsTable", rows)
* 3) 使 Configure.builder().customPolicy("detailsTable", new WmsShippingDetailTablePolicy()).build()
* 使 Controller
* 1) Word 5 //// +
* {{detailsTable}}
* 2) Service {@code List<RowRenderData>} data{@code data.put("detailsTable", rows)}
* 3) Controller 使 {@code Configure.builder().bind("detailsTable", new WmsShippingDetailTablePolicy()).build()}
* {@code WordTemplateUtil.renderToResponse(..., config, response)}
* <p>
*
* -
* - rows null
* <p>
*
* - / {{detailsTable}} bind keydata key 使 Configure
* - ClassCastException data {@code List<RowRenderData>} {@code List<Map<String,Object>>}
*
* @author Yinq
* @date 2025-12-11

Loading…
Cancel
Save