Browse Source

Merge branch 'milestone-20251016-现金管理' of http://39.101.133.168:8807/huangqizhen/gold-java into lijianlin/feature-202509231533026-现金管理-收款管理

lijianlin/feature-202509231533026-现金管理-收款管理
lijianlin 1 month ago
parent
commit
68e623029a
  1. 5
      pom.xml
  2. 6
      src/main/java/com/example/demo/DemoApplication.java
  3. 96
      src/main/java/com/example/demo/Node/RefundApprovalNode.java
  4. 37
      src/main/java/com/example/demo/config/FlowableDataSourceConfig.java
  5. 70
      src/main/java/com/example/demo/config/cash/DataInitializer.java
  6. 92
      src/main/java/com/example/demo/config/cash/RefundApprovalFlowConfig.java
  7. 69
      src/main/java/com/example/demo/config/work/FlowableEngineConfig.java
  8. 310
      src/main/java/com/example/demo/controller/cash/CashRefundController.java
  9. 8
      src/main/java/com/example/demo/domain/entity/User.java
  10. 244
      src/main/java/com/example/demo/domain/vo/cash/CashRecord.java
  11. 43
      src/main/java/com/example/demo/domain/vo/cash/Market.java
  12. 43
      src/main/java/com/example/demo/domain/vo/cash/RefundApprovalRecord.java
  13. 84
      src/main/java/com/example/demo/domain/vo/cash/User.java
  14. 173
      src/main/java/com/example/demo/service/cash/MarketService.java
  15. 221
      src/main/java/com/example/demo/service/cash/ProcessService.java
  16. 286
      src/main/java/com/example/demo/service/cash/RefundApprovalService.java
  17. 9
      src/main/resources/application-dev.yml
  18. 13
      src/main/resources/application-test.yml
  19. 5
      src/main/resources/cashMapper/CashRecordMapper.xml

5
pom.xml

@ -75,11 +75,6 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>7.0.0</version> <!-- 请查看最新版本:https://mvnrepository.com/artifact/org.flowable -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

6
src/main/java/com/example/demo/DemoApplication.java

@ -1,16 +1,14 @@
package com.example.demo;
import org.flowable.spring.boot.ProcessEngineAutoConfiguration;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling // 启用调度功能
@SpringBootApplication(exclude = {
ProcessEngineAutoConfiguration.class,
})
@SpringBootApplication
@MapperScan(basePackages = "com.example.demo.mapper.coin", sqlSessionTemplateRef = "mysql1SqlSessionTemplate")
@MapperScan(basePackages = "com.example.demo.mapper.bean", sqlSessionTemplateRef = "mysql2SqlSessionTemplate")
@MapperScan(basePackages = "com.example.demo.mapper.pay", sqlSessionTemplateRef = "mysql3SqlSessionTemplate")

96
src/main/java/com/example/demo/Node/RefundApprovalNode.java

@ -0,0 +1,96 @@
package com.example.demo.Node;
import lombok.Data;
import java.util.HashSet;
import java.util.Set;
@Data
public class RefundApprovalNode {
private String nodeName;
private Integer level;
private Set<String> roles; // 审批所需的角色集合
private Set<Integer> allowedMarketIds; // 允许审批的地区ID集合
private Set<String> allowedMarketNames; // 允许审批的地区名称集合向后兼容
private boolean required;
public RefundApprovalNode() {
this.roles = new HashSet<>();
this.allowedMarketIds = new HashSet<>();
this.allowedMarketNames = new HashSet<>();
}
public RefundApprovalNode(String nodeName, Integer level, String role, boolean required) {
this();
this.nodeName = nodeName;
this.level = level;
if (role != null) {
this.roles.add(role);
}
this.required = required;
}
public RefundApprovalNode(String nodeName, Integer level, Set<String> roles, Set<Integer> allowedMarketIds, boolean required) {
this.nodeName = nodeName;
this.level = level;
this.roles = roles != null ? roles : new HashSet<>();
this.allowedMarketIds = allowedMarketIds != null ? allowedMarketIds : new HashSet<>();
this.allowedMarketNames = new HashSet<>();
this.required = required;
}
// 构造函数接受多个角色数组形式和地区ID集合
public RefundApprovalNode(String nodeName, Integer level, String[] roles, Set<Integer> allowedMarketIds, boolean required) {
this();
this.nodeName = nodeName;
this.level = level;
if (roles != null) {
for (String role : roles) {
this.roles.add(role);
}
}
this.allowedMarketIds = allowedMarketIds != null ? allowedMarketIds : new HashSet<>();
this.required = required;
}
public void addRole(String role) {
if (this.roles == null) {
this.roles = new HashSet<>();
}
this.roles.add(role);
}
public void addMarketId(Integer marketId) {
if (this.allowedMarketIds == null) {
this.allowedMarketIds = new HashSet<>();
}
this.allowedMarketIds.add(marketId);
}
public void addMarketName(String marketName) {
if (this.allowedMarketNames == null) {
this.allowedMarketNames = new HashSet<>();
}
this.allowedMarketNames.add(marketName);
}
// 检查节点是否对指定地区ID可见
public boolean isMarketAllowed(Integer marketId) {
if (allowedMarketIds == null || allowedMarketIds.isEmpty()) {
return true; // 没有地区限制所有地区都可见
}
return allowedMarketIds.contains(marketId);
}
// 检查节点是否对指定地区名称可见向后兼容
public boolean isMarketAllowedByName(String marketName) {
if ((allowedMarketNames == null || allowedMarketNames.isEmpty()) &&
(allowedMarketIds == null || allowedMarketIds.isEmpty())) {
return true; // 没有地区限制所有地区都可见
}
if (allowedMarketNames != null && !allowedMarketNames.isEmpty()) {
return allowedMarketNames.contains(marketName);
}
return false;
}
}

37
src/main/java/com/example/demo/config/FlowableDataSourceConfig.java

@ -1,37 +0,0 @@
// FlowableDataSourceConfig.java
package com.example.demo.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class FlowableDataSourceConfig {
@Bean(name = "flowableDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.flowable")
public DataSource flowableDataSource() {
HikariDataSource ds = DataSourceBuilder
.create()
.type(HikariDataSource.class)
.build();
ds.setConnectionInitSql("SET SESSION sql_mode='TRADITIONAL';");
return ds;
}
@Bean(name = "flowableTransactionManager")
@Primary
public PlatformTransactionManager flowableTransactionManager(
@Qualifier("flowableDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

70
src/main/java/com/example/demo/config/cash/DataInitializer.java

@ -0,0 +1,70 @@
package com.example.demo.config.cash;
import com.example.demo.domain.vo.cash.Market;
import com.example.demo.service.cash.MarketService;
import com.example.demo.service.cash.RefundApprovalService;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
@Component
@RequiredArgsConstructor
public class DataInitializer implements CommandLineRunner {
private final MarketService marketService;
private final RefundApprovalService refundApprovalService;
@Override
public void run(String... args) throws Exception {
// 初始化地区数据
initializeMarketData();
}
private void initializeMarketData() {
List<Market> markets = Arrays.asList(
createMarket(1, "Capt", -1, "Capt-Capt-Capt-Capt", 0),
createMarket(2, "公司", 1, "Capt-公司-公司-公司", 1),
createMarket(3, "市场", 1, "Capt-市场部-市场部-市场部", 1),
createMarket(4, "新加坡", 3, "Capt-市场部-新加坡-新加坡", 2),
createMarket(5, "马来西亚", 3, "Capt-市场部-马来西亚-马来西亚", 2),
createMarket(9, "研发部", 2, "Capt-公司-研发部-研发部", 2),
createMarket(13, "香港", 3, "Capt-市场部-香港-香港", 2),
createMarket(9999, "总部", 2, "Capt-公司-总部-总部", 2),
createMarket(24016, "加拿大", 3, "Capt-市场部-加拿大-加拿大", 2),
createMarket(24018, "泰国", 3, "Capt-市场部-泰国-泰国", 2),
createMarket(24022, "越南HCM", 3, "Capt-市场部-越南HCM-越南HCM", 2),
createMarket(24027, "韩国", 3, "Capt-市场部-韩国-韩国", 2),
createMarket(24028, "深圳运营", 3, "Capt-市场部-深圳运营-深圳运营", 2),
createMarket(24030, "未知", 3, "Capt-市场部-未知", 2),
createMarket(24031, "其他", 3, "Capt-市场部-其他", 2),
createMarket(24032, "市场部", 3, "Capt-市场部-市场部", 2)
);
marketService.initializeMarkets(markets);
// 为特定市场初始化审批流程
refundApprovalService.initializeFlowForMarketName("新加坡");
refundApprovalService.initializeFlowForMarketName("马来西亚");
refundApprovalService.initializeFlowForMarketName("香港");
refundApprovalService.initializeFlowForMarketName("加拿大");
refundApprovalService.initializeFlowForMarketName("泰国");
refundApprovalService.initializeFlowForMarketName("越南HCM");
refundApprovalService.initializeFlowForMarketName("韩国");
refundApprovalService.initializeFlowForMarketName("深圳运营");
}
private Market createMarket(Integer id, String name, Integer parentId, String treelist, Integer type) {
Market market = new Market();
market.setId(id);
market.setName(name);
market.setParentId(parentId);
market.setTreelist(treelist);
market.setType(type);
return market;
}
}

92
src/main/java/com/example/demo/config/cash/RefundApprovalFlowConfig.java

@ -0,0 +1,92 @@
package com.example.demo.config.cash;
import com.example.demo.Node.RefundApprovalNode;
import com.example.demo.domain.vo.cash.User;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class RefundApprovalFlowConfig {
private String flowName;
private List<RefundApprovalNode> nodes;
public RefundApprovalFlowConfig() {
this.nodes = new ArrayList<>();
}
public RefundApprovalFlowConfig(String flowName, List<RefundApprovalNode> nodes) {
this.flowName = flowName;
this.nodes = nodes != null ? nodes : new ArrayList<>();
}
// 获取用户可见的状态列表基于角色和地区ID权限
public List<Integer> getVisibleStatuses(User user, Integer orderMarketId) {
List<Integer> visibleStatuses = new ArrayList<>();
for (RefundApprovalNode node : nodes) {
if (isNodeVisibleToUser(node, user, orderMarketId)) {
visibleStatuses.add(node.getLevel());
}
}
return visibleStatuses;
}
// 判断节点对用户是否可见基于角色和地区ID权限
public boolean isNodeVisibleToUser(RefundApprovalNode node, User user, Integer orderMarketId) {
if (node == null || user == null) return false;
// 检查角色权限
boolean hasRolePermission = true;
if (node.getRoles() != null && !node.getRoles().isEmpty()) {
hasRolePermission = user.hasAnyRole(node.getRoles());
}
// 检查地区权限
boolean hasMarketPermission = true;
if (node.getAllowedMarketIds() != null && !node.getAllowedMarketIds().isEmpty()) {
// 如果节点限制了地区用户需要在这些地区有权限
hasMarketPermission = user.canApproveAnyMarket(node.getAllowedMarketIds());
} else if (orderMarketId != null) {
// 如果节点没有限制地区但订单有地区用户需要在该订单地区有权限
hasMarketPermission = user.canApproveMarket(orderMarketId);
}
return hasRolePermission && hasMarketPermission;
}
// 判断用户是否可以审批某个节点的订单
public boolean canUserApproveNode(User user, Integer level, Integer orderMarketId) {
RefundApprovalNode node = findNodeByLevel(level);
if (node == null) return false;
return isNodeVisibleToUser(node, user, orderMarketId);
}
// 根据级别查找节点
public RefundApprovalNode findNodeByLevel(Integer level) {
if (nodes == null) return null;
return nodes.stream()
.filter(node -> level.equals(node.getLevel()))
.findFirst()
.orElse(null);
}
// 获取下一个审批节点
public RefundApprovalNode getNextNode(Integer currentLevel) {
if (nodes == null || nodes.isEmpty()) return null;
return nodes.stream()
.filter(node -> node.getLevel() > currentLevel)
.min((a, b) -> a.getLevel().compareTo(b.getLevel()))
.orElse(null);
}
// 获取所有涉及的市场ID
public java.util.Set<Integer> getAllInvolvedMarketIds() {
return nodes.stream()
.filter(node -> node.getAllowedMarketIds() != null)
.flatMap(node -> node.getAllowedMarketIds().stream())
.collect(java.util.stream.Collectors.toSet());
}
}

69
src/main/java/com/example/demo/config/work/FlowableEngineConfig.java

@ -1,69 +0,0 @@
package com.example.demo.config.work;
import org.flowable.common.engine.impl.history.HistoryLevel;
import org.flowable.engine.*;
import org.flowable.spring.ProcessEngineFactoryBean;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class FlowableEngineConfig {
/**
* 1. 创建 ProcessEngineFactoryBean
* Spring 会负责调用 buildProcessEngine() 并缓存实例
*/
@Bean
public ProcessEngineFactoryBean processEngineFactory(
@Qualifier("flowableDataSource") DataSource dataSource,
@Qualifier("flowableTransactionManager") PlatformTransactionManager transactionManager) {
SpringProcessEngineConfiguration cfg = new SpringProcessEngineConfiguration();
cfg.setDataSource(dataSource);
cfg.setTransactionManager(transactionManager);
cfg.setDatabaseSchemaUpdate("false"); // 自动建/升级表
cfg.setHistoryLevel(HistoryLevel.FULL); // 记录完整历史
cfg.setAsyncExecutorActivate(false); // 暂不开启异步执行器
ProcessEngineFactoryBean factory = new ProcessEngineFactoryBean();
factory.setProcessEngineConfiguration(cfg);
return factory;
}
/* 2. 各类 Service 注入 —— 都取自同一个 ProcessEngine *********************/
@Bean
public RepositoryService repositoryService(ProcessEngine processEngine) {
return processEngine.getRepositoryService();
}
@Bean
public RuntimeService runtimeService(ProcessEngine processEngine) {
return processEngine.getRuntimeService();
}
@Bean
public TaskService taskService(ProcessEngine processEngine) {
return processEngine.getTaskService();
}
@Bean
public HistoryService historyService(ProcessEngine processEngine) {
return processEngine.getHistoryService();
}
@Bean
public ManagementService managementService(ProcessEngine processEngine) {
return processEngine.getManagementService();
}
@Bean
public IdentityService identityService(ProcessEngine processEngine) {
return processEngine.getIdentityService();
}
}

310
src/main/java/com/example/demo/controller/cash/CashRefundController.java

@ -1,215 +1,95 @@
//package com.example.demo.controller.cash;
//
//
//import com.example.demo.Util.JWTUtil;
//import com.example.demo.domain.entity.Admin;
//import com.example.demo.domain.vo.cash.CashCollection;
//import com.example.demo.domain.vo.cash.CashRecordDone;
//import com.example.demo.domain.vo.coin.Page;
//import com.example.demo.domain.vo.coin.Result;
//import com.example.demo.service.cash.ProcessService;
//import com.example.demo.service.coin.MarketService;
//import com.example.demo.serviceImpl.cash.CashRefundServiceImpl;
//import com.github.pagehelper.PageInfo;
//import jakarta.annotation.Resource;
//import jakarta.servlet.http.HttpServletRequest;
//import lombok.RequiredArgsConstructor;
//import lombok.extern.slf4j.Slf4j;
//import org.apache.commons.lang3.StringUtils;
//import org.flowable.engine.ProcessEngine;
//import org.flowable.engine.RepositoryService;
//import org.flowable.engine.RuntimeService;
//import org.flowable.engine.TaskService;
//import org.flowable.task.api.Task;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.util.ObjectUtils;
//import org.springframework.web.bind.annotation.*;
//import org.springframework.web.context.request.RequestContextHolder;
//import org.springframework.web.context.request.ServletRequestAttributes;
//
//import java.util.Arrays;
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//
///**
// * @program: GOLD
// * @ClassName RefundController
// * @description:
// * @author: huangqizhen
// * @create: 202509-26 14:15
// * @Version 1.0
// **/
//@RestController
//@RequestMapping("/Money")
//@RequiredArgsConstructor
//@Slf4j
//@CrossOrigin
//public class CashRefundController {
// @Autowired
// private CashRefundServiceImpl cashRefundServiceImpl;
// @Autowired
// MarketService marketService;
// @Resource
// private RepositoryService repositoryService;
// @Resource
// private RuntimeService runtimeService;
// @Resource
// private TaskService taskService;
// @Resource
// private ProcessEngine processEngine;
// @Autowired
// private ProcessService processService;
// @PostMapping("/select")
// public Result select(@RequestBody Page page) throws Exception {
// // 获取当前请求对象
// HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// String token = request.getHeader("token");
//
//// 解析 token 获取用户信息
// Admin admin = (Admin) JWTUtil.getUserDetailsList(String.valueOf(token), Admin.class);
// List<String> userMarkets = Arrays.asList(StringUtils.split(admin.getMarkets(), ","));
// List<String> markets = marketService.getMarketIds(userMarkets);
//
//// 校验分页参数
// if (ObjectUtils.isEmpty(page.getPageNum())) {
// return Result.error("页码数为空!");
// }
// if (ObjectUtils.isEmpty(page.getPageSize())) {
// return Result.error("页大小为空!");
// }
//
//// 获取传入的市场列表
// List<String> requestedMarkets = page.getCashRecordDone() != null ? page.getCashRecordDone().getMarkets() : null;
//
//// 权限校验逻辑
// if (markets.contains("9") || markets.contains("9999")) {
// // 特权市场9 9999跳过权限校验直接放行传入的 markets
// // 如果业务需要也可以在这里做空值处理
// if (page.getCashRecordDone() != null) {
// // 保持 requestedMarkets 不变原样接受
// // 可选如果 requestedMarkets null可设为默认值或保持 null
// }
// } else {
// // 普通用户必须校验权限
// if (requestedMarkets == null || requestedMarkets.isEmpty()) {
// page.getCashRecordDone().setMarkets(requestedMarkets);
// }
// if (!markets.containsAll(requestedMarkets)) {
// return Result.error("无权限!请求的市场不在授权范围内。");
// }
// // 校验通过保持 requestedMarkets 不变
// }
// return Result.success(cashRefundServiceImpl.select(page.getPageNum(),page.getPageSize(),page.getCashRecordDone()));
// }
// @PostMapping("/add")
// public Result add(@RequestBody CashRecordDone cashCollection){
// return Result.success(cashRefundServiceImpl.add(cashCollection));
// }
// @PostMapping("/update")
// public Result update(@RequestBody CashRecordDone cashRecordDone){
// return Result.success(cashRefundServiceImpl.update(cashRecordDone));
// }
//
//
//
//
//
//
//
// /**
// * 当地财务审核
// */
// @PostMapping("/local-finance/approve")
// public Map<String, Object> localFinanceApprove(
// @RequestParam String taskId,
// @RequestParam boolean approved,
// @RequestParam(required = false) String comment) {
//
// processService.localFinanceApprove(taskId, approved, comment);
//
// Map<String, Object> result = new HashMap<>();
// result.put("success", true);
// result.put("message", approved ? "当地财务审核通过" : "当地财务已驳回");
//
// return result;
// }
//
// /**
// * 地区负责人审核
// */
// @PostMapping("/regional-manager/approve")
// public Map<String, Object> regionalManagerApprove(
// @RequestParam String taskId,
// @RequestParam boolean approved,
// @RequestParam(required = false) String comment) {
//
// processService.regionalManagerApprove(taskId, approved, comment);
//
// Map<String, Object> result = new HashMap<>();
// result.put("success", true);
// result.put("message", approved ? "地区负责人审核通过" : "地区负责人已驳回");
//
// return result;
// }
//
// /**
// * 总部财务审批
// */
// @PostMapping("/head-finance/approve")
// public Map<String, Object> headFinanceApprove(
// @RequestParam String taskId,
// @RequestParam boolean approved,
// @RequestParam(required = false) String comment) {
//
// processService.headFinanceApprove(taskId, approved, comment);
//
// Map<String, Object> result = new HashMap<>();
// result.put("success", true);
// result.put("message", approved ? "总部财务审批通过" : "总部财务已驳回");
//
// return result;
// }
//
// /**
// * 提交审批申请
// */
// @PostMapping("/submit")
// public Map<String, Object> submitApproval(@RequestBody CashRecordDone order) {
// return processService.submitApproval(order);
// }
//
// /**
// * 获取待办审批列表
// */
// @GetMapping("/pending/{assignee}")
// public Map<String, Object> getPendingApprovals(@PathVariable String assignee) {
// List<CashRecordDone> approvals = processService.getPendingApprovals(assignee);
//
// Map<String, Object> result = new HashMap<>();
// result.put("success", true);
// result.put("data", approvals);
// result.put("count", approvals.size());
//
// return result;
// }
//
// /**
// * 获取审批详情
// */
// @GetMapping("/detail/{orderId}")
// public Map<String, Object> getApprovalDetail(@PathVariable Long orderId) {
// Map<String, Object> detail = processService.getApprovalDetail(orderId);
//
// Map<String, Object> result = new HashMap<>();
// if (detail != null) {
// result.put("success", true);
// result.put("data", detail);
// } else {
// result.put("success", false);
// result.put("message", "审批单不存在");
// }
//
// return result;
// }
//}
package com.example.demo.controller.cash;
import com.example.demo.Util.JWTUtil;
import com.example.demo.domain.entity.Admin;
import com.example.demo.domain.vo.cash.CashRecordDone;
import com.example.demo.domain.vo.coin.Page;
import com.example.demo.domain.vo.coin.Result;
import com.example.demo.service.coin.MarketService;
import com.example.demo.serviceImpl.cash.CashRefundServiceImpl;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Arrays;
import java.util.List;
/**
* @program: GOLD
* @ClassName RefundController
* @description:
* @author: huangqizhen
* @create: 202509-26 14:15
* @Version 1.0
**/
@RestController
@RequestMapping("/Money")
@RequiredArgsConstructor
@Slf4j
@CrossOrigin
public class CashRefundController {
@Autowired
private CashRefundServiceImpl cashRefundServiceImpl;
@Autowired
MarketService marketService;
@PostMapping("/select")
public Result select(@RequestBody Page page) throws Exception {
// 获取当前请求对象
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
// 解析 token 获取用户信息
Admin admin = (Admin) JWTUtil.getUserDetailsList(String.valueOf(token), Admin.class);
List<String> userMarkets = Arrays.asList(StringUtils.split(admin.getMarkets(), ","));
List<String> markets = marketService.getMarketIds(userMarkets);
// 校验分页参数
if (ObjectUtils.isEmpty(page.getPageNum())) {
return Result.error("页码数为空!");
}
if (ObjectUtils.isEmpty(page.getPageSize())) {
return Result.error("页大小为空!");
}
// 获取传入的市场列表
List<String> requestedMarkets = page.getCashRecordDone() != null ? page.getCashRecordDone().getMarkets() : null;
// 权限校验逻辑
if (markets.contains("9") || markets.contains("9999")) {
// 特权市场9 9999跳过权限校验直接放行传入的 markets
// 如果业务需要也可以在这里做空值处理
if (page.getCashRecordDone() != null) {
// 保持 requestedMarkets 不变原样接受
// 可选如果 requestedMarkets null可设为默认值或保持 null
}
} else {
// 普通用户必须校验权限
if (requestedMarkets == null || requestedMarkets.isEmpty()) {
page.getCashRecordDone().setMarkets(requestedMarkets);
}
if (!markets.containsAll(requestedMarkets)) {
return Result.error("无权限!请求的市场不在授权范围内。");
}
// 校验通过保持 requestedMarkets 不变
}
return Result.success(cashRefundServiceImpl.select(page.getPageNum(),page.getPageSize(),page.getCashRecordDone()));
}
@PostMapping("/add")
public Result add(@RequestBody CashRecordDone cashCollection){
return Result.success(cashRefundServiceImpl.add(cashCollection));
}
@PostMapping("/update")
public Result update(@RequestBody CashRecordDone cashRecordDone){
return Result.success(cashRefundServiceImpl.update(cashRecordDone));
}
}

8
src/main/java/com/example/demo/domain/entity/User.java

@ -10,7 +10,9 @@ import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Data
@NoArgsConstructor
@ -76,4 +78,10 @@ public class User implements Serializable {
private Integer flag;//是否员工号
@ExcelIgnore
private Integer UserFlag;//是否员工号
@ExcelIgnore
private Set<String> roles = new HashSet<>(); // 用户角色集合
public boolean hasRole(String role) {
return roles != null && roles.contains(role);
}
}

244
src/main/java/com/example/demo/domain/vo/cash/CashRecord.java

@ -0,0 +1,244 @@
package com.example.demo.domain.vo.cash;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
@Data
public class CashRecord {
@ExcelProperty("序号")
private Integer id;
@ExcelProperty("订单类型")
private Integer orderType; // 1:收款2:退款
@ExcelProperty("精网号")
private Integer jwcode;
@ExcelProperty("姓名")
private String name;
@ExcelProperty("所属地区")
private String market;
@ExcelProperty("活动名称")
private String activity;
@ExcelProperty("金币订单号")
private String orderCode;
@ExcelProperty("银行流水订单号")
private String bankCode;
@ExcelProperty("商品名称")
private String goodsName;
@ExcelProperty("产品数量")
private Integer goodNum;
@ExcelProperty("币种")
private String paymentCurrency;
@ExcelProperty("金额")
private BigDecimal paymentAmount;
@ExcelProperty("到账币种")
private String receivedCurrency;
@ExcelProperty("到账金额")
private BigDecimal receivedAmount;
@ExcelProperty("手续费")
private BigDecimal handlingCharge;
@ExcelProperty("支付方式")
private String payType;
@ExcelProperty("到账地区")
private String receivedMarket;
@ExcelProperty("付款日期")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date payTime;
@ExcelProperty("到账日期")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date receivedTime;
@ExcelIgnore
private Integer auditId;
@ExcelProperty("订单状态")
private Integer status;
@ExcelIgnore
private Integer submitterId;
@ExcelProperty("转账凭证")
private String voucher;
@ExcelProperty("备注")
private String remark;
@ExcelProperty("驳回理由")
private String rejectReason;
@ExcelProperty("退款理由")
private String refundReason;
@ExcelProperty("退款方式")
private Integer refundModel;
@ExcelProperty("退款执行人")
private Integer executor;
@ExcelProperty("退款途径")
private String refundChannels;
@ExcelProperty("退款日期")
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "Asia/Shanghai")
private Date refundTime;
@ExcelProperty("退款备注")
private String refundRemark;
@ExcelProperty("退款截图")
private String refundVoucher;
@ExcelProperty("创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createTime;
@ExcelProperty("更新时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date updateTime;
@ExcelProperty("审核时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date auditTime;
@ExcelProperty("收款备注")
private String receivedRemark;
@ExcelIgnore
private Integer marketId; // 关联market表的id
@ExcelIgnore
private Integer receivedMarketId; // 到账地区ID
// 获取订单的主要地区ID用于审批权限判断
public Integer getPrimaryMarketId() {
return marketId != null ? marketId : (receivedMarketId != null ? receivedMarketId : null);
}
// 获取订单的主要地区名称
public String getPrimaryMarketName() {
return market != null ? market : (receivedMarket != null ? receivedMarket : null);
}
// 退款状态常量
public static final Integer STATUS_REFUND_REGIONAL_FINANCE_PENDING = 10; // 地区财务待审核
public static final Integer STATUS_REFUND_WITHDRAWN = 11; // 手动撤回待编辑提交
public static final Integer STATUS_REFUND_REGIONAL_FINANCE_REJECTED = 12; // 地区财务驳回
public static final Integer STATUS_REFUND_REGIONAL_MANAGER_PENDING = 20; // 地区负责人待审核
public static final Integer STATUS_REFUND_REGIONAL_MANAGER_REJECTED = 22; // 地区负责人驳回
public static final Integer STATUS_REFUND_HEADQUARTERS_FINANCE_PENDING = 30; // 总部财务待审核
public static final Integer STATUS_REFUND_HEADQUARTERS_FINANCE_REJECTED = 32; // 总部财务驳回
public static final Integer STATUS_REFUND_EXECUTOR_PENDING = 40; // 执行人待处理
public static final Integer STATUS_REFUND_COMPLETED = 41; // 执行人已处理退款结束
// 获取当前审批级别只处理退款订单
public Integer getCurrentApprovalLevel() {
if (!isRefundOrder()) {
return 0;
}
switch (status) {
case 10: return 1; // 地区财务待审核
case 11: return 0; // 手动撤回待编辑提交
case 12: return -1; // 地区财务驳回
case 20: return 2; // 地区负责人待审核
case 22: return -2; // 地区负责人驳回
case 30: return 3; // 总部财务待审核
case 32: return -3; // 总部财务驳回
case 40: return 4; // 执行人待处理
case 41: return 5; // 退款结束
default: return 0;
}
}
// 检查是否为退款订单
public boolean isRefundOrder() {
return orderType != null && orderType == 2;
}
// 检查是否可以撤回只有在地区财务待审核状态可以撤回
public boolean canWithdraw() {
return isRefundOrder() && status != null && status == STATUS_REFUND_REGIONAL_FINANCE_PENDING;
}
// 检查是否可以重新提交只有在撤回状态可以重新提交
public boolean canResubmit() {
return isRefundOrder() && status != null && status == STATUS_REFUND_WITHDRAWN;
}
// 检查是否在审批流程中
public boolean isInApprovalProcess() {
return isRefundOrder() && status != null &&
(status == STATUS_REFUND_REGIONAL_FINANCE_PENDING ||
status == STATUS_REFUND_REGIONAL_MANAGER_PENDING ||
status == STATUS_REFUND_HEADQUARTERS_FINANCE_PENDING ||
status == STATUS_REFUND_EXECUTOR_PENDING);
}
// 获取下一个状态
public Integer getNextStatus(boolean approved) {
if (!isRefundOrder()) {
return status;
}
if (!approved) {
// 驳回状态
switch (status) {
case 10: return STATUS_REFUND_REGIONAL_FINANCE_REJECTED;
case 20: return STATUS_REFUND_REGIONAL_MANAGER_REJECTED;
case 30: return STATUS_REFUND_HEADQUARTERS_FINANCE_REJECTED;
default: return status;
}
}
// 审批通过状态
switch (status) {
case 10: return STATUS_REFUND_REGIONAL_MANAGER_PENDING;
case 20: return STATUS_REFUND_HEADQUARTERS_FINANCE_PENDING;
case 30: return STATUS_REFUND_EXECUTOR_PENDING;
case 40: return STATUS_REFUND_COMPLETED;
default: return status;
}
}
// 撤回订单
public boolean withdraw() {
if (!canWithdraw()) {
return false;
}
this.status = STATUS_REFUND_WITHDRAWN;
this.updateTime = new Date();
return true;
}
// 重新提交订单
public boolean resubmit() {
if (!canResubmit()) {
return false;
}
this.status = STATUS_REFUND_REGIONAL_FINANCE_PENDING;
this.updateTime = new Date();
return true;
}
}

43
src/main/java/com/example/demo/domain/vo/cash/Market.java

@ -0,0 +1,43 @@
package com.example.demo.domain.vo.cash;
import lombok.Data;
import java.util.Date;
@Data
public class Market {
private Integer id;
private String name; // 地区名称
private Integer parentId; // 父级ID
private Date createTime; // 创建时间
private String treelist; // 树形路径
private Integer type; // 等级类型
// 获取地区层级
public Integer getLevel() {
if (treelist == null) return 0;
String[] levels = treelist.split("-");
return levels.length - 1; // 减去根节点
}
// 判断是否为根节点
public boolean isRoot() {
return parentId == -1;
}
// 判断是否为市场部节点
public boolean isMarketDepartment() {
return parentId != null && parentId == 3; // 市场部的父ID是3
}
// 判断是否为具体市场叶子节点
public boolean isSpecificMarket() {
return type != null && type == 2; // 等级类型为2的是具体市场
}
// 获取根节点名称
public String getRootName() {
if (treelist == null) return null;
String[] levels = treelist.split("-");
return levels.length > 0 ? levels[0] : null;
}
}

43
src/main/java/com/example/demo/domain/vo/cash/RefundApprovalRecord.java

@ -0,0 +1,43 @@
package com.example.demo.domain.vo.cash;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
@Data
public class RefundApprovalRecord {
private Integer id;
private Integer cashRecordId;
private Integer approvalLevel;
private String approvalNode;
private Integer approverId;
private String approverName;
private String approvalResult; // APPROVED, REJECTED
private String comments;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date approvalTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date createTime;
// 撤回记录相关字段
private Boolean isWithdrawal = false;
private Integer withdrawnBy;
private String withdrawReason;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date withdrawTime;
public RefundApprovalRecord() {}
public RefundApprovalRecord(Integer cashRecordId, Integer approvalLevel, String approvalNode,
Integer approverId, String approverName) {
this.cashRecordId = cashRecordId;
this.approvalLevel = approvalLevel;
this.approvalNode = approvalNode;
this.approverId = approverId;
this.approverName = approverName;
this.createTime = new Date();
}
}

84
src/main/java/com/example/demo/domain/vo/cash/User.java

@ -0,0 +1,84 @@
package com.example.demo.domain.vo.cash;
import lombok.Data;
import java.util.HashSet;
import java.util.Set;
@Data
public class User {
private Integer id;
private String name;
private Integer marketId; // 关联market表的id
private String marketName; // 地区名称
private Set<String> roles = new HashSet<>();
private Set<Integer> managedMarketIds = new HashSet<>(); // 管辖的地区ID列表
private Set<String> managedMarketNames = new HashSet<>(); // 管辖的地区名称列表
// 角色权限检查
public boolean hasRole(String role) {
return roles != null && roles.contains(role);
}
public boolean hasAnyRole(Set<String> requiredRoles) {
if (roles == null || requiredRoles == null) return false;
return requiredRoles.stream().anyMatch(roles::contains);
}
// 地区权限检查 - 基于地区ID
public boolean canApproveMarket(Integer targetMarketId) {
if (targetMarketId == null) return false;
// 优先检查管辖地区ID列表
if (managedMarketIds != null && !managedMarketIds.isEmpty()) {
return managedMarketIds.contains(targetMarketId);
}
// 如果没有管辖地区列表使用单个marketId字段
return targetMarketId.equals(this.marketId);
}
public boolean canApproveAnyMarket(Set<Integer> targetMarketIds) {
if (targetMarketIds == null || targetMarketIds.isEmpty()) return false;
if (managedMarketIds != null && !managedMarketIds.isEmpty()) {
return targetMarketIds.stream().anyMatch(managedMarketIds::contains);
}
return targetMarketIds.contains(this.marketId);
}
// 地区权限检查 - 基于地区名称向后兼容
public boolean canApproveMarketByName(String targetMarketName) {
if (targetMarketName == null) return false;
if (managedMarketNames != null && !managedMarketNames.isEmpty()) {
return managedMarketNames.contains(targetMarketName);
}
return targetMarketName.equals(this.marketName);
}
// 获取用户的所有管辖地区ID
public Set<Integer> getAllManagedMarketIds() {
Set<Integer> allMarketIds = new HashSet<>();
if (managedMarketIds != null && !managedMarketIds.isEmpty()) {
allMarketIds.addAll(managedMarketIds);
}
if (marketId != null) {
allMarketIds.add(marketId);
}
return allMarketIds;
}
// 获取用户的所有管辖地区名称
public Set<String> getAllManagedMarketNames() {
Set<String> allMarketNames = new HashSet<>();
if (managedMarketNames != null && !managedMarketNames.isEmpty()) {
allMarketNames.addAll(managedMarketNames);
}
if (marketName != null) {
allMarketNames.add(marketName);
}
return allMarketNames;
}
}

173
src/main/java/com/example/demo/service/cash/MarketService.java

@ -0,0 +1,173 @@
package com.example.demo.service.cash;
import com.example.demo.domain.vo.cash.Market;
import com.example.demo.domain.vo.cash.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
@Service
public class MarketService {
// 地区数据缓存
private final Map<Integer, Market> marketCache = new ConcurrentHashMap<>();
private final Map<String, Integer> marketNameToIdCache = new ConcurrentHashMap<>();
private final Map<Integer, List<Market>> childrenCache = new ConcurrentHashMap<>();
// 初始化地区数据实际项目中应从数据库加载
public void initializeMarkets(List<Market> markets) {
marketCache.clear();
marketNameToIdCache.clear();
childrenCache.clear();
for (Market market : markets) {
marketCache.put(market.getId(), market);
marketNameToIdCache.put(market.getName(), market.getId());
}
// 构建父子关系
for (Market market : markets) {
Integer parentId = market.getParentId();
if (!childrenCache.containsKey(parentId)) {
childrenCache.put(parentId, new ArrayList<>());
}
childrenCache.get(parentId).add(market);
}
log.info("地区数据初始化完成,共加载{}个地区", markets.size());
}
// 根据ID获取地区
public Market getMarketById(Integer id) {
return marketCache.get(id);
}
// 根据名称获取地区
public Market getMarketByName(String name) {
Integer id = marketNameToIdCache.get(name);
return id != null ? marketCache.get(id) : null;
}
// 根据名称获取地区ID
public Integer getMarketIdByName(String name) {
return marketNameToIdCache.get(name);
}
// 获取所有子地区包括所有后代
public List<Market> getAllChildren(Integer parentId) {
List<Market> allChildren = new ArrayList<>();
List<Market> directChildren = childrenCache.get(parentId);
if (directChildren != null) {
allChildren.addAll(directChildren);
for (Market child : directChildren) {
allChildren.addAll(getAllChildren(child.getId()));
}
}
return allChildren;
}
// 获取所有子地区ID包括所有后代
public Set<Integer> getAllChildrenIds(Integer parentId) {
return getAllChildren(parentId).stream()
.map(Market::getId)
.collect(Collectors.toSet());
}
// 获取所有子地区名称包括所有后代
public Set<String> getAllChildrenNames(Integer parentId) {
return getAllChildren(parentId).stream()
.map(Market::getName)
.collect(Collectors.toSet());
}
// 获取直接子地区
public List<Market> getDirectChildren(Integer parentId) {
return childrenCache.getOrDefault(parentId, new ArrayList<>());
}
// 获取市场部下的所有具体市场
public List<Market> getSpecificMarketsUnderMarketDepartment() {
List<Market> specificMarkets = new ArrayList<>();
// 市场部的ID是3
List<Market> marketDepartmentChildren = getDirectChildren(3);
for (Market market : marketDepartmentChildren) {
if (market.isSpecificMarket()) {
specificMarkets.add(market);
}
}
return specificMarkets;
}
// 获取市场部下的所有具体市场ID
public Set<Integer> getSpecificMarketIdsUnderMarketDepartment() {
return getSpecificMarketsUnderMarketDepartment().stream()
.map(Market::getId)
.collect(Collectors.toSet());
}
// 获取用户管辖的所有具体市场ID包括子地区
public Set<Integer> getUserManagedSpecificMarketIds(User user) {
Set<Integer> allSpecificMarketIds = new HashSet<>();
Set<Integer> userMarketIds = user.getAllManagedMarketIds();
for (Integer marketId : userMarketIds) {
Market market = getMarketById(marketId);
if (market != null && market.isSpecificMarket()) {
allSpecificMarketIds.add(marketId);
} else {
// 如果不是具体市场获取其下的所有具体市场
allSpecificMarketIds.addAll(getAllChildrenIds(marketId).stream()
.filter(id -> {
Market child = getMarketById(id);
return child != null && child.isSpecificMarket();
})
.collect(Collectors.toSet()));
}
}
return allSpecificMarketIds;
}
// 验证地区是否存在
public boolean marketExists(Integer marketId) {
return marketCache.containsKey(marketId);
}
// 验证地区名称是否存在
public boolean marketNameExists(String marketName) {
return marketNameToIdCache.containsKey(marketName);
}
// 获取所有地区
public List<Market> getAllMarkets() {
return new ArrayList<>(marketCache.values());
}
// 获取所有具体市场叶子节点
public List<Market> getAllSpecificMarkets() {
return marketCache.values().stream()
.filter(Market::isSpecificMarket)
.collect(Collectors.toList());
}
// 根据地区ID获取完整路径
public String getMarketFullPath(Integer marketId) {
Market market = marketCache.get(marketId);
return market != null ? market.getTreelist() : null;
}
// 根据地区名称获取完整路径
public String getMarketFullPathByName(String marketName) {
Integer marketId = marketNameToIdCache.get(marketName);
return marketId != null ? getMarketFullPath(marketId) : null;
}
}

221
src/main/java/com/example/demo/service/cash/ProcessService.java

@ -1,221 +0,0 @@
//package com.example.demo.service.cash;
//
//
//import com.example.demo.domain.vo.cash.CashRecordDone;
//import com.example.demo.mapper.cash.CashRefundMapper;
//import org.flowable.engine.*;
//import org.flowable.engine.runtime.ProcessInstance;
//import org.flowable.task.api.Task;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.stereotype.Service;
//import org.springframework.transaction.annotation.Transactional;
//
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//import java.util.UUID;
//
//@Service
//@Transactional
//public class ProcessService {
//
// @Autowired
// private RuntimeService runtimeService;
//
// @Autowired
// private TaskService taskService;
//
// @Autowired
// private RepositoryService repositoryService;
//
// @Autowired
// private HistoryService historyService;
//
// @Autowired
// private CashRefundMapper cashRefundMapper; // 新增
//
// /**
// * 提交审批申请
// */
// public Map<String, Object> submitApproval(CashRecordDone order) {
// // 生成订单号
// String orderNumber = UUID.randomUUID().toString().replaceAll("-", "");
// order.setOrderCode("XJTK" + orderNumber);
// order.setStatus(10); // 待提交
// order.setOrderType(2);
//
// // 保存业务数据
// cashRefundMapper.insert(order);
//
// // 准备流程变量
// Map<String, Object> variables = new HashMap<>();
// variables.put("name", order.getName());
// variables.put("payment_currency", order.getPaymentCurrency());
// variables.put("payment_amount", order.getPaymentAmount());
// variables.put("goods_name", order.getGoodsName());
// variables.put("market", order.getMarket());
// variables.put("activity", order.getActivity());
// variables.put("goods_num", order.getGoodsNum());
// variables.put("pay_type", order.getPayType());
//// variables.put("received_market", order.getReceivedMarket());
// variables.put("market_list", order.getMarkets());
// variables.put("jwcode", order.getJwcode());
// variables.put("voucher", order.getVoucher());
//// variables.put("bank_code", order.getBankCode());
// variables.put("refund_channels", order.getRefundChannels());
// variables.put("refund_model", order.getRefundModel());
// variables.put("refund_reason", order.getRefundReason());
// variables.put("remark", order.getRemark());
// variables.put("executor", order.getExecutor());
// variables.put("id", order.getId());
//
// // 启动流程
// ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("work", variables);
//
// // 更新业务数据的流程信息
// order.setProcessInstanceId(processInstance.getId());
//
// // 获取第一个任务并更新当前处理人
// Task currentTask = taskService.createTaskQuery()
// .processInstanceId(processInstance.getId())
// .singleResult();
// if (currentTask != null) {
// order.setCurrentTaskId(currentTask.getId());
// order.setAuditId(currentTask.getAssignee());
// order.setStatus(20); // 当地财务审核中
// }
//
// cashRefundMapper.update(order);
//
// Map<String, Object> result = new HashMap<>();
// result.put("success", true);
// result.put("orderId", order.getId());
// result.put("processInstanceId", processInstance.getId());
// result.put("taskId", currentTask != null ? currentTask.getId() : null);
//
// return result;
// }
//
// /**
// * 当地财务审核
// */
// public void localFinanceApprove(String taskId, boolean approved, String comment) {
// Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// String processInstanceId = task.getProcessInstanceId();
//
// // 更新业务数据
// CashRecordDone order = cashRefundMapper.select(processInstanceId);
// if (order != null) {
// cashRefundMapper.update(order.getId(), comment);
// }
//
// String status = approved ? "20" : "12"; // 20=通过, 12=驳回
// completeTask(taskId, status, comment, order);
// }
//
// /**
// * 地区负责人审核
// */
// public void regionalManagerApprove(String taskId, boolean approved, String comment) {
// Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// String processInstanceId = task.getProcessInstanceId();
//
// // 更新业务数据
// CashRecordDone order = cashRefundMapper.select(processInstanceId);
// if (order != null) {
// cashRefundMapper.update(order.getId(), comment);
// }
//
// String status = approved ? "30" : "22"; // 30=通过, 22=驳回
// completeTask(taskId, status, comment, order);
// }
//
// /**
// * 总部财务审批
// */
// public void headFinanceApprove(String taskId, boolean approved, String comment) {
// Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
// String processInstanceId = task.getProcessInstanceId();
//
// // 更新业务数据
// CashRecordDone order = cashRefundMapper.select(processInstanceId);
// if (order != null) {
// cashRefundMapper.update(order.getId(), comment);
// }
//
// String status = approved ? "40" : "32"; // 40=通过, 32=驳回
// completeTask(taskId, status, comment, order);
// }
//
// /**
// * 完成任务并更新业务数据
// */
// private void completeTask(String taskId, String status, String comment, CashRecordDone order) {
// Map<String, Object> variables = new HashMap<>();
// variables.put("status", status);
//
// // 添加审批意见
// if (comment != null && !comment.trim().isEmpty()) {
// taskService.addComment(taskId, null, comment);
// }
//
// taskService.complete(taskId, variables);
//
// // 更新业务数据状态
// if (order != null) {
// cashRefundMapper.update(order.getId(), status);
//
// // 如果不是结束状态更新当前任务信息
// if (!status.equals("12") && !status.equals("22") && !status.equals("32") && !status.equals("40")) {
// Task nextTask = taskService.createTaskQuery()
// .processInstanceId(order.getProcessInstanceId())
// .singleResult();
// if (nextTask != null) {
// order.setCurrentTaskId(nextTask.getId());
// order.setCurrentAssignee(nextTask.getAssignee());
// cashRefundMapper.update(order);
// }
// }
// }
// }
//
// /**
// * 根据用户获取待办审批
// */
// public List<CashRecordDone> getPendingApprovals(String assignee) {
// return cashRefundMapper.select(assignee);
// }
//
// /**
// * 根据申请人获取历史审批
// */
// public List<CashRecordDone> getApprovalHistory(String applicant) {
// return cashRefundMapper.select(applicant);
// }
//
// /**
// * 获取审批详情
// */
// public Map<String, Object> getApprovalDetail(Long orderId) {
// CashRecordDone order = cashRefundMapper.select(orderId);
// if (order == null) {
// return null;
// }
//
// Map<String, Object> result = new HashMap<>();
// result.put("order", order);
//
// // 获取流程历史
// if (order.getProcessInstanceId() != null) {
// Object history = historyService.createHistoricActivityInstanceQuery()
// .processInstanceId(order.getProcessInstanceId())
// .orderByHistoricActivityInstanceStartTime().asc()
// .list();
// result.put("processHistory", history);
// }
//
// return result;
// }
//
// // 其他方法保持不变...
//}

286
src/main/java/com/example/demo/service/cash/RefundApprovalService.java

@ -0,0 +1,286 @@
package com.example.demo.service.cash;
import com.example.demo.Node.RefundApprovalNode;
import com.example.demo.config.cash.RefundApprovalFlowConfig;
import com.example.demo.domain.vo.cash.CashRecord;
import com.example.demo.domain.vo.cash.Market;
import com.example.demo.domain.vo.cash.RefundApprovalRecord;
import com.example.demo.domain.vo.cash.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
@Service
public class RefundApprovalService {
private final MarketService marketService;
// 退款审批流程配置缓存
private static final Map<String, RefundApprovalFlowConfig> REFUND_APPROVAL_FLOW_CONFIG = new ConcurrentHashMap<>();
// 角色定义
public static final String ROLE_REGIONAL_FINANCE = "REGIONAL_FINANCE";
public static final String ROLE_REGIONAL_MANAGER = "REGIONAL_MANAGER";
public static final String ROLE_HEADQUARTERS_FINANCE = "HEADQUARTERS_FINANCE";
public static final String ROLE_EXECUTOR = "EXECUTOR";
public static final String ROLE_SUBMITTER = "SUBMITTER";
public RefundApprovalService(MarketService marketService) {
this.marketService = marketService;
initializeRefundFlowConfigs();
}
// 初始化退款审批流程配置基于地区ID
private void initializeRefundFlowConfigs() {
// 获取市场部下的所有具体市场ID
Set<Integer> specificMarketIds = marketService.getSpecificMarketIdsUnderMarketDepartment();
// 为每个具体市场创建审批流程
for (Integer marketId : specificMarketIds) {
Market market = marketService.getMarketById(marketId);
if (market != null) {
List<RefundApprovalNode> flowNodes = createRefundFlowForMarket(market);
REFUND_APPROVAL_FLOW_CONFIG.put(marketId.toString(),
new RefundApprovalFlowConfig(market.getName() + "退款审批流程", flowNodes));
}
}
// 创建通用审批流程用于没有特定配置的市场
List<RefundApprovalNode> commonRefundFlow = createCommonRefundFlow();
REFUND_APPROVAL_FLOW_CONFIG.put("COMMON", new RefundApprovalFlowConfig("通用退款审批流程", commonRefundFlow));
log.info("退款审批流程配置初始化完成,共配置{}个流程", REFUND_APPROVAL_FLOW_CONFIG.size());
}
private List<RefundApprovalNode> createRefundFlowForMarket(Market market) {
Set<Integer> marketAndChildrenIds = new HashSet<>();
marketAndChildrenIds.add(market.getId());
marketAndChildrenIds.addAll(marketService.getAllChildrenIds(market.getId()));
List<RefundApprovalNode> nodes = new ArrayList<>();
nodes.add(new RefundApprovalNode("地区财务审核", 1, new String[]{ROLE_REGIONAL_FINANCE}, marketAndChildrenIds, true));
nodes.add(new RefundApprovalNode("地区负责人审核", 2, new String[]{ROLE_REGIONAL_MANAGER}, marketAndChildrenIds, true));
nodes.add(new RefundApprovalNode("总部财务审核", 3, new String[]{ROLE_HEADQUARTERS_FINANCE}, null, true)); // 总部不限制地区
nodes.add(new RefundApprovalNode("执行人处理", 4, new String[]{ROLE_EXECUTOR}, null, true)); // 执行人不限制地区
return nodes;
}
private List<RefundApprovalNode> createCommonRefundFlow() {
List<RefundApprovalNode> nodes = new ArrayList<>();
nodes.add(new RefundApprovalNode("地区财务审核", 1, new String[]{ROLE_REGIONAL_FINANCE}, null, true));
nodes.add(new RefundApprovalNode("地区负责人审核", 2, new String[]{ROLE_REGIONAL_MANAGER}, null, true));
nodes.add(new RefundApprovalNode("总部财务审核", 3, new String[]{ROLE_HEADQUARTERS_FINANCE}, null, true));
nodes.add(new RefundApprovalNode("执行人处理", 4, new String[]{ROLE_EXECUTOR}, null, true));
return nodes;
}
// 获取适合订单的审批流程基于地区ID
private RefundApprovalFlowConfig getSuitableFlowConfig(CashRecord cashRecord) {
Integer marketId = cashRecord.getPrimaryMarketId();
if (marketId != null) {
// 优先根据具体地区ID获取流程
RefundApprovalFlowConfig specificConfig = REFUND_APPROVAL_FLOW_CONFIG.get(marketId.toString());
if (specificConfig != null) {
return specificConfig;
}
// 如果该市场没有特定配置查找父级市场的配置
Market market = marketService.getMarketById(marketId);
if (market != null && market.getParentId() != null) {
RefundApprovalFlowConfig parentConfig = REFUND_APPROVAL_FLOW_CONFIG.get(market.getParentId().toString());
if (parentConfig != null) {
return parentConfig;
}
}
}
// 默认返回通用流程
return REFUND_APPROVAL_FLOW_CONFIG.get("COMMON");
}
// 获取用户有权限审批的订单状态基于地区ID
public List<Integer> getUserVisibleStatuses(User user, CashRecord cashRecord) {
if (!cashRecord.isRefundOrder()) {
return new ArrayList<>();
}
RefundApprovalFlowConfig flowConfig = getSuitableFlowConfig(cashRecord);
if (flowConfig != null) {
return flowConfig.getVisibleStatuses(user, cashRecord.getPrimaryMarketId());
}
log.warn("未找到适合订单地区{}的退款审批流程配置", cashRecord.getPrimaryMarketId());
return new ArrayList<>();
}
// 检查用户是否有权限审批指定订单基于地区ID
public boolean canUserApproveOrder(User user, CashRecord cashRecord) {
if (!cashRecord.isRefundOrder() || !cashRecord.isInApprovalProcess()) {
return false;
}
RefundApprovalFlowConfig flowConfig = getSuitableFlowConfig(cashRecord);
if (flowConfig != null) {
return flowConfig.canUserApproveNode(user, cashRecord.getCurrentApprovalLevel(), cashRecord.getPrimaryMarketId());
}
return false;
}
// 检查用户是否可以撤回订单只有提交人可以在第一个审批节点前撤回
public boolean canUserWithdrawOrder(User user, CashRecord cashRecord) {
if (!cashRecord.isRefundOrder()) {
return false;
}
// 只有订单提交人才能撤回
boolean isSubmitter = cashRecord.getSubmitterId() != null &&
cashRecord.getSubmitterId().equals(user.getId());
return isSubmitter && cashRecord.canWithdraw();
}
// 获取用户可审批的退款订单列表基于地区ID
public List<CashRecord> getApprovableRefundOrders(User user, List<CashRecord> allOrders) {
List<CashRecord> approvableOrders = new ArrayList<>();
// 获取用户管辖的所有具体市场ID
Set<Integer> userManagedMarketIds = marketService.getUserManagedSpecificMarketIds(user);
for (CashRecord order : allOrders) {
if (order.isRefundOrder() && canUserApproveOrder(user, order)) {
// 额外检查地区权限
Integer orderMarketId = order.getPrimaryMarketId();
if (orderMarketId != null && userManagedMarketIds.contains(orderMarketId)) {
approvableOrders.add(order);
}
}
}
return approvableOrders;
}
// 提交退款审批
public boolean submitRefundApproval(User user, CashRecord cashRecord, String approvalResult, String comments) {
if (!canUserApproveOrder(user, cashRecord)) {
log.warn("用户{}没有权限审批退款订单{}", user.getId(), cashRecord.getId());
return false;
}
// 创建审批记录
RefundApprovalRecord record = new RefundApprovalRecord();
record.setCashRecordId(cashRecord.getId());
record.setApprovalLevel(cashRecord.getCurrentApprovalLevel());
record.setApproverId(user.getId());
record.setApproverName(user.getName());
record.setApprovalResult(approvalResult);
record.setComments(comments);
record.setApprovalTime(new Date());
// 更新订单状态
boolean approved = "APPROVED".equals(approvalResult);
Integer nextStatus = cashRecord.getNextStatus(approved);
cashRecord.setStatus(nextStatus);
cashRecord.setUpdateTime(new Date());
cashRecord.setAuditId(user.getId());
cashRecord.setAuditTime(new Date());
if (!approved) {
cashRecord.setRejectReason(comments);
}
log.info("用户{}审批退款订单{},结果:{},新状态:{}",
user.getName(), cashRecord.getId(), approvalResult, nextStatus);
return true;
}
// 撤回退款订单在第一个审批节点前
public boolean withdrawRefundOrder(User user, CashRecord cashRecord, String withdrawReason) {
if (!canUserWithdrawOrder(user, cashRecord)) {
log.warn("用户{}不能撤回退款订单{}", user.getId(), cashRecord.getId());
return false;
}
// 创建撤回记录
RefundApprovalRecord record = new RefundApprovalRecord();
record.setCashRecordId(cashRecord.getId());
record.setApprovalLevel(cashRecord.getCurrentApprovalLevel());
record.setApproverId(user.getId());
record.setApproverName(user.getName());
record.setIsWithdrawal(true);
record.setWithdrawnBy(user.getId());
record.setWithdrawReason(withdrawReason);
record.setWithdrawTime(new Date());
// 更新订单状态为撤回
boolean success = cashRecord.withdraw();
if (success) {
cashRecord.setUpdateTime(new Date());
log.info("用户{}撤回退款订单{},原因:{}", user.getName(), cashRecord.getId(), withdrawReason);
}
return success;
}
// 重新提交撤回的退款订单
public boolean resubmitRefundOrder(User user, CashRecord cashRecord) {
if (!cashRecord.canResubmit()) {
log.warn("退款订单{}不能重新提交", cashRecord.getId());
return false;
}
// 检查用户是否是原提交人
boolean isSubmitter = cashRecord.getSubmitterId() != null &&
cashRecord.getSubmitterId().equals(user.getId());
if (!isSubmitter) {
log.warn("用户{}不是订单{}的提交人,不能重新提交", user.getId(), cashRecord.getId());
return false;
}
boolean success = cashRecord.resubmit();
if (success) {
cashRecord.setUpdateTime(new Date());
log.info("用户{}重新提交退款订单{}", user.getName(), cashRecord.getId());
}
return success;
}
// 获取下一个审批节点信息
public RefundApprovalNode getNextApprovalNode(CashRecord cashRecord) {
RefundApprovalFlowConfig flowConfig = getSuitableFlowConfig(cashRecord);
if (flowConfig != null) {
return flowConfig.getNextNode(cashRecord.getCurrentApprovalLevel());
}
return null;
}
// 动态添加退款审批流程配置
public void addRefundFlowConfig(String marketId, RefundApprovalFlowConfig flowConfig) {
REFUND_APPROVAL_FLOW_CONFIG.put(marketId, flowConfig);
log.info("成功添加地区{}的退款审批流程配置:{}", marketId, flowConfig.getFlowName());
}
// 获取所有退款审批流程配置
public Map<String, RefundApprovalFlowConfig> getAllRefundFlowConfigs() {
return new HashMap<>(REFUND_APPROVAL_FLOW_CONFIG);
}
// 根据地区名称初始化审批流程配置
public void initializeFlowForMarketName(String marketName) {
Market market = marketService.getMarketByName(marketName);
if (market != null) {
List<RefundApprovalNode> flowNodes = createRefundFlowForMarket(market);
REFUND_APPROVAL_FLOW_CONFIG.put(market.getId().toString(),
new RefundApprovalFlowConfig(market.getName() + "退款审批流程", flowNodes));
log.info("为地区{}初始化退款审批流程配置", marketName);
}
}
}

9
src/main/resources/application-dev.yml

@ -48,14 +48,7 @@ spring:
username: gjb_test
password: qweuio!@#$2
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
flowable:
jdbc-url: jdbc:mysql://localhost:3306/flowable?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&allowMultiQueries=true&rewriteBatchedStatements=true
username: hwgoldc
password: zB48T55wCsHC8KPz
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: flowableHikariCP
maximum-pool-size: 10
application:
name: demo

13
src/main/resources/application-test.yml

@ -48,18 +48,7 @@ spring:
username: gjb_test
password: qweuio!@#$2
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
flowable:
jdbc-url: jdbc:mysql://54.255.212.181:3306/flowable?serverTimezone=Asia/Shanghai&useSSL=false&useUnicode=true&allowMultiQueries=true&rewriteBatchedStatements=true
username: flowable
password: bN8NLHLAreSWTEZB
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: FlowableHikariPool
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1200000
application:
name: demo
cache:

5
src/main/resources/cashMapper/CashRecordMapper.xml

@ -0,0 +1,5 @@
<?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="com.example.demo.mapper.cash.CashRecordMapper">
</mapper>
Loading…
Cancel
Save