敏实适配

This commit is contained in:
zjf committed 2026-09-10 11:10:42 +08:00
1 parent 0ac2115a60
commit 68d78d4b82
32 files changed
+1226 -236

No files matched your search

@@ -1,53 +1,41 @@
package com.bsd.pjcloud.canteen.dto.report;
import com.alibaba.excel.annotation.ExcelProperty;
import com.bsd.pjcloud.canteen.excel.ExcelYuanConverter;
import lombok.Data;
import java.math.BigDecimal;
/**
* 包厢台账(汇总)Excel,列序对齐样表。
*/
@Data
public class CanteenReportBoxExcel {
@ExcelProperty(index = 0)
private String monthText;
@ExcelProperty(index = 1)
private String bizDate;
@ExcelProperty(index = 5)
private String costCenter;
@ExcelProperty(index = 6)
private String costCenterCode;
@ExcelProperty(index = 8)
private String empNo;
@ExcelProperty(index = 9)
private String empName;
@ExcelProperty(index = 10, converter = ExcelYuanConverter.class)
private BigDecimal foodAmount;
@ExcelProperty(index = 11, converter = ExcelYuanConverter.class)
private BigDecimal drinkAmount;
@ExcelProperty(index = 12, converter = ExcelYuanConverter.class)
private BigDecimal totalAmount;
@ExcelProperty(index = 14)
private String legalEntity;
@ExcelProperty(index = 15)
private String remark1;
@ExcelProperty(index = 16)
private String remark2;
@ExcelProperty(index = 17)
private String park;
}
package com.bsd.pjcloud.canteen.dto.report;
import lombok.Data;
import java.math.BigDecimal;
/**
* 包厢台账(汇总)行数据。
* <p>
* 导入已改为 {@code CanteenReportBoxExcelBinder} 按规范化表头名称映射,
* 不再依赖 EasyExcel {@code @ExcelProperty}(多值 value 会被当成多级表头而非别名)。
*/
@Data
public class CanteenReportBoxExcel {
private String monthText;
private String bizDate;
private String costCenter;
private String costCenterCode;
private String empNo;
private String empName;
private BigDecimal foodAmount;
private BigDecimal drinkAmount;
private BigDecimal totalAmount;
private String legalEntity;
private String remark1;
private String remark2;
private String park;
}
@@ -4,138 +4,140 @@ import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 人脸消费系统导出 Excel,列序与样表一致(共 43 列)。
* 人脸消费系统导出 Excel。
* <p>
* 按表头名称匹配(不按固定列序号),避免不同月份导出增删/调列后字段错位。
* 明细入库原样落库;汇总到刷卡明细时再做字段映射。
*/
@Data
public class CanteenReportFaceExcel {
@ExcelProperty(index = 0)
@ExcelProperty({ "订单号" })
private String orderNo;
@ExcelProperty(index = 1)
@ExcelProperty({ "第三方订单号" })
private String thirdOrderNo;
@ExcelProperty(index = 2)
@ExcelProperty({ "创建时间" })
private String createTime;
@ExcelProperty(index = 3)
@ExcelProperty({ "支付时间" })
private String payTime;
@ExcelProperty(index = 4)
@ExcelProperty({ "扣款时间" })
private String deductTime;
@ExcelProperty(index = 5)
@ExcelProperty({ "一级组织" })
private String orgLevel1;
@ExcelProperty(index = 6)
@ExcelProperty({ "二级组织" })
private String orgLevel2;
@ExcelProperty(index = 7)
@ExcelProperty({ "三级组织" })
private String orgLevel3;
@ExcelProperty(index = 8)
@ExcelProperty({ "食堂" })
private String canteen;
@ExcelProperty(index = 9)
@ExcelProperty({ "食堂区域" })
private String canteenArea;
@ExcelProperty(index = 10)
@ExcelProperty({ "消费点" })
private String consumePoint;
@ExcelProperty(index = 11)
@ExcelProperty({ "姓名" })
private String empName;
@ExcelProperty(index = 12)
@ExcelProperty({ "手机号", "手机", "电话" })
private String phone;
@ExcelProperty(index = 13)
@ExcelProperty({ "法律归属" })
private String legalEntity;
@ExcelProperty(index = 14)
@ExcelProperty({ "人员编号", "工号", "员工编号" })
private String empNo;
@ExcelProperty(index = 15)
@ExcelProperty({ "卡号" })
private String cardNo;
@ExcelProperty(index = 16)
@ExcelProperty({ "分组" })
private String groupName;
@ExcelProperty(index = 17)
@ExcelProperty({ "部门" })
private String deptName;
@ExcelProperty(index = 18)
@ExcelProperty({ "支付方式" })
private String payMethod;
@ExcelProperty(index = 19)
@ExcelProperty({ "支付类型" })
private String payType;
@ExcelProperty(index = 20)
@ExcelProperty({ "订单金额" })
private String orderAmount;
@ExcelProperty(index = 21)
@ExcelProperty({ "动账金额" })
private String changeAmount;
@ExcelProperty(index = 22)
@ExcelProperty({ "储值动账" })
private String storedChange;
@ExcelProperty(index = 23)
@ExcelProperty({ "补贴动账" })
private String subsidyChange;
@ExcelProperty(index = 24)
@ExcelProperty({ "赠送动账" })
private String giftChange;
@ExcelProperty(index = 25)
@ExcelProperty({ "储值钱包余额" })
private String storedBalance;
@ExcelProperty(index = 26)
@ExcelProperty({ "补贴钱包余额" })
private String subsidyBalance;
@ExcelProperty(index = 27)
@ExcelProperty({ "赠送钱包余额" })
private String giftBalance;
@ExcelProperty(index = 28)
@ExcelProperty({ "动账组织" })
private String changeOrg;
@ExcelProperty(index = 29)
@ExcelProperty({ "优惠" })
private String discount;
@ExcelProperty(index = 30)
@ExcelProperty({ "优惠金额" })
private String discountAmount;
@ExcelProperty(index = 31)
@ExcelProperty({ "优惠类型" })
private String discountType;
@ExcelProperty(index = 32)
@ExcelProperty({ "券类型" })
private String couponType;
@ExcelProperty(index = 33)
@ExcelProperty({ "抵扣金额" })
private String deductAmount;
@ExcelProperty(index = 34)
@ExcelProperty({ "手续费" })
private String feeAmount;
@ExcelProperty(index = 35)
@ExcelProperty({ "餐补" })
private String mealSubsidy;
@ExcelProperty(index = 36)
@ExcelProperty({ "消费类型" })
private String consumeType;
@ExcelProperty(index = 37)
@ExcelProperty({ "餐段" })
private String mealSegment;
@ExcelProperty(index = 38)
@ExcelProperty({ "交易设备", "结算台", "设备名称" })
private String deviceName;
@ExcelProperty(index = 39)
@ExcelProperty({ "设备状态" })
private String deviceStatus;
@ExcelProperty(index = 40)
@ExcelProperty({ "备注" })
private String remark;
@ExcelProperty(index = 41)
@ExcelProperty({ "操作员" })
private String operatorName;
@ExcelProperty(index = 42)
@ExcelProperty({ "访客餐支付方式" })
private String visitorPayMethod;
}
@@ -17,6 +17,9 @@ public class CanteenReportPersonEntity extends Model<CanteenReportPersonEntity>
@TableId(type = IdType.ASSIGN_ID)
private Long id;
@Schema(description = "适用月份 yyyy-MM")
private String bizMonth;
@Schema(description = "工号")
private String empNo;
@@ -0,0 +1,136 @@
package com.bsd.pjcloud.canteen.report;
import cn.hutool.core.util.StrUtil;
import com.bsd.pjcloud.canteen.dto.report.CanteenReportBoxExcel;
import com.bsd.pjcloud.canteen.excel.ExcelYuanConverter;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
/**
* 包厢台账 Excel:按规范化表头名称映射(不依赖列序号 / EasyExcel 多值注解)。
* <p>
* 注意:EasyExcel 的 {@code @ExcelProperty({"A","B"})} 表示多级表头,不是别名;
* 样表常见表头含括号/空格,必须自行规范化匹配。
*/
public final class CanteenReportBoxExcelBinder {
private static final Map<String, String> ALIAS_TO_FIELD;
private static final Map<String, BiConsumer<CanteenReportBoxExcel, String>> FIELD_SETTERS;
static {
Map<String, String> alias = new HashMap<>();
putAliases(alias, "monthText", "月份", "业务月");
putAliases(alias, "bizDate", "日期", "业务日", "消费日期");
putAliases(alias, "costCenter", "成本中心名称(核对)", "成本中心名称", "成本中心", "核对成本", "核对成本(开票公司)");
putAliases(alias, "costCenterCode", "成本中心代码", "成本中心编码");
putAliases(alias, "empNo", "签单人工号", "工号", "人员编号", "员工编号");
putAliases(alias, "empName", "签单人", "姓名", "员工姓名");
putAliases(alias, "foodAmount", "食品", "食品金额");
putAliases(alias, "drinkAmount", "酒水", "酒水金额");
putAliases(alias, "totalAmount", "总计", "总金额", "金额", "消费金额");
putAliases(alias, "legalEntity", "费用承担公司(核对)", "费用承担公司", "法律归属", "法人公司");
putAliases(alias, "remark1", "备注1", "备注");
putAliases(alias, "remark2", "备注2");
putAliases(alias, "park", "园区", "厂区");
ALIAS_TO_FIELD = Collections.unmodifiableMap(alias);
Map<String, BiConsumer<CanteenReportBoxExcel, String>> setters = new HashMap<>();
setters.put("monthText", CanteenReportBoxExcel::setMonthText);
setters.put("bizDate", CanteenReportBoxExcel::setBizDate);
setters.put("costCenter", CanteenReportBoxExcel::setCostCenter);
setters.put("costCenterCode", CanteenReportBoxExcel::setCostCenterCode);
setters.put("empNo", CanteenReportBoxExcel::setEmpNo);
setters.put("empName", CanteenReportBoxExcel::setEmpName);
setters.put("foodAmount", (row, v) -> row.setFoodAmount(parseAmount(v)));
setters.put("drinkAmount", (row, v) -> row.setDrinkAmount(parseAmount(v)));
setters.put("totalAmount", (row, v) -> row.setTotalAmount(parseAmount(v)));
setters.put("legalEntity", CanteenReportBoxExcel::setLegalEntity);
setters.put("remark1", CanteenReportBoxExcel::setRemark1);
setters.put("remark2", CanteenReportBoxExcel::setRemark2);
setters.put("park", CanteenReportBoxExcel::setPark);
FIELD_SETTERS = Collections.unmodifiableMap(setters);
}
private CanteenReportBoxExcelBinder() {
}
private static void putAliases(Map<String, String> alias, String field, String... names) {
for (String n : names) {
alias.put(normalizeHead(n), field);
}
}
/** 去空白/全角空格等,便于表头匹配 */
public static String normalizeHead(String head) {
if (head == null) {
return "";
}
String s = head.replace('\u00A0', ' ').replace(' ', ' ').trim();
s = s.replaceAll("\\s+", "");
return s;
}
public static Map<Integer, String> buildColumnFieldMap(Map<Integer, String> rawHeadByIndex,
Set<String> outMatchedFields) {
Map<Integer, String> colField = new LinkedHashMap<>();
if (rawHeadByIndex == null) {
return colField;
}
for (Map.Entry<Integer, String> e : rawHeadByIndex.entrySet()) {
String field = ALIAS_TO_FIELD.get(normalizeHead(e.getValue()));
if (field == null) {
continue;
}
colField.put(e.getKey(), field);
if (outMatchedFields != null) {
outMatchedFields.add(field);
}
}
return colField;
}
public static boolean hasField(Set<String> matchedFields, String... fields) {
if (matchedFields == null || matchedFields.isEmpty()) {
return false;
}
for (String f : fields) {
if (matchedFields.contains(f)) {
return true;
}
}
return false;
}
public static CanteenReportBoxExcel bind(Map<Integer, String> colFieldMap, Map<Integer, ?> row) {
CanteenReportBoxExcel excel = new CanteenReportBoxExcel();
if (colFieldMap == null || row == null) {
return excel;
}
for (Map.Entry<Integer, String> e : colFieldMap.entrySet()) {
BiConsumer<CanteenReportBoxExcel, String> setter = FIELD_SETTERS.get(e.getValue());
if (setter == null) {
continue;
}
boolean preferInteger = "empNo".equals(e.getValue()) || "costCenterCode".equals(e.getValue());
String val = CanteenReportFaceExcelBinder.cellToPlainString(row.get(e.getKey()), preferInteger);
if (val != null) {
setter.accept(excel, val);
}
}
return excel;
}
private static BigDecimal parseAmount(String raw) {
if (StrUtil.isBlank(raw)) {
return null;
}
return ExcelYuanConverter.parseYuan(raw);
}
}
@@ -36,6 +36,12 @@ public final class CanteenReportConstants {
/** 刷卡备注:人员信息未命中,核对成本已回退法律归属 */
public static final String REMARK_PERSON_MISS = "未命中人员信息";
/** 人脸汇总匹配方式:按当月人员信息(工号→核对成本) */
public static final String MATCH_MODE_PERSON = "PERSON";
/** 人脸汇总匹配方式:按当月花名册(工号→人事范围/核对成本) */
public static final String MATCH_MODE_ROSTER = "ROSTER";
public static final List<String> INCOME_TYPES = Collections.unmodifiableList(Arrays.asList(
INCOME_SWIPE, INCOME_ALIPAY, INCOME_BOX, INCOME_OTHER, INCOME_PROFIT_ADJ, INCOME_INVOICE, INCOME_BAD_DEBT
));
@@ -0,0 +1,238 @@
package com.bsd.pjcloud.canteen.report;
import cn.hutool.core.util.StrUtil;
import com.bsd.pjcloud.canteen.dto.report.CanteenReportFaceExcel;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
/**
* 人脸 Excel:按规范化表头名称映射到字段(不依赖列序号 / EasyExcel 注解精确匹配)。
* <p>
* 处理空格、全角空格、常见别名;数字工号避免科学计数法。
*/
public final class CanteenReportFaceExcelBinder {
/** 规范化表头 → 字段名 */
private static final Map<String, String> ALIAS_TO_FIELD;
private static final Map<String, BiConsumer<CanteenReportFaceExcel, String>> FIELD_SETTERS;
static {
Map<String, String> alias = new HashMap<>();
putAliases(alias, "orderNo", "订单号");
putAliases(alias, "thirdOrderNo", "第三方订单号");
putAliases(alias, "createTime", "创建时间");
putAliases(alias, "payTime", "支付时间");
putAliases(alias, "deductTime", "扣款时间");
putAliases(alias, "orgLevel1", "一级组织");
putAliases(alias, "orgLevel2", "二级组织");
putAliases(alias, "orgLevel3", "三级组织");
putAliases(alias, "canteen", "食堂");
putAliases(alias, "canteenArea", "食堂区域");
putAliases(alias, "consumePoint", "消费点");
putAliases(alias, "empName", "姓名");
putAliases(alias, "phone", "手机号", "手机", "电话", "联系电话");
putAliases(alias, "legalEntity", "法律归属");
putAliases(alias, "empNo", "人员编号", "工号", "员工编号", "人员工号", "员工号");
putAliases(alias, "cardNo", "卡号");
putAliases(alias, "groupName", "分组", "员工分组");
putAliases(alias, "deptName", "部门");
putAliases(alias, "payMethod", "支付方式");
putAliases(alias, "payType", "支付类型");
putAliases(alias, "orderAmount", "订单金额");
putAliases(alias, "changeAmount", "动账金额");
putAliases(alias, "storedChange", "储值动账");
putAliases(alias, "subsidyChange", "补贴动账");
putAliases(alias, "giftChange", "赠送动账");
putAliases(alias, "storedBalance", "储值钱包余额");
putAliases(alias, "subsidyBalance", "补贴钱包余额");
putAliases(alias, "giftBalance", "赠送钱包余额");
putAliases(alias, "changeOrg", "动账组织");
putAliases(alias, "discount", "优惠");
putAliases(alias, "discountAmount", "优惠金额");
putAliases(alias, "discountType", "优惠类型");
putAliases(alias, "couponType", "券类型");
putAliases(alias, "deductAmount", "抵扣金额");
putAliases(alias, "feeAmount", "手续费");
putAliases(alias, "mealSubsidy", "餐补");
putAliases(alias, "consumeType", "消费类型");
putAliases(alias, "mealSegment", "餐段");
putAliases(alias, "deviceName", "交易设备", "结算台", "设备名称", "设备");
putAliases(alias, "deviceStatus", "设备状态");
putAliases(alias, "remark", "备注");
putAliases(alias, "operatorName", "操作员");
putAliases(alias, "visitorPayMethod", "访客餐支付方式");
ALIAS_TO_FIELD = Collections.unmodifiableMap(alias);
Map<String, BiConsumer<CanteenReportFaceExcel, String>> setters = new HashMap<>();
setters.put("orderNo", CanteenReportFaceExcel::setOrderNo);
setters.put("thirdOrderNo", CanteenReportFaceExcel::setThirdOrderNo);
setters.put("createTime", CanteenReportFaceExcel::setCreateTime);
setters.put("payTime", CanteenReportFaceExcel::setPayTime);
setters.put("deductTime", CanteenReportFaceExcel::setDeductTime);
setters.put("orgLevel1", CanteenReportFaceExcel::setOrgLevel1);
setters.put("orgLevel2", CanteenReportFaceExcel::setOrgLevel2);
setters.put("orgLevel3", CanteenReportFaceExcel::setOrgLevel3);
setters.put("canteen", CanteenReportFaceExcel::setCanteen);
setters.put("canteenArea", CanteenReportFaceExcel::setCanteenArea);
setters.put("consumePoint", CanteenReportFaceExcel::setConsumePoint);
setters.put("empName", CanteenReportFaceExcel::setEmpName);
setters.put("phone", CanteenReportFaceExcel::setPhone);
setters.put("legalEntity", CanteenReportFaceExcel::setLegalEntity);
setters.put("empNo", CanteenReportFaceExcel::setEmpNo);
setters.put("cardNo", CanteenReportFaceExcel::setCardNo);
setters.put("groupName", CanteenReportFaceExcel::setGroupName);
setters.put("deptName", CanteenReportFaceExcel::setDeptName);
setters.put("payMethod", CanteenReportFaceExcel::setPayMethod);
setters.put("payType", CanteenReportFaceExcel::setPayType);
setters.put("orderAmount", CanteenReportFaceExcel::setOrderAmount);
setters.put("changeAmount", CanteenReportFaceExcel::setChangeAmount);
setters.put("storedChange", CanteenReportFaceExcel::setStoredChange);
setters.put("subsidyChange", CanteenReportFaceExcel::setSubsidyChange);
setters.put("giftChange", CanteenReportFaceExcel::setGiftChange);
setters.put("storedBalance", CanteenReportFaceExcel::setStoredBalance);
setters.put("subsidyBalance", CanteenReportFaceExcel::setSubsidyBalance);
setters.put("giftBalance", CanteenReportFaceExcel::setGiftBalance);
setters.put("changeOrg", CanteenReportFaceExcel::setChangeOrg);
setters.put("discount", CanteenReportFaceExcel::setDiscount);
setters.put("discountAmount", CanteenReportFaceExcel::setDiscountAmount);
setters.put("discountType", CanteenReportFaceExcel::setDiscountType);
setters.put("couponType", CanteenReportFaceExcel::setCouponType);
setters.put("deductAmount", CanteenReportFaceExcel::setDeductAmount);
setters.put("feeAmount", CanteenReportFaceExcel::setFeeAmount);
setters.put("mealSubsidy", CanteenReportFaceExcel::setMealSubsidy);
setters.put("consumeType", CanteenReportFaceExcel::setConsumeType);
setters.put("mealSegment", CanteenReportFaceExcel::setMealSegment);
setters.put("deviceName", CanteenReportFaceExcel::setDeviceName);
setters.put("deviceStatus", CanteenReportFaceExcel::setDeviceStatus);
setters.put("remark", CanteenReportFaceExcel::setRemark);
setters.put("operatorName", CanteenReportFaceExcel::setOperatorName);
setters.put("visitorPayMethod", CanteenReportFaceExcel::setVisitorPayMethod);
FIELD_SETTERS = Collections.unmodifiableMap(setters);
}
private CanteenReportFaceExcelBinder() {
}
private static void putAliases(Map<String, String> alias, String field, String... names) {
for (String n : names) {
alias.put(normalizeHead(n), field);
}
}
/** 去空白/全角空格等,便于表头匹配 */
public static String normalizeHead(String head) {
if (head == null) {
return "";
}
String s = head.replace('\u00A0', ' ').replace(' ', ' ').trim();
// 去掉所有空白,兼容「人员 编号」「人员编号\n」
s = s.replaceAll("\\s+", "");
return s;
}
/**
* 根据原始表头行建立:列下标 → 字段名
*
* @return 列下标到字段;同时 outMatchedFields 填入已命中字段名
*/
public static Map<Integer, String> buildColumnFieldMap(Map<Integer, String> rawHeadByIndex,
java.util.Set<String> outMatchedFields) {
Map<Integer, String> colField = new LinkedHashMap<>();
if (rawHeadByIndex == null) {
return colField;
}
for (Map.Entry<Integer, String> e : rawHeadByIndex.entrySet()) {
String field = ALIAS_TO_FIELD.get(normalizeHead(e.getValue()));
if (field == null) {
continue;
}
colField.put(e.getKey(), field);
if (outMatchedFields != null) {
outMatchedFields.add(field);
}
}
return colField;
}
public static boolean hasField(java.util.Set<String> matchedFields, String... fields) {
if (matchedFields == null || matchedFields.isEmpty()) {
return false;
}
for (String f : fields) {
if (matchedFields.contains(f)) {
return true;
}
}
return false;
}
public static CanteenReportFaceExcel bind(Map<Integer, String> colFieldMap, Map<Integer, ?> row) {
CanteenReportFaceExcel excel = new CanteenReportFaceExcel();
if (colFieldMap == null || row == null) {
return excel;
}
for (Map.Entry<Integer, String> e : colFieldMap.entrySet()) {
BiConsumer<CanteenReportFaceExcel, String> setter = FIELD_SETTERS.get(e.getValue());
if (setter == null) {
continue;
}
String val = cellToPlainString(row.get(e.getKey()), "empNo".equals(e.getValue()) || "phone".equals(e.getValue())
|| "cardNo".equals(e.getValue()) || "orderNo".equals(e.getValue()));
if (val != null) {
setter.accept(excel, val);
}
}
return excel;
}
/**
* 单元格转字符串;对工号/手机号等尽量保留整数形式,避免 1.0093664E7。
*/
public static String cellToPlainString(Object raw, boolean preferInteger) {
if (raw == null) {
return null;
}
if (raw instanceof Number) {
return numberToPlain((Number) raw, preferInteger);
}
String s = String.valueOf(raw).trim();
if (s.isEmpty() || "null".equalsIgnoreCase(s)) {
return null;
}
if (preferInteger && s.matches("[-+]?\\d+(\\.\\d+)?([Ee][-+]?\\d+)?")) {
try {
return numberToPlain(new BigDecimal(s), true);
}
catch (Exception ignored) {
return s;
}
}
return s;
}
private static String numberToPlain(Number n, boolean preferInteger) {
BigDecimal bd = n instanceof BigDecimal ? (BigDecimal) n : new BigDecimal(n.toString());
if (preferInteger) {
try {
bd = bd.stripTrailingZeros();
if (bd.scale() <= 0) {
return bd.toBigInteger().toString();
}
}
catch (Exception ignored) {
// fall through
}
}
return bd.stripTrailingZeros().toPlainString();
}
public static String blankToNull(String s) {
return StrUtil.isBlank(s) ? null : s.trim();
}
}
@@ -28,4 +28,12 @@ public class CanteenReportBoxImportController {
@RequestParam(defaultValue = "true") boolean replaceMonth) {
return boxImportService.importExcel(file, bizMonth, replaceMonth);
}
@SysLog("清空包厢刷卡")
@Operation(summary = "按月清空包厢刷卡(source=BOX);可传 park 只清某厂区")
@DeleteMapping("/clear")
public R clear(@RequestParam String bizMonth,
@RequestParam(required = false) String park) {
return boxImportService.clearByMonthPark(bizMonth, park);
}
}
@@ -80,6 +80,16 @@ public class CanteenReportController {
return R.ok(calcService.businessReport(bizMonth));
}
@Operation(summary = "当月动态厂区列(来自刷卡/食材实际数据)")
@GetMapping("/parks")
public R parks(@RequestParam String bizMonth) {
List<String> parks = calcService.resolveParkColumns(bizMonth);
Map<String, Object> map = new java.util.LinkedHashMap<>();
map.put("parks", parks);
map.put("shareParks", parks);
return R.ok(map);
}
@ResponseExcel
@Operation(summary = "导出收入合计")
@GetMapping("/export/income")
@@ -113,14 +113,16 @@ public class CanteenReportFaceImportController {
}
@SysLog("人脸明细汇总到刷卡明细")
@Operation(summary = "统计汇总写入刷卡明细(支付方式原样作收入类别;B扫C等不自动归支付宝)")
@Operation(summary = "统计汇总写入刷卡明细;matchMode=PERSON|ROSTER;excludeParks 排除厂区(逗号分隔)")
@PostMapping("/aggregate")
public R aggregate(@RequestParam(required = false) String bizMonth,
@RequestParam(required = false) String dateStart,
@RequestParam(required = false) String dateEnd,
@RequestParam(required = false) String importLogId,
@RequestParam(defaultValue = "true") boolean replaceFace,
@RequestParam(required = false) String orgLevel2s) {
@RequestParam(required = false) String orgLevel2s,
@RequestParam(defaultValue = "PERSON") String matchMode,
@RequestParam(required = false) String excludeParks) {
Long logId = null;
if (cn.hutool.core.util.StrUtil.isNotBlank(importLogId)) {
try {
@@ -134,7 +136,11 @@ public class CanteenReportFaceImportController {
java.util.List<String> orgs = cn.hutool.core.util.StrUtil.isBlank(orgLevel2s)
? java.util.List.of()
: cn.hutool.core.util.StrUtil.splitTrim(orgLevel2s, ',');
return faceImportService.aggregateToSwipe(bizMonth, dateStart, dateEnd, logId, replaceFace, orgs);
java.util.List<String> exclude = cn.hutool.core.util.StrUtil.isBlank(excludeParks)
? java.util.List.of()
: cn.hutool.core.util.StrUtil.splitTrim(excludeParks, ',');
return faceImportService.aggregateToSwipe(bizMonth, dateStart, dateEnd, logId, replaceFace, orgs, matchMode,
exclude);
}
@Operation(summary = "明细中的二级组织列表(汇总筛选用)")
@@ -63,9 +63,11 @@ public class CanteenReportFoodEnergyController {
@SysLog("按月清空食材能耗")
@DeleteMapping("/month/{bizMonth}")
public R clearMonth(@PathVariable String bizMonth) {
public R clearMonth(@PathVariable String bizMonth,
@RequestParam(required = false) String park) {
return R.ok(foodEnergyService.remove(Wrappers.<CanteenReportFoodEnergyEntity>lambdaQuery()
.eq(CanteenReportFoodEnergyEntity::getBizMonth, bizMonth)));
.eq(CanteenReportFoodEnergyEntity::getBizMonth, bizMonth)
.eq(cn.hutool.core.util.StrUtil.isNotBlank(park), CanteenReportFoodEnergyEntity::getPark, park)));
}
@Operation(summary = "导入Excel(自动识别收发存汇总表 / 食堂报表「食材能耗」sheet)")
@@ -32,10 +32,11 @@ public class CanteenReportPersonController {
@Operation(summary = "分页")
@GetMapping("/page")
public R page(@ParameterObject Page page,
@RequestParam(required = false) String bizMonth,
@RequestParam(required = false) String empNo,
@RequestParam(required = false) String empName,
@RequestParam(required = false) String costCenter) {
return R.ok(personService.pageList(page, empNo, empName, costCenter));
return R.ok(personService.pageList(page, bizMonth, empNo, empName, costCenter));
}
@Operation(summary = "详情")
@@ -72,20 +73,28 @@ public class CanteenReportPersonController {
return personService.clearAll();
}
@SysLog("按月清空人员信息")
@Operation(summary = "按适用月清空")
@DeleteMapping("/month")
public R clearByMonth(@RequestParam String bizMonth) {
return personService.clearByMonth(bizMonth);
}
@SysLog("导入人员信息")
@Operation(summary = "导入Excel:replaceAll=true 全量覆盖;false 新增导入(同工号更新)")
@Operation(summary = "导入Excel:需传 bizMonth;replaceMonth=true 覆盖当月;false 同月同工号更新")
@PostMapping("/import")
public R importExcel(@RequestParam("file") MultipartFile file,
@RequestParam(defaultValue = "true") boolean replaceAll) throws Exception {
@RequestParam String bizMonth,
@RequestParam(defaultValue = "true") boolean replaceMonth) throws Exception {
List<CanteenReportPersonExcel> rows = CanteenReportExcelUtils.readSheet(file,
CanteenReportPersonExcel.class, "刷卡明细");
return personService.importExcel(rows, replaceAll);
return personService.importExcel(rows, bizMonth, replaceMonth);
}
@ResponseExcel
@Operation(summary = "导出")
@GetMapping("/export")
public List<CanteenReportPersonExcel> export() {
return personService.exportExcel();
public List<CanteenReportPersonExcel> export(@RequestParam(required = false) String bizMonth) {
return personService.exportExcel(bizMonth);
}
}
@@ -2,7 +2,6 @@ package com.bsd.pjcloud.canteen.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.canteen.entity.CanteenReportSettleParkEntity;
import com.bsd.pjcloud.canteen.report.CanteenReportConstants;
import com.bsd.pjcloud.canteen.service.CanteenReportSettleParkService;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
@@ -34,10 +33,10 @@ public class CanteenReportSettleParkController {
return R.ok(settleParkService.pageList(page, settleCounter, park));
}
@Operation(summary = "厂区枚举")
@Operation(summary = "厂区枚举(配置表+推荐名,可自由扩展)")
@GetMapping("/parks")
public R parks() {
return R.ok(CanteenReportConstants.PARKS);
return R.ok(settleParkService.listConfiguredParks());
}
@Operation(summary = "详情")
@@ -14,14 +14,16 @@ public interface CanteenReportFaceDetailMapper extends BaseMapper<CanteenReportF
int insertBatch(@Param("list") List<CanteenReportFaceDetailEntity> list);
int deletePhysicallyByImportLogId(@Param("importLogId") Long importLogId);
/** 分批物理删除,配合 limit 避免 OceanBase query_timeout */
int deletePhysicallyByImportLogId(@Param("importLogId") Long importLogId, @Param("limit") int limit);
int deletePhysicallyByDateRange(@Param("bizMonth") String bizMonth,
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd);
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd,
@Param("limit") int limit);
int deletePhysicallyDateRangeExceptLog(@Param("bizMonth") String bizMonth,
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd,
@Param("importLogId") Long importLogId);
@Param("importLogId") Long importLogId, @Param("limit") int limit);
/**
* 按人+维度汇总;刷脸等→员工刷卡,B扫C等扫码保持支付方式原样。金额用订单金额。
@@ -12,5 +12,7 @@ public interface CanteenReportPersonMapper extends BaseMapper<CanteenReportPerso
int deletePhysicallyAll();
int deletePhysicallyByBizMonth(@Param("bizMonth") String bizMonth);
int insertBatch(@Param("list") List<CanteenReportPersonEntity> list);
}
@@ -12,22 +12,33 @@ public interface CanteenReportSwipeMapper extends BaseMapper<CanteenReportSwipeE
int deletePhysicallyByBizMonth(@Param("bizMonth") String bizMonth);
int deletePhysicallyFaceByBizMonth(@Param("bizMonth") String bizMonth);
int deletePhysicallyFaceByBizMonth(@Param("bizMonth") String bizMonth, @Param("limit") int limit);
/**
* 物理删除人脸来源在指定日期段的数据(用于半月分段覆盖导入)
*/
int deletePhysicallyFaceByDateRange(@Param("bizMonth") String bizMonth,
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd);
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd, @Param("limit") int limit);
int deletePhysicallyByImportLogId(@Param("importLogId") Long importLogId);
int deletePhysicallyByImportLogId(@Param("importLogId") Long importLogId, @Param("limit") int limit);
int deletePhysicallyFaceRangeExceptLog(@Param("bizMonth") String bizMonth,
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd,
@Param("importLogId") Long importLogId);
@Param("importLogId") Long importLogId, @Param("limit") int limit);
/** 按来源物理删除某月刷卡(如 BOX 包厢) */
int deletePhysicallyBySourceAndBizMonth(@Param("source") String source, @Param("bizMonth") String bizMonth);
int deletePhysicallyBySourceAndBizMonth(@Param("source") String source, @Param("bizMonth") String bizMonth,
@Param("limit") int limit);
/** 按来源+月+可选厂区物理删除 */
int deletePhysicallyBySourceBizMonthPark(@Param("source") String source, @Param("bizMonth") String bizMonth,
@Param("park") String park, @Param("limit") int limit);
/**
* 删除某月 FACE,但保留 excludeParks 中的厂区数据(排除厂区汇总时用)
*/
int deletePhysicallyFaceByBizMonthExceptParks(@Param("bizMonth") String bizMonth,
@Param("excludeParks") List<String> excludeParks, @Param("limit") int limit);
int insertBatch(@Param("list") List<CanteenReportSwipeEntity> list);
}
@@ -9,4 +9,7 @@ import org.springframework.web.multipart.MultipartFile;
public interface CanteenReportBoxImportService {
R importExcel(MultipartFile file, String bizMonth, boolean replaceMonth);
/** 清空某月包厢刷卡;park 非空则只清该厂区 */
R clearByMonthPark(String bizMonth, String park);
}
@@ -23,4 +23,7 @@ public interface CanteenReportCalcService {
List<CanteenReportBusinessRowVO> businessReport(String bizMonth);
Map<String, Object> summary(String bizMonth);
/** 当月刷卡/食材实际出现的厂区列(动态) */
List<String> resolveParkColumns(String bizMonth);
}
@@ -23,10 +23,12 @@ public interface CanteenReportFaceImportService extends IService<CanteenReportIm
/**
* 将明细汇总写入刷卡明细。刷脸等→员工刷卡;B扫C等扫码保持支付方式原样(不自动归支付宝,可批量改);
* 扫码客单不匹配人员只配厂区;员工刷卡用工号匹配核对成本。厂区按结算台。入库时已按地区过滤,汇总默认全量。
* 扫码客单不匹配人员只配厂区;员工刷卡按 matchMode 用工号匹配当月人员信息或花名册。厂区按结算台。
*
* @param matchMode PERSON=当月人员信息;ROSTER=当月花名册;默认 PERSON
*/
R aggregateToSwipe(String bizMonth, String dateStart, String dateEnd, Long importLogId, boolean replaceFace,
List<String> orgLevel2s);
List<String> orgLevel2s, String matchMode, List<String> excludeParks);
Page<CanteenReportImportLogEntity> pageLogs(Page page, String bizMonth, String status);
@@ -11,21 +11,27 @@ import java.util.Map;
public interface CanteenReportPersonService extends IService<CanteenReportPersonEntity> {
Page<CanteenReportPersonEntity> pageList(Page page, String empNo, String empName, String costCenter);
Page<CanteenReportPersonEntity> pageList(Page page, String bizMonth, String empNo, String empName,
String costCenter);
R savePerson(CanteenReportPersonEntity entity);
R updatePerson(CanteenReportPersonEntity entity);
/**
* @param replaceAll true=全量覆盖;false=新增导入(同工号则更新)
* 按适用月导入。
*
* @param bizMonth 适用月 yyyy-MM
* @param replaceMonth true=先清空该月再入库;false=同月同工号更新
*/
R importExcel(List<CanteenReportPersonExcel> rows, boolean replaceAll);
R importExcel(List<CanteenReportPersonExcel> rows, String bizMonth, boolean replaceMonth);
List<CanteenReportPersonExcel> exportExcel();
List<CanteenReportPersonExcel> exportExcel(String bizMonth);
R clearAll();
/** empNo → costCenter(空核对成本也占位) */
Map<String, String> loadCostCenterMap();
R clearByMonth(String bizMonth);
/** 指定适用月:empNo → costCenter(空核对成本也占位) */
Map<String, String> loadCostCenterMap(String bizMonth);
}
@@ -10,4 +10,7 @@ public interface CanteenReportSettleParkService extends IService<CanteenReportSe
Page<CanteenReportSettleParkEntity> pageList(Page page, String settleCounter, String park);
R saveOrUpdateRow(CanteenReportSettleParkEntity entity);
/** 配置过的厂区 + 推荐常量(可自由扩展) */
java.util.List<String> listConfiguredParks();
}
@@ -3,12 +3,13 @@ package com.bsd.pjcloud.canteen.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.bsd.pjcloud.canteen.excel.ExcelYuanConverter;
import com.bsd.pjcloud.canteen.excel.ExcelYuanStringConverter;
import com.bsd.pjcloud.canteen.dto.report.CanteenReportBoxExcel;
import com.bsd.pjcloud.canteen.entity.CanteenReportSwipeEntity;
import com.bsd.pjcloud.canteen.mapper.CanteenReportSwipeMapper;
import com.bsd.pjcloud.canteen.report.CanteenReportBoxExcelBinder;
import com.bsd.pjcloud.canteen.report.CanteenReportConstants;
import com.bsd.pjcloud.canteen.service.CanteenReportBoxImportService;
import com.bsd.pjcloud.common.core.constant.CommonConstants;
@@ -23,7 +24,11 @@ import org.springframework.web.multipart.MultipartFile;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Service
@RequiredArgsConstructor
@@ -31,9 +36,33 @@ public class CanteenReportBoxImportServiceImpl implements CanteenReportBoxImport
private static final String SOURCE_BOX = "BOX";
private static final int BATCH = 800;
private static final int DELETE_BATCH = 3000;
private static final int SKIP_SAMPLE_LIMIT = 50;
private final CanteenReportSwipeMapper swipeMapper;
private int deleteBoxByMonthPark(String bizMonth, String park) {
int total = 0;
int n;
do {
n = swipeMapper.deletePhysicallyBySourceBizMonthPark(SOURCE_BOX, bizMonth, park, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
private int deleteBoxByMonth(String bizMonth) {
int total = 0;
int n;
do {
n = swipeMapper.deletePhysicallyBySourceAndBizMonth(SOURCE_BOX, bizMonth, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
@Override
@Transactional(rollbackFor = Exception.class)
public R importExcel(MultipartFile file, String bizMonth, boolean replaceMonth) {
@@ -44,13 +73,15 @@ public class CanteenReportBoxImportServiceImpl implements CanteenReportBoxImport
return R.failed("请传入业务月份 bizMonth");
}
try {
List<CanteenReportBoxExcel> rows = EasyExcel.read(file.getInputStream())
.head(CanteenReportBoxExcel.class)
.registerConverter(new ExcelYuanConverter())
.registerConverter(new ExcelYuanStringConverter())
BoxRowAccumulator acc = new BoxRowAccumulator();
EasyExcel.read(file.getInputStream(), acc)
.sheet()
.headRowNumber(1)
.doReadSync();
.doRead();
if (StrUtil.isNotBlank(acc.headError)) {
return R.failed(acc.headError);
}
List<CanteenReportBoxExcel> rows = acc.rows;
if (CollUtil.isEmpty(rows)) {
return R.failed("导入数据为空");
}
@@ -58,16 +89,27 @@ public class CanteenReportBoxImportServiceImpl implements CanteenReportBoxImport
Long tenantId = TenantContextHolder.getTenantId();
LocalDateTime now = LocalDateTime.now();
List<CanteenReportSwipeEntity> saveList = new ArrayList<>();
Map<String, Integer> skipReasonCount = new LinkedHashMap<>();
List<String> skipSamples = new ArrayList<>();
int skip = 0;
for (CanteenReportBoxExcel row : rows) {
for (int i = 0; i < rows.size(); i++) {
CanteenReportBoxExcel row = rows.get(i);
int excelRow = i + 2; // 表头第1行,数据从第2行起
if (isBlankRow(row)) {
skip++;
recordSkip(skipReasonCount, skipSamples, excelRow, "空行", row);
continue;
}
BigDecimal amount = resolveAmount(row);
if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) {
skip++;
recordSkip(skipReasonCount, skipSamples, excelRow, "无金额或金额为0(总计/食品+酒水)", row);
continue;
}
if (StrUtil.isBlank(row.getCostCenter()) && StrUtil.isBlank(row.getEmpNo())
&& StrUtil.isBlank(row.getPark())) {
skip++;
recordSkip(skipReasonCount, skipSamples, excelRow, "成本中心、签单人工号、园区均为空", row);
continue;
}
CanteenReportSwipeEntity e = new CanteenReportSwipeEntity();
@@ -95,22 +137,181 @@ public class CanteenReportBoxImportServiceImpl implements CanteenReportBoxImport
saveList.add(e);
}
if (CollUtil.isEmpty(saveList)) {
return R.failed("无有效包厢金额可入库");
Map<String, Object> emptyData = buildSkipPayload(0, skip, skipReasonCount, skipSamples);
return R.failed(emptyData, "无有效包厢金额可入库。" + buildSkipSummary(skip, skipReasonCount, skipSamples));
}
if (replaceMonth) {
swipeMapper.deletePhysicallyBySourceAndBizMonth(SOURCE_BOX, bizMonth.trim());
deleteBoxByMonth(bizMonth.trim());
}
for (List<CanteenReportSwipeEntity> chunk : CollUtil.split(saveList, BATCH)) {
swipeMapper.insertBatch(chunk);
}
return R.ok(null, String.format("包厢导入成功:写入刷卡 %d 条(收入类别=包厢收入),跳过 %d 条",
saveList.size(), skip));
Map<String, Object> data = buildSkipPayload(saveList.size(), skip, skipReasonCount, skipSamples);
String msg = String.format("包厢导入成功:写入刷卡 %d 条(收入类别=包厢收入),跳过 %d 条",
saveList.size(), skip);
if (skip > 0) {
msg = msg + "。可查看跳过明细。";
}
return R.ok(data, msg);
}
catch (Exception e) {
return R.failed("导入失败: " + e.getMessage());
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public R clearByMonthPark(String bizMonth, String park) {
if (StrUtil.isBlank(bizMonth)) {
return R.failed("请传入业务月份 bizMonth");
}
String month = bizMonth.trim();
String parkTrim = StrUtil.trim(park);
int n = deleteBoxByMonthPark(month, StrUtil.isBlank(parkTrim) ? null : parkTrim);
if (StrUtil.isNotBlank(parkTrim)) {
return R.ok(null, String.format("已清空 %s 厂区「%s」包厢刷卡 %d 条", month, parkTrim, n));
}
return R.ok(null, String.format("已清空 %s 全部包厢刷卡 %d 条", month, n));
}
/**
* 按表头名称读包厢明细(Map 行),关键列缺失时直接报错。
*/
static final class BoxRowAccumulator extends AnalysisEventListener<Map<Integer, String>> {
private final List<CanteenReportBoxExcel> rows = new ArrayList<>();
private Map<Integer, String> colFieldMap = Map.of();
private String headError;
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
if (headMap == null || headMap.isEmpty()) {
headError = "未读到表头,请确认第一行是列名";
return;
}
Set<String> matched = new HashSet<>();
this.colFieldMap = CanteenReportBoxExcelBinder.buildColumnFieldMap(headMap, matched);
List<String> missing = new ArrayList<>();
if (!CanteenReportBoxExcelBinder.hasField(matched, "costCenter")) {
missing.add("成本中心名称(核对)");
}
if (!CanteenReportBoxExcelBinder.hasField(matched, "totalAmount", "foodAmount", "drinkAmount")) {
missing.add("总计/食品/酒水");
}
if (!CanteenReportBoxExcelBinder.hasField(matched, "park")) {
missing.add("园区");
}
if (!missing.isEmpty()) {
List<String> rawHeads = new ArrayList<>();
for (String h : headMap.values()) {
if (StrUtil.isNotBlank(h)) {
rawHeads.add(h.trim());
}
}
headError = "表头缺少关键列(按名称匹配):" + String.join("、", missing)
+ "。当前表头示例:" + String.join(" | ", rawHeads.subList(0, Math.min(12, rawHeads.size())))
+ (rawHeads.size() > 12 ? " …" : "")
+ "。请导入「包厢台账」明细 Sheet(含成本中心/签单人/总计/园区)。";
}
}
@Override
public void invoke(Map<Integer, String> row, AnalysisContext context) {
if (StrUtil.isNotBlank(headError)) {
return;
}
rows.add(CanteenReportBoxExcelBinder.bind(colFieldMap, row));
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// no-op:行已全部收集到 rows
}
}
private static boolean isBlankRow(CanteenReportBoxExcel row) {
if (row == null) {
return true;
}
return StrUtil.isBlank(row.getBizDate())
&& StrUtil.isBlank(row.getCostCenter())
&& StrUtil.isBlank(row.getEmpNo())
&& StrUtil.isBlank(row.getEmpName())
&& StrUtil.isBlank(row.getPark())
&& StrUtil.isBlank(row.getLegalEntity())
&& (row.getTotalAmount() == null || row.getTotalAmount().compareTo(BigDecimal.ZERO) == 0)
&& (row.getFoodAmount() == null || row.getFoodAmount().compareTo(BigDecimal.ZERO) == 0)
&& (row.getDrinkAmount() == null || row.getDrinkAmount().compareTo(BigDecimal.ZERO) == 0);
}
private static void recordSkip(Map<String, Integer> reasonCount, List<String> samples, int excelRow,
String reason, CanteenReportBoxExcel row) {
reasonCount.merge(reason, 1, Integer::sum);
if (samples.size() >= SKIP_SAMPLE_LIMIT) {
return;
}
samples.add("Excel第" + excelRow + "行:" + reason + ";" + briefRow(row));
}
private static String briefRow(CanteenReportBoxExcel row) {
if (row == null) {
return "";
}
List<String> parts = new ArrayList<>();
if (StrUtil.isNotBlank(row.getBizDate())) {
parts.add("日期=" + row.getBizDate());
}
if (StrUtil.isNotBlank(row.getCostCenter())) {
parts.add("成本中心=" + row.getCostCenter());
}
if (StrUtil.isNotBlank(row.getEmpNo())) {
parts.add("工号=" + row.getEmpNo());
}
if (StrUtil.isNotBlank(row.getEmpName())) {
parts.add("签单人=" + row.getEmpName());
}
if (StrUtil.isNotBlank(row.getPark())) {
parts.add("园区=" + row.getPark());
}
if (row.getTotalAmount() != null) {
parts.add("总计=" + row.getTotalAmount());
}
return parts.isEmpty() ? "(无有效字段)" : String.join(",", parts);
}
private static String buildSkipSummary(int skip, Map<String, Integer> reasonCount, List<String> samples) {
if (skip <= 0) {
return "";
}
StringBuilder sb = new StringBuilder();
sb.append("共跳过 ").append(skip).append(" 行。\n");
sb.append("【分类统计】\n");
for (Map.Entry<String, Integer> e : reasonCount.entrySet()) {
sb.append("- ").append(e.getKey()).append(":").append(e.getValue()).append(" 行\n");
}
if (CollUtil.isNotEmpty(samples)) {
sb.append("【样例(最多").append(SKIP_SAMPLE_LIMIT).append("条)】\n");
for (String s : samples) {
sb.append(s).append("\n");
}
if (skip > samples.size()) {
sb.append("…其余 ").append(skip - samples.size()).append(" 行略\n");
}
}
return sb.toString();
}
private static Map<String, Object> buildSkipPayload(int written, int skip, Map<String, Integer> reasonCount,
List<String> samples) {
Map<String, Object> data = new LinkedHashMap<>();
data.put("written", written);
data.put("skip", skip);
data.put("skipReasons", reasonCount);
data.put("skipSamples", samples);
data.put("skipSummary", buildSkipSummary(skip, reasonCount, samples));
return data;
}
private static BigDecimal resolveAmount(CanteenReportBoxExcel row) {
if (row.getTotalAmount() != null) {
return row.getTotalAmount();
@@ -22,15 +22,19 @@ import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 报表计算演示实现:
* 收入合计 = 刷卡明细按成本中心×厂区汇总;
* 供应商分摊 = 权重 × (厂区食材池 − 厂区包厢 − 厂区其他收入),权重=中心员工刷卡/厂区员工刷卡;
* 经营报表 = 收入(万元) + 支出(食材=分摊−能耗−外协;能耗/外协按食材能耗分类;人工按人工列) + 毛利分析。
* 厂区列按当月刷卡/食材实际出现的厂区动态生成(有数据才出列)。
*/
@Service
@RequiredArgsConstructor
@@ -43,8 +47,64 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
private final CanteenReportFoodEnergyService foodEnergyService;
private final CanteenReportManualAdjustService adjustService;
/** 单次计算内的动态厂区列 */
private final ThreadLocal<List<String>> parkColsHolder = new ThreadLocal<>();
private List<String> parkCols() {
List<String> cols = parkColsHolder.get();
return cols != null ? cols : List.of();
}
private void beginParkCols(String bizMonth) {
parkColsHolder.set(resolveParkColumns(bizMonth));
}
private void endParkCols() {
parkColsHolder.remove();
}
/**
* 当月刷卡 + 食材能耗中出现的非空厂区;优先按常量推荐顺序,其余按名称排序。
*/
@Override
public List<String> resolveParkColumns(String bizMonth) {
Set<String> found = new LinkedHashSet<>();
if (StrUtil.isNotBlank(bizMonth)) {
for (CanteenReportSwipeEntity s : loadSwipes(bizMonth)) {
if (StrUtil.isNotBlank(s.getPark())) {
found.add(s.getPark().trim());
}
}
for (CanteenReportFoodEnergyEntity f : loadFoods(bizMonth)) {
if (StrUtil.isNotBlank(f.getPark())) {
found.add(f.getPark().trim());
}
}
}
List<String> ordered = new ArrayList<>();
for (String p : CanteenReportConstants.PARKS) {
if (found.remove(p)) {
ordered.add(p);
}
}
List<String> rest = new ArrayList<>(found);
Collections.sort(rest);
ordered.addAll(rest);
return ordered;
}
@Override
public List<CanteenReportIncomeRowVO> incomeReport(String bizMonth) {
beginParkCols(bizMonth);
try {
return incomeReport0(bizMonth);
}
finally {
endParkCols();
}
}
private List<CanteenReportIncomeRowVO> incomeReport0(String bizMonth) {
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
// key: incomeType|costCenter|park -> amount
Map<String, BigDecimal> agg = new HashMap<>();
@@ -74,6 +134,16 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
@Override
public List<CanteenReportShareRowVO> supplierShareReport(String bizMonth) {
beginParkCols(bizMonth);
try {
return supplierShareReport0(bizMonth);
}
finally {
endParkCols();
}
}
private List<CanteenReportShareRowVO> supplierShareReport0(String bizMonth) {
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
List<CanteenReportFoodEnergyEntity> foods = loadFoods(bizMonth);
List<CanteenReportManualAdjustEntity> adjusts = loadAdjusts(bizMonth);
@@ -110,7 +180,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
// 可分摊 = 厂区食材池 − 厂区包厢收入 − 厂区其他收入(与样表一致)
Map<String, BigDecimal> parkAllocable = new LinkedHashMap<>();
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
parkAllocable.put(park, nz(parkFoodPool.get(park)).subtract(nz(parkBoxOther.get(park))));
}
@@ -145,7 +215,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
List<CanteenReportShareRowVO> rows = new ArrayList<>();
Map<String, BigDecimal> shareParkTotal = new LinkedHashMap<>();
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
shareParkTotal.put(park, BigDecimal.ZERO);
}
BigDecimal totalSub = BigDecimal.ZERO;
@@ -157,7 +227,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
CanteenReportShareRowVO row = new CanteenReportShareRowVO();
row.setCostCenter(center);
BigDecimal sub = BigDecimal.ZERO;
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
BigDecimal allocable = nz(parkAllocable.get(park));
BigDecimal parkTotal = nz(parkSwipeTotal.get(park));
BigDecimal centerAmt = nz(centerParkSwipe.get(center + "|" + park));
@@ -186,7 +256,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
if (globalAdjust.compareTo(BigDecimal.ZERO) != 0) {
CanteenReportShareRowVO globalRow = new CanteenReportShareRowVO();
globalRow.setCostCenter("开票调整(全局)");
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
globalRow.getParkAmounts().put(park, BigDecimal.ZERO);
}
globalRow.setSubtotal(BigDecimal.ZERO);
@@ -201,7 +271,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
// 小计行
CanteenReportShareRowVO subtotalRow = new CanteenReportShareRowVO();
subtotalRow.setCostCenter("小计");
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
subtotalRow.getParkAmounts().put(park, nz(shareParkTotal.get(park)).setScale(SCALE, RoundingMode.HALF_UP));
}
subtotalRow.setSubtotal(totalSub.setScale(SCALE, RoundingMode.HALF_UP));
@@ -214,6 +284,16 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
@Override
public CanteenReportShareAuditVO supplierShareAudit(String bizMonth) {
beginParkCols(bizMonth);
try {
return supplierShareAudit0(bizMonth);
}
finally {
endParkCols();
}
}
private CanteenReportShareAuditVO supplierShareAudit0(String bizMonth) {
List<CanteenReportFoodEnergyEntity> foods = loadFoods(bizMonth);
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
Map<String, BigDecimal> parkFoodPool = new HashMap<>();
@@ -241,7 +321,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
}
}
Map<String, BigDecimal> parkAllocable = new LinkedHashMap<>();
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
parkAllocable.put(park, nz(parkFoodPool.get(park)).subtract(nz(parkBoxOther.get(park))));
}
@@ -256,7 +336,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
List<CanteenReportShareWeightRowVO> weightRows = new ArrayList<>();
Map<String, BigDecimal> shareParkTotal = new LinkedHashMap<>();
Map<String, BigDecimal> weightParkTotal = new LinkedHashMap<>();
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
shareParkTotal.put(park, BigDecimal.ZERO);
weightParkTotal.put(park, BigDecimal.ZERO);
}
@@ -264,7 +344,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
CanteenReportShareWeightRowVO row = new CanteenReportShareWeightRowVO();
row.setCostCenter(center);
boolean any = false;
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
BigDecimal parkTotal = nz(parkSwipeTotal.get(park));
BigDecimal centerAmt = nz(centerParkSwipe.get(center + "|" + park)).setScale(SCALE, RoundingMode.HALF_UP);
BigDecimal weight = BigDecimal.ZERO;
@@ -291,7 +371,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
}
CanteenReportShareWeightRowVO totalRow = new CanteenReportShareWeightRowVO();
totalRow.setCostCenter("合计(权重应≈1)");
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
totalRow.getSwipeAmounts().put(park, nz(parkSwipeTotal.get(park)).setScale(SCALE, RoundingMode.HALF_UP));
totalRow.getWeights().put(park, nz(weightParkTotal.get(park)).setScale(8, RoundingMode.HALF_UP));
totalRow.getShareAmounts().put(park, nz(shareParkTotal.get(park)).setScale(SCALE, RoundingMode.HALF_UP));
@@ -299,7 +379,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
weightRows.add(totalRow);
List<CanteenReportShareParkCheckVO> parkChecks = new ArrayList<>();
for (String park : CanteenReportConstants.SHARE_PARKS) {
for (String park : parkCols()) {
BigDecimal foodPool = nz(parkFoodPool.get(park)).setScale(SCALE, RoundingMode.HALF_UP);
BigDecimal boxOther = nz(parkBoxOther.get(park)).setScale(SCALE, RoundingMode.HALF_UP);
BigDecimal allocable = foodPool.subtract(boxOther).setScale(SCALE, RoundingMode.HALF_UP);
@@ -322,8 +402,18 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
@Override
public List<CanteenReportBusinessRowVO> businessReport(String bizMonth) {
beginParkCols(bizMonth);
try {
return businessReport0(bizMonth);
}
finally {
endParkCols();
}
}
private List<CanteenReportBusinessRowVO> businessReport0(String bizMonth) {
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
List<CanteenReportShareRowVO> shares = supplierShareReport(bizMonth);
List<CanteenReportShareRowVO> shares = supplierShareReport0(bizMonth);
List<CanteenReportFoodEnergyEntity> foods = loadFoods(bizMonth);
Map<String, Map<String, BigDecimal>> incomeByTypePark = new HashMap<>();
@@ -377,7 +467,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
}
}
Map<String, BigDecimal> foodOnlyYuan = emptyParkMap();
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
foodOnlyYuan.put(park, nz(shareExpenseYuan.get(park)).subtract(nz(splitFromShareYuan.get(park))));
}
@@ -418,20 +508,28 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
@Override
public Map<String, Object> summary(String bizMonth) {
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
List<CanteenReportFoodEnergyEntity> foods = loadFoods(bizMonth);
BigDecimal swipeSum = swipes.stream().map(s -> nz(s.getConsumeAmount())).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal foodSum = foods.stream().map(f -> nz(f.getSettleAmount())).reduce(BigDecimal.ZERO, BigDecimal::add);
Map<String, Object> map = new LinkedHashMap<>();
map.put("bizMonth", bizMonth);
map.put("swipeCount", swipes.size());
map.put("swipeAmount", swipeSum);
map.put("foodCount", foods.size());
map.put("foodAmount", foodSum);
CanteenReportShareAuditVO audit = supplierShareAudit(bizMonth);
map.put("shareParkChecks", audit.getParkChecks());
map.put("shareWeightRows", audit.getWeightRows());
return map;
beginParkCols(bizMonth);
try {
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
List<CanteenReportFoodEnergyEntity> foods = loadFoods(bizMonth);
BigDecimal swipeSum = swipes.stream().map(s -> nz(s.getConsumeAmount())).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal foodSum = foods.stream().map(f -> nz(f.getSettleAmount())).reduce(BigDecimal.ZERO, BigDecimal::add);
Map<String, Object> map = new LinkedHashMap<>();
map.put("bizMonth", bizMonth);
map.put("swipeCount", swipes.size());
map.put("swipeAmount", swipeSum);
map.put("foodCount", foods.size());
map.put("foodAmount", foodSum);
CanteenReportShareAuditVO audit = supplierShareAudit0(bizMonth);
map.put("shareParkChecks", audit.getParkChecks());
map.put("shareWeightRows", audit.getWeightRows());
map.put("parks", new ArrayList<>(parkCols()));
map.put("shareParks", new ArrayList<>(parkCols()));
return map;
}
finally {
endParkCols();
}
}
private static boolean isShareMetaRow(String costCenter) {
@@ -468,7 +566,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
CanteenReportIncomeRowVO row = emptyIncomeRow(incomeType, center);
BigDecimal sub = BigDecimal.ZERO;
boolean any = false;
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
BigDecimal amt = nz(agg.get(incomeType + "|" + center + "|" + park)).setScale(SCALE, RoundingMode.HALF_UP);
row.getParkAmounts().put(park, amt);
sub = sub.add(amt);
@@ -502,20 +600,20 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
return v == null ? BigDecimal.ZERO : v;
}
private static CanteenReportIncomeRowVO emptyIncomeRow(String group, String center) {
private CanteenReportIncomeRowVO emptyIncomeRow(String group, String center) {
CanteenReportIncomeRowVO row = new CanteenReportIncomeRowVO();
row.setGroupName(group);
row.setCostCenter(center);
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
row.getParkAmounts().put(park, BigDecimal.ZERO);
}
row.setSubtotal(BigDecimal.ZERO);
return row;
}
private static Map<String, BigDecimal> emptyParkMap() {
private Map<String, BigDecimal> emptyParkMap() {
Map<String, BigDecimal> map = new LinkedHashMap<>();
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
map.put(park, BigDecimal.ZERO);
}
return map;
@@ -530,12 +628,12 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
}
}
private static CanteenReportBusinessRowVO toWanRow(String type, String category, Map<String, BigDecimal> yuanByPark) {
private CanteenReportBusinessRowVO toWanRow(String type, String category, Map<String, BigDecimal> yuanByPark) {
CanteenReportBusinessRowVO row = new CanteenReportBusinessRowVO();
row.setType(type);
row.setCategory(category);
BigDecimal sub = BigDecimal.ZERO;
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
BigDecimal wan = nz(yuanByPark.get(park)).divide(WAN, SCALE, RoundingMode.HALF_UP);
row.getParkAmounts().put(park, wan);
sub = sub.add(wan);
@@ -544,7 +642,7 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
return row;
}
private static CanteenReportBusinessRowVO sumRows(String type, String category, List<CanteenReportBusinessRowVO> rows) {
private CanteenReportBusinessRowVO sumRows(String type, String category, List<CanteenReportBusinessRowVO> rows) {
CanteenReportBusinessRowVO row = new CanteenReportBusinessRowVO();
row.setType(type);
row.setCategory(category);
@@ -573,13 +671,13 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
return row;
}
private static CanteenReportBusinessRowVO subtractRow(String type, String category,
private CanteenReportBusinessRowVO subtractRow(String type, String category,
CanteenReportBusinessRowVO a, CanteenReportBusinessRowVO b) {
CanteenReportBusinessRowVO row = new CanteenReportBusinessRowVO();
row.setType(type);
row.setCategory(category);
BigDecimal sub = BigDecimal.ZERO;
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
BigDecimal v = nz(a.getParkAmounts().get(park)).subtract(nz(b.getParkAmounts().get(park)))
.setScale(SCALE, RoundingMode.HALF_UP);
row.getParkAmounts().put(park, v);
@@ -589,12 +687,12 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
return row;
}
private static CanteenReportBusinessRowVO negateRow(String type, String category, CanteenReportBusinessRowVO src) {
private CanteenReportBusinessRowVO negateRow(String type, String category, CanteenReportBusinessRowVO src) {
CanteenReportBusinessRowVO row = new CanteenReportBusinessRowVO();
row.setType(type);
row.setCategory(category);
BigDecimal sub = BigDecimal.ZERO;
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
BigDecimal v = nz(src.getParkAmounts().get(park)).negate().setScale(SCALE, RoundingMode.HALF_UP);
row.getParkAmounts().put(park, v);
sub = sub.add(v);
@@ -603,12 +701,12 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService {
return row;
}
private static CanteenReportBusinessRowVO ratioRow(String type, String category,
private CanteenReportBusinessRowVO ratioRow(String type, String category,
CanteenReportBusinessRowVO num, CanteenReportBusinessRowVO den) {
CanteenReportBusinessRowVO row = new CanteenReportBusinessRowVO();
row.setType(type);
row.setCategory(category);
for (String park : CanteenReportConstants.PARKS) {
for (String park : parkCols()) {
BigDecimal d = nz(den.getParkAmounts().get(park));
BigDecimal n = nz(num.getParkAmounts().get(park));
BigDecimal r = d.compareTo(BigDecimal.ZERO) == 0 ? BigDecimal.ZERO
@@ -5,7 +5,7 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -24,9 +24,11 @@ import com.bsd.pjcloud.canteen.mapper.CanteenReportImportLogMapper;
import com.bsd.pjcloud.canteen.mapper.CanteenReportSettleParkMapper;
import com.bsd.pjcloud.canteen.mapper.CanteenReportSwipeMapper;
import com.bsd.pjcloud.canteen.report.CanteenReportConstants;
import com.bsd.pjcloud.canteen.report.CanteenReportFaceExcelBinder;
import com.bsd.pjcloud.canteen.report.CanteenReportFaceMapper;
import com.bsd.pjcloud.canteen.service.CanteenReportFaceImportService;
import com.bsd.pjcloud.canteen.service.CanteenReportPersonService;
import com.bsd.pjcloud.canteen.service.CanteenReportRosterService;
import com.bsd.pjcloud.common.core.constant.CommonConstants;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
@@ -61,17 +63,87 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
implements CanteenReportFaceImportService {
private static final int BATCH_SIZE = 1500;
/** OceanBase query_timeout≈10s,大批量 DELETE 需分批 */
private static final int DELETE_BATCH = 3000;
private final CanteenReportFaceDetailMapper faceDetailMapper;
private final CanteenReportSwipeMapper swipeMapper;
private final CanteenReportSettleParkMapper settleParkMapper;
private final CanteenReportPersonService personService;
private final CanteenReportRosterService rosterService;
private int deleteFaceDetailsByLogId(Long importLogId) {
int total = 0;
int n;
do {
n = faceDetailMapper.deletePhysicallyByImportLogId(importLogId, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
private int deleteFaceDetailsByDateRange(String bizMonth, String dateStart, String dateEnd) {
int total = 0;
int n;
do {
n = faceDetailMapper.deletePhysicallyByDateRange(bizMonth, dateStart, dateEnd, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
private int deleteFaceDetailsDateRangeExceptLog(String bizMonth, String dateStart, String dateEnd, Long importLogId) {
int total = 0;
int n;
do {
n = faceDetailMapper.deletePhysicallyDateRangeExceptLog(bizMonth, dateStart, dateEnd, importLogId, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
private int deleteSwipeByImportLogId(Long importLogId) {
int total = 0;
int n;
do {
n = swipeMapper.deletePhysicallyByImportLogId(importLogId, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
private int deleteSwipeFaceByBizMonth(String bizMonth) {
int total = 0;
int n;
do {
n = swipeMapper.deletePhysicallyFaceByBizMonth(bizMonth, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
private int deleteSwipeFaceByBizMonthExceptParks(String bizMonth, List<String> excludeParks) {
int total = 0;
int n;
do {
n = swipeMapper.deletePhysicallyFaceByBizMonthExceptParks(bizMonth, excludeParks, DELETE_BATCH);
total += n;
}
while (n >= DELETE_BATCH);
return total;
}
@Override
public Page<CanteenReportImportLogEntity> pageLogs(Page page, String bizMonth, String status) {
return page(page, Wrappers.<CanteenReportImportLogEntity>lambdaQuery()
.eq(StrUtil.isNotBlank(bizMonth), CanteenReportImportLogEntity::getBizMonth, bizMonth)
.eq(StrUtil.isNotBlank(status), CanteenReportImportLogEntity::getStatus, status)
.orderByDesc(CanteenReportImportLogEntity::getBizMonth)
.orderByDesc(CanteenReportImportLogEntity::getCreateTime));
}
@@ -95,8 +167,8 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
if ("RUNNING".equals(logEntity.getStatus())) {
return R.failed(String.format("「%s」仍在导入中,请完成或失败后再删除", StrUtil.blankToDefault(logEntity.getFileName(), "导入任务")));
}
detailRows += faceDetailMapper.deletePhysicallyByImportLogId(id);
swipeRows += swipeMapper.deletePhysicallyByImportLogId(id);
detailRows += deleteFaceDetailsByLogId(id);
swipeRows += deleteSwipeByImportLogId(id);
removeById(id);
logCount++;
}
@@ -223,7 +295,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
catch (Throwable e) {
log.error("人脸明细异步导入失败 logId={}", logEntity.getId(), e);
try {
faceDetailMapper.deletePhysicallyByImportLogId(logEntity.getId());
deleteFaceDetailsByLogId(logEntity.getId());
}
catch (Exception ignored) {
// ignore
@@ -266,16 +338,19 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
boolean preReplace = replaceRange && StrUtil.isNotBlank(bizMonth) && StrUtil.isNotBlank(dateStart)
&& StrUtil.isNotBlank(dateEnd);
if (preReplace) {
faceDetailMapper.deletePhysicallyByDateRange(bizMonth.trim(), dateStart.trim(), dateEnd.trim());
deleteFaceDetailsByDateRange(bizMonth.trim(), dateStart.trim(), dateEnd.trim());
}
FaceDetailAccumulator acc = new FaceDetailAccumulator(logEntity.getId(), bizMonth, operator, tenantId,
LocalDateTime.now(), faceDetailMapper, BATCH_SIZE, orgLevel2s);
try (InputStream in = new BufferedInputStream(new FileInputStream(temp), 1 << 20)) {
EasyExcel.read(in, CanteenReportFaceExcel.class, acc)
.sheet()
.headRowNumber(1)
.doRead();
// 不用实体注解匹配:按规范化表头手动映射,避免空格/别名导致人员编号等列丢失
EasyExcel.read(in, acc).sheet().headRowNumber(1).doRead();
}
if (StrUtil.isNotBlank(acc.getHeadError())) {
deleteFaceDetailsByLogId(logEntity.getId());
failLog(logEntity, acc.getHeadError(), t0);
return;
}
acc.flush();
@@ -283,7 +358,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
String resolvedStart = StrUtil.blankToDefault(StrUtil.trim(dateStart), acc.getMinDate());
String resolvedEnd = StrUtil.blankToDefault(StrUtil.trim(dateEnd), acc.getMaxDate());
if (StrUtil.isBlank(resolvedMonth)) {
faceDetailMapper.deletePhysicallyByImportLogId(logEntity.getId());
deleteFaceDetailsByLogId(logEntity.getId());
failLog(logEntity, "无法确定报表归属月,请填写报表归属月", t0);
return;
}
@@ -293,8 +368,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
}
if (replaceRange && !preReplace) {
faceDetailMapper.deletePhysicallyDateRangeExceptLog(resolvedMonth, resolvedStart, resolvedEnd,
logEntity.getId());
deleteFaceDetailsDateRangeExceptLog(resolvedMonth, resolvedStart, resolvedEnd, logEntity.getId());
}
logEntity.setBizMonth(resolvedMonth);
@@ -320,7 +394,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
@Override
public R aggregateToSwipe(String bizMonth, String dateStart, String dateEnd, Long importLogId,
boolean replaceFace, List<String> orgLevel2s) {
boolean replaceFace, List<String> orgLevel2s, String matchMode, List<String> excludeParks) {
if (StrUtil.isBlank(bizMonth) && importLogId == null) {
return R.failed("请传入业务月份 bizMonth,或指定某一批导入日志");
}
@@ -391,12 +465,28 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
return R.failed("无法确定报表归属月");
}
boolean useRoster = CanteenReportConstants.MATCH_MODE_ROSTER.equalsIgnoreCase(StrUtil.trim(matchMode));
String matchLabel = useRoster ? "花名册" : "人员信息";
String missRemark = useRoster ? CanteenReportConstants.REMARK_ROSTER_MISS
: CanteenReportConstants.REMARK_PERSON_MISS;
Map<String, String> settleParkMap = loadSettleParkMap();
Map<String, String> costCenterMap = personService.loadCostCenterMap();
// 月份对月份:人员信息 / 花名册均按汇总归属月加载
Map<String, String> costCenterMap = useRoster
? rosterService.loadHrScopeMap(month)
: personService.loadCostCenterMap(month);
Set<String> excludeParkSet = new HashSet<>();
if (CollUtil.isNotEmpty(excludeParks)) {
for (String p : excludeParks) {
if (StrUtil.isNotBlank(p)) {
excludeParkSet.add(p.trim());
}
}
}
List<CanteenReportSwipeEntity> saveList = new ArrayList<>(aggRows.size());
Map<String, CanteenReportRosterMissVO> missByKey = new LinkedHashMap<>();
int unmatchedPark = 0;
int unmatchedPerson = 0;
int excludedParkRows = 0;
for (CanteenReportFaceAggRow row : aggRows) {
if (row.getAmount() == null || row.getAmount().compareTo(BigDecimal.ZERO) == 0) {
continue;
@@ -423,10 +513,10 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
missReason = "无工号";
}
else if (!costCenterMap.containsKey(empNo)) {
missReason = "人员信息无此工号";
missReason = useRoster ? "花名册无此工号" : "人员信息无此工号";
}
else if (StrUtil.isBlank(costCenterMap.get(empNo))) {
missReason = "人员信息无核对成本";
missReason = useRoster ? "花名册无人事范围" : "人员信息无核对成本";
}
else {
costCenter = costCenterMap.get(empNo);
@@ -434,7 +524,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
if (missReason != null) {
unmatchedPerson++;
costCenter = CanteenReportFaceMapper.mapCostCenter(row.getLegalEntity());
e.setRemarkTag(CanteenReportConstants.REMARK_PERSON_MISS);
e.setRemarkTag(missRemark);
String key = StrUtil.isNotBlank(empNo) ? empNo
: ("名:" + StrUtil.blankToDefault(row.getEmpName(), "") + "|法:"
+ StrUtil.blankToDefault(row.getLegalEntity(), ""));
@@ -459,6 +549,10 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
if (StrUtil.isBlank(park) && StrUtil.isNotBlank(row.getDeviceName())) {
unmatchedPark++;
}
if (StrUtil.isNotBlank(park) && excludeParkSet.contains(park)) {
excludedParkRows++;
continue;
}
e.setPark(park);
e.setImportLogId(importLogId);
e.setSource("FACE");
@@ -472,15 +566,21 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
}
if (CollUtil.isEmpty(saveList)) {
return R.failed("汇总后可写入金额均为 0,未写入刷卡明细");
return R.failed(excludedParkRows > 0
? String.format("汇总后可写入金额均为 0 或均被排除厂区过滤(排除 %d 条),未写入刷卡明细", excludedParkRows)
: "汇总后可写入金额均为 0,未写入刷卡明细");
}
if (replaceFace) {
if (importLogId != null) {
swipeMapper.deletePhysicallyByImportLogId(importLogId);
deleteSwipeByImportLogId(importLogId);
}
else if (CollUtil.isNotEmpty(excludeParkSet)) {
// 排除厂区:保留这些厂区已有 FACE,只覆盖其余
deleteSwipeFaceByBizMonthExceptParks(month, new ArrayList<>(excludeParkSet));
}
else {
swipeMapper.deletePhysicallyFaceByBizMonth(month);
deleteSwipeFaceByBizMonth(month);
}
}
@@ -509,16 +609,21 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
long cost = System.currentTimeMillis() - t0;
StringBuilder tip = new StringBuilder();
tip.append(";匹配方式:按").append(matchLabel).append("(").append(month).append(")");
if (costCenterMap.isEmpty() && unmatchedPerson > 0) {
tip.append(";人员信息为空,员工刷卡核对成本已回退法律归属映射(B扫C等扫码客单不匹配人员),请先导入人员信息");
tip.append(String.format(";当月%s为空,员工刷卡核对成本已回退法律归属映射(B扫C等扫码客单不匹配人员),请先导入当月%s",
matchLabel, matchLabel));
}
else if (unmatchedPerson > 0) {
tip.append(String.format(";有 %d 条员工刷卡、%d 人未命中人员信息,已回退法律归属(B扫C等保持支付方式原样,可批量改为支付宝);刷卡明细备注「未命中人员信息」可筛选",
unmatchedPerson, missByKey.size()));
tip.append(String.format(";有 %d 条员工刷卡、%d 人未命中%s,已回退法律归属(B扫C等保持支付方式原样,可批量改为支付宝);刷卡明细备注「%s」可筛选",
unmatchedPerson, missByKey.size(), matchLabel, missRemark));
}
if (unmatchedPark > 0) {
tip.append(String.format(";有 %d 条结算台未配置厂区,请到「结算台厂区」维护后再重新汇总", unmatchedPark));
}
if (excludedParkRows > 0) {
tip.append(String.format(";已排除厂区数据 %d 条(保留原 FACE 不覆盖)", excludedParkRows));
}
CanteenReportAggResultVO result = new CanteenReportAggResultVO();
result.setSwipeRows(saveList.size());
result.setUnmatchedRows(unmatchedPerson);
@@ -599,7 +704,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
return null;
}
static final class FaceDetailAccumulator implements ReadListener<CanteenReportFaceExcel> {
static final class FaceDetailAccumulator extends AnalysisEventListener<Map<Integer, String>> {
private static final int SKIP_SAMPLE_LIMIT = 80;
@@ -622,6 +727,9 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
private String minDate;
private String maxDate;
private String resolvedBizMonth;
private String headError;
/** 列下标 → 字段名 */
private Map<Integer, String> colFieldMap = Map.of();
FaceDetailAccumulator(Long importLogId, String bizMonthParam, String operator, Long tenantId,
LocalDateTime now, CanteenReportFaceDetailMapper faceDetailMapper, int batchSize,
@@ -641,10 +749,54 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
}
}
/**
* 按规范化表头名称映射;关键列缺失时直接失败。
*/
@Override
public void invoke(CanteenReportFaceExcel data, AnalysisContext context) {
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
if (headMap == null || headMap.isEmpty()) {
headError = "未读到表头,请确认第一行是列名";
return;
}
Set<String> matched = new HashSet<>();
this.colFieldMap = CanteenReportFaceExcelBinder.buildColumnFieldMap(headMap, matched);
List<String> missing = new ArrayList<>();
if (!CanteenReportFaceExcelBinder.hasField(matched, "empNo")) {
missing.add("人员编号");
}
if (!CanteenReportFaceExcelBinder.hasField(matched, "empName")) {
missing.add("姓名");
}
if (!CanteenReportFaceExcelBinder.hasField(matched, "orderAmount")) {
missing.add("订单金额");
}
if (!CanteenReportFaceExcelBinder.hasField(matched, "deviceName")) {
missing.add("交易设备");
}
if (!CanteenReportFaceExcelBinder.hasField(matched, "orgLevel2")) {
missing.add("二级组织");
}
if (!missing.isEmpty()) {
List<String> rawHeads = new ArrayList<>();
for (String h : headMap.values()) {
if (StrUtil.isNotBlank(h)) {
rawHeads.add(h.trim());
}
}
headError = "表头缺少关键列(按名称匹配):" + String.join("、", missing)
+ "。当前表头示例:" + String.join(" | ", rawHeads.subList(0, Math.min(12, rawHeads.size())))
+ (rawHeads.size() > 12 ? " …" : "");
}
}
@Override
public void invoke(Map<Integer, String> row, AnalysisContext context) {
if (StrUtil.isNotBlank(headError)) {
return;
}
totalRows++;
int excelRow = context.readRowHolder() != null ? context.readRowHolder().getRowIndex() + 1 : totalRows + 1;
CanteenReportFaceExcel data = CanteenReportFaceExcelBinder.bind(colFieldMap, row);
CanteenReportFaceDetailEntity e = CanteenReportFaceMapper.toDetail(data, bizMonthParam, importLogId);
if (e == null) {
recordSkip(excelRow, "空行(无订单号/姓名/人员编号/金额/时间)", data);
@@ -787,5 +939,9 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
String getResolvedBizMonth() {
return resolvedBizMonth;
}
String getHeadError() {
return headError;
}
}
}
@@ -48,6 +48,7 @@ public class CanteenReportFoodEnergyServiceImpl
.eq(StrUtil.isNotBlank(bizMonth), CanteenReportFoodEnergyEntity::getBizMonth, bizMonth)
.eq(StrUtil.isNotBlank(park), CanteenReportFoodEnergyEntity::getPark, park)
.like(StrUtil.isNotBlank(supplier), CanteenReportFoodEnergyEntity::getSupplier, supplier)
.orderByDesc(CanteenReportFoodEnergyEntity::getBizMonth)
.orderByDesc(CanteenReportFoodEnergyEntity::getId));
}
@@ -33,6 +33,7 @@ public class CanteenReportManualAdjustServiceImpl
public Page<CanteenReportManualAdjustEntity> pageList(Page page, String bizMonth) {
return page(page, Wrappers.<CanteenReportManualAdjustEntity>lambdaQuery()
.eq(StrUtil.isNotBlank(bizMonth), CanteenReportManualAdjustEntity::getBizMonth, bizMonth)
.orderByDesc(CanteenReportManualAdjustEntity::getBizMonth)
.orderByDesc(CanteenReportManualAdjustEntity::getId));
}
@@ -36,11 +36,14 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
private static final int BATCH = 800;
@Override
public Page<CanteenReportPersonEntity> pageList(Page page, String empNo, String empName, String costCenter) {
public Page<CanteenReportPersonEntity> pageList(Page page, String bizMonth, String empNo, String empName,
String costCenter) {
return page(page, Wrappers.<CanteenReportPersonEntity>lambdaQuery()
.eq(StrUtil.isNotBlank(bizMonth), CanteenReportPersonEntity::getBizMonth, bizMonth)
.like(StrUtil.isNotBlank(empNo), CanteenReportPersonEntity::getEmpNo, empNo)
.like(StrUtil.isNotBlank(empName), CanteenReportPersonEntity::getEmpName, empName)
.like(StrUtil.isNotBlank(costCenter), CanteenReportPersonEntity::getCostCenter, costCenter)
.orderByDesc(CanteenReportPersonEntity::getBizMonth)
.orderByAsc(CanteenReportPersonEntity::getEmpNo));
}
@@ -73,10 +76,14 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
@Override
@Transactional(rollbackFor = Exception.class)
public R importExcel(List<CanteenReportPersonExcel> rows, boolean replaceAll) {
public R importExcel(List<CanteenReportPersonExcel> rows, String bizMonth, boolean replaceMonth) {
if (CollUtil.isEmpty(rows)) {
return R.failed("导入数据为空");
}
String month = StrUtil.trim(bizMonth);
if (StrUtil.isBlank(month)) {
return R.failed("请选择适用月份");
}
Map<String, CanteenReportPersonExcel> byEmp = new LinkedHashMap<>();
int skipBlank = 0;
for (CanteenReportPersonExcel row : rows) {
@@ -91,7 +98,6 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
byEmp.put(empNo, row);
continue;
}
// 同工号多行:优先保留有核对成本的
if (StrUtil.isBlank(old.getCostCenter()) && StrUtil.isNotBlank(row.getCostCenter())) {
byEmp.put(empNo, row);
}
@@ -107,19 +113,20 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
Long tenantId = TenantContextHolder.getTenantId();
LocalDateTime now = LocalDateTime.now();
if (replaceAll) {
baseMapper.deletePhysicallyAll();
if (replaceMonth) {
baseMapper.deletePhysicallyByBizMonth(month);
List<CanteenReportPersonEntity> saveList = new ArrayList<>(byEmp.size());
for (Map.Entry<String, CanteenReportPersonExcel> en : byEmp.entrySet()) {
saveList.add(toEntity(en.getValue(), en.getKey(), null, operator, tenantId, now, true));
saveList.add(toEntity(en.getValue(), month, en.getKey(), null, operator, tenantId, now, true));
}
for (List<CanteenReportPersonEntity> chunk : CollUtil.split(saveList, BATCH)) {
baseMapper.insertBatch(chunk);
}
return R.ok(null, String.format("全量覆盖导入成功:入库 %d 人,跳过无工号 %d 行", saveList.size(), skipBlank));
return R.ok(null, String.format("覆盖 %s 导入成功:入库 %d 人,跳过无工号 %d 行", month, saveList.size(), skipBlank));
}
Map<String, CanteenReportPersonEntity> existMap = list(Wrappers.<CanteenReportPersonEntity>lambdaQuery()
.eq(CanteenReportPersonEntity::getBizMonth, month)
.in(CanteenReportPersonEntity::getEmpNo, byEmp.keySet())).stream()
.collect(Collectors.toMap(CanteenReportPersonEntity::getEmpNo, Function.identity(), (a, b) -> a));
@@ -128,11 +135,11 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
for (Map.Entry<String, CanteenReportPersonExcel> en : byEmp.entrySet()) {
CanteenReportPersonEntity exist = existMap.get(en.getKey());
if (exist == null) {
insertList.add(toEntity(en.getValue(), en.getKey(), null, operator, tenantId, now, true));
insertList.add(toEntity(en.getValue(), month, en.getKey(), null, operator, tenantId, now, true));
}
else {
CanteenReportPersonEntity upd = toEntity(en.getValue(), en.getKey(), exist.getId(), operator, tenantId,
now, false);
CanteenReportPersonEntity upd = toEntity(en.getValue(), month, en.getKey(), exist.getId(), operator,
tenantId, now, false);
upd.setCreateBy(exist.getCreateBy());
upd.setCreateTime(exist.getCreateTime());
upd.setDelFlag(exist.getDelFlag());
@@ -146,13 +153,15 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
for (CanteenReportPersonEntity e : updateList) {
updateById(e);
}
return R.ok(null, String.format("新增导入成功:新增 %d 人,更新 %d 人,跳过无工号 %d 行",
insertList.size(), updateList.size(), skipBlank));
return R.ok(null, String.format("%s 导入成功:新增 %d 人,更新 %d 人,跳过无工号 %d 行",
month, insertList.size(), updateList.size(), skipBlank));
}
@Override
public List<CanteenReportPersonExcel> exportExcel() {
public List<CanteenReportPersonExcel> exportExcel(String bizMonth) {
List<CanteenReportPersonEntity> list = list(Wrappers.<CanteenReportPersonEntity>lambdaQuery()
.eq(StrUtil.isNotBlank(bizMonth), CanteenReportPersonEntity::getBizMonth, bizMonth)
.orderByDesc(CanteenReportPersonEntity::getBizMonth)
.orderByAsc(CanteenReportPersonEntity::getEmpNo));
List<CanteenReportPersonExcel> result = new ArrayList<>(list.size());
for (CanteenReportPersonEntity e : list) {
@@ -171,9 +180,24 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
}
@Override
public Map<String, String> loadCostCenterMap() {
@Transactional(rollbackFor = Exception.class)
public R clearByMonth(String bizMonth) {
String month = StrUtil.trim(bizMonth);
if (StrUtil.isBlank(month)) {
return R.failed("请选择适用月份");
}
baseMapper.deletePhysicallyByBizMonth(month);
return R.ok(null, "已清空 " + month + " 人员信息");
}
@Override
public Map<String, String> loadCostCenterMap(String bizMonth) {
Map<String, String> map = new HashMap<>();
if (StrUtil.isBlank(bizMonth)) {
return map;
}
List<CanteenReportPersonEntity> list = list(Wrappers.<CanteenReportPersonEntity>lambdaQuery()
.eq(CanteenReportPersonEntity::getBizMonth, bizMonth.trim())
.select(CanteenReportPersonEntity::getEmpNo, CanteenReportPersonEntity::getCostCenter));
for (CanteenReportPersonEntity e : list) {
if (StrUtil.isBlank(e.getEmpNo())) {
@@ -186,10 +210,11 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
return map;
}
private CanteenReportPersonEntity toEntity(CanteenReportPersonExcel row, String empNo, Long id, String operator,
Long tenantId, LocalDateTime now, boolean isNew) {
private CanteenReportPersonEntity toEntity(CanteenReportPersonExcel row, String bizMonth, String empNo, Long id,
String operator, Long tenantId, LocalDateTime now, boolean isNew) {
CanteenReportPersonEntity e = new CanteenReportPersonEntity();
e.setId(id != null ? id : IdWorker.getId());
e.setBizMonth(bizMonth);
e.setEmpNo(empNo);
e.setEmpName(StrUtil.trim(row.getEmpName()));
e.setLegalEntity(StrUtil.trim(row.getLegalEntity()));
@@ -208,11 +233,16 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
return e;
}
/** 新增/编辑:工号、姓名、成本分类、厂区必填 */
/** 新增/编辑:适用月、工号、姓名、成本分类、厂区必填;同月同工号唯一 */
private R validatePerson(CanteenReportPersonEntity entity, Long excludeId) {
if (entity == null) {
return R.failed("参数为空");
}
String month = StrUtil.trim(entity.getBizMonth());
if (StrUtil.isBlank(month)) {
return R.failed("请选择适用月份");
}
entity.setBizMonth(month);
String empNo = normalizeEmpNo(entity.getEmpNo());
if (empNo == null) {
return R.failed("请填写有效工号");
@@ -231,10 +261,11 @@ public class CanteenReportPersonServiceImpl extends ServiceImpl<CanteenReportPer
entity.setCostCategory(entity.getCostCategory().trim());
entity.setPark(entity.getPark().trim());
long cnt = count(Wrappers.<CanteenReportPersonEntity>lambdaQuery()
.eq(CanteenReportPersonEntity::getBizMonth, entity.getBizMonth())
.eq(CanteenReportPersonEntity::getEmpNo, entity.getEmpNo())
.ne(excludeId != null, CanteenReportPersonEntity::getId, excludeId));
if (cnt > 0) {
return R.failed("工号已存在:" + entity.getEmpNo());
return R.failed(String.format("该月工号已存在:%s / %s", entity.getBizMonth(), entity.getEmpNo()));
}
return null;
}
@@ -41,6 +41,7 @@ public class CanteenReportRosterServiceImpl extends ServiceImpl<CanteenReportRos
.like(StrUtil.isNotBlank(empNo), CanteenReportRosterEntity::getEmpNo, empNo)
.like(StrUtil.isNotBlank(empName), CanteenReportRosterEntity::getEmpName, empName)
.like(StrUtil.isNotBlank(hrScope), CanteenReportRosterEntity::getHrScope, hrScope)
.orderByDesc(CanteenReportRosterEntity::getBizMonth)
.orderByAsc(CanteenReportRosterEntity::getEmpNo));
}
@@ -11,6 +11,8 @@ import com.bsd.pjcloud.canteen.service.CanteenReportSettleParkService;
import com.bsd.pjcloud.common.core.util.R;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CanteenReportSettleParkServiceImpl extends ServiceImpl<CanteenReportSettleParkMapper, CanteenReportSettleParkEntity>
implements CanteenReportSettleParkService {
@@ -31,10 +33,6 @@ public class CanteenReportSettleParkServiceImpl extends ServiceImpl<CanteenRepor
}
entity.setSettleCounter(entity.getSettleCounter().trim());
entity.setPark(entity.getPark().trim());
if (!CanteenReportConstants.PARKS.contains(entity.getPark())
&& !CanteenReportConstants.SHARE_PARKS.contains(entity.getPark())) {
return R.failed("厂区不在枚举内: " + entity.getPark());
}
long cnt = count(Wrappers.<CanteenReportSettleParkEntity>lambdaQuery()
.eq(CanteenReportSettleParkEntity::getSettleCounter, entity.getSettleCounter())
.ne(entity.getId() != null, CanteenReportSettleParkEntity::getId, entity.getId()));
@@ -44,4 +42,23 @@ public class CanteenReportSettleParkServiceImpl extends ServiceImpl<CanteenRepor
saveOrUpdate(entity);
return R.ok(entity);
}
@Override
public List<String> listConfiguredParks() {
List<CanteenReportSettleParkEntity> list = list(Wrappers.<CanteenReportSettleParkEntity>lambdaQuery()
.select(CanteenReportSettleParkEntity::getPark)
.isNotNull(CanteenReportSettleParkEntity::getPark)
.ne(CanteenReportSettleParkEntity::getPark, "")
.orderByAsc(CanteenReportSettleParkEntity::getPark));
java.util.LinkedHashSet<String> set = new java.util.LinkedHashSet<>();
for (String p : CanteenReportConstants.PARKS) {
set.add(p);
}
for (CanteenReportSettleParkEntity e : list) {
if (StrUtil.isNotBlank(e.getPark())) {
set.add(e.getPark().trim());
}
}
return new java.util.ArrayList<>(set);
}
}
@@ -58,6 +58,7 @@ public class CanteenReportSwipeServiceImpl extends ServiceImpl<CanteenReportSwip
.eq(importLogId != null, CanteenReportSwipeEntity::getImportLogId, importLogId)
.eq(StrUtil.isNotBlank(remarkTag), CanteenReportSwipeEntity::getRemarkTag, remarkTag)
.eq(StrUtil.isNotBlank(source), CanteenReportSwipeEntity::getSource, source)
.orderByDesc(CanteenReportSwipeEntity::getBizMonth)
.orderByDesc(CanteenReportSwipeEntity::getId));
}
@@ -33,23 +33,28 @@
</foreach>
</insert>
<!-- OceanBase 默认 query_timeout≈10s,大批量一次性 DELETE 会超时;LIMIT 分批 + 延长超时 -->
<delete id="deletePhysicallyByImportLogId">
DELETE FROM canteen_report_face_detail WHERE import_log_id = #{importLogId}
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_face_detail
WHERE import_log_id = #{importLogId}
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyByDateRange">
DELETE FROM canteen_report_face_detail
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_face_detail
WHERE biz_month = #{bizMonth}
AND biz_date &gt;= #{dateStart}
AND biz_date &lt;= #{dateEnd}
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyDateRangeExceptLog">
DELETE FROM canteen_report_face_detail
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_face_detail
WHERE biz_month = #{bizMonth}
AND biz_date &gt;= #{dateStart}
AND biz_date &lt;= #{dateEnd}
AND (import_log_id IS NULL OR import_log_id &lt;&gt; #{importLogId})
LIMIT #{limit}
</delete>
<select id="selectAggByMonth" resultType="com.bsd.pjcloud.canteen.dto.report.CanteenReportFaceAggRow">
@@ -6,15 +6,19 @@
DELETE FROM canteen_report_person
</delete>
<delete id="deletePhysicallyByBizMonth">
DELETE FROM canteen_report_person WHERE biz_month = #{bizMonth}
</delete>
<insert id="insertBatch">
INSERT INTO canteen_report_person (
id, emp_no, emp_name, legal_entity, dept_name,
id, biz_month, emp_no, emp_name, legal_entity, dept_name,
cost_center, cost_category, park, remark,
create_by, create_time, update_by, update_time, del_flag, tenant_id
) VALUES
<foreach collection="list" item="e" separator=",">
(
#{e.id}, #{e.empNo}, #{e.empName}, #{e.legalEntity}, #{e.deptName},
#{e.id}, #{e.bizMonth}, #{e.empNo}, #{e.empName}, #{e.legalEntity}, #{e.deptName},
#{e.costCenter}, #{e.costCategory}, #{e.park}, #{e.remark},
#{e.createBy}, #{e.createTime}, #{e.updateBy}, #{e.updateTime}, #{e.delFlag}, #{e.tenantId}
)
@@ -7,32 +7,67 @@
</delete>
<delete id="deletePhysicallyFaceByBizMonth">
DELETE FROM canteen_report_swipe WHERE source = 'FACE' AND biz_month = #{bizMonth}
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE source = 'FACE' AND biz_month = #{bizMonth}
LIMIT #{limit}
</delete>
<!--
物理删除人脸来源在指定日期段的数据(用于半月分段覆盖导入)
-->
<delete id="deletePhysicallyFaceByDateRange">
DELETE FROM canteen_report_swipe
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE source = 'FACE'
AND biz_month = #{bizMonth}
AND biz_date &gt;= #{dateStart}
AND biz_date &lt;= #{dateEnd}
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyByImportLogId">
DELETE FROM canteen_report_swipe WHERE import_log_id = #{importLogId}
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE import_log_id = #{importLogId}
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyFaceRangeExceptLog">
DELETE FROM canteen_report_swipe
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE source = 'FACE'
AND biz_month = #{bizMonth}
AND biz_date &gt;= #{dateStart}
AND biz_date &lt;= #{dateEnd}
AND (import_log_id IS NULL OR import_log_id &lt;&gt; #{importLogId})
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyBySourceAndBizMonth">
DELETE FROM canteen_report_swipe WHERE source = #{source} AND biz_month = #{bizMonth}
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE source = #{source} AND biz_month = #{bizMonth}
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyBySourceBizMonthPark">
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE source = #{source}
AND biz_month = #{bizMonth}
<if test="park != null and park != ''">
AND park = #{park}
</if>
LIMIT #{limit}
</delete>
<delete id="deletePhysicallyFaceByBizMonthExceptParks">
DELETE /*+ QUERY_TIMEOUT(120000000) */ FROM canteen_report_swipe
WHERE source = 'FACE'
AND biz_month = #{bizMonth}
<if test="excludeParks != null and excludeParks.size() &gt; 0">
AND (park IS NULL OR park = '' OR park NOT IN
<foreach collection="excludeParks" item="p" open="(" separator="," close=")">
#{p}
</foreach>
)
</if>
LIMIT #{limit}
</delete>
<insert id="insertBatch">