diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/base/service/impl/RepairWorkerServiceImpl.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/base/service/impl/RepairWorkerServiceImpl.java index 59b33182..f872e5e7 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/base/service/impl/RepairWorkerServiceImpl.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/base/service/impl/RepairWorkerServiceImpl.java @@ -32,6 +32,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; @@ -177,7 +178,15 @@ public class RepairWorkerServiceImpl extends ServiceImpl ids = titems.stream().flatMap(item -> Arrays.stream(item.getRepairIds().split(","))).collect(Collectors.toSet()); + // 取所有的员工ID + Set ids = titems.stream() + .filter(item -> item.getRepairIds() != null) // 过滤掉repairIds为null的项 + .flatMap(item -> Arrays.stream(item.getRepairIds().split(","))) + .filter(StringUtils::hasText) // 过滤掉空字符串 + .collect(Collectors.toSet()); + if (ids.isEmpty()) { + return Collections.emptyList(); + } List repairWorkerList = baseMapper.selectList(new LambdaQueryWrapper().in(RepairWorker::getUserId, ids)); if (!repairWorkerList.isEmpty()) { // 单位字典 diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/controller/admin/DlRepairTicketsController.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/controller/admin/DlRepairTicketsController.java index 3d0291a5..d857314c 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/controller/admin/DlRepairTicketsController.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/controller/admin/DlRepairTicketsController.java @@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.iocoder.yudao.common.RecordTypeEnum; import cn.iocoder.yudao.common.RepairCons; +import cn.iocoder.yudao.common.RepairDictConstants; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; @@ -13,19 +14,30 @@ import cn.iocoder.yudao.module.custom.entity.CarMain; import cn.iocoder.yudao.module.custom.entity.CustomerMain; import cn.iocoder.yudao.module.custom.service.CarMainService; import cn.iocoder.yudao.module.custom.service.CustomerMainService; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; +import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.module.tickets.entity.DlRepairTickets; import cn.iocoder.yudao.module.tickets.service.DlRepairTicketsService; import cn.iocoder.yudao.module.tickets.vo.*; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.ExcelWriter; +import com.alibaba.excel.write.metadata.WriteSheet; +import com.alibaba.excel.write.metadata.style.WriteCellStyle; +import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.v3.oas.annotations.Operation; import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.net.URLEncoder; +import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; @@ -53,6 +65,9 @@ public class DlRepairTicketsController { @Resource private RepairRecordsService repairRecordsService; + @Resource + private DictDataApi dictDataApi; + /** * 维修工单表 新增 @@ -528,6 +543,18 @@ public class DlRepairTicketsController { return success(dlRepairTicketsService.getCusAndCarById(id)); } + /** + * @Author 许 + * @Description 工单统计 + * @Date 15:53 2025/8/19 + * @Param [repairTicketsReqVO] + * @return cn.iocoder.yudao.framework.common.pojo.CommonResult + **/ + @GetMapping("/getStatistics") + public CommonResult getStatistics(DlRepairTicketsReqVO repairTicketsReqVO) { + return success(dlRepairTicketsService.getStatistics(repairTicketsReqVO)); + } + /** * 导出数据 * @@ -559,9 +586,48 @@ public class DlRepairTicketsController { if (CollUtil.isEmpty(list)){ throw exception0(500, "没有数据可以导出"); } - ExcelUtils.write(response, "工单数据.xls", "数据", TicketsExportVO.class, list); + + Map statistics = dlRepairTicketsService.getStatistics(repairTicketsReqVO); + + List dictDataList = dictDataApi.getDictDataList(RepairDictConstants.REPAIR_TYPE); + // 转换map + Map repairTypeMap = dictDataList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel)); + + List> rows = new ArrayList<>(); + + // 第一行可以写汇总信息或标题行 + String totalLaborPartsMoney = statistics.get("totalLaborPartsMoney") != null ? statistics.get("totalLaborPartsMoney").toString() : "0"; + String totalProfit = statistics.get("totalProfit") != null ? statistics.get("totalProfit").toString() : "0"; + String profitRateWithLabor = statistics.get("profitRateWithLabor") != null ? formatPercentage(statistics.get("profitRateWithLabor").toString()) : "0%"; + String profitRateWithoutLabor = statistics.get("profitRateWithoutLabor") != null ? formatPercentage(statistics.get("profitRateWithoutLabor").toString()) : "0%"; + + rows.add(CollUtil.newArrayList( + "产值:", totalLaborPartsMoney, + "毛利:", totalProfit, + "含工时毛利率:", profitRateWithLabor, + "不含工时毛利率:", profitRateWithoutLabor)); + + // 第二行写表头 + rows.add(CollUtil.newArrayList("订单编号","维修类别", "客户名称", "车牌号", "车系", "手机号", "经办人姓名", "经办人电话")); + + // 后面循环写数据 + for (TicketsExportVO item : list) { + rows.add(CollUtil.newArrayList( + item.getTicketNo(), + repairTypeMap.get(item.getRepairType()), + item.getUserName(), + item.getCarNo(), + item.getCarBrandName(), + item.getUserMobile(), + item.getHandleName(), + item.getHandleMobile() + )); + } + + ExcelUtils.exportExcel("维修工单", rows, CollUtil.newArrayList(3), response); } + /** * 导出数据 根据工单状态 * @@ -618,5 +684,19 @@ public class DlRepairTicketsController { dlRepairTicketsService.pickCar(id,image,remark); return CommonResult.ok(); } + + /** + * 格式化为百分比字符串 + * @param value 值 + * @return 百分比格式字符串 + */ + private String formatPercentage(String value) { + try { + double rate = Double.parseDouble(value); + return String.format("%.2f%%", rate * 100); + } catch (NumberFormatException e) { + return "0.00%"; + } + } } diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/dto/JobTypeProfitDTO.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/dto/JobTypeProfitDTO.java new file mode 100644 index 00000000..54ef3d40 --- /dev/null +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/dto/JobTypeProfitDTO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.tickets.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * @BelongsProject: lanan-system + * @BelongsPackage: cn.iocoder.yudao.module.tickets.dto + * @Author: 许 + * @CreateTime: 2025-08-18 14:51 + * @Description: 工种利润 + * @Version: 1.0 + */ +@Data +public class JobTypeProfitDTO { + private String jobTypeCode; // 工种编码 + private String jobTypeName; // 工种名称 + private BigDecimal profit; // 毛利 + private BigDecimal money; // 金额 + private BigDecimal profitRate;// 毛利率 + private BigDecimal cost; //成本 + private BigDecimal profitRateWithoutWork; // 不含工时毛利率 + private BigDecimal profitRateWithWork; // 含工时毛利率 + private BigDecimal workMoney; // 工时金额(分母用) + +} + diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/entity/DlRepairTitem.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/entity/DlRepairTitem.java index ab123328..fcd6c127 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/entity/DlRepairTitem.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/entity/DlRepairTitem.java @@ -19,110 +19,110 @@ import lombok.EqualsAndHashCode; @TableName(value ="dl_repair_titem") @Data @EqualsAndHashCode(callSuper = true) -public class DlRepairTitem extends TenantBaseDO { - /** - * 主键标识 - */ - @TableId(type = IdType.ASSIGN_ID) - private String id; + public class DlRepairTitem extends TenantBaseDO { + /** + * 主键标识 + */ + @TableId(type = IdType.ASSIGN_ID) + private String id; - /** - * 工单ID(dl_repair_tickets表的ID) - */ - private String ticketId; + /** + * 工单ID(dl_repair_tickets表的ID) + */ + private String ticketId; - /** - * 名称;计划前端写的时候可以监听一下,动态查一下历史的记录推荐给使用者选 - */ - private String itemName; + /** + * 名称;计划前端写的时候可以监听一下,动态查一下历史的记录推荐给使用者选 + */ + private String itemName; - /** - * 数量 - */ - private Integer itemCount; + /** + * 数量 + */ + private Integer itemCount; - /** - * 单位;计划前端写的时候可以监听一下,动态查一下历史的记录推荐给使用者选 - */ - private String itemUnit; + /** + * 单位;计划前端写的时候可以监听一下,动态查一下历史的记录推荐给使用者选 + */ + private String itemUnit; - /** - * 单价 - */ - private BigDecimal itemPrice; + /** + * 单价 + */ + private BigDecimal itemPrice; - /** - * 折扣 - */ - private BigDecimal itemDiscount; + /** + * 折扣 + */ + private BigDecimal itemDiscount; - /** - * 金额;正常是自动计算 - */ - private BigDecimal itemMoney; + /** + * 金额;正常是自动计算 + */ + private BigDecimal itemMoney; - /** - * 利润 - */ - private BigDecimal itemProfit; + /** + * 利润 + */ + private BigDecimal itemProfit; - /** - * 毛利率 - */ - private BigDecimal itemProfitRate; + /** + * 毛利率 + */ + private BigDecimal itemProfitRate; - /** - * 维修人员ID(system_users表的ID) - */ - private String repairIds; + /** + * 维修人员ID(system_users表的ID) + */ + private String repairIds; - /** - * 维修人员名字(company_staff表的name) - */ - private String repairNames; + /** + * 维修人员名字(company_staff表的name) + */ + private String repairNames; - /** - * 销售人员ID(system_users表的ID) - */ - private Long saleId; + /** + * 销售人员ID(system_users表的ID) + */ + private Long saleId; - /** - * 销售人员名字(company_staff表的name) - */ - private String saleName; + /** + * 销售人员名字(company_staff表的name) + */ + private String saleName; - /** - * 子项类型(字典repair_item_type) - */ - private String itemType; + /** + * 子项类型(字典repair_item_type) + */ + private String itemType; - /** - * 项目ID(dl_repair_project表的ID) - */ - private String projectId; + /** + * 项目ID(dl_repair_project表的ID) + */ + private String projectId; - /** - * 配件ID(dl_base_type表的ID) - */ - private String partId; + /** + * 配件ID(dl_base_type表的ID) + */ + private String partId; - /** - * 其他ID(dl_base_type表的ID) - */ - private String otherId; + /** + * 其他ID(dl_base_type表的ID) + */ + private String otherId; - /** - * 子项类型ID(dl_base_type表的ID) - */ - private String itemTypeId; + /** + * 子项类型ID(dl_base_type表的ID) + */ + private String itemTypeId; - /** - * 状态(字典repair_item_status) - */ - private String itemStatus; + /** + * 状态(字典repair_item_status) + */ + private String itemStatus; - /** - * 备注 - */ - private String remark; -} + /** + * 备注 + */ + private String remark; + } diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/mapper/DlRepairTicketsMapper.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/mapper/DlRepairTicketsMapper.java index ba318aed..c97cc4a3 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/mapper/DlRepairTicketsMapper.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/mapper/DlRepairTicketsMapper.java @@ -9,6 +9,7 @@ import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; +import java.util.Map; /** * 针对表【dl_repair_tickets(维修工单表)】的数据库操作Mapper @@ -55,6 +56,16 @@ public interface DlRepairTicketsMapper extends BaseMapper { * @return java.lang.Long **/ List selectTicketIdByParams(@Param("nowDate")String dayDate, @Param("recordCode")String recordCode, @Param("startTime")String startTime, @Param("endTime")String endTime); + + /** + * @Author 许 + * @Description 统计工单 + * @Date 15:54 2025/8/19 + * @Param [repairTicketsReqVO] + * @return java.util.Map + **/ + + Map getStatistics(@Param("map") DlRepairTicketsReqVO repairTicketsReqVO); } diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/DlRepairTicketsService.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/DlRepairTicketsService.java index d8ba8f39..e3d2fb24 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/DlRepairTicketsService.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/DlRepairTicketsService.java @@ -281,4 +281,14 @@ public interface DlRepairTicketsService extends IService { **/ void pickCar(String id, String image, String remark); + + /** + * @Author 许 + * @Description 工单统计 + * @Date 15:54 2025/8/19 + * @Param [repairTicketsReqVO] + * @return java.util.Map + **/ + + Map getStatistics(DlRepairTicketsReqVO repairTicketsReqVO); } diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/impl/DlRepairTicketsServiceImpl.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/impl/DlRepairTicketsServiceImpl.java index 3ac919ee..4b29b4dd 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/impl/DlRepairTicketsServiceImpl.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/service/impl/DlRepairTicketsServiceImpl.java @@ -49,6 +49,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.UserDTO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.service.user.AdminUserService; +import cn.iocoder.yudao.module.tickets.dto.JobTypeProfitDTO; import cn.iocoder.yudao.module.tickets.entity.DlRepairTickets; import cn.iocoder.yudao.module.tickets.entity.DlRepairTitem; import cn.iocoder.yudao.module.tickets.entity.DlTicketWares; @@ -96,6 +97,7 @@ import java.util.stream.Collectors; import static cn.iocoder.yudao.common.BaseConstants.ORDER_TENANT_NAME; import static cn.iocoder.yudao.common.RepairCons.*; +import static cn.iocoder.yudao.common.RepairDictConstants.REPAIR_WORK_TYPE; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0; @@ -568,19 +570,98 @@ public class DlRepairTicketsServiceImpl extends ServiceImpl typeMap = recordTypeList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel)); result.setRecords(records.stream().peek(item -> item.setType(typeMap.get(item.getType()))).collect(Collectors.toList())); + //查询维修工人表 + List workers = repairWorkerService.listByTicketId(id); + //转成map + Map workerMap = workers.stream().collect(Collectors.toMap(RepairWorker::getUserId, item -> item)); + + //配件总利润 BigDecimal waresProfit = BigDecimal.ZERO; + List resultList = new ArrayList<>(); + if (CollectionUtil.isNotEmpty(items)) { for (DlRepairTitemReqVO item : items) { - if ("02".equals(item.getItemType())) { - // 获取配件利润 加到总利润中 - BigDecimal itemProfit = item.getItemProfit() != null ? item.getItemProfit() : BigDecimal.ZERO; - waresProfit = waresProfit.add(itemProfit); - System.out.println("配件利润:" + itemProfit); + BigDecimal itemProfit = item.getItemProfit() != null ? item.getItemProfit() : BigDecimal.ZERO; + waresProfit = waresProfit.add(itemProfit); + BigDecimal itemMoney = item.getItemMoney() != null ? item.getItemMoney() : BigDecimal.ZERO; + // 成本 = 售价 - 利润(也可以直接从 item.getItemCost() 取,如果有字段) + BigDecimal itemCost = itemMoney.subtract(itemProfit); + + if (StringUtils.isNotEmpty(item.getRepairIds())) { + if (StringUtils.isNotEmpty(item.getRepairIds())) { + String[] repairIdArr = item.getRepairIds().split(","); + for (String repairIdStr : repairIdArr) { + if (StringUtils.isNotEmpty(repairIdStr)) { + RepairWorker repairWorker = workerMap.get(Long.valueOf(repairIdStr)); + if (repairWorker != null) { + String jobType = repairWorker.getWorkType(); + + JobTypeProfitDTO dto = resultList.stream() + .filter(r -> r.getJobTypeCode().equals(jobType)) + .findFirst() + .orElseGet(() -> { + JobTypeProfitDTO newDto = new JobTypeProfitDTO(); + newDto.setJobTypeCode(jobType); + newDto.setJobTypeName(dictDataApi.getDictDataLabel(REPAIR_WORK_TYPE, jobType)); + newDto.setProfit(BigDecimal.ZERO); + newDto.setMoney(BigDecimal.ZERO); + newDto.setWorkMoney(BigDecimal.ZERO); + newDto.setCost(BigDecimal.ZERO); // 新增成本字段 + resultList.add(newDto); + return newDto; + }); + + if ("02".equals(item.getItemType())) { // 配件 + // 多人平摊利润/金额/成本 + int numWorkers = repairIdArr.length; + BigDecimal profitPerWorker = itemProfit.divide(BigDecimal.valueOf(numWorkers), 4, RoundingMode.HALF_UP); + BigDecimal moneyPerWorker = itemMoney.divide(BigDecimal.valueOf(numWorkers), 4, RoundingMode.HALF_UP); + BigDecimal costPerWorker = itemCost.divide(BigDecimal.valueOf(numWorkers), 4, RoundingMode.HALF_UP); + + dto.setProfit(dto.getProfit().add(profitPerWorker)); + dto.setMoney(dto.getMoney().add(moneyPerWorker)); + dto.setCost(dto.getCost().add(costPerWorker)); + } else if ("01".equals(item.getItemType())) { // 工时 + dto.setWorkMoney(dto.getWorkMoney().add(itemMoney)); + } + } + } + } + } + } + } } + // ====== 计算分组毛利率 ====== + for (JobTypeProfitDTO dto : resultList) { + BigDecimal partsProfit = dto.getProfit(); // 配件利润 + BigDecimal partsMoney = dto.getMoney(); // 配件售价 + BigDecimal workMoney = dto.getWorkMoney(); // 工时售价 + + // 不含工时 + BigDecimal profitRateWithoutWork = BigDecimal.ZERO; + if (partsMoney.compareTo(BigDecimal.ZERO) > 0) { + profitRateWithoutWork = partsProfit.divide(partsMoney, 4, RoundingMode.HALF_UP); + } + + // 含工时 + BigDecimal profitRateWithWork = BigDecimal.ZERO; + BigDecimal totalMoney = partsMoney.add(workMoney); + if (totalMoney.compareTo(BigDecimal.ZERO) > 0) { + profitRateWithWork = partsProfit.divide(totalMoney, 4, RoundingMode.HALF_UP); + } + + dto.setProfitRateWithoutWork(profitRateWithoutWork); + dto.setProfitRateWithWork(profitRateWithWork); + } + + + // 最终返回 list + result.setGroupByJobType(resultList); + //计算含工时项目毛利率 //配件总利润除以工单总价 @@ -590,6 +671,9 @@ public class DlRepairTicketsServiceImpl extends ServiceImpl + * @Author 许 + * @Description 工单统计 + * @Date 15:54 2025/8/19 + * @Param [repairTicketsReqVO] + */ + @Override + public Map getStatistics(DlRepairTicketsReqVO repairTicketsReqVO) { + return baseMapper.getStatistics(repairTicketsReqVO); + } } diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsReqVO.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsReqVO.java index 07fa1ec8..a6550a94 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsReqVO.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsReqVO.java @@ -8,6 +8,7 @@ import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; import java.util.List; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; /** @@ -20,8 +21,7 @@ public class DlRepairTicketsReqVO extends DlRepairTickets { /** 时间区间 */ @Schema(pattern = "时间区间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private Date[] searchTimeArray; + private String[] searchTimeArray; /** 工单中项目指定的施工人员的ids */ private List userIds; @@ -55,4 +55,7 @@ public class DlRepairTicketsReqVO extends DlRepairTickets { /** 统计参数 wxz:维修中 wjs:未结算 zc:在厂*/ private String statisticsType; + + /** 工种 */ + private String workType; } diff --git a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsRespVO.java b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsRespVO.java index 761b4e1c..298e15c0 100644 --- a/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsRespVO.java +++ b/dl-module-repair/src/main/java/cn/iocoder/yudao/module/tickets/vo/DlRepairTicketsRespVO.java @@ -4,12 +4,14 @@ import cn.iocoder.yudao.module.base.vo.RepairRecordsRespVO; import cn.iocoder.yudao.module.booking.entity.DlRepairBooking; import cn.iocoder.yudao.module.custom.entity.CustomerMain; import cn.iocoder.yudao.module.custom.vo.CarMainRespVO; +import cn.iocoder.yudao.module.tickets.dto.JobTypeProfitDTO; import cn.iocoder.yudao.module.tickets.entity.DlRepairTickets; import cn.iocoder.yudao.module.tickets.entity.DlRepairTitem; import lombok.Data; import java.math.BigDecimal; import java.util.List; +import java.util.Map; /** * 维修工单表 响应VO @@ -82,4 +84,6 @@ public class DlRepairTicketsRespVO extends DlRepairTickets { /** 不含工时项目毛利率*/ private BigDecimal profitRateNo; + + private List groupByJobType; } diff --git a/dl-module-repair/src/main/resources/mapper/tickets/DlRepairTicketsMapper.xml b/dl-module-repair/src/main/resources/mapper/tickets/DlRepairTicketsMapper.xml index 0ac6bdfe..a761c29c 100644 --- a/dl-module-repair/src/main/resources/mapper/tickets/DlRepairTicketsMapper.xml +++ b/dl-module-repair/src/main/resources/mapper/tickets/DlRepairTicketsMapper.xml @@ -218,7 +218,7 @@ ) - and drt.create_time between #{map.searchTimeArray[0]} and #{map.searchTimeArray[1]} + AND (drt.create_time BETWEEN CONCAT(#{map.searchTimeArray[0]}, ' 00:00:00') AND CONCAT(#{map.searchTimeArray[1]}, ' 23:59:59')) and drt.repair_type = #{map.repairType} @@ -268,7 +268,7 @@ ) - and (drt.create_time between #{map.searchTimeArray[0]} and #{map.searchTimeArray[1]}) + AND (drt.create_time BETWEEN CONCAT(#{map.searchTimeArray[0]}, ' 00:00:00') AND CONCAT(#{map.searchTimeArray[1]}, ' 23:59:59')) AND (drt.repair_type=#{map.repairType}) @@ -389,96 +389,108 @@ + + diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelExtraHelper.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelExtraHelper.java new file mode 100644 index 00000000..99b9bc1c --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelExtraHelper.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.framework.excel.core.util; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.poi.excel.ExcelWriter; +import org.apache.poi.ss.usermodel.*; + +import java.util.List; + +public class ExcelExtraHelper { + + /** + * 根据内容自动设置列宽,并设置样式 + * @param writer ExcelWriter + * @param rows 所有行(含表头) + * @param timeColumnIndexList 哪些列是时间列(索引从0开始) + * @param numberColumnIndexList 哪些列是数字列(右对齐) + */ + public static void enhanceExcel(ExcelWriter writer, List> rows, + List timeColumnIndexList, List numberColumnIndexList) { + if (CollUtil.isEmpty(rows)) return; + + // ============ 1. 表头样式 ============ + CellStyle headStyle = writer.getHeadCellStyle(); + Font headFont = writer.getWorkbook().createFont(); + headFont.setBold(true); + headFont.setFontHeightInPoints((short) 12); + headStyle.setFont(headFont); + // 设置背景色 + headStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); + headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + // ============ 2. 内容样式 ============ + CellStyle contentStyle = writer.getCellStyle(); + contentStyle.setAlignment(HorizontalAlignment.LEFT); + contentStyle.setVerticalAlignment(VerticalAlignment.CENTER); + + // ============ 3. 时间列样式 ============ + CellStyle timeStyle = writer.getWorkbook().createCellStyle(); + timeStyle.cloneStyleFrom(contentStyle); + timeStyle.setDataFormat(writer.getWorkbook().createDataFormat().getFormat("yyyy-MM-dd HH:mm")); + + // ============ 4. 数字列样式 ============ + CellStyle numberStyle = writer.getWorkbook().createCellStyle(); + numberStyle.cloneStyleFrom(contentStyle); + numberStyle.setAlignment(HorizontalAlignment.RIGHT); + + // ============ 5. 根据内容自动调节列宽 ============ + int colSize = rows.get(0).size(); + int[] maxLength = new int[colSize]; + + for (List row : rows) { + for (int colIndex = 0; colIndex < colSize; colIndex++) { + if (row.size() > colIndex && row.get(colIndex) != null) { + String cell = row.get(colIndex); + int length = getStringDisplayLength(cell); + if (length > maxLength[colIndex]) { + maxLength[colIndex] = length; + } + } + } + } + for (int i = 0; i < colSize; i++) { + writer.setColumnWidth(i, maxLength[i] + 2); + } + + // ============ 6. 设置时间列 & 数字列格式 ============ + Sheet sheet = writer.getSheet(); + int rowCount = rows.size(); + + for (int rowIdx = 1; rowIdx < rowCount; rowIdx++) { // 第0行是表头 + Row row = sheet.getRow(rowIdx); + if (row == null) continue; + if (timeColumnIndexList != null) + for (Integer colIdx : timeColumnIndexList) { + Cell cell = row.getCell(colIdx); + if (cell != null) { + cell.setCellStyle(timeStyle); + } + } + if (numberColumnIndexList != null) + for (Integer colIdx : numberColumnIndexList) { + Cell cell = row.getCell(colIdx); + if (cell != null) { + cell.setCellStyle(numberStyle); + } + } + } + } + + /** + * 中文算2长度,英文算1 + */ + private static int getStringDisplayLength(String str) { + int length = 0; + for (char c : str.toCharArray()) { + if (c >= 0x4E00 && c <= 0x9FA5) { + length += 2; + } else { + length += 1; + } + } + return length; + } +} diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java index 964506bc..421d7732 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java @@ -1,5 +1,8 @@ package cn.iocoder.yudao.framework.excel.core.util; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.poi.excel.ExcelUtil; +import cn.hutool.poi.excel.ExcelWriter; import cn.iocoder.yudao.framework.excel.core.handler.CellTextWrapHandler; import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; import cn.iocoder.yudao.framework.excel.core.handler.SetColumnWidthHandler; @@ -212,4 +215,46 @@ public class ExcelUtils { } } + /** + * 通用 Excel 导出方法 + * + * @param title Excel标题 + * @param rows 数据行 + * @param mergeCols 需要增强或特殊处理的列索引(可选) + * @param response HttpServletResponse + * @throws IOException + */ + public static void exportExcel(String title, List> rows, List mergeCols, HttpServletResponse response) throws IOException { + if (CollUtil.isEmpty(rows)) { + throw new IllegalArgumentException("导出数据为空!"); + } + + // 创建 ExcelWriter + ExcelWriter writer = ExcelUtil.getWriter(); + + // ====== 合并单元格写标题 ====== + writer.merge(rows.get(0).size() - 1, title); // 根据第一行列数合并单元格 + writer.setRowHeight(0, 30); // 设置标题行高 + + // 写入数据,true 表示包含表头 + writer.write(rows, true); + + // 如果有需要增强的列 + if (mergeCols != null && !mergeCols.isEmpty()) { + ExcelExtraHelper.enhanceExcel(writer, rows, mergeCols, null); + } + + // 设置响应头 + response.setContentType("application/vnd.ms-excel;charset=utf-8"); + response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(title + ".xls", "UTF-8")); + + try (ServletOutputStream out = response.getOutputStream()) { + writer.flush(out, true); + } finally { + writer.close(); // 释放资源 + } + + } + + }