feat(warehouse): 优化智能代理功能和库存管理

- 修改默认出库类型从销售出库改为生产出库
- 添加产品类别自动推断功能,支持基于历史数据和关键词的智能分类
- 实现库存查询结果显示和选择功能,优化用户体验
- 增强代理计划的消息提示和错误处理机制
- 添加现有产品类别的获取和展示功能
- 优化库存不足和产品未找到的提示信息
- 修复前端表单默认值设置问题
This commit is contained in:
lixiaolong 2026-07-17 09:17:42 +08:00
parent 7737fda061
commit d872e3e235
7 changed files with 471 additions and 14 deletions

View File

@ -12,7 +12,6 @@ ruoyi:
addressEnabled: false addressEnabled: false
# 验证码类型 math 数字计算 char 字符验证 # 验证码类型 math 数字计算 char 字符验证
captchaType: math captchaType: math
# 开发环境配置 # 开发环境配置
server: server:
# 服务器的HTTP端口默认为8080 # 服务器的HTTP端口默认为8080

View File

@ -9,6 +9,7 @@ public interface IProductInfoService
public List<ProductInfo> selectProductInfoAll(); public List<ProductInfo> selectProductInfoAll();
public ProductInfo selectProductInfoById(Long id); public ProductInfo selectProductInfoById(Long id);
public boolean checkProductCodeUnique(ProductInfo productInfo); public boolean checkProductCodeUnique(ProductInfo productInfo);
public String inferProductCategory(ProductInfo productInfo);
public int insertProductInfo(ProductInfo productInfo); public int insertProductInfo(ProductInfo productInfo);
public int updateProductInfo(ProductInfo productInfo); public int updateProductInfo(ProductInfo productInfo);
public int deleteProductInfoByIds(Long[] ids); public int deleteProductInfoByIds(Long[] ids);

View File

@ -9,8 +9,10 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.beans.factory.annotation.Autowired; 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) if (ACTION_CREATE_OUT.equals(plan.getAction()) && plan.getOutType() == null)
{ {
plan.setOutType(3); plan.setOutType(1);
plan.getWarnings().add("未识别出库类型,默认销售出库"); plan.getWarnings().add("未识别出库类型,默认生产出库");
} }
if (ACTION_CONFIRM_IN.equals(plan.getAction())) if (ACTION_CONFIRM_IN.equals(plan.getAction()))
@ -274,7 +276,7 @@ public class AgentServiceImpl implements IAgentService
fillStockResults(plan); fillStockResults(plan);
plan.setNeedConfirm(false); plan.setNeedConfirm(false);
plan.setExecutable(true); 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())) if (StringUtils.isBlank(plan.getSummary()))
{ {
plan.setSummary("查询库存"); plan.setSummary("查询库存");
@ -312,7 +314,7 @@ public class AgentServiceImpl implements IAgentService
fillSummary(plan); fillSummary(plan);
if (!plan.isExecutable() && StringUtils.isBlank(plan.getMessage())) if (!plan.isExecutable() && StringUtils.isBlank(plan.getMessage()))
{ {
plan.setMessage("信息不完整或存在多个候选,请补充后再执行"); fillUnableToExecuteMessage(plan);
} }
return plan; return plan;
} }
@ -418,7 +420,8 @@ public class AgentServiceImpl implements IAgentService
+ "如果用户说第几个、第二个、第2条等referenceIndex 返回从 1 开始的序号。" + "如果用户说第几个、第二个、第2条等referenceIndex 返回从 1 开始的序号。"
+ "入库类型 inType1生产入库2采购入库3归还入库4退货入库。" + "入库类型 inType1生产入库2采购入库3归还入库4退货入库。"
+ "出库类型 outType1生产出库2退货出库3销售出库。" + "出库类型 outType1生产出库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) private String buildUserInput(String text, List<AgentResultItem> contextResults)
@ -426,6 +429,7 @@ public class AgentServiceImpl implements IAgentService
JSONObject payload = new JSONObject(); JSONObject payload = new JSONObject();
payload.put("text", text); payload.put("text", text);
payload.put("contextResults", contextResults == null ? new ArrayList<>() : contextResults); payload.put("contextResults", contextResults == null ? new ArrayList<>() : contextResults);
payload.put("existingCategories", existingCategories());
return payload.toJSONString(); return payload.toJSONString();
} }
@ -850,7 +854,13 @@ public class AgentServiceImpl implements IAgentService
ProductInfo product = match.getProduct(); ProductInfo product = match.getProduct();
plan.getCandidates().add(new AgentCandidate(product.getId(), "product", product.getProductName(), productDetail(product))); 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) private void resolveReferencedResult(AgentPlan plan, String originalText, List<AgentResultItem> contextResults)
@ -965,6 +975,37 @@ public class AgentServiceImpl implements IAgentService
{ {
plan.setUnit(plan.getUnit().trim()); 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) private boolean requiresOperator(String action)
@ -987,6 +1028,80 @@ public class AgentServiceImpl implements IAgentService
return true; 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) private void fillActionCandidates(AgentPlan plan)
{ {
plan.getCandidates().add(new AgentCandidate(1L, "action", "查库存", "例如:查一下路由器库存")); plan.getCandidates().add(new AgentCandidate(1L, "action", "查库存", "例如:查一下路由器库存"));
@ -1199,6 +1314,51 @@ public class AgentServiceImpl implements IAgentService
return item; 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) private void fillCurrentStock(AgentPlan plan)
{ {
if (plan.getAreaId() == null || plan.getProductId() == null) if (plan.getAreaId() == null || plan.getProductId() == null)
@ -1235,7 +1395,8 @@ public class AgentServiceImpl implements IAgentService
private void suggestAlternativeStockAreas(AgentPlan plan) 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; return;
} }

View File

@ -1,6 +1,9 @@
package com.ruoyi.warehouse.service.impl; package com.ruoyi.warehouse.service.impl;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ruoyi.common.constant.UserConstants; 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; 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 @Override
public int insertProductInfo(ProductInfo productInfo) public int insertProductInfo(ProductInfo productInfo)
{ {
@ -79,6 +97,10 @@ public class ProductInfoServiceImpl implements IProductInfoService
productInfo.setCategory(trimToNull(productInfo.getCategory())); productInfo.setCategory(trimToNull(productInfo.getCategory()));
productInfo.setSpec(trimToNull(productInfo.getSpec())); productInfo.setSpec(trimToNull(productInfo.getSpec()));
productInfo.setUnit(trimToNull(productInfo.getUnit())); productInfo.setUnit(trimToNull(productInfo.getUnit()));
if (StringUtils.isBlank(productInfo.getCategory()) && StringUtils.isNotBlank(productInfo.getProductName()))
{
productInfo.setCategory(inferProductCategory(productInfo));
}
} }
private void validateProductInfoUnique(ProductInfo productInfo) private void validateProductInfoUnique(ProductInfo productInfo)
@ -95,4 +117,151 @@ public class ProductInfoServiceImpl implements IProductInfoService
{ {
return StringUtils.isBlank(value) ? null : value.trim(); 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));
}
} }

View File

@ -58,6 +58,11 @@
</div> </div>
<el-table v-if="agent.plan.results && agent.plan.results.length" :data="agent.plan.results" size="mini" class="agent-table"> <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 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="库区" prop="areaName" width="120" />
<el-table-column label="产品" min-width="220"> <el-table-column label="产品" min-width="220">
<template slot-scope="scope"> <template slot-scope="scope">
@ -229,6 +234,60 @@ export default {
this.agent.text = '' this.agent.text = ''
this.agent.plan = null 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) { actionLabel(action) {
const map = { const map = {
query_stock: '查询库存', query_stock: '查询库存',

View File

@ -76,12 +76,15 @@
<div v-if="agent.plan.candidates && agent.plan.candidates.length" class="agent-candidates"> <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> <span v-for="item in agent.plan.candidates" :key="item.type + item.id">{{ item.name }} {{ item.detail || '' }}</span>
</div> </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>
<div class="card-title"> {{ index + 1 }} {{ item.productName || '-' }}</div> <div class="card-title"> {{ index + 1 }} {{ item.productName || '-' }}</div>
<div class="meta-line">{{ item.areaName || '-' }} {{ productMeta(item) }}</div> <div class="meta-line">{{ item.areaName || '-' }} {{ productMeta(item) }}</div>
</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> </article>
</div> </div>
</div> </div>
@ -546,6 +549,64 @@ export default {
this.agent.plan = null this.agent.plan = null
this.agent.pendingNextPlan = 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) { autoFillAgentForm(plan) {
if (!plan || !plan.executable) { if (!plan || !plan.executable) {
return return
@ -561,7 +622,7 @@ export default {
spec: plan.spec, spec: plan.spec,
unit: plan.unit, unit: plan.unit,
outOperator: plan.operatorName, outOperator: plan.operatorName,
outType: plan.outType || 3, outType: plan.outType || 1,
outNum: plan.quantity, outNum: plan.quantity,
sourceNo: undefined, sourceNo: undefined,
remark: plan.remark remark: plan.remark
@ -574,7 +635,7 @@ export default {
areaId: plan.areaId, areaId: plan.areaId,
productId: plan.productId, productId: plan.productId,
outOperator: plan.operatorName, outOperator: plan.operatorName,
outType: plan.outType || 3, outType: plan.outType || 1,
outNum: plan.quantity, outNum: plan.quantity,
sourceNo: undefined, sourceNo: undefined,
remark: plan.remark remark: plan.remark
@ -739,7 +800,7 @@ export default {
areaId: undefined, areaId: undefined,
productId: undefined, productId: undefined,
inOperator: undefined, inOperator: undefined,
inType: 1, inType: 2,
inNum: undefined, inNum: undefined,
remark: undefined remark: undefined
}, data || {}) }, data || {})
@ -1133,6 +1194,13 @@ export default {
font-size: 18px; font-size: 18px;
white-space: nowrap; white-space: nowrap;
} }
.agent-stock-action {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
flex-shrink: 0;
}
.quick-panel { .quick-panel {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));

View File

@ -198,7 +198,7 @@ export default {
}, },
cancel() { this.open = false; this.reset() }, cancel() { this.open = false; this.reset() },
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') this.resetForm('form')
}, },
handleQuery() { this.queryParams.pageNum = 1; this.getList() }, handleQuery() { this.queryParams.pageNum = 1; this.getList() },