Parcourir la source

1、报销信息管理CRUD接口

dongpo il y a 7 mois
Parent
commit
8e7b8c8d43
14 fichiers modifiés avec 678 ajouts et 349 suppressions
  1. 2 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/resources/mapper/stamp/stampinfo/OaStampInfoMapper.xml
  2. 5 0
      yudao-module-finance/yudao-module-expense-api/src/main/java/cn/iocoder/yudao/module/expense/enums/ErrorCodeConstants.java
  3. 5 0
      yudao-module-finance/yudao-module-expense-biz/pom.xml
  4. 120 15
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/ExpenseInfoController.java
  5. 8 40
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoPageReqVO.java
  6. 29 12
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoRespVO.java
  7. 13 47
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoSaveReqVO.java
  8. 9 16
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expenseinfo/ExpenseInfoMapper.java
  9. 2 1
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoService.java
  10. 174 10
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoServiceImpl.java
  11. 95 0
      yudao-module-finance/yudao-module-expense-biz/src/main/resources/mapper/expenseinfo/ExpenseInfoMapper.xml
  12. 200 206
      yudao-module-finance/yudao-module-expense-biz/src/test/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoServiceImplTest.java
  13. 10 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java
  14. 6 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java

+ 2 - 2
yudao-module-bpm/yudao-module-bpm-biz/src/main/resources/mapper/stamp/stampinfo/OaStampInfoMapper.xml

@@ -19,7 +19,7 @@
         LEFT JOIN system_dept sd ON si.dept_id = sd.id and sd.deleted = 0
         WHERE si.deleted = 0
         <if test="page.employeeName != null and page.employeeName != ''">
-            AND ei.nickname like concat('%',#{page.employeeName},'%')
+            AND ei.name like concat('%',#{page.employeeName},'%')
         </if>
         <if test="page.deptId != null">
             AND si.dept_id = #{page.deptId}
@@ -68,7 +68,7 @@
         LEFT JOIN system_dept sd ON si.dept_id = sd.id and sd.deleted = 0
         WHERE si.deleted = 0
         <if test="page.employeeName != null and page.employeeName != ''">
-            AND ei.nickname like concat('%',#{page.employeeName},'%')
+            AND ei.name like concat('%',#{page.employeeName},'%')
         </if>
         <if test="page.deptId != null">
             AND si.dept_id = #{page.deptId}

+ 5 - 0
yudao-module-finance/yudao-module-expense-api/src/main/java/cn/iocoder/yudao/module/expense/enums/ErrorCodeConstants.java

@@ -10,4 +10,9 @@ public interface ErrorCodeConstants {
     ErrorCode EXPENSE_ITEM_NOT_EXISTS = new ErrorCode(1_040_002_001, "报销费用项目信息不存在");
     // ========== 报销信息 1_040_003_001 ==========
     ErrorCode INFO_NOT_EXISTS = new ErrorCode(1_040_003_001, "报销信息不存在");
+    ErrorCode EXPENSE_INFO_OBJ_ITEM_BLANK = new ErrorCode(1_040_003_002, "报销子项费用项目不能为空");
+    ErrorCode EXPENSE_INFO_OBJ_MONEY_ZERO = new ErrorCode(1_040_003_003, "报销子项报销金额不能为空");
+    ErrorCode EXPENSE_INFO_OBJ_START_DATE_NULL = new ErrorCode(1_040_003_004, "报销子项开始日期不能为空");
+    ErrorCode EXPENSE_INFO_OBJ_END_DATE_NULL = new ErrorCode(1_040_003_005, "报销子项结束日期不能为空");
+    ErrorCode EXPENSE_INFO_TOTAL_MONEY_ERROR = new ErrorCode(1_040_003_006, "报销总金额与子项金额之后不相等");
 }

+ 5 - 0
yudao-module-finance/yudao-module-expense-biz/pom.xml

@@ -32,6 +32,11 @@
             <artifactId>yudao-module-system-api</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-employee-api</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!-- 业务组件 -->
         <dependency>

+ 120 - 15
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/ExpenseInfoController.java

@@ -4,18 +4,29 @@ import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.employee.api.EmployeeApi;
+import cn.iocoder.yudao.module.employee.api.dto.EmployeeRespDTO;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoPageReqVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoRespVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoSaveReqVO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoDO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoObjDO;
+import cn.iocoder.yudao.module.expense.dal.dataobject.expenseitem.ExpenseItemDO;
+import cn.iocoder.yudao.module.expense.dal.dataobject.expensetype.ExpenseTypeDO;
 import cn.iocoder.yudao.module.expense.service.expenseinfo.ExpenseInfoService;
+import cn.iocoder.yudao.module.expense.service.expenseitem.ExpenseItemService;
+import cn.iocoder.yudao.module.expense.service.expensetype.ExpenseTypeService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.PostApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
+import org.springdoc.api.annotations.ParameterObject;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
@@ -24,6 +35,9 @@ import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -37,17 +51,34 @@ public class ExpenseInfoController {
     @Resource
     private ExpenseInfoService infoService;
 
+    @Resource
+    private ExpenseTypeService expenseTypeService;
+
+    @Resource
+    private ExpenseItemService expenseItemService;
+
+    @Resource
+    private DeptApi deptApi;
+
+    @Resource
+    private PostApi postApi;
+
+    @Resource
+    private EmployeeApi employeeApi;
+
     @PostMapping("/create")
     @Operation(summary = "创建报销信息")
-    @PreAuthorize("@ss.hasPermission('expense:info:create')")
+    // @PreAuthorize("@ss.hasPermission('expense:info:create')")
     public CommonResult<Long> createInfo(@Valid @RequestBody ExpenseInfoSaveReqVO createReqVO) {
+        createReqVO.setInfoSource("1");
         return success(infoService.createInfo(createReqVO));
     }
 
     @PutMapping("/update")
     @Operation(summary = "更新报销信息")
-    @PreAuthorize("@ss.hasPermission('expense:info:update')")
+    // @PreAuthorize("@ss.hasPermission('expense:info:update')")
     public CommonResult<Boolean> updateInfo(@Valid @RequestBody ExpenseInfoSaveReqVO updateReqVO) {
+        updateReqVO.setInfoSource("1");
         infoService.updateInfo(updateReqVO);
         return success(true);
     }
@@ -55,7 +86,7 @@ public class ExpenseInfoController {
     @DeleteMapping("/delete")
     @Operation(summary = "删除报销信息")
     @Parameter(name = "id", description = "编号", required = true)
-    @PreAuthorize("@ss.hasPermission('expense:info:delete')")
+    // @PreAuthorize("@ss.hasPermission('expense:info:delete')")
     public CommonResult<Boolean> deleteInfo(@RequestParam("id") Long id) {
         infoService.deleteInfo(id);
         return success(true);
@@ -63,32 +94,106 @@ public class ExpenseInfoController {
 
     @GetMapping("/get")
     @Operation(summary = "获得报销信息")
-    @Parameter(name = "id", description = "编号", required = true, example = "1024")
-    @PreAuthorize("@ss.hasPermission('expense:info:query')")
+    @Parameter(name = "id", description = "编号", required = true, example = "1")
+    // @PreAuthorize("@ss.hasPermission('expense:info:query')")
     public CommonResult<ExpenseInfoRespVO> getInfo(@RequestParam("id") Long id) {
+        // 从报销服务中获取报销信息
         ExpenseInfoDO info = infoService.getInfo(id);
-        return success(BeanUtils.toBean(info, ExpenseInfoRespVO.class));
+        // 将报销信息转换为响应视图对象
+        ExpenseInfoRespVO expenseInfoRespVO = BeanUtils.toBean(info, ExpenseInfoRespVO.class);
+
+        // 获取并设置员工信息
+        Long employeeId = info.getEmployeeId();
+        EmployeeRespDTO employee = employeeApi.getEmployeeById(employeeId);
+        if (Objects.nonNull(employee)) {
+            expenseInfoRespVO.setEmployeeName(employee.getName());
+            expenseInfoRespVO.setEmployeePhone(employee.getPhone());
+        }
+
+        // 获取并设置部门信息
+        Long deptId = info.getDeptId();
+        DeptRespDTO dept = deptApi.getDept(deptId);
+        if (Objects.nonNull(dept)) {
+            expenseInfoRespVO.setDeptName(dept.getName());
+        }
+
+        // 获取并设置职位信息
+        Long postId = info.getPostId();
+        PostRespDTO post = postApi.getPost(postId);
+        if (Objects.nonNull(post)) {
+            expenseInfoRespVO.setPosition(post.getName());
+        }
+
+        // 获取并设置报销类型信息
+        Long expenseTypeId = info.getExpenseTypeId();
+        ExpenseTypeDO expenseTypeDO = expenseTypeService.getType(expenseTypeId);
+        if (expenseTypeDO != null) {
+            expenseInfoRespVO.setExpenseTypeName(expenseTypeDO.getName());
+        }
+
+        // 根据状态码设置状态描述
+        String status = info.getStatus();
+        if (Objects.equals(status, "0")) {
+            expenseInfoRespVO.setStatusDesc("已完成");
+        } else if (Objects.equals(status, "1")) {
+            expenseInfoRespVO.setStatusDesc("已作废");
+        } else {
+            expenseInfoRespVO.setStatusDesc("未知");
+        }
+
+        // 根据信息来源码设置信息来源描述
+        String infoSource = info.getInfoSource();
+        if (Objects.equals(infoSource, "0")) {
+            expenseInfoRespVO.setInfoSourceDesc("流程添加");
+        } else if (Objects.equals(infoSource, "1")) {
+            expenseInfoRespVO.setInfoSourceDesc("手动添加");
+        } else {
+            expenseInfoRespVO.setInfoSourceDesc("未知来源");
+        }
+
+        // 获取并设置创建员工信息
+        Long createEmployeeId = info.getCreateEmployeeId();
+        EmployeeRespDTO createEmployee = employeeApi.getEmployeeById(createEmployeeId);
+        if (Objects.nonNull(createEmployee)) {
+            expenseInfoRespVO.setCreateEmployeeName(createEmployee.getName());
+        }
+
+        // 获取报销明细信息并补充报销项目名称
+        List<ExpenseInfoObjDO> expenseInfoObjDOs = infoService.getExpenseInfoObjListByExpenseId(info.getId());
+        List<Long> expenseItemIdList = expenseInfoObjDOs.stream().map(ExpenseInfoObjDO::getExpenseItemId).collect(Collectors.toList());
+        List<ExpenseItemDO> expenseItemDOList = expenseItemService.getExpenseItemList(expenseItemIdList);
+        Map<Long, ExpenseItemDO> longExpenseItemDOMap = CollectionUtils.convertMap(expenseItemDOList, ExpenseItemDO::getId);
+        for (ExpenseInfoObjDO expenseInfoObjDO : expenseInfoObjDOs) {
+            ExpenseItemDO expenseItemDO = longExpenseItemDOMap.get(expenseInfoObjDO.getExpenseItemId());
+            if (Objects.nonNull(expenseItemDO)) {
+                expenseInfoObjDO.setExpenseItemName(expenseItemDO.getName());
+            }
+        }
+        expenseInfoRespVO.setExpenseInfoObjs(expenseInfoObjDOs);
+
+        // 返回成功响应
+        return success(expenseInfoRespVO);
     }
 
     @GetMapping("/page")
     @Operation(summary = "获得报销信息分页")
-    @PreAuthorize("@ss.hasPermission('expense:info:query')")
-    public CommonResult<PageResult<ExpenseInfoRespVO>> getInfoPage(@Valid ExpenseInfoPageReqVO pageReqVO) {
-        PageResult<ExpenseInfoDO> pageResult = infoService.getInfoPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, ExpenseInfoRespVO.class));
+    // @PreAuthorize("@ss.hasPermission('expense:info:query')")
+    public CommonResult<PageResult<ExpenseInfoRespVO>> getInfoPage(@Valid @ParameterObject ExpenseInfoPageReqVO pageReqVO) {
+        PageResult<ExpenseInfoRespVO> infoPage = infoService.getInfoPage(pageReqVO);
+        return success(infoPage);
     }
 
     @GetMapping("/export-excel")
     @Operation(summary = "导出报销信息 Excel")
-    @PreAuthorize("@ss.hasPermission('expense:info:export')")
+    // @PreAuthorize("@ss.hasPermission('expense:info:export')")
     @ApiAccessLog(operateType = EXPORT)
     public void exportInfoExcel(@Valid ExpenseInfoPageReqVO pageReqVO,
               HttpServletResponse response) throws IOException {
         pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        List<ExpenseInfoDO> list = infoService.getInfoPage(pageReqVO).getList();
+        List<ExpenseInfoRespVO> list = infoService.getInfoPage(pageReqVO).getList();
         // 导出 Excel
         ExcelUtils.write(response, "报销信息.xls", "数据", ExpenseInfoRespVO.class,
-                        BeanUtils.toBean(list, ExpenseInfoRespVO.class));
+                        list);
     }
 
     // ==================== 子表(报销信息子) ====================
@@ -96,7 +201,7 @@ public class ExpenseInfoController {
     @GetMapping("/expense-info-obj/list-by-expense-id")
     @Operation(summary = "获得报销信息子列表")
     @Parameter(name = "expenseId", description = "报销主表主键id")
-    @PreAuthorize("@ss.hasPermission('expense:info:query')")
+    // @PreAuthorize("@ss.hasPermission('expense:info:query')")
     public CommonResult<List<ExpenseInfoObjDO>> getExpenseInfoObjListByExpenseId(@RequestParam("expenseId") Long expenseId) {
         return success(infoService.getExpenseInfoObjListByExpenseId(expenseId));
     }

+ 8 - 40
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoPageReqVO.java

@@ -1,11 +1,12 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo;
 
-import lombok.*;
-import java.util.*;
-import io.swagger.v3.oas.annotations.media.Schema;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import java.math.BigDecimal;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
 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;
@@ -16,64 +17,31 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @ToString(callSuper = true)
 public class ExpenseInfoPageReqVO extends PageParam {
 
-    @Schema(description = "uuid", example = "22476")
-    private String expenseUuid;
-
-    @Schema(description = "报销申请人id", example = "27928")
-    private Long employeeId;
-
-    @Schema(description = "报销申请人uuid", example = "29769")
-    private String employeeUuid;
-
     @Schema(description = "报销申请员工姓名", example = "李四")
     private String employeeName;
 
     @Schema(description = "报销申请员工手机号")
     private String employeePhone;
 
-    @Schema(description = "用户账号id", example = "28869")
-    private Long userId;
-
-    @Schema(description = "用户账号uuid", example = "952")
-    private String userUuid;
-
     @Schema(description = "部门id", example = "16541")
     private Long deptId;
 
-    @Schema(description = "部门uuid", example = "32396")
-    private String deptUuid;
-
     @Schema(description = "职位id", example = "15670")
     private Long postId;
 
-    @Schema(description = "员工职位")
-    private String position;
-
     @Schema(description = "报销类型主键id", example = "31388")
     private Long expenseTypeId;
 
-    @Schema(description = "报销类型名称", example = "张三")
-    private String expenseTypeName;
-
-    @Schema(description = "费用所属区间")
-    private String expenseMonth;
-
-    @Schema(description = "总预算金额,单位(元)")
-    private BigDecimal totalMoney;
-
     @Schema(description = "备注")
     private String remarks;
 
-    @Schema(description = "状态(0已完成、1已作废、2已生效)", example = "2")
-    private String status;
-
     @Schema(description = "数据来源,0流程添加、1手动添加")
     private String infoSource;
 
-    @Schema(description = "创建员工id", example = "32302")
-    private Long createEmployeeId;
+    @Schema(description = "创建员工姓名", example = "李四")
+    private String createEmployeeName;
 
-    @Schema(description = "创建时间")
+    @Schema(description = "创建时间", example = "2024-08-06 00:00:00,2024-08-06 23:59:59")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;
 

+ 29 - 12
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoRespVO.java

@@ -1,13 +1,14 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo;
 
+import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoObjDO;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-import java.util.*;
+import lombok.Data;
+
 import java.math.BigDecimal;
-import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
-import com.alibaba.excel.annotation.*;
+import java.util.List;
 
 @Schema(description = "管理后台 - 报销信息 Response VO")
 @Data
@@ -46,13 +47,13 @@ public class ExpenseInfoRespVO {
     @ExcelProperty("用户账号uuid")
     private String userUuid;
 
-    @Schema(description = "部门id", example = "16541")
+    @Schema(description = "部门id")
     @ExcelProperty("部门id")
     private Long deptId;
 
-    @Schema(description = "部门uuid", example = "32396")
-    @ExcelProperty("部门uuid")
-    private String deptUuid;
+    @Schema(description = "部门名称")
+    @ExcelProperty("部门名称")
+    private String deptName;
 
     @Schema(description = "职位id", example = "15670")
     @ExcelProperty("职位id")
@@ -82,20 +83,36 @@ public class ExpenseInfoRespVO {
     @ExcelProperty("备注")
     private String remarks;
 
-    @Schema(description = "状态(0已完成、1已作废、2已生效)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    @Schema(description = "状态(0已完成、1已作废、2已生效)")
     @ExcelProperty("状态(0已完成、1已作废、2已生效)")
     private String status;
 
-    @Schema(description = "数据来源,0流程添加、1手动添加", requiredMode = Schema.RequiredMode.REQUIRED)
+    @Schema(description = "状态描述(0已完成、1已作废、2已生效)")
+    @ExcelProperty("状态描述(0已完成、1已作废、2已生效)")
+    private String statusDesc;
+
+    @Schema(description = "数据来源,0流程添加、1手动添加")
     @ExcelProperty("数据来源,0流程添加、1手动添加")
     private String infoSource;
 
-    @Schema(description = "创建员工id", example = "32302")
+    @Schema(description = "数据来源描述,0流程添加、1手动添加")
+    @ExcelProperty("数据来源描述,0流程添加、1手动添加")
+    private String infoSourceDesc;
+
+    @Schema(description = "创建员工id")
     @ExcelProperty("创建员工id")
     private Long createEmployeeId;
 
+    @Schema(description = "创建员工姓名")
+    @ExcelProperty("创建员工id")
+    private String createEmployeeName;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;
 
+    @Schema(description = "报销信息子列表")
+    private List<ExpenseInfoObjDO> expenseInfoObjs;
+
+
 }

+ 13 - 47
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoSaveReqVO.java

@@ -4,6 +4,8 @@ import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoObj
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
 import java.math.BigDecimal;
@@ -13,70 +15,34 @@ import java.util.List;
 @Data
 public class ExpenseInfoSaveReqVO {
 
-    @Schema(description = "表单主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "30165")
+    @Schema(description = "表单主键")
     private Long id;
 
-    @Schema(description = "uuid", example = "22476")
-    private String expenseUuid;
-
-    @Schema(description = "报销申请人id", example = "27928")
+    @Schema(description = "报销申请人id", example = "1")
+    @NotNull(message = "报销申请人id不能为空")
     private Long employeeId;
 
-    @Schema(description = "报销申请人uuid", example = "29769")
-    private String employeeUuid;
-
-    @Schema(description = "报销申请员工姓名", example = "李四")
-    private String employeeName;
-
-    @Schema(description = "报销申请员工手机号")
-    private String employeePhone;
-
-    @Schema(description = "用户账号id", example = "28869")
-    private Long userId;
-
-    @Schema(description = "用户账号uuid", example = "952")
-    private String userUuid;
-
-    @Schema(description = "部门id", example = "16541")
-    private Long deptId;
-
-    @Schema(description = "部门uuid", example = "32396")
-    private String deptUuid;
-
-    @Schema(description = "职位id", example = "15670")
-    private Long postId;
-
-    @Schema(description = "员工职位")
-    private String position;
-
-    @Schema(description = "报销类型主键id", example = "31388")
+    @Schema(description = "报销类型主键id", example = "1")
+    @NotNull(message = "报销类型主键id不能为空")
     private Long expenseTypeId;
 
-    @Schema(description = "报销类型名称", example = "张三")
-    private String expenseTypeName;
-
-    @Schema(description = "费用所属区间")
+    @Schema(description = "费用所属区间", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-08")
+    @NotBlank(message = "费用所属区间不能为空")
     private String expenseMonth;
 
     @Schema(description = "总预算金额,单位(元)", requiredMode = Schema.RequiredMode.REQUIRED)
     @NotNull(message = "总预算金额,单位(元)不能为空")
+    @Min(value = 0, message = "总预算金额,单位(元)不能为负数")
     private BigDecimal totalMoney;
 
     @Schema(description = "备注")
     private String remarks;
 
-    @Schema(description = "状态(0已完成、1已作废、2已生效)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    @NotEmpty(message = "状态(0已完成、1已作废、2已生效)不能为空")
-    private String status;
-
-    @Schema(description = "数据来源,0流程添加、1手动添加", requiredMode = Schema.RequiredMode.REQUIRED)
-    @NotEmpty(message = "数据来源,0流程添加、1手动添加不能为空")
+    @Schema(description = "数据来源,0流程添加、1手动添加")
     private String infoSource;
 
-    @Schema(description = "创建员工id", example = "32302")
-    private Long createEmployeeId;
-
-    @Schema(description = "报销信息子列表")
+    @Schema(description = "报销信息子列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "报销信息子列表不能为空")
     private List<ExpenseInfoObjDO> expenseInfoObjs;
 
 }

+ 9 - 16
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expenseinfo/ExpenseInfoMapper.java

@@ -1,13 +1,15 @@
 package cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo;
 
-import java.util.*;
-
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoPageReqVO;
+import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoRespVO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoDO;
 import org.apache.ibatis.annotations.Mapper;
-import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.*;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
 
 /**
  * 报销信息 Mapper
@@ -19,27 +21,18 @@ public interface ExpenseInfoMapper extends BaseMapperX<ExpenseInfoDO> {
 
     default PageResult<ExpenseInfoDO> selectPage(ExpenseInfoPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<ExpenseInfoDO>()
-                .eqIfPresent(ExpenseInfoDO::getExpenseUuid, reqVO.getExpenseUuid())
-                .eqIfPresent(ExpenseInfoDO::getEmployeeId, reqVO.getEmployeeId())
-                .eqIfPresent(ExpenseInfoDO::getEmployeeUuid, reqVO.getEmployeeUuid())
                 .likeIfPresent(ExpenseInfoDO::getEmployeeName, reqVO.getEmployeeName())
                 .eqIfPresent(ExpenseInfoDO::getEmployeePhone, reqVO.getEmployeePhone())
-                .eqIfPresent(ExpenseInfoDO::getUserId, reqVO.getUserId())
-                .eqIfPresent(ExpenseInfoDO::getUserUuid, reqVO.getUserUuid())
                 .eqIfPresent(ExpenseInfoDO::getDeptId, reqVO.getDeptId())
-                .eqIfPresent(ExpenseInfoDO::getDeptUuid, reqVO.getDeptUuid())
                 .eqIfPresent(ExpenseInfoDO::getPostId, reqVO.getPostId())
-                .eqIfPresent(ExpenseInfoDO::getPosition, reqVO.getPosition())
                 .eqIfPresent(ExpenseInfoDO::getExpenseTypeId, reqVO.getExpenseTypeId())
-                .likeIfPresent(ExpenseInfoDO::getExpenseTypeName, reqVO.getExpenseTypeName())
-                .eqIfPresent(ExpenseInfoDO::getExpenseMonth, reqVO.getExpenseMonth())
-                .eqIfPresent(ExpenseInfoDO::getTotalMoney, reqVO.getTotalMoney())
                 .eqIfPresent(ExpenseInfoDO::getRemarks, reqVO.getRemarks())
-                .eqIfPresent(ExpenseInfoDO::getStatus, reqVO.getStatus())
                 .eqIfPresent(ExpenseInfoDO::getInfoSource, reqVO.getInfoSource())
-                .eqIfPresent(ExpenseInfoDO::getCreateEmployeeId, reqVO.getCreateEmployeeId())
                 .betweenIfPresent(ExpenseInfoDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(ExpenseInfoDO::getId));
     }
 
+    long selectPageCount(@Param("page") ExpenseInfoPageReqVO pageReqVO);
+
+    List<ExpenseInfoRespVO> selectPageList(@Param("page") ExpenseInfoPageReqVO pageReqVO);
 }

+ 2 - 1
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoService.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.expense.service.expenseinfo;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoPageReqVO;
+import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoRespVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoSaveReqVO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoDO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoObjDO;
@@ -52,7 +53,7 @@ public interface ExpenseInfoService {
      * @param pageReqVO 分页查询
      * @return 报销信息分页
      */
-    PageResult<ExpenseInfoDO> getInfoPage(ExpenseInfoPageReqVO pageReqVO);
+    PageResult<ExpenseInfoRespVO> getInfoPage(ExpenseInfoPageReqVO pageReqVO);
 
     // ==================== 子表(报销信息子) ====================
 

+ 174 - 10
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoServiceImpl.java

@@ -1,8 +1,16 @@
 package cn.iocoder.yudao.module.expense.service.expenseinfo;
 
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.employee.api.EmployeeApi;
+import cn.iocoder.yudao.module.employee.api.dto.EmployeeRespDTO;
+import cn.iocoder.yudao.module.employee.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoPageReqVO;
+import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoRespVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoSaveReqVO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoDO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoObjDO;
@@ -13,10 +21,12 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
+import java.math.BigDecimal;
 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.expense.enums.ErrorCodeConstants.INFO_NOT_EXISTS;
+import static cn.iocoder.yudao.module.expense.enums.ErrorCodeConstants.*;
 
 /**
  * 报销信息 Service 实现类
@@ -32,26 +42,148 @@ public class ExpenseInfoServiceImpl implements ExpenseInfoService {
     @Resource
     private ExpenseInfoObjMapper expenseInfoObjMapper;
 
+    @Resource
+    private EmployeeApi employeeApi;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createInfo(ExpenseInfoSaveReqVO createReqVO) {
-        // 插入
+        // 获取当前登录用户ID
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // 通过用户ID获取登录员工信息
+        EmployeeRespDTO loginEmployee = employeeApi.getEmployeeByUserId(loginUserId);
+        // 获取报销员工ID
+        Long employeeId = createReqVO.getEmployeeId();
+        // 通过员工ID获取员工详细信息
+        EmployeeRespDTO employee = employeeApi.getEmployeeById(employeeId);
+        // 校验员工信息是否存在
+        if (Objects.isNull(loginEmployee) || Objects.isNull(employee)) {
+            throw exception(ErrorCodeConstants.EMPLOYEE_INFO_NOT_EXISTS);
+        }
+
+        // 处理费用明细信息
+        List<ExpenseInfoObjDO> oaExpenseObjs = createReqVO.getExpenseInfoObjs();
+        for (ExpenseInfoObjDO oaExpenseObj : oaExpenseObjs) {
+            // 设置费用明细的创建人
+            oaExpenseObj.setCreator(String.valueOf(loginEmployee.getId()));
+            oaExpenseObj.setMoney(oaExpenseObj.getMoney().setScale(2, BigDecimal.ROUND_HALF_UP));
+            oaExpenseObj.setExpenseObjUuid(IdUtil.fastSimpleUUID());
+
+            // 校验费用项目是否为空
+            if (oaExpenseObj.getExpenseItemId() == null) {
+                throw exception(EXPENSE_INFO_OBJ_ITEM_BLANK);
+            }
+            // 校验费用金额是否有效
+            if (oaExpenseObj.getMoney() == null || oaExpenseObj.getMoney().compareTo(BigDecimal.ZERO) < 1) {
+                throw exception(EXPENSE_INFO_OBJ_MONEY_ZERO);
+            }
+            // 校验开始日期是否为空
+            if (StrUtil.isBlank(oaExpenseObj.getStartDate())) {
+                throw exception(EXPENSE_INFO_OBJ_START_DATE_NULL);
+            }
+            // 校验结束日期是否为空
+            if (StrUtil.isBlank(oaExpenseObj.getEndDate())) {
+                throw exception(EXPENSE_INFO_OBJ_END_DATE_NULL);
+            }
+        }
+
+        // 准备插入费用主表数据
         ExpenseInfoDO info = BeanUtils.toBean(createReqVO, ExpenseInfoDO.class);
+        // 获取所有费用信息的总金额
+        BigDecimal totalMoney = info.getTotalMoney().setScale(2, BigDecimal.ROUND_HALF_UP);
+        // 使用流操作累加oaExpenseObjs中所有ExpenseInfoObjDO对象的金额,并与0进行比较
+        BigDecimal reduce = oaExpenseObjs.stream()
+                .map(ExpenseInfoObjDO::getMoney)
+                .reduce(BigDecimal.ZERO, BigDecimal::add)
+                .setScale(2, BigDecimal.ROUND_HALF_UP);
+        // 比较总金额与累加金额是否一致,如果不一致则抛出异常
+        if (totalMoney.compareTo(reduce) != 0) {
+            throw exception(EXPENSE_INFO_TOTAL_MONEY_ERROR);
+        }
+        info.setTotalMoney(info.getTotalMoney().setScale(2, BigDecimal.ROUND_HALF_UP));
+        // 生成唯一标识符
+        info.setExpenseUuid(IdUtil.fastSimpleUUID());
+        // 设置部门ID、岗位ID、用户ID和创建人信息
+        info.setDeptId(employee.getDeptId());
+        info.setPostId(employee.getPostId());
+        info.setStatus("0");
+        info.setUserId(loginUserId);
+        info.setCreateEmployeeId(loginEmployee.getId());
+        info.setCreator(String.valueOf(loginEmployee.getId()));
+        // 插入费用主表数据
         infoMapper.insert(info);
 
-        // 插入子表
+        // 插入费用明细数据
         createExpenseInfoObjList(info.getId(), createReqVO.getExpenseInfoObjs());
-        // 返回
+
+        // 返回费用主表ID
         return info.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateInfo(ExpenseInfoSaveReqVO updateReqVO) {
+        // 获取当前登录用户ID
+        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
+        // 通过用户ID获取登录员工信息
+        EmployeeRespDTO loginEmployee = employeeApi.getEmployeeByUserId(loginUserId);
+        // 获取报销员工ID
+        Long employeeId = updateReqVO.getEmployeeId();
+        // 通过员工ID获取员工详细信息
+        EmployeeRespDTO employee = employeeApi.getEmployeeById(employeeId);
+        // 校验员工信息是否存在
+        if (Objects.isNull(loginEmployee) || Objects.isNull(employee)) {
+            throw exception(ErrorCodeConstants.EMPLOYEE_INFO_NOT_EXISTS);
+        }
+
+        // 处理费用明细信息
+        List<ExpenseInfoObjDO> oaExpenseObjs = updateReqVO.getExpenseInfoObjs();
+        for (ExpenseInfoObjDO oaExpenseObj : oaExpenseObjs) {
+            // 设置费用明细的创建人
+            oaExpenseObj.setCreator(String.valueOf(loginEmployee.getId()));
+            oaExpenseObj.setMoney(oaExpenseObj.getMoney().setScale(2, BigDecimal.ROUND_HALF_UP));
+            oaExpenseObj.setExpenseObjUuid(IdUtil.fastSimpleUUID());
+
+            // 校验费用项目是否为空
+            if (oaExpenseObj.getExpenseItemId() == null) {
+                throw exception(EXPENSE_INFO_OBJ_ITEM_BLANK);
+            }
+            // 校验费用金额是否有效
+            if (oaExpenseObj.getMoney() == null || oaExpenseObj.getMoney().compareTo(BigDecimal.ZERO) < 1) {
+                throw exception(EXPENSE_INFO_OBJ_MONEY_ZERO);
+            }
+            // 校验开始日期是否为空
+            if (StrUtil.isBlank(oaExpenseObj.getStartDate())) {
+                throw exception(EXPENSE_INFO_OBJ_START_DATE_NULL);
+            }
+            // 校验结束日期是否为空
+            if (StrUtil.isBlank(oaExpenseObj.getEndDate())) {
+                throw exception(EXPENSE_INFO_OBJ_END_DATE_NULL);
+            }
+        }
+
         // 校验存在
-        validateInfoExists(updateReqVO.getId());
+        ExpenseInfoDO expenseInfoBefore = validateInfoExists(updateReqVO.getId());
         // 更新
         ExpenseInfoDO updateObj = BeanUtils.toBean(updateReqVO, ExpenseInfoDO.class);
+
+        // 获取所有费用信息的总金额
+        BigDecimal totalMoney = updateObj.getTotalMoney().setScale(2, BigDecimal.ROUND_HALF_UP);
+        // 使用流操作累加oaExpenseObjs中所有ExpenseInfoObjDO对象的金额,并与0进行比较
+        BigDecimal reduce = oaExpenseObjs.stream()
+                .map(ExpenseInfoObjDO::getMoney)
+                .reduce(BigDecimal.ZERO, BigDecimal::add)
+                .setScale(2, BigDecimal.ROUND_HALF_UP);
+        // 比较总金额与累加金额是否一致,如果不一致则抛出异常
+        if (totalMoney.compareTo(reduce) != 0) {
+            throw exception(EXPENSE_INFO_TOTAL_MONEY_ERROR);
+        }
+
+        // 设置部门ID、岗位ID、用户ID和创建人信息
+        updateObj.setDeptId(employee.getDeptId());
+        updateObj.setPostId(employee.getPostId());
+        updateObj.setStatus("0");
+
         infoMapper.updateById(updateObj);
 
         // 更新子表
@@ -70,10 +202,12 @@ public class ExpenseInfoServiceImpl implements ExpenseInfoService {
         deleteExpenseInfoObjByExpenseId(id);
     }
 
-    private void validateInfoExists(Long id) {
-        if (infoMapper.selectById(id) == null) {
+    private ExpenseInfoDO validateInfoExists(Long id) {
+        ExpenseInfoDO expenseInfoDO = infoMapper.selectById(id);
+        if (expenseInfoDO == null) {
             throw exception(INFO_NOT_EXISTS);
         }
+        return expenseInfoDO;
     }
 
     @Override
@@ -82,8 +216,38 @@ public class ExpenseInfoServiceImpl implements ExpenseInfoService {
     }
 
     @Override
-    public PageResult<ExpenseInfoDO> getInfoPage(ExpenseInfoPageReqVO pageReqVO) {
-        return infoMapper.selectPage(pageReqVO);
+    public PageResult<ExpenseInfoRespVO> getInfoPage(ExpenseInfoPageReqVO pageReqVO) {
+
+        pageReqVO.setPageNo(pageReqVO.getPageNo() - 1);
+
+        long pageCount = 0;
+        if (!Objects.equals(pageReqVO.getPageSize(), PageParam.PAGE_SIZE_NONE)) {
+            pageCount = infoMapper.selectPageCount(pageReqVO);
+        }
+
+        List<ExpenseInfoRespVO> pageList = infoMapper.selectPageList(pageReqVO);
+
+        for (ExpenseInfoRespVO expenseInfoRespVO : pageList) {
+            String status = expenseInfoRespVO.getStatus();
+            if (Objects.equals(status, "0")) {
+                expenseInfoRespVO.setStatusDesc("已完成");
+            } else if (Objects.equals(status, "1")) {
+                expenseInfoRespVO.setStatusDesc("已作废");
+            } else {
+                expenseInfoRespVO.setStatusDesc("未知");
+            }
+
+            String infoSource = expenseInfoRespVO.getInfoSource();
+            if (Objects.equals(infoSource, "0")) {
+                expenseInfoRespVO.setInfoSourceDesc("流程添加");
+            } else if (Objects.equals(infoSource, "1")) {
+                expenseInfoRespVO.setInfoSourceDesc("手动添加");
+            } else {
+                expenseInfoRespVO.setInfoSourceDesc("未知来源");
+            }
+        }
+
+        return new PageResult<>(pageList, pageCount);
     }
 
     // ==================== 子表(报销信息子) ====================
@@ -100,7 +264,7 @@ public class ExpenseInfoServiceImpl implements ExpenseInfoService {
 
     private void updateExpenseInfoObjList(Long expenseId, List<ExpenseInfoObjDO> list) {
         deleteExpenseInfoObjByExpenseId(expenseId);
-		list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
+		list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null).setCreateTime(null).setDeleted(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
         createExpenseInfoObjList(expenseId, list);
     }
 

+ 95 - 0
yudao-module-finance/yudao-module-expense-biz/src/main/resources/mapper/expenseinfo/ExpenseInfoMapper.xml

@@ -9,4 +9,99 @@
         文档可见:https://www.iocoder.cn/MyBatis/x-plugins/
      -->
 
+    <select id="selectPageCount" resultType="java.lang.Long">
+        SELECT COUNT(fei.id)
+        FROM
+        finance_expense_info fei
+        LEFT JOIN employee_info ei ON fei.employee_id = ei.id and ei.deleted = 0
+        LEFT JOIN system_post sp ON sp.id = ei.post_id and sp.deleted = 0
+        LEFT JOIN employee_info ei2 ON fei.create_employee_id = ei2.id and ei2.deleted = 0
+        LEFT JOIN system_dept sd ON fei.dept_id = sd.id and sd.deleted = 0
+        LEFT JOIN finance_expense_type fet ON fei.expense_type_id = fet.id and fet.deleted = 0
+        WHERE fei.deleted = 0
+        <if test="page.employeeName != null and page.employeeName != ''">
+            AND ei.name like concat('%',#{page.employeeName},'%')
+        </if>
+        <if test="page.deptId != null">
+            AND fei.dept_id = #{page.deptId}
+        </if>
+        <if test="page.postId != null">
+            AND fei.id = #{page.postId}
+        </if>
+        <if test="page.employeePhone != null and page.employeePhone != ''">
+            AND ei.phone like concat('%',#{page.employeePhone},'%')
+        </if>
+        <if test="page.expenseTypeId != null">
+            AND fei.expense_type_id = #{page.expenseTypeId}
+        </if>
+        <if test="page.infoSource != null and page.infoSource != ''">
+            AND fei.info_source = #{page.infoSource}
+        </if>
+        <if test="page.createEmployeeName != null and page.createEmployeeName != ''">
+            AND ei2.name like concat('%',#{page.createEmployeeName},'%')
+        </if>
+        <if test="page.createTime != null and page.createTime.length > 0">
+            AND fei.create_time BETWEEN #{page.createTime[0]} AND #{page.createTime[1]}
+        </if>
+    </select>
+
+    <select id="selectPageList"
+            resultType="cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoRespVO">
+        SELECT
+            fei.id,
+            fei.expense_uuid,
+            fei.employee_id,
+            ei.name employeeName,
+            ei.phone employeePhone,
+            fei.post_id,
+            sp.name AS "position",
+            fei.dept_id,
+            sd.`name` deptName,
+            fei.expense_type_id,
+            fet.name expenseTypeName,
+            fei.expense_month,
+            fei.total_money,
+            fei.remarks,
+            fei.status,
+            fei.info_source,
+            fei.create_employee_id,
+            fei.create_time,
+            ei2.name createEmployeeName
+        FROM
+        finance_expense_info fei
+        LEFT JOIN employee_info ei ON fei.employee_id = ei.id and ei.deleted = 0
+        LEFT JOIN system_post sp ON sp.id = ei.post_id and sp.deleted = 0
+        LEFT JOIN employee_info ei2 ON fei.create_employee_id = ei2.id and ei2.deleted = 0
+        LEFT JOIN system_dept sd ON fei.dept_id = sd.id and sd.deleted = 0
+        LEFT JOIN finance_expense_type fet ON fei.expense_type_id = fet.id and fet.deleted = 0
+        WHERE fei.deleted = 0
+        <if test="page.employeeName != null and page.employeeName != ''">
+            AND ei.name like concat('%',#{page.employeeName},'%')
+        </if>
+        <if test="page.deptId != null">
+            AND fei.dept_id = #{page.deptId}
+        </if>
+        <if test="page.postId != null">
+            AND fei.id = #{page.postId}
+        </if>
+        <if test="page.employeePhone != null and page.employeePhone != ''">
+            AND ei.phone like concat('%',#{page.employeePhone},'%')
+        </if>
+        <if test="page.expenseTypeId != null">
+            AND fei.expense_type_id = #{page.expenseTypeId}
+        </if>
+        <if test="page.infoSource != null and page.infoSource != ''">
+            AND fei.info_source = #{page.infoSource}
+        </if>
+        <if test="page.createEmployeeName != null and page.createEmployeeName != ''">
+            AND ei2.name like concat('%',#{page.createEmployeeName},'%')
+        </if>
+        <if test="page.createTime != null and page.createTime.length > 0">
+            AND fei.create_time BETWEEN #{page.createTime[0]} AND #{page.createTime[1]}
+        </if>
+        ORDER BY fei.create_time DESC
+        <if test="page.pageSize != -1">
+            LIMIT #{page.pageNo}, #{page.pageSize}
+        </if>
+    </select>
 </mapper>

+ 200 - 206
yudao-module-finance/yudao-module-expense-biz/src/test/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoServiceImplTest.java

@@ -1,206 +1,200 @@
-package cn.iocoder.yudao.module.expense.service.expenseinfo;
-
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.mock.mockito.MockBean;
-
-import javax.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
-import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.*;
-import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoDO;
-import cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo.ExpenseInfoMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
-import javax.annotation.Resource;
-import org.springframework.context.annotation.Import;
-import java.util.*;
-import java.time.LocalDateTime;
-
-import static cn.hutool.core.util.RandomUtil.*;
-import static cn.iocoder.yudao.module.expense.enums.ErrorCodeConstants.*;
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.*;
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.Mockito.*;
-
-/**
- * {@link ExpenseInfoServiceImpl} 的单元测试类
- *
- * @author dp
- */
-@Import(ExpenseInfoServiceImpl.class)
-public class ExpenseInfoServiceImplTest extends BaseDbUnitTest {
-
-    @Resource
-    private ExpenseInfoServiceImpl infoService;
-
-    @Resource
-    private ExpenseInfoMapper infoMapper;
-
-    @Test
-    public void testCreateInfo_success() {
-        // 准备参数
-        ExpenseInfoSaveReqVO createReqVO = randomPojo(ExpenseInfoSaveReqVO.class).setId(null);
-
-        // 调用
-        Long infoId = infoService.createInfo(createReqVO);
-        // 断言
-        assertNotNull(infoId);
-        // 校验记录的属性是否正确
-        ExpenseInfoDO info = infoMapper.selectById(infoId);
-        assertPojoEquals(createReqVO, info, "id");
-    }
-
-    @Test
-    public void testUpdateInfo_success() {
-        // mock 数据
-        ExpenseInfoDO dbInfo = randomPojo(ExpenseInfoDO.class);
-        infoMapper.insert(dbInfo);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        ExpenseInfoSaveReqVO updateReqVO = randomPojo(ExpenseInfoSaveReqVO.class, o -> {
-            o.setId(dbInfo.getId()); // 设置更新的 ID
-        });
-
-        // 调用
-        infoService.updateInfo(updateReqVO);
-        // 校验是否更新正确
-        ExpenseInfoDO info = infoMapper.selectById(updateReqVO.getId()); // 获取最新的
-        assertPojoEquals(updateReqVO, info);
-    }
-
-    @Test
-    public void testUpdateInfo_notExists() {
-        // 准备参数
-        ExpenseInfoSaveReqVO updateReqVO = randomPojo(ExpenseInfoSaveReqVO.class);
-
-        // 调用, 并断言异常
-        assertServiceException(() -> infoService.updateInfo(updateReqVO), INFO_NOT_EXISTS);
-    }
-
-    @Test
-    public void testDeleteInfo_success() {
-        // mock 数据
-        ExpenseInfoDO dbInfo = randomPojo(ExpenseInfoDO.class);
-        infoMapper.insert(dbInfo);// @Sql: 先插入出一条存在的数据
-        // 准备参数
-        Long id = dbInfo.getId();
-
-        // 调用
-        infoService.deleteInfo(id);
-       // 校验数据不存在了
-       assertNull(infoMapper.selectById(id));
-    }
-
-    @Test
-    public void testDeleteInfo_notExists() {
-        // 准备参数
-        Long id = randomLongId();
-
-        // 调用, 并断言异常
-        assertServiceException(() -> infoService.deleteInfo(id), INFO_NOT_EXISTS);
-    }
-
-    @Test
-    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
-    public void testGetInfoPage() {
-       // mock 数据
-       ExpenseInfoDO dbInfo = randomPojo(ExpenseInfoDO.class, o -> { // 等会查询到
-           o.setExpenseUuid(null);
-           o.setEmployeeId(null);
-           o.setEmployeeUuid(null);
-           o.setEmployeeName(null);
-           o.setEmployeePhone(null);
-           o.setUserId(null);
-           o.setUserUuid(null);
-           o.setDeptId(null);
-           o.setDeptUuid(null);
-           o.setPostId(null);
-           o.setPosition(null);
-           o.setExpenseTypeId(null);
-           o.setExpenseTypeName(null);
-           o.setExpenseMonth(null);
-           o.setTotalMoney(null);
-           o.setRemarks(null);
-           o.setStatus(null);
-           o.setInfoSource(null);
-           o.setCreateEmployeeId(null);
-           o.setCreateTime(null);
-       });
-       infoMapper.insert(dbInfo);
-       // 测试 expenseUuid 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseUuid(null)));
-       // 测试 employeeId 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeeId(null)));
-       // 测试 employeeUuid 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeeUuid(null)));
-       // 测试 employeeName 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeeName(null)));
-       // 测试 employeePhone 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeePhone(null)));
-       // 测试 userId 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setUserId(null)));
-       // 测试 userUuid 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setUserUuid(null)));
-       // 测试 deptId 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setDeptId(null)));
-       // 测试 deptUuid 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setDeptUuid(null)));
-       // 测试 postId 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setPostId(null)));
-       // 测试 position 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setPosition(null)));
-       // 测试 expenseTypeId 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseTypeId(null)));
-       // 测试 expenseTypeName 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseTypeName(null)));
-       // 测试 expenseMonth 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseMonth(null)));
-       // 测试 totalMoney 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setTotalMoney(null)));
-       // 测试 remarks 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setRemarks(null)));
-       // 测试 status 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setStatus(null)));
-       // 测试 infoSource 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setInfoSource(null)));
-       // 测试 createEmployeeId 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setCreateEmployeeId(null)));
-       // 测试 createTime 不匹配
-       infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setCreateTime(null)));
-       // 准备参数
-       ExpenseInfoPageReqVO reqVO = new ExpenseInfoPageReqVO();
-       reqVO.setExpenseUuid(null);
-       reqVO.setEmployeeId(null);
-       reqVO.setEmployeeUuid(null);
-       reqVO.setEmployeeName(null);
-       reqVO.setEmployeePhone(null);
-       reqVO.setUserId(null);
-       reqVO.setUserUuid(null);
-       reqVO.setDeptId(null);
-       reqVO.setDeptUuid(null);
-       reqVO.setPostId(null);
-       reqVO.setPosition(null);
-       reqVO.setExpenseTypeId(null);
-       reqVO.setExpenseTypeName(null);
-       reqVO.setExpenseMonth(null);
-       reqVO.setTotalMoney(null);
-       reqVO.setRemarks(null);
-       reqVO.setStatus(null);
-       reqVO.setInfoSource(null);
-       reqVO.setCreateEmployeeId(null);
-       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
-
-       // 调用
-       PageResult<ExpenseInfoDO> pageResult = infoService.getInfoPage(reqVO);
-       // 断言
-       assertEquals(1, pageResult.getTotal());
-       assertEquals(1, pageResult.getList().size());
-       assertPojoEquals(dbInfo, pageResult.getList().get(0));
-    }
-
-}
+// package cn.iocoder.yudao.module.expense.service.expenseinfo;
+//
+// import cn.iocoder.yudao.framework.common.pojo.PageResult;
+// import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+// import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoPageReqVO;
+// import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoRespVO;
+// import cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo.ExpenseInfoSaveReqVO;
+// import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoDO;
+// import cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo.ExpenseInfoMapper;
+// import org.junit.jupiter.api.Disabled;
+// import org.junit.jupiter.api.Test;
+// import org.springframework.context.annotation.Import;
+//
+// import javax.annotation.Resource;
+//
+// import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+// import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+// import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+// import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+// import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+// import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+// import static cn.iocoder.yudao.module.expense.enums.ErrorCodeConstants.INFO_NOT_EXISTS;
+// import static org.junit.jupiter.api.Assertions.*;
+//
+// /**
+//  * {@link ExpenseInfoServiceImpl} 的单元测试类
+//  *
+//  * @author dp
+//  */
+// @Import(ExpenseInfoServiceImpl.class)
+// public class ExpenseInfoServiceImplTest extends BaseDbUnitTest {
+//
+//     @Resource
+//     private ExpenseInfoServiceImpl infoService;
+//
+//     @Resource
+//     private ExpenseInfoMapper infoMapper;
+//
+//     @Test
+//     public void testCreateInfo_success() {
+//         // 准备参数
+//         ExpenseInfoSaveReqVO createReqVO = randomPojo(ExpenseInfoSaveReqVO.class).setId(null);
+//
+//         // 调用
+//         Long infoId = infoService.createInfo(createReqVO);
+//         // 断言
+//         assertNotNull(infoId);
+//         // 校验记录的属性是否正确
+//         ExpenseInfoDO info = infoMapper.selectById(infoId);
+//         assertPojoEquals(createReqVO, info, "id");
+//     }
+//
+//     @Test
+//     public void testUpdateInfo_success() {
+//         // mock 数据
+//         ExpenseInfoDO dbInfo = randomPojo(ExpenseInfoDO.class);
+//         infoMapper.insert(dbInfo);// @Sql: 先插入出一条存在的数据
+//         // 准备参数
+//         ExpenseInfoSaveReqVO updateReqVO = randomPojo(ExpenseInfoSaveReqVO.class, o -> {
+//             o.setId(dbInfo.getId()); // 设置更新的 ID
+//         });
+//
+//         // 调用
+//         infoService.updateInfo(updateReqVO);
+//         // 校验是否更新正确
+//         ExpenseInfoDO info = infoMapper.selectById(updateReqVO.getId()); // 获取最新的
+//         assertPojoEquals(updateReqVO, info);
+//     }
+//
+//     @Test
+//     public void testUpdateInfo_notExists() {
+//         // 准备参数
+//         ExpenseInfoSaveReqVO updateReqVO = randomPojo(ExpenseInfoSaveReqVO.class);
+//
+//         // 调用, 并断言异常
+//         assertServiceException(() -> infoService.updateInfo(updateReqVO), INFO_NOT_EXISTS);
+//     }
+//
+//     @Test
+//     public void testDeleteInfo_success() {
+//         // mock 数据
+//         ExpenseInfoDO dbInfo = randomPojo(ExpenseInfoDO.class);
+//         infoMapper.insert(dbInfo);// @Sql: 先插入出一条存在的数据
+//         // 准备参数
+//         Long id = dbInfo.getId();
+//
+//         // 调用
+//         infoService.deleteInfo(id);
+//        // 校验数据不存在了
+//        assertNull(infoMapper.selectById(id));
+//     }
+//
+//     @Test
+//     public void testDeleteInfo_notExists() {
+//         // 准备参数
+//         Long id = randomLongId();
+//
+//         // 调用, 并断言异常
+//         assertServiceException(() -> infoService.deleteInfo(id), INFO_NOT_EXISTS);
+//     }
+//
+//     @Test
+//     @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+//     public void testGetInfoPage() {
+//        // mock 数据
+//        ExpenseInfoDO dbInfo = randomPojo(ExpenseInfoDO.class, o -> { // 等会查询到
+//            o.setExpenseUuid(null);
+//            o.setEmployeeId(null);
+//            o.setEmployeeUuid(null);
+//            o.setEmployeeName(null);
+//            o.setEmployeePhone(null);
+//            o.setUserId(null);
+//            o.setUserUuid(null);
+//            o.setDeptId(null);
+//            o.setDeptUuid(null);
+//            o.setPostId(null);
+//            o.setPosition(null);
+//            o.setExpenseTypeId(null);
+//            o.setExpenseTypeName(null);
+//            o.setExpenseMonth(null);
+//            o.setTotalMoney(null);
+//            o.setRemarks(null);
+//            o.setStatus(null);
+//            o.setInfoSource(null);
+//            o.setCreateEmployeeId(null);
+//            o.setCreateTime(null);
+//        });
+//        infoMapper.insert(dbInfo);
+//        // 测试 expenseUuid 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseUuid(null)));
+//        // 测试 employeeId 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeeId(null)));
+//        // 测试 employeeUuid 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeeUuid(null)));
+//        // 测试 employeeName 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeeName(null)));
+//        // 测试 employeePhone 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setEmployeePhone(null)));
+//        // 测试 userId 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setUserId(null)));
+//        // 测试 userUuid 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setUserUuid(null)));
+//        // 测试 deptId 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setDeptId(null)));
+//        // 测试 deptUuid 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setDeptUuid(null)));
+//        // 测试 postId 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setPostId(null)));
+//        // 测试 position 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setPosition(null)));
+//        // 测试 expenseTypeId 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseTypeId(null)));
+//        // 测试 expenseTypeName 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseTypeName(null)));
+//        // 测试 expenseMonth 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setExpenseMonth(null)));
+//        // 测试 totalMoney 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setTotalMoney(null)));
+//        // 测试 remarks 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setRemarks(null)));
+//        // 测试 status 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setStatus(null)));
+//        // 测试 infoSource 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setInfoSource(null)));
+//        // 测试 createEmployeeId 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setCreateEmployeeId(null)));
+//        // 测试 createTime 不匹配
+//        infoMapper.insert(cloneIgnoreId(dbInfo, o -> o.setCreateTime(null)));
+//        // 准备参数
+//        ExpenseInfoPageReqVO reqVO = new ExpenseInfoPageReqVO();
+//        reqVO.setExpenseUuid(null);
+//        reqVO.setEmployeeId(null);
+//        reqVO.setEmployeeUuid(null);
+//        reqVO.setEmployeeName(null);
+//        reqVO.setEmployeePhone(null);
+//        reqVO.setUserId(null);
+//        reqVO.setUserUuid(null);
+//        reqVO.setDeptId(null);
+//        reqVO.setDeptUuid(null);
+//        reqVO.setPostId(null);
+//        reqVO.setPosition(null);
+//        reqVO.setExpenseTypeId(null);
+//        reqVO.setExpenseTypeName(null);
+//        reqVO.setExpenseMonth(null);
+//        reqVO.setTotalMoney(null);
+//        reqVO.setRemarks(null);
+//        reqVO.setStatus(null);
+//        reqVO.setInfoSource(null);
+//        reqVO.setCreateEmployeeId(null);
+//        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+//
+//        // 调用
+//        PageResult<ExpenseInfoRespVO> pageResult = infoService.getInfoPage(reqVO);
+//        // 断言
+//        assertEquals(1, pageResult.getTotal());
+//        assertEquals(1, pageResult.getList().size());
+//        assertPojoEquals(dbInfo, pageResult.getList().get(0));
+//     }
+//
+// }

+ 10 - 0
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java

@@ -35,5 +35,15 @@ public interface PostApi {
         List<PostRespDTO> list = getPostList(ids);
         return CollectionUtils.convertMap(list, PostRespDTO::getId);
     }
+    
+    /**
+     * 根据职位ID获取职位详情
+     * 此方法用于从数据库中检索特定职位的详细信息,并将其封装到一个响应DTO中
+     * 主要用于满足用户查看特定职位的需求
+     * 
+     * @param id 职位的唯一标识符通过这个ID可以在数据库中找到对应的职位
+     * @return 返回一个包含职位详细信息的响应DTO如果找不到对应的职位,则可能返回null
+     */
+    PostRespDTO getPost(Long id);
 
 }

+ 6 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApiImpl.java

@@ -32,4 +32,10 @@ public class PostApiImpl implements PostApi {
         return BeanUtils.toBean(list, PostRespDTO.class);
     }
 
+    @Override
+    public PostRespDTO getPost(Long id) {
+        PostDO post = postService.getPost(id);
+        return BeanUtils.toBean(post, PostRespDTO.class);
+    }
+
 }