敏实报表
This commit is contained in:
1 parent
db277500de
commit
94f39deb2c
41 files changed
+1793
-124
No files matched your search
+18
@@ -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<CanteenReportRosterMissVO> unmatched = new ArrayList<>();
|
||||
}
|
||||
+53
@@ -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;
|
||||
}
|
||||
+9
-8
@@ -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 = "食堂仓库")
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package com.bsd.pjcloud.canteen.dto.report;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CanteenReportOrgLevel2VO {
|
||||
|
||||
private String name;
|
||||
|
||||
private Long cnt;
|
||||
}
|
||||
+72
@@ -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;
|
||||
}
|
||||
+59
@@ -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;
|
||||
}
|
||||
+25
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -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 = "收入类别")
|
||||
|
||||
+3
@@ -48,6 +48,9 @@ public class CanteenReportImportLogEntity extends Model<CanteenReportImportLogEn
|
||||
@Schema(description = "跳过行数")
|
||||
private Integer skipRows;
|
||||
|
||||
@Schema(description = "跳过原因摘要")
|
||||
private String skipSummary;
|
||||
|
||||
@Schema(description = "汇总写入刷卡明细行数")
|
||||
private Integer aggRows;
|
||||
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.bsd.pjcloud.canteen.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("canteen_report_roster")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(description = "食堂报表-花名册")
|
||||
public class CanteenReportRosterEntity extends Model<CanteenReportRosterEntity> {
|
||||
|
||||
@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;
|
||||
}
|
||||
+40
@@ -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<BigDecimal> {
|
||||
|
||||
private final ExcelYuanConverter delegate = new ExcelYuanConverter();
|
||||
|
||||
@Override
|
||||
public Class<BigDecimal> 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);
|
||||
}
|
||||
}
|
||||
+3
@@ -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<String> INCOME_TYPES = Collections.unmodifiableList(Arrays.asList(
|
||||
INCOME_SWIPE, INCOME_ALIPAY, INCOME_BOX, INCOME_OTHER, INCOME_PROFIT_ADJ, INCOME_INVOICE, INCOME_BAD_DEBT
|
||||
));
|
||||
|
||||
+13
@@ -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();
|
||||
|
||||
+18
@@ -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<CanteenReportShareParkCheckVO> parkChecks = new ArrayList<>();
|
||||
|
||||
@Schema(description = "各成本中心在各厂区的权重与员工刷卡")
|
||||
private List<CanteenReportShareWeightRowVO> weightRows = new ArrayList<>();
|
||||
}
|
||||
+32
@@ -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;
|
||||
}
|
||||
+25
@@ -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<String, BigDecimal> swipeAmounts = new LinkedHashMap<>();
|
||||
|
||||
@Schema(description = "权重 0~1,分摊金额 = 权重 × 该厂区可分摊")
|
||||
private Map<String, BigDecimal> weights = new LinkedHashMap<>();
|
||||
|
||||
@Schema(description = "分摊金额(元)= 权重 × 可分摊")
|
||||
private Map<String, BigDecimal> shareAmounts = new LinkedHashMap<>();
|
||||
}
|
||||
+31
@@ -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);
|
||||
}
|
||||
}
|
||||
+6
@@ -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) {
|
||||
|
||||
+56
-4
@@ -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<String> 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<String> 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<String> orgs = cn.hutool.core.util.StrUtil.isBlank(orgLevel2s)
|
||||
? java.util.List.of()
|
||||
: cn.hutool.core.util.StrUtil.splitTrim(orgLevel2s, ',');
|
||||
return faceImportService.aggregateToSwipe(bizMonth, dateStart, dateEnd, logId, replaceFace, orgs);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
+28
-5
@@ -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<CanteenReportFoodEnergyExcel> 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<CanteenReportFoodEnergyExcel> 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
|
||||
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+19
-2
@@ -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 = "详情")
|
||||
|
||||
+9
-2
@@ -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<CanteenReportF
|
||||
/**
|
||||
* 按人+维度汇总;B扫C 等归入支付宝,其余归入员工刷卡。金额用订单金额。
|
||||
* 传入 importLogId 时只按批次汇总,忽略日期段。
|
||||
* orgLevel2s 非空时只汇总这些二级组织。
|
||||
*/
|
||||
List<CanteenReportFaceAggRow> selectAggByMonth(@Param("bizMonth") String bizMonth,
|
||||
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd,
|
||||
@Param("importLogId") Long importLogId);
|
||||
@Param("importLogId") Long importLogId,
|
||||
@Param("orgLevel2s") List<String> orgLevel2s);
|
||||
|
||||
long countDetail(@Param("bizMonth") String bizMonth, @Param("importLogId") Long importLogId);
|
||||
long countDetail(@Param("bizMonth") String bizMonth, @Param("importLogId") Long importLogId,
|
||||
@Param("orgLevel2s") List<String> orgLevel2s);
|
||||
|
||||
List<CanteenReportOrgLevel2VO> listOrgLevel2(@Param("bizMonth") String bizMonth,
|
||||
@Param("importLogId") Long importLogId);
|
||||
}
|
||||
+16
@@ -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<CanteenReportRosterEntity> {
|
||||
|
||||
int deletePhysicallyByBizMonth(@Param("bizMonth") String bizMonth);
|
||||
|
||||
int insertBatch(@Param("list") List<CanteenReportRosterEntity> list);
|
||||
}
|
||||
+3
@@ -26,5 +26,8 @@ public interface CanteenReportSwipeMapper extends BaseMapper<CanteenReportSwipeE
|
||||
@Param("dateStart") String dateStart, @Param("dateEnd") String dateEnd,
|
||||
@Param("importLogId") Long importLogId);
|
||||
|
||||
/** 按来源物理删除某月刷卡(如 BOX 包厢) */
|
||||
int deletePhysicallyBySourceAndBizMonth(@Param("source") String source, @Param("bizMonth") String bizMonth);
|
||||
|
||||
int insertBatch(@Param("list") List<CanteenReportSwipeEntity> list);
|
||||
}
|
||||
+12
@@ -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);
|
||||
}
|
||||
+4
@@ -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<CanteenReportShareRowVO> supplierShareReport(String bizMonth);
|
||||
|
||||
/** 厂区核对 + 权重明细 */
|
||||
CanteenReportShareAuditVO supplierShareAudit(String bizMonth);
|
||||
|
||||
List<CanteenReportBusinessRowVO> businessReport(String bizMonth);
|
||||
|
||||
Map<String, Object> summary(String bizMonth);
|
||||
|
||||
+14
-2
@@ -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<CanteenReportImportLogEntity> {
|
||||
|
||||
/**
|
||||
@@ -22,11 +26,19 @@ public interface CanteenReportFaceImportService extends IService<CanteenReportIm
|
||||
* @param dateEnd 可选日期段
|
||||
* @param importLogId 可选,仅汇总某一批导入
|
||||
* @param replaceFace 是否先清空该月已有 FACE 汇总刷卡行
|
||||
* @param orgLevel2s 可选二级组织,非空时只汇总这些组织(如嘉兴地区、湖州地区)
|
||||
*/
|
||||
R aggregateToSwipe(String bizMonth, String dateStart, String dateEnd, Long importLogId, boolean replaceFace);
|
||||
R aggregateToSwipe(String bizMonth, String dateStart, String dateEnd, Long importLogId, boolean replaceFace,
|
||||
List<String> orgLevel2s);
|
||||
|
||||
Page<CanteenReportImportLogEntity> pageLogs(Page page, String bizMonth, String status);
|
||||
|
||||
Page<CanteenReportFaceDetailEntity> pageDetails(Page page, String bizMonth, Long importLogId, String empNo,
|
||||
String payMethod);
|
||||
String payMethod, String deviceName, String empName, String phone, List<String> orgLevel2s);
|
||||
|
||||
/** 与明细分页相同条件的订单金额合计(元) */
|
||||
Map<String, Object> sumDetails(String bizMonth, Long importLogId, String empNo, String payMethod,
|
||||
String deviceName, String empName, String phone, List<String> orgLevel2s);
|
||||
|
||||
List<CanteenReportOrgLevel2VO> listOrgLevel2(String bizMonth, Long importLogId);
|
||||
}
|
||||
+7
@@ -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<CanteenReportFo
|
||||
|
||||
Page<CanteenReportFoodEnergyEntity> pageList(Page page, String bizMonth, String park, String supplier);
|
||||
|
||||
/** 手工新增/修改一行(外协、能耗、耗材、食材等) */
|
||||
R saveOrUpdateRow(CanteenReportFoodEnergyEntity entity);
|
||||
|
||||
R importExcel(List<CanteenReportFoodEnergyExcel> rows, String bizMonth, boolean replaceMonth, BindingResult bindingResult);
|
||||
|
||||
/** 导入「食堂收发存报表(汇总)」列布局(由 /import 自动识别调用) */
|
||||
R importReceiptStock(MultipartFile file, String bizMonth, boolean replaceMonth);
|
||||
|
||||
List<CanteenReportFoodEnergyExcel> exportExcel(String bizMonth);
|
||||
}
|
||||
+19
@@ -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<CanteenReportRosterEntity> {
|
||||
|
||||
Page<CanteenReportRosterEntity> pageList(Page page, String bizMonth, String empNo, String empName, String hrScope);
|
||||
|
||||
R importExcel(MultipartFile file, String bizMonth, boolean replaceMonth);
|
||||
|
||||
/** empNo -> 人事范围;人在花名册但人事范围为空时值为空串(key 仍存在) */
|
||||
Map<String, String> loadHrScopeMap(String bizMonth);
|
||||
}
|
||||
+7
-1
@@ -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<CanteenReportSwipeEntity> {
|
||||
|
||||
Page<CanteenReportSwipeEntity> 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<String, Object> sumAmount(String bizMonth, String park, String incomeType, String costCenter,
|
||||
String empNo, Long importLogId, String remarkTag, String source);
|
||||
|
||||
R importExcel(List<CanteenReportSwipeExcel> rows, String bizMonth, boolean replaceMonth, BindingResult bindingResult);
|
||||
|
||||
|
||||
+139
@@ -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<CanteenReportBoxExcel> 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<CanteenReportSwipeEntity> 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<CanteenReportSwipeEntity> 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;
|
||||
}
|
||||
}
|
||||
+148
-10
@@ -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<CanteenReportFoodEnergyEntity> foods = loadFoods(bizMonth);
|
||||
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
|
||||
Map<String, BigDecimal> 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<String, BigDecimal> parkBoxOther = new HashMap<>();
|
||||
Map<String, BigDecimal> parkSwipeTotal = new HashMap<>();
|
||||
Map<String, BigDecimal> 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<String, BigDecimal> parkAllocable = new LinkedHashMap<>();
|
||||
for (String park : CanteenReportConstants.SHARE_PARKS) {
|
||||
parkAllocable.put(park, nz(parkFoodPool.get(park)).subtract(nz(parkBoxOther.get(park))));
|
||||
}
|
||||
|
||||
List<String> 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<CanteenReportShareWeightRowVO> weightRows = new ArrayList<>();
|
||||
Map<String, BigDecimal> shareParkTotal = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> 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<CanteenReportShareParkCheckVO> 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<CanteenReportBusinessRowVO> businessReport(String bizMonth) {
|
||||
List<CanteenReportSwipeEntity> swipes = loadSwipes(bizMonth);
|
||||
List<CanteenReportShareRowVO> shares = supplierShareReport(bizMonth);
|
||||
List<CanteenReportFoodEnergyEntity> foods = loadFoods(bizMonth);
|
||||
|
||||
Map<String, Map<String, BigDecimal>> 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<String, BigDecimal> foodExpenseYuan = emptyParkMap();
|
||||
// 分摊合计(元)含食材+能耗+外协;经营报表拆开展示时,食材行 = 分摊 − 能耗 − 外协,避免重复
|
||||
Map<String, BigDecimal> shareExpenseYuan = emptyParkMap();
|
||||
for (CanteenReportShareRowVO share : shares) {
|
||||
if (isShareMetaRow(share.getCostCenter())) {
|
||||
continue;
|
||||
}
|
||||
for (Map.Entry<String, BigDecimal> 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<String, BigDecimal> energyYuan = emptyParkMap();
|
||||
Map<String, BigDecimal> outsourceYuan = emptyParkMap();
|
||||
Map<String, BigDecimal> laborYuan = emptyParkMap();
|
||||
Map<String, BigDecimal> 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<String, BigDecimal> 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;
|
||||
}
|
||||
|
||||
|
||||
+339
-75
@@ -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<CanteenRepor
|
||||
private final CanteenReportFaceDetailMapper faceDetailMapper;
|
||||
private final CanteenReportSwipeMapper swipeMapper;
|
||||
private final CanteenReportSettleParkMapper settleParkMapper;
|
||||
private final CanteenReportRosterService rosterService;
|
||||
private final RemoteFileService remoteFileService;
|
||||
|
||||
@Override
|
||||
@@ -71,15 +78,56 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
|
||||
@Override
|
||||
public Page<CanteenReportFaceDetailEntity> pageDetails(Page page, String bizMonth, Long importLogId, String empNo,
|
||||
String payMethod) {
|
||||
String payMethod, String deviceName, String empName, String phone, List<String> orgLevel2s) {
|
||||
List<String> orgs = normalizeOrgLevel2s(orgLevel2s);
|
||||
return faceDetailMapper.selectPage(page, Wrappers.<CanteenReportFaceDetailEntity>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<String, Object> sumDetails(String bizMonth, Long importLogId, String empNo, String payMethod,
|
||||
String deviceName, String empName, String phone, List<String> orgLevel2s) {
|
||||
List<String> orgs = normalizeOrgLevel2s(orgLevel2s);
|
||||
QueryWrapper<CanteenReportFaceDetailEntity> 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<Map<String, Object>> list = faceDetailMapper.selectMaps(qw);
|
||||
if (CollUtil.isEmpty(list) || list.get(0) == null) {
|
||||
Map<String, Object> empty = new HashMap<>();
|
||||
empty.put("totalAmount", BigDecimal.ZERO);
|
||||
empty.put("totalCount", 0);
|
||||
return empty;
|
||||
}
|
||||
Map<String, Object> raw = list.get(0);
|
||||
Map<String, Object> 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<CanteenRepor
|
||||
return R.failed("仅支持 .xlsx / .xls");
|
||||
}
|
||||
|
||||
long fileBytes = file.getSize();
|
||||
long t0 = System.currentTimeMillis();
|
||||
String operator = currentUser();
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
@@ -118,81 +167,122 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
logEntity.setTenantId(tenantId);
|
||||
save(logEntity);
|
||||
|
||||
File temp = null;
|
||||
File temp;
|
||||
try {
|
||||
temp = Files.createTempFile("canteen-face-", ".xlsx").toFile();
|
||||
file.transferTo(temp);
|
||||
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,
|
||||
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) || StrUtil.isBlank(resolvedStart) || StrUtil.isBlank(resolvedEnd)) {
|
||||
faceDetailMapper.deletePhysicallyByImportLogId(logEntity.getId());
|
||||
failLog(logEntity, "无法确定业务月或日期段,请填写业务月与数据起止日期", t0);
|
||||
return R.failed(logEntity.getErrorMsg());
|
||||
}
|
||||
|
||||
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.setStatus("SUCCESS");
|
||||
logEntity.setAggStatus("NONE");
|
||||
logEntity.setCostMs(System.currentTimeMillis() - t0);
|
||||
logEntity.setUpdateTime(LocalDateTime.now());
|
||||
updateById(logEntity);
|
||||
|
||||
return R.ok(logEntity, String.format(
|
||||
"明细导入成功:入库 %d 条,跳过 %d 条,日期 %s~%s,耗时 %d 秒。请点击「统计汇总到刷卡明细」生成报表数据。",
|
||||
acc.getSuccessRows(), acc.getSkipRows(), resolvedStart, resolvedEnd,
|
||||
(logEntity.getCostMs() + 999) / 1000));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("人脸明细导入失败 logId={}", logEntity.getId(), e);
|
||||
failLog(logEntity, "接收上传文件失败: " + e.getMessage(), t0);
|
||||
return R.failed("接收上传文件失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
final File tempFile = temp;
|
||||
final String month = bizMonth;
|
||||
final String ds = dateStart;
|
||||
final String de = dateEnd;
|
||||
final boolean replace = replaceRange;
|
||||
final String fileName = originalName;
|
||||
Thread worker = new Thread(() -> {
|
||||
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<String> orgLevel2s) {
|
||||
if (StrUtil.isBlank(bizMonth) && importLogId == null) {
|
||||
return R.failed("请传入业务月份 bizMonth,或指定某一批导入日志");
|
||||
}
|
||||
@@ -205,6 +295,7 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
// 指定导入批次时:只用 importLogId,日期段不参与过滤(避免日志日期与明细 biz_date 不一致)
|
||||
String useDateStart = importLogId != null ? null : StrUtil.trim(dateStart);
|
||||
String useDateEnd = importLogId != null ? null : StrUtil.trim(dateEnd);
|
||||
List<String> orgs = normalizeOrgLevel2s(orgLevel2s);
|
||||
|
||||
if (importLogId != null) {
|
||||
CanteenReportImportLogEntity logEntity = getById(importLogId);
|
||||
@@ -214,17 +305,23 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
if (StrUtil.isBlank(month)) {
|
||||
month = logEntity.getBizMonth();
|
||||
}
|
||||
long cnt = faceDetailMapper.countDetail(null, importLogId);
|
||||
long cnt = faceDetailMapper.countDetail(null, importLogId, null);
|
||||
if (cnt <= 0) {
|
||||
return R.failed(String.format(
|
||||
"该批导入日志下没有明细(importLogId=%s)。请打开「明细」确认能否查到数据;若明细也是空的,需要重新导入 Excel",
|
||||
importLogId));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(orgs)) {
|
||||
long filtered = faceDetailMapper.countDetail(null, importLogId, orgs);
|
||||
if (filtered <= 0) {
|
||||
return R.failed("所选二级组织下没有明细,请改选「嘉兴地区 / 湖州地区」等后再汇总");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
long cnt = faceDetailMapper.countDetail(month, null);
|
||||
long cnt = faceDetailMapper.countDetail(month, null, null);
|
||||
if (cnt <= 0) {
|
||||
long any = faceDetailMapper.countDetail(null, null);
|
||||
long any = faceDetailMapper.countDetail(null, null, null);
|
||||
if (any <= 0) {
|
||||
return R.failed("库中没有任何人脸明细,请先成功导入 Excel(导入日志里「明细入库」应大于 0)");
|
||||
}
|
||||
@@ -232,12 +329,18 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
"归属月 %s 下没有明细(库中其它月份共有 %d 条)。请核对导入日志的「业务月」,或改选正确归属月再汇总",
|
||||
month, any));
|
||||
}
|
||||
if (CollUtil.isNotEmpty(orgs)) {
|
||||
long filtered = faceDetailMapper.countDetail(month, null, orgs);
|
||||
if (filtered <= 0) {
|
||||
return R.failed("所选二级组织下没有明细,请核对筛选后再汇总");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<CanteenReportFaceAggRow> 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<CanteenRepor
|
||||
}
|
||||
|
||||
Map<String, String> settleParkMap = loadSettleParkMap();
|
||||
Map<String, String> hrScopeMap = rosterService.loadHrScopeMap(month);
|
||||
List<CanteenReportSwipeEntity> saveList = new ArrayList<>(aggRows.size());
|
||||
Map<String, CanteenReportRosterMissVO> 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<CanteenRepor
|
||||
e.setSettleCounter(emptyToNull(row.getDeviceName()));
|
||||
e.setConsumeAmount(row.getAmount());
|
||||
e.setIncomeType(row.getIncomeType());
|
||||
e.setCostCenter(CanteenReportFaceMapper.mapCostCenter(row.getLegalEntity()));
|
||||
String empNo = StrUtil.trim(row.getEmpNo());
|
||||
String costCenter = null;
|
||||
String missReason = null;
|
||||
if (StrUtil.isBlank(empNo)) {
|
||||
missReason = "无工号";
|
||||
}
|
||||
else if (!hrScopeMap.containsKey(empNo)) {
|
||||
missReason = "花名册无此工号";
|
||||
}
|
||||
else if (StrUtil.isBlank(hrScopeMap.get(empNo))) {
|
||||
missReason = "花名册无人事范围";
|
||||
}
|
||||
else {
|
||||
costCenter = hrScopeMap.get(empNo);
|
||||
}
|
||||
if (missReason != null) {
|
||||
unmatchedRoster++;
|
||||
costCenter = CanteenReportFaceMapper.mapCostCenter(row.getLegalEntity());
|
||||
e.setRemarkTag(CanteenReportConstants.REMARK_ROSTER_MISS);
|
||||
String key = StrUtil.isNotBlank(empNo) ? empNo
|
||||
: ("名:" + StrUtil.blankToDefault(row.getEmpName(), "") + "|法:"
|
||||
+ StrUtil.blankToDefault(row.getLegalEntity(), ""));
|
||||
CanteenReportRosterMissVO miss = missByKey.get(key);
|
||||
if (miss == null) {
|
||||
miss = new CanteenReportRosterMissVO();
|
||||
miss.setEmpNo(emptyToNull(empNo));
|
||||
miss.setEmpName(emptyToNull(row.getEmpName()));
|
||||
miss.setLegalEntity(emptyToNull(row.getLegalEntity()));
|
||||
miss.setCostCenter(costCenter);
|
||||
miss.setReason(missReason);
|
||||
miss.setRowCount(1);
|
||||
missByKey.put(key, miss);
|
||||
}
|
||||
else {
|
||||
miss.setRowCount(miss.getRowCount() + 1);
|
||||
}
|
||||
}
|
||||
e.setCostCenter(costCenter);
|
||||
String park = settleParkMap.get(StrUtil.trim(row.getDeviceName()));
|
||||
if (StrUtil.isBlank(park) && StrUtil.isNotBlank(row.getDeviceName())) {
|
||||
unmatchedPark++;
|
||||
@@ -323,10 +466,52 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
}
|
||||
|
||||
long cost = System.currentTimeMillis() - t0;
|
||||
String tip = unmatchedPark > 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<com.bsd.pjcloud.canteen.dto.report.CanteenReportOrgLevel2VO> listOrgLevel2(String bizMonth,
|
||||
Long importLogId) {
|
||||
return faceDetailMapper.listOrgLevel2(StrUtil.trim(bizMonth), importLogId);
|
||||
}
|
||||
|
||||
private static List<String> normalizeOrgLevel2s(List<String> orgLevel2s) {
|
||||
if (CollUtil.isEmpty(orgLevel2s)) {
|
||||
return List.of();
|
||||
}
|
||||
List<String> 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<String, String> loadSettleParkMap() {
|
||||
@@ -376,6 +561,16 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
updateById(logEntity);
|
||||
}
|
||||
|
||||
private static String lastDayOfMonth(String yyyyMm) {
|
||||
try {
|
||||
java.time.YearMonth ym = java.time.YearMonth.parse(yyyyMm);
|
||||
return ym.atEndOfMonth().toString();
|
||||
}
|
||||
catch (Exception e) {
|
||||
return yyyyMm + "-28";
|
||||
}
|
||||
}
|
||||
|
||||
private static String currentUser() {
|
||||
try {
|
||||
if (SecurityUtils.getUser() != null) {
|
||||
@@ -390,6 +585,8 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
|
||||
static final class FaceDetailAccumulator implements ReadListener<CanteenReportFaceExcel> {
|
||||
|
||||
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<CanteenRepor
|
||||
private final int batchSize;
|
||||
|
||||
private final List<CanteenReportFaceDetailEntity> buffer = new ArrayList<>();
|
||||
private final Map<String, Integer> skipReasonCount = new LinkedHashMap<>();
|
||||
private final List<String> skipSamples = new ArrayList<>();
|
||||
private int totalRows;
|
||||
private int successRows;
|
||||
private int skipRows;
|
||||
@@ -420,12 +619,20 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
@Override
|
||||
public void invoke(CanteenReportFaceExcel data, AnalysisContext context) {
|
||||
totalRows++;
|
||||
int excelRow = context.readRowHolder() != null ? context.readRowHolder().getRowIndex() + 1 : totalRows + 1;
|
||||
CanteenReportFaceDetailEntity e = CanteenReportFaceMapper.toDetail(data, bizMonthParam, importLogId);
|
||||
if (e == null || StrUtil.isBlank(e.getBizDate()) || StrUtil.isBlank(e.getBizMonth())) {
|
||||
skipRows++;
|
||||
if (e == null) {
|
||||
recordSkip(excelRow, "空行(无订单号/姓名/人员编号/金额/时间)", data);
|
||||
return;
|
||||
}
|
||||
trackDate(e.getBizDate());
|
||||
if (StrUtil.isBlank(e.getBizMonth())) {
|
||||
recordSkip(excelRow, "无法确定报表归属月(请填写归属月,或保证时间列可解析)", data);
|
||||
return;
|
||||
}
|
||||
// 有归属月即可入库;无业务日仍保留(汇总按月不依赖单日)
|
||||
if (StrUtil.isNotBlank(e.getBizDate())) {
|
||||
trackDate(e.getBizDate());
|
||||
}
|
||||
if (resolvedBizMonth == null) {
|
||||
resolvedBizMonth = e.getBizMonth();
|
||||
}
|
||||
@@ -442,6 +649,63 @@ public class CanteenReportFaceImportServiceImpl extends ServiceImpl<CanteenRepor
|
||||
}
|
||||
}
|
||||
|
||||
private void recordSkip(int excelRow, String reason, CanteenReportFaceExcel data) {
|
||||
skipRows++;
|
||||
skipReasonCount.merge(reason, 1, Integer::sum);
|
||||
if (skipSamples.size() < SKIP_SAMPLE_LIMIT) {
|
||||
String hint = briefRow(data);
|
||||
skipSamples.add("Excel第" + excelRow + "行:" + reason + (StrUtil.isNotBlank(hint) ? ";" + hint : ""));
|
||||
}
|
||||
}
|
||||
|
||||
private static String briefRow(CanteenReportFaceExcel data) {
|
||||
if (data == null) {
|
||||
return "";
|
||||
}
|
||||
List<String> 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<String, Integer> 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();
|
||||
|
||||
+161
-13
@@ -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<CanteenReportFoodEnergyExcel> 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<CanteenReportReceiptStockExcel> 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<CanteenReportFoodEnergyEntity> 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<CanteenReportFoodEnergyEntity> 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<CanteenReportFoodEnergyEntity> 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())));
|
||||
|
||||
+129
@@ -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<CanteenReportRosterMapper, CanteenReportRosterEntity>
|
||||
implements CanteenReportRosterService {
|
||||
|
||||
private static final int BATCH = 1000;
|
||||
|
||||
@Override
|
||||
public Page<CanteenReportRosterEntity> pageList(Page page, String bizMonth, String empNo, String empName,
|
||||
String hrScope) {
|
||||
return page(page, Wrappers.<CanteenReportRosterEntity>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<CanteenReportRosterExcel> 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<CanteenReportRosterEntity> 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<CanteenReportRosterEntity> 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<String, String> loadHrScopeMap(String bizMonth) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
List<CanteenReportRosterEntity> list = list(Wrappers.<CanteenReportRosterEntity>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;
|
||||
}
|
||||
}
|
||||
+40
-1
@@ -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<CanteenReportSwip
|
||||
|
||||
@Override
|
||||
public Page<CanteenReportSwipeEntity> 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.<CanteenReportSwipeEntity>lambdaQuery()
|
||||
.eq(StrUtil.isNotBlank(bizMonth), CanteenReportSwipeEntity::getBizMonth, bizMonth)
|
||||
.eq(StrUtil.isNotBlank(park), CanteenReportSwipeEntity::getPark, park)
|
||||
@@ -47,9 +50,44 @@ public class CanteenReportSwipeServiceImpl extends ServiceImpl<CanteenReportSwip
|
||||
.like(StrUtil.isNotBlank(costCenter), CanteenReportSwipeEntity::getCostCenter, costCenter)
|
||||
.like(StrUtil.isNotBlank(empNo), CanteenReportSwipeEntity::getEmpNo, empNo)
|
||||
.eq(importLogId != null, CanteenReportSwipeEntity::getImportLogId, importLogId)
|
||||
.eq(StrUtil.isNotBlank(remarkTag), CanteenReportSwipeEntity::getRemarkTag, remarkTag)
|
||||
.eq(StrUtil.isNotBlank(source), CanteenReportSwipeEntity::getSource, source)
|
||||
.orderByDesc(CanteenReportSwipeEntity::getId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> sumAmount(String bizMonth, String park, String incomeType, String costCenter,
|
||||
String empNo, Long importLogId, String remarkTag, String source) {
|
||||
QueryWrapper<CanteenReportSwipeEntity> 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<String, Object> map = getMap(qw);
|
||||
if (map == null) {
|
||||
map = new HashMap<>();
|
||||
map.put("totalAmount", BigDecimal.ZERO);
|
||||
map.put("totalCount", 0);
|
||||
}
|
||||
QueryWrapper<CanteenReportSwipeEntity> 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<CanteenReportSwipeEntity> 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<CanteenReportSwipeExcel> rows, String bizMonth, boolean replaceMonth,
|
||||
@@ -95,6 +133,7 @@ public class CanteenReportSwipeServiceImpl extends ServiceImpl<CanteenReportSwip
|
||||
CanteenReportSwipeEntity e = new CanteenReportSwipeEntity();
|
||||
BeanUtils.copyProperties(row, e);
|
||||
e.setBizMonth(bizMonth);
|
||||
e.setSource("EXCEL");
|
||||
if (e.getConsumeAmount() == null) {
|
||||
e.setConsumeAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
+50
@@ -3,6 +3,8 @@ package com.bsd.pjcloud.canteen.util;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelReader;
|
||||
import com.alibaba.excel.read.metadata.ReadSheet;
|
||||
import com.bsd.pjcloud.canteen.excel.ExcelYuanConverter;
|
||||
import com.bsd.pjcloud.canteen.excel.ExcelYuanStringConverter;
|
||||
import com.bsd.pjcloud.common.excel.converters.LocalDateStringConverter;
|
||||
import com.bsd.pjcloud.common.excel.converters.LocalDateTimeStringConverter;
|
||||
import com.bsd.pjcloud.common.excel.converters.LocalTimeStringConverter;
|
||||
@@ -10,6 +12,11 @@ import com.bsd.pjcloud.common.excel.converters.LongStringConverter;
|
||||
import com.bsd.pjcloud.common.excel.converters.StringArrayConverter;
|
||||
import com.bsd.pjcloud.common.excel.handler.DefaultAnalysisEventListener;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -32,12 +39,55 @@ public class CanteenReportExcelUtils {
|
||||
.registerConverter(LocalDateTimeStringConverter.INSTANCE)
|
||||
.registerConverter(LongStringConverter.INSTANCE)
|
||||
.registerConverter(StringArrayConverter.INSTANCE)
|
||||
.registerConverter(new ExcelYuanConverter())
|
||||
.registerConverter(new ExcelYuanStringConverter())
|
||||
.sheet(sheetNo)
|
||||
.headRowNumber(1)
|
||||
.doRead();
|
||||
return (List<T>) 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;
|
||||
|
||||
+27
@@ -86,6 +86,12 @@
|
||||
<if test="importLogId == null and dateEnd != null and dateEnd != ''">
|
||||
AND (biz_date IS NULL OR biz_date <= #{dateEnd})
|
||||
</if>
|
||||
<if test="orgLevel2s != null and orgLevel2s.size() > 0">
|
||||
AND org_level2 IN
|
||||
<foreach collection="orgLevel2s" item="org" open="(" separator="," close=")">
|
||||
#{org}
|
||||
</foreach>
|
||||
</if>
|
||||
GROUP BY
|
||||
IFNULL(emp_no, ''),
|
||||
IFNULL(emp_name, ''),
|
||||
@@ -114,5 +120,26 @@
|
||||
<if test="bizMonth != null and bizMonth != ''">
|
||||
AND biz_month = #{bizMonth}
|
||||
</if>
|
||||
<if test="orgLevel2s != null and orgLevel2s.size() > 0">
|
||||
AND org_level2 IN
|
||||
<foreach collection="orgLevel2s" item="org" open="(" separator="," close=")">
|
||||
#{org}
|
||||
</foreach>
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="listOrgLevel2" resultType="com.bsd.pjcloud.canteen.dto.report.CanteenReportOrgLevel2VO">
|
||||
SELECT IFNULL(org_level2, '') AS name, COUNT(1) AS cnt
|
||||
FROM canteen_report_face_detail
|
||||
WHERE IFNULL(del_flag, '0') = '0'
|
||||
AND org_level2 IS NOT NULL AND TRIM(org_level2) <> ''
|
||||
<if test="importLogId != null">
|
||||
AND import_log_id = #{importLogId}
|
||||
</if>
|
||||
<if test="importLogId == null and bizMonth != null and bizMonth != ''">
|
||||
AND biz_month = #{bizMonth}
|
||||
</if>
|
||||
GROUP BY org_level2
|
||||
ORDER BY cnt DESC
|
||||
</select>
|
||||
</mapper>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.bsd.pjcloud.canteen.mapper.CanteenReportRosterMapper">
|
||||
|
||||
<delete id="deletePhysicallyByBizMonth">
|
||||
DELETE FROM canteen_report_roster WHERE biz_month = #{bizMonth}
|
||||
</delete>
|
||||
|
||||
<insert id="insertBatch">
|
||||
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
|
||||
<foreach collection="list" item="e" separator=",">
|
||||
(
|
||||
#{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}
|
||||
)
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
+4
@@ -31,6 +31,10 @@
|
||||
AND (import_log_id IS NULL OR import_log_id <> #{importLogId})
|
||||
</delete>
|
||||
|
||||
<delete id="deletePhysicallyBySourceAndBizMonth">
|
||||
DELETE FROM canteen_report_swipe WHERE source = #{source} AND biz_month = #{bizMonth}
|
||||
</delete>
|
||||
|
||||
<insert id="insertBatch">
|
||||
INSERT INTO canteen_report_swipe (
|
||||
id, biz_month, biz_date, emp_no, emp_name, legal_entity, dept_name,
|
||||
|
||||
Reference in new issue
Block a user