Ver código fonte

Merge branch 'master_20240722' of http://git.dgtis.com/15896567520/oneportal_saas into master_20240722

# Conflicts:
#	yudao-module-personnel/yudao-module-employee-api/src/main/java/cn/iocoder/yudao/module/employee/api/dto/EmployeeRespDTO.java
zhaopeiqing 7 meses atrás
pai
commit
c385b6a56e
30 arquivos alterados com 1454 adições e 84 exclusões
  1. 2 2
      yudao-module-bpm/yudao-module-bpm-biz/src/main/resources/mapper/stamp/stampinfo/OaStampInfoMapper.xml
  2. 9 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. 209 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/ExpenseInfoController.java
  5. 48 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoPageReqVO.java
  6. 118 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoRespVO.java
  7. 48 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseinfo/vo/ExpenseInfoSaveReqVO.java
  8. 27 4
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/ExpenseItemController.java
  9. 7 20
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/vo/ExpenseItemPageReqVO.java
  10. 12 5
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/vo/ExpenseItemRespVO.java
  11. 3 10
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/vo/ExpenseItemSaveReqVO.java
  12. 24 1
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/ExpenseTypeController.java
  13. 4 17
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/vo/ExpenseTypePageReqVO.java
  14. 12 5
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/vo/ExpenseTypeRespVO.java
  15. 3 7
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/vo/ExpenseTypeSaveReqVO.java
  16. 108 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/dataobject/expenseinfo/ExpenseInfoDO.java
  17. 68 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/dataobject/expenseinfo/ExpenseInfoObjDO.java
  18. 38 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expenseinfo/ExpenseInfoMapper.java
  19. 25 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expenseinfo/ExpenseInfoObjMapper.java
  20. 0 4
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expenseitem/ExpenseItemMapper.java
  21. 2 7
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expensetype/ExpenseTypeMapper.java
  22. 68 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoService.java
  23. 275 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoServiceImpl.java
  24. 7 1
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseitem/ExpenseItemServiceImpl.java
  25. 9 0
      yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expensetype/ExpenseTypeServiceImpl.java
  26. 107 0
      yudao-module-finance/yudao-module-expense-biz/src/main/resources/mapper/expenseinfo/ExpenseInfoMapper.xml
  27. 200 0
      yudao-module-finance/yudao-module-expense-biz/src/test/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoServiceImplTest.java
  28. 0 1
      yudao-module-personnel/yudao-module-employee-api/src/main/java/cn/iocoder/yudao/module/employee/api/dto/EmployeeRespDTO.java
  29. 10 0
      yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dept/PostApi.java
  30. 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}

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

@@ -8,4 +8,13 @@ public interface ErrorCodeConstants {
     ErrorCode EXPENSE_TYPE_NOT_EXISTS = new ErrorCode(1_040_001_001, "报销类型信息不存在");
     // ========== 报销费用项目信息 1_040_002_001 ==========
     ErrorCode EXPENSE_ITEM_NOT_EXISTS = new ErrorCode(1_040_002_001, "报销费用项目信息不存在");
+    ErrorCode EXPENSE_ITEM_EXISTS = new ErrorCode(1_040_002_002, "请先删除报销类型下的费用项目");
+    ErrorCode EXPENSE_TYPE_ID_NULL = new ErrorCode(1_040_002_003, "报销类型不能为空");
+    // ========== 报销信息 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>

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

@@ -0,0 +1,209 @@
+package cn.iocoder.yudao.module.expense.controller.admin.expenseinfo;
+
+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.springdoc.api.annotations.ParameterObject;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+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;
+
+@Tag(name = "管理后台 - 报销信息")
+@RestController
+@RequestMapping("/expense/info")
+@Validated
+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')")
+    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')")
+    public CommonResult<Boolean> updateInfo(@Valid @RequestBody ExpenseInfoSaveReqVO updateReqVO) {
+        updateReqVO.setInfoSource("1");
+        infoService.updateInfo(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除报销信息")
+    @Parameter(name = "id", description = "编号", required = true)
+    // @PreAuthorize("@ss.hasPermission('expense:info:delete')")
+    public CommonResult<Boolean> deleteInfo(@RequestParam("id") Long id) {
+        infoService.deleteInfo(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得报销信息")
+    @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);
+        // 将报销信息转换为响应视图对象
+        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 @ParameterObject ExpenseInfoPageReqVO pageReqVO) {
+        PageResult<ExpenseInfoRespVO> infoPage = infoService.getInfoPage(pageReqVO);
+        return success(infoPage);
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出报销信息 Excel")
+    // @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<ExpenseInfoRespVO> list = infoService.getInfoPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "报销信息.xls", "数据", ExpenseInfoRespVO.class,
+                        list);
+    }
+
+    // ==================== 子表(报销信息子) ====================
+
+    @GetMapping("/expense-info-obj/list-by-expense-id")
+    @Operation(summary = "获得报销信息子列表")
+    @Parameter(name = "expenseId", description = "报销主表主键id")
+    // @PreAuthorize("@ss.hasPermission('expense:info:query')")
+    public CommonResult<List<ExpenseInfoObjDO>> getExpenseInfoObjListByExpenseId(@RequestParam("expenseId") Long expenseId) {
+        return success(infoService.getExpenseInfoObjListByExpenseId(expenseId));
+    }
+
+}

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

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+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;
+
+@Schema(description = "管理后台 - 报销信息分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ExpenseInfoPageReqVO extends PageParam {
+
+    @Schema(description = "报销申请员工姓名", example = "李四")
+    private String employeeName;
+
+    @Schema(description = "报销申请员工手机号")
+    private String employeePhone;
+
+    @Schema(description = "部门id", example = "16541")
+    private Long deptId;
+
+    @Schema(description = "职位id", example = "15670")
+    private Long postId;
+
+    @Schema(description = "报销类型主键id", example = "31388")
+    private Long expenseTypeId;
+
+    @Schema(description = "备注")
+    private String remarks;
+
+    @Schema(description = "数据来源,0流程添加、1手动添加")
+    private String infoSource;
+
+    @Schema(description = "创建员工姓名", example = "李四")
+    private String createEmployeeName;
+
+    @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;
+
+}

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

@@ -0,0 +1,118 @@
+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.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 报销信息 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ExpenseInfoRespVO {
+
+    @Schema(description = "表单主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "30165")
+    @ExcelProperty("表单主键")
+    private Long id;
+
+    @Schema(description = "uuid", example = "22476")
+    @ExcelProperty("uuid")
+    private String expenseUuid;
+
+    @Schema(description = "报销申请人id", example = "27928")
+    @ExcelProperty("报销申请人id")
+    private Long employeeId;
+
+    @Schema(description = "报销申请人uuid", example = "29769")
+    @ExcelProperty("报销申请人uuid")
+    private String employeeUuid;
+
+    @Schema(description = "报销申请员工姓名", example = "李四")
+    @ExcelProperty("报销申请员工姓名")
+    private String employeeName;
+
+    @Schema(description = "报销申请员工手机号")
+    @ExcelProperty("报销申请员工手机号")
+    private String employeePhone;
+
+    @Schema(description = "用户账号id", example = "28869")
+    @ExcelProperty("用户账号id")
+    private Long userId;
+
+    @Schema(description = "用户账号uuid", example = "952")
+    @ExcelProperty("用户账号uuid")
+    private String userUuid;
+
+    @Schema(description = "部门id")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+    @Schema(description = "部门名称")
+    @ExcelProperty("部门名称")
+    private String deptName;
+
+    @Schema(description = "职位id", example = "15670")
+    @ExcelProperty("职位id")
+    private Long postId;
+
+    @Schema(description = "员工职位")
+    @ExcelProperty("员工职位")
+    private String position;
+
+    @Schema(description = "报销类型主键id", example = "31388")
+    @ExcelProperty("报销类型主键id")
+    private Long expenseTypeId;
+
+    @Schema(description = "报销类型名称", example = "张三")
+    @ExcelProperty("报销类型名称")
+    private String expenseTypeName;
+
+    @Schema(description = "费用所属区间")
+    @ExcelProperty("费用所属区间")
+    private String expenseMonth;
+
+    @Schema(description = "总预算金额,单位(元)", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("总预算金额,单位(元)")
+    private BigDecimal totalMoney;
+
+    @Schema(description = "备注")
+    @ExcelProperty("备注")
+    private String remarks;
+
+    @Schema(description = "状态(0已完成、1已作废、2已生效)")
+    @ExcelProperty("状态(0已完成、1已作废、2已生效)")
+    private String status;
+
+    @Schema(description = "状态描述(0已完成、1已作废、2已生效)")
+    @ExcelProperty("状态描述(0已完成、1已作废、2已生效)")
+    private String statusDesc;
+
+    @Schema(description = "数据来源,0流程添加、1手动添加")
+    @ExcelProperty("数据来源,0流程添加、1手动添加")
+    private String infoSource;
+
+    @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;
+
+
+}

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

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.expense.controller.admin.expenseinfo.vo;
+
+import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoObjDO;
+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;
+import java.util.List;
+
+@Schema(description = "管理后台 - 报销信息新增/修改 Request VO")
+@Data
+public class ExpenseInfoSaveReqVO {
+
+    @Schema(description = "表单主键")
+    private Long id;
+
+    @Schema(description = "报销申请人id", example = "1")
+    @NotNull(message = "报销申请人id不能为空")
+    private Long employeeId;
+
+    @Schema(description = "报销类型主键id", example = "1")
+    @NotNull(message = "报销类型主键id不能为空")
+    private Long expenseTypeId;
+
+    @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手动添加")
+    private String infoSource;
+
+    @Schema(description = "报销信息子列表", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "报销信息子列表不能为空")
+    private List<ExpenseInfoObjDO> expenseInfoObjs;
+
+}

+ 27 - 4
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/ExpenseItemController.java

@@ -1,11 +1,15 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expenseitem;
 
+import cn.hutool.core.collection.CollectionUtil;
 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.expenseitem.vo.ExpenseItemPageReqVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseitem.vo.ExpenseItemRespVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expenseitem.vo.ExpenseItemSaveReqVO;
@@ -14,7 +18,7 @@ import cn.iocoder.yudao.module.expense.service.expenseitem.ExpenseItemService;
 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.*;
 
@@ -23,6 +27,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;
@@ -36,6 +43,9 @@ public class ExpenseItemController {
     @Resource
     private ExpenseItemService itemService;
 
+    @Resource
+    private EmployeeApi employeeApi;
+
     @PostMapping("/create")
     @Operation(summary = "创建报销费用项目信息")
     // @PreAuthorize("@ss.hasPermission('expense:item:create')")
@@ -72,16 +82,29 @@ public class ExpenseItemController {
     @GetMapping("/page")
     @Operation(summary = "获得报销费用项目信息分页")
     // @PreAuthorize("@ss.hasPermission('expense:item:query')")
-    public CommonResult<PageResult<ExpenseItemRespVO>> getItemPage(@Valid ExpenseItemPageReqVO pageReqVO) {
+    public CommonResult<PageResult<ExpenseItemRespVO>> getItemPage(@Valid @ParameterObject ExpenseItemPageReqVO pageReqVO) {
         PageResult<ExpenseItemDO> pageResult = itemService.getItemPage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, ExpenseItemRespVO.class));
+        PageResult<ExpenseItemRespVO> voPageResult = BeanUtils.toBean(pageResult, ExpenseItemRespVO.class);
+        List<ExpenseItemRespVO> list = voPageResult.getList();
+        if (CollectionUtil.isNotEmpty(list)) {
+            List<Long> userIdList = list.stream().map(ExpenseItemRespVO::getCreator).collect(Collectors.toList());
+            List<EmployeeRespDTO> employeeRespDTOList = employeeApi.getEmployeeListByUserIds(userIdList);
+            Map<Long, EmployeeRespDTO> longEmployeeRespDTOMap = CollectionUtils.convertMap(employeeRespDTOList, EmployeeRespDTO::getUserId);
+            for (ExpenseItemRespVO expenseItemRespVO : list) {
+                EmployeeRespDTO employeeRespDTO = longEmployeeRespDTOMap.get(expenseItemRespVO.getCreator());
+                if (Objects.nonNull(employeeRespDTO)) {
+                    expenseItemRespVO.setCreateEmployeeName(employeeRespDTO.getName());
+                }
+            }
+        }
+        return success(voPageResult);
     }
 
     @GetMapping("/export-excel")
     @Operation(summary = "导出报销费用项目信息 Excel")
     // @PreAuthorize("@ss.hasPermission('expense:item:export')")
     @ApiAccessLog(operateType = EXPORT)
-    public void exportItemExcel(@Valid ExpenseItemPageReqVO pageReqVO,
+    public void exportItemExcel(@Valid @ParameterObject ExpenseItemPageReqVO pageReqVO,
               HttpServletResponse response) throws IOException {
         pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
         List<ExpenseItemDO> list = itemService.getItemPage(pageReqVO).getList();

+ 7 - 20
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/vo/ExpenseItemPageReqVO.java

@@ -1,13 +1,12 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expenseitem.vo;
 
-import lombok.*;
-import java.util.*;
-import io.swagger.v3.oas.annotations.media.Schema;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import org.springframework.format.annotation.DateTimeFormat;
-import java.time.LocalDateTime;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
 
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+import javax.validation.constraints.NotNull;
 
 @Schema(description = "管理后台 - 报销费用项目信息分页 Request VO")
 @Data
@@ -15,23 +14,11 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @ToString(callSuper = true)
 public class ExpenseItemPageReqVO extends PageParam {
 
-    @Schema(description = "报销类型主键id", example = "2556")
+    @Schema(description = "报销类型主键id", example = "1")
+    @NotNull(message = "报销类型不能为空")
     private Long expenseTypeId;
 
-    @Schema(description = "报销类型uuid", example = "14367")
-    private String expenseTypeUuid;
-
-    @Schema(description = "费用项目uuid", example = "10133")
-    private String expenseItemUuid;
-
     @Schema(description = "费用项目", example = "芋艿")
     private String name;
 
-    @Schema(description = "备注")
-    private String remarks;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
 }

+ 12 - 5
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/vo/ExpenseItemRespVO.java

@@ -1,12 +1,11 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expenseitem.vo;
 
+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 org.springframework.format.annotation.DateTimeFormat;
+import lombok.Data;
+
 import java.time.LocalDateTime;
-import com.alibaba.excel.annotation.*;
 
 @Schema(description = "管理后台 - 报销费用项目信息 Response VO")
 @Data
@@ -37,6 +36,14 @@ public class ExpenseItemRespVO {
     @ExcelProperty("备注")
     private String remarks;
 
+    @Schema(description = "创建者")
+    @ExcelProperty("创建者id")
+    private Long creator;
+
+    @Schema(description = "创建员工姓名")
+    @ExcelProperty("创建员工id")
+    private String createEmployeeName;
+
     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;

+ 3 - 10
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expenseitem/vo/ExpenseItemSaveReqVO.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expenseitem.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-import javax.validation.constraints.*;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
 
 @Schema(description = "管理后台 - 报销费用项目信息新增/修改 Request VO")
 @Data
@@ -13,15 +13,8 @@ public class ExpenseItemSaveReqVO {
     private Long id;
 
     @Schema(description = "报销类型主键id")
-    @NotNull(message = "报销类型主键id不能为空")
     private Long expenseTypeId;
 
-    @Schema(description = "报销类型uuid")
-    private String expenseTypeUuid;
-
-    @Schema(description = "费用项目uuid")
-    private String expenseItemUuid;
-
     @Schema(description = "费用项目")
     @NotBlank(message = "费用项目名称不能为空")
     private String name;

+ 24 - 1
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/ExpenseTypeController.java

@@ -1,11 +1,15 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expensetype;
 
+import cn.hutool.core.collection.CollectionUtil;
 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.expensetype.vo.ExpenseTypePageReqVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expensetype.vo.ExpenseTypeRespVO;
 import cn.iocoder.yudao.module.expense.controller.admin.expensetype.vo.ExpenseTypeSaveReqVO;
@@ -23,6 +27,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;
@@ -36,6 +43,9 @@ public class ExpenseTypeController {
     @Resource
     private ExpenseTypeService typeService;
 
+    @Resource
+    private EmployeeApi employeeApi;
+
     @PostMapping("/create")
     @Operation(summary = "创建报销类型信息")
     // @PreAuthorize("@ss.hasPermission('expense:type:create')")
@@ -74,7 +84,20 @@ public class ExpenseTypeController {
     // @PreAuthorize("@ss.hasPermission('expense:type:query')")
     public CommonResult<PageResult<ExpenseTypeRespVO>> getTypePage(@Valid ExpenseTypePageReqVO pageReqVO) {
         PageResult<ExpenseTypeDO> pageResult = typeService.getTypePage(pageReqVO);
-        return success(BeanUtils.toBean(pageResult, ExpenseTypeRespVO.class));
+        PageResult<ExpenseTypeRespVO> voPageResult = BeanUtils.toBean(pageResult, ExpenseTypeRespVO.class);
+        List<ExpenseTypeRespVO> list = voPageResult.getList();
+        if (CollectionUtil.isNotEmpty(list)) {
+            List<Long> userIdList = list.stream().map(ExpenseTypeRespVO::getCreator).collect(Collectors.toList());
+            List<EmployeeRespDTO> employeeRespDTOList = employeeApi.getEmployeeListByUserIds(userIdList);
+            Map<Long, EmployeeRespDTO> longEmployeeRespDTOMap = CollectionUtils.convertMap(employeeRespDTOList, EmployeeRespDTO::getUserId);
+            for (ExpenseTypeRespVO expenseTypeRespVO : list) {
+                EmployeeRespDTO employeeRespDTO = longEmployeeRespDTOMap.get(expenseTypeRespVO.getCreator());
+                if (Objects.nonNull(employeeRespDTO)) {
+                    expenseTypeRespVO.setCreateEmployeeName(employeeRespDTO.getName());
+                }
+            }
+        }
+        return success(voPageResult);
     }
 
     @GetMapping("/list")

+ 4 - 17
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/vo/ExpenseTypePageReqVO.java

@@ -1,13 +1,10 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expensetype.vo;
 
-import lombok.*;
-import java.util.*;
-import io.swagger.v3.oas.annotations.media.Schema;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
-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;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
 
 @Schema(description = "管理后台 - 报销类型信息分页 Request VO")
 @Data
@@ -15,17 +12,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
 @ToString(callSuper = true)
 public class ExpenseTypePageReqVO extends PageParam {
 
-    @Schema(description = "uuid", example = "18264")
-    private String expenseTypeUuid;
-
     @Schema(description = "报销类型", example = "芋艿")
     private String name;
 
-    @Schema(description = "备注")
-    private String remarks;
-
-    @Schema(description = "创建时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] createTime;
-
 }

+ 12 - 5
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/vo/ExpenseTypeRespVO.java

@@ -1,12 +1,11 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expensetype.vo;
 
+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 org.springframework.format.annotation.DateTimeFormat;
+import lombok.Data;
+
 import java.time.LocalDateTime;
-import com.alibaba.excel.annotation.*;
 
 @Schema(description = "管理后台 - 报销类型信息 Response VO")
 @Data
@@ -29,6 +28,14 @@ public class ExpenseTypeRespVO {
     @ExcelProperty("备注")
     private String remarks;
 
+    @Schema(description = "创建者")
+    @ExcelProperty("创建者id")
+    private Long creator;
+
+    @Schema(description = "创建员工姓名")
+    @ExcelProperty("创建员工id")
+    private String createEmployeeName;
+
     @Schema(description = "创建时间")
     @ExcelProperty("创建时间")
     private LocalDateTime createTime;

+ 3 - 7
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/controller/admin/expensetype/vo/ExpenseTypeSaveReqVO.java

@@ -1,10 +1,9 @@
 package cn.iocoder.yudao.module.expense.controller.admin.expensetype.vo;
 
 import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.*;
-import java.util.*;
-import javax.validation.constraints.*;
-import cn.iocoder.yudao.module.expense.dal.dataobject.expenseitem.ExpenseItemDO;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
 
 @Schema(description = "管理后台 - 报销类型信息新增/修改 Request VO")
 @Data
@@ -13,9 +12,6 @@ public class ExpenseTypeSaveReqVO {
     @Schema(description = "主键")
     private Long id;
 
-    @Schema(description = "uuid")
-    private String expenseTypeUuid;
-
     @Schema(description = "报销类型")
     @NotBlank(message = "报销类型名称不能为空")
     private String name;

+ 108 - 0
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/dataobject/expenseinfo/ExpenseInfoDO.java

@@ -0,0 +1,108 @@
+package cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo;
+
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 报销信息 DO
+ *
+ * @author dp
+ */
+@TableName("finance_expense_info")
+@KeySequence("finance_expense_info_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExpenseInfoDO extends BaseDO {
+
+    /**
+     * 表单主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * uuid
+     */
+    private String expenseUuid;
+    /**
+     * 报销申请人id
+     */
+    private Long employeeId;
+    /**
+     * 报销申请人uuid
+     */
+    private String employeeUuid;
+    /**
+     * 报销申请员工姓名
+     */
+    private String employeeName;
+    /**
+     * 报销申请员工手机号
+     */
+    private String employeePhone;
+    /**
+     * 用户账号id
+     */
+    private Long userId;
+    /**
+     * 用户账号uuid
+     */
+    private String userUuid;
+    /**
+     * 部门id
+     */
+    private Long deptId;
+    /**
+     * 部门uuid
+     */
+    private String deptUuid;
+    /**
+     * 职位id
+     */
+    private Long postId;
+    /**
+     * 员工职位
+     */
+    private String position;
+    /**
+     * 报销类型主键id
+     */
+    private Long expenseTypeId;
+    /**
+     * 报销类型名称
+     */
+    private String expenseTypeName;
+    /**
+     * 费用所属区间
+     */
+    private String expenseMonth;
+    /**
+     * 总预算金额,单位(元)
+     */
+    private BigDecimal totalMoney;
+    /**
+     * 备注
+     */
+    private String remarks;
+    /**
+     * 状态(0已完成、1已作废、2已生效)
+     */
+    private String status;
+    /**
+     * 数据来源,0流程添加、1手动添加
+     */
+    private String infoSource;
+    /**
+     * 创建员工id
+     */
+    private Long createEmployeeId;
+
+}

+ 68 - 0
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/dataobject/expenseinfo/ExpenseInfoObjDO.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo;
+
+import lombok.*;
+import java.util.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.LocalDateTime;
+import com.baomidou.mybatisplus.annotation.*;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+
+/**
+ * 报销信息子 DO
+ *
+ * @author dp
+ */
+@TableName("finance_expense_info_obj")
+@KeySequence("finance_expense_info_obj_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExpenseInfoObjDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 报销主表主键id
+     */
+    private Long expenseId;
+    /**
+     * 报销主表uuid
+     */
+    private String expenseUuid;
+    /**
+     * 报销子表uuid
+     */
+    private String expenseObjUuid;
+    /**
+     * 费用项目主键id
+     */
+    private Long expenseItemId;
+    /**
+     * 费用项目名称
+     */
+    private String expenseItemName;
+    /**
+     * 预算金额,单位(元)
+     */
+    private BigDecimal money;
+    /**
+     * 开始日期
+     */
+    private String startDate;
+    /**
+     * 结束日期
+     */
+    private String endDate;
+    /**
+     * 说明
+     */
+    private String remarks;
+
+}

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

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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 org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 报销信息 Mapper
+ *
+ * @author dp
+ */
+@Mapper
+public interface ExpenseInfoMapper extends BaseMapperX<ExpenseInfoDO> {
+
+    default PageResult<ExpenseInfoDO> selectPage(ExpenseInfoPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ExpenseInfoDO>()
+                .likeIfPresent(ExpenseInfoDO::getEmployeeName, reqVO.getEmployeeName())
+                .eqIfPresent(ExpenseInfoDO::getEmployeePhone, reqVO.getEmployeePhone())
+                .eqIfPresent(ExpenseInfoDO::getDeptId, reqVO.getDeptId())
+                .eqIfPresent(ExpenseInfoDO::getPostId, reqVO.getPostId())
+                .eqIfPresent(ExpenseInfoDO::getExpenseTypeId, reqVO.getExpenseTypeId())
+                .eqIfPresent(ExpenseInfoDO::getRemarks, reqVO.getRemarks())
+                .eqIfPresent(ExpenseInfoDO::getInfoSource, reqVO.getInfoSource())
+                .betweenIfPresent(ExpenseInfoDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(ExpenseInfoDO::getId));
+    }
+
+    long selectPageCount(@Param("page") ExpenseInfoPageReqVO pageReqVO);
+
+    List<ExpenseInfoRespVO> selectPageList(@Param("page") ExpenseInfoPageReqVO pageReqVO);
+}

+ 25 - 0
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expenseinfo/ExpenseInfoObjMapper.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.expense.dal.dataobject.expenseinfo.ExpenseInfoObjDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 报销信息子 Mapper
+ *
+ * @author dp
+ */
+@Mapper
+public interface ExpenseInfoObjMapper extends BaseMapperX<ExpenseInfoObjDO> {
+
+    default List<ExpenseInfoObjDO> selectListByExpenseId(Long expenseId) {
+        return selectList(ExpenseInfoObjDO::getExpenseId, expenseId);
+    }
+
+    default int deleteByExpenseId(Long expenseId) {
+        return delete(ExpenseInfoObjDO::getExpenseId, expenseId);
+    }
+
+}

+ 0 - 4
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expenseitem/ExpenseItemMapper.java

@@ -20,11 +20,7 @@ public interface ExpenseItemMapper extends BaseMapperX<ExpenseItemDO> {
     default PageResult<ExpenseItemDO> selectPage(ExpenseItemPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<ExpenseItemDO>()
                 .eqIfPresent(ExpenseItemDO::getExpenseTypeId, reqVO.getExpenseTypeId())
-                .eqIfPresent(ExpenseItemDO::getExpenseTypeUuid, reqVO.getExpenseTypeUuid())
-                .eqIfPresent(ExpenseItemDO::getExpenseItemUuid, reqVO.getExpenseItemUuid())
                 .likeIfPresent(ExpenseItemDO::getName, reqVO.getName())
-                .eqIfPresent(ExpenseItemDO::getRemarks, reqVO.getRemarks())
-                .betweenIfPresent(ExpenseItemDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(ExpenseItemDO::getId));
     }
 

+ 2 - 7
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/dal/mysql/expensetype/ExpenseTypeMapper.java

@@ -1,13 +1,11 @@
 package cn.iocoder.yudao.module.expense.dal.mysql.expensetype;
 
-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.expensetype.vo.ExpenseTypePageReqVO;
 import cn.iocoder.yudao.module.expense.dal.dataobject.expensetype.ExpenseTypeDO;
 import org.apache.ibatis.annotations.Mapper;
-import cn.iocoder.yudao.module.expense.controller.admin.expensetype.vo.*;
 
 /**
  * 报销类型信息 Mapper
@@ -19,10 +17,7 @@ public interface ExpenseTypeMapper extends BaseMapperX<ExpenseTypeDO> {
 
     default PageResult<ExpenseTypeDO> selectPage(ExpenseTypePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<ExpenseTypeDO>()
-                .eqIfPresent(ExpenseTypeDO::getExpenseTypeUuid, reqVO.getExpenseTypeUuid())
                 .likeIfPresent(ExpenseTypeDO::getName, reqVO.getName())
-                .eqIfPresent(ExpenseTypeDO::getRemarks, reqVO.getRemarks())
-                .betweenIfPresent(ExpenseTypeDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(ExpenseTypeDO::getId));
     }
 

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

@@ -0,0 +1,68 @@
+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;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 报销信息 Service 接口
+ *
+ * @author dp
+ */
+public interface ExpenseInfoService {
+
+    /**
+     * 创建报销信息
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createInfo(@Valid ExpenseInfoSaveReqVO createReqVO);
+
+    /**
+     * 更新报销信息
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateInfo(@Valid ExpenseInfoSaveReqVO updateReqVO);
+
+    /**
+     * 删除报销信息
+     *
+     * @param id 编号
+     */
+    void deleteInfo(Long id);
+
+    /**
+     * 获得报销信息
+     *
+     * @param id 编号
+     * @return 报销信息
+     */
+    ExpenseInfoDO getInfo(Long id);
+
+    /**
+     * 获得报销信息分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 报销信息分页
+     */
+    PageResult<ExpenseInfoRespVO> getInfoPage(ExpenseInfoPageReqVO pageReqVO);
+
+    // ==================== 子表(报销信息子) ====================
+
+    /**
+     * 获得报销信息子列表
+     *
+     * @param expenseId 报销主表主键id
+     * @return 报销信息子列表
+     */
+    List<ExpenseInfoObjDO> getExpenseInfoObjListByExpenseId(Long expenseId);
+
+}

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

@@ -0,0 +1,275 @@
+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;
+import cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo.ExpenseInfoMapper;
+import cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo.ExpenseInfoObjMapper;
+import org.springframework.stereotype.Service;
+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.*;
+
+/**
+ * 报销信息 Service 实现类
+ *
+ * @author dp
+ */
+@Service
+@Validated
+public class ExpenseInfoServiceImpl implements ExpenseInfoService {
+
+    @Resource
+    private ExpenseInfoMapper infoMapper;
+    @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);
+            }
+        }
+
+        // 校验存在
+        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);
+
+        // 更新子表
+        updateExpenseInfoObjList(updateReqVO.getId(), updateReqVO.getExpenseInfoObjs());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteInfo(Long id) {
+        // 校验存在
+        validateInfoExists(id);
+        // 删除
+        infoMapper.deleteById(id);
+
+        // 删除子表
+        deleteExpenseInfoObjByExpenseId(id);
+    }
+
+    private ExpenseInfoDO validateInfoExists(Long id) {
+        ExpenseInfoDO expenseInfoDO = infoMapper.selectById(id);
+        if (expenseInfoDO == null) {
+            throw exception(INFO_NOT_EXISTS);
+        }
+        return expenseInfoDO;
+    }
+
+    @Override
+    public ExpenseInfoDO getInfo(Long id) {
+        return infoMapper.selectById(id);
+    }
+
+    @Override
+    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);
+    }
+
+    // ==================== 子表(报销信息子) ====================
+
+    @Override
+    public List<ExpenseInfoObjDO> getExpenseInfoObjListByExpenseId(Long expenseId) {
+        return expenseInfoObjMapper.selectListByExpenseId(expenseId);
+    }
+
+    private void createExpenseInfoObjList(Long expenseId, List<ExpenseInfoObjDO> list) {
+        list.forEach(o -> o.setExpenseId(expenseId));
+        expenseInfoObjMapper.insertBatch(list);
+    }
+
+    private void updateExpenseInfoObjList(Long expenseId, List<ExpenseInfoObjDO> list) {
+        deleteExpenseInfoObjByExpenseId(expenseId);
+		list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null).setCreateTime(null).setDeleted(null)); // 解决更新情况下:1)id 冲突;2)updateTime 不更新
+        createExpenseInfoObjList(expenseId, list);
+    }
+
+    private void deleteExpenseInfoObjByExpenseId(Long expenseId) {
+        expenseInfoObjMapper.deleteByExpenseId(expenseId);
+    }
+
+}

+ 7 - 1
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expenseitem/ExpenseItemServiceImpl.java

@@ -16,9 +16,10 @@ import javax.annotation.Resource;
 import java.util.Collection;
 import java.util.Collections;
 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.EXPENSE_ITEM_NOT_EXISTS;
+import static cn.iocoder.yudao.module.expense.enums.ErrorCodeConstants.*;
 
 /**
  * 报销费用项目信息 Service 实现类
@@ -34,6 +35,10 @@ public class ExpenseItemServiceImpl implements ExpenseItemService {
 
     @Override
     public Long createItem(ExpenseItemSaveReqVO createReqVO) {
+        Long expenseTypeId = createReqVO.getExpenseTypeId();
+        if (Objects.isNull(expenseTypeId)) {
+            throw exception(EXPENSE_TYPE_ID_NULL);
+        }
         // 插入
         ExpenseItemDO item = BeanUtils.toBean(createReqVO, ExpenseItemDO.class);
         item.setExpenseItemUuid(IdUtil.fastSimpleUUID());
@@ -48,6 +53,7 @@ public class ExpenseItemServiceImpl implements ExpenseItemService {
         validateItemExists(updateReqVO.getId());
         // 更新
         ExpenseItemDO updateObj = BeanUtils.toBean(updateReqVO, ExpenseItemDO.class);
+        updateObj.setExpenseTypeId(null);
         itemMapper.updateById(updateObj);
     }
 

+ 9 - 0
yudao-module-finance/yudao-module-expense-biz/src/main/java/cn/iocoder/yudao/module/expense/service/expensetype/ExpenseTypeServiceImpl.java

@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.expense.enums.ErrorCodeConstants.EXPENSE_ITEM_EXISTS;
 import static cn.iocoder.yudao.module.expense.enums.ErrorCodeConstants.EXPENSE_TYPE_NOT_EXISTS;
 
 /**
@@ -70,6 +71,7 @@ public class ExpenseTypeServiceImpl implements ExpenseTypeService {
     public void deleteType(Long id) {
         // 校验存在
         validateTypeExists(id);
+        validateTypeItemExists(id);
         // 删除
         typeMapper.deleteById(id);
 
@@ -77,6 +79,13 @@ public class ExpenseTypeServiceImpl implements ExpenseTypeService {
         // deleteItemByExpenseTypeId(id);
     }
 
+    private void validateTypeItemExists(Long id) {
+        List<ExpenseItemDO> expenseItemDOList = itemMapper.selectListByExpenseTypeId(id);
+        if (CollectionUtil.isNotEmpty(expenseItemDOList)) {
+            throw exception(EXPENSE_ITEM_EXISTS);
+        }
+    }
+
     private void validateTypeExists(Long id) {
         if (typeMapper.selectById(id) == null) {
             throw exception(EXPENSE_TYPE_NOT_EXISTS);

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

@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="cn.iocoder.yudao.module.expense.dal.mysql.expenseinfo.ExpenseInfoMapper">
+
+    <!--
+        一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
+        无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
+        代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
+        文档可见: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 - 0
yudao-module-finance/yudao-module-expense-biz/src/test/java/cn/iocoder/yudao/module/expense/service/expenseinfo/ExpenseInfoServiceImplTest.java

@@ -0,0 +1,200 @@
+// 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));
+//     }
+//
+// }

+ 0 - 1
yudao-module-personnel/yudao-module-employee-api/src/main/java/cn/iocoder/yudao/module/employee/api/dto/EmployeeRespDTO.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.employee.api.dto;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.*;
 
-import java.math.BigDecimal;
 import java.time.LocalDate;
 
 /**

+ 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);
+    }
+
 }