更新0820
This commit is contained in:
parent
d85b0d8028
commit
6788c0c1fd
@ -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<RepairWorkerMapper, Rep
|
||||
.eq(DlRepairTitem::getItemType, "01");
|
||||
}));
|
||||
// 取所有的员工ID
|
||||
Set<String> ids = titems.stream().flatMap(item -> Arrays.stream(item.getRepairIds().split(","))).collect(Collectors.toSet());
|
||||
// 取所有的员工ID
|
||||
Set<String> 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<RepairWorker> repairWorkerList = baseMapper.selectList(new LambdaQueryWrapper<RepairWorker>().in(RepairWorker::getUserId, ids));
|
||||
if (!repairWorkerList.isEmpty()) {
|
||||
// 单位字典
|
||||
|
||||
@ -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<String, Object> statistics = dlRepairTicketsService.getStatistics(repairTicketsReqVO);
|
||||
|
||||
List<DictDataRespDTO> dictDataList = dictDataApi.getDictDataList(RepairDictConstants.REPAIR_TYPE);
|
||||
// 转换map
|
||||
Map<String, String> repairTypeMap = dictDataList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
|
||||
|
||||
List<List<String>> 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%";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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; // 工时金额(分母用)
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<DlRepairTickets> {
|
||||
* @return java.lang.Long
|
||||
**/
|
||||
List<String> 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<java.lang.String,java.lang.Object>
|
||||
**/
|
||||
|
||||
Map<String, Object> getStatistics(@Param("map") DlRepairTicketsReqVO repairTicketsReqVO);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -281,4 +281,14 @@ public interface DlRepairTicketsService extends IService<DlRepairTickets> {
|
||||
**/
|
||||
|
||||
void pickCar(String id, String image, String remark);
|
||||
|
||||
/**
|
||||
* @Author 许
|
||||
* @Description 工单统计
|
||||
* @Date 15:54 2025/8/19
|
||||
* @Param [repairTicketsReqVO]
|
||||
* @return java.util.Map<java.lang.String,java.lang.Object>
|
||||
**/
|
||||
|
||||
Map<String, Object> getStatistics(DlRepairTicketsReqVO repairTicketsReqVO);
|
||||
}
|
||||
|
||||
@ -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<DlRepairTicketsMappe
|
||||
Map<String, String> 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<RepairWorker> workers = repairWorkerService.listByTicketId(id);
|
||||
//转成map
|
||||
Map<Long, RepairWorker> workerMap = workers.stream().collect(Collectors.toMap(RepairWorker::getUserId, item -> item));
|
||||
|
||||
|
||||
//配件总利润
|
||||
BigDecimal waresProfit = BigDecimal.ZERO;
|
||||
List<JobTypeProfitDTO> 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<DlRepairTicketsMappe
|
||||
profitRate = waresProfit.divide(dlRepairTickets.getTotalPrice(), 2, RoundingMode.HALF_UP);
|
||||
}
|
||||
result.setProfitRate(profitRate);
|
||||
|
||||
// 工单成本 售价 - 利润
|
||||
result.setCost(dlRepairTickets.getTotalPrice().subtract(waresProfit));
|
||||
}
|
||||
//计算不含工时项目毛利率
|
||||
if (ObjectUtil.isNotEmpty(dlRepairTickets.getPartPrice())) {
|
||||
@ -600,6 +684,8 @@ public class DlRepairTicketsServiceImpl extends ServiceImpl<DlRepairTicketsMappe
|
||||
result.setProfitRateNo(profitRateNo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -2194,6 +2280,18 @@ public class DlRepairTicketsServiceImpl extends ServiceImpl<DlRepairTicketsMappe
|
||||
/* 2.步骤表里添加接车*/
|
||||
repairRecordsService.saveRepairRecord(id, null, RecordTypeEnum.PICKCAR.getCode(), "接车", image);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return java.util.Map<java.lang.String, java.lang.Object>
|
||||
* @Author 许
|
||||
* @Description 工单统计
|
||||
* @Date 15:54 2025/8/19
|
||||
* @Param [repairTicketsReqVO]
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getStatistics(DlRepairTicketsReqVO repairTicketsReqVO) {
|
||||
return baseMapper.getStatistics(repairTicketsReqVO);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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<Long> userIds;
|
||||
@ -55,4 +55,7 @@ public class DlRepairTicketsReqVO extends DlRepairTickets {
|
||||
|
||||
/** 统计参数 wxz:维修中 wjs:未结算 zc:在厂*/
|
||||
private String statisticsType;
|
||||
|
||||
/** 工种 */
|
||||
private String workType;
|
||||
}
|
||||
|
||||
@ -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<JobTypeProfitDTO> groupByJobType;
|
||||
}
|
||||
|
||||
@ -218,7 +218,7 @@
|
||||
)
|
||||
</if>
|
||||
<if test="map.searchTimeArray != null and map.searchTimeArray.length > 0">
|
||||
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'))
|
||||
</if>
|
||||
<if test="map.repairType != null and map.repairType != ''">
|
||||
and drt.repair_type = #{map.repairType}
|
||||
@ -268,7 +268,7 @@
|
||||
)
|
||||
</if>
|
||||
<if test="map.searchTimeArray != null and map.searchTimeArray.length > 0">
|
||||
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'))
|
||||
</if>
|
||||
<if test="map.repairType !=null and map.repairType !=''">
|
||||
AND (drt.repair_type=#{map.repairType})
|
||||
@ -389,96 +389,108 @@
|
||||
</select>
|
||||
|
||||
<select id="getPageTypeAll" resultMap="APPBaseResultMap">
|
||||
select drt.*
|
||||
from dl_repair_tickets drt
|
||||
SELECT drt.*
|
||||
FROM dl_repair_tickets drt
|
||||
<if test="map.cusFrom != null and map.cusFrom!=''">
|
||||
-- 按客户来源查,需要关联客户表 --
|
||||
left join base_customer_main bcm ON drt.user_id = bcm.id
|
||||
LEFT JOIN base_customer_main bcm ON drt.user_id = bcm.id
|
||||
</if>
|
||||
left join dl_repair_titem drti
|
||||
on drt.id = drti.ticket_id AND drti.deleted = '0' AND drti.item_type='01'
|
||||
where drt.deleted = '0' AND tickets_status!='03'
|
||||
LEFT JOIN dl_repair_titem drti
|
||||
ON drt.id = drti.ticket_id AND drti.deleted = '0' AND drti.item_type='01'
|
||||
LEFT JOIN dl_repair_worker drw
|
||||
ON FIND_IN_SET(drw.user_id, drti.repair_ids) > 0 AND drw.deleted = '0'
|
||||
WHERE drt.deleted = '0' AND tickets_status!='03'
|
||||
|
||||
<!-- 模糊搜索 -->
|
||||
<if test="map.ticketNo != null and map.ticketNo != ''">
|
||||
and (
|
||||
drt.ticket_no like concat('%', #{map.ticketNo}, '%')
|
||||
or
|
||||
drt.car_no like concat('%', #{map.ticketNo}, '%')
|
||||
or
|
||||
drt.user_name like concat('%', #{map.ticketNo}, '%')
|
||||
or
|
||||
drt.user_mobile like concat('%', #{map.ticketNo}, '%')
|
||||
or
|
||||
drt.remark like concat('%', #{map.ticketNo}, '%')
|
||||
or
|
||||
drt.car_brand_name like concat('%', #{map.ticketNo}, '%')
|
||||
or
|
||||
drt.handle_name like concat('%', #{map.ticketNo}, '%')
|
||||
or
|
||||
drti.item_name like concat('%', #{map.ticketNo}, '%')
|
||||
AND (
|
||||
drt.ticket_no LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.car_no LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.user_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.user_mobile LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.remark LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.car_brand_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.handle_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drti.item_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
)
|
||||
</if>
|
||||
|
||||
<!-- 时间区间 -->
|
||||
<if test="map.searchTimeArray != null and map.searchTimeArray.length > 0">
|
||||
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'))
|
||||
</if>
|
||||
<if test="map.startDate != null and map.startDate != ''">
|
||||
and (drt.create_time >= #{map.startDate} and drt.create_time <= #{map.endDate})
|
||||
AND (drt.create_time >= #{map.startDate} AND drt.create_time <= #{map.endDate})
|
||||
</if>
|
||||
|
||||
<!-- repairType -->
|
||||
<if test="map.repairType !=null and map.repairType !=''">
|
||||
AND (drt.repair_type=#{map.repairType})
|
||||
AND drt.repair_type = #{map.repairType}
|
||||
</if>
|
||||
|
||||
<!-- 状态列表 -->
|
||||
<if test="map.statusList !=null and map.statusList.size > 0">
|
||||
AND (drt.tickets_status IN
|
||||
AND drt.tickets_status IN
|
||||
<foreach collection="map.statusList" item="item" index="index" open="(" close=")" separator=",">
|
||||
#{item}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
|
||||
<!-- id 列表 -->
|
||||
<if test="map.idList !=null and map.idList.size > 0">
|
||||
AND (drt.id IN
|
||||
AND drt.id IN
|
||||
<foreach collection="map.idList" item="item" index="index" open="(" close=")" separator=",">
|
||||
#{item}
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
|
||||
<!-- 客户来源 -->
|
||||
<if test="map.cusFrom != null and map.cusFrom!=''">
|
||||
-- 客户来源 --
|
||||
AND (bcm.data_from = #{map.cusFrom})
|
||||
AND bcm.data_from = #{map.cusFrom}
|
||||
</if>
|
||||
|
||||
<!-- 服务顾问 -->
|
||||
<if test="map.adviserId != null and map.adviserId != ''">
|
||||
-- 服务顾问查所有的就是服务顾问是自己的 --
|
||||
AND (drt.adviser_id = #{map.adviserId})
|
||||
AND drt.adviser_id = #{map.adviserId}
|
||||
</if>
|
||||
|
||||
<!-- 是否交车 -->
|
||||
<if test="map.isHandover != null and map.isHandover != ''">
|
||||
-- 是否交车 --
|
||||
AND (drt.is_handover = #{map.isHandover})
|
||||
AND drt.is_handover = #{map.isHandover}
|
||||
</if>
|
||||
|
||||
<!-- 工人 ID -->
|
||||
<if test="map.userIds != null and map.userIds.size > 0">
|
||||
-- 维修工或维修厂长查所有的就是维修人是自己的或者是自己班组内的 --
|
||||
AND (
|
||||
<foreach item="item" collection="map.userIds" index="index" open="" separator="or" close="">
|
||||
find_in_set(#{item}, drti.repair_ids) > 0
|
||||
<foreach item="item" collection="map.userIds" index="index" separator="or">
|
||||
FIND_IN_SET(#{item}, drti.repair_ids) > 0
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
|
||||
<!-- 按工种筛选 -->
|
||||
<if test="map.workType != null and map.workType != ''">
|
||||
AND drw.work_type = #{map.workType}
|
||||
</if>
|
||||
|
||||
<!-- 统计类型 -->
|
||||
<if test="map.statisticsType != null and map.statisticsType != ''">
|
||||
-- 根据统计类型查询 --
|
||||
<if test="map.statisticsType == 'wxz'">
|
||||
-- 维修中 --
|
||||
AND drt.tickets_status = '05'
|
||||
AND drt.tickets_status = '05'
|
||||
</if>
|
||||
<if test="map.statisticsType == 'wjs'">
|
||||
-- 未结算 --
|
||||
AND drt.tickets_status in ('04','05','07','01')
|
||||
AND drt.tickets_status IN ('04','05','07','01')
|
||||
</if>
|
||||
<if test="map.statisticsType == 'zc'">
|
||||
-- 在厂 --
|
||||
AND drt.is_handover = '0' AND drt.tickets_status != '03'
|
||||
AND drt.is_handover = '0' AND drt.tickets_status != '03'
|
||||
</if>
|
||||
</if>
|
||||
|
||||
GROUP BY drt.id
|
||||
order by drt.update_time desc
|
||||
ORDER BY drt.update_time DESC
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectTicketIdByParams" resultType="java.lang.String">
|
||||
SELECT
|
||||
DISTINCT drt.id
|
||||
@ -498,4 +510,119 @@
|
||||
AND drr.create_time <= #{endTime}
|
||||
</if>
|
||||
</select>
|
||||
<select id="getStatistics" resultType="java.util.Map"
|
||||
parameterType="cn.iocoder.yudao.module.tickets.vo.DlRepairTicketsReqVO">
|
||||
SELECT
|
||||
-- 配件毛利
|
||||
COALESCE(SUM(CASE WHEN drti.item_type = '02' THEN drti.item_profit ELSE 0 END), 0) AS totalProfit,
|
||||
|
||||
-- 工单总金额 (产值)
|
||||
COALESCE(SUM(drt.total_price), 0) AS totalOutput,
|
||||
|
||||
-- 配件金额
|
||||
COALESCE(SUM(CASE WHEN drti.item_type = '02' THEN drti.item_money ELSE 0 END), 0) AS totalPartsMoney,
|
||||
|
||||
-- 工时+配件金额
|
||||
COALESCE(SUM(CASE WHEN drti.item_type IN ('01','02') THEN drti.item_money ELSE 0 END), 0) AS totalLaborPartsMoney,
|
||||
|
||||
-- 含工时毛利率 = 配件毛利 / (工时金额+配件金额)
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN drti.item_type IN ('01','02') THEN drti.item_money ELSE 0 END) = 0 THEN 0
|
||||
ELSE ROUND(SUM(CASE WHEN drti.item_type = '02' THEN drti.item_profit ELSE 0 END)
|
||||
/ SUM(CASE WHEN drti.item_type IN ('01','02') THEN drti.item_money ELSE 0 END), 4)
|
||||
END AS profitRateWithLabor,
|
||||
|
||||
-- 不含工时毛利率 = 配件毛利 / 配件金额
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN drti.item_type = '02' THEN drti.item_money ELSE 0 END) = 0 THEN 0
|
||||
ELSE ROUND(SUM(CASE WHEN drti.item_type = '02' THEN drti.item_profit ELSE 0 END)
|
||||
/ SUM(CASE WHEN drti.item_type = '02' THEN drti.item_money ELSE 0 END), 4)
|
||||
END AS profitRateWithoutLabor
|
||||
FROM dl_repair_tickets drt
|
||||
LEFT JOIN dl_repair_titem drti
|
||||
ON drt.id = drti.ticket_id
|
||||
AND drti.deleted = '0'
|
||||
<if test="map.cusFrom != null and map.cusFrom!=''">
|
||||
LEFT JOIN base_customer_main bcm ON drt.user_id = bcm.id
|
||||
</if>
|
||||
LEFT JOIN dl_repair_worker drw
|
||||
ON FIND_IN_SET(drw.user_id, drti.repair_ids) > 0 AND drw.deleted = '0'
|
||||
WHERE drt.deleted = '0'
|
||||
AND drt.tickets_status != '03'
|
||||
|
||||
<!-- 以下保持和分页查询一致的条件 -->
|
||||
<if test="map.ticketNo != null and map.ticketNo != ''">
|
||||
AND (
|
||||
drt.ticket_no LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.car_no LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.user_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.user_mobile LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.remark LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.car_brand_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drt.handle_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
OR drti.item_name LIKE CONCAT('%', #{map.ticketNo}, '%')
|
||||
)
|
||||
</if>
|
||||
|
||||
<if test="map.searchTimeArray != null and map.searchTimeArray.length > 0">
|
||||
AND (drt.create_time BETWEEN CONCAT(#{map.searchTimeArray[0]}, ' 00:00:00') AND CONCAT(#{map.searchTimeArray[1]}, ' 23:59:59'))
|
||||
</if>
|
||||
<if test="map.startDate != null and map.startDate != ''">
|
||||
AND (drt.create_time >= #{map.startDate} AND drt.create_time <= #{map.endDate})
|
||||
</if>
|
||||
|
||||
<if test="map.repairType !=null and map.repairType !=''">
|
||||
AND drt.repair_type = #{map.repairType}
|
||||
</if>
|
||||
|
||||
<if test="map.statusList !=null and map.statusList.size > 0">
|
||||
AND drt.tickets_status IN
|
||||
<foreach collection="map.statusList" item="item" open="(" close=")" separator=",">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="map.idList !=null and map.idList.size > 0">
|
||||
AND drt.id IN
|
||||
<foreach collection="map.idList" item="item" open="(" close=")" separator=",">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
|
||||
<if test="map.cusFrom != null and map.cusFrom!=''">
|
||||
AND bcm.data_from = #{map.cusFrom}
|
||||
</if>
|
||||
|
||||
<if test="map.adviserId != null and map.adviserId != ''">
|
||||
AND drt.adviser_id = #{map.adviserId}
|
||||
</if>
|
||||
|
||||
<if test="map.isHandover != null and map.isHandover != ''">
|
||||
AND drt.is_handover = #{map.isHandover}
|
||||
</if>
|
||||
|
||||
<if test="map.userIds != null and map.userIds.size > 0">
|
||||
AND (
|
||||
<foreach item="item" collection="map.userIds" separator="or">
|
||||
FIND_IN_SET(#{item}, drti.repair_ids) > 0
|
||||
</foreach>
|
||||
)
|
||||
</if>
|
||||
|
||||
<if test="map.workType != null and map.workType != ''">
|
||||
AND drw.work_type = #{map.workType}
|
||||
</if>
|
||||
|
||||
<if test="map.statisticsType != null and map.statisticsType != ''">
|
||||
<if test="map.statisticsType == 'wxz'">
|
||||
AND drt.tickets_status = '05'
|
||||
</if>
|
||||
<if test="map.statisticsType == 'wjs'">
|
||||
AND drt.tickets_status IN ('04','05','07','01')
|
||||
</if>
|
||||
<if test="map.statisticsType == 'zc'">
|
||||
AND drt.is_handover = '0' AND drt.tickets_status != '03'
|
||||
</if>
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
@ -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<List<String>> rows,
|
||||
List<Integer> timeColumnIndexList, List<Integer> 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<String> 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;
|
||||
}
|
||||
}
|
||||
@ -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<List<String>> rows, List<Integer> 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(); // 释放资源
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user