feat: 合并 upstream/v1.3 新增功能模块(安全合并策略)
新增功能模块:
- 药房管理:住院退药、处方审核功能
- 报表管理:门诊管理报表、药房结算报表、医嘱统计报表
- 支付管理:三方对账功能
- 新增枚举类:电子处方类型、频次类型、病历状态等10个
- 新增实体类:处方审核记录、第三方支付请求、中医结算目录
- 工具类增强:年龄计算、Excel工具
合并策略:仅合并低风险新增文件,保留现有业务功能
上游版本:v1.3 (2025-03-06发版)
合并分支:merge-upstream-v1.3-0310
🤖 Auto-generated by Claude Code
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.core.common.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Excel额外表头信息注解
|
||||
@@ -14,7 +15,7 @@ public @interface ExcelExtra {
|
||||
/**
|
||||
* 表头名称
|
||||
*/
|
||||
String name();
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* 日期格式,如:yyyy-MM-dd HH:mm:ss
|
||||
@@ -35,4 +36,15 @@ public @interface ExcelExtra {
|
||||
* 是否导出
|
||||
*/
|
||||
boolean isExport() default true;
|
||||
|
||||
/**
|
||||
* 精度 默认:-1(默认不开启BigDecimal格式化)
|
||||
*/
|
||||
int scale() default -1;
|
||||
|
||||
/**
|
||||
* BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
|
||||
*/
|
||||
int roundingMode() default BigDecimal.ROUND_HALF_EVEN;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.core.common.enums;
|
||||
|
||||
/**
|
||||
* 角色枚举
|
||||
*
|
||||
* @author swb
|
||||
* @date 2026-01-29
|
||||
*/
|
||||
public enum RoleEnum {
|
||||
DOCTOR("doctor", "医生"),
|
||||
NURSE("nurse", "护士"),
|
||||
ADMIN("admin", "管理员");
|
||||
private final String code;
|
||||
private final String info;
|
||||
|
||||
RoleEnum(String code, String info) {
|
||||
this.code = code;
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getInfo() {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -33,67 +33,125 @@ public final class AgeCalculatorUtil {
|
||||
return period.getYears();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 当前年龄取得(床位列表表示年龄用)
|
||||
// */
|
||||
// public static String getAge(Date date) {
|
||||
// // 将 Date 转换为 LocalDateTime
|
||||
// LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
// LocalDateTime now = LocalDateTime.now();
|
||||
// int years = now.getYear() - dateTime.getYear();
|
||||
// if (years > 2) {
|
||||
// return String.format("%d岁", years);
|
||||
// }
|
||||
//
|
||||
// Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
||||
// int months = period.getMonths();
|
||||
// int days = period.getDays();
|
||||
// long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
||||
//
|
||||
// if (hours < 0) {
|
||||
// hours += 24;
|
||||
// days--;
|
||||
// }
|
||||
// if (days < 0) {
|
||||
// months--;
|
||||
// days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
||||
// }
|
||||
// if (months < 0) {
|
||||
// months += 12;
|
||||
// years--;
|
||||
// }
|
||||
// if (years < 0) {
|
||||
// return "1小时";
|
||||
// }
|
||||
//
|
||||
// if (years > 0 && months > 0) {
|
||||
// return String.format("%d岁%d月", years, months);
|
||||
// }
|
||||
// if (years > 0) {
|
||||
// return String.format("%d岁", years);
|
||||
// }
|
||||
// if (months > 0 && days > 0) {
|
||||
// return String.format("%d月%d天", months, days);
|
||||
// }
|
||||
// if (months > 0) {
|
||||
// return String.format("%d月", months);
|
||||
// }
|
||||
// if (days > 0 && hours > 0) {
|
||||
// return String.format("%d天%d小时", days, hours);
|
||||
// }
|
||||
// if (days > 0) {
|
||||
// return String.format("%d天", days);
|
||||
// }
|
||||
// if (hours > 0) {
|
||||
// return String.format("%d小时", hours);
|
||||
// }
|
||||
// return "1小时";
|
||||
// }
|
||||
/**
|
||||
* 当前年龄取得(床位列表表示年龄用)
|
||||
* 复刻Oracle函数FUN_GET_AGE的核心逻辑:返回年龄字符串
|
||||
*
|
||||
* @param birthDate 出生日期
|
||||
* @return 年龄字符串(如:29岁、3岁5月、2月15天、18天),出生日期晚于当前日期返回空字符串
|
||||
*/
|
||||
public static String getAge(Date date) {
|
||||
// 添加空值检查
|
||||
if (date == null) {
|
||||
public static String getAge(Date birthDate) {
|
||||
// 入参校验
|
||||
if (birthDate == null) {
|
||||
return "";
|
||||
}
|
||||
// 将 Date 转换为 LocalDateTime
|
||||
LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int years = now.getYear() - dateTime.getYear();
|
||||
if (years > 2) {
|
||||
return String.format("%d岁", years);
|
||||
|
||||
// 将Date转换为LocalDate(使用系统默认时区)
|
||||
LocalDate birthLocalDate = birthDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
LocalDate currentDate = LocalDate.now();
|
||||
|
||||
// 计算总天数(对应Oracle中的IDAY)
|
||||
long totalDays = ChronoUnit.DAYS.between(birthLocalDate, currentDate);
|
||||
|
||||
// 若出生日期晚于当前日期,返回空字符串
|
||||
if (totalDays < 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Period period = Period.between(dateTime.toLocalDate(), now.toLocalDate());
|
||||
int months = period.getMonths();
|
||||
int days = period.getDays();
|
||||
long hours = ChronoUnit.HOURS.between(dateTime, now) - (days * 24L);
|
||||
// 计算年份(复刻Oracle的闰年补偿逻辑:(当前年-出生年)/4 补偿闰年天数)
|
||||
int birthYear = birthLocalDate.getYear();
|
||||
int currentYear = currentDate.getYear();
|
||||
long leapYearCompensation = (currentYear - birthYear) / 4;
|
||||
long adjustedDays = totalDays - leapYearCompensation;
|
||||
|
||||
if (hours < 0) {
|
||||
hours += 24;
|
||||
days--;
|
||||
}
|
||||
if (days < 0) {
|
||||
months--;
|
||||
days = getLastDayOfMonth(dateTime) - dateTime.getDayOfMonth() + now.getDayOfMonth();
|
||||
}
|
||||
if (months < 0) {
|
||||
months += 12;
|
||||
years--;
|
||||
}
|
||||
if (years < 0) {
|
||||
return "1小时";
|
||||
}
|
||||
// 计算年、月、天(按365天/年、30天/月粗略折算,与Oracle逻辑一致)
|
||||
int iYear = (int) (adjustedDays / 365);
|
||||
long remainingDaysAfterYear = adjustedDays - iYear * 365;
|
||||
int iMonth = (int) (remainingDaysAfterYear / 30);
|
||||
int iDay = (int) (remainingDaysAfterYear - iMonth * 30);
|
||||
|
||||
if (years > 0 && months > 0) {
|
||||
return String.format("%d岁%d月", years, months);
|
||||
// 按原函数规则拼接返回字符串
|
||||
if (iYear <= 0) {
|
||||
// 小于1岁
|
||||
if (iMonth <= 0) {
|
||||
// 小于1个月,返回X天
|
||||
return iDay + "天";
|
||||
} else {
|
||||
// 1个月及以上,返回X月X天
|
||||
return iMonth + "月" + iDay + "天";
|
||||
}
|
||||
} else {
|
||||
// 1岁及以上
|
||||
if (iYear < 5) {
|
||||
// 1-4岁
|
||||
if (iMonth <= 0) {
|
||||
// 无整月,返回X岁X天
|
||||
return iYear + "岁" + iDay + "天";
|
||||
} else {
|
||||
// 有整月,返回X岁X月
|
||||
return iYear + "岁" + iMonth + "月";
|
||||
}
|
||||
} else {
|
||||
// 5岁及以上,仅返回X岁
|
||||
return iYear + "岁";
|
||||
}
|
||||
}
|
||||
if (years > 0) {
|
||||
return String.format("%d岁", years);
|
||||
}
|
||||
if (months > 0 && days > 0) {
|
||||
return String.format("%d月%d天", months, days);
|
||||
}
|
||||
if (months > 0) {
|
||||
return String.format("%d月", months);
|
||||
}
|
||||
if (days > 0 && hours > 0) {
|
||||
return String.format("%d天%d小时", days, hours);
|
||||
}
|
||||
if (days > 0) {
|
||||
return String.format("%d天", days);
|
||||
}
|
||||
if (hours > 0) {
|
||||
return String.format("%d小时", hours);
|
||||
}
|
||||
return "1小时";
|
||||
}
|
||||
|
||||
private static int getLastDayOfMonth(LocalDateTime dateTime) {
|
||||
int[] daysInMonth = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
if (isLeapYear(dateTime.getYear()) && dateTime.getMonthValue() == 2) {
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package com.core.common.utils;
|
||||
|
||||
import com.core.common.annotation.Excel;
|
||||
import com.core.common.annotation.Excel.ColumnType;
|
||||
import com.core.common.annotation.Excel.Type;
|
||||
import com.core.common.annotation.ExcelExtra;
|
||||
import com.core.common.annotation.Excels;
|
||||
import com.core.common.config.CoreConfig;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.core.text.Convert;
|
||||
import com.core.common.exception.UtilException;
|
||||
import com.core.common.utils.file.FileTypeUtils;
|
||||
import com.core.common.utils.file.FileUtils;
|
||||
import com.core.common.utils.file.ImageUtils;
|
||||
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
||||
import com.core.common.utils.reflect.ReflectUtils;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
@@ -30,17 +29,20 @@ import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import com.core.common.annotation.Excel;
|
||||
import com.core.common.annotation.Excel.ColumnType;
|
||||
import com.core.common.annotation.Excel.Type;
|
||||
import com.core.common.annotation.ExcelExtra;
|
||||
import com.core.common.annotation.Excels;
|
||||
import com.core.common.config.CoreConfig;
|
||||
import com.core.common.core.domain.AjaxResult;
|
||||
import com.core.common.core.text.Convert;
|
||||
import com.core.common.exception.UtilException;
|
||||
import com.core.common.utils.file.FileTypeUtils;
|
||||
import com.core.common.utils.file.FileUtils;
|
||||
import com.core.common.utils.file.ImageUtils;
|
||||
import com.core.common.utils.poi.ExcelHandlerAdapter;
|
||||
import com.core.common.utils.reflect.ReflectUtils;
|
||||
|
||||
/**
|
||||
* Excel相关处理
|
||||
@@ -1164,6 +1166,11 @@ public class NewExcelUtil<T> {
|
||||
ParameterizedType pt = (ParameterizedType)field.getGenericType();
|
||||
Class<?> subClass = (Class<?>)pt.getActualTypeArguments()[0];
|
||||
this.subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class);
|
||||
if (StringUtils.isNotEmpty(includeFields)) {
|
||||
this.subFields = this.subFields.stream().filter(f -> ArrayUtils.contains(includeFields, f.getName())).collect(Collectors.toList());
|
||||
} else if (StringUtils.isNotEmpty(excludeFields)) {
|
||||
this.subFields = this.subFields.stream().filter(f -> !ArrayUtils.contains(excludeFields, f.getName())).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1441,7 +1448,28 @@ public class NewExcelUtil<T> {
|
||||
}
|
||||
|
||||
try {
|
||||
// 计算表格体总列数
|
||||
int totalCols = 0;
|
||||
for (Object[] os : fields) {
|
||||
Field field = (Field)os[0];
|
||||
if (Collection.class.isAssignableFrom(field.getType()) && subFields != null) {
|
||||
long subCount = subFields.stream().filter(f -> f.isAnnotationPresent(Excel.class)).count();
|
||||
totalCols += subCount;
|
||||
} else {
|
||||
totalCols++;
|
||||
}
|
||||
}
|
||||
if (totalCols == 0) totalCols = 1;
|
||||
|
||||
int currentRowNum = rownum;
|
||||
int colIndex = 0;
|
||||
Row row = null;
|
||||
boolean hasVisible = false;
|
||||
|
||||
// 布局配置:Label占用1列,Value占用2列,共3列
|
||||
int labelCols = 1;
|
||||
int valueCols = 2;
|
||||
int itemCols = labelCols + valueCols;
|
||||
|
||||
for (Object[] os : extraFields) {
|
||||
Field field = (Field)os[0];
|
||||
@@ -1451,43 +1479,50 @@ public class NewExcelUtil<T> {
|
||||
if (isExtraFieldHidden(field.getName())) {
|
||||
continue;
|
||||
}
|
||||
hasVisible = true;
|
||||
|
||||
Row row = sheet.createRow(currentRowNum++);
|
||||
// 自动换行:如果不是行首,且剩余空间不足,则换行
|
||||
if (row == null) {
|
||||
row = sheet.createRow(currentRowNum);
|
||||
} else if (colIndex > 0 && colIndex + itemCols > totalCols) {
|
||||
currentRowNum++;
|
||||
row = sheet.createRow(currentRowNum);
|
||||
colIndex = 0;
|
||||
}
|
||||
|
||||
// 创建标签单元格(第0列)
|
||||
Cell labelCell = row.createCell(0);
|
||||
// 1. 创建 Label 单元格
|
||||
Cell labelCell = row.createCell(colIndex);
|
||||
labelCell.setCellValue(attr.name());
|
||||
labelCell.setCellStyle(styles.get("extraLabel"));
|
||||
|
||||
// 创建值单元格(第1列)
|
||||
Cell valueCell = row.createCell(1);
|
||||
// 2. 创建 Value 单元格
|
||||
int valueStartCol = colIndex + labelCols;
|
||||
Cell valueCell = row.createCell(valueStartCol);
|
||||
Object value = field.get(entity);
|
||||
String cellValue = formatExtraCellValue(value, attr);
|
||||
valueCell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue);
|
||||
valueCell.setCellStyle(styles.get("extraValue"));
|
||||
|
||||
// 创建合并区域(第1列到第2列)
|
||||
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), 1, 2);
|
||||
sheet.addMergedRegion(mergedRegion);
|
||||
// 3. 合并 Value 单元格
|
||||
if (valueCols > 1) {
|
||||
int valueEndCol = valueStartCol + valueCols - 1;
|
||||
CellRangeAddress mergedRegion = new CellRangeAddress(row.getRowNum(), row.getRowNum(), valueStartCol, valueEndCol);
|
||||
sheet.addMergedRegion(mergedRegion);
|
||||
|
||||
// 设置边框
|
||||
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
||||
}
|
||||
|
||||
// 手动设置合并区域的边框,确保完整显示
|
||||
RegionUtil.setBorderTop(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderBottom(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderLeft(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setBorderRight(BorderStyle.THIN, mergedRegion, sheet);
|
||||
RegionUtil.setTopBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
RegionUtil.setBottomBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
RegionUtil.setLeftBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
RegionUtil.setRightBorderColor(IndexedColors.BLACK.getIndex(), mergedRegion, sheet);
|
||||
colIndex += itemCols;
|
||||
}
|
||||
|
||||
// 设置列宽
|
||||
sheet.setColumnWidth(0, 15 * 256); // 标签列宽
|
||||
sheet.setColumnWidth(1, 20 * 256); // 值列宽
|
||||
sheet.setColumnWidth(2, 20 * 256); // 值列宽
|
||||
|
||||
// 更新当前行号,在额外表头和数据表头之间空一行
|
||||
rownum = currentRowNum + 1;
|
||||
if (hasVisible) {
|
||||
rownum = currentRowNum + 2;
|
||||
}
|
||||
subMergedFirstRowNum = rownum;
|
||||
subMergedLastRowNum = rownum;
|
||||
|
||||
@@ -1508,6 +1543,10 @@ public class NewExcelUtil<T> {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value instanceof BigDecimal && attr.scale() >= 0) {
|
||||
return ((BigDecimal) value).setScale(attr.scale(), attr.roundingMode()).toString();
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(attr.dateFormat())) {
|
||||
return parseDateToStr(attr.dateFormat(), value);
|
||||
}
|
||||
@@ -1808,7 +1847,7 @@ public class NewExcelUtil<T> {
|
||||
row = sheet.createRow(rowNo);
|
||||
}
|
||||
// 子字段也要排序
|
||||
List<Field> subFields = FieldUtils.getFieldsListWithAnnotation(obj.getClass(), Excel.class);
|
||||
List<Field> subFields = this.subFields;
|
||||
List<Field> sortedSubFields = subFields.stream().sorted(Comparator.comparing(subField -> {
|
||||
Excel subExcel = subField.getAnnotation(Excel.class);
|
||||
return subExcel.sort();
|
||||
@@ -1832,4 +1871,4 @@ public class NewExcelUtil<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user