feat(oa): 新增车间报表汇总功能

- 在OaProjectScheduleDelayMapper.xml中增加关联项目表的查询字段
- 扩展OaProjectScheduleDelayVo实体类以支持更多项目相关信息展示
- 创建新的服务接口IOaWorkshopReportService及其实现类OaWorkshopReportServiceImpl
- 实现发货单和工艺卡数据的综合统计逻辑
- 添加用于导出车间报表汇总信息的控制器OaWorkshopReportController
- 提供按时间段筛选的数据统计与Excel导出功能
- 增加OaWorkshopReportSummaryVo视图对象来封装报表统计数据
This commit is contained in:
2025-12-11 16:46:13 +08:00
parent 1bbbba050c
commit e326b53e18
4 changed files with 266 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
package com.ruoyi.oa.controller.dashboard;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.oa.domain.vo.dashboard.OaWorkshopReportSummaryVo;
import com.ruoyi.oa.service.IOaWorkshopReportService;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Date;
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/oa/workshopReport")
public class OaWorkshopReportController extends BaseController {
private final IOaWorkshopReportService workshopReportService;
@GetMapping("/summary")
public R<OaWorkshopReportSummaryVo> summary(@RequestParam(required = false) Date startTime,
@RequestParam(required = false) Date endTime) {
return R.ok(workshopReportService.summary(startTime, endTime));
}
@GetMapping("/export")
public void export(@RequestParam(required = false) Date startTime,
@RequestParam(required = false) Date endTime,
HttpServletResponse response) {
OaWorkshopReportSummaryVo vo = workshopReportService.summary(startTime, endTime);
ExcelUtil.exportExcel(Collections.singletonList(vo), "车间报表汇总", OaWorkshopReportSummaryVo.class, response);
}
}

View File

@@ -0,0 +1,52 @@
package com.ruoyi.oa.domain.vo.dashboard;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
@ExcelIgnoreUnannotated
public class OaWorkshopReportSummaryVo {
@ExcelProperty("涉及项目数")
private Integer involvedProjectCount;
@ExcelProperty("发货单数")
private Integer deliveryOrderCount;
@ExcelProperty("发货明细数")
private Integer deliveryDetailCount;
@ExcelProperty("合计数量")
private BigDecimal totalQuantity;
@ExcelProperty("合计净重kg")
private BigDecimal totalNetWeight;
@ExcelProperty("合计毛重kg")
private BigDecimal totalGrossWeight;
@ExcelProperty("合计体积m3")
private BigDecimal totalVolume;
@ExcelProperty("设备种类数")
private Integer equipmentKinds;
@ExcelProperty("型号种类数")
private Integer modelKinds;
@ExcelProperty("工艺卡数")
private Integer processCardCount;
@ExcelProperty("工艺卡明细数")
private Integer processCardDetailCount;
@ExcelProperty("平均工时h")
private BigDecimal avgWorkHours;
@ExcelProperty("总工时h")
private BigDecimal totalWorkHours;
@ExcelProperty("最小工时h")
private BigDecimal minWorkHours;
@ExcelProperty("最大工时h")
private BigDecimal maxWorkHours;
@ExcelProperty("参与统计工序数")
private Integer processWithTimeCount;
@ExcelProperty("制造负责人列表")
private List<String> manufacturingLeaders;
@ExcelProperty("作业负责人列表")
private List<String> operationLeaders;
}

View File

@@ -0,0 +1,9 @@
package com.ruoyi.oa.service;
import com.ruoyi.oa.domain.vo.dashboard.OaWorkshopReportSummaryVo;
import java.util.Date;
public interface IOaWorkshopReportService {
OaWorkshopReportSummaryVo summary(Date startTime, Date endTime);
}

View File

@@ -0,0 +1,165 @@
package com.ruoyi.oa.service.impl.dashboard;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.oa.domain.OaDeliveryOrder;
import com.ruoyi.oa.domain.OaDeliveryOrderDetail;
import com.ruoyi.oa.domain.OaProcessCard;
import com.ruoyi.oa.domain.OaProcessCardDetail;
import com.ruoyi.oa.domain.vo.dashboard.OaWorkshopReportSummaryVo;
import com.ruoyi.oa.mapper.OaDeliveryOrderDetailMapper;
import com.ruoyi.oa.mapper.OaDeliveryOrderMapper;
import com.ruoyi.oa.mapper.OaProcessCardDetailMapper;
import com.ruoyi.oa.mapper.OaProcessCardMapper;
import com.ruoyi.oa.service.IOaWorkshopReportService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class OaWorkshopReportServiceImpl implements IOaWorkshopReportService {
private final OaDeliveryOrderMapper deliveryOrderMapper;
private final OaDeliveryOrderDetailMapper deliveryOrderDetailMapper;
private final OaProcessCardMapper processCardMapper;
private final OaProcessCardDetailMapper processCardDetailMapper;
@Override
public OaWorkshopReportSummaryVo summary(Date startTime, Date endTime) {
OaWorkshopReportSummaryVo vo = new OaWorkshopReportSummaryVo();
// 1) 发货单全局统计(按创建时间可选过滤)
LambdaQueryWrapper<OaDeliveryOrder> orderWrap = Wrappers.lambdaQuery();
orderWrap.eq(OaDeliveryOrder::getDelFlag, 0);
if (startTime != null) {
orderWrap.ge(OaDeliveryOrder::getCreateTime, startTime);
}
if (endTime != null) {
orderWrap.le(OaDeliveryOrder::getCreateTime, endTime);
}
List<OaDeliveryOrder> orders = deliveryOrderMapper.selectList(orderWrap);
vo.setDeliveryOrderCount(orders.size());
Set<Long> projFromOrders = orders.stream()
.map(OaDeliveryOrder::getProjectId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
List<Long> orderIds = orders.stream().map(OaDeliveryOrder::getOrderId).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(orderIds)) {
LambdaQueryWrapper<OaDeliveryOrderDetail> dWrap = Wrappers.lambdaQuery();
dWrap.in(OaDeliveryOrderDetail::getOrderId, orderIds).eq(OaDeliveryOrderDetail::getDelFlag, 0);
List<OaDeliveryOrderDetail> details = deliveryOrderDetailMapper.selectList(dWrap);
vo.setDeliveryDetailCount(details.size());
BigDecimal qty = details.stream()
.map(d -> d.getQuantity() == null ? BigDecimal.ZERO : new BigDecimal(d.getQuantity()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal net = details.stream()
.map(d -> d.getNetWeight() != null ? d.getNetWeight() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal gross = details.stream()
.map(d -> d.getGrossWeight() != null ? d.getGrossWeight() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal vol = details.stream()
.map(d -> d.getVolume() != null ? d.getVolume() : BigDecimal.ZERO)
.reduce(BigDecimal.ZERO, BigDecimal::add);
vo.setTotalQuantity(qty);
vo.setTotalNetWeight(net);
vo.setTotalGrossWeight(gross);
vo.setTotalVolume(vol);
Set<String> equipKinds = details.stream().map(OaDeliveryOrderDetail::getEquipmentName).filter(Objects::nonNull).collect(Collectors.toSet());
Set<String> modelKinds = details.stream().map(OaDeliveryOrderDetail::getModelSpec).filter(Objects::nonNull).collect(Collectors.toSet());
vo.setEquipmentKinds(equipKinds.size());
vo.setModelKinds(modelKinds.size());
} else {
vo.setDeliveryDetailCount(0);
vo.setTotalQuantity(BigDecimal.ZERO);
vo.setTotalNetWeight(BigDecimal.ZERO);
vo.setTotalGrossWeight(BigDecimal.ZERO);
vo.setTotalVolume(BigDecimal.ZERO);
vo.setEquipmentKinds(0);
vo.setModelKinds(0);
}
// 2) 工艺卡全局统计(可选创建时间过滤,明细可选工序时间过滤)
LambdaQueryWrapper<OaProcessCard> cardWrap = Wrappers.lambdaQuery();
cardWrap.eq(OaProcessCard::getDelFlag, 0);
if (startTime != null) {
cardWrap.ge(OaProcessCard::getCreateTime, startTime);
}
if (endTime != null) {
cardWrap.le(OaProcessCard::getCreateTime, endTime);
}
List<OaProcessCard> cards = processCardMapper.selectList(cardWrap);
vo.setProcessCardCount(cards.size());
Set<Long> projFromCards = cards.stream().map(OaProcessCard::getProjectId).filter(Objects::nonNull).collect(Collectors.toSet());
List<Long> cardIds = cards.stream().map(OaProcessCard::getCardId).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(cardIds)) {
LambdaQueryWrapper<OaProcessCardDetail> cdWrap = Wrappers.lambdaQuery();
cdWrap.in(OaProcessCardDetail::getCardId, cardIds).eq(OaProcessCardDetail::getDelFlag, 0);
if (startTime != null) {
cdWrap.ge(OaProcessCardDetail::getProcessStartTime, startTime);
}
if (endTime != null) {
cdWrap.le(OaProcessCardDetail::getProcessEndTime, endTime);
}
List<OaProcessCardDetail> cardDetails = processCardDetailMapper.selectList(cdWrap);
vo.setProcessCardDetailCount(cardDetails.size());
List<BigDecimal> hoursList = cardDetails.stream()
.filter(d -> d.getProcessStartTime() != null && d.getProcessEndTime() != null)
.map(d -> {
long minutes = Math.max(0, Duration.between(d.getProcessStartTime().toInstant(), d.getProcessEndTime().toInstant()).toMinutes());
return new BigDecimal(minutes).divide(new BigDecimal(60), 2, java.math.RoundingMode.HALF_UP);
})
.collect(Collectors.toList());
vo.setProcessWithTimeCount(hoursList.size());
BigDecimal totalHours = hoursList.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
vo.setTotalWorkHours(totalHours);
if (!hoursList.isEmpty()) {
vo.setAvgWorkHours(totalHours.divide(new BigDecimal(hoursList.size()), 2, java.math.RoundingMode.HALF_UP));
vo.setMinWorkHours(hoursList.stream().min(Comparator.naturalOrder()).orElse(BigDecimal.ZERO));
vo.setMaxWorkHours(hoursList.stream().max(Comparator.naturalOrder()).orElse(BigDecimal.ZERO));
} else {
vo.setAvgWorkHours(BigDecimal.ZERO);
vo.setMinWorkHours(BigDecimal.ZERO);
vo.setMaxWorkHours(BigDecimal.ZERO);
}
Set<String> manufacturingLeaders = cards.stream()
.map(OaProcessCard::getManufacturingLeader)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(java.util.LinkedHashSet::new));
Set<String> operationLeaders = cards.stream()
.map(OaProcessCard::getOperationLeader)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(java.util.LinkedHashSet::new));
vo.setManufacturingLeaders(new java.util.ArrayList<>(manufacturingLeaders));
vo.setOperationLeaders(new java.util.ArrayList<>(operationLeaders));
} else {
vo.setProcessCardDetailCount(0);
vo.setAvgWorkHours(BigDecimal.ZERO);
vo.setTotalWorkHours(BigDecimal.ZERO);
vo.setMinWorkHours(BigDecimal.ZERO);
vo.setMaxWorkHours(BigDecimal.ZERO);
vo.setProcessWithTimeCount(0);
vo.setManufacturingLeaders(java.util.Collections.emptyList());
vo.setOperationLeaders(java.util.Collections.emptyList());
}
// 3) 涉及项目数(发货+工艺卡并集)
Set<Long> involvedProjects = new HashSet<>();
involvedProjects.addAll(projFromOrders);
involvedProjects.addAll(projFromCards);
vo.setInvolvedProjectCount(involvedProjects.size());
return vo;
}
}