feat(warehouse): 优化智能代理功能和库存管理
- 修改默认出库类型从销售出库改为生产出库 - 添加产品类别自动推断功能,支持基于历史数据和关键词的智能分类 - 实现库存查询结果显示和选择功能,优化用户体验 - 增强代理计划的消息提示和错误处理机制 - 添加现有产品类别的获取和展示功能 - 优化库存不足和产品未找到的提示信息 - 修复前端表单默认值设置问题
This commit is contained in:
parent
7737fda061
commit
d872e3e235
@ -12,7 +12,6 @@ ruoyi:
|
||||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
captchaType: math
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
|
||||
@ -9,6 +9,7 @@ public interface IProductInfoService
|
||||
public List<ProductInfo> selectProductInfoAll();
|
||||
public ProductInfo selectProductInfoById(Long id);
|
||||
public boolean checkProductCodeUnique(ProductInfo productInfo);
|
||||
public String inferProductCategory(ProductInfo productInfo);
|
||||
public int insertProductInfo(ProductInfo productInfo);
|
||||
public int updateProductInfo(ProductInfo productInfo);
|
||||
public int deleteProductInfoByIds(Long[] ids);
|
||||
|
||||
@ -9,8 +9,10 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -258,8 +260,8 @@ public class AgentServiceImpl implements IAgentService
|
||||
}
|
||||
if (ACTION_CREATE_OUT.equals(plan.getAction()) && plan.getOutType() == null)
|
||||
{
|
||||
plan.setOutType(3);
|
||||
plan.getWarnings().add("未识别出库类型,默认销售出库");
|
||||
plan.setOutType(1);
|
||||
plan.getWarnings().add("未识别出库类型,默认生产出库");
|
||||
}
|
||||
|
||||
if (ACTION_CONFIRM_IN.equals(plan.getAction()))
|
||||
@ -274,7 +276,7 @@ public class AgentServiceImpl implements IAgentService
|
||||
fillStockResults(plan);
|
||||
plan.setNeedConfirm(false);
|
||||
plan.setExecutable(true);
|
||||
plan.setMessage(plan.getResults().isEmpty() ? "未查询到库存" : "查询到 " + plan.getResults().size() + " 条库存");
|
||||
plan.setMessage(plan.getResults().isEmpty() ? stockNotFoundMessage(plan) : "查询到 " + plan.getResults().size() + " 条库存");
|
||||
if (StringUtils.isBlank(plan.getSummary()))
|
||||
{
|
||||
plan.setSummary("查询库存");
|
||||
@ -312,7 +314,7 @@ public class AgentServiceImpl implements IAgentService
|
||||
fillSummary(plan);
|
||||
if (!plan.isExecutable() && StringUtils.isBlank(plan.getMessage()))
|
||||
{
|
||||
plan.setMessage("信息不完整或存在多个候选,请补充后再执行");
|
||||
fillUnableToExecuteMessage(plan);
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
@ -418,7 +420,8 @@ public class AgentServiceImpl implements IAgentService
|
||||
+ "如果用户说第几个、第二个、第2条等,referenceIndex 返回从 1 开始的序号。"
|
||||
+ "入库类型 inType:1生产入库,2采购入库,3归还入库,4退货入库。"
|
||||
+ "出库类型 outType:1生产出库,2退货出库,3销售出库。"
|
||||
+ "新增产品时尽量从用户输入提取 productName, brand, category, spec, unit;新增库区提取 areaName。不要编造信息;无法识别就填 null。";
|
||||
+ "新增产品时尽量从用户输入提取 productName, brand, category, spec, unit;如果用户未提供 category,优先从 existingCategories 选择合适类别,不合适时推断一个简短合理的类别。"
|
||||
+ "除 category 可按规则推断外,不要编造其他字段;无法识别就填 null。新增库区提取 areaName。";
|
||||
}
|
||||
|
||||
private String buildUserInput(String text, List<AgentResultItem> contextResults)
|
||||
@ -426,6 +429,7 @@ public class AgentServiceImpl implements IAgentService
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("text", text);
|
||||
payload.put("contextResults", contextResults == null ? new ArrayList<>() : contextResults);
|
||||
payload.put("existingCategories", existingCategories());
|
||||
return payload.toJSONString();
|
||||
}
|
||||
|
||||
@ -850,7 +854,13 @@ public class AgentServiceImpl implements IAgentService
|
||||
ProductInfo product = match.getProduct();
|
||||
plan.getCandidates().add(new AgentCandidate(product.getId(), "product", product.getProductName(), productDetail(product)));
|
||||
}
|
||||
plan.setMessage("产品不唯一,请补充名称、规格、品牌或类别");
|
||||
if (!ACTION_QUERY_STOCK.equals(plan.getAction()))
|
||||
{
|
||||
fillProductChoices(plan, topMatches);
|
||||
}
|
||||
plan.setMessage(plan.getResults().isEmpty()
|
||||
? "产品不唯一,请选择候选产品,或补充名称、规格、品牌、类别"
|
||||
: "找到多个匹配产品,请在下方选择要操作的库存项");
|
||||
}
|
||||
|
||||
private void resolveReferencedResult(AgentPlan plan, String originalText, List<AgentResultItem> contextResults)
|
||||
@ -965,6 +975,37 @@ public class AgentServiceImpl implements IAgentService
|
||||
{
|
||||
plan.setUnit(plan.getUnit().trim());
|
||||
}
|
||||
if (StringUtils.isBlank(plan.getCategory()) && StringUtils.isNotBlank(plan.getProductName()))
|
||||
{
|
||||
ProductInfo draft = new ProductInfo();
|
||||
draft.setProductName(plan.getProductName());
|
||||
draft.setBrand(plan.getBrand());
|
||||
draft.setSpec(plan.getSpec());
|
||||
draft.setUnit(plan.getUnit());
|
||||
String category = productService.inferProductCategory(draft);
|
||||
if (StringUtils.isNotBlank(category))
|
||||
{
|
||||
plan.setCategory(category);
|
||||
plan.getWarnings().add("未提供类别,已自动分类为:" + category);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> existingCategories()
|
||||
{
|
||||
Set<String> categories = new LinkedHashSet<>();
|
||||
for (ProductInfo product : productService.selectProductInfoAll())
|
||||
{
|
||||
if (StringUtils.isNotBlank(product.getCategory()))
|
||||
{
|
||||
categories.add(product.getCategory().trim());
|
||||
}
|
||||
if (categories.size() >= 80)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new ArrayList<>(categories);
|
||||
}
|
||||
|
||||
private boolean requiresOperator(String action)
|
||||
@ -987,6 +1028,80 @@ public class AgentServiceImpl implements IAgentService
|
||||
return true;
|
||||
}
|
||||
|
||||
private void fillUnableToExecuteMessage(AgentPlan plan)
|
||||
{
|
||||
if (plan.getProductId() == null && hasProductClue(plan) && noProductCandidates(plan))
|
||||
{
|
||||
plan.setMessage("没找到产品:" + productClue(plan) + "。请先确认产品名称、品牌、类别或规格是否已维护。");
|
||||
return;
|
||||
}
|
||||
if (plan.getAreaId() == null && StringUtils.isNotBlank(plan.getAreaName()) && noAreaCandidates(plan))
|
||||
{
|
||||
plan.setMessage("没找到库区:" + plan.getAreaName() + "。请先维护库区后再操作。");
|
||||
return;
|
||||
}
|
||||
if (ACTION_CREATE_OUT.equals(plan.getAction()) && plan.getProductId() != null && plan.getResults().isEmpty())
|
||||
{
|
||||
String product = StringUtils.defaultIfBlank(plan.getProductName(), "该产品");
|
||||
plan.setMessage("没找到可出库库存:" + product + "。请确认产品是否已有库存,或选择正确库区。");
|
||||
return;
|
||||
}
|
||||
if (plan.getCandidates() != null && !plan.getCandidates().isEmpty())
|
||||
{
|
||||
plan.setMessage("存在多个候选,请先选择要操作的数据");
|
||||
return;
|
||||
}
|
||||
if (!validQuantity(plan))
|
||||
{
|
||||
plan.setMessage(ACTION_CREATE_CHECK.equals(plan.getAction()) ? "未识别到实际盘点数量" : "未识别到有效数量");
|
||||
return;
|
||||
}
|
||||
plan.setMessage("信息不完整,请补充后再执行");
|
||||
}
|
||||
|
||||
private boolean noProductCandidates(AgentPlan plan)
|
||||
{
|
||||
for (AgentCandidate candidate : plan.getCandidates())
|
||||
{
|
||||
if ("product".equals(candidate.getType()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasProductClue(AgentPlan plan)
|
||||
{
|
||||
return StringUtils.isNotBlank(plan.getProductName())
|
||||
|| StringUtils.isNotBlank(plan.getBrand())
|
||||
|| StringUtils.isNotBlank(plan.getCategory())
|
||||
|| StringUtils.isNotBlank(plan.getSpec());
|
||||
}
|
||||
|
||||
private String productClue(AgentPlan plan)
|
||||
{
|
||||
return firstNotBlank(plan.getProductName(), plan.getBrand(), plan.getCategory(), plan.getSpec(), "未识别产品");
|
||||
}
|
||||
|
||||
private String stockNotFoundMessage(AgentPlan plan)
|
||||
{
|
||||
List<String> parts = new ArrayList<>();
|
||||
if (StringUtils.isNotBlank(plan.getAreaName()))
|
||||
{
|
||||
parts.add("库区:" + plan.getAreaName());
|
||||
}
|
||||
if (hasProductClue(plan))
|
||||
{
|
||||
parts.add("产品:" + productClue(plan));
|
||||
}
|
||||
if (parts.isEmpty())
|
||||
{
|
||||
return "未查询到库存数据";
|
||||
}
|
||||
return "未查询到符合条件的库存数据(" + String.join(",", parts) + ")";
|
||||
}
|
||||
|
||||
private void fillActionCandidates(AgentPlan plan)
|
||||
{
|
||||
plan.getCandidates().add(new AgentCandidate(1L, "action", "查库存", "例如:查一下路由器库存"));
|
||||
@ -1199,6 +1314,51 @@ public class AgentServiceImpl implements IAgentService
|
||||
return item;
|
||||
}
|
||||
|
||||
private AgentResultItem toProductResult(ProductInfo product)
|
||||
{
|
||||
AgentResultItem item = new AgentResultItem();
|
||||
item.setProductId(product.getId());
|
||||
item.setProductName(product.getProductName());
|
||||
item.setBrand(product.getBrand());
|
||||
item.setCategory(product.getCategory());
|
||||
item.setSpec(product.getSpec());
|
||||
item.setUnit(product.getUnit());
|
||||
return item;
|
||||
}
|
||||
|
||||
private void fillProductChoices(AgentPlan plan, List<ScoredProduct> matches)
|
||||
{
|
||||
int count = 0;
|
||||
for (ScoredProduct match : first(matches, 10))
|
||||
{
|
||||
ProductInfo product = match.getProduct();
|
||||
StockInfo query = new StockInfo();
|
||||
query.setAreaId(plan.getAreaId());
|
||||
query.setProductId(product.getId());
|
||||
List<StockInfo> stockList = stockInfoService.selectStockInfoList(query);
|
||||
if (stockList.isEmpty())
|
||||
{
|
||||
if (!ACTION_CREATE_OUT.equals(plan.getAction()))
|
||||
{
|
||||
plan.getResults().add(toProductResult(product));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (StockInfo stock : stockList)
|
||||
{
|
||||
if (count++ >= 20)
|
||||
{
|
||||
plan.getWarnings().add("匹配结果较多,仅展示前 20 条");
|
||||
return;
|
||||
}
|
||||
plan.getResults().add(toResult(stock));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillCurrentStock(AgentPlan plan)
|
||||
{
|
||||
if (plan.getAreaId() == null || plan.getProductId() == null)
|
||||
@ -1235,7 +1395,8 @@ public class AgentServiceImpl implements IAgentService
|
||||
|
||||
private void suggestAlternativeStockAreas(AgentPlan plan)
|
||||
{
|
||||
if (!ACTION_CREATE_OUT.equals(plan.getAction()) || plan.getProductId() == null || plan.isExecutable())
|
||||
if (!ACTION_CREATE_OUT.equals(plan.getAction()) || plan.getProductId() == null || plan.isExecutable()
|
||||
|| (plan.getAreaId() == null && (StringUtils.isNotBlank(plan.getAreaName()) || !noAreaCandidates(plan))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
package com.ruoyi.warehouse.service.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.constant.UserConstants;
|
||||
@ -46,6 +49,21 @@ public class ProductInfoServiceImpl implements IProductInfoService
|
||||
return StringUtils.isNull(info) || info.getId().longValue() == id.longValue() ? UserConstants.UNIQUE : UserConstants.NOT_UNIQUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inferProductCategory(ProductInfo productInfo)
|
||||
{
|
||||
if (productInfo == null || StringUtils.isBlank(productInfo.getProductName()))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
String category = inferCategoryFromHistory(productInfo);
|
||||
if (StringUtils.isNotBlank(category))
|
||||
{
|
||||
return category;
|
||||
}
|
||||
return generateCategory(productInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int insertProductInfo(ProductInfo productInfo)
|
||||
{
|
||||
@ -79,6 +97,10 @@ public class ProductInfoServiceImpl implements IProductInfoService
|
||||
productInfo.setCategory(trimToNull(productInfo.getCategory()));
|
||||
productInfo.setSpec(trimToNull(productInfo.getSpec()));
|
||||
productInfo.setUnit(trimToNull(productInfo.getUnit()));
|
||||
if (StringUtils.isBlank(productInfo.getCategory()) && StringUtils.isNotBlank(productInfo.getProductName()))
|
||||
{
|
||||
productInfo.setCategory(inferProductCategory(productInfo));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProductInfoUnique(ProductInfo productInfo)
|
||||
@ -95,4 +117,151 @@ public class ProductInfoServiceImpl implements IProductInfoService
|
||||
{
|
||||
return StringUtils.isBlank(value) ? null : value.trim();
|
||||
}
|
||||
|
||||
private String inferCategoryFromHistory(ProductInfo productInfo)
|
||||
{
|
||||
Map<String, Integer> scores = new HashMap<>();
|
||||
for (ProductInfo product : productInfoMapper.selectProductInfoAll())
|
||||
{
|
||||
if (StringUtils.isBlank(product.getCategory()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int score = categoryProductScore(productInfo, product);
|
||||
if (score > 0)
|
||||
{
|
||||
String category = product.getCategory().trim();
|
||||
scores.put(category, scores.getOrDefault(category, 0) + score);
|
||||
}
|
||||
}
|
||||
String bestCategory = null;
|
||||
int bestScore = 0;
|
||||
int secondScore = 0;
|
||||
for (Map.Entry<String, Integer> entry : scores.entrySet())
|
||||
{
|
||||
int score = entry.getValue();
|
||||
if (score > bestScore)
|
||||
{
|
||||
secondScore = bestScore;
|
||||
bestScore = score;
|
||||
bestCategory = entry.getKey();
|
||||
}
|
||||
else if (score > secondScore)
|
||||
{
|
||||
secondScore = score;
|
||||
}
|
||||
}
|
||||
if (bestScore >= 4 && bestScore >= secondScore + 2)
|
||||
{
|
||||
return bestCategory;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int categoryProductScore(ProductInfo source, ProductInfo target)
|
||||
{
|
||||
int score = 0;
|
||||
score += fieldScore(source.getProductName(), target.getProductName(), 8);
|
||||
score += fieldScore(source.getBrand(), target.getBrand(), 6);
|
||||
score += fieldScore(source.getSpec(), target.getSpec(), 4);
|
||||
score += fieldScore(source.getUnit(), target.getUnit(), 1);
|
||||
String draftText = productDraftText(source);
|
||||
score += containsField(draftText, target.getProductName()) ? 4 : 0;
|
||||
score += containsField(draftText, target.getBrand()) ? 2 : 0;
|
||||
score += containsField(draftText, target.getSpec()) ? 2 : 0;
|
||||
return score;
|
||||
}
|
||||
|
||||
private int fieldScore(String input, String target, int exactScore)
|
||||
{
|
||||
if (StringUtils.isBlank(input) || StringUtils.isBlank(target))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (input.trim().equalsIgnoreCase(target.trim()))
|
||||
{
|
||||
return exactScore;
|
||||
}
|
||||
if (containsIgnoreCase(input, target) || containsIgnoreCase(target, input))
|
||||
{
|
||||
return Math.max(1, exactScore / 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String productDraftText(ProductInfo productInfo)
|
||||
{
|
||||
return StringUtils.defaultString(productInfo.getProductName()) + " "
|
||||
+ StringUtils.defaultString(productInfo.getBrand()) + " "
|
||||
+ StringUtils.defaultString(productInfo.getSpec()) + " "
|
||||
+ StringUtils.defaultString(productInfo.getUnit());
|
||||
}
|
||||
|
||||
private String generateCategory(ProductInfo productInfo)
|
||||
{
|
||||
String text = productDraftText(productInfo).toLowerCase(Locale.ROOT);
|
||||
if (containsAny(text, "路由器", "交换机", "网关", "ap", "wifi", "网线", "光模块", "网络"))
|
||||
{
|
||||
return "网络设备";
|
||||
}
|
||||
if (containsAny(text, "电脑", "主机", "显示器", "键盘", "鼠标", "硬盘", "内存", "打印机", "扫描仪"))
|
||||
{
|
||||
return "办公设备";
|
||||
}
|
||||
if (containsAny(text, "纸", "抽纸", "卷纸", "硒鼓", "墨盒", "墨粉", "笔", "文件夹", "订书", "胶带"))
|
||||
{
|
||||
return "办公耗材";
|
||||
}
|
||||
if (containsAny(text, "手套", "口罩", "安全帽", "护目镜", "反光", "劳保"))
|
||||
{
|
||||
return "劳保用品";
|
||||
}
|
||||
if (containsAny(text, "扳手", "螺丝刀", "钳", "锤", "钻", "工具"))
|
||||
{
|
||||
return "工具";
|
||||
}
|
||||
if (containsAny(text, "螺丝", "螺母", "垫片", "扎带", "卡扣", "五金"))
|
||||
{
|
||||
return "五金耗材";
|
||||
}
|
||||
if (containsAny(text, "传感器", "继电器", "电源", "线束", "电缆", "接头", "模块", "芯片", "电阻", "电容"))
|
||||
{
|
||||
return "电子元器件";
|
||||
}
|
||||
if (containsAny(text, "机油", "润滑", "清洗剂", "胶水", "胶粘", "油漆", "化学"))
|
||||
{
|
||||
return "化工耗材";
|
||||
}
|
||||
if (containsAny(text, "轮胎", "刹车", "电瓶", "电池", "车灯", "滤芯", "汽车"))
|
||||
{
|
||||
return "汽车配件";
|
||||
}
|
||||
return "通用物料";
|
||||
}
|
||||
|
||||
private boolean containsAny(String text, String... keywords)
|
||||
{
|
||||
for (String keyword : keywords)
|
||||
{
|
||||
if (containsIgnoreCase(text, keyword))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsField(String text, String field)
|
||||
{
|
||||
return StringUtils.isNotBlank(text) && StringUtils.isNotBlank(field) && containsIgnoreCase(text, field);
|
||||
}
|
||||
|
||||
private boolean containsIgnoreCase(String text, String keyword)
|
||||
{
|
||||
if (StringUtils.isBlank(text) || StringUtils.isBlank(keyword))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return text.toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,6 +58,11 @@
|
||||
</div>
|
||||
<el-table v-if="agent.plan.results && agent.plan.results.length" :data="agent.plan.results" size="mini" class="agent-table">
|
||||
<el-table-column type="index" label="#" width="54" />
|
||||
<el-table-column v-if="canSelectAgentResult(agent.plan)" label="选择" width="86">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="primary" size="mini" plain @click="selectAgentResult(scope.row)">选择</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库区" prop="areaName" width="120" />
|
||||
<el-table-column label="产品" min-width="220">
|
||||
<template slot-scope="scope">
|
||||
@ -229,6 +234,60 @@ export default {
|
||||
this.agent.text = ''
|
||||
this.agent.plan = null
|
||||
},
|
||||
canSelectAgentResult(plan) {
|
||||
return plan && ['create_out_record', 'create_in_record', 'create_stock_check'].includes(plan.action) && plan.results && plan.results.length
|
||||
},
|
||||
selectAgentResult(item) {
|
||||
if (!this.agent.plan || !item) {
|
||||
return
|
||||
}
|
||||
const plan = this.completePlanWithResult(this.agent.plan, item)
|
||||
this.agent.plan = plan
|
||||
this.agent.contextResults = [item]
|
||||
this.$modal.msgSuccess('已选择:' + (item.productName || '库存项'))
|
||||
},
|
||||
completePlanWithResult(source, item) {
|
||||
const plan = Object.assign({}, source, {
|
||||
stockId: item.id,
|
||||
areaId: item.areaId,
|
||||
productId: item.productId,
|
||||
areaName: item.areaName,
|
||||
productName: item.productName,
|
||||
brand: item.brand,
|
||||
category: item.category,
|
||||
spec: item.spec,
|
||||
unit: item.unit,
|
||||
currentStock: item.stockNum,
|
||||
candidates: [],
|
||||
results: [],
|
||||
needConfirm: true,
|
||||
message: '已选择产品,请确认后执行'
|
||||
})
|
||||
const quantity = this.toNumber(plan.quantity)
|
||||
const stock = this.toNumber(item.stockNum)
|
||||
if (plan.action === 'create_out_record') {
|
||||
plan.executable = !!(plan.areaId && plan.productId && quantity > 0)
|
||||
plan.afterStock = quantity > 0 && item.stockNum !== undefined && item.stockNum !== null ? stock - quantity : undefined
|
||||
if (plan.executable && stock < quantity) {
|
||||
plan.executable = false
|
||||
plan.message = '库存不足,当前库存:' + item.stockNum
|
||||
}
|
||||
} else if (plan.action === 'create_in_record') {
|
||||
plan.executable = !!(plan.areaId && plan.productId && quantity > 0)
|
||||
plan.afterStock = quantity > 0 && item.stockNum !== undefined && item.stockNum !== null ? stock + quantity : undefined
|
||||
} else if (plan.action === 'create_stock_check') {
|
||||
plan.executable = !!(plan.areaId && plan.productId && quantity >= 0)
|
||||
plan.afterStock = quantity >= 0 ? quantity : undefined
|
||||
}
|
||||
if (!plan.executable && plan.message === '已选择产品,请确认后执行') {
|
||||
plan.message = '已选择产品,还缺少库区或数量'
|
||||
}
|
||||
return plan
|
||||
},
|
||||
toNumber(value) {
|
||||
const numberValue = Number(value)
|
||||
return Number.isFinite(numberValue) ? numberValue : 0
|
||||
},
|
||||
actionLabel(action) {
|
||||
const map = {
|
||||
query_stock: '查询库存',
|
||||
|
||||
@ -76,12 +76,15 @@
|
||||
<div v-if="agent.plan.candidates && agent.plan.candidates.length" class="agent-candidates">
|
||||
<span v-for="item in agent.plan.candidates" :key="item.type + item.id">{{ item.name }} {{ item.detail || '' }}</span>
|
||||
</div>
|
||||
<article v-for="(item, index) in (agent.plan.results || [])" :key="'agent-result-' + item.id" class="agent-stock">
|
||||
<article v-for="(item, index) in (agent.plan.results || [])" :key="'agent-result-' + index + '-' + (item.id || item.productId || '')" class="agent-stock">
|
||||
<div>
|
||||
<div class="card-title">第 {{ index + 1 }} 项:{{ item.productName || '-' }}</div>
|
||||
<div class="meta-line">{{ item.areaName || '-' }} | {{ productMeta(item) }}</div>
|
||||
</div>
|
||||
<strong>{{ item.stockNum }}</strong>
|
||||
<div class="agent-stock-action">
|
||||
<strong>{{ item.stockNum === undefined || item.stockNum === null ? '-' : item.stockNum }}</strong>
|
||||
<el-button v-if="canSelectAgentResult(agent.plan)" type="primary" size="mini" plain @click="selectAgentResult(item)">选择</el-button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
@ -546,6 +549,64 @@ export default {
|
||||
this.agent.plan = null
|
||||
this.agent.pendingNextPlan = null
|
||||
},
|
||||
canSelectAgentResult(plan) {
|
||||
return plan && ['create_out_record', 'create_in_record', 'create_stock_check'].includes(plan.action) && plan.results && plan.results.length
|
||||
},
|
||||
selectAgentResult(item) {
|
||||
if (!this.agent.plan || !item) {
|
||||
return
|
||||
}
|
||||
const plan = this.completePlanWithResult(this.agent.plan, item)
|
||||
this.agent.plan = plan
|
||||
this.agent.contextResults = [item]
|
||||
if (plan.executable) {
|
||||
this.autoFillAgentForm(plan)
|
||||
} else {
|
||||
this.$modal.msgWarning(plan.message || '已选择产品,请补充缺少的信息')
|
||||
}
|
||||
},
|
||||
completePlanWithResult(source, item) {
|
||||
const plan = Object.assign({}, source, {
|
||||
stockId: item.id,
|
||||
areaId: item.areaId,
|
||||
productId: item.productId,
|
||||
areaName: item.areaName,
|
||||
productName: item.productName,
|
||||
brand: item.brand,
|
||||
category: item.category,
|
||||
spec: item.spec,
|
||||
unit: item.unit,
|
||||
currentStock: item.stockNum,
|
||||
candidates: [],
|
||||
results: [],
|
||||
needConfirm: true,
|
||||
message: '已选择产品,请确认后执行'
|
||||
})
|
||||
const quantity = this.toNumber(plan.quantity)
|
||||
const stock = this.toNumber(item.stockNum)
|
||||
if (plan.action === 'create_out_record') {
|
||||
plan.executable = !!(plan.areaId && plan.productId && quantity > 0)
|
||||
plan.afterStock = quantity > 0 && item.stockNum !== undefined && item.stockNum !== null ? stock - quantity : undefined
|
||||
if (plan.executable && stock < quantity) {
|
||||
plan.executable = false
|
||||
plan.message = '库存不足,当前库存:' + item.stockNum
|
||||
}
|
||||
} else if (plan.action === 'create_in_record') {
|
||||
plan.executable = !!(plan.areaId && plan.productId && quantity > 0)
|
||||
plan.afterStock = quantity > 0 && item.stockNum !== undefined && item.stockNum !== null ? stock + quantity : undefined
|
||||
} else if (plan.action === 'create_stock_check') {
|
||||
plan.executable = !!(plan.areaId && plan.productId && quantity >= 0)
|
||||
plan.afterStock = quantity >= 0 ? quantity : undefined
|
||||
}
|
||||
if (!plan.executable && plan.message === '已选择产品,请确认后执行') {
|
||||
plan.message = '已选择产品,还缺少库区或数量'
|
||||
}
|
||||
return plan
|
||||
},
|
||||
toNumber(value) {
|
||||
const numberValue = Number(value)
|
||||
return Number.isFinite(numberValue) ? numberValue : 0
|
||||
},
|
||||
autoFillAgentForm(plan) {
|
||||
if (!plan || !plan.executable) {
|
||||
return
|
||||
@ -561,7 +622,7 @@ export default {
|
||||
spec: plan.spec,
|
||||
unit: plan.unit,
|
||||
outOperator: plan.operatorName,
|
||||
outType: plan.outType || 3,
|
||||
outType: plan.outType || 1,
|
||||
outNum: plan.quantity,
|
||||
sourceNo: undefined,
|
||||
remark: plan.remark
|
||||
@ -574,7 +635,7 @@ export default {
|
||||
areaId: plan.areaId,
|
||||
productId: plan.productId,
|
||||
outOperator: plan.operatorName,
|
||||
outType: plan.outType || 3,
|
||||
outType: plan.outType || 1,
|
||||
outNum: plan.quantity,
|
||||
sourceNo: undefined,
|
||||
remark: plan.remark
|
||||
@ -739,7 +800,7 @@ export default {
|
||||
areaId: undefined,
|
||||
productId: undefined,
|
||||
inOperator: undefined,
|
||||
inType: 1,
|
||||
inType: 2,
|
||||
inNum: undefined,
|
||||
remark: undefined
|
||||
}, data || {})
|
||||
@ -1133,6 +1194,13 @@ export default {
|
||||
font-size: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.agent-stock-action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.quick-panel {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
|
||||
@ -198,7 +198,7 @@ export default {
|
||||
},
|
||||
cancel() { this.open = false; this.reset() },
|
||||
reset() {
|
||||
this.form = { id: undefined, inNo: undefined, areaId: undefined, productId: undefined, inOperator: undefined, inType: 1, inNum: undefined, remark: undefined }
|
||||
this.form = { id: undefined, inNo: undefined, areaId: undefined, productId: undefined, inOperator: undefined, inType: 2, inNum: undefined, remark: undefined }
|
||||
this.resetForm('form')
|
||||
},
|
||||
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
|
||||
|
||||
Loading…
Reference in New Issue
Block a user