diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java index 5a7340095..adcfc345b 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.text.csv.CsvRow; import cn.hutool.core.text.csv.CsvUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.ip.core.Area; import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; @@ -17,6 +18,7 @@ import java.util.Map; import java.util.function.Function; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst; /** * 区域工具类 @@ -74,6 +76,58 @@ public class AreaUtils { return areas.get(id); } + /** + * 获得指定区域对应的编号 + * + * @param path 区域编号 + * @return 编号 + */ + public static Area getArea(String path) { + String[] paths = path.split("/"); + Area area = null; + for (int i = 0; i < paths.length; i++) { + final int finalI = i; + if (area == null) { + area = findFirst(convertList(areas.values(), a -> a), item -> ObjUtil.equal(paths[finalI], item.getName())); + continue; + } + area = findFirst(area.getChildren(), item -> ObjUtil.equal(paths[finalI], item.getName())); + } + return area; + } + + /** + * 获取所有节点的全路径名称如:河南省/石家庄市/新华区 + * + * @param areas 地区树 + * @return 所有节点的全路径名称 + */ + public static List getAllAreaNodePaths(List areas) { + List paths = new ArrayList<>(); + areas.forEach(area -> traverse(area, "", paths)); + return paths; + } + + /** + * 构建一棵树的所有节点的全路径名称,并将其存储为 "祖先/父级/子级" 的形式 + * + * @param node 父节点 + * @param path 全路径名称 + * @param paths 全路径名称列表 + */ + private static void traverse(Area node, String path, List paths) { + if (node == null) { + return; + } + // 构建当前节点的路径 + String currentPath = path.isEmpty() ? node.getName() : path + "/" + node.getName(); + paths.add(currentPath); + // 递归遍历子节点 + for (Area child : node.getChildren()) { + traverse(child, currentPath, paths); + } + } + /** * 格式化区域 * @@ -88,13 +142,13 @@ public class AreaUtils { * 格式化区域 * * 例如说: - * 1. id = “静安区”时:上海 上海市 静安区 - * 2. id = “上海市”时:上海 上海市 - * 3. id = “上海”时:上海 - * 4. id = “美国”时:美国 + * 1. id = “静安区”时:上海 上海市 静安区 + * 2. id = “上海市”时:上海 上海市 + * 3. id = “上海”时:上海 + * 4. id = “美国”时:美国 * 当区域在中国时,默认不显示中国 * - * @param id 区域编号 + * @param id 区域编号 * @param separator 分隔符 * @return 格式化后的区域 */ diff --git a/yudao-framework/yudao-spring-boot-starter-excel/pom.xml b/yudao-framework/yudao-spring-boot-starter-excel/pom.xml index 5280f72f7..b5d5c24a2 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-excel/pom.xml @@ -46,6 +46,11 @@ com.alibaba easyexcel + + cn.iocoder.boot + yudao-spring-boot-starter-biz-ip + provided + diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java new file mode 100644 index 000000000..f218c0b14 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.framework.excel.core.convert; + +import cn.hutool.core.convert.Convert; +import cn.iocoder.yudao.framework.ip.core.Area; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import lombok.extern.slf4j.Slf4j; + +/** + * Excel 数据地区转换器 + * + * @author HUIHUI + */ +@Slf4j +public class AreaConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + // 解析地区编号 + String label = readCellData.getStringValue(); + Area area = AreaUtils.getArea(label); + if (area == null) { + log.error("[convertToJavaData][label({}) 解析不掉]", label); + return null; + } + // 将 value 转换成对应的属性 + Class fieldClazz = contentProperty.getField().getType(); + return Convert.convert(fieldClazz, area.getId()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java new file mode 100644 index 000000000..661db5369 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java @@ -0,0 +1,107 @@ +package cn.iocoder.yudao.framework.excel.core.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import org.apache.poi.hssf.usermodel.HSSFDataValidation; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddressList; + +import java.util.Comparator; +import java.util.List; + +/** + * 基于固定 sheet 实现下拉框 + * + * @author HUIHUI + */ +public class SelectSheetWriteHandler implements SheetWriteHandler { + + private final List>> selectMap; + + private static final char[] ALPHABET = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + + public SelectSheetWriteHandler(List>> selectMap) { + selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错 + this.selectMap = selectMap; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + if (selectMap == null || CollUtil.isEmpty(selectMap)) { + return; + } + // 需要设置下拉框的sheet页 + Sheet curSheet = writeSheetHolder.getSheet(); + DataValidationHelper helper = curSheet.getDataValidationHelper(); + String dictSheetName = "字典sheet"; + Workbook workbook = writeWorkbookHolder.getWorkbook(); + // 数据字典的sheet页 + Sheet dictSheet = workbook.createSheet(dictSheetName); + for (KeyValue> keyValue : selectMap) { + // 设置下拉单元格的首行、末行、首列、末列 + CellRangeAddressList rangeAddressList = new CellRangeAddressList(1, 65533, keyValue.getKey(), keyValue.getKey()); + int rowLen = keyValue.getValue().size(); + // 设置字典sheet页的值 每一列一个字典项 + for (int i = 0; i < rowLen; i++) { + Row row = dictSheet.getRow(i); + if (row == null) { + row = dictSheet.createRow(i); + } + row.createCell(keyValue.getKey()).setCellValue(keyValue.getValue().get(i)); + } + String excelColumn = getExcelColumn(keyValue.getKey()); + // 下拉框数据来源 eg:字典sheet!$B1:$B2 + String refers = dictSheetName + "!$" + excelColumn + "$1:$" + excelColumn + "$" + rowLen; + // 创建可被其他单元格引用的名称 + Name name = workbook.createName(); + // 设置名称的名字 + name.setNameName("dict" + keyValue.getKey()); + // 设置公式 + name.setRefersToFormula(refers); + // 设置引用约束 + DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); + // 设置约束 + DataValidation validation = helper.createValidation(constraint, rangeAddressList); + if (validation instanceof HSSFDataValidation) { + validation.setSuppressDropDownArrow(false); + } else { + validation.setSuppressDropDownArrow(true); + validation.setShowErrorBox(true); + } + // 阻止输入非下拉框的值 + validation.setErrorStyle(DataValidation.ErrorStyle.STOP); + validation.createErrorBox("提示", "此值不存在于下拉选择中!"); + // 添加下拉框约束 + writeSheetHolder.getSheet().addValidationData(validation); + } + } + + /** + * 将数字列转化成为字母列 + * + * @param num 数字 + * @return 字母 + */ + private String getExcelColumn(int num) { + String column = ""; + int len = ALPHABET.length - 1; + int first = num / len; + int second = num % len; + if (num <= len) { + column = ALPHABET[num] + ""; + } else { + column = ALPHABET[first - 1] + ""; + if (second == 0) { + column = column + ALPHABET[len] + ""; + } else { + column = column + ALPHABET[second - 1] + ""; + } + } + return column; + } + +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java index c2104693e..83877ece8 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java @@ -1,11 +1,13 @@ package cn.iocoder.yudao.framework.excel.core.util; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.converters.longconverter.LongStringConverter; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; -import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -21,12 +23,12 @@ public class ExcelUtils { /** * 将列表以 Excel 响应给前端 * - * @param response 响应 - * @param filename 文件名 + * @param response 响应 + * @param filename 文件名 * @param sheetName Excel sheet 名 - * @param head Excel head 头 - * @param data 数据列表哦 - * @param 泛型,保证 head 和 data 类型的一致性 + * @param head Excel head 头 + * @param data 数据列表哦 + * @param 泛型,保证 head 和 data 类型的一致性 * @throws IOException 写入失败的情况 */ public static void write(HttpServletResponse response, String filename, String sheetName, @@ -38,12 +40,37 @@ public class ExcelUtils { .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 .sheet(sheetName).doWrite(data); // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8)); + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name())); + response.setContentType("application/vnd.ms-excel;charset=UTF-8"); + } + + /** + * 将列表以 Excel 响应给前端 + * + * @param response 响应 + * @param filename 文件名 + * @param sheetName Excel sheet 名 + * @param head Excel head 头 + * @param data 数据列表哦 + * @param selectMap 下拉选择数据 Map + * @throws IOException 写入失败的情况 + */ + public static void write(HttpServletResponse response, String filename, String sheetName, + Class head, List data, List>> selectMap) throws IOException { + // 输出 Excel + EasyExcel.write(response.getOutputStream(), head) + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 + .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框 + .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 + .sheet(sheetName).doWrite(data); + // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name())); response.setContentType("application/vnd.ms-excel;charset=UTF-8"); } public static List read(MultipartFile file, Class head) throws IOException { - return EasyExcel.read(file.getInputStream(), head, null) + return EasyExcel.read(file.getInputStream(), head, null) .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 .doReadAllSync(); } diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java index 278989c73..5640d16a2 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java @@ -13,7 +13,7 @@ public interface ErrorCodeConstants { ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); ErrorCode CONTRACT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_001, "合同更新失败,原因:合同不是草稿状态"); ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态"); - ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是处理中状态"); + ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态"); // ========== 线索管理 1-020-001-000 ========== ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); @@ -33,9 +33,14 @@ public interface ErrorCodeConstants { // ========== 回款 1-020-004-000 ========== ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); + ErrorCode RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_004_001, "更新回款失败,原因:禁止编辑"); + ErrorCode RECEIVABLE_DELETE_FAIL = new ErrorCode(1_020_004_002, "删除回款失败,原因: 被回款计划所使用,不允许删除"); + ErrorCode RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_004_003, "回款提交审核失败,原因:回款没处在未提交状态"); + ErrorCode RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_004_004, "更新回款审核状态失败,原因:回款不是审核中状态"); - // ========== 合同管理 1-020-005-000 ========== + // ========== 回款计划 1-020-005-000 ========== ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在"); + ErrorCode RECEIVABLE_PLAN_UPDATE_FAIL = new ErrorCode(1_020_006_000, "更想回款计划失败,原因:{}"); // ========== 客户管理 1_020_006_000 ========== ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在"); diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java index b55594bab..65bdc1b89 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java @@ -141,6 +141,8 @@ public interface LogRecordConstants { String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}"; String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款"; String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; + String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批"; + String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功"; // ======================= CRM_RECEIVABLE_PLAN 回款计划 ======================= diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReturnTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReturnTypeEnum.java new file mode 100644 index 000000000..8aacadce3 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReturnTypeEnum.java @@ -0,0 +1,51 @@ +package cn.iocoder.yudao.module.crm.enums.receivable; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * CRM 回款方式枚举 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum CrmReturnTypeEnum implements IntArrayValuable { + + // 支票 + CHECK(1, "支票"), + // 现金 + CASH(2, "现金"), + // 邮政汇款 + POSTAL_REMITTANCE(3, "邮政汇款"), + // 电汇 + TELEGRAPHIC_TRANSFER(4, "电汇"), + // 网上转账 + ONLINE_TRANSFER(5, "网上转账"), + // 支付宝 + ALIPAY(6, "支付宝"), + // 微信支付 + WECHAT_PAY(7, "微信支付"), + // 其他 + OTHER(8, "其它"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmReturnTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java index e567aeecd..df6e0e625 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; @@ -10,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.ip.core.Area; import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; @@ -19,6 +21,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; @@ -34,6 +37,7 @@ import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -44,6 +48,7 @@ import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; import static java.util.Collections.singletonList; @Tag(name = "管理后台 - CRM 客户") @@ -61,6 +66,8 @@ public class CrmCustomerController { private DeptApi deptApi; @Resource private AdminUserApi adminUserApi; + @Resource + private DictDataApi dictDataApi; @PostMapping("/create") @Operation(summary = "创建客户") @@ -258,7 +265,7 @@ public class CrmCustomerController { .areaId(null).detailAddress("").remark("").build() ); // 输出 - ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list, builderSelectMap()); } @PostMapping("/import") @@ -314,4 +321,21 @@ public class CrmCustomerController { return success(true); } + private List>> builderSelectMap() { + List>> selectMap = new ArrayList<>(); + // 获取地区下拉数据 + Area area = AreaUtils.getArea(Area.ID_CHINA); + selectMap.add(new KeyValue<>(6, AreaUtils.getAllAreaNodePaths(area.getChildren()))); + // 获取客户所属行业 + List customerIndustries = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_INDUSTRY); + selectMap.add(new KeyValue<>(8, customerIndustries)); + // 获取客户等级 + List customerLevels = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_LEVEL); + selectMap.add(new KeyValue<>(9, customerLevels)); + // 获取客户来源 + List customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE); + selectMap.add(new KeyValue<>(10, customerSources)); + return selectMap; + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java index 4b21fff1c..2c39472fd 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; import com.alibaba.excel.annotation.ExcelProperty; import lombok.AllArgsConstructor; @@ -39,14 +40,12 @@ public class CrmCustomerImportExcelVO { @ExcelProperty("邮箱") private String email; - // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点; - @ExcelProperty("地区编号") + @ExcelProperty(value = "地区", converter = AreaConvert.class) private Integer areaId; @ExcelProperty("详细地址") private String detailAddress; - // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框 @ExcelProperty(value = "所属行业", converter = DictConvert.class) @DictFormat(CRM_CUSTOMER_INDUSTRY) private Integer industryId; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java index f29bc5139..5bbaefb5b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java @@ -5,12 +5,12 @@ import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO; import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; @@ -61,14 +61,14 @@ public class CrmReceivableController { @PostMapping("/create") @Operation(summary = "创建回款") @PreAuthorize("@ss.hasPermission('crm:receivable:create')") - public CommonResult createReceivable(@Valid @RequestBody CrmReceivableCreateReqVO createReqVO) { - return success(receivableService.createReceivable(createReqVO, getLoginUserId())); + public CommonResult createReceivable(@Valid @RequestBody CrmReceivableSaveReqVO createReqVO) { + return success(receivableService.createReceivable(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新回款") @PreAuthorize("@ss.hasPermission('crm:receivable:update')") - public CommonResult updateReceivable(@Valid @RequestBody CrmReceivableUpdateReqVO updateReqVO) { + public CommonResult updateReceivable(@Valid @RequestBody CrmReceivableSaveReqVO updateReqVO) { receivableService.updateReceivable(updateReqVO); return success(true); } @@ -88,7 +88,7 @@ public class CrmReceivableController { @PreAuthorize("@ss.hasPermission('crm:receivable:query')") public CommonResult getReceivable(@RequestParam("id") Long id) { CrmReceivableDO receivable = receivableService.getReceivable(id); - return success(CrmReceivableConvert.INSTANCE.convert(receivable)); + return success(BeanUtils.toBean(receivable, CrmReceivableRespVO.class)); } @GetMapping("/page") @@ -152,4 +152,12 @@ public class CrmReceivableController { return success(receivableService.getCheckReceivablesCount(getLoginUserId())); } + @PutMapping("/submit") + @Operation(summary = "提交回款审批") + @PreAuthorize("@ss.hasPermission('crm:receivable:submit')") + public CommonResult submitContract(@RequestParam("id") Long id) { + receivableService.submitReceivable(id, getLoginUserId()); + return success(true); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java index 6144ae6d3..2500c6f15 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java @@ -5,12 +5,12 @@ import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO; import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; @@ -67,14 +67,14 @@ public class CrmReceivablePlanController { @PostMapping("/create") @Operation(summary = "创建回款计划") @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')") - public CommonResult createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) { - return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId())); + public CommonResult createReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO createReqVO) { + return success(receivablePlanService.createReceivablePlan(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新回款计划") @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')") - public CommonResult updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanUpdateReqVO updateReqVO) { + public CommonResult updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO updateReqVO) { receivablePlanService.updateReceivablePlan(updateReqVO); return success(true); } @@ -94,7 +94,7 @@ public class CrmReceivablePlanController { @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") public CommonResult getReceivablePlan(@RequestParam("id") Long id) { CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id); - return success(CrmReceivablePlanConvert.INSTANCE.convert(receivablePlan)); + return success(BeanUtils.toBean(receivablePlan, CrmReceivablePlanRespVO.class)); } @GetMapping("/page") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java deleted file mode 100644 index 70272b8e8..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -/** - * 回款计划 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class CrmReceivablePlanBaseVO { - - @Schema(description = "期数", example = "1") - private Integer period; - - @Schema(description = "回款计划编号", example = "19852") - private Long receivableId; - - @Schema(description = "计划回款金额", example = "29675") - private Integer price; - - @Schema(description = "计划回款日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime returnTime; - - @Schema(description = "提前几天提醒") - private Integer remindDays; - - @Schema(description = "提醒日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime remindTime; - - @Schema(description = "客户名称", example = "18026") - private Long customerId; - - @Schema(description = "合同编号", example = "3473") - private Long contractId; - - // TODO @liuhongfeng:负责人编号 - @Schema(description = "负责人编号", example = "17828") - private Long ownerUserId; - - @Schema(description = "显示顺序") - private Integer sort; - - @Schema(description = "备注", example = "备注") - private String remark; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java deleted file mode 100644 index 193a44bf4..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; - -import lombok.*; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "管理后台 - CRM 回款计划创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivablePlanCreateReqVO extends CrmReceivablePlanBaseVO { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java index d5e9de187..9e6d1a03f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java @@ -1,18 +1,51 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; + +import java.math.BigDecimal; import java.time.LocalDateTime; @Schema(description = "管理后台 - CRM 回款计划 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO { +public class CrmReceivablePlanRespVO { - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") private Long id; + @Schema(description = "回款编号", example = "19852") + private Long receivableId; + + @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer period; + + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + private BigDecimal price; + + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + private LocalDateTime returnTime; + + @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer remindDays; + + @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + private LocalDateTime remindTime; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long customerId; + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long contractId; + + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long ownerUserId; + + @Schema(description = "显示顺序") + private Integer sort; + + @Schema(description = "备注", example = "备注") + private String remark; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; @@ -34,7 +67,7 @@ public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO { @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") private Boolean finishStatus; - @Schema(description = "回款方式", example = "1") // 来自 Receivable 的 returnType 字段 - private Integer returnType; + @Schema(description = "回款方式", example = "1") + private Integer returnType; // 来自 Receivable 的 returnType 字段 } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java new file mode 100644 index 000000000..235daf328 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 回款计划新增/修改 Request VO") +@Data +public class CrmReceivablePlanSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long id; + + @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "期数不能为空") + private Integer period; + + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + @NotNull(message = "计划回款金额不能为空") + private BigDecimal price; + + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + @NotNull(message = "计划回款日期不能为空") + private LocalDateTime returnTime; + + @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "提前几天提醒不能为空") + private Integer remindDays; + + @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + @NotNull(message = "提醒日期不能为空") + private LocalDateTime remindTime; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "客户编号不能为空") + private Long customerId; + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "合同编号不能为空") + private Long contractId; + + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "负责人编号不能为空") + private Long ownerUserId; + + @Schema(description = "显示顺序") + private Integer sort; + + @Schema(description = "备注", example = "备注") + private String remark; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java deleted file mode 100644 index 2e83a1cbb..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - CRM 回款计划更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivablePlanUpdateReqVO extends CrmReceivablePlanBaseVO { - - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") - @NotNull(message = "ID不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java deleted file mode 100644 index c32bc9ad5..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -/** - * 回款 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class CrmReceivableBaseVO { - - @Schema(description = "回款编号",requiredMode = Schema.RequiredMode.REQUIRED, example = "31177") - private String no; - - // TODO @liuhongfeng:回款计划编号 - @Schema(description = "回款计划", example = "31177") - private Long planId; - - // TODO @liuhongfeng:客户编号 - @Schema(description = "客户名称", example = "4963") - private Long customerId; - - // TODO @liuhongfeng:客户编号 - @Schema(description = "合同名称", example = "30305") - private Long contractId; - - // TODO @liuhongfeng:这个字段,应该不是前端传递的噢,而是后端自己生成的 - @Schema(description = "审批状态", example = "1") - @InEnum(CrmAuditStatusEnum.class) - private Integer checkStatus; - - @Schema(description = "回款日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime returnTime; - - @Schema(description = "回款方式", example = "2") - private Integer returnType; - - @Schema(description = "回款金额,单位:分", example = "31859") - private Integer price; - - // TODO @liuhongfeng:负责人编号 - @Schema(description = "负责人", example = "22202") - private Long ownerUserId; - - @Schema(description = "显示顺序") - private Integer sort; - - @Schema(description = "备注", example = "备注") - private String remark; - - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java deleted file mode 100644 index 4471b780a..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; - -import lombok.*; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "管理后台 - CRM 回款创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivableCreateReqVO extends CrmReceivableBaseVO { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java index 7c536bd51..2bb721b46 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java @@ -1,19 +1,49 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; + +import java.math.BigDecimal; import java.time.LocalDateTime; // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能 @Schema(description = "管理后台 - CRM 回款 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivableRespVO extends CrmReceivableBaseVO { +public class CrmReceivableRespVO { - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") + @Schema(description = "编号", example = "25787") private Long id; + @Schema(description = "回款编号", example = "31177") + private String no; + + @Schema(description = "回款计划编号", example = "1024") + private Long planId; + + @Schema(description = "回款方式", example = "2") + private Integer returnType; + + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + private BigDecimal price; + + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + private LocalDateTime returnTime; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long customerId; + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long contractId; + + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long ownerUserId; + + @Schema(description = "显示顺序") + private Integer sort; + + @Schema(description = "备注", example = "备注") + private String remark; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java new file mode 100644 index 000000000..5340b4319 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java @@ -0,0 +1,55 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReturnTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 回款新增/修改 Request VO") +@Data +public class CrmReceivableSaveReqVO { + + @Schema(description = "编号", example = "25787") + private Long id; + + @Schema(description = "回款编号", example = "31177") + private String no; + + @Schema(description = "回款计划编号", example = "1024") + private Long planId; // 不是通过回款计划创建的回款没有回款计划编号 + + @Schema(description = "回款方式", example = "2") + @InEnum(CrmReturnTypeEnum.class) + private Integer returnType; + + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + @NotNull(message = "回款金额不能为空") + private BigDecimal price; + + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + @NotNull(message = "计划回款日期不能为空") + private LocalDateTime returnTime; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "客户编号不能为空") + private Long customerId; + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "合同编号不能为空") + private Long contractId; + + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "负责人编号不能为空") + private Long ownerUserId; + + @Schema(description = "显示顺序") + private Integer sort; + + @Schema(description = "备注", example = "备注") + private String remark; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java deleted file mode 100644 index 0f63978c8..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - CRM 回款更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivableUpdateReqVO extends CrmReceivableBaseVO { - - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") - @NotNull(message = "ID不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java index 3b0c23aae..d53d14fda 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java @@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; @@ -28,12 +26,6 @@ public interface CrmReceivableConvert { CrmReceivableConvert INSTANCE = Mappers.getMapper(CrmReceivableConvert.class); - CrmReceivableDO convert(CrmReceivableCreateReqVO bean); - - CrmReceivableDO convert(CrmReceivableUpdateReqVO bean); - - CrmReceivableRespVO convert(CrmReceivableDO bean); - default PageResult convertPage(PageResult pageResult, Map userMap, List customerList, List contractList) { PageResult voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java index 9b6bb3e82..bdd422e00 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java @@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; @@ -29,12 +27,6 @@ public interface CrmReceivablePlanConvert { CrmReceivablePlanConvert INSTANCE = Mappers.getMapper(CrmReceivablePlanConvert.class); - CrmReceivablePlanDO convert(CrmReceivablePlanCreateReqVO bean); - - CrmReceivablePlanDO convert(CrmReceivablePlanUpdateReqVO bean); - - CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean); - default PageResult convertPage(PageResult pageResult, Map userMap, List customerList, List contractList, List receivableList) { diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java index 842fc96b4..6c1610935 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java @@ -2,11 +2,16 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReturnTypeEnum; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDateTime; /** @@ -33,64 +38,55 @@ public class CrmReceivableDO extends BaseDO { * 回款编号 */ private String no; - // TODO @liuhongfeng:“对应实体”,参考别的模块,关联 {@link TableField.MetaInfo#getJdbcType()} /** - * 回款计划 - * - * TODO @liuhongfeng:这个字段什么时候更新,也可以写下 - * - * 对应实体 {@link CrmReceivablePlanDO} + * 回款计划编号,关联 {@link CrmReceivablePlanDO#getId()} */ private Long planId; /** - * 客户 ID - * - * 对应实体 {@link cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO} + * 客户编号,关联 {@link CrmCustomerDO#getId()} */ private Long customerId; /** - * 合同 ID - * - * 对应实体 {@link CrmContractDO} + * 合同编号,关联 {@link CrmContractDO#getId()} */ private Long contractId; /** - * 工作流编号 - * - * TODO @liuhongfeng:这个字段,后续要写下关联的实体哈 + * 负责人编号,关联 {@link AdminUserRespDTO#getId()} */ - private Long processInstanceId; + private Long ownerUserId; + /** * 回款日期 */ private LocalDateTime returnTime; - // TODO @liuhongfeng:少个枚举 /** - * 回款方式 + * 回款方式,关联枚举{@link CrmReturnTypeEnum} */ private Integer returnType; /** - * 回款金额 + * 计划回款金额,单位:元 */ - private Integer price; - // TODO @liuhongfeng:少关联实体; - /** - * 负责人 - */ - private Long ownerUserId; + private BigDecimal price; /** * 显示顺序 */ private Integer sort; - /** - * 审核状态 - * - * 枚举 {@link cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum} - */ - private Integer auditStatus; /** * 备注 */ private String remark; + /** + * 工作流编号 + * + * 关联 ProcessInstance 的 id 属性 + */ + private String processInstanceId; + /** + * 审批状态 + * + * 枚举 {@link CrmAuditStatusEnum} + */ + private Integer auditStatus; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java index 78658e609..80a04ece7 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java @@ -1,11 +1,15 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDateTime; /** @@ -24,7 +28,7 @@ import java.time.LocalDateTime; public class CrmReceivablePlanDO extends BaseDO { /** - * ID + * 编号 */ @TableId private Long id; @@ -33,19 +37,13 @@ public class CrmReceivablePlanDO extends BaseDO { */ private Integer period; /** - * 回款ID - * - * TODO @liuhongfeng:少关联实体; + * 回款编号,关联 {@link CrmReceivableDO#getId()} */ private Long receivableId; /** - * 完成状态 + * 计划回款金额,单位:元 */ - private Boolean finishStatus; - /** - * 计划回款金额,单位:分 - */ - private Integer price; + private BigDecimal price; /** * 计划回款日期 */ @@ -59,21 +57,15 @@ public class CrmReceivablePlanDO extends BaseDO { */ private LocalDateTime remindTime; /** - * 客户 ID - * - * TODO @liuhongfeng:少关联实体; + * 客户编号,关联 {@link CrmCustomerDO#getId()} */ private Long customerId; /** - * 合同 ID - * - * TODO @liuhongfeng:少关联实体; + * 合同编号,关联 {@link CrmContractDO#getId()} */ private Long contractId; /** - * 负责人 ID - * - * TODO @liuhongfeng:少关联实体; + * 负责人编号,关联 {@link AdminUserRespDTO#getId()} */ private Long ownerUserId; /** @@ -85,4 +77,9 @@ public class CrmReceivablePlanDO extends BaseDO { */ private String remark; + /** + * 完成状态 + */ + private Boolean finishStatus; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java index 119af7b37..9862048f5 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java @@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; import java.time.LocalDateTime; @@ -25,12 +24,6 @@ import java.util.List; @Mapper public interface CrmReceivablePlanMapper extends BaseMapperX { - default int updateOwnerUserIdById(Long id, Long ownerUserId) { - return update(new LambdaUpdateWrapper() - .eq(CrmReceivablePlanDO::getId, id) - .set(CrmReceivablePlanDO::getOwnerUserId, ownerUserId)); - } - default PageResult selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java index 2100dbd95..dcb892349 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java @@ -10,7 +10,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; -import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; @@ -48,6 +47,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus; /** * CRM 合同 Service 实现类 @@ -303,10 +303,7 @@ public class CrmContractServiceImpl implements CrmContractService { } // 2. 更新合同审批结果 - Integer auditStatus = BpmProcessInstanceResultEnum.APPROVE.getResult().equals(bpmResult) ? CrmAuditStatusEnum.APPROVE.getStatus() - : BpmProcessInstanceResultEnum.REJECT.getResult().equals(bpmResult) ? CrmAuditStatusEnum.REJECT.getStatus() - : BpmProcessInstanceResultEnum.CANCEL.getResult(); - Assert.notNull(auditStatus, "BPM 审批结果({}) 转换失败", bpmResult); + Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); contractMapper.updateById(new CrmContractDO().setId(id).setAuditStatus(auditStatus)); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java index ebc80f25c..7f5854b44 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.crm.service.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; import jakarta.validation.Valid; @@ -24,14 +23,22 @@ public interface CrmReceivablePlanService { * @param createReqVO 创建信息 * @return 编号 */ - Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO, Long userId); + Long createReceivablePlan(@Valid CrmReceivablePlanSaveReqVO createReqVO); /** * 更新回款计划 * * @param updateReqVO 更新信息 */ - void updateReceivablePlan(@Valid CrmReceivablePlanUpdateReqVO updateReqVO); + void updateReceivablePlan(@Valid CrmReceivablePlanSaveReqVO updateReqVO); + + /** + * 更新回款计划关联的回款编号 + * + * @param id 编号 + * @param receivableId 回款编号 + */ + void updateReceivableId(Long id, Long receivableId); /** * 删除回款计划 @@ -84,4 +91,5 @@ public interface CrmReceivablePlanService { * @return 提醒数量 */ Long getRemindReceivablePlanCount(Long userId); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java index d74219a6e..db8ea9e0c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java @@ -5,10 +5,8 @@ import cn.hutool.core.collection.ListUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; @@ -20,22 +18,24 @@ import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import jakarta.annotation.Resource; +import org.hibernate.validator.internal.util.stereotypes.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.Collection; import java.util.List; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; -// TODO @liuhongfeng:参考 CrmReceivableServiceImpl 写的 todo 哈; - /** * 回款计划 Service 实现类 * @@ -48,73 +48,87 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { @Resource private CrmReceivablePlanMapper receivablePlanMapper; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private CrmReceivableService receivableService; @Resource private CrmContractService contractService; @Resource private CrmCustomerService customerService; @Resource private CrmPermissionService permissionService; + @Resource + private AdminUserApi adminUserApi; @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}", success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS) - public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO, Long userId) { - // TODO @liuhongfeng:第几期的计算;基于是 contractId + contractDO 的第几个还款 - // TODO @liuhongfeng contractId:校验合同是否存在 - // 插入 - CrmReceivablePlanDO receivablePlan = CrmReceivablePlanConvert.INSTANCE.convert(createReqVO); - receivablePlan.setFinishStatus(false); - - checkReceivablePlan(receivablePlan); + public Long createReceivablePlan(CrmReceivablePlanSaveReqVO createReqVO) { + // 1.1 校验关联数据是否存在 + validateRelationDataExists(createReqVO); + // 1.2 查验关联合同回款数量 + Long count = receivableService.getReceivableCountByContractId(createReqVO.getContractId()); + int period = (int) (count + 1); + createReqVO.setPeriod(createReqVO.getPeriod() != period ? period : createReqVO.getPeriod()); // 如果期数不对则纠正 + // 2.1 插入 + CrmReceivablePlanDO receivablePlan = BeanUtils.toBean(createReqVO, CrmReceivablePlanDO.class).setId(null).setFinishStatus(false); receivablePlanMapper.insert(receivablePlan); - // 创建数据权限 - permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + // 2.2 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(createReqVO.getOwnerUserId()) .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); - // 4. 记录操作日志上下文 + // 3. 记录操作日志上下文 LogRecordContext.putVariable("receivablePlan", receivablePlan); return receivablePlan.getId(); } - private void checkReceivablePlan(CrmReceivablePlanDO receivablePlan) { - - if (ObjectUtil.isNull(receivablePlan.getContractId())) { - throw exception(CONTRACT_NOT_EXISTS); - } - - CrmContractDO contract = contractService.getContract(receivablePlan.getContractId()); - if (ObjectUtil.isNull(contract)) { - throw exception(CONTRACT_NOT_EXISTS); - } - - CrmCustomerDO customer = customerService.getCustomer(receivablePlan.getCustomerId()); - if (ObjectUtil.isNull(customer)) { - throw exception(CUSTOMER_NOT_EXISTS); - } - - } - @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) - public void updateReceivablePlan(CrmReceivablePlanUpdateReqVO updateReqVO) { - // TODO @liuhongfeng:如果已经有对应的还款,则不允许编辑; - // 校验存在 + public void updateReceivablePlan(CrmReceivablePlanSaveReqVO updateReqVO) { + // 1. 校验存在 + validateRelationDataExists(updateReqVO); CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId()); + if (Objects.nonNull(oldReceivablePlan.getReceivableId())) { // 如果已经有对应的还款,则不允许编辑; + throw exception(RECEIVABLE_PLAN_UPDATE_FAIL, "已经有对应的还款"); + } - // 更新 - CrmReceivablePlanDO updateObj = CrmReceivablePlanConvert.INSTANCE.convert(updateReqVO); + // 2. 更新 + CrmReceivablePlanDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivablePlanDO.class); receivablePlanMapper.updateById(updateObj); // 3. 记录操作日志上下文 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanUpdateReqVO.class)); + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class)); LogRecordContext.putVariable("receivablePlan", oldReceivablePlan); } @Override + public void updateReceivableId(Long id, Long receivableId) { + // 校验存在 + validateReceivablePlanExists(id); + // 更新回款计划 + receivablePlanMapper.updateById(new CrmReceivablePlanDO().setReceivableId(receivableId).setFinishStatus(true)); + } + + private void validateRelationDataExists(CrmReceivablePlanSaveReqVO reqVO) { + adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在 + CrmContractDO contract = contractService.getContract(reqVO.getContractId()); + if (ObjectUtil.isNull(contract)) { // 合同不存在 + throw exception(CONTRACT_NOT_EXISTS); + } + CrmCustomerDO customer = customerService.getCustomer(reqVO.getCustomerId()); + if (ObjectUtil.isNull(customer)) { // 客户不存在 + throw exception(CUSTOMER_NOT_EXISTS); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java index f92db6a4d..8de653a7f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.crm.service.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; import jakarta.validation.Valid; @@ -22,17 +21,24 @@ public interface CrmReceivableService { * 创建回款 * * @param createReqVO 创建信息 - * @param userId 用户编号 * @return 编号 */ - Long createReceivable(@Valid CrmReceivableCreateReqVO createReqVO, Long userId); + Long createReceivable(@Valid CrmReceivableSaveReqVO createReqVO); /** * 更新回款 * * @param updateReqVO 更新信息 */ - void updateReceivable(@Valid CrmReceivableUpdateReqVO updateReqVO); + void updateReceivable(@Valid CrmReceivableSaveReqVO updateReqVO); + + /** + * 更新回款流程审批结果 + * + * @param id 回款编号 + * @param bpmResult BPM 审批结果 + */ + void updateReceivableAuditStatus(Long id, Integer bpmResult); /** * 删除回款 @@ -41,6 +47,14 @@ public interface CrmReceivableService { */ void deleteReceivable(Long id); + /** + * 发起回款审批流程 + * + * @param id 回款编号 + * @param userId 用户编号 + */ + void submitReceivable(Long id, Long userId); + /** * 获得回款 * @@ -86,4 +100,12 @@ public interface CrmReceivableService { */ Long getCheckReceivablesCount(Long userId); + /** + * 根据合同编号,获取合同关联的回款数量 + * + * @param contractId 合同编号 + * @return 数量 + */ + Long getReceivableCountByContractId(Long contractId); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java index d2f7db036..d11bb939d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java @@ -2,14 +2,16 @@ package cn.iocoder.yudao.module.crm.service.receivable; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; +import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; @@ -23,19 +25,24 @@ import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.Collection; import java.util.List; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus; /** * CRM 回款 Service 实现类 @@ -44,8 +51,14 @@ import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; */ @Service @Validated +@Slf4j public class CrmReceivableServiceImpl implements CrmReceivableService { + /** + * BPM 回款审批流程标识 + */ + public static final String RECEIVABLE_APPROVE = "receivable-approve"; + @Resource private CrmReceivableMapper receivableMapper; @@ -57,51 +70,47 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { private CrmReceivablePlanService receivablePlanService; @Resource private CrmPermissionService permissionService; + @Resource + private AdminUserApi adminUserApi; + @Resource + private BpmProcessInstanceApi bpmProcessInstanceApi; @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", success = CRM_RECEIVABLE_CREATE_SUCCESS) - public Long createReceivable(CrmReceivableCreateReqVO createReqVO, Long userId) { - // 插入还款 - CrmReceivableDO receivable = CrmReceivableConvert.INSTANCE.convert(createReqVO); - if (ObjectUtil.isNull(receivable.getAuditStatus())) { - receivable.setAuditStatus(CommonStatusEnum.ENABLE.getStatus()); - } - receivable.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); - - // TODO @liuhongfeng:一般来说,逻辑的写法,是要先检查,后操作 db;所以,你这个 check 应该放到 CrmReceivableDO receivable 之前; - checkReceivable(receivable); - + public Long createReceivable(CrmReceivableSaveReqVO createReqVO) { + // 1. 校验关联数据存在 + validateRelationDataExists(createReqVO); + // 2. 插入还款 + CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); receivableMapper.insert(receivable); // 3. 创建数据权限 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()) - .setBizId(receivable.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 - // TODO @liuhongfeng:需要更新关联的 plan - - // 4. 记录操作日志上下文 + .setBizId(receivable.getId()).setUserId(createReqVO.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 + // 4. 更新关联的回款计划 + if (Objects.nonNull(createReqVO.getPlanId())) { + receivablePlanService.updateReceivableId(receivable.getPlanId(), receivable.getId()); + } + // 5. 记录操作日志上下文 LogRecordContext.putVariable("receivable", receivable); return receivable.getId(); } - // TODO @liuhongfeng:这里的括号要注意排版; - private void checkReceivable(CrmReceivableDO receivable) { - // TODO @liuhongfeng:校验 no 的唯一性 - // TODO @liuhongfeng:这个放在参数校验合适 - if (ObjectUtil.isNull(receivable.getContractId())) { - throw exception(CONTRACT_NOT_EXISTS); - } - - CrmContractDO contract = contractService.getContract(receivable.getContractId()); + private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) { + adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在 + CrmContractDO contract = contractService.getContract(reqVO.getContractId()); if (ObjectUtil.isNull(contract)) { throw exception(CONTRACT_NOT_EXISTS); } - - CrmCustomerDO customer = customerService.getCustomer(receivable.getCustomerId()); + CrmCustomerDO customer = customerService.getCustomer(reqVO.getCustomerId()); if (ObjectUtil.isNull(customer)) { throw exception(CUSTOMER_NOT_EXISTS); } - - CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(receivable.getPlanId()); + if (Objects.isNull(reqVO.getPlanId())) { // 没有回款计划编号则不校验 + return; + } + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(reqVO.getPlanId()); if (ObjectUtil.isNull(receivablePlan)) { throw exception(RECEIVABLE_PLAN_NOT_EXISTS); } @@ -109,46 +118,92 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { } @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", success = CRM_RECEIVABLE_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) - public void updateReceivable(CrmReceivableUpdateReqVO updateReqVO) { - // 校验存在 - CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId()); - // TODO @liuhongfeng:只有在草稿、审核中,可以提交修改 + public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) { + Assert.notNull(updateReqVO.getId(), "回款编号不能为空"); + // 1.1 校验存在 + CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId()); + // 1.2 只有草稿、审批中,可以编辑; + if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), + CrmAuditStatusEnum.PROCESS.getStatus())) { + throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED); + } - // 更新还款 - CrmReceivableDO updateObj = CrmReceivableConvert.INSTANCE.convert(updateReqVO); + // 2. 更新还款 + CrmReceivableDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivableDO.class); receivableMapper.updateById(updateObj); - // TODO @liuhongfeng:需要更新关联的 plan // 3. 记录操作日志上下文 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivable, CrmReceivableUpdateReqVO.class)); - LogRecordContext.putVariable("receivable", oldReceivable); + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class)); + LogRecordContext.putVariable("receivable", receivable); } - // TODO @liuhongfeng:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum + @Override + public void updateReceivableAuditStatus(Long id, Integer bpmResult) { + // 1.1 校验存在 + CrmReceivableDO receivable = validateReceivableExists(id); + // 1.2 只有审批中,可以更新审批结果 + if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { + log.error("[updateReceivableAuditStatus][receivable({}) 不处于审批中,无法更新审批结果({})]", + receivable.getId(), bpmResult); + throw exception(RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); + } - // TODO @liuhongfeng:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum + // 2. 更新回款审批状态 + Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); + receivableMapper.updateById(new CrmReceivableDO().setId(id).setAuditStatus(auditStatus)); + } + + // TODO @liuhongfeng:缺一个取消回款的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = CRM_RECEIVABLE_DELETE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteReceivable(Long id) { - // TODO @liuhongfeng:如果被 CrmReceivablePlanDO 所使用,则不允许删除 // 校验存在 CrmReceivableDO receivable = validateReceivableExists(id); + // 如果被 CrmReceivablePlanDO 所使用,则不允许删除 + if (Objects.nonNull(receivable.getPlanId()) && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) { + throw exception(RECEIVABLE_DELETE_FAIL); + } // 删除 receivableMapper.deleteById(id); // 删除数据权限 - permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); + permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id); // 记录操作日志上下文 LogRecordContext.putVariable("receivable", receivable); } + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_RECEIVABLE_SUBMIT_SUCCESS) + public void submitReceivable(Long id, Long userId) { + // 1. 校验回款是否在审批 + CrmReceivableDO receivable = validateReceivableExists(id); + if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) { + throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT); + } + + // 2. 创建回款审批流程实例 + String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() + .setProcessDefinitionKey(RECEIVABLE_APPROVE).setBusinessKey(String.valueOf(id))); + + // 3. 更新回款工作流编号 + receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId) + .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); + + // 4. 记录日志 + LogRecordContext.putVariable("receivableNo", receivable.getNo()); + } + private CrmReceivableDO validateReceivableExists(Long id) { CrmReceivableDO receivable = receivableMapper.selectById(id); if (receivable == null) { @@ -187,4 +242,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { return receivableMapper.selectCheckReceivablesCount(userId); } + @Override + public Long getReceivableCountByContractId(Long contractId) { + return receivableMapper.selectCount(CrmReceivableDO::getContractId, contractId); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java new file mode 100644 index 000000000..769fc35ad --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.crm.service.receivable.listener; + +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEventListener; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableServiceImpl; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * 回款审批的结果的监听器实现类 + * + * @author HUIHUI + */ +@Component +public class CrmReceivableResultListener extends BpmProcessInstanceResultEventListener { + + @Resource + private CrmReceivableService receivableService; + + @Override + public String getProcessDefinitionKey() { + return CrmReceivableServiceImpl.RECEIVABLE_APPROVE; + } + + @Override + public void onEvent(BpmProcessInstanceResultEvent event) { + receivableService.updateReceivableAuditStatus(Long.parseLong(event.getBusinessKey()), event.getResult()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java new file mode 100644 index 000000000..0169b0d87 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.util; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; + +/** + * CRM 流程工具类 + * + * @author HUIHUI + */ +public class CrmAuditStatusUtils { + + /** + * BPM 审批结果转换 + * + * @param bpmResult BPM 审批结果 + */ + public static Integer convertBpmResultToAuditStatus(Integer bpmResult) { + Assert.isTrue(isEndResult(bpmResult), "BPM 审批结果({}) 转换失败, 流程状态不是最终结果", bpmResult); + Integer auditStatus = BpmProcessInstanceResultEnum.APPROVE.getResult().equals(bpmResult) ? CrmAuditStatusEnum.APPROVE.getStatus() + : BpmProcessInstanceResultEnum.REJECT.getResult().equals(bpmResult) ? CrmAuditStatusEnum.REJECT.getStatus() + : BpmProcessInstanceResultEnum.CANCEL.getResult(); + Assert.notNull(auditStatus, "BPM 审批结果({}) 转换失败", bpmResult); + return auditStatus; + } + + /** + * 判断该结果是否处于 End 最终结果 + * + * @param bpmResult BPM 审批结果 + * @return 是否 + */ + public static boolean isEndResult(Integer bpmResult) { + return ObjectUtils.equalsAny(bpmResult, BpmProcessInstanceResultEnum.APPROVE.getResult(), + BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult()); + } + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java index 107184564..bfc2a1590 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.dict; import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; import java.util.Collection; +import java.util.List; /** * 字典数据 API 接口 @@ -39,4 +40,12 @@ public interface DictDataApi { */ DictDataRespDTO parseDictData(String type, String label); + /** + * 获得字典数据标签列表 + * + * @param dictType 字典类型 + * @return 字典数据标签列表 + */ + List getDictDataLabelList(String dictType); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java index 55313c7cf..f3ddb6927 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java @@ -1,13 +1,18 @@ package cn.iocoder.yudao.module.system.api.dict; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; import cn.iocoder.yudao.module.system.service.dict.DictDataService; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** * 字典数据 API 实现类 @@ -37,4 +42,13 @@ public class DictDataApiImpl implements DictDataApi { return BeanUtils.toBean(dictData, DictDataRespDTO.class); } + @Override + public List getDictDataLabelList(String dictType) { + List dictDataList = dictDataService.getDictDataListByDictType(dictType); + if (CollUtil.isEmpty(dictDataList)) { + return Collections.emptyList(); + } + return convertList(dictDataList, DictDataDO::getLabel); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java index a752476da..ce75c9afe 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java @@ -99,4 +99,12 @@ public interface DictDataService { */ DictDataDO parseDictData(String dictType, String label); + /** + * 获得字典数据列表 + * + * @param dictType 字典类型 + * @return 字典数据列表 + */ + List getDictDataListByDictType(String dictType); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java index 95ef27ef5..7982e2302 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java @@ -11,10 +11,10 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO; import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper; import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.Comparator; import java.util.List; @@ -169,4 +169,9 @@ public class DictDataServiceImpl implements DictDataService { return dictDataMapper.selectByDictTypeAndLabel(dictType, label); } + @Override + public List getDictDataListByDictType(String dictType) { + return dictDataMapper.selectList(DictDataDO::getDictType, dictType); + } + }