diff --git a/pom.xml b/pom.xml
index 215085e..1eef049 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,11 +75,6 @@
spring-boot-starter-web
- org.flowable
- flowable-spring-boot-starter
- 7.0.0
-
-
org.springframework.boot
spring-boot-starter-aop
diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java
index cd49fb5..54a0a53 100644
--- a/src/main/java/com/example/demo/DemoApplication.java
+++ b/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")
diff --git a/src/main/java/com/example/demo/Node/RefundApprovalNode.java b/src/main/java/com/example/demo/Node/RefundApprovalNode.java
new file mode 100644
index 0000000..9979390
--- /dev/null
+++ b/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 roles; // 审批所需的角色集合
+ private Set allowedMarketIds; // 允许审批的地区ID集合
+ private Set 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 roles, Set 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 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/config/FlowableDataSourceConfig.java b/src/main/java/com/example/demo/config/FlowableDataSourceConfig.java
deleted file mode 100644
index 4183be8..0000000
--- a/src/main/java/com/example/demo/config/FlowableDataSourceConfig.java
+++ /dev/null
@@ -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);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/config/cash/DataInitializer.java b/src/main/java/com/example/demo/config/cash/DataInitializer.java
new file mode 100644
index 0000000..15ceb0f
--- /dev/null
+++ b/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 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/config/cash/RefundApprovalFlowConfig.java b/src/main/java/com/example/demo/config/cash/RefundApprovalFlowConfig.java
new file mode 100644
index 0000000..e1014ff
--- /dev/null
+++ b/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 nodes;
+
+ public RefundApprovalFlowConfig() {
+ this.nodes = new ArrayList<>();
+ }
+
+ public RefundApprovalFlowConfig(String flowName, List nodes) {
+ this.flowName = flowName;
+ this.nodes = nodes != null ? nodes : new ArrayList<>();
+ }
+
+ // 获取用户可见的状态列表(基于角色和地区ID权限)
+ public List getVisibleStatuses(User user, Integer orderMarketId) {
+ List 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 getAllInvolvedMarketIds() {
+ return nodes.stream()
+ .filter(node -> node.getAllowedMarketIds() != null)
+ .flatMap(node -> node.getAllowedMarketIds().stream())
+ .collect(java.util.stream.Collectors.toSet());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/config/work/FlowableEngineConfig.java b/src/main/java/com/example/demo/config/work/FlowableEngineConfig.java
deleted file mode 100644
index 04d00ca..0000000
--- a/src/main/java/com/example/demo/config/work/FlowableEngineConfig.java
+++ /dev/null
@@ -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();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/controller/cash/CashRefundController.java b/src/main/java/com/example/demo/controller/cash/CashRefundController.java
index 123ebd3..b2fd794 100644
--- a/src/main/java/com/example/demo/controller/cash/CashRefundController.java
+++ b/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: 2025−09-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 userMarkets = Arrays.asList(StringUtils.split(admin.getMarkets(), ","));
-// List markets = marketService.getMarketIds(userMarkets);
-//
-//// 校验分页参数
-// if (ObjectUtils.isEmpty(page.getPageNum())) {
-// return Result.error("页码数为空!");
-// }
-// if (ObjectUtils.isEmpty(page.getPageSize())) {
-// return Result.error("页大小为空!");
-// }
-//
-//// 获取传入的市场列表
-// List 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 localFinanceApprove(
-// @RequestParam String taskId,
-// @RequestParam boolean approved,
-// @RequestParam(required = false) String comment) {
-//
-// processService.localFinanceApprove(taskId, approved, comment);
-//
-// Map result = new HashMap<>();
-// result.put("success", true);
-// result.put("message", approved ? "当地财务审核通过" : "当地财务已驳回");
-//
-// return result;
-// }
-//
-// /**
-// * 地区负责人审核
-// */
-// @PostMapping("/regional-manager/approve")
-// public Map regionalManagerApprove(
-// @RequestParam String taskId,
-// @RequestParam boolean approved,
-// @RequestParam(required = false) String comment) {
-//
-// processService.regionalManagerApprove(taskId, approved, comment);
-//
-// Map result = new HashMap<>();
-// result.put("success", true);
-// result.put("message", approved ? "地区负责人审核通过" : "地区负责人已驳回");
-//
-// return result;
-// }
-//
-// /**
-// * 总部财务审批
-// */
-// @PostMapping("/head-finance/approve")
-// public Map headFinanceApprove(
-// @RequestParam String taskId,
-// @RequestParam boolean approved,
-// @RequestParam(required = false) String comment) {
-//
-// processService.headFinanceApprove(taskId, approved, comment);
-//
-// Map result = new HashMap<>();
-// result.put("success", true);
-// result.put("message", approved ? "总部财务审批通过" : "总部财务已驳回");
-//
-// return result;
-// }
-//
-// /**
-// * 提交审批申请
-// */
-// @PostMapping("/submit")
-// public Map submitApproval(@RequestBody CashRecordDone order) {
-// return processService.submitApproval(order);
-// }
-//
-// /**
-// * 获取待办审批列表
-// */
-// @GetMapping("/pending/{assignee}")
-// public Map getPendingApprovals(@PathVariable String assignee) {
-// List approvals = processService.getPendingApprovals(assignee);
-//
-// Map result = new HashMap<>();
-// result.put("success", true);
-// result.put("data", approvals);
-// result.put("count", approvals.size());
-//
-// return result;
-// }
-//
-// /**
-// * 获取审批详情
-// */
-// @GetMapping("/detail/{orderId}")
-// public Map getApprovalDetail(@PathVariable Long orderId) {
-// Map detail = processService.getApprovalDetail(orderId);
-//
-// Map 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: 2025−09-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 userMarkets = Arrays.asList(StringUtils.split(admin.getMarkets(), ","));
+ List markets = marketService.getMarketIds(userMarkets);
+
+// 校验分页参数
+ if (ObjectUtils.isEmpty(page.getPageNum())) {
+ return Result.error("页码数为空!");
+ }
+ if (ObjectUtils.isEmpty(page.getPageSize())) {
+ return Result.error("页大小为空!");
+ }
+
+// 获取传入的市场列表
+ List 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));
+ }
+}
diff --git a/src/main/java/com/example/demo/domain/entity/User.java b/src/main/java/com/example/demo/domain/entity/User.java
index 20b0cc8..1dda6e9 100644
--- a/src/main/java/com/example/demo/domain/entity/User.java
+++ b/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 roles = new HashSet<>(); // 用户角色集合
+ public boolean hasRole(String role) {
+ return roles != null && roles.contains(role);
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/domain/vo/cash/CashRecord.java b/src/main/java/com/example/demo/domain/vo/cash/CashRecord.java
new file mode 100644
index 0000000..d81312f
--- /dev/null
+++ b/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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/domain/vo/cash/Market.java b/src/main/java/com/example/demo/domain/vo/cash/Market.java
new file mode 100644
index 0000000..6f62701
--- /dev/null
+++ b/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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/domain/vo/cash/RefundApprovalRecord.java b/src/main/java/com/example/demo/domain/vo/cash/RefundApprovalRecord.java
new file mode 100644
index 0000000..f186d7d
--- /dev/null
+++ b/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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/domain/vo/cash/User.java b/src/main/java/com/example/demo/domain/vo/cash/User.java
new file mode 100644
index 0000000..92ebd8c
--- /dev/null
+++ b/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 roles = new HashSet<>();
+ private Set managedMarketIds = new HashSet<>(); // 管辖的地区ID列表
+ private Set managedMarketNames = new HashSet<>(); // 管辖的地区名称列表
+
+ // 角色权限检查
+ public boolean hasRole(String role) {
+ return roles != null && roles.contains(role);
+ }
+
+ public boolean hasAnyRole(Set 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 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 getAllManagedMarketIds() {
+ Set allMarketIds = new HashSet<>();
+ if (managedMarketIds != null && !managedMarketIds.isEmpty()) {
+ allMarketIds.addAll(managedMarketIds);
+ }
+ if (marketId != null) {
+ allMarketIds.add(marketId);
+ }
+ return allMarketIds;
+ }
+
+ // 获取用户的所有管辖地区名称
+ public Set getAllManagedMarketNames() {
+ Set allMarketNames = new HashSet<>();
+ if (managedMarketNames != null && !managedMarketNames.isEmpty()) {
+ allMarketNames.addAll(managedMarketNames);
+ }
+ if (marketName != null) {
+ allMarketNames.add(marketName);
+ }
+ return allMarketNames;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/service/cash/MarketService.java b/src/main/java/com/example/demo/service/cash/MarketService.java
new file mode 100644
index 0000000..e7c32fc
--- /dev/null
+++ b/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 marketCache = new ConcurrentHashMap<>();
+ private final Map marketNameToIdCache = new ConcurrentHashMap<>();
+ private final Map> childrenCache = new ConcurrentHashMap<>();
+
+ // 初始化地区数据(实际项目中应从数据库加载)
+ public void initializeMarkets(List 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 getAllChildren(Integer parentId) {
+ List allChildren = new ArrayList<>();
+ List directChildren = childrenCache.get(parentId);
+
+ if (directChildren != null) {
+ allChildren.addAll(directChildren);
+ for (Market child : directChildren) {
+ allChildren.addAll(getAllChildren(child.getId()));
+ }
+ }
+
+ return allChildren;
+ }
+
+ // 获取所有子地区ID(包括所有后代)
+ public Set getAllChildrenIds(Integer parentId) {
+ return getAllChildren(parentId).stream()
+ .map(Market::getId)
+ .collect(Collectors.toSet());
+ }
+
+ // 获取所有子地区名称(包括所有后代)
+ public Set getAllChildrenNames(Integer parentId) {
+ return getAllChildren(parentId).stream()
+ .map(Market::getName)
+ .collect(Collectors.toSet());
+ }
+
+ // 获取直接子地区
+ public List getDirectChildren(Integer parentId) {
+ return childrenCache.getOrDefault(parentId, new ArrayList<>());
+ }
+
+ // 获取市场部下的所有具体市场
+ public List getSpecificMarketsUnderMarketDepartment() {
+ List specificMarkets = new ArrayList<>();
+
+ // 市场部的ID是3
+ List marketDepartmentChildren = getDirectChildren(3);
+ for (Market market : marketDepartmentChildren) {
+ if (market.isSpecificMarket()) {
+ specificMarkets.add(market);
+ }
+ }
+
+ return specificMarkets;
+ }
+
+ // 获取市场部下的所有具体市场ID
+ public Set getSpecificMarketIdsUnderMarketDepartment() {
+ return getSpecificMarketsUnderMarketDepartment().stream()
+ .map(Market::getId)
+ .collect(Collectors.toSet());
+ }
+
+ // 获取用户管辖的所有具体市场ID(包括子地区)
+ public Set getUserManagedSpecificMarketIds(User user) {
+ Set allSpecificMarketIds = new HashSet<>();
+
+ Set 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 getAllMarkets() {
+ return new ArrayList<>(marketCache.values());
+ }
+
+ // 获取所有具体市场(叶子节点)
+ public List 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;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/service/cash/ProcessService.java b/src/main/java/com/example/demo/service/cash/ProcessService.java
deleted file mode 100644
index fa4cbc6..0000000
--- a/src/main/java/com/example/demo/service/cash/ProcessService.java
+++ /dev/null
@@ -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 submitApproval(CashRecordDone order) {
-// // 生成订单号
-// String orderNumber = UUID.randomUUID().toString().replaceAll("-", "");
-// order.setOrderCode("XJTK" + orderNumber);
-// order.setStatus(10); // 待提交
-// order.setOrderType(2);
-//
-// // 保存业务数据
-// cashRefundMapper.insert(order);
-//
-// // 准备流程变量
-// Map 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 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 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 getPendingApprovals(String assignee) {
-// return cashRefundMapper.select(assignee);
-// }
-//
-// /**
-// * 根据申请人获取历史审批
-// */
-// public List getApprovalHistory(String applicant) {
-// return cashRefundMapper.select(applicant);
-// }
-//
-// /**
-// * 获取审批详情
-// */
-// public Map getApprovalDetail(Long orderId) {
-// CashRecordDone order = cashRefundMapper.select(orderId);
-// if (order == null) {
-// return null;
-// }
-//
-// Map 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;
-// }
-//
-// // 其他方法保持不变...
-//}
\ No newline at end of file
diff --git a/src/main/java/com/example/demo/service/cash/RefundApprovalService.java b/src/main/java/com/example/demo/service/cash/RefundApprovalService.java
new file mode 100644
index 0000000..2d7bb45
--- /dev/null
+++ b/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 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 specificMarketIds = marketService.getSpecificMarketIdsUnderMarketDepartment();
+
+ // 为每个具体市场创建审批流程
+ for (Integer marketId : specificMarketIds) {
+ Market market = marketService.getMarketById(marketId);
+ if (market != null) {
+ List flowNodes = createRefundFlowForMarket(market);
+ REFUND_APPROVAL_FLOW_CONFIG.put(marketId.toString(),
+ new RefundApprovalFlowConfig(market.getName() + "退款审批流程", flowNodes));
+ }
+ }
+
+ // 创建通用审批流程(用于没有特定配置的市场)
+ List commonRefundFlow = createCommonRefundFlow();
+ REFUND_APPROVAL_FLOW_CONFIG.put("COMMON", new RefundApprovalFlowConfig("通用退款审批流程", commonRefundFlow));
+
+ log.info("退款审批流程配置初始化完成,共配置{}个流程", REFUND_APPROVAL_FLOW_CONFIG.size());
+ }
+
+ private List createRefundFlowForMarket(Market market) {
+ Set marketAndChildrenIds = new HashSet<>();
+ marketAndChildrenIds.add(market.getId());
+ marketAndChildrenIds.addAll(marketService.getAllChildrenIds(market.getId()));
+
+ List 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 createCommonRefundFlow() {
+ List 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 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 getApprovableRefundOrders(User user, List allOrders) {
+ List approvableOrders = new ArrayList<>();
+
+ // 获取用户管辖的所有具体市场ID
+ Set 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 getAllRefundFlowConfigs() {
+ return new HashMap<>(REFUND_APPROVAL_FLOW_CONFIG);
+ }
+
+ // 根据地区名称初始化审批流程配置
+ public void initializeFlowForMarketName(String marketName) {
+ Market market = marketService.getMarketByName(marketName);
+ if (market != null) {
+ List flowNodes = createRefundFlowForMarket(market);
+ REFUND_APPROVAL_FLOW_CONFIG.put(market.getId().toString(),
+ new RefundApprovalFlowConfig(market.getName() + "退款审批流程", flowNodes));
+ log.info("为地区{}初始化退款审批流程配置", marketName);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index 9afe9e6..1dc2d17 100644
--- a/src/main/resources/application-dev.yml
+++ b/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
diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml
index b4c62ea..bb697d8 100644
--- a/src/main/resources/application-test.yml
+++ b/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:
diff --git a/src/main/resources/cashMapper/CashRecordMapper.xml b/src/main/resources/cashMapper/CashRecordMapper.xml
new file mode 100644
index 0000000..3a018e2
--- /dev/null
+++ b/src/main/resources/cashMapper/CashRecordMapper.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file