diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportAggResultVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportAggResultVO.java new file mode 100644 index 000000000..2f034fa12 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportAggResultVO.java @@ -0,0 +1,18 @@ +package com.bsd.pjcloud.canteen.dto.report; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class CanteenReportAggResultVO { + + private Integer swipeRows; + + private Integer unmatchedRows; + + private Integer unmatchedPeople; + + private List unmatched = new ArrayList<>(); +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportBoxExcel.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportBoxExcel.java new file mode 100644 index 000000000..a14a58914 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportBoxExcel.java @@ -0,0 +1,53 @@ +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; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportFoodEnergyExcel.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportFoodEnergyExcel.java index 66ca7c05e..b6ef7958e 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportFoodEnergyExcel.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportFoodEnergyExcel.java @@ -1,6 +1,7 @@ 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; @@ -35,28 +36,28 @@ public class CanteenReportFoodEnergyExcel { @ExcelProperty(index = 7, value = "单位") private String unit; - @ExcelProperty(index = 8, value = "数量") + @ExcelProperty(index = 8, value = "数量", converter = ExcelYuanConverter.class) private BigDecimal qty; - @ExcelProperty(index = 9, value = "单价") + @ExcelProperty(index = 9, value = "单价", converter = ExcelYuanConverter.class) private BigDecimal unitPrice; - @ExcelProperty(index = 10, value = "定价") + @ExcelProperty(index = 10, value = "定价", converter = ExcelYuanConverter.class) private BigDecimal listPrice; - @ExcelProperty(index = 11, value = "差异") + @ExcelProperty(index = 11, value = "差异", converter = ExcelYuanConverter.class) private BigDecimal priceDiff; - @ExcelProperty(index = 12, value = "结算单价") + @ExcelProperty(index = 12, value = "结算单价", converter = ExcelYuanConverter.class) private BigDecimal settleUnitPrice; - @ExcelProperty(index = 13, value = "结算总金额") + @ExcelProperty(index = 13, value = "结算总金额", converter = ExcelYuanConverter.class) private BigDecimal settleAmount; - @ExcelProperty(index = 14, value = "金额") + @ExcelProperty(index = 14, value = "金额", converter = ExcelYuanConverter.class) private BigDecimal amount; - @ExcelProperty(index = 15, value = "金额差异") + @ExcelProperty(index = 15, value = "金额差异", converter = ExcelYuanConverter.class) private BigDecimal amountDiff; @ExcelProperty(index = 16, value = "食堂仓库") diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportOrgLevel2VO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportOrgLevel2VO.java new file mode 100644 index 000000000..d3dc2bc2d --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportOrgLevel2VO.java @@ -0,0 +1,11 @@ +package com.bsd.pjcloud.canteen.dto.report; + +import lombok.Data; + +@Data +public class CanteenReportOrgLevel2VO { + + private String name; + + private Long cnt; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportReceiptStockExcel.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportReceiptStockExcel.java new file mode 100644 index 000000000..45d7749c3 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportReceiptStockExcel.java @@ -0,0 +1,72 @@ +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; + +/** + * 食堂收发存报表(汇总)→ 映射为食材能耗。 + * 列序对齐:2026年7月份食堂收发存报表(汇总).xlsx Sheet1 + */ +@Data +public class CanteenReportReceiptStockExcel { + + @ExcelProperty(index = 0) + private String bizDate; + + @ExcelProperty(index = 1) + private String receiptNo; + + @ExcelProperty(index = 2) + private String supplier; + + @ExcelProperty(index = 3) + private String productCode; + + @ExcelProperty(index = 4) + private String category; + + @ExcelProperty(index = 5) + private String productName; + + @ExcelProperty(index = 6) + private String brand; + + @ExcelProperty(index = 7) + private String spec; + + @ExcelProperty(index = 8) + private String unit; + + @ExcelProperty(index = 9, converter = ExcelYuanConverter.class) + private BigDecimal qty; + + @ExcelProperty(index = 10, converter = ExcelYuanConverter.class) + private BigDecimal unitPrice; + + @ExcelProperty(index = 11, converter = ExcelYuanConverter.class) + private BigDecimal listPrice; + + @ExcelProperty(index = 12, converter = ExcelYuanConverter.class) + private BigDecimal priceDiff; + + @ExcelProperty(index = 13, converter = ExcelYuanConverter.class) + private BigDecimal settleUnitPrice; + + @ExcelProperty(index = 14, converter = ExcelYuanConverter.class) + private BigDecimal settleAmount; + + @ExcelProperty(index = 15, converter = ExcelYuanConverter.class) + private BigDecimal amount; + + @ExcelProperty(index = 16, converter = ExcelYuanConverter.class) + private BigDecimal amountDiff; + + @ExcelProperty(index = 17) + private String warehouse; + + @ExcelProperty(index = 21) + private String park; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportRosterExcel.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportRosterExcel.java new file mode 100644 index 000000000..97f98b93e --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportRosterExcel.java @@ -0,0 +1,59 @@ +package com.bsd.pjcloud.canteen.dto.report; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +/** + * 花名册 Excel(列序对齐 202607花名册.xlsx) + */ +@Data +public class CanteenReportRosterExcel { + + @ExcelProperty(index = 0) + private String empNo; + + @ExcelProperty(index = 1) + private String empName; + + @ExcelProperty(index = 2) + private String empGroup; + + @ExcelProperty(index = 3) + private String deptCxo1; + + @ExcelProperty(index = 4) + private String deptCxo2; + + @ExcelProperty(index = 5) + private String deptCxo3; + + @ExcelProperty(index = 6) + private String jobTitle; + + @ExcelProperty(index = 7) + private String jobNature; + + @ExcelProperty(index = 8) + private String hrScope; + + @ExcelProperty(index = 9) + private String legalCompany; + + @ExcelProperty(index = 10) + private String rdFlag; + + @ExcelProperty(index = 11) + private String financeCode; + + @ExcelProperty(index = 12) + private String financeName; + + @ExcelProperty(index = 13) + private String erpCostCode; + + @ExcelProperty(index = 14) + private String erpCostName; + + @ExcelProperty(index = 15) + private String orgTag; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportRosterMissVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportRosterMissVO.java new file mode 100644 index 000000000..1444a4a60 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportRosterMissVO.java @@ -0,0 +1,25 @@ +package com.bsd.pjcloud.canteen.dto.report; + +import lombok.Data; + +/** + * 汇总时未命中花名册人事范围的人员(已回退法律归属) + */ +@Data +public class CanteenReportRosterMissVO { + + private String empNo; + + private String empName; + + private String legalEntity; + + /** 回退后的核对成本 */ + private String costCenter; + + /** 无工号 / 花名册无此工号 / 花名册无人事范围 */ + private String reason; + + /** 汇总刷卡行数 */ + private Integer rowCount; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportSwipeExcel.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportSwipeExcel.java index 4a50d1a60..ce14ea4ff 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportSwipeExcel.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/dto/report/CanteenReportSwipeExcel.java @@ -29,7 +29,7 @@ public class CanteenReportSwipeExcel { @ExcelProperty(index = 5, value = "结算台") private String settleCounter; - @ExcelProperty(index = 6, value = "消费金额") + @ExcelProperty(index = 6, value = "消费金额", converter = com.bsd.pjcloud.canteen.excel.ExcelYuanConverter.class) private BigDecimal consumeAmount; @ExcelProperty(index = 7, value = "收入类别") diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenReportImportLogEntity.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenReportImportLogEntity.java index ea63a9ac8..9a2e6f5d0 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenReportImportLogEntity.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/entity/CanteenReportImportLogEntity.java @@ -48,6 +48,9 @@ public class CanteenReportImportLogEntity extends Model { + + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + @Schema(description = "适用月份 yyyy-MM") + private String bizMonth; + + @Schema(description = "全球工号") + private String empNo; + + @Schema(description = "员工姓名") + private String empName; + + private String empGroup; + + private String deptCxo1; + + private String deptCxo2; + + private String deptCxo3; + + private String jobTitle; + + private String jobNature; + + @Schema(description = "人事范围→核对成本") + private String hrScope; + + private String legalCompany; + + private String rdFlag; + + private String financeCode; + + private String financeName; + + private String erpCostCode; + + private String erpCostName; + + private String orgTag; + + @TableField(fill = FieldFill.INSERT) + private String createBy; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateBy; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + @TableLogic + @TableField(fill = FieldFill.INSERT) + private String delFlag; + + private Long tenantId; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/excel/ExcelYuanStringConverter.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/excel/ExcelYuanStringConverter.java new file mode 100644 index 000000000..7b1755e99 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/excel/ExcelYuanStringConverter.java @@ -0,0 +1,40 @@ +package com.bsd.pjcloud.canteen.excel; + +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; + +import java.math.BigDecimal; + +/** + * 覆盖 EasyExcel 默认 {@code BigDecimalStringConverter}:文本「杯」等非数字返回 null,不抛异常。 + */ +public class ExcelYuanStringConverter implements Converter { + + private final ExcelYuanConverter delegate = new ExcelYuanConverter(); + + @Override + public Class supportJavaTypeKey() { + return BigDecimal.class; + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + return CellDataTypeEnum.STRING; + } + + @Override + public BigDecimal convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return delegate.convertToJavaData(cellData, contentProperty, globalConfiguration); + } + + @Override + public WriteCellData convertToExcelData(BigDecimal value, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + return delegate.convertToExcelData(value, contentProperty, globalConfiguration); + } +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportConstants.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportConstants.java index 28fe43429..7a8b5c6a4 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportConstants.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportConstants.java @@ -30,6 +30,9 @@ public final class CanteenReportConstants { public static final String INCOME_OTHER = "其他收入"; public static final String INCOME_BOX = "包厢收入"; + /** 刷卡备注:花名册未命中,核对成本已回退法律归属 */ + public static final String REMARK_ROSTER_MISS = "未命中花名册"; + public static final List INCOME_TYPES = Collections.unmodifiableList(Arrays.asList( INCOME_SWIPE, INCOME_ALIPAY, INCOME_BOX, INCOME_OTHER, INCOME_PROFIT_ADJ, INCOME_INVOICE, INCOME_BAD_DEBT )); diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportFaceMapper.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportFaceMapper.java index 5ef4cddde..788aad421 100644 --- a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportFaceMapper.java +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/report/CanteenReportFaceMapper.java @@ -171,6 +171,19 @@ public final class CanteenReportFaceMapper { // continue } } + // Excel 序列日期(如 45854 或 45854.375) + if (s.matches("\\d{4,5}(\\.\\d+)?")) { + try { + double serial = Double.parseDouble(s); + if (serial > 20000 && serial < 80000) { + LocalDate base = LocalDate.of(1899, 12, 30); + return base.plusDays((long) serial).toString(); + } + } + catch (Exception ignored) { + // continue + } + } try { String head = s.length() >= 10 ? s.substring(0, 10).replace('/', '-') : s.replace('/', '-'); return LocalDate.parse(head, DateTimeFormatter.ISO_LOCAL_DATE).toString(); diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareAuditVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareAuditVO.java new file mode 100644 index 000000000..55949e4d6 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareAuditVO.java @@ -0,0 +1,18 @@ +package com.bsd.pjcloud.canteen.vo.report; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +@Schema(description = "供应商分摊核对:厂区食材池 + 权重明细") +public class CanteenReportShareAuditVO { + + @Schema(description = "各厂区食材池 / 可分摊 / 分摊合计") + private List parkChecks = new ArrayList<>(); + + @Schema(description = "各成本中心在各厂区的权重与员工刷卡") + private List weightRows = new ArrayList<>(); +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareParkCheckVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareParkCheckVO.java new file mode 100644 index 000000000..e94a8d117 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareParkCheckVO.java @@ -0,0 +1,32 @@ +package com.bsd.pjcloud.canteen.vo.report; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Schema(description = "供应商分摊-厂区核对(元)") +public class CanteenReportShareParkCheckVO { + + @Schema(description = "厂区") + private String park; + + @Schema(description = "食材池:食材能耗结算总金额,分类∈食材/能耗/外协") + private BigDecimal foodPool = BigDecimal.ZERO; + + @Schema(description = "厂区包厢+其他收入(刷卡)") + private BigDecimal boxOther = BigDecimal.ZERO; + + @Schema(description = "该厂区员工刷卡合计(权重分母)") + private BigDecimal swipeTotal = BigDecimal.ZERO; + + @Schema(description = "可分摊 = 食材池 − 包厢其他") + private BigDecimal allocable = BigDecimal.ZERO; + + @Schema(description = "各成本中心分摊合计") + private BigDecimal shareTotal = BigDecimal.ZERO; + + @Schema(description = "差额 = 分摊合计 − 可分摊,接近 0 即核对通过") + private BigDecimal diff = BigDecimal.ZERO; +} diff --git a/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareWeightRowVO.java b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareWeightRowVO.java new file mode 100644 index 000000000..9fea481ab --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-api/src/main/java/com/bsd/pjcloud/canteen/vo/report/CanteenReportShareWeightRowVO.java @@ -0,0 +1,25 @@ +package com.bsd.pjcloud.canteen.vo.report; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.LinkedHashMap; +import java.util.Map; + +@Data +@Schema(description = "供应商分摊-权重行:该公司该厂区员工刷卡 / 厂区员工刷卡合计") +public class CanteenReportShareWeightRowVO { + + @Schema(description = "成本中心") + private String costCenter; + + @Schema(description = "该公司各厂区员工刷卡(元)") + private Map swipeAmounts = new LinkedHashMap<>(); + + @Schema(description = "权重 0~1,分摊金额 = 权重 × 该厂区可分摊") + private Map weights = new LinkedHashMap<>(); + + @Schema(description = "分摊金额(元)= 权重 × 可分摊") + private Map shareAmounts = new LinkedHashMap<>(); +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportBoxImportController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportBoxImportController.java new file mode 100644 index 000000000..730aa2122 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportBoxImportController.java @@ -0,0 +1,31 @@ +package com.bsd.pjcloud.canteen.controller; + +import com.bsd.pjcloud.canteen.service.CanteenReportBoxImportService; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.log.annotation.SysLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/canteenReport/boxImport") +@Tag(description = "canteenReportBoxImport", name = "食堂报表-包厢台账导入(演示)") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class CanteenReportBoxImportController { + + private final CanteenReportBoxImportService boxImportService; + + @SysLog("导入包厢台账") + @Operation(summary = "导入包厢台账Excel → 刷卡明细(包厢收入)") + @PostMapping("/import") + public R importExcel(@RequestParam("file") MultipartFile file, + @RequestParam String bizMonth, + @RequestParam(defaultValue = "true") boolean replaceMonth) { + return boxImportService.importExcel(file, bizMonth, replaceMonth); + } +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportController.java index 53342034c..26b0f5ad9 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportController.java @@ -51,6 +51,12 @@ public class CanteenReportController { return R.ok(calcService.supplierShareReport(bizMonth)); } + @Operation(summary = "供应商分摊-厂区核对与权重明细") + @GetMapping("/supplierShare/parkCheck") + public R supplierShareParkCheck(@RequestParam String bizMonth) { + return R.ok(calcService.supplierShareAudit(bizMonth)); + } + @Operation(summary = "经营报表") @GetMapping("/business") public R business(@RequestParam String bizMonth) { diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFaceImportController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFaceImportController.java index 828b9eeff..7d788ccbd 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFaceImportController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFaceImportController.java @@ -45,7 +45,11 @@ public class CanteenReportFaceImportController { @RequestParam(required = false) String bizMonth, @RequestParam(required = false) String importLogId, @RequestParam(required = false) String empNo, - @RequestParam(required = false) String payMethod) { + @RequestParam(required = false) String payMethod, + @RequestParam(required = false) String deviceName, + @RequestParam(required = false) String empName, + @RequestParam(required = false) String phone, + @RequestParam(required = false) String orgLevel2s) { Long logId = null; if (cn.hutool.core.util.StrUtil.isNotBlank(importLogId)) { try { @@ -55,7 +59,35 @@ public class CanteenReportFaceImportController { return R.failed("importLogId 非法"); } } - return R.ok(faceImportService.pageDetails(page, bizMonth, logId, empNo, payMethod)); + java.util.List orgs = cn.hutool.core.util.StrUtil.isBlank(orgLevel2s) + ? java.util.List.of() + : cn.hutool.core.util.StrUtil.splitTrim(orgLevel2s, ','); + return R.ok(faceImportService.pageDetails(page, bizMonth, logId, empNo, payMethod, deviceName, empName, phone, orgs)); + } + + @Operation(summary = "人脸明细按筛选条件汇总订单金额(元)") + @GetMapping("/detail/sum") + public R detailSum(@RequestParam(required = false) String bizMonth, + @RequestParam(required = false) String importLogId, + @RequestParam(required = false) String empNo, + @RequestParam(required = false) String payMethod, + @RequestParam(required = false) String deviceName, + @RequestParam(required = false) String empName, + @RequestParam(required = false) String phone, + @RequestParam(required = false) String orgLevel2s) { + Long logId = null; + if (cn.hutool.core.util.StrUtil.isNotBlank(importLogId)) { + try { + logId = Long.parseLong(importLogId.trim()); + } + catch (NumberFormatException e) { + return R.failed("importLogId 非法"); + } + } + java.util.List orgs = cn.hutool.core.util.StrUtil.isBlank(orgLevel2s) + ? java.util.List.of() + : cn.hutool.core.util.StrUtil.splitTrim(orgLevel2s, ','); + return R.ok(faceImportService.sumDetails(bizMonth, logId, empNo, payMethod, deviceName, empName, phone, orgs)); } @SysLog("人脸消费Excel导入明细") @@ -76,7 +108,8 @@ public class CanteenReportFaceImportController { @RequestParam(required = false) String dateStart, @RequestParam(required = false) String dateEnd, @RequestParam(required = false) String importLogId, - @RequestParam(defaultValue = "true") boolean replaceFace) { + @RequestParam(defaultValue = "true") boolean replaceFace, + @RequestParam(required = false) String orgLevel2s) { Long logId = null; if (cn.hutool.core.util.StrUtil.isNotBlank(importLogId)) { try { @@ -86,6 +119,25 @@ public class CanteenReportFaceImportController { return R.failed("importLogId 非法: " + importLogId); } } - return faceImportService.aggregateToSwipe(bizMonth, dateStart, dateEnd, logId, replaceFace); + java.util.List 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); + } + + @Operation(summary = "明细中的二级组织列表(汇总筛选用)") + @GetMapping("/orgLevel2") + public R listOrgLevel2(@RequestParam(required = false) String bizMonth, + @RequestParam(required = false) String importLogId) { + Long logId = null; + if (cn.hutool.core.util.StrUtil.isNotBlank(importLogId)) { + try { + logId = Long.parseLong(importLogId.trim()); + } + catch (NumberFormatException e) { + return R.failed("importLogId 非法"); + } + } + return R.ok(faceImportService.listOrgLevel2(bizMonth, logId)); } } \ No newline at end of file diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFoodEnergyController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFoodEnergyController.java index 204260c18..4132604c9 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFoodEnergyController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportFoodEnergyController.java @@ -43,6 +43,18 @@ public class CanteenReportFoodEnergyController { return R.ok(foodEnergyService.getById(id)); } + @SysLog("新增食材能耗") + @PostMapping + public R save(@RequestBody CanteenReportFoodEnergyEntity entity) { + return foodEnergyService.saveOrUpdateRow(entity); + } + + @SysLog("修改食材能耗") + @PutMapping + public R update(@RequestBody CanteenReportFoodEnergyEntity entity) { + return foodEnergyService.saveOrUpdateRow(entity); + } + @SysLog("删除食材能耗") @DeleteMapping public R remove(@RequestBody Long[] ids) { @@ -56,14 +68,25 @@ public class CanteenReportFoodEnergyController { .eq(CanteenReportFoodEnergyEntity::getBizMonth, bizMonth))); } - @Operation(summary = "导入Excel(自动识别「食材能耗」sheet,可传整本报表)") + @Operation(summary = "导入Excel(自动识别收发存汇总表 / 食堂报表「食材能耗」sheet)") @PostMapping("/import") public R importExcel(@RequestParam("file") org.springframework.web.multipart.MultipartFile file, @RequestParam String bizMonth, - @RequestParam(defaultValue = "true") boolean replaceMonth) throws Exception { - List rows = com.bsd.pjcloud.canteen.util.CanteenReportExcelUtils - .readSheet(file, CanteenReportFoodEnergyExcel.class, "食材能耗"); - return foodEnergyService.importExcel(rows, bizMonth, replaceMonth, null); + @RequestParam(defaultValue = "true") boolean replaceMonth) { + try { + if (com.bsd.pjcloud.canteen.util.CanteenReportExcelUtils.isReceiptStockLayout(file)) { + return foodEnergyService.importReceiptStock(file, bizMonth, replaceMonth); + } + List rows = com.bsd.pjcloud.canteen.util.CanteenReportExcelUtils + .readSheet(file, CanteenReportFoodEnergyExcel.class, "食材能耗"); + return foodEnergyService.importExcel(rows, bizMonth, replaceMonth, null); + } + catch (com.alibaba.excel.exception.ExcelDataConvertException e) { + return R.failed("Excel 列对不上。请上传收发存汇总表,或整本食堂报表(含「食材能耗」sheet)"); + } + catch (Exception e) { + return R.failed("导入失败: " + e.getMessage()); + } } @ResponseExcel diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportRosterController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportRosterController.java new file mode 100644 index 000000000..232d05b98 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportRosterController.java @@ -0,0 +1,43 @@ +package com.bsd.pjcloud.canteen.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.canteen.service.CanteenReportRosterService; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.log.annotation.SysLog; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/canteenReport/roster") +@Tag(description = "canteenReportRoster", name = "食堂报表-花名册(演示)") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class CanteenReportRosterController { + + private final CanteenReportRosterService rosterService; + + @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 hrScope) { + return R.ok(rosterService.pageList(page, bizMonth, empNo, empName, hrScope)); + } + + @SysLog("导入花名册") + @Operation(summary = "导入花名册Excel") + @PostMapping("/import") + public R importExcel(@RequestParam("file") MultipartFile file, + @RequestParam String bizMonth, + @RequestParam(defaultValue = "true") boolean replaceMonth) { + return rosterService.importExcel(file, bizMonth, replaceMonth); + } +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportSwipeController.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportSwipeController.java index c6eab4466..32b6c25ee 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportSwipeController.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/controller/CanteenReportSwipeController.java @@ -38,8 +38,25 @@ public class CanteenReportSwipeController { @RequestParam(required = false) String incomeType, @RequestParam(required = false) String costCenter, @RequestParam(required = false) String empNo, - @RequestParam(required = false) Long importLogId) { - return R.ok(swipeService.pageList(page, bizMonth, park, incomeType, costCenter, empNo, importLogId)); + @RequestParam(required = false) Long importLogId, + @RequestParam(required = false) String remarkTag, + @RequestParam(required = false) String source) { + return R.ok(swipeService.pageList(page, bizMonth, park, incomeType, costCenter, empNo, importLogId, remarkTag, + source)); + } + + @Operation(summary = "按当前筛选条件汇总消费金额(元)") + @GetMapping("/sum") + public R sum(@RequestParam(required = false) String bizMonth, + @RequestParam(required = false) String park, + @RequestParam(required = false) String incomeType, + @RequestParam(required = false) String costCenter, + @RequestParam(required = false) String empNo, + @RequestParam(required = false) Long importLogId, + @RequestParam(required = false) String remarkTag, + @RequestParam(required = false) String source) { + return R.ok(swipeService.sumAmount(bizMonth, park, incomeType, costCenter, empNo, importLogId, remarkTag, + source)); } @Operation(summary = "详情") diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportFaceDetailMapper.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportFaceDetailMapper.java index 203dc4020..c457998a5 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportFaceDetailMapper.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportFaceDetailMapper.java @@ -2,6 +2,7 @@ package com.bsd.pjcloud.canteen.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.bsd.pjcloud.canteen.dto.report.CanteenReportFaceAggRow; +import com.bsd.pjcloud.canteen.dto.report.CanteenReportOrgLevel2VO; import com.bsd.pjcloud.canteen.entity.CanteenReportFaceDetailEntity; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -25,10 +26,16 @@ public interface CanteenReportFaceDetailMapper extends BaseMapper selectAggByMonth(@Param("bizMonth") String bizMonth, @Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd, - @Param("importLogId") Long importLogId); + @Param("importLogId") Long importLogId, + @Param("orgLevel2s") List orgLevel2s); - long countDetail(@Param("bizMonth") String bizMonth, @Param("importLogId") Long importLogId); + long countDetail(@Param("bizMonth") String bizMonth, @Param("importLogId") Long importLogId, + @Param("orgLevel2s") List orgLevel2s); + + List listOrgLevel2(@Param("bizMonth") String bizMonth, + @Param("importLogId") Long importLogId); } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportRosterMapper.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportRosterMapper.java new file mode 100644 index 000000000..af6f49b86 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportRosterMapper.java @@ -0,0 +1,16 @@ +package com.bsd.pjcloud.canteen.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.bsd.pjcloud.canteen.entity.CanteenReportRosterEntity; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface CanteenReportRosterMapper extends BaseMapper { + + int deletePhysicallyByBizMonth(@Param("bizMonth") String bizMonth); + + int insertBatch(@Param("list") List list); +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportSwipeMapper.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportSwipeMapper.java index ed1c042c8..60368a857 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportSwipeMapper.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/mapper/CanteenReportSwipeMapper.java @@ -26,5 +26,8 @@ public interface CanteenReportSwipeMapper extends BaseMapper list); } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportBoxImportService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportBoxImportService.java new file mode 100644 index 000000000..013d59548 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportBoxImportService.java @@ -0,0 +1,12 @@ +package com.bsd.pjcloud.canteen.service; + +import com.bsd.pjcloud.common.core.util.R; +import org.springframework.web.multipart.MultipartFile; + +/** + * 包厢台账 → 刷卡明细(收入类别=包厢收入,source=BOX) + */ +public interface CanteenReportBoxImportService { + + R importExcel(MultipartFile file, String bizMonth, boolean replaceMonth); +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportCalcService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportCalcService.java index 37056e0b2..549bb9155 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportCalcService.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportCalcService.java @@ -2,6 +2,7 @@ package com.bsd.pjcloud.canteen.service; import com.bsd.pjcloud.canteen.vo.report.CanteenReportBusinessRowVO; import com.bsd.pjcloud.canteen.vo.report.CanteenReportIncomeRowVO; +import com.bsd.pjcloud.canteen.vo.report.CanteenReportShareAuditVO; import com.bsd.pjcloud.canteen.vo.report.CanteenReportShareRowVO; import java.util.List; @@ -16,6 +17,9 @@ public interface CanteenReportCalcService { List supplierShareReport(String bizMonth); + /** 厂区核对 + 权重明细 */ + CanteenReportShareAuditVO supplierShareAudit(String bizMonth); + List businessReport(String bizMonth); Map summary(String bizMonth); diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFaceImportService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFaceImportService.java index 335214711..ccce683c9 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFaceImportService.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFaceImportService.java @@ -2,11 +2,15 @@ package com.bsd.pjcloud.canteen.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.canteen.dto.report.CanteenReportOrgLevel2VO; import com.bsd.pjcloud.canteen.entity.CanteenReportFaceDetailEntity; import com.bsd.pjcloud.canteen.entity.CanteenReportImportLogEntity; import com.bsd.pjcloud.common.core.util.R; import org.springframework.web.multipart.MultipartFile; +import java.util.List; +import java.util.Map; + public interface CanteenReportFaceImportService extends IService { /** @@ -22,11 +26,19 @@ public interface CanteenReportFaceImportService extends IService orgLevel2s); Page pageLogs(Page page, String bizMonth, String status); Page pageDetails(Page page, String bizMonth, Long importLogId, String empNo, - String payMethod); + String payMethod, String deviceName, String empName, String phone, List orgLevel2s); + + /** 与明细分页相同条件的订单金额合计(元) */ + Map sumDetails(String bizMonth, Long importLogId, String empNo, String payMethod, + String deviceName, String empName, String phone, List orgLevel2s); + + List listOrgLevel2(String bizMonth, Long importLogId); } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFoodEnergyService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFoodEnergyService.java index 4cb76c0e9..bdbf36288 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFoodEnergyService.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportFoodEnergyService.java @@ -6,6 +6,7 @@ import com.bsd.pjcloud.canteen.dto.report.CanteenReportFoodEnergyExcel; import com.bsd.pjcloud.canteen.entity.CanteenReportFoodEnergyEntity; import com.bsd.pjcloud.common.core.util.R; import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -13,7 +14,13 @@ public interface CanteenReportFoodEnergyService extends IService pageList(Page page, String bizMonth, String park, String supplier); + /** 手工新增/修改一行(外协、能耗、耗材、食材等) */ + R saveOrUpdateRow(CanteenReportFoodEnergyEntity entity); + R importExcel(List rows, String bizMonth, boolean replaceMonth, BindingResult bindingResult); + /** 导入「食堂收发存报表(汇总)」列布局(由 /import 自动识别调用) */ + R importReceiptStock(MultipartFile file, String bizMonth, boolean replaceMonth); + List exportExcel(String bizMonth); } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportRosterService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportRosterService.java new file mode 100644 index 000000000..eec88d2bd --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportRosterService.java @@ -0,0 +1,19 @@ +package com.bsd.pjcloud.canteen.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.canteen.entity.CanteenReportRosterEntity; +import com.bsd.pjcloud.common.core.util.R; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Map; + +public interface CanteenReportRosterService extends IService { + + Page pageList(Page page, String bizMonth, String empNo, String empName, String hrScope); + + R importExcel(MultipartFile file, String bizMonth, boolean replaceMonth); + + /** empNo -> 人事范围;人在花名册但人事范围为空时值为空串(key 仍存在) */ + Map loadHrScopeMap(String bizMonth); +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportSwipeService.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportSwipeService.java index 843146cb0..3afdbe8d6 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportSwipeService.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/CanteenReportSwipeService.java @@ -7,12 +7,18 @@ import com.bsd.pjcloud.canteen.entity.CanteenReportSwipeEntity; import com.bsd.pjcloud.common.core.util.R; import org.springframework.validation.BindingResult; +import java.math.BigDecimal; import java.util.List; +import java.util.Map; public interface CanteenReportSwipeService extends IService { Page pageList(Page page, String bizMonth, String park, String incomeType, String costCenter, - String empNo, Long importLogId); + String empNo, Long importLogId, String remarkTag, String source); + + /** 与分页相同条件的消费金额合计(元),含 bySource 拆分 */ + Map sumAmount(String bizMonth, String park, String incomeType, String costCenter, + String empNo, Long importLogId, String remarkTag, String source); R importExcel(List rows, String bizMonth, boolean replaceMonth, BindingResult bindingResult); diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportBoxImportServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportBoxImportServiceImpl.java new file mode 100644 index 000000000..27bc57b7d --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportBoxImportServiceImpl.java @@ -0,0 +1,139 @@ +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.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.CanteenReportConstants; +import com.bsd.pjcloud.canteen.service.CanteenReportBoxImportService; +import com.bsd.pjcloud.common.core.constant.CommonConstants; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.data.tenant.TenantContextHolder; +import com.bsd.pjcloud.common.security.util.SecurityUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CanteenReportBoxImportServiceImpl implements CanteenReportBoxImportService { + + private static final String SOURCE_BOX = "BOX"; + private static final int BATCH = 800; + + private final CanteenReportSwipeMapper swipeMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public R importExcel(MultipartFile file, String bizMonth, boolean replaceMonth) { + if (file == null || file.isEmpty()) { + return R.failed("请上传包厢台账 Excel"); + } + if (StrUtil.isBlank(bizMonth)) { + return R.failed("请传入业务月份 bizMonth"); + } + try { + List rows = EasyExcel.read(file.getInputStream()) + .head(CanteenReportBoxExcel.class) + .registerConverter(new ExcelYuanConverter()) + .registerConverter(new ExcelYuanStringConverter()) + .sheet() + .headRowNumber(1) + .doReadSync(); + if (CollUtil.isEmpty(rows)) { + return R.failed("导入数据为空"); + } + String operator = currentUser(); + Long tenantId = TenantContextHolder.getTenantId(); + LocalDateTime now = LocalDateTime.now(); + List saveList = new ArrayList<>(); + int skip = 0; + for (CanteenReportBoxExcel row : rows) { + BigDecimal amount = resolveAmount(row); + if (amount == null || amount.compareTo(BigDecimal.ZERO) == 0) { + skip++; + continue; + } + if (StrUtil.isBlank(row.getCostCenter()) && StrUtil.isBlank(row.getEmpNo()) + && StrUtil.isBlank(row.getPark())) { + skip++; + continue; + } + CanteenReportSwipeEntity e = new CanteenReportSwipeEntity(); + e.setId(IdWorker.getId()); + e.setBizMonth(bizMonth.trim()); + e.setBizDate(blankToNull(row.getBizDate())); + e.setEmpNo(blankToNull(row.getEmpNo())); + e.setEmpName(blankToNull(row.getEmpName())); + e.setLegalEntity(blankToNull(row.getLegalEntity())); + e.setConsumeAmount(amount); + e.setIncomeType(CanteenReportConstants.INCOME_BOX); + e.setCostCenter(StrUtil.blankToDefault(blankToNull(row.getCostCenter()), "未匹配成本中心")); + e.setPark(blankToNull(row.getPark())); + String remark = StrUtil.join(" ", + StrUtil.blankToDefault(row.getRemark1(), ""), + StrUtil.blankToDefault(row.getRemark2(), "")).trim(); + e.setRemarkTag(StrUtil.isBlank(remark) ? "包厢台账" : remark); + e.setSource(SOURCE_BOX); + e.setCreateBy(operator); + e.setUpdateBy(operator); + e.setCreateTime(now); + e.setUpdateTime(now); + e.setDelFlag(CommonConstants.STATUS_NORMAL); + e.setTenantId(tenantId); + saveList.add(e); + } + if (CollUtil.isEmpty(saveList)) { + return R.failed("无有效包厢金额可入库"); + } + if (replaceMonth) { + swipeMapper.deletePhysicallyBySourceAndBizMonth(SOURCE_BOX, bizMonth.trim()); + } + for (List chunk : CollUtil.split(saveList, BATCH)) { + swipeMapper.insertBatch(chunk); + } + return R.ok(null, String.format("包厢导入成功:写入刷卡 %d 条(收入类别=包厢收入),跳过 %d 条", + saveList.size(), skip)); + } + catch (Exception e) { + return R.failed("导入失败: " + e.getMessage()); + } + } + + private static BigDecimal resolveAmount(CanteenReportBoxExcel row) { + if (row.getTotalAmount() != null) { + return row.getTotalAmount(); + } + BigDecimal food = row.getFoodAmount() == null ? BigDecimal.ZERO : row.getFoodAmount(); + BigDecimal drink = row.getDrinkAmount() == null ? BigDecimal.ZERO : row.getDrinkAmount(); + BigDecimal sum = food.add(drink); + return sum.compareTo(BigDecimal.ZERO) == 0 ? null : sum; + } + + private static String blankToNull(String s) { + return StrUtil.isBlank(s) ? null : s.trim(); + } + + private static String currentUser() { + try { + if (SecurityUtils.getUser() != null) { + return SecurityUtils.getUser().getUsername(); + } + } + catch (Exception ignored) { + // ignore + } + return null; + } +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportCalcServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportCalcServiceImpl.java index ed3541ff2..fb1dded30 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportCalcServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportCalcServiceImpl.java @@ -12,7 +12,10 @@ import com.bsd.pjcloud.canteen.service.CanteenReportManualAdjustService; import com.bsd.pjcloud.canteen.service.CanteenReportSwipeService; import com.bsd.pjcloud.canteen.vo.report.CanteenReportBusinessRowVO; import com.bsd.pjcloud.canteen.vo.report.CanteenReportIncomeRowVO; +import com.bsd.pjcloud.canteen.vo.report.CanteenReportShareAuditVO; +import com.bsd.pjcloud.canteen.vo.report.CanteenReportShareParkCheckVO; import com.bsd.pjcloud.canteen.vo.report.CanteenReportShareRowVO; +import com.bsd.pjcloud.canteen.vo.report.CanteenReportShareWeightRowVO; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,7 +30,7 @@ import java.util.Map; * 报表计算演示实现: * 收入合计 = 刷卡明细按成本中心×厂区汇总; * 供应商分摊 = 权重 × (厂区食材池 − 厂区包厢 − 厂区其他收入),权重=中心员工刷卡/厂区员工刷卡; - * 经营报表 = 收入(万元) + 食材支出(分摊/10000) + 毛利分析。 + * 经营报表 = 收入(万元) + 支出(食材=分摊−能耗−外协;能耗/外协按食材能耗分类;人工按人工列) + 毛利分析。 */ @Service @RequiredArgsConstructor @@ -209,10 +212,119 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService { return rows; } + @Override + public CanteenReportShareAuditVO supplierShareAudit(String bizMonth) { + List foods = loadFoods(bizMonth); + List swipes = loadSwipes(bizMonth); + Map parkFoodPool = new HashMap<>(); + for (CanteenReportFoodEnergyEntity f : foods) { + String classify = StrUtil.blankToDefault(f.getClassify(), "食材").trim(); + if (!CanteenReportConstants.SHARE_FOOD_CLASSIFY.contains(classify)) { + continue; + } + parkFoodPool.merge(StrUtil.blankToDefault(f.getPark(), ""), nz(f.getSettleAmount()), BigDecimal::add); + } + Map parkBoxOther = new HashMap<>(); + Map parkSwipeTotal = new HashMap<>(); + Map centerParkSwipe = new HashMap<>(); + for (CanteenReportSwipeEntity s : swipes) { + String park = StrUtil.blankToDefault(s.getPark(), ""); + String center = StrUtil.blankToDefault(s.getCostCenter(), "未匹配成本中心"); + String type = StrUtil.blankToDefault(s.getIncomeType(), ""); + BigDecimal amt = nz(s.getConsumeAmount()); + if (CanteenReportConstants.INCOME_SWIPE.equals(type)) { + parkSwipeTotal.merge(park, amt, BigDecimal::add); + centerParkSwipe.merge(center + "|" + park, amt, BigDecimal::add); + } + if (CanteenReportConstants.SHARE_BOX_OTHER_TYPES.contains(type)) { + parkBoxOther.merge(park, amt, BigDecimal::add); + } + } + Map parkAllocable = new LinkedHashMap<>(); + for (String park : CanteenReportConstants.SHARE_PARKS) { + parkAllocable.put(park, nz(parkFoodPool.get(park)).subtract(nz(parkBoxOther.get(park)))); + } + + List centers = new ArrayList<>(CanteenReportConstants.COST_CENTERS); + for (String k : centerParkSwipe.keySet()) { + String c = k.substring(0, k.indexOf('|')); + if (!centers.contains(c)) { + centers.add(c); + } + } + + List weightRows = new ArrayList<>(); + Map shareParkTotal = new LinkedHashMap<>(); + Map weightParkTotal = new LinkedHashMap<>(); + for (String park : CanteenReportConstants.SHARE_PARKS) { + shareParkTotal.put(park, BigDecimal.ZERO); + weightParkTotal.put(park, BigDecimal.ZERO); + } + for (String center : centers) { + CanteenReportShareWeightRowVO row = new CanteenReportShareWeightRowVO(); + row.setCostCenter(center); + boolean any = false; + for (String park : CanteenReportConstants.SHARE_PARKS) { + BigDecimal parkTotal = nz(parkSwipeTotal.get(park)); + BigDecimal centerAmt = nz(centerParkSwipe.get(center + "|" + park)).setScale(SCALE, RoundingMode.HALF_UP); + BigDecimal weight = BigDecimal.ZERO; + BigDecimal share = BigDecimal.ZERO; + BigDecimal allocable = nz(parkAllocable.get(park)); + if (parkTotal.compareTo(BigDecimal.ZERO) > 0) { + weight = centerAmt.divide(parkTotal, 8, RoundingMode.HALF_UP); + if (allocable.compareTo(BigDecimal.ZERO) != 0) { + share = weight.multiply(allocable).setScale(SCALE, RoundingMode.HALF_UP); + } + } + row.getSwipeAmounts().put(park, centerAmt); + row.getWeights().put(park, weight); + row.getShareAmounts().put(park, share); + shareParkTotal.merge(park, share, BigDecimal::add); + weightParkTotal.merge(park, weight, BigDecimal::add); + if (centerAmt.compareTo(BigDecimal.ZERO) != 0 || share.compareTo(BigDecimal.ZERO) != 0) { + any = true; + } + } + if (any) { + weightRows.add(row); + } + } + CanteenReportShareWeightRowVO totalRow = new CanteenReportShareWeightRowVO(); + totalRow.setCostCenter("合计(权重应≈1)"); + for (String park : CanteenReportConstants.SHARE_PARKS) { + 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)); + } + weightRows.add(totalRow); + + List parkChecks = new ArrayList<>(); + for (String park : CanteenReportConstants.SHARE_PARKS) { + 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); + BigDecimal shareTotal = nz(shareParkTotal.get(park)).setScale(SCALE, RoundingMode.HALF_UP); + CanteenReportShareParkCheckVO vo = new CanteenReportShareParkCheckVO(); + vo.setPark(park); + vo.setFoodPool(foodPool); + vo.setBoxOther(boxOther); + vo.setAllocable(allocable); + vo.setSwipeTotal(nz(parkSwipeTotal.get(park)).setScale(SCALE, RoundingMode.HALF_UP)); + vo.setShareTotal(shareTotal); + vo.setDiff(shareTotal.subtract(allocable).setScale(SCALE, RoundingMode.HALF_UP)); + parkChecks.add(vo); + } + CanteenReportShareAuditVO audit = new CanteenReportShareAuditVO(); + audit.setParkChecks(parkChecks); + audit.setWeightRows(weightRows); + return audit; + } + @Override public List businessReport(String bizMonth) { List swipes = loadSwipes(bizMonth); List shares = supplierShareReport(bizMonth); + List foods = loadFoods(bizMonth); Map> incomeByTypePark = new HashMap<>(); for (String t : CanteenReportConstants.INCOME_TYPES) { @@ -236,22 +348,45 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService { incomeSub = sumRows("收入(万元)", "小计", rows); rows.add(incomeSub); - // 食材支出 = 分摊表各厂区成本中心分摊之和 / 10000 - Map foodExpenseYuan = emptyParkMap(); + // 分摊合计(元)含食材+能耗+外协;经营报表拆开展示时,食材行 = 分摊 − 能耗 − 外协,避免重复 + Map shareExpenseYuan = emptyParkMap(); for (CanteenReportShareRowVO share : shares) { if (isShareMetaRow(share.getCostCenter())) { continue; } for (Map.Entry e : share.getParkAmounts().entrySet()) { - foodExpenseYuan.merge(e.getKey(), nz(e.getValue()), BigDecimal::add); + shareExpenseYuan.merge(e.getKey(), nz(e.getValue()), BigDecimal::add); } } - CanteenReportBusinessRowVO foodRow = toWanRow("支出(万元)", "食材", foodExpenseYuan); - rows.add(foodRow); - rows.add(toWanRow("支出(万元)", "能耗", emptyParkMap())); - rows.add(toWanRow("支出(万元)", "人工", emptyParkMap())); - rows.add(toWanRow("支出(万元)", "外协", emptyParkMap())); - CanteenReportBusinessRowVO expenseSub = cloneAs("支出(万元)", "小计", foodRow); + Map energyYuan = emptyParkMap(); + Map outsourceYuan = emptyParkMap(); + Map laborYuan = emptyParkMap(); + Map splitFromShareYuan = emptyParkMap(); + for (CanteenReportFoodEnergyEntity f : foods) { + String park = StrUtil.blankToDefault(f.getPark(), ""); + String classify = StrUtil.blankToDefault(f.getClassify(), "食材").trim(); + BigDecimal settle = nz(f.getSettleAmount()); + laborYuan.merge(park, nz(f.getLaborAmount()), BigDecimal::add); + if ("能耗".equals(classify)) { + energyYuan.merge(park, settle, BigDecimal::add); + splitFromShareYuan.merge(park, settle, BigDecimal::add); + } + else if ("外协".equals(classify)) { + outsourceYuan.merge(park, settle, BigDecimal::add); + splitFromShareYuan.merge(park, settle, BigDecimal::add); + } + } + Map foodOnlyYuan = emptyParkMap(); + for (String park : CanteenReportConstants.PARKS) { + foodOnlyYuan.put(park, nz(shareExpenseYuan.get(park)).subtract(nz(splitFromShareYuan.get(park)))); + } + + int expenseFrom = rows.size(); + rows.add(toWanRow("支出(万元)", "食材", foodOnlyYuan)); + rows.add(toWanRow("支出(万元)", "能耗", energyYuan)); + rows.add(toWanRow("支出(万元)", "人工", laborYuan)); + rows.add(toWanRow("支出(万元)", "外协", outsourceYuan)); + CanteenReportBusinessRowVO expenseSub = sumRows("支出(万元)", "小计", rows.subList(expenseFrom, rows.size())); rows.add(expenseSub); // 毛利 @@ -293,6 +428,9 @@ public class CanteenReportCalcServiceImpl implements CanteenReportCalcService { 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; } diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFaceImportServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFaceImportServiceImpl.java index a8181e389..3dba9cb28 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFaceImportServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFaceImportServiceImpl.java @@ -6,13 +6,16 @@ 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.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.admin.api.feign.RemoteFileService; +import com.bsd.pjcloud.canteen.dto.report.CanteenReportAggResultVO; import com.bsd.pjcloud.canteen.dto.report.CanteenReportFaceAggRow; import com.bsd.pjcloud.canteen.dto.report.CanteenReportFaceExcel; +import com.bsd.pjcloud.canteen.dto.report.CanteenReportRosterMissVO; import com.bsd.pjcloud.canteen.entity.CanteenReportFaceDetailEntity; import com.bsd.pjcloud.canteen.entity.CanteenReportImportLogEntity; import com.bsd.pjcloud.canteen.entity.CanteenReportSettleParkEntity; @@ -21,8 +24,10 @@ import com.bsd.pjcloud.canteen.mapper.CanteenReportFaceDetailMapper; 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.CanteenReportFaceMapper; import com.bsd.pjcloud.canteen.service.CanteenReportFaceImportService; +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; @@ -42,6 +47,7 @@ import java.nio.file.Files; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -59,6 +65,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl pageDetails(Page page, String bizMonth, Long importLogId, String empNo, - String payMethod) { + String payMethod, String deviceName, String empName, String phone, List orgLevel2s) { + List orgs = normalizeOrgLevel2s(orgLevel2s); return faceDetailMapper.selectPage(page, Wrappers.lambdaQuery() .eq(StrUtil.isNotBlank(bizMonth), CanteenReportFaceDetailEntity::getBizMonth, bizMonth) .eq(importLogId != null, CanteenReportFaceDetailEntity::getImportLogId, importLogId) .like(StrUtil.isNotBlank(empNo), CanteenReportFaceDetailEntity::getEmpNo, empNo) .like(StrUtil.isNotBlank(payMethod), CanteenReportFaceDetailEntity::getPayMethod, payMethod) + .like(StrUtil.isNotBlank(deviceName), CanteenReportFaceDetailEntity::getDeviceName, deviceName) + .like(StrUtil.isNotBlank(empName), CanteenReportFaceDetailEntity::getEmpName, empName) + .like(StrUtil.isNotBlank(phone), CanteenReportFaceDetailEntity::getPhone, phone) + .in(CollUtil.isNotEmpty(orgs), CanteenReportFaceDetailEntity::getOrgLevel2, orgs) .orderByDesc(CanteenReportFaceDetailEntity::getId)); } + @Override + public Map sumDetails(String bizMonth, Long importLogId, String empNo, String payMethod, + String deviceName, String empName, String phone, List orgLevel2s) { + List orgs = normalizeOrgLevel2s(orgLevel2s); + QueryWrapper qw = Wrappers.query(); + qw.select("IFNULL(SUM(order_amount),0) AS totalAmount", "COUNT(1) AS totalCount"); + qw.eq(StrUtil.isNotBlank(bizMonth), "biz_month", bizMonth); + qw.eq(importLogId != null, "import_log_id", importLogId); + qw.like(StrUtil.isNotBlank(empNo), "emp_no", empNo); + qw.like(StrUtil.isNotBlank(payMethod), "pay_method", payMethod); + qw.like(StrUtil.isNotBlank(deviceName), "device_name", deviceName); + qw.like(StrUtil.isNotBlank(empName), "emp_name", empName); + qw.like(StrUtil.isNotBlank(phone), "phone", phone); + qw.in(CollUtil.isNotEmpty(orgs), "org_level2", orgs); + List> list = faceDetailMapper.selectMaps(qw); + if (CollUtil.isEmpty(list) || list.get(0) == null) { + Map empty = new HashMap<>(); + empty.put("totalAmount", BigDecimal.ZERO); + empty.put("totalCount", 0); + return empty; + } + Map raw = list.get(0); + Map out = new HashMap<>(); + Object amt = raw.get("totalAmount"); + if (amt == null) { + amt = raw.get("TOTALAMOUNT"); + } + Object cnt = raw.get("totalCount"); + if (cnt == null) { + cnt = raw.get("TOTALCOUNT"); + } + out.put("totalAmount", amt != null ? amt : BigDecimal.ZERO); + out.put("totalCount", cnt != null ? cnt : 0); + return out; + } + @Override public R importFaceExcel(MultipartFile file, String bizMonth, String dateStart, String dateEnd, boolean replaceRange) { @@ -91,6 +139,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl { + TenantContextHolder.setTenantId(tenantId); try { - faceDetailMapper.deletePhysicallyByImportLogId(logEntity.getId()); + doFaceImportAsync(tempFile, fileName, logEntity, month, ds, de, replace, operator, tenantId, t0); } - catch (Exception ignored) { - // ignore + catch (Throwable e) { + log.error("人脸明细异步导入失败 logId={}", logEntity.getId(), e); + try { + faceDetailMapper.deletePhysicallyByImportLogId(logEntity.getId()); + } + catch (Exception ignored) { + // ignore + } + failLog(logEntity, + StrUtil.maxLength(StrUtil.blankToDefault(e.getMessage(), e.getClass().getSimpleName()), 900), t0); } - failLog(logEntity, StrUtil.maxLength(StrUtil.blankToDefault(e.getMessage(), e.getClass().getSimpleName()), 900), - t0); - return R.failed("导入失败: " + e.getMessage()); + finally { + TenantContextHolder.clear(); + if (tempFile.exists()) { + FileUtil.del(tempFile); + } + } + }, "canteen-face-import-" + logEntity.getId()); + worker.setDaemon(false); + worker.start(); + + String eta = estimateImportMinutes(fileBytes); + return R.ok(logEntity, "已提交导入,后台正在处理。请在下方导入列表查看状态(导入中→成功/失败)。" + eta + + "完成后请再点「统计汇总到刷卡明细」。"); + } + + /** 按文件大小粗估耗时(经验值,供前端提示) */ + private static String estimateImportMinutes(long fileBytes) { + double mb = fileBytes / (1024.0 * 1024.0); + if (mb < 5) { + return "预计约 1 分钟内完成。"; } - finally { - if (temp != null && temp.exists()) { - FileUtil.del(temp); - } + int low = Math.max(2, (int) Math.ceil(mb / 20)); + int high = Math.max(low + 1, (int) Math.ceil(mb / 12)); + return String.format("当前文件约 %.0f MB,预计约 %d~%d 分钟。", mb, low, high); + } + + private void doFaceImportAsync(File temp, String originalName, CanteenReportImportLogEntity logEntity, + String bizMonth, String dateStart, String dateEnd, boolean replaceRange, String operator, Long tenantId, + long t0) throws Exception { + archiveExcel(temp, originalName, logEntity); + + boolean preReplace = replaceRange && StrUtil.isNotBlank(bizMonth) && StrUtil.isNotBlank(dateStart) + && StrUtil.isNotBlank(dateEnd); + if (preReplace) { + faceDetailMapper.deletePhysicallyByDateRange(bizMonth.trim(), dateStart.trim(), dateEnd.trim()); } + + FaceDetailAccumulator acc = new FaceDetailAccumulator(logEntity.getId(), bizMonth, operator, tenantId, + LocalDateTime.now(), faceDetailMapper, BATCH_SIZE); + try (InputStream in = new BufferedInputStream(new FileInputStream(temp), 1 << 20)) { + EasyExcel.read(in, CanteenReportFaceExcel.class, acc) + .sheet() + .headRowNumber(1) + .doRead(); + } + acc.flush(); + + String resolvedMonth = StrUtil.blankToDefault(StrUtil.trim(bizMonth), acc.getResolvedBizMonth()); + 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()); + failLog(logEntity, "无法确定报表归属月,请填写报表归属月", t0); + return; + } + if (StrUtil.isBlank(resolvedStart) || StrUtil.isBlank(resolvedEnd)) { + resolvedStart = resolvedMonth + "-01"; + resolvedEnd = lastDayOfMonth(resolvedMonth); + } + + if (replaceRange && !preReplace) { + faceDetailMapper.deletePhysicallyDateRangeExceptLog(resolvedMonth, resolvedStart, resolvedEnd, + logEntity.getId()); + } + + logEntity.setBizMonth(resolvedMonth); + logEntity.setDateStart(resolvedStart); + logEntity.setDateEnd(resolvedEnd); + logEntity.setTotalRows(acc.getTotalRows()); + logEntity.setSuccessRows(acc.getSuccessRows()); + logEntity.setSkipRows(acc.getSkipRows()); + logEntity.setSkipSummary(acc.buildSkipSummary()); + logEntity.setStatus("SUCCESS"); + logEntity.setAggStatus("NONE"); + logEntity.setCostMs(System.currentTimeMillis() - t0); + logEntity.setUpdateTime(LocalDateTime.now()); + updateById(logEntity); + log.info("人脸明细异步导入完成 logId={} success={} skip={} costMs={}", logEntity.getId(), + acc.getSuccessRows(), acc.getSkipRows(), logEntity.getCostMs()); } @Override public R aggregateToSwipe(String bizMonth, String dateStart, String dateEnd, Long importLogId, - boolean replaceFace) { + boolean replaceFace, List orgLevel2s) { if (StrUtil.isBlank(bizMonth) && importLogId == null) { return R.failed("请传入业务月份 bizMonth,或指定某一批导入日志"); } @@ -205,6 +295,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl orgs = normalizeOrgLevel2s(orgLevel2s); if (importLogId != null) { CanteenReportImportLogEntity logEntity = getById(importLogId); @@ -214,17 +305,23 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl aggRows = faceDetailMapper.selectAggByMonth(month, useDateStart, useDateEnd, - importLogId); + importLogId, orgs); if (CollUtil.isEmpty(aggRows)) { - return R.failed("明细存在但汇总结果为空,请检查订单金额是否都为 0"); + return R.failed("明细存在但汇总结果为空,请检查订单金额是否都为 0,或所选二级组织是否有数据"); } if (StrUtil.isBlank(month) && importLogId != null) { @@ -251,8 +354,11 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl settleParkMap = loadSettleParkMap(); + Map hrScopeMap = rosterService.loadHrScopeMap(month); List saveList = new ArrayList<>(aggRows.size()); + Map missByKey = new LinkedHashMap<>(); int unmatchedPark = 0; + int unmatchedRoster = 0; for (CanteenReportFaceAggRow row : aggRows) { if (row.getAmount() == null || row.getAmount().compareTo(BigDecimal.ZERO) == 0) { continue; @@ -269,7 +375,44 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl 0 - ? String.format(";有 %d 条结算台未配置厂区,请到「结算台厂区」维护后再重新汇总", unmatchedPark) - : ""; - return R.ok(null, String.format("汇总成功:写入刷卡明细 %d 条,耗时 %d 秒%s", saveList.size(), (cost + 999) / 1000, tip)); + StringBuilder tip = new StringBuilder(); + if (hrScopeMap.isEmpty()) { + tip.append(";当月花名册为空,核对成本已回退法律归属映射,请先导入花名册"); + } + else if (unmatchedRoster > 0) { + tip.append(String.format(";有 %d 条、%d 人未命中花名册,已回退法律归属,刷卡明细备注「未命中花名册」可筛选", + unmatchedRoster, missByKey.size())); + } + if (unmatchedPark > 0) { + tip.append(String.format(";有 %d 条结算台未配置厂区,请到「结算台厂区」维护后再重新汇总", unmatchedPark)); + } + if (CollUtil.isNotEmpty(orgs)) { + tip.append(";已按二级组织筛选:").append(String.join("、", orgs)); + } + CanteenReportAggResultVO result = new CanteenReportAggResultVO(); + result.setSwipeRows(saveList.size()); + result.setUnmatchedRows(unmatchedRoster); + result.setUnmatchedPeople(missByKey.size()); + result.setUnmatched(new ArrayList<>(missByKey.values())); + return R.ok(result, String.format("汇总成功:写入刷卡明细 %d 条,耗时 %d 秒%s", + saveList.size(), (cost + 999) / 1000, tip)); + } + + @Override + public List listOrgLevel2(String bizMonth, + Long importLogId) { + return faceDetailMapper.listOrgLevel2(StrUtil.trim(bizMonth), importLogId); + } + + private static List normalizeOrgLevel2s(List orgLevel2s) { + if (CollUtil.isEmpty(orgLevel2s)) { + return List.of(); + } + List list = new ArrayList<>(); + for (String s : orgLevel2s) { + if (StrUtil.isBlank(s)) { + continue; + } + for (String part : s.split(",")) { + String t = StrUtil.trim(part); + if (StrUtil.isNotBlank(t) && !list.contains(t)) { + list.add(t); + } + } + } + return list; } private Map loadSettleParkMap() { @@ -376,6 +561,16 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl { + private static final int SKIP_SAMPLE_LIMIT = 80; + private final Long importLogId; private final String bizMonthParam; private final String operator; @@ -399,6 +596,8 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl buffer = new ArrayList<>(); + private final Map skipReasonCount = new LinkedHashMap<>(); + private final List skipSamples = new ArrayList<>(); private int totalRows; private int successRows; private int skipRows; @@ -420,12 +619,20 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl parts = new ArrayList<>(); + if (StrUtil.isNotBlank(data.getOrderNo())) { + parts.add("订单号=" + data.getOrderNo()); + } + if (StrUtil.isNotBlank(data.getEmpNo())) { + parts.add("人员编号=" + data.getEmpNo()); + } + if (StrUtil.isNotBlank(data.getEmpName())) { + parts.add("姓名=" + data.getEmpName()); + } + if (StrUtil.isNotBlank(data.getDeductTime())) { + parts.add("扣款时间=" + data.getDeductTime()); + } + else if (StrUtil.isNotBlank(data.getPayTime())) { + parts.add("支付时间=" + data.getPayTime()); + } + else if (StrUtil.isNotBlank(data.getCreateTime())) { + parts.add("创建时间=" + data.getCreateTime()); + } + return String.join(",", parts); + } + + String buildSkipSummary() { + if (skipRows <= 0) { + return null; + } + StringBuilder sb = new StringBuilder(); + sb.append("共跳过 ").append(skipRows).append(" 行。\n"); + sb.append("【分类统计】\n"); + for (Map.Entry e : skipReasonCount.entrySet()) { + sb.append("- ").append(e.getKey()).append(":").append(e.getValue()).append(" 行\n"); + } + if (CollUtil.isNotEmpty(skipSamples)) { + sb.append("【样例(最多").append(SKIP_SAMPLE_LIMIT).append("条)】\n"); + for (String s : skipSamples) { + sb.append(s).append("\n"); + } + if (skipRows > skipSamples.size()) { + sb.append("…其余 ").append(skipRows - skipSamples.size()).append(" 行略\n"); + } + } + return sb.toString(); + } + @Override public void doAfterAllAnalysed(AnalysisContext context) { flush(); diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFoodEnergyServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFoodEnergyServiceImpl.java index a83be93f9..a7be59559 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFoodEnergyServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportFoodEnergyServiceImpl.java @@ -2,12 +2,16 @@ 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.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.canteen.dto.report.CanteenReportFoodEnergyExcel; +import com.bsd.pjcloud.canteen.dto.report.CanteenReportReceiptStockExcel; import com.bsd.pjcloud.canteen.entity.CanteenReportFoodEnergyEntity; +import com.bsd.pjcloud.canteen.excel.ExcelYuanConverter; +import com.bsd.pjcloud.canteen.excel.ExcelYuanStringConverter; import com.bsd.pjcloud.canteen.mapper.CanteenReportFoodEnergyMapper; import com.bsd.pjcloud.canteen.service.CanteenReportFoodEnergyService; import com.bsd.pjcloud.common.core.constant.CommonConstants; @@ -20,10 +24,12 @@ import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -45,6 +51,41 @@ public class CanteenReportFoodEnergyServiceImpl .orderByDesc(CanteenReportFoodEnergyEntity::getId)); } + @Override + @Transactional(rollbackFor = Exception.class) + public R saveOrUpdateRow(CanteenReportFoodEnergyEntity entity) { + if (entity == null) { + return R.failed("参数为空"); + } + if (StrUtil.isBlank(entity.getBizMonth())) { + return R.failed("请填写业务月"); + } + if (StrUtil.isBlank(entity.getSupplier())) { + return R.failed("请填写供应商(或费用归属说明,如「嘉兴前台提供」)"); + } + if (StrUtil.isBlank(entity.getClassify())) { + entity.setClassify(mapClassify(entity.getCategory(), entity.getProductName())); + } + fillCalculatedAmounts(entity); + applyClassifyAmounts(entity); + if (entity.getId() == null) { + fillInsertMeta(Collections.singletonList(entity)); + baseMapper.insert(entity); + return R.ok(entity, "新增成功"); + } + entity.setUpdateTime(LocalDateTime.now()); + try { + if (SecurityUtils.getUser() != null) { + entity.setUpdateBy(SecurityUtils.getUser().getUsername()); + } + } + catch (Exception ignored) { + // ignore + } + updateById(entity); + return R.ok(entity, "保存成功"); + } + @Override @Transactional(rollbackFor = Exception.class) public R importExcel(List rows, String bizMonth, boolean replaceMonth, @@ -79,19 +120,11 @@ public class CanteenReportFoodEnergyServiceImpl CanteenReportFoodEnergyEntity e = new CanteenReportFoodEnergyEntity(); BeanUtils.copyProperties(row, e); e.setBizMonth(bizMonth); + if (StrUtil.isBlank(e.getClassify())) { + e.setClassify(mapClassify(e.getCategory(), e.getProductName())); + } fillCalculatedAmounts(e); - if (e.getEnergyAmount() == null) { - e.setEnergyAmount(BigDecimal.ZERO); - } - if (e.getLaborAmount() == null) { - e.setLaborAmount(BigDecimal.ZERO); - } - if (e.getConsumableAmount() == null) { - e.setConsumableAmount(BigDecimal.ZERO); - } - if (e.getOutsourceAmount() == null) { - e.setOutsourceAmount(BigDecimal.ZERO); - } + applyClassifyAmounts(e); saveList.add(e); } if (CollUtil.isNotEmpty(errorMessageList)) { @@ -109,6 +142,114 @@ public class CanteenReportFoodEnergyServiceImpl return R.ok(null, "导入成功,共 " + saveList.size() + " 条"); } + @Override + @Transactional(rollbackFor = Exception.class) + public R importReceiptStock(MultipartFile file, String bizMonth, boolean replaceMonth) { + if (file == null || file.isEmpty()) { + return R.failed("请上传收发存 Excel"); + } + if (StrUtil.isBlank(bizMonth)) { + return R.failed("请传入业务月份 bizMonth,格式 yyyy-MM"); + } + try { + List rows = EasyExcel.read(file.getInputStream()) + .head(CanteenReportReceiptStockExcel.class) + .registerConverter(new ExcelYuanConverter()) + .registerConverter(new ExcelYuanStringConverter()) + .sheet() + .headRowNumber(1) + .doReadSync(); + if (CollUtil.isEmpty(rows)) { + return R.failed("导入数据为空"); + } + List saveList = new ArrayList<>(Math.min(rows.size(), 32768)); + int skip = 0; + for (CanteenReportReceiptStockExcel row : rows) { + if (StrUtil.isBlank(row.getSupplier()) && row.getSettleAmount() == null && row.getAmount() == null) { + skip++; + continue; + } + if (StrUtil.isBlank(row.getSupplier())) { + skip++; + continue; + } + CanteenReportFoodEnergyEntity e = new CanteenReportFoodEnergyEntity(); + e.setBizMonth(bizMonth.trim()); + e.setBizDate(trimToNull(row.getBizDate())); + e.setSupplier(row.getSupplier().trim()); + e.setReceiptNo(trimToNull(row.getReceiptNo())); + e.setCategory(trimToNull(row.getCategory())); + e.setProductCode(trimToNull(row.getProductCode())); + e.setProductName(trimToNull(row.getProductName())); + e.setSpec(trimToNull(row.getSpec())); + e.setUnit(trimToNull(row.getUnit())); + e.setQty(row.getQty()); + e.setUnitPrice(row.getUnitPrice()); + e.setListPrice(row.getListPrice()); + e.setPriceDiff(row.getPriceDiff()); + e.setSettleUnitPrice(row.getSettleUnitPrice()); + e.setSettleAmount(row.getSettleAmount()); + e.setAmount(row.getAmount()); + e.setAmountDiff(row.getAmountDiff()); + e.setWarehouse(trimToNull(row.getWarehouse())); + e.setPark(trimToNull(row.getPark())); + e.setClassify(mapClassify(row.getCategory(), row.getProductName())); + fillCalculatedAmounts(e); + applyClassifyAmounts(e); + saveList.add(e); + } + if (CollUtil.isEmpty(saveList)) { + return R.failed("无有效数据可入库(需有供应商)"); + } + if (replaceMonth) { + baseMapper.deletePhysicallyByBizMonth(bizMonth.trim()); + } + fillInsertMeta(saveList); + for (List chunk : CollUtil.split(saveList, BATCH_SIZE)) { + baseMapper.insertBatch(chunk); + } + return R.ok(null, String.format("收发存导入成功:入库 %d 条,跳过 %d 条", saveList.size(), skip)); + } + catch (Exception e) { + if (e instanceof com.alibaba.excel.exception.ExcelDataConvertException) { + return R.failed("Excel 金额/数量列含非数字,请检查列是否错位: " + e.getMessage()); + } + return R.failed("导入失败: " + e.getMessage()); + } + } + + /** 类别/品名含能耗、水电等 → 能耗;含外协 → 外协;含耗材 → 耗材;默认食材 */ + private static String mapClassify(String category, String productName) { + String t = (StrUtil.blankToDefault(category, "") + " " + StrUtil.blankToDefault(productName, "")).trim(); + if (StrUtil.containsAny(t, "能耗", "水电", "电费", "水费", "燃气", "能源")) { + return "能耗"; + } + if (StrUtil.contains(t, "外协")) { + return "外协"; + } + if (StrUtil.contains(t, "耗材")) { + return "耗材"; + } + return "食材"; + } + + /** 按分类把结算金额落到能耗/外协/耗材列 */ + private static void applyClassifyAmounts(CanteenReportFoodEnergyEntity e) { + String c = StrUtil.blankToDefault(e.getClassify(), "食材").trim(); + e.setClassify(c); + BigDecimal settle = nz(e.getSettleAmount()); + e.setEnergyAmount("能耗".equals(c) ? settle : BigDecimal.ZERO); + e.setOutsourceAmount("外协".equals(c) ? settle : BigDecimal.ZERO); + e.setConsumableAmount("耗材".equals(c) ? settle : BigDecimal.ZERO); + if (e.getLaborAmount() == null) { + e.setLaborAmount(BigDecimal.ZERO); + } + } + + private static String trimToNull(String s) { + return StrUtil.isBlank(s) ? null : s.trim(); + } + private void fillInsertMeta(List list) { LocalDateTime now = LocalDateTime.now(); String user = null; @@ -150,7 +291,14 @@ public class CanteenReportFoodEnergyServiceImpl e.setSettleAmount(nz(e.getSettleUnitPrice()).multiply(qty).setScale(2, java.math.RoundingMode.HALF_UP)); } if (e.getAmount() == null) { - e.setAmount(unitPrice.multiply(qty).setScale(2, java.math.RoundingMode.HALF_UP)); + // 手工补外协/能耗等往往只填结算总金额,金额默认与之相同 + if (qty.compareTo(BigDecimal.ZERO) == 0 && unitPrice.compareTo(BigDecimal.ZERO) == 0 + && e.getSettleAmount() != null) { + e.setAmount(e.getSettleAmount()); + } + else { + e.setAmount(unitPrice.multiply(qty).setScale(2, java.math.RoundingMode.HALF_UP)); + } } if (e.getAmountDiff() == null) { e.setAmountDiff(nz(e.getAmount()).subtract(nz(e.getSettleAmount()))); diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportRosterServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportRosterServiceImpl.java new file mode 100644 index 000000000..18d2df464 --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportRosterServiceImpl.java @@ -0,0 +1,129 @@ +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.baomidou.mybatisplus.core.toolkit.IdWorker; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bsd.pjcloud.canteen.dto.report.CanteenReportRosterExcel; +import com.bsd.pjcloud.canteen.entity.CanteenReportRosterEntity; +import com.bsd.pjcloud.canteen.mapper.CanteenReportRosterMapper; +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; +import com.bsd.pjcloud.common.security.util.SecurityUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class CanteenReportRosterServiceImpl extends ServiceImpl + implements CanteenReportRosterService { + + private static final int BATCH = 1000; + + @Override + public Page pageList(Page page, String bizMonth, String empNo, String empName, + String hrScope) { + return page(page, Wrappers.lambdaQuery() + .eq(StrUtil.isNotBlank(bizMonth), CanteenReportRosterEntity::getBizMonth, bizMonth) + .like(StrUtil.isNotBlank(empNo), CanteenReportRosterEntity::getEmpNo, empNo) + .like(StrUtil.isNotBlank(empName), CanteenReportRosterEntity::getEmpName, empName) + .like(StrUtil.isNotBlank(hrScope), CanteenReportRosterEntity::getHrScope, hrScope) + .orderByAsc(CanteenReportRosterEntity::getEmpNo)); + } + + @Override + public R importExcel(MultipartFile file, String bizMonth, boolean replaceMonth) { + if (file == null || file.isEmpty()) { + return R.failed("请上传花名册 Excel"); + } + if (StrUtil.isBlank(bizMonth)) { + return R.failed("请传入适用月份 bizMonth"); + } + try { + List rows = EasyExcel.read(file.getInputStream()) + .head(CanteenReportRosterExcel.class) + .sheet() + .headRowNumber(1) + .doReadSync(); + if (CollUtil.isEmpty(rows)) { + return R.failed("导入数据为空"); + } + String operator = currentUser(); + Long tenantId = TenantContextHolder.getTenantId(); + LocalDateTime now = LocalDateTime.now(); + List saveList = new ArrayList<>(Math.min(rows.size(), 32768)); + int skip = 0; + for (CanteenReportRosterExcel row : rows) { + if (StrUtil.isBlank(row.getEmpNo())) { + skip++; + continue; + } + CanteenReportRosterEntity e = new CanteenReportRosterEntity(); + BeanUtils.copyProperties(row, e); + e.setId(IdWorker.getId()); + e.setBizMonth(bizMonth.trim()); + e.setEmpNo(row.getEmpNo().trim()); + e.setCreateBy(operator); + e.setUpdateBy(operator); + e.setCreateTime(now); + e.setUpdateTime(now); + e.setDelFlag(CommonConstants.STATUS_NORMAL); + e.setTenantId(tenantId); + saveList.add(e); + } + if (replaceMonth) { + baseMapper.deletePhysicallyByBizMonth(bizMonth.trim()); + } + for (List chunk : CollUtil.split(saveList, BATCH)) { + baseMapper.insertBatch(chunk); + } + return R.ok(null, String.format("花名册导入成功:入库 %d 条,跳过 %d 条", saveList.size(), skip)); + } + catch (Exception e) { + return R.failed("导入失败: " + e.getMessage()); + } + } + + @Override + public Map loadHrScopeMap(String bizMonth) { + Map map = new HashMap<>(); + List list = list(Wrappers.lambdaQuery() + .eq(StrUtil.isNotBlank(bizMonth), CanteenReportRosterEntity::getBizMonth, bizMonth) + .select(CanteenReportRosterEntity::getEmpNo, CanteenReportRosterEntity::getHrScope)); + for (CanteenReportRosterEntity e : list) { + if (StrUtil.isBlank(e.getEmpNo())) { + continue; + } + String empNo = e.getEmpNo().trim(); + String hr = StrUtil.trim(e.getHrScope()); + // 空人事范围也占位,便于区分「不在花名册」和「在册但无人事范围」 + map.putIfAbsent(empNo, hr == null ? "" : hr); + } + return map; + } + + private static String currentUser() { + try { + if (SecurityUtils.getUser() != null) { + return SecurityUtils.getUser().getUsername(); + } + } + catch (Exception ignored) { + // ignore + } + return null; + } +} diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportSwipeServiceImpl.java b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportSwipeServiceImpl.java index 8734895c0..72e88bfd1 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportSwipeServiceImpl.java +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/java/com/bsd/pjcloud/canteen/service/impl/CanteenReportSwipeServiceImpl.java @@ -2,6 +2,7 @@ package com.bsd.pjcloud.canteen.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.IdWorker; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -25,8 +26,10 @@ import org.springframework.validation.BindingResult; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; @Service @@ -39,7 +42,7 @@ public class CanteenReportSwipeServiceImpl extends ServiceImpl pageList(Page page, String bizMonth, String park, String incomeType, - String costCenter, String empNo, Long importLogId) { + String costCenter, String empNo, Long importLogId, String remarkTag, String source) { return page(page, Wrappers.lambdaQuery() .eq(StrUtil.isNotBlank(bizMonth), CanteenReportSwipeEntity::getBizMonth, bizMonth) .eq(StrUtil.isNotBlank(park), CanteenReportSwipeEntity::getPark, park) @@ -47,9 +50,44 @@ public class CanteenReportSwipeServiceImpl extends ServiceImpl sumAmount(String bizMonth, String park, String incomeType, String costCenter, + String empNo, Long importLogId, String remarkTag, String source) { + QueryWrapper qw = Wrappers.query(); + qw.select("IFNULL(SUM(consume_amount),0) AS totalAmount", "COUNT(1) AS totalCount"); + applySumFilters(qw, bizMonth, park, incomeType, costCenter, empNo, importLogId, remarkTag, source); + Map map = getMap(qw); + if (map == null) { + map = new HashMap<>(); + map.put("totalAmount", BigDecimal.ZERO); + map.put("totalCount", 0); + } + QueryWrapper bySourceQw = Wrappers.query(); + bySourceQw.select("IFNULL(source,'') AS source", "IFNULL(SUM(consume_amount),0) AS totalAmount", + "COUNT(1) AS totalCount"); + applySumFilters(bySourceQw, bizMonth, park, incomeType, costCenter, empNo, importLogId, remarkTag, source); + bySourceQw.groupBy("source"); + map.put("bySource", listMaps(bySourceQw)); + return map; + } + + private void applySumFilters(QueryWrapper qw, String bizMonth, String park, + String incomeType, String costCenter, String empNo, Long importLogId, String remarkTag, String source) { + qw.eq(StrUtil.isNotBlank(bizMonth), "biz_month", bizMonth); + qw.eq(StrUtil.isNotBlank(park), "park", park); + qw.eq(StrUtil.isNotBlank(incomeType), "income_type", incomeType); + qw.like(StrUtil.isNotBlank(costCenter), "cost_center", costCenter); + qw.like(StrUtil.isNotBlank(empNo), "emp_no", empNo); + qw.eq(importLogId != null, "import_log_id", importLogId); + qw.eq(StrUtil.isNotBlank(remarkTag), "remark_tag", remarkTag); + qw.eq(StrUtil.isNotBlank(source), "source", source); + } + @Override @Transactional(rollbackFor = Exception.class) public R importExcel(List rows, String bizMonth, boolean replaceMonth, @@ -95,6 +133,7 @@ public class CanteenReportSwipeServiceImpl extends ServiceImpl) listener.getList(); } + /** + * 收发存汇总表:第2列是「收货单号」,且有「品牌」;与「食材能耗」sheet(第2列是供应商)区分。 + */ + public static boolean isReceiptStockLayout(MultipartFile file) { + if (file == null || file.isEmpty()) { + return false; + } + String name = file.getOriginalFilename(); + if (name != null && name.contains("收发存")) { + return true; + } + try (Workbook wb = WorkbookFactory.create(file.getInputStream())) { + Sheet sheet = wb.getNumberOfSheets() > 0 ? wb.getSheetAt(0) : null; + if (sheet == null) { + return false; + } + Row row = sheet.getRow(0); + if (row == null) { + return false; + } + DataFormatter fmt = new DataFormatter(); + StringBuilder headers = new StringBuilder(); + int last = Math.min(row.getLastCellNum(), 24); + for (int i = 0; i < last; i++) { + String h = fmt.formatCellValue(row.getCell(i)); + if (h != null) { + headers.append(h.trim()); + } + } + String all = headers.toString(); + if (all.contains("收货单号") && (all.contains("品牌") || all.contains("结算金额"))) { + return true; + } + String col1 = fmt.formatCellValue(row.getCell(1)); + return col1 != null && col1.contains("收货单号"); + } + catch (Exception e) { + return false; + } + } + private static int resolveSheetNo(byte[] bytes, String preferredSheetName) { if (preferredSheetName == null || preferredSheetName.isEmpty()) { return 0; diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportFaceDetailMapper.xml b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportFaceDetailMapper.xml index 3b9414f6d..87a34f74f 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportFaceDetailMapper.xml +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportFaceDetailMapper.xml @@ -86,6 +86,12 @@ AND (biz_date IS NULL OR biz_date <= #{dateEnd}) + + AND org_level2 IN + + #{org} + + GROUP BY IFNULL(emp_no, ''), IFNULL(emp_name, ''), @@ -114,5 +120,26 @@ AND biz_month = #{bizMonth} + + AND org_level2 IN + + #{org} + + + + + diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportRosterMapper.xml b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportRosterMapper.xml new file mode 100644 index 000000000..2bd35a52c --- /dev/null +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportRosterMapper.xml @@ -0,0 +1,25 @@ + + + + + + DELETE FROM canteen_report_roster WHERE biz_month = #{bizMonth} + + + + INSERT INTO canteen_report_roster ( + id, biz_month, emp_no, emp_name, emp_group, dept_cxo1, dept_cxo2, dept_cxo3, + job_title, job_nature, hr_scope, legal_company, rd_flag, finance_code, finance_name, + erp_cost_code, erp_cost_name, org_tag, + create_by, create_time, update_by, update_time, del_flag, tenant_id + ) VALUES + + ( + #{e.id}, #{e.bizMonth}, #{e.empNo}, #{e.empName}, #{e.empGroup}, #{e.deptCxo1}, #{e.deptCxo2}, #{e.deptCxo3}, + #{e.jobTitle}, #{e.jobNature}, #{e.hrScope}, #{e.legalCompany}, #{e.rdFlag}, #{e.financeCode}, #{e.financeName}, + #{e.erpCostCode}, #{e.erpCostName}, #{e.orgTag}, + #{e.createBy}, #{e.createTime}, #{e.updateBy}, #{e.updateTime}, #{e.delFlag}, #{e.tenantId} + ) + + + diff --git a/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportSwipeMapper.xml b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportSwipeMapper.xml index f0176a6fc..28db1a60f 100644 --- a/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportSwipeMapper.xml +++ b/pjcloud-canteen/pjcloud-canteen-biz/src/main/resources/mapper/CanteenReportSwipeMapper.xml @@ -31,6 +31,10 @@ AND (import_log_id IS NULL OR import_log_id <> #{importLogId}) + + DELETE FROM canteen_report_swipe WHERE source = #{source} AND biz_month = #{bizMonth} + + INSERT INTO canteen_report_swipe ( id, biz_month, biz_date, emp_no, emp_name, legal_entity, dept_name,