1.0.50后端:

fix(budget):项目预算变更功能版本处理更新;完善市场项目预算导出
   feat(budget):完成研发项目预算导出功能
dev
xs 2 months ago
parent 8414f24f0d
commit 9c3efa00bb

@ -15,7 +15,8 @@ public enum FlowConfigEnum {
/**
*
*/
BUDGET("HWOABudget", "erp_budget_info", "budget_status", OAStatusEnum.COMPLETED.getStatus(), OAStatusEnum.DRAFT.getStatus(), "flow_status", "预算审批", "budget_id");
BUDGET("HWOABudget", "erp_budget_info", "budget_status", OAStatusEnum.COMPLETED.getStatus(), OAStatusEnum.DRAFT.getStatus(), "flow_status", "预算审批", "budget_id", OAStatusEnum.INVALID.getStatus(),"project_id");
/**
@ -59,9 +60,20 @@ public enum FlowConfigEnum {
*/
private final String businessPk;
/**
*
*/
private final String invalidBusinessStatus;
/**
* ID
*/
private final String relationIdField;
FlowConfigEnum(String flowCode, String tableName, String businessStatusField,
String completedBusinessStatus, String draftBusinessStatus, String flowStatusField, String businessTitle, String businessPk) {
String completedBusinessStatus, String draftBusinessStatus, String flowStatusField,
String businessTitle, String businessPk,String invalidBusinessStatus,String relationIdField) {
this.flowCode = flowCode;
this.tableName = tableName;
this.businessStatusField = businessStatusField;
@ -70,6 +82,8 @@ public enum FlowConfigEnum {
this.flowStatusField = flowStatusField;
this.businessTitle = businessTitle;
this.businessPk = businessPk;
this.invalidBusinessStatus = invalidBusinessStatus;
this.relationIdField = relationIdField;
}
/**

@ -43,14 +43,18 @@ public abstract class AbstractProcessEventHandler implements IProcessEventHandle
FlowConfigEnum flowConfig = FlowConfigEnum.getByFlowCode(flowCode);
// 步骤2: 构建更新字段
Map<String, Object> setFields = buildSetFields(flowConfig,processEvent);
Map<String, Object> setFields = buildSetFields(flowConfig, processEvent.getStatus());
// 步骤3: 构建查询条件
Map<String, Object> conditions = buildConditions(flowConfig,processEvent);
Map<String, Object> conditions = buildConditions(flowConfig, processEvent);
// 步骤4: 执行更新
int updateCount = doUpdate(flowConfig, setFields, conditions);
// 如果流程完成,更新业务状态
if (BusinessStatusEnum.FINISH.getStatus().equals(processEvent.getStatus())) {
makeInvalid(flowConfig, processEvent);
}
// 步骤5: 验证更新结果
validateUpdateResult(processEvent, updateCount);
@ -69,7 +73,7 @@ public abstract class AbstractProcessEventHandler implements IProcessEventHandle
// 检查是否支持该流程编码
FlowConfigEnum flowConfig = FlowConfigEnum.getByFlowCode(flowCode);
if (flowConfig == null) {
throw new IllegalArgumentException("不支持的流程编码: "+flowCode+", 忽略处理");
throw new IllegalArgumentException("不支持的流程编码: " + flowCode + ", 忽略处理");
}
// if (flowConfig == null) {
// throw new IllegalArgumentException("流程配置不能为空");
@ -83,19 +87,28 @@ public abstract class AbstractProcessEventHandler implements IProcessEventHandle
}
/**
*
*
* @param flowConfig
* @param processEvent
*/
protected abstract void makeInvalid(FlowConfigEnum flowConfig, ProcessEvent processEvent);
/**
*
*/
protected Map<String, Object> buildSetFields(FlowConfigEnum flowConfig, ProcessEvent processEvent) {
protected Map<String, Object> buildSetFields(FlowConfigEnum flowConfig, String status) {
Map<String, Object> setFields = new HashMap<>();
// 设置流程状态
setFields.put(flowConfig.getFlowStatusField(), processEvent.getStatus());
setFields.put(flowConfig.getFlowStatusField(), status);
// 如果流程完成,更新业务状态
if (BusinessStatusEnum.FINISH.getStatus().equals(processEvent.getStatus())) {
if (BusinessStatusEnum.FINISH.getStatus().equals(status)) {
setFields.put(flowConfig.getBusinessStatusField(), flowConfig.getCompletedBusinessStatus());
} else if (BusinessStatusEnum.BACK.getStatus().equals(processEvent.getStatus())) {
} else if (BusinessStatusEnum.BACK.getStatus().equals(status)) {
setFields.put(flowConfig.getBusinessStatusField(), flowConfig.getDraftBusinessStatus());
}
@ -127,7 +140,13 @@ public abstract class AbstractProcessEventHandler implements IProcessEventHandle
* -
*/
protected abstract int doUpdate(FlowConfigEnum flowConfig,
Map<String, Object> setFields, Map<String, Object> conditions);
Map<String, Object> setFields, Map<String, Object> conditions);
/**
* () -
*/
protected abstract int doUpdateWithOperator(FlowConfigEnum flowConfig,
Map<String, Object> setFields, Map<String, Object> conditions,String businessId);
/**
* -

@ -17,6 +17,20 @@ import java.util.Map;
*/
public class UniversalSqlProvider {
/**
*
*/
public String buildDynamicSelect(@Param("tableName") String tableName,
@Param("selectField") String selectField,
@Param("whereField") String whereField,
@Param("whereValue") Object whereValue) {
return new SQL() {{
SELECT(selectField);
FROM(tableName);
WHERE(whereField + " = #{whereValue}");
}}.toString();
}
/**
* SQL
*/
@ -47,7 +61,7 @@ public class UniversalSqlProvider {
*/
public String buildDynamicUpdateWithOperator(@Param("tableName") String tableName,
@Param("setFields") Map<String, Object> setFields,
@Param("conditions") Map<String, Object> conditions) {
@Param("conditions") Map<String, String> conditions) { // 修改为Map<String, String>
return new SQL() {{
UPDATE(tableName);
@ -58,41 +72,44 @@ public class UniversalSqlProvider {
Object value = entry.getValue();
if (value instanceof String && ((String) value).startsWith("+")) {
// 增量更新,如: set quantity = quantity + 1
SET(key + " = " + key + " + " + ((String) value).substring(1));
} else if (value instanceof String && ((String) value).startsWith("-")) {
// 减量更新
SET(key + " = " + key + " - " + ((String) value).substring(1));
} else if (value instanceof String && ((String) value).startsWith("*")) {
SET(key + " = " + key + " * " + ((String) value).substring(1));
} else if (value instanceof String && ((String) value).startsWith("/")) {
SET(key + " = " + key + " / " + ((String) value).substring(1));
} else {
// 普通赋值更新
SET(key + " = #{setFields." + key + "}");
}
}
}
// 设置条件
// 设置条件 - 现在支持完整表达式
if (conditions != null && !conditions.isEmpty()) {
for (Map.Entry<String, Object> entry : conditions.entrySet()) {
for (Map.Entry<String, String> entry : conditions.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
String value = entry.getValue();
if (value instanceof String) {
String strValue = (String) value;
if (strValue.startsWith(">=")) {
WHERE(key + " >= " + strValue.substring(2));
} else if (strValue.startsWith("<=")) {
WHERE(key + " <= " + strValue.substring(2));
} else if (strValue.startsWith(">")) {
WHERE(key + " > " + strValue.substring(1));
} else if (strValue.startsWith("<")) {
WHERE(key + " < " + strValue.substring(1));
} else if (strValue.startsWith("!=")) {
WHERE(key + " != " + strValue.substring(2));
} else if (strValue.startsWith("LIKE ")) {
WHERE(key + " LIKE '%" + strValue.substring(5) + "%'");
} else {
WHERE(key + " = #{conditions." + key + "}");
}
// 如果key包含等号说明是一个完整表达式
if (key.contains("=") || key.contains(">") || key.contains("<") ||
key.contains("LIKE") || key.contains("IN")) {
WHERE(key); // 直接使用key作为WHERE条件
} else if (value.startsWith("(")) {
// 子查询
WHERE(key + " = " + value);
} else if (value.startsWith(">=")) {
WHERE(key + " >= " + value.substring(2));
} else if (value.startsWith("<=")) {
WHERE(key + " <= " + value.substring(2));
} else if (value.startsWith(">")) {
WHERE(key + " > " + value.substring(1));
} else if (value.startsWith("<")) {
WHERE(key + " < " + value.substring(1));
} else if (value.startsWith("!=")) {
WHERE(key + " != " + value.substring(2));
} else if (value.startsWith("LIKE ")) {
WHERE(key + " LIKE '%" + value.substring(5) + "%'");
} else {
WHERE(key + " = #{conditions." + key + "}");
}

@ -1,5 +1,6 @@
package org.dromara.oa.erp.controller;
import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor;
@ -12,6 +13,7 @@ import org.dromara.oa.base.service.IBaseMaterialInfoService;
import org.dromara.oa.erp.domain.bo.ErpProjectInfoBo;
import org.dromara.oa.erp.domain.vo.ErpProjectInfoVo;
import org.dromara.oa.erp.service.IErpProjectInfoService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.validation.annotation.Validated;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
@ -67,6 +69,34 @@ public class ErpBudgetInfoController extends BaseController {
ExcelUtil.exportExcel(list, "项目预算", ErpBudgetInfoVo.class, response);
}
/**
*
*/
@SaCheckPermission("oa:erp/budgetInfo:export")
@Log(title = "项目预算", businessType = BusinessType.EXPORT)
@PostMapping("/exportBudgetInfo")
public void exportBudgetInfo(ErpBudgetInfoBo bo, HttpServletResponse response) throws IOException {
try {
byte[] excelBytes = erpBudgetInfoService.exportBudgetInfo(bo.getBudgetId());
// String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
// "attachment; filename=\"" + encodedFileName + "\"");
// response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(excelBytes.length));
// 写入响应流
response.getOutputStream().write(excelBytes);
response.getOutputStream().flush();
} catch (Exception e) {
e.printStackTrace();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.getWriter().write("导出失败: " + e.getMessage());
}
}
/**
*
*
@ -75,7 +105,7 @@ public class ErpBudgetInfoController extends BaseController {
@SaCheckPermission("oa:erp/budgetInfo:query")
@GetMapping("/{budgetId}")
public R<ErpBudgetInfoVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable("budgetId") Long budgetId) {
@PathVariable("budgetId") Long budgetId) {
return R.ok(erpBudgetInfoService.queryById(budgetId));
}

@ -0,0 +1,51 @@
package org.dromara.oa.erp.enums;
import lombok.Getter;
/**
* @author xins
* @description erp_rd_budget_labor_cost
* @date 2025/12/11 13:59
*/
@Getter
public enum LaborTypeEnum {
/**
*
*/
LABOR_COST("1", "人工费"),
/**
*
*/
SERVICE_COST("2", "劳务费");
/**
*
*/
private final String code;
/**
*
*/
private final String name;
LaborTypeEnum(String code, String name) {
this.code = code;
this.name = name;
}
/**
*
*/
public static LaborTypeEnum getByCode(String code) {
for (LaborTypeEnum config : values()) {
if (config.getCode().equals(code)) {
return config;
}
}
return null;
}
}

@ -0,0 +1,56 @@
package org.dromara.oa.erp.enums;
import lombok.Getter;
/**
* @author xins
* @description erp_rd_budget_literature_cost
* @date 2025/12/11 16:22
*/
@Getter
public enum LiteratureTypeEnum {
/**
*
*/
MATERIAL_COST("1", "资料费"),
/**
*
*/
DOCUMENT_COST("2", "文献检索费"),
/**
*
*/
SOFTWARE_COST("3", "专用软件购买费");
/**
*
*/
private final String code;
/**
*
*/
private final String name;
LiteratureTypeEnum(String code, String name) {
this.code = code;
this.name = name;
}
/**
*
*/
public static LiteratureTypeEnum getByCode(String code) {
for (LiteratureTypeEnum config : values()) {
if (config.getCode().equals(code)) {
return config;
}
}
return null;
}
}

@ -5,8 +5,7 @@ import org.dromara.common.core.enums.OAStatusEnum;
/**
* @author xins
* @description
*
* @description
* @date 2025/11/4 9:41
*/
@Getter

@ -0,0 +1,51 @@
package org.dromara.oa.erp.enums;
import lombok.Getter;
/**
* @author xins
* @description erp_rd_budget_material_cost
* @date 2025/12/18 16:07
*/
@Getter
public enum RdMaterialTypeEnum {
/**
*
*/
MAIN_MATERIAL("1", "主要材料费"),
/**
*
*/
OTHER_MATERIAL("2", "其他材料费");
/**
*
*/
private final String code;
/**
*
*/
private final String name;
RdMaterialTypeEnum(String code, String name) {
this.code = code;
this.name = name;
}
/**
*
*/
public static RdMaterialTypeEnum getByCode(String code) {
for (RdMaterialTypeEnum config : values()) {
if (config.getCode().equals(code)) {
return config;
}
}
return null;
}
}

@ -0,0 +1,56 @@
package org.dromara.oa.erp.enums;
import lombok.Getter;
/**
* @author xins
* @description erp_rd_budget_tech_cost
* @date 2025/12/11 13:58
*/
@Getter
public enum TechTypeEnum {
/**
*
*/
TECH_CONSULT("1", "技术咨询开发"),
/**
* -
*/
EXPERT_MEETING("2", "专家咨询-会议形式"),
/**
* -
*/
EXPERT_COMMUNICATION("3", "专家咨询-通讯形式");
/**
*
*/
private final String code;
/**
*
*/
private final String name;
TechTypeEnum(String code, String name) {
this.code = code;
this.name = name;
}
/**
*
*/
public static TechTypeEnum getByCode(String code) {
for (TechTypeEnum config : values()) {
if (config.getCode().equals(code)) {
return config;
}
}
return null;
}
}

@ -2,6 +2,7 @@ package org.dromara.oa.erp.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.dromara.common.mybatis.utils.UniversalSqlProvider;
@ -14,6 +15,16 @@ import java.util.Map;
@Mapper
public interface OaUniversalMapper {
/**
* ID
*/
@SelectProvider(type = UniversalSqlProvider.class, method = "buildDynamicSelect")
String selectRelationId(@Param("tableName") String tableName,
@Param("selectField") String selectField,
@Param("whereField") String whereField,
@Param("whereValue") Object whereValue);
/**
*
*
@ -26,4 +37,22 @@ public interface OaUniversalMapper {
int dynamicUpdate(@Param("tableName") String tableName,
@Param("setFields") Map<String, Object> setFields,
@Param("conditions") Map<String, Object> conditions);
/**
* SQL
*
* @param tableName
* @param setFields
* @param conditions
* @return
*/
@UpdateProvider(type = UniversalSqlProvider.class, method = "buildDynamicUpdateWithOperator")
int dynamicUpdateWithOperator(@Param("tableName") String tableName,
@Param("setFields") Map<String, Object> setFields,
@Param("conditions") Map<String, Object> conditions,
@Param("businessId") String businessId);
}

@ -5,6 +5,7 @@ import org.dromara.oa.erp.domain.vo.ErpBudgetInfoVo;
import org.dromara.oa.erp.domain.bo.ErpBudgetInfoBo;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
@ -74,4 +75,11 @@ public interface IErpBudgetInfoService {
* @return
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
*
* @param budgetId
* @return excel
*/
public byte[] exportBudgetInfo(Long budgetId);
}

@ -23,6 +23,8 @@ import org.dromara.oa.erp.domain.*;
import org.dromara.oa.erp.enums.ProjectCategoryEnum;
import org.dromara.oa.erp.enums.RdBudgetCostEnums;
import org.dromara.oa.erp.mapper.*;
import org.dromara.oa.excel.MarketProjectBudgetExcelExporter;
import org.dromara.oa.excel.RdProjectBudgetExcelExporter;
import org.dromara.oa.workflow.strategy.BudgetWorkflowStrategy;
import org.dromara.workflow.api.RemoteWorkflowService;
import org.dromara.workflow.api.domain.RemoteStartProcess;
@ -35,6 +37,7 @@ import org.dromara.oa.erp.service.IErpBudgetInfoService;
import org.dromara.workflow.strategy.WorkflowStrategy;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
@ -83,6 +86,10 @@ public class ErpBudgetInfoServiceImpl extends AbstractWorkflowService<ErpBudgetI
private final ErpRdBudgetOtherCostMapper erpRdBudgetOtherCostMapper;
private final MarketProjectBudgetExcelExporter marketProjectBudgetExcelExporter;
private final RdProjectBudgetExcelExporter rdProjectBudgetExcelExporter;
@DubboReference(timeout = 30000)
private RemoteWorkflowService remoteWorkflowService;
@ -342,7 +349,7 @@ public class ErpBudgetInfoServiceImpl extends AbstractWorkflowService<ErpBudgetI
@Transactional(rollbackFor = Exception.class)
public Boolean changeByBo(ErpBudgetInfoBo bo) {
//当前变更预算的版本
Integer currentBudgetVersion = bo.getBudgetVersion();
Integer currentBudgetVersion = bo.getBudgetVersion() - 1;
// 1. 查询项目的所有预算(包括不可用的)
LambdaQueryWrapper<ErpBudgetInfo> queryWrapper = new LambdaQueryWrapper<>();
@ -375,8 +382,6 @@ public class ErpBudgetInfoServiceImpl extends AbstractWorkflowService<ErpBudgetI
throw new RuntimeException("已经有正在变更的项目预算,版本号:" + latestVersion);
}
bo.setBudgetVersion(currentBudgetVersion+1);
return insertByBo(bo);
}
@ -765,4 +770,26 @@ public class ErpBudgetInfoServiceImpl extends AbstractWorkflowService<ErpBudgetI
return true;
// return baseMapper.deleteByIds(ids) > 0;
}
/**
*
* @param budgetId
* @return excel
*/
@Override
@Transactional(readOnly = true)
public byte[] exportBudgetInfo(Long budgetId) {
try {
ErpBudgetInfoVo erpBudgetInfoVo = this.queryById(budgetId);
String projectCategory = erpBudgetInfoVo.getProjectCategory();
if (projectCategory.equals(ProjectCategoryEnum.MARKET.getCode()) || projectCategory.equals(ProjectCategoryEnum.MARKET_PART.getCode())) {
return marketProjectBudgetExcelExporter.exportToByteArray(erpBudgetInfoVo);
} else {
return rdProjectBudgetExcelExporter.exportToByteArray(erpBudgetInfoVo);
}
} catch (Exception e) {
throw new RuntimeException("导出Excel失败: " + e.getMessage(), e);
}
}
}

@ -0,0 +1,679 @@
package org.dromara.oa.excel;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.dromara.oa.erp.domain.vo.ErpBudgetInfoVo;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Author xins
* @Date 2025/12/9 14:20
* @Description: Excel
*
*/
@Slf4j
public abstract class BaseExcelExporter {
protected Workbook workbook;
// 样式定义
protected CellStyle titleStyle;
protected CellStyle formLeftStyle;
protected CellStyle formRightStyle;
protected CellStyle formFormulaStyle;
protected CellStyle formPercentStyle;
protected CellStyle headerStyle;
protected CellStyle mergedHeaderStyle;
protected CellStyle dataStyle;
protected CellStyle percentStyle;
protected CellStyle formulaStyle;
protected CellStyle footerFormulaStyle;
protected CellStyle moneyStyle;
protected CellStyle remarkStyle;
protected CellStyle leftMergeStyle;
// 存储各sheet的总计行位置
protected final Map<String, Integer> sheetTotalRowMap = new ConcurrentHashMap<>();
/**
* Excel
*/
public abstract byte[] exportToByteArray(ErpBudgetInfoVo budget) throws IOException;
/**
* Excel
*/
public abstract void exportToFile(ErpBudgetInfoVo budget, String filePath) throws IOException;
/**
* 簿
*/
protected Workbook createWorkbook() {
return new XSSFWorkbook();
}
/**
*
*/
protected void createStyles() {
// 创建标题样式
createTitleStyle();
// 创建表单样式
createFormStyles();
// 创建表头样式
createHeaderStyles();
// 创建数据样式
createDataStyles();
// 创建其他样式
createOtherStyles();
}
/**
*
*/
protected void createTitleStyle() {
titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontName("微软雅黑");
titleFont.setFontHeightInPoints((short) 16);
titleStyle.setFont(titleFont);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);
setBorder(titleStyle, BorderStyle.MEDIUM);
}
/**
*
*/
protected void createFormStyles() {
Font formFont = workbook.createFont();
formFont.setColor(IndexedColors.WHITE.getIndex());
formFont.setFontName("微软雅黑");
formFont.setFontHeightInPoints((short) 10);
// 表单居左样式
formLeftStyle = workbook.createCellStyle();
formLeftStyle.setFont(formFont);
formLeftStyle.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
formLeftStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorder(formLeftStyle, BorderStyle.THIN);
formLeftStyle.setVerticalAlignment(VerticalAlignment.CENTER);
formLeftStyle.setAlignment(HorizontalAlignment.LEFT);
// 表单居右样式
formRightStyle = workbook.createCellStyle();
formRightStyle.cloneStyleFrom(formLeftStyle);
formRightStyle.setAlignment(HorizontalAlignment.RIGHT);
// 表单公式样式
formFormulaStyle = workbook.createCellStyle();
formFormulaStyle.cloneStyleFrom(formRightStyle);
formFormulaStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
// 表单百分比样式
formPercentStyle = workbook.createCellStyle();
formPercentStyle.cloneStyleFrom(formRightStyle);
formPercentStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00%"));
}
/**
*
*/
protected void createHeaderStyles() {
// 普通表头样式
headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setFontName("微软雅黑");
headerFont.setFontHeightInPoints((short) 10);
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.LIGHT_ORANGE.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorder(headerStyle, BorderStyle.THIN);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
headerStyle.setWrapText(true);
// 合并表头样式
mergedHeaderStyle = workbook.createCellStyle();
Font mergedHeaderFont = workbook.createFont();
mergedHeaderFont.setBold(true);
mergedHeaderFont.setFontName("微软雅黑");
mergedHeaderFont.setColor(IndexedColors.WHITE.getIndex());
mergedHeaderFont.setFontHeightInPoints((short) 10);
mergedHeaderStyle.setFont(mergedHeaderFont);
mergedHeaderStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
mergedHeaderStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorder(mergedHeaderStyle, BorderStyle.THIN);
mergedHeaderStyle.setAlignment(HorizontalAlignment.CENTER);
mergedHeaderStyle.setVerticalAlignment(VerticalAlignment.CENTER);
}
/**
*
*/
protected void createDataStyles() {
// 基础数据样式
dataStyle = workbook.createCellStyle();
Font dataFont = workbook.createFont();
dataFont.setFontName("微软雅黑");
dataFont.setFontHeightInPoints((short) 10);
dataStyle.setFont(dataFont);
setBorder(dataStyle, BorderStyle.THIN);
dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
dataStyle.setAlignment(HorizontalAlignment.CENTER);
// 公式样式
formulaStyle = workbook.createCellStyle();
formulaStyle.cloneStyleFrom(dataStyle);
formulaStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
// 金额样式
moneyStyle = workbook.createCellStyle();
moneyStyle.cloneStyleFrom(formulaStyle);
// 百分比样式
percentStyle = workbook.createCellStyle();
percentStyle.cloneStyleFrom(dataStyle);
percentStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00%"));
// 底部公式样式
footerFormulaStyle = workbook.createCellStyle();
footerFormulaStyle.cloneStyleFrom(headerStyle);
footerFormulaStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
}
/**
*
*/
protected void createOtherStyles() {
// 备注样式
remarkStyle = workbook.createCellStyle();
Font remarkFont = workbook.createFont();
remarkFont.setItalic(true);
remarkFont.setFontName("微软雅黑");
remarkFont.setFontHeightInPoints((short) 10);
remarkStyle.setFont(remarkFont);
remarkStyle.setWrapText(true);
// 左侧合并样式
leftMergeStyle = workbook.createCellStyle();
Font leftMergeFont = workbook.createFont();
leftMergeFont.setBold(true);
leftMergeFont.setFontName("微软雅黑");
leftMergeFont.setFontHeightInPoints((short) 10);
leftMergeStyle.setFont(leftMergeFont);
leftMergeStyle.setAlignment(HorizontalAlignment.CENTER);
leftMergeStyle.setVerticalAlignment(VerticalAlignment.CENTER);
leftMergeStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
leftMergeStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorder(leftMergeStyle, BorderStyle.MEDIUM);
}
/**
*
*/
protected void setBorder(CellStyle style, BorderStyle borderStyle) {
style.setBorderTop(borderStyle);
style.setBorderBottom(borderStyle);
style.setBorderLeft(borderStyle);
style.setBorderRight(borderStyle);
style.setTopBorderColor(IndexedColors.BLACK.getIndex());
style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
style.setRightBorderColor(IndexedColors.BLACK.getIndex());
}
/**
*
*/
protected void createMergedCell(Sheet sheet, Row row, int firstCol, int lastCol, String value, CellStyle style) {
if (firstCol > lastCol) {
throw new IllegalArgumentException("firstCol must be less than or equal to lastCol");
}
Cell cell = row.createCell(firstCol);
cell.setCellValue(value);
if (style != null) {
cell.setCellStyle(style);
}
// 创建合并区域
if (firstCol < lastCol) {
CellRangeAddress region = new CellRangeAddress(
row.getRowNum(), row.getRowNum(), firstCol, lastCol);
sheet.addMergedRegion(region);
applyStyleToMergedRegion(sheet, region, style);
}
}
/**
*
*/
protected void createVerticalMergedCellWithBorder(Sheet sheet, int startRow, int endRow,
int col, String value, CellStyle style) {
if (startRow < 0 || endRow < startRow || col < 0) {
return;
}
// 创建主单元格
Row mainRow = getOrCreateRow(sheet, startRow);
Cell mainCell = mainRow.createCell(col);
mainCell.setCellValue(value);
// 创建合并区域
CellRangeAddress region = new CellRangeAddress(startRow, endRow, col, col);
sheet.addMergedRegion(region);
// 为整个合并区域设置边框
setBorderForRegion(sheet, region, style);
// 设置主单元格的样式(值显示)
mainCell.setCellStyle(style);
}
/**
*
*/
private void setBorderForRegion(Sheet sheet, CellRangeAddress region, CellStyle templateStyle) {
int firstRow = region.getFirstRow();
int lastRow = region.getLastRow();
int firstCol = region.getFirstColumn();
int lastCol = region.getLastColumn();
// 获取边框样式
BorderStyle top = templateStyle.getBorderTop();
BorderStyle bottom = templateStyle.getBorderBottom();
BorderStyle left = templateStyle.getBorderLeft();
BorderStyle right = templateStyle.getBorderRight();
short borderColor = templateStyle.getTopBorderColor();
// 遍历区域的所有单元格并设置边框
for (int row = firstRow; row <= lastRow; row++) {
Row currentRow = getOrCreateRow(sheet, row);
for (int col = firstCol; col <= lastCol; col++) {
Cell cell = currentRow.getCell(col);
if (cell == null) {
cell = currentRow.createCell(col);
}
CellStyle cellStyle = workbook.createCellStyle();
// 复制字体、对齐等基本样式
copyBasicStyle(cellStyle, templateStyle);
// 设置边框
setCellBorder(cellStyle, top, bottom, left, right, borderColor);
// 如果是角上的单元格,设置对应的边框
if (row == firstRow) {
cellStyle.setBorderTop(top);
}
if (row == lastRow) {
cellStyle.setBorderBottom(bottom);
}
if (col == firstCol) {
cellStyle.setBorderLeft(left);
}
if (col == lastCol) {
cellStyle.setBorderRight(right);
}
cell.setCellStyle(cellStyle);
}
}
}
/**
*
*/
private void copyBasicStyle(CellStyle target, CellStyle source) {
// 复制字体
target.setFont(workbook.getFontAt(source.getFontIndex()));
// 复制对齐方式
target.setAlignment(source.getAlignment());
target.setVerticalAlignment(source.getVerticalAlignment());
// 复制填充
target.setFillForegroundColor(source.getFillForegroundColor());
target.setFillPattern(source.getFillPattern());
// 复制数据格式
target.setDataFormat(source.getDataFormat());
}
/**
*
*/
private void setCellBorder(CellStyle style, BorderStyle top, BorderStyle bottom,
BorderStyle left, BorderStyle right, short borderColor) {
style.setBorderTop(top);
style.setBorderBottom(bottom);
style.setBorderLeft(left);
style.setBorderRight(right);
style.setTopBorderColor(borderColor);
style.setBottomBorderColor(borderColor);
style.setLeftBorderColor(borderColor);
style.setRightBorderColor(borderColor);
}
/**
*
*/
protected Cell createCell(Row row, int column, String value, CellStyle style) {
Cell cell = row.createCell(column);
cell.setCellValue(value);
if (style != null) {
cell.setCellStyle(style);
}
return cell;
}
/**
*
*/
protected Cell createNumericCell(Row row, int column, Double value, CellStyle style) {
Cell cell = row.createCell(column);
if (value != null) {
cell.setCellValue(value);
}
if (style != null) {
cell.setCellStyle(style);
}
return cell;
}
/**
* BigDecimal
*/
protected Cell createNumericCell(Row row, int column, BigDecimal value, CellStyle style) {
if (value == null) {
return createCell(row, column, "", style);
}
return createNumericCell(row, column, value.doubleValue(), style);
}
/**
*
*/
protected void applyStyleToMergedRegion(Sheet sheet, CellRangeAddress region, CellStyle style) {
for (int rowNum = region.getFirstRow(); rowNum <= region.getLastRow(); rowNum++) {
Row row = getOrCreateRow(sheet, rowNum);
for (int colNum = region.getFirstColumn(); colNum <= region.getLastColumn(); colNum++) {
Cell cell = row.getCell(colNum);
if (cell == null) {
cell = row.createCell(colNum);
}
// 只设置边框和填充,保留原始字体
CellStyle newStyle = workbook.createCellStyle();
newStyle.cloneStyleFrom(style);
cell.setCellStyle(newStyle);
}
}
}
/**
*
*/
protected Row getOrCreateRow(Sheet sheet, int rowNum) {
Row row = sheet.getRow(rowNum);
if (row == null) {
row = sheet.createRow(rowNum);
}
return row;
}
/**
*
*/
protected void setColumnWidths(Sheet sheet, int[] widths) {
for (int i = 0; i < widths.length; i++) {
sheet.setColumnWidth(i, widths[i] * 256);
}
}
/**
*
*/
protected void createDataRowsWithDynamicMerge(Sheet sheet, int dataStartRow, int rows, int firstCol, int lastCol, String cellValue) {
// 1. 先横向合并前两列A列和B列
// 在数据区域的第一行创建"人工费"单元格
Row firstDataRow = getOrCreateRow(sheet, dataStartRow);
Cell leftMergeCell = firstDataRow.createCell(0);
leftMergeCell.setCellValue(cellValue);
// 创建左侧合并单元格的样式
leftMergeCell.setCellStyle(leftMergeStyle);
// 2. 根据右侧行数动态纵向合并
if (rows > 1) {
// 纵向合并前两列(覆盖所有数据行)
CellRangeAddress verticalMerge = new CellRangeAddress(
dataStartRow, dataStartRow + rows - 1, firstCol, lastCol);
sheet.addMergedRegion(verticalMerge);
// 为纵向合并区域的所有单元格设置样式
applyStyleToMergedRegion(sheet, verticalMerge, leftMergeStyle);
}
}
/**
*
*/
protected void clearTotalRowMap() {
sheetTotalRowMap.clear();
}
/**
*
*/
protected void addTotalRowMapping(String sheetName, Integer rowNum) {
sheetTotalRowMap.put(sheetName, rowNum);
}
/**
*
*/
protected Integer getTotalRow(String sheetName) {
return sheetTotalRowMap.get(sheetName);
}
/**
*
*/
protected byte[] writeToByteArray() throws IOException {
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
workbook.write(outputStream);
return outputStream.toByteArray();
}
}
/**
*
*/
protected void writeToFile(String filePath) throws IOException {
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
workbook.write(outputStream);
}
}
/**
*
*/
protected int fillEmptyRow(Sheet sheet, int rowNum, int startCol, int endCol, int fillRowNum) {
return fillEmptyRow(sheet, rowNum, startCol, endCol, fillRowNum, dataStyle);
}
/**
*
*/
protected int fillEmptyRow(Sheet sheet, int rowNum, int startCol, int endCol, int fillRowNum, CellStyle style) {
while (rowNum < fillRowNum) {
Row dataRow = sheet.getRow(rowNum);
if (dataRow == null) {
dataRow = sheet.createRow(rowNum);
}
for (int i = startCol; i <= endCol; i++) {
createCell(dataRow, i, "", style);
}
rowNum++;
}
return rowNum;
}
/**
*
*/
protected int fillEmptyRow(Sheet sheet, int rowNum, int startCol, int endCol, int fillRowNum,String formula, CellStyle style) {
while (rowNum < fillRowNum) {
Row dataRow = sheet.getRow(rowNum);
if (dataRow == null) {
dataRow = sheet.createRow(rowNum);
}
for (int i = startCol; i <= endCol; i++) {
createCell(dataRow, i, "", style);
}
rowNum++;
}
return rowNum;
}
/**
*
*
*/
protected void createVerticalMergedCell(Sheet sheet, int startRow, int endRow,
int col, String value, CellStyle style) {
if (startRow < 0 || endRow < startRow || col < 0) {
throw new IllegalArgumentException("Invalid parameters for vertical merge");
}
// 确保所有行都存在
for (int i = startRow; i <= endRow; i++) {
getOrCreateRow(sheet, i);
}
// 创建主单元格
Row mainRow = sheet.getRow(startRow);
Cell mainCell = mainRow.createCell(col);
mainCell.setCellValue(value);
// 创建合并区域
CellRangeAddress region = new CellRangeAddress(startRow, endRow, col, col);
sheet.addMergedRegion(region);
// 关键步骤:为合并区域的所有单元格设置独立样式和边框
fixMergedCellBorders(sheet, region, style);
// 设置主单元格的样式(用于显示内容)
mainCell.setCellStyle(style);
}
/**
*
*
*/
protected void fixMergedCellBorders(Sheet sheet, CellRangeAddress region, CellStyle templateStyle) {
int firstRow = region.getFirstRow();
int lastRow = region.getLastRow();
int firstCol = region.getFirstColumn();
int lastCol = region.getLastColumn();
// 遍历合并区域的所有单元格
for (int rowNum = firstRow; rowNum <= lastRow; rowNum++) {
Row row = getOrCreateRow(sheet, rowNum);
for (int colNum = firstCol; colNum <= lastCol; colNum++) {
Cell cell = row.getCell(colNum);
if (cell == null) {
cell = row.createCell(colNum);
}
// 创建独立的样式对象
CellStyle cellStyle = workbook.createCellStyle();
// 复制模板样式的基本属性
copyStyleWithoutBorder(cellStyle, templateStyle);
// 根据单元格位置设置边框
setBorderBasedOnPosition(cellStyle, templateStyle,
rowNum, colNum, firstRow, lastRow, firstCol, lastCol);
// 应用样式到单元格
cell.setCellStyle(cellStyle);
}
}
}
/**
*
*/
private void copyStyleWithoutBorder(CellStyle target, CellStyle source) {
// 复制字体
target.setFont(workbook.getFontAt(source.getFontIndex()));
// 复制对齐方式
target.setAlignment(source.getAlignment());
target.setVerticalAlignment(source.getVerticalAlignment());
// 复制填充
target.setFillForegroundColor(source.getFillForegroundColor());
target.setFillPattern(source.getFillPattern());
// 复制数据格式
target.setDataFormat(source.getDataFormat());
// 复制换行设置
target.setWrapText(source.getWrapText());
}
/**
*
*/
private void setBorderBasedOnPosition(CellStyle style, CellStyle templateStyle,
int row, int col,
int firstRow, int lastRow,
int firstCol, int lastCol) {
// 获取模板的边框样式
BorderStyle top = templateStyle.getBorderTop();
BorderStyle bottom = templateStyle.getBorderBottom();
BorderStyle left = templateStyle.getBorderLeft();
BorderStyle right = templateStyle.getBorderRight();
short borderColor = templateStyle.getTopBorderColor();
// 设置所有边框
style.setBorderTop(top);
style.setBorderBottom(bottom);
style.setBorderLeft(left);
style.setBorderRight(right);
style.setTopBorderColor(borderColor);
style.setBottomBorderColor(borderColor);
style.setLeftBorderColor(borderColor);
style.setRightBorderColor(borderColor);
}
}

@ -2,6 +2,7 @@ package org.dromara.oa.workflow.handler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.oa.erp.mapper.OaUniversalMapper;
import org.dromara.workflow.api.event.ProcessEvent;
import org.dromara.workflow.enums.FlowConfigEnum;
@ -10,6 +11,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
/**
@ -51,4 +53,39 @@ public class OaProcessEventHandler extends AbstractProcessEventHandler {
return oaUniversalMapper.dynamicUpdate(tableName, setFields, conditions);
}
@Override
protected int doUpdateWithOperator(FlowConfigEnum flowConfig, Map<String, Object> setFields, Map<String, Object> conditions,String businessId) {
String tableName = flowConfig.getTableName();
log.debug("执行数据库更新with operator: table={}, conditions={}", tableName, conditions);
return oaUniversalMapper.dynamicUpdateWithOperator(tableName, setFields, conditions,businessId);
}
@Override
protected void makeInvalid(FlowConfigEnum flowConfig, ProcessEvent processEvent) {
String invalidStatus = flowConfig.getInvalidBusinessStatus();
if (StringUtils.isNotBlank(invalidStatus)) {
Map<String, Object> params = processEvent.getParams();
// 步骤1: 构建更新字段
Map<String, Object> setFields = new HashMap<>();
setFields.put(flowConfig.getBusinessStatusField(),invalidStatus);
String relationId = oaUniversalMapper.selectRelationId(
flowConfig.getTableName(),
flowConfig.getRelationIdField(),
flowConfig.getBusinessPk(),
processEvent.getBusinessId()
);
// 步骤2: 构建查询条件
Map<String, Object> conditions = new HashMap<>();
conditions.put(flowConfig.getBusinessPk(), "!=" + processEvent.getBusinessId());
conditions.put(flowConfig.getRelationIdField(),relationId);
// 步骤3: 执行更新
int updateCount = doUpdateWithOperator(flowConfig, setFields, conditions,processEvent.getBusinessId());
}
}
}

@ -28,6 +28,7 @@ public class BudgetWorkflowStrategy implements WorkflowStrategy<ErpBudgetInfoBo>
@Override
public Map<String, Object> getVariables(ErpBudgetInfoBo bo) {
Map<String, Object> variables = new HashMap<>();
variables.put("projectId", bo.getProjectId());
variables.put("projectName", bo.getProjectName());
return variables;
}

Loading…
Cancel
Save