zzw
18 小时以前 ce841ddf85c48bae32f9e3068403fdfe98ac8711
emergency-repair-server/src/main/java/com/emergencyrepair/business/service/BusinessService.java
@@ -4,22 +4,24 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.emergencyrepair.business.dto.Requests;
import com.emergencyrepair.business.dto.CompletionFaultQueryItem;
import com.emergencyrepair.business.entity.Acceptance;
import com.emergencyrepair.business.entity.CompletionInfo;
import com.emergencyrepair.business.entity.Contractor;
import com.emergencyrepair.business.entity.Fault;
import com.emergencyrepair.business.entity.TeamMajor;
import com.emergencyrepair.business.entity.WorkOrder;
import com.emergencyrepair.business.mapper.AcceptanceMapper;
import com.emergencyrepair.business.mapper.CompletionInfoMapper;
import com.emergencyrepair.business.mapper.ContractorMapper;
import com.emergencyrepair.business.mapper.FaultMapper;
import com.emergencyrepair.business.mapper.TeamMajorMapper;
import com.emergencyrepair.business.mapper.WorkOrderMapper;
import com.emergencyrepair.common.BusinessException;
import com.emergencyrepair.common.StatusConstants;
import com.emergencyrepair.common.audit.CurrentUserContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@@ -30,24 +32,27 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class BusinessService {
    private static final DateTimeFormatter FAULT_DATE_FORMAT = DateTimeFormatter.ofPattern("yyMMdd");
    private static final String FAULT_CODE_PREFIX = "\u6f5c1\u57fa-";
    private static final Object FAULT_CODE_LOCK = new Object();
    @Value("${fault-code.prefix:\u6f5c1\u57fa-}")
    private String faultCodePrefix;
    private final FaultMapper faultMapper;
    private final JdbcTemplate jdbcTemplate;
    private final TeamMajorMapper teamMajorMapper;
    private final WorkOrderMapper workOrderMapper;
    private final ContractorMapper contractorMapper;
    private final CompletionInfoMapper completionInfoMapper;
    private final AcceptanceMapper acceptanceMapper;
    private final ObjectMapper objectMapper;
@@ -168,26 +173,30 @@
    private String generateFaultCode(Fault fault) {
        String xNo = normalizeXNo(fault.getXNo());
        if (xNo == null) {
            throw new BusinessException("请选择X号,格式为3位数字");
            throw new BusinessException("请选择rk_X号,格式为3位数字");
        }
        if (!StringUtils.hasText(fault.getTeamMajor())) {
            throw new BusinessException("请选择T队专业");
            throw new BusinessException("请选择rk_T队专业");
        }
        TeamMajor teamMajor = teamMajorMapper.selectOne(Wrappers.<TeamMajor>lambdaQuery()
                .eq(TeamMajor::getName, fault.getTeamMajor().trim())
                .last("LIMIT 1"));
        if (teamMajor == null) {
            throw new BusinessException("T队专业未配置,请先在系统管理 > T队专业配置中维护:" + fault.getTeamMajor());
            throw new BusinessException("rk_T队专业未配置,请先在系统管理 > rk_T队专业配置中维护:" + fault.getTeamMajor());
        }
        String category = teamMajor.getCategory() == null ? "" : teamMajor.getCategory().trim().toUpperCase();
        if (!"A".equals(category) && !"B".equals(category)) {
            throw new BusinessException("T队专业分类只能为A或B:" + fault.getTeamMajor());
            throw new BusinessException("rk_T队专业分类只能为A或B:" + fault.getTeamMajor());
        }
        String prefix = FAULT_CODE_PREFIX + xNo + "/" + category + "-" + fault.getReportDate().format(FAULT_DATE_FORMAT) + "-";
        String prefix = faultCodePrefix + xNo + "/" + category + "-" + fault.getReportDate().format(FAULT_DATE_FORMAT) + "-";
        int next = 1;
        for (Fault item : faultMapper.selectList(Wrappers.<Fault>lambdaQuery()
                .likeRight(Fault::getFaultCode, prefix))) {
            next = Math.max(next, parseFaultSequence(item.getFaultCode(), prefix) + 1);
        // Include logically deleted rows so a generated code never collides with
        // the unique index on FAULT_CODE.
        List<String> existingCodes = jdbcTemplate.query(
                "SELECT FAULT_CODE FROM LX.T_FAULT WHERE FAULT_CODE LIKE ?",
                new Object[]{prefix + "%"}, (rs, rowNum) -> rs.getString(1));
        for (String existingCode : existingCodes) {
            next = Math.max(next, parseFaultSequence(existingCode, prefix) + 1);
        }
        return prefix + String.format("%03d", next);
    }
@@ -244,7 +253,7 @@
            throw new BusinessException("请填写销项理由");
        }
        String cancelledBy = StringUtils.hasText(request.getCancelledBy())
                ? request.getCancelledBy().trim() : "XCB助理";
                ? request.getCancelledBy().trim() : "rk_XCB助理";
        for (Long id : request.getIds()) {
            Fault fault = requireFault(id);
            if (!StatusConstants.Task.REPORTED_XCB.equals(fault.getTaskStatus())
@@ -281,16 +290,16 @@
        if (request.getContractorIds() == null || request.getContractorIds().isEmpty()) {
            throw new BusinessException("至少分配一个承修单位");
        }
        List<Contractor> contractors = contractorMapper.selectBatchIds(request.getContractorIds());
        Map<Long, Contractor> contractorMap = contractors.stream()
                .collect(Collectors.toMap(Contractor::getId, Function.identity()));
        Map<Long, String> contractorMap = contractorNamesByIds(request.getContractorIds());
        if (contractorMap.isEmpty()) {
            throw new BusinessException("承修单位不存在或未在部门表中配置");
        }
        String contractorIds = request.getContractorIds().stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        String contractorNames = request.getContractorIds().stream()
                .map(contractorMap::get)
                .filter(item -> item != null)
                .map(Contractor::getName)
                .filter(Objects::nonNull)
                .collect(Collectors.joining("、"));
        int index = 0;
@@ -298,7 +307,7 @@
            Fault fault = requireFault(id);
            if (!StatusConstants.Task.REPORTED_XCB.equals(fault.getTaskStatus())
                    && !StatusConstants.Task.RETURNED.equals(fault.getTaskStatus())) {
                throw new BusinessException("故障 " + fault.getFaultCode() + " 当前状态不允许XCB助理审核");
                throw new BusinessException("故障 " + fault.getFaultCode() + " 当前状态不允许rk_XCB助理审核");
            }
            index++;
            fault.setContractorIds(contractorIds);
@@ -325,21 +334,21 @@
            LocalDateTime now = LocalDateTime.now();
            switch (request.getStage()) {
                case "CMB":
                    requireStatus(fault, StatusConstants.Task.XCB_REVIEWED, "CMB领导审批");
                    requireStatus(fault, StatusConstants.Task.XCB_REVIEWED, "rk_CMB领导审批");
                    fault.setCmbOpinion(request.getOpinion());
                    fault.setCmbResult(pass ? "通过" : "不通过");
                    fault.setCmbTime(now);
                    fault.setTaskStatus(pass ? StatusConstants.Task.CMB_APPROVED : StatusConstants.Task.RETURNED);
                    break;
                case "JCC_ASSISTANT":
                    requireStatus(fault, StatusConstants.Task.CMB_APPROVED, "JCC助理审批");
                    requireStatus(fault, StatusConstants.Task.CMB_APPROVED, "rk_JCC助理审批");
                    fault.setJccAssistantOpinion(request.getOpinion());
                    fault.setJccAssistantResult(pass ? "通过" : "不通过");
                    fault.setJccAssistantTime(now);
                    fault.setTaskStatus(pass ? StatusConstants.Task.JCC_ASSISTANT_APPROVED : StatusConstants.Task.RETURNED);
                    break;
                case "JCC_DIRECTOR":
                    requireStatus(fault, StatusConstants.Task.JCC_ASSISTANT_APPROVED, "JCC处长审批");
                    requireStatus(fault, StatusConstants.Task.JCC_ASSISTANT_APPROVED, "rk_JCC处长审批");
                    fault.setJccDirectorOpinion(request.getOpinion());
                    fault.setJccDirectorResult(pass ? "通过" : "不通过");
                    fault.setJccDirectorTime(now);
@@ -373,19 +382,17 @@
            if (exists > 0) {
                throw new BusinessException("故障 " + fault.getFaultCode() + " 已经派单");
            }
            List<Contractor> contractors = contractorMapper.selectBatchIds(contractorIds);
            Map<Long, Contractor> contractorMap = contractors.stream()
                    .collect(Collectors.toMap(Contractor::getId, Function.identity()));
            Map<Long, String> contractorMap = contractorNamesByIds(contractorIds);
            int sequence = 1;
            for (Long contractorId : contractorIds) {
                Contractor contractor = contractorMap.get(contractorId);
                if (contractor == null) {
                String contractorName = contractorMap.get(contractorId);
                if (contractorName == null) {
                    continue;
                }
                WorkOrder workOrder = workOrderFromFault(fault);
                workOrder.setWorkOrderNo(fault.getFaultCode() + "/" + String.format("%02d", sequence++));
                workOrder.setContractorId(contractor.getId());
                workOrder.setContractorName(contractor.getName());
                workOrder.setContractorId(contractorId);
                workOrder.setContractorName(contractorName);
                workOrder.setCompletionStatus(StatusConstants.Completion.UNFINISHED);
                workOrder.setDispatchTime(LocalDateTime.now());
                workOrder.setCreateTime(LocalDateTime.now());
@@ -401,9 +408,172 @@
        return created;
    }
    public List<WorkOrder> listWorkOrders(String status, String keyword) {
    public List<CompletionFaultQueryItem> listCompletionQueries(String keyword, String completionStatus,
                                                               String contractorName, LocalDate startDate,
                                                               LocalDate endDate) {
        List<CompletionInfo> completions = completionInfoMapper.selectList(Wrappers.<CompletionInfo>lambdaQuery()
                .orderByDesc(CompletionInfo::getSubmitTime).orderByDesc(CompletionInfo::getUpdateTime));
        if (completions.isEmpty()) {
            return Collections.emptyList();
        }
        Set<Long> workOrderIds = completions.stream().map(CompletionInfo::getWorkOrderId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        if (workOrderIds.isEmpty()) {
            return Collections.emptyList();
        }
        Map<Long, WorkOrder> workOrderMap = workOrderMapper.selectBatchIds(workOrderIds).stream()
                .collect(Collectors.toMap(WorkOrder::getId, item -> item));
        Set<Long> faultIds = workOrderMap.values().stream().map(WorkOrder::getFaultId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        if (faultIds.isEmpty()) {
            return Collections.emptyList();
        }
        Map<Long, Fault> faultMap = faultMapper.selectBatchIds(faultIds).stream()
                .collect(Collectors.toMap(Fault::getId, item -> item));
        String normalizedKeyword = StringUtils.hasText(keyword) ? keyword.trim().toLowerCase() : null;
        String normalizedContractor = StringUtils.hasText(contractorName) ? contractorName.trim().toLowerCase() : null;
        Map<Long, CompletionFaultQueryItem> grouped = new LinkedHashMap<>();
        for (CompletionInfo completion : completions) {
            WorkOrder workOrder = workOrderMap.get(completion.getWorkOrderId());
            if (workOrder == null) {
                continue;
            }
            if (StringUtils.hasText(completionStatus)
                    && !completionStatus.equalsIgnoreCase(workOrder.getCompletionStatus())) {
                continue;
            }
            if (normalizedContractor != null && !containsIgnoreCase(workOrder.getContractorName(), normalizedContractor)) {
                continue;
            }
            LocalDate completionDate = completion.getFinishDate() != null
                    ? completion.getFinishDate()
                    : (completion.getSubmitTime() == null ? null : completion.getSubmitTime().toLocalDate());
            if (startDate != null && (completionDate == null || completionDate.isBefore(startDate))) {
                continue;
            }
            if (endDate != null && (completionDate == null || completionDate.isAfter(endDate))) {
                continue;
            }
            Fault fault = faultMap.get(workOrder.getFaultId());
            if (fault == null) {
                continue;
            }
            CompletionFaultQueryItem.WorkOrderCompletionItem workOrderItem = buildCompletionWorkOrderItem(workOrder, completion);
            if (normalizedKeyword != null && !matchesCompletionKeyword(fault, workOrderItem, normalizedKeyword)) {
                continue;
            }
            CompletionFaultQueryItem item = grouped.get(fault.getId());
            if (item == null) {
                item = new CompletionFaultQueryItem();
                item.setFaultId(String.valueOf(fault.getId()));
                item.setFaultCode(fault.getFaultCode());
                item.setXNo(fault.getXNo());
                item.setEquipmentName(fault.getEquipmentName());
                item.setParentSystem(fault.getParentSystem());
                item.setEngineeringMajor(fault.getEngineeringMajor());
                item.setTeamMajor(fault.getTeamMajor());
                item.setReportUnit(fault.getReportUnit());
                item.setReportDate(fault.getReportDate());
                item.setOwner(fault.getOwner());
                item.setEngineeringNameDesc(fault.getEngineeringNameDesc());
                item.setTaskStatus(fault.getTaskStatus());
                grouped.put(fault.getId(), item);
            }
            item.getWorkOrders().add(workOrderItem);
        }
        List<CompletionFaultQueryItem> result = new ArrayList<>(grouped.values());
        result.forEach(item -> item.getWorkOrders().sort(Comparator.comparing(
                CompletionFaultQueryItem.WorkOrderCompletionItem::getWorkOrderNo,
                Comparator.nullsLast(String::compareTo))));
        result.sort(Comparator.comparing(CompletionFaultQueryItem::getReportDate,
                Comparator.nullsLast(Comparator.reverseOrder())));
        return result;
    }
    private CompletionFaultQueryItem.WorkOrderCompletionItem buildCompletionWorkOrderItem(
            WorkOrder workOrder, CompletionInfo completion) {
        CompletionFaultQueryItem.WorkOrderCompletionItem item = new CompletionFaultQueryItem.WorkOrderCompletionItem();
        item.setWorkOrderId(String.valueOf(workOrder.getId()));
        item.setWorkOrderNo(workOrder.getWorkOrderNo());
        item.setContractorName(workOrder.getContractorName());
        item.setCompletionStatus(workOrder.getCompletionStatus());
        item.setEngineeringNo(completion.getEngineeringNo());
        item.setStartDate(completion.getStartDate());
        item.setFinishDate(completion.getFinishDate());
        item.setContractorPerson(completion.getContractorPerson());
        item.setTInternalConstruction(completion.getTInternalConstruction());
        item.setRelatedProject(completion.getRelatedProject());
        item.setDurationDays(completion.getDurationDays());
        item.setPersonCount(completion.getPersonCount());
        item.setAcceptanceMode(completion.getAcceptanceMode());
        item.setQualityInfo(completion.getQualityInfo());
        item.setReasonAnalysis(completion.getReasonAnalysis());
        item.setFaultDescription(completion.getFaultDescription());
        item.setItemName(completion.getItemName());
        item.setSubmitTime(completion.getSubmitTime());
        List<Requests.SolutionItem> solutions = parseJsonList(completion.getSolutionsJson(), Requests.SolutionItem.class);
        if (solutions.isEmpty() && StringUtils.hasText(completion.getSolution())) {
            Requests.SolutionItem legacy = new Requests.SolutionItem();
            legacy.setContent(completion.getSolution());
            solutions.add(legacy);
        }
        item.setSolutions(solutions);
        item.setMaterials(parseJsonList(completion.getConsumablesJson(), Requests.MaterialItem.class));
        return item;
    }
    private boolean matchesCompletionKeyword(Fault fault, CompletionFaultQueryItem.WorkOrderCompletionItem item,
                                             String keyword) {
        if (containsIgnoreCase(fault.getFaultCode(), keyword)
                || containsIgnoreCase(fault.getXNo(), keyword)
                || containsIgnoreCase(fault.getEquipmentName(), keyword)
                || containsIgnoreCase(fault.getEngineeringNameDesc(), keyword)
                || containsIgnoreCase(fault.getReportUnit(), keyword)
                || containsIgnoreCase(item.getWorkOrderNo(), keyword)
                || containsIgnoreCase(item.getContractorName(), keyword)
                || containsIgnoreCase(item.getEngineeringNo(), keyword)
                || containsIgnoreCase(item.getItemName(), keyword)
                || containsIgnoreCase(item.getContractorPerson(), keyword)) {
            return true;
        }
        return item.getSolutions().stream().anyMatch(solution -> containsIgnoreCase(solution.getContent(), keyword))
                || item.getMaterials().stream().anyMatch(material -> containsIgnoreCase(material.getName(), keyword)
                || containsIgnoreCase(material.getSpecification(), keyword));
    }
    private boolean containsIgnoreCase(String value, String keyword) {
        return StringUtils.hasText(value) && value.toLowerCase().contains(keyword);
    }
    private <T> List<T> parseJsonList(String json, Class<T> type) {
        if (!StringUtils.hasText(json)) {
            return new ArrayList<>();
        }
        try {
            return objectMapper.readValue(json, objectMapper.getTypeFactory().constructCollectionType(List.class, type));
        } catch (Exception exception) {
            return new ArrayList<>();
        }
    }
    public List<WorkOrder> listWorkOrders(String status, String keyword, boolean completionReportable) {
        LambdaQueryWrapper<WorkOrder> query = Wrappers.lambdaQuery();
        if (StringUtils.hasText(status)) {
        if (completionReportable) {
            query.eq(WorkOrder::getCompletionStatus, StatusConstants.Completion.COMPLETED)
                    .notInSql(WorkOrder::getId, "SELECT WORK_ORDER_ID FROM LX.T_COMPLETION_INFO WHERE DEL_FLAG = 0 OR DEL_FLAG IS NULL");
            List<String> unitNames = currentUserContractorNames();
            if (unitNames != null) {
                if (unitNames.isEmpty()) {
                    return Collections.emptyList();
                }
                query.in(WorkOrder::getContractorName, unitNames);
            }
        } else if (StringUtils.hasText(status)) {
            query.in(WorkOrder::getCompletionStatus, Arrays.asList(status.split(",")));
        }
        if (StringUtils.hasText(keyword)) {
@@ -417,6 +587,54 @@
        return workOrderMapper.selectList(query);
    }
    /**
     * Returns null when the current user sees every unit (system administrator).
     * Otherwise returns the contractor names matching the logged-in user's department.
     */
    private List<String> currentUserContractorNames() {
        String username = CurrentUserContext.get();
        if (!StringUtils.hasText(username) || "admin".equalsIgnoreCase(username)) {
            return null;
        }
        List<Map<String, Object>> rows = jdbcTemplate.query(
                "SELECT d.DEPT_NAME, d.DEPT_TYPE FROM CSICZB_ZB.SYS_USER u "
                        + "LEFT JOIN CSICZB_ZB.SYS_DEPT d ON d.DEPT_ID = u.DEPT_ID "
                        + "WHERE u.USER_NAME = ? AND (u.DEL_FLAG = '0' OR u.DEL_FLAG IS NULL)",
                new Object[]{username}, (rs, rowNum) -> {
                    Map<String, Object> row = new LinkedHashMap<>();
                    row.put("deptName", rs.getString("DEPT_NAME"));
                    row.put("deptType", rs.getString("DEPT_TYPE"));
                    return row;
                });
        if (rows.isEmpty() || !StringUtils.hasText((String) rows.get(0).get("deptName"))) {
            return Collections.emptyList();
        }
        Map<String, Object> dept = rows.get(0);
        if (!"cj".equalsIgnoreCase(String.valueOf(dept.get("deptType")))) {
            return Collections.emptyList();
        }
        return Collections.singletonList(((String) dept.get("deptName")).trim());
    }
    private Map<Long, String> contractorNamesByIds(Collection<Long> ids) {
        Map<Long, String> result = new LinkedHashMap<>();
        if (ids == null || ids.isEmpty()) {
            return result;
        }
        String placeholders = ids.stream().map(item -> "?").collect(Collectors.joining(","));
        List<Object> args = new ArrayList<>(ids);
        return jdbcTemplate.query(
                "SELECT DEPT_ID, DEPT_NAME FROM CSICZB_ZB.SYS_DEPT "
                        + "WHERE DEPT_TYPE = 'cj' AND (DEL_FLAG = '0' OR DEL_FLAG IS NULL) "
                        + "AND DEPT_ID IN (" + placeholders + ")",
                args.toArray(), rs -> {
                    while (rs.next()) {
                        result.put(rs.getLong("DEPT_ID"), rs.getString("DEPT_NAME"));
                    }
                    return result;
                });
    }
    public WorkOrder getWorkOrder(Long id) {
        return workOrderMapper.selectById(id);
    }
@@ -424,13 +642,15 @@
    @Transactional
    public void updateCompletionStatus(Requests.StatusRequest request) {
        WorkOrder workOrder = requireWorkOrder(request.getId());
        if (StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())) {
        if (StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())
                && !StatusConstants.Completion.COMPLETED.equals(request.getStatus())) {
            throw new BusinessException("已完工派工单不能退回普通完工状态");
        }
        if (!Arrays.asList(
                StatusConstants.Completion.UNFINISHED,
                StatusConstants.Completion.WAIT_PARTS,
                StatusConstants.Completion.WAIT_TEST).contains(request.getStatus())) {
                StatusConstants.Completion.WAIT_TEST,
                StatusConstants.Completion.COMPLETED).contains(request.getStatus())) {
            throw new BusinessException("完工状态不合法");
        }
        workOrder.setCompletionStatus(request.getStatus());
@@ -453,21 +673,22 @@
    @Transactional
    public CompletionInfo submitCompletion(Requests.CompletionRequest request) {
        WorkOrder workOrder = requireWorkOrder(request.getWorkOrderId());
        if (StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())) {
        CompletionInfo info = completionInfoMapper.selectOne(Wrappers.<CompletionInfo>lambdaQuery()
                .eq(CompletionInfo::getWorkOrderId, workOrder.getId()).last("LIMIT 1"));
        if (info != null) {
            throw new BusinessException("该派工单已完工上报,不能重复提交");
        }
        if (!StatusConstants.Completion.COMPLETED.equals(workOrder.getCompletionStatus())) {
            throw new BusinessException("该派工单尚未在完工情况处理中确认完工,不能填报完工信息");
        }
        LocalDateTime now = LocalDateTime.now();
        workOrder.setCompletionStatus(StatusConstants.Completion.COMPLETED);
        workOrder.setUpdateTime(now);
        workOrderMapper.updateById(workOrder);
        CompletionInfo info = completionInfoMapper.selectOne(Wrappers.<CompletionInfo>lambdaQuery()
                .eq(CompletionInfo::getWorkOrderId, workOrder.getId()).last("LIMIT 1"));
        if (info == null) {
            info = new CompletionInfo();
            info.setWorkOrderId(workOrder.getId());
            info.setCreateTime(now);
        }
        info = new CompletionInfo();
        info.setWorkOrderId(workOrder.getId());
        info.setCreateTime(now);
        info.setWorkOrderNo(workOrder.getWorkOrderNo());
        info.setXNo(workOrder.getXNo());
        info.setContractorName(workOrder.getContractorName());
@@ -713,7 +934,7 @@
            ids.add(fault.getEquipmentId());
        }
        if (ids.isEmpty()) {
            throw new BusinessException("请选择装备名称");
            throw new BusinessException("请选择设备名称");
        }
        List<Long> normalized = new ArrayList<>(ids);
        fault.setEquipmentIds(normalized.stream().map(String::valueOf).collect(Collectors.joining(",")));