From f2c47f86bd36c3d4c86d728c98d283f475877d73 Mon Sep 17 00:00:00 2001 From: "zangch@mesnac.com" Date: Tue, 16 Sep 2025 17:10:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(dms):=20=E8=AE=BE=E5=A4=87=E6=95=85?= =?UTF-8?q?=E9=9A=9C=E5=A4=84=E7=90=86=E8=AE=B0=E5=BD=95=E8=A1=A8=EF=BC=88?= =?UTF-8?q?=E8=AF=A6=E7=BB=86=E8=BF=BD=E6=BA=AF=EF=BC=89=E6=8A=A5=E8=A1=A8?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DmsReportController、DmsReportMapper、DmsReportServiceImpl等类 - 实现设备故障处理记录的查询和导出功能 - 添加 FaultTraceReportBo、FaultTraceReportVo 等数据传输对象 - 编写复杂的 SQL 查询语句,计算产量损失和工单延误数 --- .../dms/controller/DmsReportController.java | 42 ++ .../dms/domain/bo/FaultTraceReportBo.java | 48 ++ .../dms/domain/vo/FaultTraceReportVo.java | 59 +++ .../dromara/dms/mapper/DmsReportMapper.java | 16 + .../dms/service/IDmsReportService.java | 17 + .../service/impl/DmsReportServiceImpl.java | 31 ++ .../resources/mapper/dms/DmsReportMapper.xml | 414 ++++++++++++++++++ .../mes/utils/DynamicExcelExportUtil.java | 207 +++++++++ 8 files changed, 834 insertions(+) create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportController.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/FaultTraceReportBo.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/FaultTraceReportVo.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportMapper.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportService.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportServiceImpl.java create mode 100644 ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportMapper.xml create mode 100644 ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/utils/DynamicExcelExportUtil.java diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportController.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportController.java new file mode 100644 index 00000000..ab0ecf7b --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/controller/DmsReportController.java @@ -0,0 +1,42 @@ +package org.dromara.dms.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.common.core.domain.R; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.dms.domain.bo.FaultTraceReportBo; +import org.dromara.dms.domain.vo.FaultTraceReportVo; +import org.dromara.dms.service.IDmsReportService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/report/faultTrace") +public class DmsReportController { + + private final IDmsReportService dmsReportService; + + @SaCheckPermission("dms:report:faultTrace:list") + @GetMapping("/list") + public TableDataInfo list(FaultTraceReportBo bo, PageQuery pageQuery) { + return dmsReportService.queryFaultTrace(bo, pageQuery); + } + + @SaCheckPermission("dms:report:faultTrace:export") + @Log(title = "设备故障处理记录表(详细追溯)", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(FaultTraceReportBo bo, HttpServletResponse response) { + List list = dmsReportService.exportFaultTrace(bo); + ExcelUtil.exportExcel(list, "设备故障处理记录表(详细追溯)", FaultTraceReportVo.class, response); + } +} \ No newline at end of file diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/FaultTraceReportBo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/FaultTraceReportBo.java new file mode 100644 index 00000000..d2a10650 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/bo/FaultTraceReportBo.java @@ -0,0 +1,48 @@ +package org.dromara.dms.domain.bo; + +import lombok.Data; + +import java.util.Date; +import java.util.Map; + +/** + * 设备故障处理记录表(详细追溯)查询条件 + */ +@Data +public class FaultTraceReportBo { + + /** + * 开始日期(基于 apply_time) + */ + private Date startDate; + + /** + * 结束日期(基于 apply_time) + */ + private Date endDate; + + /** + * 设备模型ID(dms_device_mode.device_mode_id) + */ + private Long deviceModeId; + + /** + * 设备编号(模糊查询) + */ + private String machineCode; + + /** + * 故障类型(1 外部故障、2 内部故障、其他) + */ + private String faultType; + + /** + * 设备ID + */ + private Long machineId; + + /** + * 预留扩展参数 + */ + private Map params; +} \ No newline at end of file diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/FaultTraceReportVo.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/FaultTraceReportVo.java new file mode 100644 index 00000000..dfd939f7 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/domain/vo/FaultTraceReportVo.java @@ -0,0 +1,59 @@ +package org.dromara.dms.domain.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 设备故障处理记录表(详细追溯)结果 + */ +@Data +public class FaultTraceReportVo { + + @ExcelProperty(value = "统计周期") + private String periodLabel; + + private Date periodStart; + + @ExcelProperty(value = "设备类型") + private String deviceType; + + @ExcelProperty(value = "设备编号") + private String machineCode; + + @ExcelProperty(value = "故障类型") + @ExcelDictFormat(dictType = "activity_fault_type") + private String faultType; + + + @ExcelProperty(value = "故障次数") + private Long faultCount; + + @ExcelProperty(value = "总停机时长(小时)") + private BigDecimal totalDowntimeHours; + + @ExcelProperty(value = "MTBF(小时)") + private BigDecimal mtbfHours; + + @ExcelProperty(value = "MTTR(分钟)") + private BigDecimal mttrMinutes; + + @ExcelProperty(value = "产量损失(件)") + private BigDecimal outputLoss; + + @ExcelProperty(value = "工单延误数") + private Long delayCount; + + @ExcelProperty(value = "高频故障原因") + private String topCause; + + @ExcelProperty(value = "改善措施") + private String topResolution; + + @ExcelProperty(value = "措施实施时间") + private Date topResolutionTime; + +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportMapper.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportMapper.java new file mode 100644 index 00000000..fa5c09b0 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/mapper/DmsReportMapper.java @@ -0,0 +1,16 @@ +package org.dromara.dms.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; +import org.dromara.dms.domain.bo.FaultTraceReportBo; +import org.dromara.dms.domain.vo.FaultTraceReportVo; + +import java.util.List; + +public interface DmsReportMapper { + + IPage selectFaultTracePage(Page page, @Param("bo") FaultTraceReportBo bo); + + List selectFaultTraceList(@Param("bo") FaultTraceReportBo bo); +} \ No newline at end of file diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportService.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportService.java new file mode 100644 index 00000000..ed6e4918 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/IDmsReportService.java @@ -0,0 +1,17 @@ +package org.dromara.dms.service; + + +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.dms.domain.bo.FaultTraceReportBo; +import org.dromara.dms.domain.vo.FaultTraceReportVo; + +import java.util.List; + +public interface IDmsReportService { + + public TableDataInfo queryFaultTrace(FaultTraceReportBo bo, PageQuery pageQuery); + + public List exportFaultTrace(FaultTraceReportBo bo); + +} diff --git a/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportServiceImpl.java b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportServiceImpl.java new file mode 100644 index 00000000..e7a06355 --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/java/org/dromara/dms/service/impl/DmsReportServiceImpl.java @@ -0,0 +1,31 @@ +package org.dromara.dms.service.impl; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.RequiredArgsConstructor; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.dms.domain.bo.FaultTraceReportBo; +import org.dromara.dms.domain.vo.FaultTraceReportVo; +import org.dromara.dms.mapper.DmsReportMapper; +import org.dromara.dms.service.IDmsReportService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@RequiredArgsConstructor +@Service +public class DmsReportServiceImpl implements IDmsReportService { + + private final DmsReportMapper dmsReportMapper; + + @Override + public TableDataInfo queryFaultTrace(FaultTraceReportBo bo, PageQuery pageQuery) { + IPage page = dmsReportMapper.selectFaultTracePage(pageQuery.build(), bo); + return TableDataInfo.build(page); + } + + @Override + public List exportFaultTrace(FaultTraceReportBo bo) { + return dmsReportMapper.selectFaultTraceList(bo); + } +} diff --git a/ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportMapper.xml b/ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportMapper.xml new file mode 100644 index 00000000..50209b2c --- /dev/null +++ b/ruoyi-modules/hwmom-dms/src/main/resources/mapper/dms/DmsReportMapper.xml @@ -0,0 +1,414 @@ + + + + + + + + + + select + g.tenant_id, + g.period_start, + g.period_label, + g.period_hours, + g.device_type, + g.machine_code, + g.fault_type, + count(1) as fault_count, + sum(g.downtime_hours) as total_downtime_hours, + sum(case when g.real_end_time is not null and g.require_end_time is not null and g.real_end_time > g.require_end_time then 1 else 0 end) as delay_count, + sum(g.output_loss_qty) as total_output_loss, + sum(g.order_delay_count) as total_order_delay_count + from ( + select + fb.tenant_id as tenant_id, + fb.repair_instance_id, + fb.machine_id, + fb.machine_code, + fb.device_type, + fb.apply_time, + fb.real_begin_time, + fb.real_end_time, + fb.require_end_time, + -- 周起始 + dateadd(week, datediff(week, datefromparts(year(fb.apply_time), month(fb.apply_time), 1), fb.apply_time), + datefromparts(year(fb.apply_time), month(fb.apply_time), 1)) as period_start, + cast(year(fb.apply_time) as varchar(4)) + N' 年 ' + + cast(month(fb.apply_time) as varchar(2)) + N' 月第 ' + + cast(1 + datediff(week, datefromparts(year(fb.apply_time), month(fb.apply_time), 1), fb.apply_time) as varchar(2)) + N' 周' as period_label, + datediff(hour, + dateadd(week, datediff(week, datefromparts(year(fb.apply_time), month(fb.apply_time), 1), fb.apply_time), + datefromparts(year(fb.apply_time), month(fb.apply_time), 1)), + dateadd(day, 7, + dateadd(week, datediff(week, datefromparts(year(fb.apply_time), month(fb.apply_time), 1), fb.apply_time), + datefromparts(year(fb.apply_time), month(fb.apply_time), 1))) + ) as period_hours, + -- 故障类型:直接使用原始值 + coalesce(af.fault_type, '') as fault_type, + cast(case + when fb.real_begin_time is not null and fb.real_end_time is not null and fb.real_end_time >= fb.real_begin_time + then datediff(second, fb.real_begin_time, fb.real_end_time) / 3600.0 + else 0.0 + end as decimal(18,6)) as downtime_hours, + -- 产量损失:停机秒数 / 标准秒/件 + cast(case + when fb.real_begin_time is not null and fb.real_end_time is not null and fb.real_end_time >= fb.real_begin_time + and fb.standard_seconds_per_unit is not null and fb.standard_seconds_per_unit > 0 + then (datediff(second, fb.real_begin_time, fb.real_end_time) / fb.standard_seconds_per_unit) + else 0.0 + end as decimal(18,2)) as output_loss_qty, + -- 工单延误数:故障窗与计划窗时间重叠、同机台可执行工序、且未完成的工单数 + ( + select count(distinct p.plan_id) + from prod_plan_info p + join prod_base_machine_process mp2 + on mp2.process_id = p.process_id + where mp2.machine_id = fb.machine_id + and p.plan_status <> '3' + and p.plan_begin_time is not null + and p.plan_end_time is not null + and ( + (p.plan_begin_time between fb.real_begin_time and fb.real_end_time) or + (p.plan_end_time between fb.real_begin_time and fb.real_end_time) or + (fb.real_begin_time between p.plan_begin_time and p.plan_end_time) + ) + ) as order_delay_count + from ( + select + fi.tenant_id as tenant_id, + fi.repair_instance_id, + fi.machine_id, + fi.apply_time, + fi.real_begin_time, + fi.real_end_time, + fi.require_end_time, + m.machine_code, + dm.device_mode_name as device_type, + std.standard_seconds_per_unit + from dms_bills_fault_instance fi + join prod_base_machine_info m + on m.machine_id = fi.machine_id + left join dms_device_mode dm + on dm.device_mode_id = m.device_mode_id + left join ( + select + mp.tenant_id, + mp.machine_id, + coalesce( + nullif(min(pi.production_time), 0), + nullif(min(pi.theoretical_cycle_time), 0), + 60 + ) as standard_seconds_per_unit + from prod_base_machine_process mp + join prod_base_process_info pi + on pi.process_id = mp.process_id + where pi.production_time > 0 or pi.theoretical_cycle_time > 0 + group by mp.tenant_id, mp.machine_id + ) std + on std.machine_id = fi.machine_id + and std.tenant_id = fi.tenant_id + where fi.active_flag = '1' + and fi.apply_time is not null + + and fi.apply_time >= #{bo.startDate} + + + and fi.apply_time < #{bo.endDate} + + + and fi.machine_id = #{bo.machineId} + + + and m.machine_code like concat('%', #{bo.machineCode}, '%') + + + and m.device_mode_id = #{bo.deviceModeId} + + ) fb + left join ( + select repair_instance_id, fault_type + from ( + select repair_instance_id, fault_type, start_time, + row_number() over(partition by repair_instance_id order by start_time asc, instance_activity_id asc) as rn + from dms_fault_instance_activity + ) t + where t.rn = 1 + ) af + on af.repair_instance_id = fb.repair_instance_id + + where coalesce(af.fault_type, '') = #{bo.faultType} + + ) g + group by + g.tenant_id, + g.period_start, g.period_label, g.period_hours, + g.device_type, g.machine_code, g.fault_type + + + + + with agg as ( + + ), + top_cause as ( + select period_start, device_type, machine_code, fault_type, top_cause, tenant_id + from ( + select + fi.tenant_id as tenant_id, + dateadd(week, datediff(week, datefromparts(year(fi.apply_time), month(fi.apply_time), 1), fi.apply_time), + datefromparts(year(fi.apply_time), month(fi.apply_time), 1)) as period_start, + coalesce(dm.device_mode_name, N'') as device_type, + m.machine_code, + coalesce(af.fault_type, '') as fault_type, + a.fault_description as top_cause, + count(*) as cnt, + row_number() over( + partition by + fi.tenant_id, + dateadd(week, datediff(week, datefromparts(year(fi.apply_time), month(fi.apply_time), 1), fi.apply_time), + datefromparts(year(fi.apply_time), month(fi.apply_time), 1)), + coalesce(dm.device_mode_name, N''), + m.machine_code, + coalesce(af.fault_type, '') + order by count(*) desc, a.fault_description + ) as rn + from dms_fault_instance_activity a + join dms_bills_fault_instance fi + on fi.repair_instance_id = a.repair_instance_id + join prod_base_machine_info m + on m.machine_id = fi.machine_id + left join dms_device_mode dm + on dm.device_mode_id = m.device_mode_id + left join ( + select repair_instance_id, fault_type + from ( + select repair_instance_id, fault_type, start_time, + row_number() over(partition by repair_instance_id order by start_time asc, instance_activity_id asc) as rn + from dms_fault_instance_activity + ) t2 + where t2.rn = 1 + ) af + on af.repair_instance_id = fi.repair_instance_id + where fi.active_flag = '1' + and fi.apply_time is not null + and a.fault_description is not null and ltrim(rtrim(a.fault_description)) <> '' + + and fi.apply_time >= #{bo.startDate} + + + and fi.apply_time < #{bo.endDate} + + + and fi.machine_id = #{bo.machineId} + + + and m.machine_code like concat('%', #{bo.machineCode}, '%') + + + and m.device_mode_id = #{bo.deviceModeId} + + + and coalesce(af.fault_type, '') = #{bo.faultType} + + group by + fi.tenant_id, + dateadd(week, datediff(week, datefromparts(year(fi.apply_time), month(fi.apply_time), 1), fi.apply_time), + datefromparts(year(fi.apply_time), month(fi.apply_time), 1)), + coalesce(dm.device_mode_name, N''), + m.machine_code, + coalesce(af.fault_type, ''), + a.fault_description + ) c + where c.rn = 1 + ), + top_resolution as ( + select * + from ( + select + fi.tenant_id as tenant_id, + dateadd(week, datediff(week, datefromparts(year(fi.apply_time), month(fi.apply_time), 1), fi.apply_time), + datefromparts(year(fi.apply_time), month(fi.apply_time), 1)) as period_start, + coalesce(dm.device_mode_name, N'') as device_type, + m.machine_code, + coalesce(af.fault_type, '') as fault_type, + a2.process_handle_resolution as top_resolution, + a2.handle_time as top_resolution_time, + row_number() over( + partition by + fi.tenant_id, + dateadd(week, datediff(week, datefromparts(year(fi.apply_time), month(fi.apply_time), 1), fi.apply_time), + datefromparts(year(fi.apply_time), month(fi.apply_time), 1)), + coalesce(dm.device_mode_name, N''), + m.machine_code, + coalesce(af.fault_type, '') + order by a2.handle_time desc, a2.instance_activity_id desc + ) as rn + from dms_fault_instance_activity a2 + join dms_bills_fault_instance fi + on fi.repair_instance_id = a2.repair_instance_id + join prod_base_machine_info m + on m.machine_id = fi.machine_id + left join dms_device_mode dm + on dm.device_mode_id = m.device_mode_id + left join ( + select repair_instance_id, fault_type + from ( + select repair_instance_id, fault_type, start_time, + row_number() over(partition by repair_instance_id order by start_time asc, instance_activity_id asc) as rn + from dms_fault_instance_activity + ) t3 + where t3.rn = 1 + ) af + on af.repair_instance_id = fi.repair_instance_id + where fi.active_flag = '1' + and fi.apply_time is not null + and a2.process_handle_resolution is not null and ltrim(rtrim(a2.process_handle_resolution)) <> '' + + and fi.apply_time >= #{bo.startDate} + + + and fi.apply_time < #{bo.endDate} + + + and fi.machine_id = #{bo.machineId} + + + and m.machine_code like concat('%', #{bo.machineCode}, '%') + + + and m.device_mode_id = #{bo.deviceModeId} + + + and coalesce(af.fault_type, '') = #{bo.faultType} + + ) z where z.rn = 1 + ) + select + a.period_label, + a.period_start, + a.device_type, + a.machine_code, + a.fault_type, + a.fault_count, + cast(a.total_downtime_hours as decimal(18,2)) as total_downtime_hours, + cast(a.period_hours / nullif(a.fault_count, 0) as decimal(18,2)) as mtbf_hours, + cast(a.total_downtime_hours * 60.0 / nullif(a.fault_count, 0) as decimal(18,2)) as mttr_minutes, + cast(a.total_output_loss as decimal(18,2)) as output_loss, + a.total_order_delay_count as delay_count, + coalesce(ca.top_cause, N'') as top_cause, + coalesce(tr.top_resolution, N'') as top_resolution, + tr.top_resolution_time + from agg a + left join top_cause ca + on ca.period_start = a.period_start + and ca.device_type = a.device_type + and ca.machine_code = a.machine_code + and ca.fault_type = a.fault_type + left join top_resolution tr + on tr.period_start = a.period_start + and tr.device_type = a.device_type + and tr.machine_code = a.machine_code + and tr.fault_type = a.fault_type + order by a.period_start desc, a.machine_code, a.fault_type + + + + + + + + + diff --git a/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/utils/DynamicExcelExportUtil.java b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/utils/DynamicExcelExportUtil.java new file mode 100644 index 00000000..8b977115 --- /dev/null +++ b/ruoyi-modules/hwmom-mes/src/main/java/org/dromara/mes/utils/DynamicExcelExportUtil.java @@ -0,0 +1,207 @@ +package org.dromara.mes.utils; + +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.WriteTable; +import com.alibaba.excel.write.metadata.style.WriteCellStyle; +import com.alibaba.excel.write.metadata.style.WriteFont; +import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.dromara.mes.domain.vo.ProdProductPlanDetailStatisticsVo; + +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; + +/** + * 动态表头Excel导出工具类 + * + * @author Yinq + * @date 2025-03-05 + */ +public class DynamicExcelExportUtil { + + /** + * 导出按机台班次统计的Excel + * + * @param response HTTP响应 + * @param fileName 文件名 + * @param dataList 统计数据列表 + * @param shiftNames 动态班次名称列表 + * @throws IOException IO异常 + */ + public static void exportMachineShiftStatistics(HttpServletResponse response, String fileName, + List dataList, + List shiftNames) throws IOException { + + // 设置响应头 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx"); + + // 构建动态表头 + List> headers = buildDynamicHeaders(shiftNames); + + // 构建数据行 + List> dataRows = buildDataRows(dataList, shiftNames); + + // 创建样式策略 + HorizontalCellStyleStrategy styleStrategy = createStyleStrategy(); + + // 写入Excel + EasyExcel.write(response.getOutputStream()) + .head(headers) + .registerWriteHandler(styleStrategy) + .sheet("机台班次统计") + .doWrite(dataRows); + } + + /** + * 构建动态表头 + * + * @param shiftNames 班次名称列表 + * @return 表头列表 + */ + private static List> buildDynamicHeaders(List shiftNames) { + List> headers = new ArrayList<>(); + + // 固定列:序号、机台 + headers.add(Arrays.asList("序号")); + headers.add(Arrays.asList("机台")); + + // 动态班次列 + if (shiftNames != null && !shiftNames.isEmpty()) { + for (String shiftName : shiftNames) { + headers.add(Arrays.asList(shiftName)); + } + } else { + // 默认早、夜班次 + headers.add(Arrays.asList("早")); + headers.add(Arrays.asList("夜")); + } + + // 固定列:合计、单位 + headers.add(Arrays.asList("合计")); + headers.add(Arrays.asList("单位")); + + return headers; + } + + /** + * 构建数据行 + * + * @param dataList 统计数据列表 + * @param shiftNames 班次名称列表 + * @return 数据行列表 + */ + private static List> buildDataRows(List dataList, + List shiftNames) { + List> dataRows = new ArrayList<>(); + + // 数据行 + for (ProdProductPlanDetailStatisticsVo vo : dataList) { + List row = new ArrayList<>(); + + // 序号 + row.add(vo.getRowNumber()); + + // 机台 + row.add(vo.getMachineCode()); + + // 动态班次数据 + if (shiftNames != null && !shiftNames.isEmpty()) { + for (String shiftName : shiftNames) { + BigDecimal amount = vo.getShiftAmountMap() != null ? + vo.getShiftAmountMap().getOrDefault(shiftName, BigDecimal.ZERO) : BigDecimal.ZERO; + row.add(amount); + } + } else { + // 默认早、夜班次 + row.add(vo.getMorningShiftAmount() != null ? vo.getMorningShiftAmount() : BigDecimal.ZERO); + row.add(vo.getNightShiftAmount() != null ? vo.getNightShiftAmount() : BigDecimal.ZERO); + } + + // 合计 + row.add(vo.getTotalAmount() != null ? vo.getTotalAmount() : BigDecimal.ZERO); + + // 单位 + row.add(vo.getUnitName() != null ? vo.getUnitName() : ""); + + dataRows.add(row); + } + + // 合计行 + if (!dataList.isEmpty()) { + List totalRow = new ArrayList<>(); + totalRow.add(""); // 序号列空白 + totalRow.add("合计"); // 机台列显示"合计" + + // 计算各班次合计 + if (shiftNames != null && !shiftNames.isEmpty()) { + for (String shiftName : shiftNames) { + BigDecimal shiftTotal = dataList.stream() + .filter(vo -> vo.getShiftAmountMap() != null) + .map(vo -> vo.getShiftAmountMap().getOrDefault(shiftName, BigDecimal.ZERO)) + .reduce(BigDecimal.ZERO, BigDecimal::add); + totalRow.add(shiftTotal); + } + } else { + // 默认早、夜班次合计 + BigDecimal morningTotal = dataList.stream() + .map(vo -> vo.getMorningShiftAmount() != null ? vo.getMorningShiftAmount() : BigDecimal.ZERO) + .reduce(BigDecimal.ZERO, BigDecimal::add); + BigDecimal nightTotal = dataList.stream() + .map(vo -> vo.getNightShiftAmount() != null ? vo.getNightShiftAmount() : BigDecimal.ZERO) + .reduce(BigDecimal.ZERO, BigDecimal::add); + totalRow.add(morningTotal); + totalRow.add(nightTotal); + } + + // 总合计 + BigDecimal grandTotal = dataList.stream() + .map(vo -> vo.getTotalAmount() != null ? vo.getTotalAmount() : BigDecimal.ZERO) + .reduce(BigDecimal.ZERO, BigDecimal::add); + totalRow.add(grandTotal); + + // 单位列空白 + totalRow.add(""); + + dataRows.add(totalRow); + } + + return dataRows; + } + + /** + * 创建样式策略 + * + * @return 样式策略 + */ + private static HorizontalCellStyleStrategy createStyleStrategy() { + // 表头样式 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontName("Arial"); + headWriteFont.setFontHeightInPoints((short) 12); + headWriteFont.setBold(true); + headWriteCellStyle.setWriteFont(headWriteFont); + + // 内容样式 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); + WriteFont contentWriteFont = new WriteFont(); + contentWriteFont.setFontName("Arial"); + contentWriteFont.setFontHeightInPoints((short) 11); + contentWriteCellStyle.setWriteFont(contentWriteFont); + + return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); + } +} \ No newline at end of file