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:
2026-03-10 18:16:23 +08:00
parent 39b608dfd0
commit fe07cee58c
116 changed files with 5406 additions and 330 deletions

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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> {
}
}
}
}
}