package com.vincent.rsf.server.ai.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.vincent.rsf.server.ai.model.AiPromptContext;
|
import com.vincent.rsf.server.manager.entity.Loc;
|
import com.vincent.rsf.server.manager.entity.LocItem;
|
import com.vincent.rsf.server.manager.mapper.LocItemMapper;
|
import com.vincent.rsf.server.manager.mapper.LocMapper;
|
import org.springframework.stereotype.Service;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.util.*;
|
|
@Service
|
public class AiWarehouseSummaryService implements AiPromptContextProvider {
|
|
private static final Map<String, String> LOC_STATUS_LABELS = new LinkedHashMap<>();
|
|
static {
|
LOC_STATUS_LABELS.put("O", "空库");
|
LOC_STATUS_LABELS.put("D", "空板");
|
LOC_STATUS_LABELS.put("R", "预约出库");
|
LOC_STATUS_LABELS.put("S", "预约入库");
|
LOC_STATUS_LABELS.put("X", "禁用");
|
LOC_STATUS_LABELS.put("F", "在库");
|
}
|
|
@Resource
|
private LocMapper locMapper;
|
@Resource
|
private LocItemMapper locItemMapper;
|
|
@Override
|
public boolean supports(AiPromptContext context) {
|
return context != null && shouldSummarize(context.getQuestion());
|
}
|
|
@Override
|
public String buildContext(AiPromptContext context) {
|
if (!supports(context)) {
|
return "";
|
}
|
|
List<Loc> activeLocs = locMapper.selectList(new LambdaQueryWrapper<Loc>()
|
.select(Loc::getUseStatus)
|
.eq(Loc::getStatus, 1));
|
long totalLoc = activeLocs.size();
|
|
List<LocItem> activeLocItems = locItemMapper.selectList(new LambdaQueryWrapper<LocItem>()
|
.select(LocItem::getLocCode, LocItem::getMatnrCode, LocItem::getMaktx, LocItem::getAnfme)
|
.eq(LocItem::getStatus, 1));
|
|
Map<String, Long> locStatusCounters = new LinkedHashMap<>();
|
for (Loc loc : activeLocs) {
|
String useStatus = loc.getUseStatus();
|
locStatusCounters.put(useStatus, locStatusCounters.getOrDefault(useStatus, 0L) + 1);
|
}
|
|
long itemRows = activeLocItems.size();
|
Set<String> locCodes = new HashSet<>();
|
Set<String> materialCodes = new HashSet<>();
|
BigDecimal totalQty = BigDecimal.ZERO;
|
Map<String, LocAggregate> locAggregates = new LinkedHashMap<>();
|
Map<String, MaterialAggregate> materialAggregates = new LinkedHashMap<>();
|
for (LocItem item : activeLocItems) {
|
if (item.getLocCode() != null && !item.getLocCode().trim().isEmpty()) {
|
locCodes.add(item.getLocCode());
|
}
|
if (item.getMatnrCode() != null && !item.getMatnrCode().trim().isEmpty()) {
|
materialCodes.add(item.getMatnrCode());
|
}
|
totalQty = totalQty.add(toDecimal(item.getAnfme()));
|
|
String locKey = item.getLocCode();
|
if (locKey != null && !locKey.trim().isEmpty()) {
|
LocAggregate locAggregate = locAggregates.computeIfAbsent(locKey, key -> new LocAggregate());
|
locAggregate.totalQty = locAggregate.totalQty.add(toDecimal(item.getAnfme()));
|
if (item.getMatnrCode() != null && !item.getMatnrCode().trim().isEmpty()) {
|
locAggregate.materialCodes.add(item.getMatnrCode());
|
}
|
}
|
|
String materialKey = item.getMatnrCode();
|
if (materialKey != null && !materialKey.trim().isEmpty()) {
|
MaterialAggregate materialAggregate = materialAggregates.computeIfAbsent(materialKey, key -> new MaterialAggregate());
|
materialAggregate.matnrCode = materialKey;
|
materialAggregate.maktx = item.getMaktx();
|
materialAggregate.totalQty = materialAggregate.totalQty.add(toDecimal(item.getAnfme()));
|
if (item.getLocCode() != null && !item.getLocCode().trim().isEmpty()) {
|
materialAggregate.locCodes.add(item.getLocCode());
|
}
|
}
|
}
|
|
List<Map.Entry<String, LocAggregate>> topLocRows = new ArrayList<>(locAggregates.entrySet());
|
topLocRows.sort((left, right) -> right.getValue().totalQty.compareTo(left.getValue().totalQty));
|
if (topLocRows.size() > 5) {
|
topLocRows = topLocRows.subList(0, 5);
|
}
|
|
List<MaterialAggregate> topMaterialRows = new ArrayList<>(materialAggregates.values());
|
topMaterialRows.sort((left, right) -> right.totalQty.compareTo(left.totalQty));
|
if (topMaterialRows.size() > 5) {
|
topMaterialRows = topMaterialRows.subList(0, 5);
|
}
|
|
StringBuilder summary = new StringBuilder();
|
summary.append("以下是基于 man_loc 和 man_loc_item 的实时汇总,请优先依据这些数据回答;如果超出这两张表可推断的范围,请明确说明。");
|
summary.append("\n库位概况:总库位 ")
|
.append(totalLoc)
|
.append(" 个;状态分布:")
|
.append(formatLocStatuses(locStatusCounters))
|
.append("。");
|
summary.append("\n库存概况:库存记录 ")
|
.append(itemRows)
|
.append(" 条,覆盖库位 ")
|
.append(locCodes.size())
|
.append(" 个,涉及物料 ")
|
.append(materialCodes.size())
|
.append(" 种,总数量 ")
|
.append(formatDecimal(totalQty))
|
.append("。");
|
if (!topLocRows.isEmpty()) {
|
summary.append("\n库存最多的库位 TOP5:")
|
.append(formatTopLocs(topLocRows))
|
.append("。");
|
}
|
if (!topMaterialRows.isEmpty()) {
|
summary.append("\n库存最多的物料 TOP5:")
|
.append(formatTopMaterials(topMaterialRows))
|
.append("。");
|
}
|
return summary.toString();
|
}
|
|
private boolean shouldSummarize(String question) {
|
if (question == null || question.trim().isEmpty()) {
|
return false;
|
}
|
String normalized = question.toLowerCase(Locale.ROOT);
|
return normalized.contains("loc")
|
|| normalized.contains("库位")
|
|| normalized.contains("货位")
|
|| normalized.contains("库区")
|
|| normalized.contains("库存")
|
|| normalized.contains("物料")
|
|| normalized.contains("巷道")
|
|| normalized.contains("储位");
|
}
|
|
private String formatLocStatuses(Map<String, Long> counters) {
|
if (counters == null || counters.isEmpty()) {
|
return "暂无数据";
|
}
|
List<String> parts = new ArrayList<>();
|
for (Map.Entry<String, String> entry : LOC_STATUS_LABELS.entrySet()) {
|
parts.add(entry.getValue() + " " + counters.getOrDefault(entry.getKey(), 0L) + " 个");
|
}
|
return String.join(",", parts);
|
}
|
|
private String formatTopLocs(List<Map.Entry<String, LocAggregate>> rows) {
|
List<String> parts = new ArrayList<>();
|
for (Map.Entry<String, LocAggregate> row : rows) {
|
parts.add(row.getKey()
|
+ "(数量 " + formatDecimal(row.getValue().totalQty)
|
+ ",物料 " + row.getValue().materialCodes.size() + " 种)");
|
}
|
return String.join(";", parts);
|
}
|
|
private String formatTopMaterials(List<MaterialAggregate> rows) {
|
List<String> parts = new ArrayList<>();
|
for (MaterialAggregate row : rows) {
|
String matnrCode = row.matnrCode;
|
String maktx = Objects.toString(row.maktx, "");
|
String title = maktx == null || maktx.trim().isEmpty() ? matnrCode : matnrCode + "/" + maktx;
|
parts.add(title
|
+ "(数量 " + formatDecimal(row.totalQty)
|
+ ",分布库位 " + row.locCodes.size() + " 个)");
|
}
|
return String.join(";", parts);
|
}
|
|
private String formatDecimal(Object value) {
|
BigDecimal decimal = toDecimal(value);
|
return decimal.stripTrailingZeros().toPlainString();
|
}
|
|
private BigDecimal toDecimal(Object value) {
|
if (value == null) {
|
return BigDecimal.ZERO;
|
}
|
if (value instanceof BigDecimal) {
|
return (BigDecimal) value;
|
}
|
if (value instanceof Number) {
|
return BigDecimal.valueOf(((Number) value).doubleValue());
|
}
|
return new BigDecimal(String.valueOf(value));
|
}
|
|
private static class LocAggregate {
|
private BigDecimal totalQty = BigDecimal.ZERO;
|
private final Set<String> materialCodes = new HashSet<>();
|
}
|
|
private static class MaterialAggregate {
|
private String matnrCode;
|
private String maktx;
|
private BigDecimal totalQty = BigDecimal.ZERO;
|
private final Set<String> locCodes = new HashSet<>();
|
}
|
}
|