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