package com.ruoyi.ai.tools;
|
|
import com.alibaba.fastjson2.JSON;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
|
import com.ruoyi.ai.context.AiSessionUserContext;
|
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.device.mapper.DeviceDefectRecordMapper;
|
import com.ruoyi.device.mapper.DeviceLedgerMapper;
|
import com.ruoyi.device.mapper.DeviceRepairMapper;
|
import com.ruoyi.device.pojo.DeviceDefectRecord;
|
import com.ruoyi.device.pojo.DeviceLedger;
|
import com.ruoyi.device.pojo.DeviceRepair;
|
import com.ruoyi.framework.security.LoginUser;
|
import com.ruoyi.procurementrecord.mapper.ProcurementExceptionRecordMapper;
|
import com.ruoyi.procurementrecord.pojo.ProcurementExceptionRecord;
|
import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
|
import com.ruoyi.production.mapper.ProductionOrderMapper;
|
import com.ruoyi.production.mapper.ProductionPlanMapper;
|
import com.ruoyi.production.mapper.ProductionProductMainMapper;
|
import com.ruoyi.production.pojo.ProductionOperationTask;
|
import com.ruoyi.production.pojo.ProductionOrder;
|
import com.ruoyi.production.pojo.ProductionPlan;
|
import com.ruoyi.production.pojo.ProductionProductMain;
|
import com.ruoyi.quality.mapper.QualityInspectMapper;
|
import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
|
import com.ruoyi.quality.pojo.QualityInspect;
|
import com.ruoyi.quality.pojo.QualityUnqualified;
|
import com.ruoyi.stock.mapper.StockInventoryMapper;
|
import com.ruoyi.stock.pojo.StockInventory;
|
import dev.langchain4j.agent.tool.P;
|
import dev.langchain4j.agent.tool.Tool;
|
import dev.langchain4j.agent.tool.ToolMemoryId;
|
import org.springframework.stereotype.Component;
|
import org.springframework.util.StringUtils;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.ZoneId;
|
import java.time.format.DateTimeFormatter;
|
import java.time.temporal.ChronoUnit;
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.stream.Collectors;
|
|
@Component
|
public class ManufacturingAgentTools {
|
|
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
private static final int DEFAULT_LIMIT = 10;
|
private static final int MAX_LIMIT = 30;
|
private static final int DEVICE_REPAIR_STATUS_PENDING = 0;
|
|
private final ProductionPlanMapper productionPlanMapper;
|
private final ProductionOrderMapper productionOrderMapper;
|
private final ProductionOperationTaskMapper productionOperationTaskMapper;
|
private final ProductionProductMainMapper productionProductMainMapper;
|
private final DeviceLedgerMapper deviceLedgerMapper;
|
private final DeviceRepairMapper deviceRepairMapper;
|
private final DeviceDefectRecordMapper deviceDefectRecordMapper;
|
private final QualityInspectMapper qualityInspectMapper;
|
private final QualityUnqualifiedMapper qualityUnqualifiedMapper;
|
private final StockInventoryMapper stockInventoryMapper;
|
private final ProcurementExceptionRecordMapper procurementExceptionRecordMapper;
|
private final AiSessionUserContext aiSessionUserContext;
|
|
public ManufacturingAgentTools(ProductionPlanMapper productionPlanMapper,
|
ProductionOrderMapper productionOrderMapper,
|
ProductionOperationTaskMapper productionOperationTaskMapper,
|
ProductionProductMainMapper productionProductMainMapper,
|
DeviceLedgerMapper deviceLedgerMapper,
|
DeviceRepairMapper deviceRepairMapper,
|
DeviceDefectRecordMapper deviceDefectRecordMapper,
|
QualityInspectMapper qualityInspectMapper,
|
QualityUnqualifiedMapper qualityUnqualifiedMapper,
|
StockInventoryMapper stockInventoryMapper,
|
ProcurementExceptionRecordMapper procurementExceptionRecordMapper,
|
AiSessionUserContext aiSessionUserContext) {
|
this.productionPlanMapper = productionPlanMapper;
|
this.productionOrderMapper = productionOrderMapper;
|
this.productionOperationTaskMapper = productionOperationTaskMapper;
|
this.productionProductMainMapper = productionProductMainMapper;
|
this.deviceLedgerMapper = deviceLedgerMapper;
|
this.deviceRepairMapper = deviceRepairMapper;
|
this.deviceDefectRecordMapper = deviceDefectRecordMapper;
|
this.qualityInspectMapper = qualityInspectMapper;
|
this.qualityUnqualifiedMapper = qualityUnqualifiedMapper;
|
this.stockInventoryMapper = stockInventoryMapper;
|
this.procurementExceptionRecordMapper = procurementExceptionRecordMapper;
|
this.aiSessionUserContext = aiSessionUserContext;
|
}
|
|
@Tool(name = "查询制造业务域数据", value = "按业务域查询生产现场、计划、工单、设备、质量、物料、异常处理相关数据。")
|
public String queryDomain(@ToolMemoryId String memoryId,
|
@P(value = "业务域,site/plan/workorder/device/quality/material/exception") String domain,
|
@P(value = "关键字,可不传", required = false) String keyword,
|
@P(value = "返回条数,默认10,最大30", required = false) Integer limit,
|
@P(value = "开始日期 yyyy-MM-dd,可不传", required = false) String startDate,
|
@P(value = "结束日期 yyyy-MM-dd,可不传", required = false) String endDate,
|
@P(value = "时间范围描述,例如今年、本月、近30天", required = false) String timeRange) {
|
LoginUser loginUser = currentLoginUser(memoryId);
|
int finalLimit = normalizeLimit(limit);
|
DateRange range = resolveDateRange(startDate, endDate, timeRange);
|
boolean hasTimeConstraint = hasTimeConstraint(startDate, endDate, timeRange);
|
String normalizedDomain = normalizeDomain(domain);
|
|
return switch (normalizedDomain) {
|
case "site" -> siteSnapshot(loginUser, range);
|
case "plan" -> listProductionPlans(loginUser, keyword, finalLimit, range);
|
case "workorder" -> listWorkOrders(loginUser, keyword, finalLimit, range);
|
case "device" -> isRepairIntent(keyword, timeRange)
|
? listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint)
|
: listDevices(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit);
|
case "repair" -> listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint);
|
case "quality" -> listQualityIssues(loginUser, keyword, finalLimit, range);
|
case "material" -> listMaterialInventory(loginUser, keyword, finalLimit);
|
case "exception" -> listExceptions(loginUser, keyword, finalLimit, range);
|
default -> jsonResponse(false, "manufacturing_query", "不支持的业务域: " + safe(domain), Map.of(), Map.of(), Map.of());
|
};
|
}
|
|
@Tool(name = "制造预警看板", value = "计算计划、工单、设备、质量、物料、异常处理的预警信息。")
|
public String getWarningBoard(@ToolMemoryId String memoryId,
|
@P(value = "开始日期 yyyy-MM-dd,可不传", required = false) String startDate,
|
@P(value = "结束日期 yyyy-MM-dd,可不传", required = false) String endDate,
|
@P(value = "时间范围描述,例如今天、本周、本月、近30天", required = false) String timeRange) {
|
LoginUser loginUser = currentLoginUser(memoryId);
|
DateRange range = resolveDateRange(startDate, endDate, timeRange);
|
LocalDate today = LocalDate.now();
|
|
long overduePlanCount = countOverduePlans(loginUser, today);
|
long overdueWorkOrderCount = countOverdueWorkOrders(loginUser, today);
|
long pendingRepairCount = countPendingRepairs(loginUser);
|
long qualityOpenCount = countOpenQualityIssues(loginUser, range);
|
long lowStockCount = countLowStock(loginUser);
|
long exceptionCount = countExceptionRecords(loginUser, range);
|
|
List<Map<String, Object>> warningItems = new ArrayList<>();
|
if (overduePlanCount > 0) {
|
warningItems.add(warningItem("high", "计划逾期", overduePlanCount, "有生产计划超过需求日期仍未完成"));
|
}
|
if (overdueWorkOrderCount > 0) {
|
warningItems.add(warningItem("high", "工单逾期", overdueWorkOrderCount, "有工单计划结束日期已过仍未完工"));
|
}
|
if (pendingRepairCount > 0) {
|
warningItems.add(warningItem("medium", "设备待维修", pendingRepairCount, "存在待维修/维修中的设备"));
|
}
|
if (qualityOpenCount > 0) {
|
warningItems.add(warningItem("high", "质量未闭环", qualityOpenCount, "存在未处理完成的不合格记录"));
|
}
|
if (lowStockCount > 0) {
|
warningItems.add(warningItem("medium", "物料低库存", lowStockCount, "库存数量低于或等于预警阈值"));
|
}
|
if (exceptionCount > 0) {
|
warningItems.add(warningItem("medium", "异常记录", exceptionCount, "时间范围内存在异常处理记录"));
|
}
|
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("timeRange", range.label());
|
summary.put("startDate", range.start().toString());
|
summary.put("endDate", range.end().toString());
|
summary.put("warningCount", warningItems.size());
|
summary.put("overduePlanCount", overduePlanCount);
|
summary.put("overdueWorkOrderCount", overdueWorkOrderCount);
|
summary.put("pendingRepairCount", pendingRepairCount);
|
summary.put("qualityOpenCount", qualityOpenCount);
|
summary.put("lowStockCount", lowStockCount);
|
summary.put("exceptionCount", exceptionCount);
|
|
return jsonResponse(true, "manufacturing_warning", "已返回制造预警看板。", summary,
|
Map.of("items", warningItems), Map.of());
|
}
|
|
@Tool(name = "制造经营分析", value = "按时间范围输出制造关键指标,支持查、问、分析场景。")
|
public String analyzeFactory(@ToolMemoryId String memoryId,
|
@P(value = "开始日期 yyyy-MM-dd,可不传", required = false) String startDate,
|
@P(value = "结束日期 yyyy-MM-dd,可不传", required = false) String endDate,
|
@P(value = "时间范围描述,例如本月、近30天", required = false) String timeRange) {
|
LoginUser loginUser = currentLoginUser(memoryId);
|
DateRange range = resolveDateRange(startDate, endDate, timeRange);
|
|
long planTotal = countPlans(loginUser, range);
|
long planCompleted = countPlansByStatus(loginUser, range, 2);
|
long workOrderTotal = countWorkOrders(loginUser, range);
|
long workOrderCompleted = countWorkOrdersByStatus(loginUser, range, 2);
|
long workOrderInProgress = countWorkOrdersByStatus(loginUser, range, 1);
|
|
long outputCount = countOutputs(loginUser, range);
|
long deviceTotal = countDevices(loginUser);
|
long pendingRepairCount = countPendingRepairs(loginUser);
|
long qualityInspectTotal = countQualityInspect(loginUser, range);
|
long qualityNgCount = countOpenQualityIssues(loginUser, range);
|
long materialSkuCount = countInventorySku(loginUser);
|
long lowStockCount = countLowStock(loginUser);
|
long exceptionCount = countExceptionRecords(loginUser, range);
|
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("timeRange", range.label());
|
summary.put("startDate", range.start().toString());
|
summary.put("endDate", range.end().toString());
|
summary.put("planTotal", planTotal);
|
summary.put("planCompleted", planCompleted);
|
summary.put("planCompletionRate", toRate(planCompleted, planTotal));
|
summary.put("workOrderTotal", workOrderTotal);
|
summary.put("workOrderCompleted", workOrderCompleted);
|
summary.put("workOrderInProgress", workOrderInProgress);
|
summary.put("workOrderCompletionRate", toRate(workOrderCompleted, workOrderTotal));
|
summary.put("outputCount", outputCount);
|
summary.put("deviceTotal", deviceTotal);
|
summary.put("pendingRepairCount", pendingRepairCount);
|
summary.put("qualityInspectTotal", qualityInspectTotal);
|
summary.put("qualityNgCount", qualityNgCount);
|
summary.put("qualityIssueRate", toRate(qualityNgCount, qualityInspectTotal));
|
summary.put("materialSkuCount", materialSkuCount);
|
summary.put("lowStockCount", lowStockCount);
|
summary.put("exceptionCount", exceptionCount);
|
|
List<Map<String, Object>> coreMetrics = List.of(
|
metric("计划完成率", toRate(planCompleted, planTotal)),
|
metric("工单完成率", toRate(workOrderCompleted, workOrderTotal)),
|
metric("质量异常率", toRate(qualityNgCount, qualityInspectTotal)),
|
metric("低库存占比", toRate(lowStockCount, materialSkuCount))
|
);
|
|
Map<String, Object> charts = new LinkedHashMap<>();
|
charts.put("domainBarOption", buildDomainBarOption(summary));
|
charts.put("qualityPieOption", buildQualityPieOption(qualityInspectTotal, qualityNgCount));
|
|
return jsonResponse(true, "manufacturing_analysis", "已返回制造分析结果。", summary,
|
Map.of("coreMetrics", coreMetrics), charts);
|
}
|
|
@Tool(name = "生成制造办理建议", value = "根据用户问题输出可执行的办理动作建议,包括目标业务接口、必填字段和示例。")
|
public String planActions(@ToolMemoryId String memoryId,
|
@P("用户诉求原文") String userQuery) {
|
LoginUser loginUser = currentLoginUser(memoryId);
|
List<Map<String, Object>> actionCards = new ArrayList<>();
|
|
if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "工单", "派工", "作业")) {
|
actionCards.add(actionCard(
|
"workorder_assign",
|
"工单派工",
|
"POST",
|
"/productionOperationTask/assign",
|
List.of("id", "userIds"),
|
Map.of("id", 10001, "userIds", "12,13"),
|
"将工单分配给指定人员,适用于现场调度。"));
|
}
|
if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "设备", "维修", "故障")) {
|
actionCards.add(actionCard(
|
"device_repair_create",
|
"创建设备维修单",
|
"POST",
|
"/device/repair",
|
List.of("deviceLedgerId", "deviceName", "repairName", "remark"),
|
Map.of("deviceLedgerId", 1001, "deviceName", "空压机A-01", "repairName", "张三", "remark", "异响并伴随温升"),
|
"新建维修单,进入设备异常处理闭环。"));
|
}
|
if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "质量", "不合格", "闭环")) {
|
actionCards.add(actionCard(
|
"quality_unqualified_deal",
|
"处理不合格单",
|
"POST",
|
"/quality/qualityUnqualified/deal",
|
List.of("id", "dealResult", "dealName"),
|
Map.of("id", 3001, "dealResult", "返工后复检", "dealName", "李四"),
|
"对不合格记录执行处置并闭环。"));
|
}
|
if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "物料", "库存", "补料")) {
|
actionCards.add(actionCard(
|
"material_inbound",
|
"补充库存",
|
"POST",
|
"/stockInventory/addstockInventory",
|
List.of("productModelId", "batchNo", "qualitity"),
|
Map.of("productModelId", 5001, "batchNo", "B2026051601", "qualitity", 120),
|
"当低库存预警触发时,增加库存数量。"));
|
}
|
if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "异常", "采购异常", "来料异常")) {
|
actionCards.add(actionCard(
|
"procurement_exception_add",
|
"登记异常记录",
|
"POST",
|
"/procurementExceptionRecord/add",
|
List.of("purchaseLedgerId", "exceptionReason", "exceptionNum"),
|
Map.of("purchaseLedgerId", 888, "exceptionReason", "到料短缺", "exceptionNum", 24),
|
"登记采购/来料异常,便于后续追踪和分析。"));
|
}
|
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("actionCount", actionCards.size());
|
summary.put("userId", loginUser.getUserId());
|
summary.put("tenantId", loginUser.getTenantId());
|
|
return jsonResponse(true, "manufacturing_action_plan", "已生成办理建议,请前端引导用户确认后调用目标业务接口。",
|
summary, Map.of("actionCards", actionCards), Map.of());
|
}
|
|
private String siteSnapshot(LoginUser loginUser, DateRange range) {
|
long planTotal = countPlans(loginUser, range);
|
long workOrderTotal = countWorkOrders(loginUser, range);
|
long outputCount = countOutputs(loginUser, range);
|
long deviceTotal = countDevices(loginUser);
|
long pendingRepairCount = countPendingRepairs(loginUser);
|
long qualityOpenCount = countOpenQualityIssues(loginUser, range);
|
long lowStockCount = countLowStock(loginUser);
|
long exceptionCount = countExceptionRecords(loginUser, range);
|
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("timeRange", range.label());
|
summary.put("startDate", range.start().toString());
|
summary.put("endDate", range.end().toString());
|
summary.put("planTotal", planTotal);
|
summary.put("workOrderTotal", workOrderTotal);
|
summary.put("outputCount", outputCount);
|
summary.put("deviceTotal", deviceTotal);
|
summary.put("pendingRepairCount", pendingRepairCount);
|
summary.put("qualityOpenCount", qualityOpenCount);
|
summary.put("lowStockCount", lowStockCount);
|
summary.put("exceptionCount", exceptionCount);
|
|
return jsonResponse(true, "manufacturing_site_snapshot", "已返回生产现场概览。", summary, Map.of(), Map.of());
|
}
|
|
private String listProductionPlans(LoginUser loginUser, String keyword, int limit, DateRange range) {
|
LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
|
wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end());
|
if (StringUtils.hasText(keyword)) {
|
wrapper.and(w -> w.like(ProductionPlan::getMpsNo, keyword)
|
.or().like(ProductionPlan::getRemark, keyword)
|
.or().like(ProductionPlan::getSource, keyword));
|
}
|
wrapper.orderByDesc(ProductionPlan::getRequiredDate, ProductionPlan::getId).last("limit " + limit);
|
|
List<Map<String, Object>> items = defaultList(productionPlanMapper.selectList(wrapper)).stream()
|
.map(this::toPlanItem)
|
.collect(Collectors.toList());
|
return jsonResponse(true, "manufacturing_plan_list", "已返回生产计划列表。",
|
rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
|
}
|
|
private String listWorkOrders(LoginUser loginUser, String keyword, int limit, DateRange range) {
|
LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
|
wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
|
.le(ProductionOperationTask::getPlanEndTime, range.end());
|
if (StringUtils.hasText(keyword)) {
|
wrapper.and(w -> w.like(ProductionOperationTask::getWorkOrderNo, keyword)
|
.or().like(ProductionOperationTask::getUserIds, keyword));
|
}
|
wrapper.orderByDesc(ProductionOperationTask::getPlanEndTime, ProductionOperationTask::getId)
|
.last("limit " + limit);
|
|
List<Map<String, Object>> items = defaultList(productionOperationTaskMapper.selectList(wrapper)).stream()
|
.map(this::toWorkOrderItem)
|
.collect(Collectors.toList());
|
return jsonResponse(true, "manufacturing_workorder_list", "已返回工单列表。",
|
rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
|
}
|
|
private String listDevices(LoginUser loginUser, String keyword, int limit) {
|
LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
|
if (StringUtils.hasText(keyword)) {
|
wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword)
|
.or().like(DeviceLedger::getDeviceModel, keyword)
|
.or().like(DeviceLedger::getDeviceBrand, keyword));
|
}
|
wrapper.orderByDesc(DeviceLedger::getId).last("limit " + limit);
|
|
Map<Long, Long> pendingRepairMap = pendingRepairCountByDevice(loginUser);
|
List<Map<String, Object>> items = defaultList(deviceLedgerMapper.selectList(wrapper)).stream()
|
.map(item -> toDeviceItem(item, pendingRepairMap.getOrDefault(item.getId(), 0L)))
|
.collect(Collectors.toList());
|
return jsonResponse(true, "manufacturing_device_list", "已返回设备列表。",
|
Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of());
|
}
|
|
private String listDeviceRepairs(LoginUser loginUser, String keyword, int limit, DateRange range, boolean hasTimeConstraint) {
|
LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
|
Long currentDeptId = loginUser.getCurrentDeptId();
|
if (currentDeptId != null) {
|
wrapper.and(w -> w.eq(DeviceRepair::getDeptId, currentDeptId).or().isNull(DeviceRepair::getDeptId));
|
}
|
if (hasTimeConstraint) {
|
wrapper.ge(DeviceRepair::getCreateTime, range.start().atStartOfDay())
|
.lt(DeviceRepair::getCreateTime, range.end().plusDays(1).atStartOfDay());
|
}
|
if (StringUtils.hasText(keyword)) {
|
List<Long> matchedDeviceIds = findDeviceLedgerIdsByKeyword(loginUser, keyword);
|
wrapper.and(w -> {
|
w.like(DeviceRepair::getDeviceName, keyword)
|
.or().like(DeviceRepair::getDeviceModel, keyword)
|
.or().like(DeviceRepair::getRemark, keyword)
|
.or().like(DeviceRepair::getRepairName, keyword)
|
.or().like(DeviceRepair::getMaintenanceName, keyword);
|
if (!matchedDeviceIds.isEmpty()) {
|
w.or().in(DeviceRepair::getDeviceLedgerId, matchedDeviceIds);
|
}
|
});
|
}
|
wrapper.orderByDesc(DeviceRepair::getCreateTime, DeviceRepair::getId).last("limit " + limit);
|
|
List<Map<String, Object>> items = defaultList(deviceRepairMapper.selectList(wrapper)).stream()
|
.map(this::toDeviceRepairItem)
|
.collect(Collectors.toList());
|
return jsonResponse(true, "manufacturing_device_repair_list", "已返回设备维修记录。",
|
rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
|
}
|
|
private String listQualityIssues(LoginUser loginUser, String keyword, int limit, DateRange range) {
|
LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId);
|
wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start()))
|
.lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end()));
|
if (StringUtils.hasText(keyword)) {
|
wrapper.and(w -> w.like(QualityUnqualified::getProductName, keyword)
|
.or().like(QualityUnqualified::getDefectivePhenomena, keyword)
|
.or().like(QualityUnqualified::getDealResult, keyword));
|
}
|
wrapper.orderByDesc(QualityUnqualified::getCheckTime, QualityUnqualified::getId).last("limit " + limit);
|
|
List<Map<String, Object>> items = defaultList(qualityUnqualifiedMapper.selectList(wrapper)).stream()
|
.map(this::toQualityItem)
|
.collect(Collectors.toList());
|
return jsonResponse(true, "manufacturing_quality_list", "已返回质量异常列表。",
|
rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
|
}
|
|
private String listMaterialInventory(LoginUser loginUser, String keyword, int limit) {
|
LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
|
if (StringUtils.hasText(keyword)) {
|
wrapper.and(w -> w.like(StockInventory::getBatchNo, keyword)
|
.or().like(StockInventory::getProductModelId, keyword));
|
}
|
wrapper.orderByDesc(StockInventory::getId).last("limit " + limit);
|
|
List<Map<String, Object>> items = defaultList(stockInventoryMapper.selectList(wrapper)).stream()
|
.map(this::toMaterialItem)
|
.collect(Collectors.toList());
|
return jsonResponse(true, "manufacturing_material_list", "已返回物料库存列表。",
|
Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of());
|
}
|
|
private String listExceptions(LoginUser loginUser, String keyword, int limit, DateRange range) {
|
LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId);
|
wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay())
|
.lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay());
|
if (StringUtils.hasText(keyword)) {
|
wrapper.like(ProcurementExceptionRecord::getExceptionReason, keyword);
|
}
|
wrapper.orderByDesc(ProcurementExceptionRecord::getCreateTime, ProcurementExceptionRecord::getId)
|
.last("limit " + limit);
|
|
List<Map<String, Object>> items = defaultList(procurementExceptionRecordMapper.selectList(wrapper)).stream()
|
.map(this::toExceptionItem)
|
.collect(Collectors.toList());
|
return jsonResponse(true, "manufacturing_exception_list", "已返回异常处理列表。",
|
rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of());
|
}
|
|
private long countPlans(LoginUser loginUser, DateRange range) {
|
LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
|
wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end());
|
return productionPlanMapper.selectCount(wrapper);
|
}
|
|
private long countPlansByStatus(LoginUser loginUser, DateRange range, int status) {
|
LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
|
wrapper.ge(ProductionPlan::getRequiredDate, range.start())
|
.le(ProductionPlan::getRequiredDate, range.end())
|
.eq(ProductionPlan::getStatus, status);
|
return productionPlanMapper.selectCount(wrapper);
|
}
|
|
private long countWorkOrders(LoginUser loginUser, DateRange range) {
|
LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
|
wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
|
.le(ProductionOperationTask::getPlanEndTime, range.end());
|
return productionOperationTaskMapper.selectCount(wrapper);
|
}
|
|
private long countWorkOrdersByStatus(LoginUser loginUser, DateRange range, int status) {
|
LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
|
wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start())
|
.le(ProductionOperationTask::getPlanEndTime, range.end())
|
.eq(ProductionOperationTask::getStatus, status);
|
return productionOperationTaskMapper.selectCount(wrapper);
|
}
|
|
private long countOutputs(LoginUser loginUser, DateRange range) {
|
LambdaQueryWrapper<ProductionProductMain> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId);
|
wrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay())
|
.lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay());
|
return productionProductMainMapper.selectCount(wrapper);
|
}
|
|
private long countDevices(LoginUser loginUser) {
|
LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
|
return deviceLedgerMapper.selectCount(wrapper);
|
}
|
|
private long countPendingRepairs(LoginUser loginUser) {
|
LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
|
wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING);
|
return deviceRepairMapper.selectCount(wrapper);
|
}
|
|
private long countQualityInspect(LoginUser loginUser, DateRange range) {
|
LambdaQueryWrapper<QualityInspect> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), QualityInspect::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityInspect::getDeptId);
|
wrapper.ge(QualityInspect::getCheckTime, toDate(range.start()))
|
.lt(QualityInspect::getCheckTime, toExclusiveEndDate(range.end()));
|
return qualityInspectMapper.selectCount(wrapper);
|
}
|
|
private long countOpenQualityIssues(LoginUser loginUser, DateRange range) {
|
LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId);
|
wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start()))
|
.lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end()))
|
.ne(QualityUnqualified::getInspectState, 2);
|
return qualityUnqualifiedMapper.selectCount(wrapper);
|
}
|
|
private long countInventorySku(LoginUser loginUser) {
|
LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
|
return stockInventoryMapper.selectCount(wrapper);
|
}
|
|
private long countLowStock(LoginUser loginUser) {
|
LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
|
wrapper.isNotNull(StockInventory::getWarnNum);
|
List<StockInventory> stocks = defaultList(stockInventoryMapper.selectList(wrapper));
|
return stocks.stream()
|
.filter(this::isLowStock)
|
.count();
|
}
|
|
private long countExceptionRecords(LoginUser loginUser, DateRange range) {
|
LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId);
|
wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay())
|
.lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay());
|
return procurementExceptionRecordMapper.selectCount(wrapper);
|
}
|
|
private long countOverduePlans(LoginUser loginUser, LocalDate today) {
|
LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
|
wrapper.lt(ProductionPlan::getRequiredDate, today).ne(ProductionPlan::getStatus, 2);
|
return productionPlanMapper.selectCount(wrapper);
|
}
|
|
private long countOverdueWorkOrders(LoginUser loginUser, LocalDate today) {
|
LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>();
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
|
wrapper.lt(ProductionOperationTask::getPlanEndTime, today).ne(ProductionOperationTask::getStatus, 2);
|
return productionOperationTaskMapper.selectCount(wrapper);
|
}
|
|
private Map<Long, Long> pendingRepairCountByDevice(LoginUser loginUser) {
|
LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
|
applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
|
wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING);
|
return defaultList(deviceRepairMapper.selectList(wrapper)).stream()
|
.filter(item -> item.getDeviceLedgerId() != null)
|
.collect(Collectors.groupingBy(DeviceRepair::getDeviceLedgerId, Collectors.counting()));
|
}
|
|
private Map<String, Object> toPlanItem(ProductionPlan item) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("id", item.getId());
|
map.put("mpsNo", safe(item.getMpsNo()));
|
map.put("requiredDate", formatDate(item.getRequiredDate()));
|
map.put("promisedDeliveryDate", formatDate(item.getPromisedDeliveryDate()));
|
map.put("qtyRequired", item.getQtyRequired());
|
map.put("quantityIssued", item.getQuantityIssued());
|
map.put("status", item.getStatus());
|
map.put("source", safe(item.getSource()));
|
map.put("remark", safe(item.getRemark()));
|
return map;
|
}
|
|
private Map<String, Object> toWorkOrderItem(ProductionOperationTask item) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("id", item.getId());
|
map.put("workOrderNo", safe(item.getWorkOrderNo()));
|
map.put("productionOrderId", item.getProductionOrderId());
|
map.put("planStartTime", formatDate(item.getPlanStartTime()));
|
map.put("planEndTime", formatDate(item.getPlanEndTime()));
|
map.put("actualStartTime", formatDate(item.getActualStartTime()));
|
map.put("actualEndTime", formatDate(item.getActualEndTime()));
|
map.put("planQuantity", item.getPlanQuantity());
|
map.put("completeQuantity", item.getCompleteQuantity());
|
map.put("status", item.getStatus());
|
map.put("userIds", safe(item.getUserIds()));
|
return map;
|
}
|
|
private Map<String, Object> toDeviceItem(DeviceLedger item, long pendingRepairCount) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("id", item.getId());
|
map.put("deviceName", safe(item.getDeviceName()));
|
map.put("deviceModel", safe(item.getDeviceModel()));
|
map.put("deviceBrand", safe(item.getDeviceBrand()));
|
map.put("status", safe(item.getStatus()));
|
map.put("storageLocation", safe(item.getStorageLocation()));
|
map.put("supplierName", safe(item.getSupplierName()));
|
map.put("pendingRepairCount", pendingRepairCount);
|
return map;
|
}
|
|
private Map<String, Object> toDeviceRepairItem(DeviceRepair item) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("id", item.getId());
|
map.put("deviceLedgerId", item.getDeviceLedgerId());
|
map.put("deviceName", safe(item.getDeviceName()));
|
map.put("deviceModel", safe(item.getDeviceModel()));
|
map.put("repairTime", formatDate(item.getRepairTime()));
|
map.put("repairName", safe(item.getRepairName()));
|
map.put("maintenanceName", safe(item.getMaintenanceName()));
|
map.put("maintenanceTime", formatDateTime(item.getMaintenanceTime()));
|
map.put("maintenanceResult", safe(item.getMaintenanceResult()));
|
map.put("acceptanceName", safe(item.getAcceptanceName()));
|
map.put("acceptanceTime", formatDateTime(item.getAcceptanceTime()));
|
map.put("status", item.getStatus());
|
map.put("remark", safe(item.getRemark()));
|
map.put("createTime", formatDateTime(item.getCreateTime()));
|
return map;
|
}
|
|
private Map<String, Object> toQualityItem(QualityUnqualified item) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("id", item.getId());
|
map.put("checkTime", formatDate(item.getCheckTime()));
|
map.put("inspectState", item.getInspectState());
|
map.put("productId", item.getProductId());
|
map.put("productName", safe(item.getProductName()));
|
map.put("model", safe(item.getModel()));
|
map.put("quantity", item.getQuantity());
|
map.put("defectivePhenomena", safe(item.getDefectivePhenomena()));
|
map.put("dealResult", safe(item.getDealResult()));
|
map.put("dealName", safe(item.getDealName()));
|
map.put("dealTime", formatDate(item.getDealTime()));
|
return map;
|
}
|
|
private Map<String, Object> toMaterialItem(StockInventory item) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("id", item.getId());
|
map.put("productModelId", item.getProductModelId());
|
map.put("batchNo", safe(item.getBatchNo()));
|
map.put("qualitity", item.getQualitity());
|
map.put("lockedQuantity", item.getLockedQuantity());
|
map.put("warnNum", item.getWarnNum());
|
map.put("lowStock", isLowStock(item));
|
map.put("remark", safe(item.getRemark()));
|
return map;
|
}
|
|
private Map<String, Object> toExceptionItem(ProcurementExceptionRecord item) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("id", item.getId());
|
map.put("purchaseLedgerId", item.getPurchaseLedgerId());
|
map.put("exceptionReason", safe(item.getExceptionReason()));
|
map.put("exceptionNum", item.getExceptionNum());
|
map.put("createTime", formatDateTime(item.getCreateTime()));
|
return map;
|
}
|
|
private boolean isLowStock(StockInventory item) {
|
BigDecimal quantity = item.getQualitity();
|
BigDecimal warnNum = item.getWarnNum();
|
if (quantity == null || warnNum == null) {
|
return false;
|
}
|
return quantity.compareTo(warnNum) <= 0;
|
}
|
|
private Map<String, Object> warningItem(String level, String title, long count, String detail) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("level", level);
|
map.put("title", title);
|
map.put("count", count);
|
map.put("detail", detail);
|
return map;
|
}
|
|
private Map<String, Object> actionCard(String code,
|
String name,
|
String method,
|
String targetApi,
|
List<String> requiredFields,
|
Map<String, Object> examplePayload,
|
String description) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("code", code);
|
map.put("name", name);
|
map.put("method", method);
|
map.put("targetApi", targetApi);
|
map.put("requiredFields", requiredFields);
|
map.put("examplePayload", examplePayload);
|
map.put("description", description);
|
return map;
|
}
|
|
private Map<String, Object> metric(String label, String value) {
|
Map<String, Object> map = new LinkedHashMap<>();
|
map.put("label", label);
|
map.put("value", value);
|
return map;
|
}
|
|
private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) {
|
Map<String, Object> summary = new LinkedHashMap<>();
|
summary.put("timeRange", range.label());
|
summary.put("startDate", range.start().toString());
|
summary.put("endDate", range.end().toString());
|
summary.put("count", count);
|
summary.put("keyword", safe(keyword));
|
return summary;
|
}
|
|
private Map<String, Object> buildDomainBarOption(Map<String, Object> summary) {
|
List<String> xData = List.of("计划", "工单", "设备", "质量", "物料", "异常");
|
List<Number> yData = List.of(
|
numberValue(summary.get("planTotal")),
|
numberValue(summary.get("workOrderTotal")),
|
numberValue(summary.get("deviceTotal")),
|
numberValue(summary.get("qualityNgCount")),
|
numberValue(summary.get("lowStockCount")),
|
numberValue(summary.get("exceptionCount"))
|
);
|
Map<String, Object> option = new LinkedHashMap<>();
|
option.put("title", Map.of("text", "制造域关键数量", "left", "center"));
|
option.put("tooltip", Map.of("trigger", "axis"));
|
option.put("xAxis", Map.of("type", "category", "data", xData));
|
option.put("yAxis", Map.of("type", "value"));
|
option.put("series", List.of(Map.of("name", "数量", "type", "bar", "data", yData)));
|
return option;
|
}
|
|
private Map<String, Object> buildQualityPieOption(long inspectTotal, long ngCount) {
|
long passCount = Math.max(inspectTotal - ngCount, 0);
|
List<Map<String, Object>> data = List.of(
|
Map.of("name", "不合格", "value", ngCount),
|
Map.of("name", "非不合格", "value", passCount)
|
);
|
Map<String, Object> option = new LinkedHashMap<>();
|
option.put("title", Map.of("text", "质量结果分布", "left", "center"));
|
option.put("tooltip", Map.of("trigger", "item"));
|
option.put("series", List.of(Map.of("name", "质量", "type", "pie", "radius", "60%", "data", data)));
|
return option;
|
}
|
|
private int numberValue(Object value) {
|
if (value instanceof Number number) {
|
return number.intValue();
|
}
|
return 0;
|
}
|
|
private String toRate(long numerator, long denominator) {
|
if (denominator <= 0) {
|
return "0.00%";
|
}
|
BigDecimal rate = new BigDecimal(numerator)
|
.multiply(new BigDecimal("100"))
|
.divide(new BigDecimal(denominator), 2, RoundingMode.HALF_UP);
|
return rate.toPlainString() + "%";
|
}
|
|
private String normalizeDomain(String domain) {
|
if (!StringUtils.hasText(domain)) {
|
return "";
|
}
|
String value = domain.trim().toLowerCase();
|
return switch (value) {
|
case "生产现场", "site", "factory", "workshop" -> "site";
|
case "计划", "plan", "schedule" -> "plan";
|
case "工单", "workorder", "work_order", "task" -> "workorder";
|
case "设备", "device", "equipment" -> "device";
|
case "维修", "repair", "maintenance" -> "repair";
|
case "质量", "quality", "qc" -> "quality";
|
case "物料", "material", "inventory", "stock" -> "material";
|
case "异常", "exception", "abnormal" -> "exception";
|
default -> value;
|
};
|
}
|
|
private boolean isRepairIntent(String keyword, String userQuery) {
|
String query = safe(userQuery);
|
return containsAny(safe(keyword), "维修", "报修", "检修", "维护")
|
|| containsAny(query, "维修", "报修", "检修", "维护");
|
}
|
|
private String normalizeDeviceQueryKeyword(String keyword, String userQuery) {
|
String source = StringUtils.hasText(keyword) ? keyword : userQuery;
|
if (!StringUtils.hasText(source)) {
|
return null;
|
}
|
String cleaned = source
|
.replace("查询", "")
|
.replace("查看", "")
|
.replace("帮我", "")
|
.replace("请", "")
|
.replace("查", "")
|
.replace("设备", "")
|
.replace("维修记录", "")
|
.replace("维修情况", "")
|
.replace("报修记录", "")
|
.replace("报修情况", "")
|
.replace("维修", "")
|
.replace("报修", "")
|
.replace("情况", "")
|
.replace("记录", "")
|
.replace("信息", "")
|
.replace("的", "")
|
.replace("一下", "")
|
.trim();
|
return cleaned.length() >= 2 ? cleaned : null;
|
}
|
|
private List<Long> findDeviceLedgerIdsByKeyword(LoginUser loginUser, String keyword) {
|
if (!StringUtils.hasText(keyword)) {
|
return List.of();
|
}
|
LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
|
applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
|
Long currentDeptId = loginUser.getCurrentDeptId();
|
if (currentDeptId != null) {
|
wrapper.and(w -> w.eq(DeviceLedger::getDeptId, currentDeptId).or().isNull(DeviceLedger::getDeptId));
|
}
|
wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword)
|
.or().like(DeviceLedger::getDeviceModel, keyword)
|
.or().like(DeviceLedger::getDeviceBrand, keyword));
|
wrapper.orderByDesc(DeviceLedger::getId).last("limit 200");
|
return defaultList(deviceLedgerMapper.selectList(wrapper)).stream()
|
.map(DeviceLedger::getId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toList());
|
}
|
|
private boolean hasTimeConstraint(String startDate, String endDate, String userQuery) {
|
if (StringUtils.hasText(startDate) || StringUtils.hasText(endDate)) {
|
return true;
|
}
|
if (!StringUtils.hasText(userQuery)) {
|
return false;
|
}
|
String text = userQuery.trim();
|
return containsAny(text, "今天", "昨天", "本周", "上周", "本月", "上月", "今年", "去年", "近", "最近");
|
}
|
|
private DateRange resolveDateRange(String startDate, String endDate, String timeRange) {
|
LocalDate today = LocalDate.now();
|
LocalDate start = parseLocalDate(startDate);
|
LocalDate end = parseLocalDate(endDate);
|
if (start != null || end != null) {
|
LocalDate s = start != null ? start : end;
|
LocalDate e = end != null ? end : start;
|
if (s.isAfter(e)) {
|
LocalDate temp = s;
|
s = e;
|
e = temp;
|
}
|
return new DateRange(s, e, s + "至" + e);
|
}
|
if (!StringUtils.hasText(timeRange)) {
|
return new DateRange(today.minusDays(29), today, "近30天");
|
}
|
String text = timeRange.trim();
|
if (text.contains("今天")) {
|
return new DateRange(today, today, "今天");
|
}
|
if (text.contains("本周")) {
|
LocalDate startOfWeek = today.minusDays(today.getDayOfWeek().getValue() - 1L);
|
return new DateRange(startOfWeek, today, "本周");
|
}
|
if (text.contains("本月")) {
|
return new DateRange(today.withDayOfMonth(1), today, "本月");
|
}
|
if (text.contains("本年") || text.contains("今年")) {
|
return new DateRange(today.withDayOfYear(1), today, "今年");
|
}
|
if (text.contains("去年")) {
|
LocalDate firstDay = today.minusYears(1).withDayOfYear(1);
|
LocalDate lastDay = today.minusYears(1).withMonth(12).withDayOfMonth(31);
|
return new DateRange(firstDay, lastDay, "去年");
|
}
|
if (text.contains("上月")) {
|
LocalDate startOfLastMonth = today.minusMonths(1).withDayOfMonth(1);
|
return new DateRange(startOfLastMonth, startOfLastMonth.withDayOfMonth(startOfLastMonth.lengthOfMonth()), "上月");
|
}
|
java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(近|最近)(\\d+)(天|周|个月|月|年)").matcher(text);
|
if (matcher.find()) {
|
int amount = Integer.parseInt(matcher.group(2));
|
String unit = matcher.group(3);
|
LocalDate relativeStart = switch (unit) {
|
case "天" -> today.minusDays(Math.max(amount - 1L, 0));
|
case "周" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1);
|
case "个月", "月" -> today.minusMonths(Math.max(amount, 1)).plusDays(1);
|
case "年" -> today.minusYears(Math.max(amount, 1)).plusDays(1);
|
default -> today.minusDays(29);
|
};
|
return new DateRange(relativeStart, today, "近" + amount + unit);
|
}
|
return new DateRange(today.minusDays(29), today, "近30天");
|
}
|
|
private LocalDate parseLocalDate(String text) {
|
if (!StringUtils.hasText(text)) {
|
return null;
|
}
|
return LocalDate.parse(text.trim(), DATE_FMT);
|
}
|
|
private Date toDate(LocalDate date) {
|
return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
|
}
|
|
private Date toExclusiveEndDate(LocalDate date) {
|
return Date.from(date.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
|
}
|
|
private String formatDate(LocalDate date) {
|
return date == null ? "" : DATE_FMT.format(date);
|
}
|
|
private String formatDate(Date date) {
|
if (date == null) {
|
return "";
|
}
|
return DATE_FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
|
}
|
|
private String formatDateTime(LocalDateTime time) {
|
if (time == null) {
|
return "";
|
}
|
return time.truncatedTo(ChronoUnit.SECONDS).toString().replace('T', ' ');
|
}
|
|
private int normalizeLimit(Integer limit) {
|
if (limit == null || limit <= 0) {
|
return DEFAULT_LIMIT;
|
}
|
return Math.min(limit, MAX_LIMIT);
|
}
|
|
private boolean containsAny(String text, String... values) {
|
for (String value : values) {
|
if (text.contains(value)) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) {
|
if (tenantId != null) {
|
wrapper.eq(field, tenantId);
|
}
|
}
|
|
private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) {
|
if (deptId != null) {
|
wrapper.eq(field, deptId);
|
}
|
}
|
|
private LoginUser currentLoginUser(String memoryId) {
|
LoginUser loginUser = aiSessionUserContext.get(memoryId);
|
if (loginUser != null) {
|
return loginUser;
|
}
|
return SecurityUtils.getLoginUser();
|
}
|
|
private String safe(Object value) {
|
return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ');
|
}
|
|
private <T> List<T> defaultList(List<T> list) {
|
return list == null ? List.of() : list;
|
}
|
|
private String jsonResponse(boolean success,
|
String type,
|
String description,
|
Map<String, Object> summary,
|
Map<String, Object> data,
|
Map<String, Object> charts) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("success", success);
|
result.put("type", type);
|
result.put("description", description);
|
result.put("summary", summary == null ? Map.of() : summary);
|
result.put("data", data == null ? Map.of() : data);
|
result.put("charts", charts == null ? Map.of() : charts);
|
return JSON.toJSONString(result);
|
}
|
|
private record DateRange(LocalDate start, LocalDate end, String label) {
|
}
|
}
|