Merge branch 'repair'

This commit is contained in:
xyc 2025-08-27 15:04:01 +08:00
commit d260fe72e1
35 changed files with 1837 additions and 346 deletions

View File

@ -132,6 +132,10 @@
su.status,
su.avatar AS avatar,
dsc.folder_id AS folderId,
dsc.safe_date AS safeDate,
dsc.formal_date AS formalDate,
dsc.address AS address,
dsc.id_number AS idNumber,
GROUP_CONCAT(DISTINCT sr2.name SEPARATOR ',') AS roleNames
FROM
system_users su

View File

@ -88,7 +88,12 @@ public enum RecordTypeEnum {
JC("jc", "交车"),
/** 内返派工 */
NFPG("nfpg", "内返派工");
NFPG("nfpg", "内返派工"),
/** 收款*/
SK("sk", "收款"),
PICKCAR("pickcar", "接车"),
JSSQ("jssq", "结算申请"),
JSSP("jssp", "结算审批");
/**
* code

View File

@ -34,4 +34,7 @@ public interface RepairDictConstants {
/** 调拨状态 */
String REPAIR_ST_STATUS = "repair_st_status";
/** 基础业务配置 */
String BASE_BUSINESS_CONFIG = "base_business_config";
}

View File

@ -90,6 +90,31 @@ public class RepairStaffController {
return CommonResult.ok();
}
/**
* 新增员工
*
* @param repairStaff 员工信息
* @return
*/
@PostMapping("/insert")
public CommonResult<?> insert(@RequestBody RepairStaffSaveVo repairStaff) {
repairStaffService.insert(repairStaff);
return CommonResult.ok();
}
/**
* @Author
* @Description 更新员工
* @Date 10:38 2025/8/21
* @Param [repairStaff]
* @return cn.iocoder.yudao.framework.common.pojo.CommonResult<?>
**/
@PostMapping("/updateStaff")
public CommonResult<?> updateStaff(@RequestBody RepairStaffSaveVo repairStaff) {
repairStaffService.updateStaff(repairStaff);
return CommonResult.ok();
}
@GetMapping("/listSelectUser")
public CommonResult<?> listSelectUser(@Valid UserPageReqVO pageReqVO) {
// 查询目前所有的userId

View File

@ -2,11 +2,15 @@ package cn.iocoder.yudao.module.base.controller.admin;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.base.service.RepairStatisticsService;
import cn.iocoder.yudao.module.base.vo.QueryBusinessReqVO;
import cn.iocoder.yudao.module.base.vo.QueryBusinessResp;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
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.annotation.Resource;
@ -136,4 +140,19 @@ public class RepairStatisticsController {
return CommonResult.success(statisticsService.listWorks());
}
}
/**
* @Author
* @Description 统计最近业务车辆或客户
* @Date 11:16 2025/8/22
* @Param [reqVO]
* @return cn.iocoder.yudao.framework.common.pojo.CommonResult<?>
**/
@GetMapping("/listBusiness")
public CommonResult<?> listBusinessByCustomerOrCar(QueryBusinessReqVO reqVO,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize) {
Page<QueryBusinessResp> page = new Page<>(pageNo, pageSize);
return CommonResult.success(statisticsService.listBusinessByCustomerOrCar(reqVO,page));
}
}

View File

@ -50,4 +50,9 @@ public class RepairRecords extends TenantBaseDO {
*/
private Long dealUserId;
/**
* 冗余字段
*/
private String otherData;
}

View File

@ -3,8 +3,12 @@ package cn.iocoder.yudao.module.base.entity;
import cn.iocoder.yudao.module.system.api.user.dto.UserDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.util.List;
@Data
@ -60,4 +64,15 @@ public class RepairStaff {
* 文件夹id
*/
private String folderId;
/** 家庭住址 */
private String address;
private String idNumber;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date safeDate;
@JsonFormat(pattern = "yyyy-MM-dd")
private Date formalDate;
}

View File

@ -14,6 +14,7 @@ public class RepairStaffSaveVo {
* 选择的员工
*/
public List<UserDTO> repairStaffs;
public UserDTO repairStaff;
/**
* 角色id
*/

View File

@ -1,9 +1,14 @@
package cn.iocoder.yudao.module.base.mapper;
import cn.iocoder.yudao.module.base.vo.QueryBusinessReqVO;
import cn.iocoder.yudao.module.base.vo.QueryBusinessResp;
import cn.iocoder.yudao.module.base.vo.RepairStatisticsVO;
import cn.iocoder.yudao.module.base.vo.RepairTicketStatisticsVO;
import cn.iocoder.yudao.module.tickets.vo.DlRepairTicketsRespVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@ -96,4 +101,21 @@ public interface RepairStatisticsMapper {
**/
List<RepairStatisticsVO> listWorks();
/**
* @Author
* @Description 统计最近业务
* @Date 11:24 2025/8/22
* @Param [reqVO]
* @return java.util.List
**/
IPage<QueryBusinessResp> listBusinessByCustomer(@Param("reqVO") QueryBusinessReqVO reqVO,@Param("page") Page<QueryBusinessResp> page);
/**
* @Author
* @Description 统计最近业务
* @Date 11:24 2025/8/22
* @Param [reqVO]
* @return java.util.List
**/
IPage<QueryBusinessResp> listBusinessByCar(@Param("reqVO") QueryBusinessReqVO reqVO,@Param("page") Page<QueryBusinessResp> page);
}

View File

@ -28,6 +28,16 @@ public interface RepairRecordsService extends IService<RepairRecords> {
**/
void saveRepairRecord(String ticketId, String repairItemId, String type, String remark, String images);
/**
* @Author
* @Description 保存维修记录
* @Date 13:38 2025/8/20
* @Param [ticketId, repairItemId, type, remark, images, otherData]
* @return void
**/
void saveRepairRecord(String ticketId, String repairItemId, String type, String remark, String images, String otherData);
/**
* 根据条件查询维修记录
* @author PQZ

View File

@ -40,4 +40,22 @@ public interface RepairStaffService {
* @date: 2025/8/11 13:22
*/
UserDTO getStaff(Long id);
/**
* @Author
* @Description 新增员工
* @Date 16:17 2025/8/20
* @Param [repairStaff]
* @return void
**/
void insert(RepairStaffSaveVo repairStaff);
/**
* @Author
* @Description
* @Date 10:44 2025/8/21
* @Param [repairStaff]
* @return void
**/
void updateStaff(RepairStaffSaveVo repairStaff);
}

View File

@ -1,8 +1,12 @@
package cn.iocoder.yudao.module.base.service;
import cn.iocoder.yudao.module.base.vo.QueryBusinessReqVO;
import cn.iocoder.yudao.module.base.vo.QueryBusinessResp;
import cn.iocoder.yudao.module.base.vo.RepairStatisticsVO;
import cn.iocoder.yudao.module.base.vo.RepairTicketStatisticsVO;
import cn.iocoder.yudao.module.tickets.vo.DlRepairTicketsRespVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.Map;
@ -82,4 +86,12 @@ public interface RepairStatisticsService {
List<RepairStatisticsVO> listWorks();
/**
* @Author
* @Description 统计最近业务车辆或客户
* @Date 15:04 2025/8/25
* @Param [reqVO, page]
* @return com.baomidou.mybatisplus.core.metadata.IPage<cn.iocoder.yudao.module.base.vo.QueryBusinessResp>
**/
IPage<QueryBusinessResp> listBusinessByCustomerOrCar(QueryBusinessReqVO reqVO, Page<QueryBusinessResp> page);
}

View File

@ -76,6 +76,33 @@ public class RepairRecordsServiceImpl extends ServiceImpl<RepairRecordsMapper, R
//保存附件信息
itemService.saveItem(repairRecords.getId(), ticketId, repairItemId, images);
}
/**
* @Author
* @Description 保存维修记录
* @Date 13:38 2025/8/20
* @Param [ticketId, repairItemId, type, remark, images]
* @return void
**/
@Override
public void saveRepairRecord(String ticketId, String repairItemId, String type, String remark, String images, String otherData) {
//获取当前登录用户
Long userId = SecurityFrameworkUtils.getLoginUserId();
AdminUserRespDTO loginUser = userApi.getUser(userId);
//初始化维修记录
RepairRecords repairRecords = new RepairRecords();
repairRecords.setTicketId(ticketId);
repairRecords.setRepairItemId(repairItemId);
repairRecords.setType(type);
repairRecords.setRemark(remark);
repairRecords.setDealUserId(loginUser.getId());
repairRecords.setDealUserName(loginUser.getNickname());
repairRecords.setOtherData(otherData);
//保存维修记录
save(repairRecords);
//保存附件信息
itemService.saveItem(repairRecords.getId(), ticketId, repairItemId, images);
}
/**
* 根据条件查询维修记录

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.base.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.common.CommonErrorCodeConstants;
@ -154,6 +155,100 @@ public class RepairStaffServiceImpl implements RepairStaffService {
return companyStaffService.getStaff(id);
}
/**
* @return void
* @Author
* @Description 新增员工
* @Date 16:17 2025/8/20
* @Param [repairStaff]
*/
@Override
public void insert(RepairStaffSaveVo repairStaff) {
// 获取当前登录用户的详细信息
AdminUserRespDTO loginUser = getLoginUser();
// 判断用户是否存在
AdminUserRespDTO checkUserName = adminUserApi.getUserByUsername(repairStaff.getRepairStaffs().get(0).getUsername());
if (ObjectUtil.isEmpty(checkUserName)) {
// 新增
Long user = adminUserApi.createUser(repairStaff.getRepairStaffs().get(0));
repairStaff.getRepairStaffs().get(0).setId(user);
}
// 1. 分配角色
repairStaff.getRepairStaffs().forEach(item -> {
permissionService.assignUserRoleByServicePackageId(item.getId(), repairStaff.getRoleIds(), SystemEnum.REPAIR.getCode());
});
// 2. 查看员工表中是否存在
List<Long> oldIds = repairStaff.getRepairStaffs().stream().map(UserDTO::getId).collect(Collectors.toList());
List<CompanyStaff> staffList = companyStaffService.list(Wrappers.<CompanyStaff>lambdaQuery()
.in(CompanyStaff::getUserId, oldIds));
// 获取员工表中已有的用户ID集合
Set<Long> existUserIds = staffList.stream()
.map(CompanyStaff::getUserId)
.collect(Collectors.toSet());
// 找出不存在于员工表中的用户ID
List<UserDTO> notExistUsers = repairStaff.getRepairStaffs().stream()
.filter(user -> !existUserIds.contains(user.getId()))
.collect(Collectors.toList());
/* 插入员工库 */
DeptRespDTO loginDept = getLoginDept(loginUser.getDeptId());
// 添加员工
if (CollectionUtil.isNotEmpty(notExistUsers)) {
List<CompanyStaff> companyStaffs = new ArrayList<>();
for (UserDTO notExistUser : notExistUsers) {
CompanyStaff companyStaff = new CompanyStaff();
BeanUtil.copyProperties(notExistUser, companyStaff);
companyStaff.setId(null);
companyStaff.setCreateTime(null);
companyStaff.setUpdateTime(null);
companyStaff.setCreator(null);
companyStaff.setUpdater(null);
// 1 获取当前登录用户的企业信息给添加的员工
// 2 生成唯一推广码
String uniqueCode = uniqueCodeService.createUniqueCode();
if (!ObjectUtil.isNotEmpty(uniqueCode)) {
throw exception(CommonErrorCodeConstants.UNIQUE_CODE_CREATE_REPEAT);
}
companyStaff.setCorpId(loginDept.getCorpId());
companyStaff.setUserId(notExistUser.getId());
companyStaff.setName(notExistUser.getNickname());
companyStaff.setTel(notExistUser.getUsername());
companyStaff.setUniqueCode(uniqueCode);
companyStaffs.add(companyStaff);
}
companyStaffService.saveBatch(companyStaffs);
}
}
/**
* @return void
* @Author
* @Description
* @Date 10:44 2025/8/21
* @Param [repairStaff]
*/
@Override
public void updateStaff(RepairStaffSaveVo repairStaff) {
UserDTO staff = repairStaff.getRepairStaff();
CompanyStaff companyStaff = BeanUtil.copyProperties(staff, CompanyStaff.class);
companyStaff.setCreator(null);
companyStaff.setUpdater(null);
companyStaff.setUpdateTime(null);
companyStaff.setCreateTime(null);
companyStaff.setDeptId(null);
companyStaff.setId(null);
companyStaffService.update(companyStaff, Wrappers.<CompanyStaff>lambdaUpdate()
.eq(CompanyStaff::getUserId, staff.getId()));
}
/**
* 获取当前登录用户的部门详细信息
*

View File

@ -2,13 +2,18 @@ package cn.iocoder.yudao.module.base.service.impl;
import cn.iocoder.yudao.module.base.mapper.RepairStatisticsMapper;
import cn.iocoder.yudao.module.base.service.RepairStatisticsService;
import cn.iocoder.yudao.module.base.vo.QueryBusinessReqVO;
import cn.iocoder.yudao.module.base.vo.QueryBusinessResp;
import cn.iocoder.yudao.module.base.vo.RepairStatisticsVO;
import cn.iocoder.yudao.module.base.vo.RepairTicketStatisticsVO;
import cn.iocoder.yudao.module.tickets.vo.DlRepairTicketsRespVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -128,4 +133,22 @@ public class RepairStatisticsServiceImpl implements RepairStatisticsService {
public List<RepairStatisticsVO> listWorks() {
return statisticsMapper.listWorks();
}
/**
* @Author
* @Description 统计最近业务车辆或客户
* @Date 15:03 2025/8/25
* @Param [reqVO, page]
* @return com.baomidou.mybatisplus.core.metadata.IPage<cn.iocoder.yudao.module.base.vo.QueryBusinessResp>
**/
@Override
public IPage<QueryBusinessResp> listBusinessByCustomerOrCar(QueryBusinessReqVO reqVO, Page<QueryBusinessResp> page) {
IPage<QueryBusinessResp> queryBusinessResps = null;
if ("customer".equals(reqVO.getType())) {
queryBusinessResps = statisticsMapper.listBusinessByCustomer(reqVO,page);
}else if ("car".equals(reqVO.getType())) {
queryBusinessResps = statisticsMapper.listBusinessByCar(reqVO,page);
}
return queryBusinessResps;
}
}

View File

@ -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()) {
// 单位字典

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.base.vo;
import lombok.Data;
/**
*@BelongsProject: lanan-system
*@BelongsPackage: cn.iocoder.yudao.module.base.vo
*@Author:
*@CreateTime: 2025-08-22 11:14
*@Description: 业务统计请求参数
*@Version: 1.0
*/
@Data
public class QueryBusinessReqVO {
/**
* 时间范围
*/
private String[] dateRange;
/**
* 类型 car: 车辆 customer: 客户
*/
private String type;
/**
* 搜索
*/
private String search;
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.base.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.util.Date;
/**
*@BelongsProject: lanan-system
*@BelongsPackage: cn.iocoder.yudao.module.base.vo
*@Author:
*@CreateTime: 2025-08-22 14:04
*@Description: TODO
*@Version: 1.0
*/
@Data
public class QueryBusinessResp {
/**
* 客户id
*/
private String customerId;
/**
* 客户名称
*/
private String customerName;
/**
* 客户手机号
*/
private String customerPhone;
/**
* 业务id
*/
private String bizId;
/**
* 业务编号
*/
private String bizNo;
/**
* 业务时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date bizTime;
/**
* 业务来源
*/
private String source;
/**
* 车牌号
*/
private String carNum;
}

View File

@ -31,6 +31,7 @@ import cn.iocoder.yudao.module.tickets.service.DlRepairTicketsService;
import cn.iocoder.yudao.module.tickets.service.DlRepairTitemService;
import cn.iocoder.yudao.module.tickets.service.DlTicketWaresService;
import cn.iocoder.yudao.module.tickets.service.DlTwItemService;
import cn.iocoder.yudao.module.tickets.utils.TicketsOperateUtil;
import cn.iocoder.yudao.module.tickets.vo.AppWaresGroupVO;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@ -44,6 +45,7 @@ import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -53,7 +55,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
/**
* 针对表dl_repair_so(采购单领料单)的数据库操作Service实现
*
* @author 小李
* @date 9:11 2024/9/13
**/
@ -108,9 +109,11 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
@Lazy
private RepairRecordsService recordsService;
@Resource
private TicketsOperateUtil operateUtil;
/**
* 采购单/领料单 新增
*
* @param repairSoRespVO 采购单对象
* @author 小李
* @date 10:49 2024/9/14
@ -171,7 +174,6 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
/**
* 采购单/领料单新增分页
*
* @param repairSoReqVO 查询对象
* @author 小李
* @date 18:14 2024/9/14
@ -183,7 +185,6 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
/**
* 采购单/领料单 作废
*
* @param repairSoReqVO 作废对象
* @author 小李
* @date 11:12 2024/9/18
@ -217,7 +218,7 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
// 如果主表记录的领退料人与操作人一致即是员工点击的作废即需要通知仓库
boolean flag = so.getUserId().equals(loginUserId);
if (flag) {
repairWorkerService.sentMessage(SystemEnum.REPAIR.getCode(),Long.valueOf(so.getCreator()), (so.getSoType().equals("02") ? "领料单:" : "退料单:") + so.getSoNo() + "已被" + so.getUserName() + "作废");
repairWorkerService.sentMessage(SystemEnum.REPAIR.getCode(), Long.valueOf(so.getCreator()), (so.getSoType().equals("02") ? "领料单:" : "退料单:") + so.getSoNo() + "已被" + so.getUserName() + "作废");
// 需要更新库存和申请表数据
// 查单据子表
List<DlRepairSoi> sois = repairSoiService.list(new LambdaQueryWrapper<DlRepairSoi>().in(DlRepairSoi::getSoId, so.getId()));
@ -272,7 +273,6 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
/**
* 采购单/领料单 查看
*
* @param id 主键
* @author 小李
* @date 9:34 2024/9/22
@ -306,7 +306,6 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
/**
* 员工确认领料
*
* @param id 单据ID 领料单主表
* @author 小李
* @date 11:58 2024/10/21
@ -341,11 +340,11 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
}).collect(Collectors.toList());
/* 这里添加新逻辑,员工确认领料时,把领的配件添加进工单子表---start */
this.dealWareItem(true,so.getTwId(),sois,repairWares);
this.dealWareItem(true, so.getTwId(), sois, repairWares);
/* 这里添加新逻辑,员工确认领料时,把领的配件添加进工单子表---end */
// 通知仓库
repairWorkerService.sentMessage(SystemEnum.REPAIR.getCode(),Long.valueOf(so.getCreator()), so.getUserName() + "已确认领料单:" + so.getSoNo());
repairWorkerService.sentMessage(SystemEnum.REPAIR.getCode(), Long.valueOf(so.getCreator()), so.getUserName() + "已确认领料单:" + so.getSoNo());
// 记录日志
// 查配件申请表
@ -376,7 +375,6 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
/**
* 员工确认退料
*
* @param id 退料单主表ID
* @author 小李
* @date 19:41 2024/10/21
@ -397,7 +395,7 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
// 查主表
DlRepairSo newSo = baseMapper.selectById(id);
// 通知仓库
repairWorkerService.sentMessage(SystemEnum.REPAIR.getCode(),Long.valueOf(newSo.getCreator()), newSo.getUserName() + "已确认退料单:" + newSo.getSoNo());
repairWorkerService.sentMessage(SystemEnum.REPAIR.getCode(), Long.valueOf(newSo.getCreator()), newSo.getUserName() + "已确认退料单:" + newSo.getSoNo());
// 记录日志
// 查子表信息
List<DlRepairSoi> sois = repairSoiService.list(new LambdaQueryWrapper<DlRepairSoi>().eq(DlRepairSoi::getSoId, id));
@ -414,7 +412,7 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
StringBuilder remark = new StringBuilder("确认退料: ");
/* 这里添加新逻辑,员工确认退料时,把退的配件从工单子表中删除---start */
this.dealWareItem(false,newSo.getTwId(),sois,repairWares);
this.dealWareItem(false, newSo.getTwId(), sois, repairWares);
/* 这里添加新逻辑,员工确认退料时,把退的配件从工单子表中删除---end */
// 构建备注
@ -441,19 +439,19 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
/**
* 领料退料计算工单子表的通用方法
* @param ifGet 是否是领料
* @param applyId 配件申请单id
* @param sois 领料单退料单配件明细
* @param repairWares 配件库的明细
* @author vinjor-M
* @date 14:31 2024/12/4
* @param ifGet 是否是领料
* @param applyId 配件申请单id
* @param sois 领料单退料单配件明细
* @param repairWares 配件库的明细
**/
private void dealWareItem(Boolean ifGet,String applyId,List<DlRepairSoi> sois,List<RepairWares> repairWares){
**/
private void dealWareItem(Boolean ifGet, String applyId, List<DlRepairSoi> sois, List<RepairWares> repairWares) {
//配件申请单
DlTicketWares dlTicketWares = ticketWaresService.getById(applyId);
//工单现有的配件
List<DlRepairTitem> titems = titemService.list(new LambdaQueryWrapper<DlRepairTitem>().and(i -> {
i.eq(DlRepairTitem::getTicketId,dlTicketWares.getTicketId())
i.eq(DlRepairTitem::getTicketId, dlTicketWares.getTicketId())
.eq(DlRepairTitem::getItemType, "02");
}));
//更新或插入的数据集合
@ -461,37 +459,64 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
//删除的数据id集合
List<String> delIdList = new ArrayList<>();
//工单现有配件转MAP
Map<String,DlRepairTitem> itemMap = titems.stream().collect(Collectors.toMap(DlRepairTitem::getPartId,Function.identity()));
Map<String, DlRepairTitem> itemMap = titems.stream().collect(Collectors.toMap(DlRepairTitem::getPartId, Function.identity()));
//配件库的数据集合转MAP
Map<String,RepairWares> repairWaresMap = repairWares.stream().collect(Collectors.toMap(RepairWares::getId,Function.identity()));
Map<String, RepairWares> repairWaresMap = repairWares.stream().collect(Collectors.toMap(RepairWares::getId, Function.identity()));
//遍历本次领料/退料的所有配件
for (DlRepairSoi repairSoi:sois){
if(itemMap.containsKey(repairSoi.getGoodsId())){
for (DlRepairSoi repairSoi : sois) {
if (itemMap.containsKey(repairSoi.getGoodsId())) {
//工单中有这个配件
if(ifGet){
if (ifGet) {
//领料
//工单现有配件中就有这个领取的配件需要更新这个配件的数量价格
DlRepairTitem item = itemMap.get(repairSoi.getGoodsId());
item.setItemCount(item.getItemCount() + repairSoi.getGoodsCount());
item.setItemMoney(item.getItemPrice().multiply(BigDecimal.valueOf(item.getItemCount())).multiply(item.getItemDiscount()));
item.setItemStatus(TicketsItemStatusEnum.RECEIVED.getCode());
// 获取配件的售价和进价
BigDecimal salePrice = repairWaresMap.get(repairSoi.getGoodsId()).getPrice(); // 售价
BigDecimal purchasePrice = repairWaresMap.get(repairSoi.getGoodsId()).getPurPrice(); // 进价
//计算利润
BigDecimal profit = operateUtil.calcProfit(salePrice, purchasePrice, item.getItemCount());
// 计算毛利率
BigDecimal profitRate = operateUtil.calcProfitRate(salePrice, purchasePrice, item.getItemCount());
item.setItemProfit(profit);
item.setItemProfitRate(profitRate);
saveOrUpdateList.add(item);
}else{
} else {
//退料
//工单现有配件中就有这个领取的配件需要更新这个配件的数量价格
DlRepairTitem item = itemMap.get(repairSoi.getGoodsId());
if(item.getItemCount()>repairSoi.getGoodsCount()){
if (item.getItemCount() > repairSoi.getGoodsCount()) {
//现有数量大于要退的数量扣掉
item.setItemCount(item.getItemCount() - repairSoi.getGoodsCount());
item.setItemMoney(item.getItemPrice().multiply(BigDecimal.valueOf(item.getItemCount())).multiply(item.getItemDiscount()));
item.setItemStatus(TicketsItemStatusEnum.RECEIVED.getCode());
// 重新计算利润和利润率
BigDecimal salePrice = repairWaresMap.get(repairSoi.getGoodsId()).getPrice(); // 售价
BigDecimal purchasePrice = repairWaresMap.get(repairSoi.getGoodsId()).getPurPrice(); // 进价
//计算利润
BigDecimal profit = operateUtil.calcProfit(salePrice, purchasePrice, item.getItemCount());
// 计算毛利率
BigDecimal profitRate = operateUtil.calcProfitRate(salePrice, purchasePrice, item.getItemCount());
item.setItemProfit(profit);
item.setItemProfitRate(profitRate);
saveOrUpdateList.add(item);
}else{
} else {
//现有数量小于或等于要退的数量直接将这个配件删掉
delIdList.add(item.getId());
}
}
}else{
} else {
//工单中没有这个配件
if (ifGet) {
//领料需要把这个配件添加到工单子表中
@ -515,25 +540,37 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
titem.setSaleName(dlTicketWares.getAdviserName());
titem.setPartId(repairSoi.getGoodsId());
titem.setItemStatus(TicketsItemStatusEnum.RECEIVED.getCode());
// 获取售价和进价
BigDecimal salePrice = waresItem.getPrice(); // 售价
BigDecimal purchasePrice = waresItem.getPurPrice(); // 进价
//计算利润
BigDecimal profit = operateUtil.calcProfit(salePrice, purchasePrice, titem.getItemCount());
// 计算毛利率
BigDecimal profitRate = operateUtil.calcProfitRate(salePrice, purchasePrice, titem.getItemCount());
titem.setItemProfitRate(profitRate);
titem.setItemProfit(profit);
saveOrUpdateList.add(titem);
}else{
} else {
//退料不可能出现没领料就退料的情况不用处理
}
}
}
if(!saveOrUpdateList.isEmpty()){
if (!saveOrUpdateList.isEmpty()) {
titemService.saveOrUpdateBatch(saveOrUpdateList);
}
if(!delIdList.isEmpty()){
if (!delIdList.isEmpty()) {
titemService.removeBatchByIds(delIdList);
}
//重新计算工单的价格和订单的价格
ticketsService.computeTicket(dlTicketWares.getTicketId());
}
/**
* 采购入库
*
* @param reqVO 接参实体中需传入采购单id单据编号soNo备注remark,入库总价totalPrice子表中商品id子表中入库数量inCount
* @author PQZ
* @date 14:32 2024/10/24
@ -649,7 +686,6 @@ public class DlRepairSoServiceImpl extends ServiceImpl<DlRepairSoMapper, DlRepai
/**
* 领料单退料单APP查看
*
* @param id 单据id
* @return cn.iocoder.yudao.framework.common.pojo.CommonResult<?>
* @author vinjor-M

View File

@ -5,27 +5,41 @@ 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;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.base.service.RepairRecordsService;
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.system.api.permission.PermissionApi;
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 +67,12 @@ public class DlRepairTicketsController {
@Resource
private RepairRecordsService repairRecordsService;
@Resource
private DictDataApi dictDataApi;
@Resource
private PermissionApi permissionApi;
/**
* 维修工单表 新增
@ -188,19 +208,60 @@ public class DlRepairTicketsController {
}
/**
* 维修工单表 结算
* 维修工单表 收款
*
* @param repairTicketsRespVO 工单
* @author 小李
* @date 8:50 2024/9/23
**/
@PostMapping("/paid")
@Operation(summary = "维修工单表 结算")
@Operation(summary = "维修工单表 收款")
public CommonResult<?> setTicketsPaid(@RequestBody DlRepairTicketsRespVO repairTicketsRespVO) {
dlRepairTicketsService.setTicketsPaid(repairTicketsRespVO);
return CommonResult.ok();
}
/**
* 维修工单表 结算
*
* @param vo 工单
* @author 小李
* @date 8:50 2024/9/23
**/
@PostMapping("/settlement")
@Operation(summary = "维修工单表 结算")
public CommonResult<?> setTicketsSettlement(@RequestBody TicketsSettlementVO vo) {
dlRepairTicketsService.setTicketsSettlement(vo);
return CommonResult.ok();
}
/**
* @Author
* @Description 结算审批
* @Date 13:43 2025/8/20
* @Param [vo]
* @return cn.iocoder.yudao.framework.common.pojo.CommonResult<?>
**/
@PostMapping("/settlementReview")
@Operation(summary = "维修工单表 结算")
public CommonResult<?> settlementReview(@RequestBody TicketsSettlementVO vo) {
dlRepairTicketsService.settlementReview(vo);
return CommonResult.ok();
}
/**
* @Author
* @Description 查询审批金额
* @Date 14:20 2025/8/20
* @Param [repairTicketsReqVO]
* @return cn.iocoder.yudao.framework.common.pojo.CommonResult<?>
**/
@GetMapping("/getSettlement")
public CommonResult<?> getSettlement(DlRepairTicketsReqVO repairTicketsReqVO) {
return CommonResult.success(dlRepairTicketsService.getSettlement(repairTicketsReqVO));
}
// /**
// * 维修工单表 导出
// *
@ -528,6 +589,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 +632,52 @@ 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%";
boolean hasAnyPermissions = permissionApi.hasAnyPermissions(SecurityFrameworkUtils.getLoginUserId(), "repair:tick:profit");
if (hasAnyPermissions) {
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);
}
/**
* 导出数据 根据工单状态
*
@ -603,5 +719,34 @@ public class DlRepairTicketsController {
}
ExcelUtils.write(response, name, "数据", TicketExportByStatusVO.class, list);
}
/**
* @Author
* @Description //TODO 接车
* @Date 14:06 2025/8/13
* @Param [id, image, remark]
* @return cn.iocoder.yudao.framework.common.pojo.CommonResult<?>
**/
@GetMapping("/pickCar")
public CommonResult<?> pickCar(@RequestParam("id") String id,
@RequestParam(value="image",required = false) String image,
@RequestParam(value="remark",required = false) String remark){
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%";
}
}
}

View File

@ -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; // 工时金额分母用
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.tickets.entity;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.tickets.vo.TicketsSettlementVO;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@ -202,6 +203,8 @@ public class DlRepairTickets extends TenantBaseDO {
private String isFinish;
/** 是否交车0未交车|1已交车 */
private String isHandover;
/** 是否接车0未接车|1已接车 */
private String isPickCar;
/** 工单当前施工人id */
private Long nowRepairId;
@ -247,6 +250,9 @@ public class DlRepairTickets extends TenantBaseDO {
/** 商业险保费 */
private BigDecimal shangye;
/** 支付状态 字典repair_pat_status */
private String payStatus;
/** 更新时上传的图片 */
@TableField(exist = false)
private String image;
@ -256,4 +262,10 @@ public class DlRepairTickets extends TenantBaseDO {
/** 支付方式文本 */
@TableField(exist = false)
private String payTypeText;
@TableField(exist = false)
private String settlementStr;
@TableField(exist = false)
private TicketsSettlementVO settlement;
@TableField(exist = false)
private boolean ifShow = false;
}

View File

@ -19,100 +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;
/**
* 维修人员ID(system_users表的ID)
*/
private String repairIds;
/**
* 利润
*/
private BigDecimal itemProfit;
/**
* 维修人员名字(company_staff表的name)
*/
private String repairNames;
/**
* 毛利率
*/
private BigDecimal itemProfitRate;
/**
* 销售人员ID(system_users表的ID)
*/
private Long saleId;
/**
* 维修人员ID(system_users表的ID)
*/
private String repairIds;
/**
* 销售人员名字(company_staff表的name)
*/
private String saleName;
/**
* 维修人员名字(company_staff表的name)
*/
private String repairNames;
/**
* 子项类型(字典repair_item_type)
*/
private String itemType;
/**
* 销售人员ID(system_users表的ID)
*/
private Long saleId;
/**
* 项目ID(dl_repair_project表的ID)
*/
private String projectId;
/**
* 销售人员名字(company_staff表的name)
*/
private String saleName;
/**
* 配件ID(dl_base_type表的ID)
*/
private String partId;
/**
* 子项类型(字典repair_item_type)
*/
private String itemType;
/**
* 其他ID(dl_base_type表的ID)
*/
private String otherId;
/**
* 项目ID(dl_repair_project表的ID)
*/
private String projectId;
/**
* 子项类型ID(dl_base_type表的ID)
*/
private String itemTypeId;
/**
* 配件ID(dl_base_type表的ID)
*/
private String partId;
/**
* 状态(字典repair_item_status)
*/
private String itemStatus;
/**
* 其他ID(dl_base_type表的ID)
*/
private String otherId;
/**
* 备注
*/
private String remark;
}
/**
* 子项类型ID(dl_base_type表的ID)
*/
private String itemTypeId;
/**
* 状态(字典repair_item_status)
*/
private String itemStatus;
/**
* 备注
*/
private String remark;
}

View File

@ -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);
}

View File

@ -1,10 +1,7 @@
package cn.iocoder.yudao.module.tickets.service;
import cn.iocoder.yudao.module.tickets.entity.DlRepairTickets;
import cn.iocoder.yudao.module.tickets.vo.CustomerAndCarVO;
import cn.iocoder.yudao.module.tickets.vo.DlRepairTicketsReqVO;
import cn.iocoder.yudao.module.tickets.vo.DlRepairTicketsRespVO;
import cn.iocoder.yudao.module.tickets.vo.NoticeCusVO;
import cn.iocoder.yudao.module.tickets.vo.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
@ -271,4 +268,54 @@ public interface DlRepairTicketsService extends IService<DlRepairTickets> {
* @param id 工单ID
**/
void refreshUpdateTime(String id);
/**
* @Author
* @Description //TODO 接车
* @Date 14:07 2025/8/13
* @Param [id, image, remark]
* @return void
**/
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);
/**
* @Author
* @Description 结算申请
* @Date 10:12 2025/8/20
* @Param [repairTicketsRespVO]
* @return void
**/
void setTicketsSettlement(TicketsSettlementVO repairTicketsRespVO);
/**
* @Author
* @Description 结算审批
* @Date 13:41 2025/8/20
* @Param [vo]
* @return void
**/
void settlementReview(TicketsSettlementVO vo);
/**
* @Author
* @Description 查询审批金额
* @Date 14:20 2025/8/20
* @Param [repairTicketsReqVO]
* @return cn.iocoder.yudao.module.tickets.vo.TicketsSettlementVO
**/
TicketsSettlementVO getSettlement(DlRepairTicketsReqVO repairTicketsReqVO);
}

View File

@ -3,26 +3,85 @@ package cn.iocoder.yudao.module.tickets.utils;
import cn.hutool.core.date.DateUtil;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
/**
* 工单操作常用util
* @author vinjor-M
* @date 16:15 2024/10/18
**/
**/
@Component
public class TicketsOperateUtil {
/**
* 计算车龄
* @param registerDate 车辆注册日期
* @return java.lang.Double
* @author vinjor-M
* @date 14:47 2025/1/9
* @param registerDate 车辆注册日期
* @return java.lang.Double
**/
public Double computeCarYear(Date registerDate){
public Double computeCarYear(Date registerDate) {
long betweenMonth = DateUtil.betweenMonth(registerDate, new Date(), true);
String carYear = String.format("%.2f", (double) betweenMonth / 12);
return Double.parseDouble(carYear);
}
/**
* 计算利润
* @param salePrice 单个售价不含折扣
* @param purchasePrice 单个进价采购价格
* @param count 数量
* @return 利润金额
*/
public BigDecimal calcProfit(BigDecimal salePrice, BigDecimal purchasePrice, int count) {
if (salePrice == null || purchasePrice == null) {
return BigDecimal.ZERO;
}
BigDecimal c = BigDecimal.valueOf(count);
return salePrice.multiply(c).subtract(purchasePrice.multiply(c));
}
/**
* 计算利润率
* @param salePrice 单个售价不含折扣
* @param purchasePrice 单个进价采购价格
* @param count 数量
* @return 利润率0-1之间的小数保留2位
*/
public BigDecimal calcProfitRate(BigDecimal salePrice, BigDecimal purchasePrice, int count) {
if (salePrice == null || purchasePrice == null || count <= 0) {
return BigDecimal.ZERO;
}
BigDecimal c = BigDecimal.valueOf(count);
BigDecimal saleTotal = salePrice.multiply(c);
if (saleTotal.compareTo(BigDecimal.ZERO) == 0) {
return BigDecimal.ZERO;
}
BigDecimal profit = saleTotal.subtract(purchasePrice.multiply(c));
return profit.divide(saleTotal, 2, RoundingMode.HALF_UP);
}
/**
* 计算利润
* @param salePrice 单个售价不含折扣
* @param purchasePrice 单个进价采购价格
* @return 利润金额
*/
public BigDecimal calcProfit(BigDecimal salePrice, BigDecimal purchasePrice) {
return calcProfit(salePrice, purchasePrice, 1);
}
/**
* 计算利润率
* @param salePrice 售价不含折扣
* @param purchasePrice 进价采购价格
* @return 利润率0-1之间的小数保留2位
*/
public BigDecimal calcProfitRate(BigDecimal salePrice, BigDecimal purchasePrice) {
return calcProfitRate(salePrice, purchasePrice, 1);
}
}

View File

@ -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;
@ -52,4 +52,10 @@ public class DlRepairTicketsReqVO extends DlRepairTickets {
private List<String> statusList;
/** 工单id集和 */
private List<String> idList;
/** 统计参数 wxz:维修中 wjs未结算 zc在厂*/
private String statisticsType;
/** 工种 */
private String workType;
}

View File

@ -4,11 +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
@ -75,4 +78,12 @@ public class DlRepairTicketsRespVO extends DlRepairTickets {
/** 客户来源 */
private String cusFrom;
/** 含工时项目毛利率*/
private BigDecimal profitRate;
/** 不含工时项目毛利率*/
private BigDecimal profitRateNo;
private List<JobTypeProfitDTO> groupByJobType;
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.tickets.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* @BelongsProject: lanan-system
* @BelongsPackage: cn.iocoder.yudao.module.tickets.vo
* @Author:
* @CreateTime: 2025-08-20 12:47
* @Description: TODO
* @Version: 1.0
*/
@Data
public class TicketsSettlementVO {
/**
* 工单ID
*/
private String ticketId;
/**
* 实际金额
*/
private BigDecimal actualMoney;
/**
* 金额
*/
private BigDecimal money;
/**
* 优惠类型
*/
private String discountType;
/**
* 优惠金额|折扣
*/
private Integer discount;
}

View File

@ -123,4 +123,113 @@
GROUP BY drw.user_id,drr.ticket_id
ORDER BY value DESC
</select>
<select id="listBusinessByCustomer" resultType="cn.iocoder.yudao.module.base.vo.QueryBusinessResp"
parameterType="cn.iocoder.yudao.module.base.vo.QueryBusinessReqVO">
SELECT t.*
FROM (
SELECT
c.id AS customerId,
c.cus_name AS customerName,
c.phone_number AS customerPhone,
r.id AS bizId,
r.ticket_no AS bizNo,
r.repair_type AS bizType,
r.in_time AS bizTime,
'repair' AS source,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY r.in_time DESC) AS rn
FROM base_customer_main c
JOIN dl_repair_tickets r ON c.id = r.user_id
WHERE
1 = 1 and c.deleted = 0
<if test="reqVO.dateRange != null">
AND r.in_time BETWEEN #{reqVO.dateRange[0]} AND #{reqVO.dateRange[1]}
</if>
<if test="reqVO.search != null and reqVO.search != ''">
AND (c.cus_name LIKE CONCAT('%', #{reqVO.search}, '%')
OR c.phone_number LIKE CONCAT('%', #{reqVO.search}, '%')
OR r.ticket_no LIKE CONCAT('%', #{reqVO.search}, '%')
)
</if>
<!--
UNION ALL
SELECT
c.id AS customerId,
c.cus_name AS customerName,
c.phone_number AS customerPhone,
ri.id AS bizId,
NULL AS bizNo,
ri.rescue_type AS bizType,
ri.rescue_time AS bizTime,
'rescue' AS source,
ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY ri.rescue_time DESC) AS rn
FROM base_customer_main c
JOIN rescue_info ri ON c.user_id = ri.user_id
WHERE
1 = 1 and c.deleted = 0
<if test="reqVO.dateRange != null">
AND ri.rescue_time BETWEEN #{reqVO.dateRange[0]} AND #{reqVO.dateRange[1]}
</if>
<if test="reqVO.search != null and reqVO.search != ''">
AND (c.cus_name LIKE CONCAT('%', #{reqVO.search}, '%')
OR c.phone_number LIKE CONCAT('%', #{reqVO.search}, '%')
)
</if>-->
) t
WHERE t.rn = 1;
</select>
<select id="listBusinessByCar" resultType="cn.iocoder.yudao.module.base.vo.QueryBusinessResp"
parameterType="cn.iocoder.yudao.module.base.vo.QueryBusinessReqVO">
SELECT t.*
FROM (
-- 维修工单
SELECT
car.id AS carId,
car.license_number carNum,
c.id AS customerId,
c.cus_name AS customerName,
c.phone_number AS customerPhone,
r.id AS bizId,
r.ticket_no AS bizNo,
r.repair_type AS bizType,
r.create_time AS bizTime,
'repair' AS source,
ROW_NUMBER() OVER (PARTITION BY car.id ORDER BY r.in_time DESC) AS rn
FROM base_car_main car
JOIN base_customer_car cc ON car.id = cc.car_id
JOIN base_customer_main c ON cc.cus_id = c.id
JOIN dl_repair_tickets r ON c.id = r.user_id
-- UNION ALL
--
-- -- 救援单
-- SELECT
-- car.id AS car_id,
-- car.license_number,
-- c.id AS customer_id,
-- c.cus_name,
-- c.phone_number,
-- ri.id AS biz_id,
-- NULL AS biz_no,
-- ri.rescue_type AS biz_type,
-- ri.rescue_time AS biz_time,
-- 'rescue' AS source,
-- ROW_NUMBER() OVER (PARTITION BY car.id ORDER BY ri.rescue_time DESC) AS rn
-- FROM base_car_main car
-- JOIN base_customer_car cc ON car.id = cc.car_id
-- JOIN base_customer_main c ON cc.cus_id = c.id
-- JOIN rescue_info ri ON c.id = ri.user_id
-- WHERE ri.rescue_time BETWEEN '2025-01-01 00:00:00' AND '2025-01-31 23:59:59'
) t
WHERE t.rn = 1
<if test="reqVO.dateRange != null">
AND t.biz_time BETWEEN #{reqVO.dateRange[0]} AND #{reqVO.dateRange[1]}
</if>
<if test="reqVO.search != null and reqVO.search != ''">
AND (t.customerName LIKE CONCAT('%', #{reqVO.search}, '%')
OR t.customerPhone LIKE CONCAT('%', #{reqVO.search}, '%')
OR t.carNum LIKE CONCAT('%', #{reqVO.search}, '%')
)
</if>
</select>
</mapper>

View File

@ -60,6 +60,8 @@
<result property="insuranceType" column="insurance_type" />
<result property="jiaoqiang" column="jiaoqiang" />
<result property="shangye" column="shangye" />
<result property="payStatus" column="pay_status" />
<result property="settlementStr" column="settlementStr"/>
</resultMap>
<resultMap id="APPBaseResultMap" type="cn.iocoder.yudao.module.tickets.vo.DlRepairTicketsRespVO">
@ -102,6 +104,7 @@
<result property="partStatus" column="part_status" />
<result property="ticketsWorkStatus" column="tickets_work_status" />
<result property="isFinish" column="is_finish" />
<result property="isPickCar" column="is_pick_car" />
<result property="nowRepairId" column="now_repair_id" />
<result property="nowRepairName" column="now_repair_name" />
<result property="partShow" column="part_show" />
@ -190,19 +193,31 @@
SELECT * FROM dl_repair_booking WHERE tickets_id = #{id}
</select>
<select id="getTicketsPage" resultMap="BaseResultMap">
<include refid="Base_SQL"/>
-- 已结算的
SELECT drt.*
<if test="map.payStatus != null and map.payStatus != '' and map.payStatus == '01'">
,drr.other_data AS settlementStr
</if>
FROM dl_repair_tickets drt
<if test="map.payStatus != null and map.payStatus != '' and map.payStatus == '01'">
LEFT JOIN dl_repair_records drr ON drt.id = drr.ticket_id AND drr.type = 'jssq'
</if>
<where>
<!-- 已结算的 -->
<if test="map.ticketsStatus != null and map.ticketsStatus != '' and map.ticketsStatus == '02'">
and drt.tickets_status in ('08', #{map.ticketsStatus})
</if>
-- 已作废的
<!-- 已作废的 -->
<if test="map.ticketsStatus != null and map.ticketsStatus != '' and map.ticketsStatus == '03'">
and drt.tickets_status = #{map.ticketsStatus}
</if>
-- 待结算的
<!-- 待结算的 -->
<if test="map.ticketsStatus != null and map.ticketsStatus != '' and map.ticketsStatus == '01'">
and (drt.tickets_status = #{map.ticketsStatus})
</if>
<if test="map.payStatus != null and map.payStatus != ''">
and drt.pay_status = #{map.payStatus}
</if>
<if test="map.ticketNo != null and map.ticketNo != ''">
and (
drt.ticket_no like concat('%', #{map.ticketNo}, '%')
@ -217,7 +232,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}
@ -231,16 +246,17 @@
<if test="map.ticketsWorkStatus != null and map.ticketsWorkStatus != ''">
and drt.tickets_work_status = #{map.ticketsWorkStatus}
</if>
</where>
order by drt.create_time desc
</select>
<select id="getPageType" resultMap="APPBaseResultMap">
-- 查待处理数据 --
select drt.*
<if test="map.roleCode=='repair_staff'">
<if test="map.roleCode=='repair_staff'">
-- 维修工,需要判断出当前用户是否可以重新派工 --
, IF(FIND_IN_SET(drt.now_repair_id, #{map.userIdsStr}) > 0,true,false) AS can_operate
</if>
, IF(FIND_IN_SET(drt.now_repair_id, #{map.userIdsStr}) > 0,true,false) AS can_operate
</if>
from dl_repair_tickets drt
<if test="map.cusFrom != null and map.cusFrom!=''">
-- 按客户来源查,需要关联客户表 --
@ -250,6 +266,10 @@
-- 维修工需要关联操作记录,查没有总检记录的工单 --
left join dl_repair_records drr ON drt.id = drr.ticket_id AND drr.type='zj'
</if>
<if test=" map.statisticsType == 'yjg'">
-- 维修工需要关联操作记录,查没有总检记录的工单 --
inner join dl_repair_records drr ON drt.id = drr.ticket_id AND drr.type='zj'
</if>
left join dl_repair_titem drti
on drt.id = drti.ticket_id AND drti.deleted = '0'
where (drt.deleted = '0') AND drt.tickets_status IN ('04','05','01','07','06','02')
@ -267,7 +287,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})
@ -276,6 +296,24 @@
-- 维修工需要关联操作记录,查没有总检记录的工单 --
AND (drr.id IS NULL)
</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 test="map.statisticsType == 'yjg'">
-- 已竣工 --
</if>
</if>
<if test="map.cusFrom != null and map.cusFrom!=''">
<choose>
<when test="map.cusFrom == '06'">
@ -304,12 +342,12 @@
</if>
-- 服务顾问查待处理(服务顾问之间可以相互查) --
<if test="map.nowRepairIds != null and map.nowRepairIds.size > 0">
AND (
drt.now_repair_id in
<foreach collection="map.nowRepairIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
)
AND (
drt.now_repair_id in
<foreach collection="map.nowRepairIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
)
</if>
</when>
<otherwise>
@ -324,7 +362,7 @@
)
</when>
<otherwise>
-- 服务顾问和仓管查待办都是查未结束的工单 --
-- 服务顾问和仓管查待办都是查未结束的工单 --
AND drt.is_finish = '0'
<if test="map.adviserId != null and map.adviserId != ''">
-- 查服务顾问 当前处理人或服务顾问是自己的--
@ -334,7 +372,8 @@
-- 查总检待处理的 --
AND (drt.is_finish = '0')
AND (drt.now_repair_id in
<foreach collection="map.userIds" item="item" index="index" open="(" separator="," close=")">
<foreach collection="map.userIds" item="item" index="index" open="(" separator=","
close=")">
#{item}
</foreach>
)
@ -372,81 +411,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 &gt;= #{map.startDate} and drt.create_time &lt;= #{map.endDate})
AND (drt.create_time &gt;= #{map.startDate} AND drt.create_time &lt;= #{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'
</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>
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
@ -466,4 +532,119 @@
AND drr.create_time &lt;= #{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 &gt;= #{map.startDate} AND drt.create_time &lt;= #{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>

View File

@ -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;
}
}

View File

@ -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(); // 释放资源
}
}
}

View File

@ -1,6 +1,11 @@
package cn.iocoder.yudao.module.system.api.user.dto;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@Data
public class UserDTO {
@ -68,4 +73,19 @@ public class UserDTO {
*/
private String folderId;
// ================== 员工字段 ==================
/** 家庭住址 */
private String address;
private String idNumber;
@JSONField(format = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date safeDate;
@JSONField(format = "yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date formalDate;
}