Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46ec60d6f5 | |||
| e76c07fb5e | |||
| d872e3e235 | |||
| 7737fda061 |
@ -0,0 +1,32 @@
|
||||
package com.ruoyi.web.controller.warehouse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentAnalyzeRequest;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentExecuteRequest;
|
||||
import com.ruoyi.warehouse.service.IAgentService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/warehouse/agent")
|
||||
public class AgentController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IAgentService agentService;
|
||||
|
||||
@PostMapping("/analyze")
|
||||
public AjaxResult analyze(@RequestBody AgentAnalyzeRequest request)
|
||||
{
|
||||
return success(agentService.analyze(request.getText(), request.getContextResults(), getUsername()));
|
||||
}
|
||||
|
||||
@PostMapping("/execute")
|
||||
public AjaxResult execute(@RequestBody AgentExecuteRequest request)
|
||||
{
|
||||
return success(agentService.execute(request.getPlan(), getUsername()));
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.ruoyi.web.controller.warehouse;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -11,10 +12,14 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.PageDomain;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.core.page.TableSupport;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.warehouse.domain.OutRecord;
|
||||
import com.ruoyi.warehouse.domain.StockInfo;
|
||||
@ -31,10 +36,31 @@ public class StockInfoController extends BaseController
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(StockInfo stockInfo)
|
||||
{
|
||||
if (StringUtils.isNotBlank(stockInfo.getProductName()))
|
||||
{
|
||||
return getKeywordDataTable(stockInfoService.selectStockInfoList(stockInfo));
|
||||
}
|
||||
startPage();
|
||||
return getDataTable(stockInfoService.selectStockInfoList(stockInfo));
|
||||
}
|
||||
|
||||
private TableDataInfo getKeywordDataTable(List<StockInfo> list)
|
||||
{
|
||||
PageDomain pageDomain = TableSupport.buildPageRequest();
|
||||
int pageNum = pageDomain.getPageNum() == null || pageDomain.getPageNum() < 1 ? 1 : pageDomain.getPageNum();
|
||||
int pageSize = pageDomain.getPageSize() == null || pageDomain.getPageSize() < 1 ? 10 : pageDomain.getPageSize();
|
||||
int total = list == null ? 0 : list.size();
|
||||
int fromIndex = Math.min((pageNum - 1) * pageSize, total);
|
||||
int toIndex = Math.min(fromIndex + pageSize, total);
|
||||
|
||||
TableDataInfo rspData = new TableDataInfo();
|
||||
rspData.setCode(HttpStatus.SUCCESS);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setTotal(total);
|
||||
rspData.setRows(total == 0 ? Collections.emptyList() : list.subList(fromIndex, toIndex));
|
||||
return rspData;
|
||||
}
|
||||
|
||||
@PreAuthorize("@ss.hasPermi('warehouse:stock:export')")
|
||||
@Log(title = "库存信息", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
|
||||
@ -0,0 +1,80 @@
|
||||
package com.ruoyi.web.controller.warehouse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
import com.ruoyi.warehouse.domain.WmsBatchActionUpdateRequest;
|
||||
import com.ruoyi.warehouse.domain.WmsBatchItem;
|
||||
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
||||
import com.ruoyi.warehouse.service.IWmsBatchImportService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/warehouse/batch")
|
||||
public class WmsBatchImportController extends BaseController
|
||||
{
|
||||
@Autowired
|
||||
private IWmsBatchImportService batchImportService;
|
||||
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(WmsBatchJob job)
|
||||
{
|
||||
startPage();
|
||||
return getDataTable(batchImportService.selectJobList(job));
|
||||
}
|
||||
|
||||
@PostMapping("/upload")
|
||||
@Log(title = "WMS批量导入", businessType = BusinessType.IMPORT)
|
||||
public AjaxResult upload(MultipartFile file) throws Exception
|
||||
{
|
||||
return success(batchImportService.uploadExcel(file, getUsername()));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getInfo(@PathVariable Long id)
|
||||
{
|
||||
return success(batchImportService.selectJobById(id));
|
||||
}
|
||||
|
||||
@GetMapping("/{jobId}/items")
|
||||
public AjaxResult items(@PathVariable Long jobId)
|
||||
{
|
||||
return success(batchImportService.selectItemList(jobId));
|
||||
}
|
||||
|
||||
@PutMapping("/item")
|
||||
public AjaxResult updateItem(@RequestBody WmsBatchItem item)
|
||||
{
|
||||
return success(batchImportService.updateItem(item));
|
||||
}
|
||||
|
||||
@PutMapping("/{jobId}/items/action")
|
||||
public AjaxResult updateItemAction(@PathVariable Long jobId, @RequestBody WmsBatchActionUpdateRequest request)
|
||||
{
|
||||
return toAjax(batchImportService.updateItemAction(jobId, request.getActionType(), request.getItemIds()));
|
||||
}
|
||||
|
||||
@DeleteMapping("/item/{id}")
|
||||
public AjaxResult deleteItem(@PathVariable Long id)
|
||||
{
|
||||
return toAjax(batchImportService.deleteItem(id));
|
||||
}
|
||||
|
||||
@PostMapping("/{jobId}/confirm")
|
||||
@Log(title = "WMS批量确认执行", businessType = BusinessType.INSERT)
|
||||
public AjaxResult confirm(@PathVariable Long jobId)
|
||||
{
|
||||
return success(batchImportService.confirmJob(jobId, getUsername()));
|
||||
}
|
||||
}
|
||||
@ -6,13 +6,12 @@ ruoyi:
|
||||
version: 3.9.2
|
||||
# 版权年份
|
||||
copyrightYear: 2026
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||
profile: D:/ruoyi/uploadPath
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux/Docker配置 /app/uploadPath)
|
||||
profile: ${RUOYI_PROFILE:D:/ruoyi/uploadPath}
|
||||
# 获取ip地址开关
|
||||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
captchaType: math
|
||||
|
||||
# 开发环境配置
|
||||
server:
|
||||
# 服务器的HTTP端口,默认为8080
|
||||
@ -57,9 +56,9 @@ spring:
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 10MB
|
||||
max-file-size: ${MAX_FILE_SIZE:50MB}
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 20MB
|
||||
max-request-size: ${MAX_REQUEST_SIZE:60MB}
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
@ -142,3 +141,10 @@ xss:
|
||||
excludes: /system/notice
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
|
||||
# WMS agent model config. Keep api-key in environment variables, not in source code.
|
||||
agent:
|
||||
openai:
|
||||
base-url: ${OPENAI_BASE_URL:http://192.168.28.10:18080}
|
||||
api-key: ${OPENAI_API_KEY:sk-dd185946191d42eaa54892dec488bf7b54cad2d7433aee8aac7a017365b0829d}
|
||||
model: ${OPENAI_MODEL:gpt-5.5}
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
package com.ruoyi.warehouse.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class WmsBatchActionUpdateRequest
|
||||
{
|
||||
private String actionType;
|
||||
private List<Long> itemIds;
|
||||
|
||||
public String getActionType()
|
||||
{
|
||||
return actionType;
|
||||
}
|
||||
|
||||
public void setActionType(String actionType)
|
||||
{
|
||||
this.actionType = actionType;
|
||||
}
|
||||
|
||||
public List<Long> getItemIds()
|
||||
{
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
public void setItemIds(List<Long> itemIds)
|
||||
{
|
||||
this.itemIds = itemIds;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package com.ruoyi.warehouse.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
public class WmsBatchItem extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private Long jobId;
|
||||
private Integer rowNo;
|
||||
private String actionType;
|
||||
private String rawText;
|
||||
private String parsedJson;
|
||||
private Integer status;
|
||||
private String errorMsg;
|
||||
private String warningMsg;
|
||||
private Long refId;
|
||||
private String areaName;
|
||||
private String productName;
|
||||
private String brand;
|
||||
private String category;
|
||||
private String spec;
|
||||
private String unit;
|
||||
private BigDecimal quantity;
|
||||
private String operatorName;
|
||||
private Integer typeCode;
|
||||
private String sourceNo;
|
||||
private String remark;
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getJobId() { return jobId; }
|
||||
public void setJobId(Long jobId) { this.jobId = jobId; }
|
||||
public Integer getRowNo() { return rowNo; }
|
||||
public void setRowNo(Integer rowNo) { this.rowNo = rowNo; }
|
||||
public String getActionType() { return actionType; }
|
||||
public void setActionType(String actionType) { this.actionType = actionType; }
|
||||
public String getRawText() { return rawText; }
|
||||
public void setRawText(String rawText) { this.rawText = rawText; }
|
||||
public String getParsedJson() { return parsedJson; }
|
||||
public void setParsedJson(String parsedJson) { this.parsedJson = parsedJson; }
|
||||
public Integer getStatus() { return status; }
|
||||
public void setStatus(Integer status) { this.status = status; }
|
||||
public String getErrorMsg() { return errorMsg; }
|
||||
public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; }
|
||||
public String getWarningMsg() { return warningMsg; }
|
||||
public void setWarningMsg(String warningMsg) { this.warningMsg = warningMsg; }
|
||||
public Long getRefId() { return refId; }
|
||||
public void setRefId(Long refId) { this.refId = refId; }
|
||||
public String getAreaName() { return areaName; }
|
||||
public void setAreaName(String areaName) { this.areaName = areaName; }
|
||||
public String getProductName() { return productName; }
|
||||
public void setProductName(String productName) { this.productName = productName; }
|
||||
public String getBrand() { return brand; }
|
||||
public void setBrand(String brand) { this.brand = brand; }
|
||||
public String getCategory() { return category; }
|
||||
public void setCategory(String category) { this.category = category; }
|
||||
public String getSpec() { return spec; }
|
||||
public void setSpec(String spec) { this.spec = spec; }
|
||||
public String getUnit() { return unit; }
|
||||
public void setUnit(String unit) { this.unit = unit; }
|
||||
public BigDecimal getQuantity() { return quantity; }
|
||||
public void setQuantity(BigDecimal quantity) { this.quantity = quantity; }
|
||||
public String getOperatorName() { return operatorName; }
|
||||
public void setOperatorName(String operatorName) { this.operatorName = operatorName; }
|
||||
public Integer getTypeCode() { return typeCode; }
|
||||
public void setTypeCode(Integer typeCode) { this.typeCode = typeCode; }
|
||||
public String getSourceNo() { return sourceNo; }
|
||||
public void setSourceNo(String sourceNo) { this.sourceNo = sourceNo; }
|
||||
public String getRemark() { return remark; }
|
||||
public void setRemark(String remark) { this.remark = remark; }
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
package com.ruoyi.warehouse.domain;
|
||||
|
||||
import com.ruoyi.common.core.domain.BaseEntity;
|
||||
|
||||
public class WmsBatchJob extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
private String jobNo;
|
||||
private String sourceType;
|
||||
private String bizType;
|
||||
private String fileName;
|
||||
private String filePath;
|
||||
private Integer status;
|
||||
private Integer progress;
|
||||
private Integer totalCount;
|
||||
private Integer successCount;
|
||||
private Integer failCount;
|
||||
private String errorMsg;
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getJobNo()
|
||||
{
|
||||
return jobNo;
|
||||
}
|
||||
|
||||
public void setJobNo(String jobNo)
|
||||
{
|
||||
this.jobNo = jobNo;
|
||||
}
|
||||
|
||||
public String getSourceType()
|
||||
{
|
||||
return sourceType;
|
||||
}
|
||||
|
||||
public void setSourceType(String sourceType)
|
||||
{
|
||||
this.sourceType = sourceType;
|
||||
}
|
||||
|
||||
public String getBizType()
|
||||
{
|
||||
return bizType;
|
||||
}
|
||||
|
||||
public void setBizType(String bizType)
|
||||
{
|
||||
this.bizType = bizType;
|
||||
}
|
||||
|
||||
public String getFileName()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName)
|
||||
{
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getFilePath()
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void setFilePath(String filePath)
|
||||
{
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public Integer getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(Integer status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Integer getProgress()
|
||||
{
|
||||
return progress;
|
||||
}
|
||||
|
||||
public void setProgress(Integer progress)
|
||||
{
|
||||
this.progress = progress;
|
||||
}
|
||||
|
||||
public Integer getTotalCount()
|
||||
{
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public void setTotalCount(Integer totalCount)
|
||||
{
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
|
||||
public Integer getSuccessCount()
|
||||
{
|
||||
return successCount;
|
||||
}
|
||||
|
||||
public void setSuccessCount(Integer successCount)
|
||||
{
|
||||
this.successCount = successCount;
|
||||
}
|
||||
|
||||
public Integer getFailCount()
|
||||
{
|
||||
return failCount;
|
||||
}
|
||||
|
||||
public void setFailCount(Integer failCount)
|
||||
{
|
||||
this.failCount = failCount;
|
||||
}
|
||||
|
||||
public String getErrorMsg()
|
||||
{
|
||||
return errorMsg;
|
||||
}
|
||||
|
||||
public void setErrorMsg(String errorMsg)
|
||||
{
|
||||
this.errorMsg = errorMsg;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AgentAnalyzeRequest
|
||||
{
|
||||
private String text;
|
||||
private List<AgentResultItem> contextResults = new ArrayList<>();
|
||||
|
||||
public String getText()
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text)
|
||||
{
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public List<AgentResultItem> getContextResults()
|
||||
{
|
||||
return contextResults;
|
||||
}
|
||||
|
||||
public void setContextResults(List<AgentResultItem> contextResults)
|
||||
{
|
||||
this.contextResults = contextResults;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
public class AgentCandidate
|
||||
{
|
||||
private Long id;
|
||||
private String type;
|
||||
private String name;
|
||||
private String detail;
|
||||
|
||||
public AgentCandidate()
|
||||
{
|
||||
}
|
||||
|
||||
public AgentCandidate(Long id, String type, String name, String detail)
|
||||
{
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type)
|
||||
{
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDetail()
|
||||
{
|
||||
return detail;
|
||||
}
|
||||
|
||||
public void setDetail(String detail)
|
||||
{
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
public class AgentExecuteRequest
|
||||
{
|
||||
private AgentPlan plan;
|
||||
|
||||
public AgentPlan getPlan()
|
||||
{
|
||||
return plan;
|
||||
}
|
||||
|
||||
public void setPlan(AgentPlan plan)
|
||||
{
|
||||
this.plan = plan;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,317 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class AgentPlan
|
||||
{
|
||||
private String action;
|
||||
private String summary;
|
||||
private String message;
|
||||
private boolean executable;
|
||||
private boolean needConfirm;
|
||||
private Long areaId;
|
||||
private Long productId;
|
||||
private Long stockId;
|
||||
private Long recordId;
|
||||
private Integer referenceIndex;
|
||||
private String areaName;
|
||||
private String productName;
|
||||
private String brand;
|
||||
private String category;
|
||||
private String spec;
|
||||
private String unit;
|
||||
private BigDecimal quantity;
|
||||
private Integer inType;
|
||||
private Integer outType;
|
||||
private String operatorName;
|
||||
private String recordNo;
|
||||
private String remark;
|
||||
private BigDecimal currentStock;
|
||||
private BigDecimal afterStock;
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
private List<AgentCandidate> candidates = new ArrayList<>();
|
||||
private List<AgentResultItem> results = new ArrayList<>();
|
||||
private AgentPlan nextPlan;
|
||||
|
||||
public String getAction()
|
||||
{
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(String action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getSummary()
|
||||
{
|
||||
return summary;
|
||||
}
|
||||
|
||||
public void setSummary(String summary)
|
||||
{
|
||||
this.summary = summary;
|
||||
}
|
||||
|
||||
public String getMessage()
|
||||
{
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public boolean isExecutable()
|
||||
{
|
||||
return executable;
|
||||
}
|
||||
|
||||
public void setExecutable(boolean executable)
|
||||
{
|
||||
this.executable = executable;
|
||||
}
|
||||
|
||||
public boolean isNeedConfirm()
|
||||
{
|
||||
return needConfirm;
|
||||
}
|
||||
|
||||
public void setNeedConfirm(boolean needConfirm)
|
||||
{
|
||||
this.needConfirm = needConfirm;
|
||||
}
|
||||
|
||||
public Long getAreaId()
|
||||
{
|
||||
return areaId;
|
||||
}
|
||||
|
||||
public void setAreaId(Long areaId)
|
||||
{
|
||||
this.areaId = areaId;
|
||||
}
|
||||
|
||||
public Long getProductId()
|
||||
{
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId)
|
||||
{
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public Long getStockId()
|
||||
{
|
||||
return stockId;
|
||||
}
|
||||
|
||||
public void setStockId(Long stockId)
|
||||
{
|
||||
this.stockId = stockId;
|
||||
}
|
||||
|
||||
public Long getRecordId()
|
||||
{
|
||||
return recordId;
|
||||
}
|
||||
|
||||
public void setRecordId(Long recordId)
|
||||
{
|
||||
this.recordId = recordId;
|
||||
}
|
||||
|
||||
public Integer getReferenceIndex()
|
||||
{
|
||||
return referenceIndex;
|
||||
}
|
||||
|
||||
public void setReferenceIndex(Integer referenceIndex)
|
||||
{
|
||||
this.referenceIndex = referenceIndex;
|
||||
}
|
||||
|
||||
public String getAreaName()
|
||||
{
|
||||
return areaName;
|
||||
}
|
||||
|
||||
public void setAreaName(String areaName)
|
||||
{
|
||||
this.areaName = areaName;
|
||||
}
|
||||
|
||||
public String getProductName()
|
||||
{
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName)
|
||||
{
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getBrand()
|
||||
{
|
||||
return brand;
|
||||
}
|
||||
|
||||
public void setBrand(String brand)
|
||||
{
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public String getCategory()
|
||||
{
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category)
|
||||
{
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getSpec()
|
||||
{
|
||||
return spec;
|
||||
}
|
||||
|
||||
public void setSpec(String spec)
|
||||
{
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public String getUnit()
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit)
|
||||
{
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public BigDecimal getQuantity()
|
||||
{
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(BigDecimal quantity)
|
||||
{
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Integer getInType()
|
||||
{
|
||||
return inType;
|
||||
}
|
||||
|
||||
public void setInType(Integer inType)
|
||||
{
|
||||
this.inType = inType;
|
||||
}
|
||||
|
||||
public Integer getOutType()
|
||||
{
|
||||
return outType;
|
||||
}
|
||||
|
||||
public void setOutType(Integer outType)
|
||||
{
|
||||
this.outType = outType;
|
||||
}
|
||||
|
||||
public String getOperatorName()
|
||||
{
|
||||
return operatorName;
|
||||
}
|
||||
|
||||
public void setOperatorName(String operatorName)
|
||||
{
|
||||
this.operatorName = operatorName;
|
||||
}
|
||||
|
||||
public String getRecordNo()
|
||||
{
|
||||
return recordNo;
|
||||
}
|
||||
|
||||
public void setRecordNo(String recordNo)
|
||||
{
|
||||
this.recordNo = recordNo;
|
||||
}
|
||||
|
||||
public String getRemark()
|
||||
{
|
||||
return remark;
|
||||
}
|
||||
|
||||
public void setRemark(String remark)
|
||||
{
|
||||
this.remark = remark;
|
||||
}
|
||||
|
||||
public BigDecimal getCurrentStock()
|
||||
{
|
||||
return currentStock;
|
||||
}
|
||||
|
||||
public void setCurrentStock(BigDecimal currentStock)
|
||||
{
|
||||
this.currentStock = currentStock;
|
||||
}
|
||||
|
||||
public BigDecimal getAfterStock()
|
||||
{
|
||||
return afterStock;
|
||||
}
|
||||
|
||||
public void setAfterStock(BigDecimal afterStock)
|
||||
{
|
||||
this.afterStock = afterStock;
|
||||
}
|
||||
|
||||
public List<String> getWarnings()
|
||||
{
|
||||
return warnings;
|
||||
}
|
||||
|
||||
public void setWarnings(List<String> warnings)
|
||||
{
|
||||
this.warnings = warnings;
|
||||
}
|
||||
|
||||
public List<AgentCandidate> getCandidates()
|
||||
{
|
||||
return candidates;
|
||||
}
|
||||
|
||||
public void setCandidates(List<AgentCandidate> candidates)
|
||||
{
|
||||
this.candidates = candidates;
|
||||
}
|
||||
|
||||
public List<AgentResultItem> getResults()
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
public void setResults(List<AgentResultItem> results)
|
||||
{
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public AgentPlan getNextPlan()
|
||||
{
|
||||
return nextPlan;
|
||||
}
|
||||
|
||||
public void setNextPlan(AgentPlan nextPlan)
|
||||
{
|
||||
this.nextPlan = nextPlan;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
package com.ruoyi.warehouse.domain.agent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class AgentResultItem
|
||||
{
|
||||
private Long id;
|
||||
private Long areaId;
|
||||
private Long productId;
|
||||
private String areaName;
|
||||
private String productName;
|
||||
private String brand;
|
||||
private String category;
|
||||
private String spec;
|
||||
private String unit;
|
||||
private BigDecimal stockNum;
|
||||
private String recordNo;
|
||||
|
||||
public Long getId()
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id)
|
||||
{
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Long getAreaId()
|
||||
{
|
||||
return areaId;
|
||||
}
|
||||
|
||||
public void setAreaId(Long areaId)
|
||||
{
|
||||
this.areaId = areaId;
|
||||
}
|
||||
|
||||
public Long getProductId()
|
||||
{
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId)
|
||||
{
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public String getAreaName()
|
||||
{
|
||||
return areaName;
|
||||
}
|
||||
|
||||
public void setAreaName(String areaName)
|
||||
{
|
||||
this.areaName = areaName;
|
||||
}
|
||||
|
||||
public String getProductName()
|
||||
{
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName)
|
||||
{
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getBrand()
|
||||
{
|
||||
return brand;
|
||||
}
|
||||
|
||||
public void setBrand(String brand)
|
||||
{
|
||||
this.brand = brand;
|
||||
}
|
||||
|
||||
public String getCategory()
|
||||
{
|
||||
return category;
|
||||
}
|
||||
|
||||
public void setCategory(String category)
|
||||
{
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
public String getSpec()
|
||||
{
|
||||
return spec;
|
||||
}
|
||||
|
||||
public void setSpec(String spec)
|
||||
{
|
||||
this.spec = spec;
|
||||
}
|
||||
|
||||
public String getUnit()
|
||||
{
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit)
|
||||
{
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public BigDecimal getStockNum()
|
||||
{
|
||||
return stockNum;
|
||||
}
|
||||
|
||||
public void setStockNum(BigDecimal stockNum)
|
||||
{
|
||||
this.stockNum = stockNum;
|
||||
}
|
||||
|
||||
public String getRecordNo()
|
||||
{
|
||||
return recordNo;
|
||||
}
|
||||
|
||||
public void setRecordNo(String recordNo)
|
||||
{
|
||||
this.recordNo = recordNo;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.ruoyi.warehouse.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.warehouse.domain.WmsBatchItem;
|
||||
|
||||
public interface WmsBatchItemMapper
|
||||
{
|
||||
public List<WmsBatchItem> selectWmsBatchItemList(WmsBatchItem item);
|
||||
public WmsBatchItem selectWmsBatchItemById(Long id);
|
||||
public int insertWmsBatchItem(WmsBatchItem item);
|
||||
public int updateWmsBatchItem(WmsBatchItem item);
|
||||
public int deleteWmsBatchItemById(Long id);
|
||||
public int deleteWmsBatchItemByJobId(Long jobId);
|
||||
public int countByJobIdAndStatus(WmsBatchItem item);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.ruoyi.warehouse.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
||||
|
||||
public interface WmsBatchJobMapper
|
||||
{
|
||||
public List<WmsBatchJob> selectWmsBatchJobList(WmsBatchJob job);
|
||||
public WmsBatchJob selectWmsBatchJobById(Long id);
|
||||
public int insertWmsBatchJob(WmsBatchJob job);
|
||||
public int updateWmsBatchJob(WmsBatchJob job);
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.ruoyi.warehouse.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentPlan;
|
||||
import com.ruoyi.warehouse.domain.agent.AgentResultItem;
|
||||
|
||||
public interface IAgentService
|
||||
{
|
||||
public AgentPlan analyze(String text, List<AgentResultItem> contextResults, String username);
|
||||
|
||||
public AgentPlan execute(AgentPlan plan, String username);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
package com.ruoyi.warehouse.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.warehouse.domain.WmsBatchItem;
|
||||
import com.ruoyi.warehouse.domain.WmsBatchJob;
|
||||
|
||||
public interface IWmsBatchImportService
|
||||
{
|
||||
public WmsBatchJob uploadExcel(MultipartFile file, String username) throws Exception;
|
||||
public List<WmsBatchJob> selectJobList(WmsBatchJob job);
|
||||
public WmsBatchJob selectJobById(Long id);
|
||||
public List<WmsBatchItem> selectItemList(Long jobId);
|
||||
public WmsBatchItem updateItem(WmsBatchItem item);
|
||||
public int updateItemAction(Long jobId, String actionType, List<Long> itemIds);
|
||||
public int deleteItem(Long id);
|
||||
public WmsBatchJob confirmJob(Long jobId, String username);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package com.ruoyi.warehouse.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.warehouse.domain.OutRecord;
|
||||
import com.ruoyi.warehouse.domain.StockInfo;
|
||||
import com.ruoyi.warehouse.mapper.StockInfoMapper;
|
||||
@ -13,6 +16,8 @@ import com.ruoyi.warehouse.service.IStockInfoService;
|
||||
@Service
|
||||
public class StockInfoServiceImpl implements IStockInfoService
|
||||
{
|
||||
private static final int MAX_KEYWORD_TOKEN_COUNT = 5;
|
||||
|
||||
@Autowired
|
||||
private StockInfoMapper stockInfoMapper;
|
||||
|
||||
@ -22,7 +27,121 @@ public class StockInfoServiceImpl implements IStockInfoService
|
||||
@Override
|
||||
public List<StockInfo> selectStockInfoList(StockInfo stockInfo)
|
||||
{
|
||||
return stockInfoMapper.selectStockInfoList(stockInfo);
|
||||
List<String> tokens = parseKeywordTokens(stockInfo);
|
||||
List<StockInfo> list = stockInfoMapper.selectStockInfoList(stockInfo);
|
||||
if (tokens.isEmpty())
|
||||
{
|
||||
return list;
|
||||
}
|
||||
List<ScoredStock> scoredList = new ArrayList<>();
|
||||
for (StockInfo item : list)
|
||||
{
|
||||
int score = scoreStock(item, tokens);
|
||||
if (score > 0)
|
||||
{
|
||||
scoredList.add(new ScoredStock(item, score));
|
||||
}
|
||||
}
|
||||
scoredList.sort((left, right) -> {
|
||||
int scoreCompare = Integer.compare(right.score, left.score);
|
||||
if (scoreCompare != 0)
|
||||
{
|
||||
return scoreCompare;
|
||||
}
|
||||
long leftId = left.stock.getId() == null ? 0L : left.stock.getId();
|
||||
long rightId = right.stock.getId() == null ? 0L : right.stock.getId();
|
||||
return Long.compare(rightId, leftId);
|
||||
});
|
||||
List<StockInfo> result = new ArrayList<>(scoredList.size());
|
||||
for (ScoredStock item : scoredList)
|
||||
{
|
||||
result.add(item.stock);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> parseKeywordTokens(StockInfo stockInfo)
|
||||
{
|
||||
if (stockInfo == null || StringUtils.isBlank(stockInfo.getProductName()))
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
LinkedHashSet<String> tokens = new LinkedHashSet<>();
|
||||
String keyword = stockInfo.getProductName().trim().toLowerCase();
|
||||
for (String item : keyword.split("[\\s,,;;、]+"))
|
||||
{
|
||||
if (StringUtils.isNotBlank(item))
|
||||
{
|
||||
tokens.add(item.trim());
|
||||
}
|
||||
if (tokens.size() >= MAX_KEYWORD_TOKEN_COUNT)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tokens.isEmpty())
|
||||
{
|
||||
return new ArrayList<>();
|
||||
}
|
||||
stockInfo.getParams().put("keywordTokens", new ArrayList<>(tokens));
|
||||
return new ArrayList<>(tokens);
|
||||
}
|
||||
|
||||
private int scoreStock(StockInfo stock, List<String> tokens)
|
||||
{
|
||||
int totalScore = 0;
|
||||
for (String token : tokens)
|
||||
{
|
||||
int tokenScore = 0;
|
||||
tokenScore += scoreField(stock.getProductName(), token, 120, 90, 60);
|
||||
tokenScore += scoreField(stock.getSpec(), token, 90, 70, 45);
|
||||
tokenScore += scoreField(stock.getBrand(), token, 80, 60, 40);
|
||||
tokenScore += scoreField(stock.getCategory(), token, 70, 50, 35);
|
||||
tokenScore += scoreField(stock.getAreaName(), token, 65, 45, 30);
|
||||
tokenScore += scoreField(stock.getUnit(), token, 35, 0, 20);
|
||||
tokenScore += scoreField(stock.getProductCode(), token, 0, 0, 10);
|
||||
tokenScore += scoreField(stock.getAreaCode(), token, 0, 0, 10);
|
||||
if (tokenScore <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
totalScore += tokenScore;
|
||||
}
|
||||
return totalScore;
|
||||
}
|
||||
|
||||
private int scoreField(String value, String token, int exactScore, int prefixScore, int containsScore)
|
||||
{
|
||||
if (StringUtils.isBlank(value) || StringUtils.isBlank(token))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
String lowerValue = value.toLowerCase();
|
||||
if (exactScore > 0 && lowerValue.equals(token))
|
||||
{
|
||||
return exactScore;
|
||||
}
|
||||
if (prefixScore > 0 && lowerValue.startsWith(token))
|
||||
{
|
||||
return prefixScore;
|
||||
}
|
||||
if (containsScore > 0 && lowerValue.contains(token))
|
||||
{
|
||||
return containsScore;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static class ScoredStock
|
||||
{
|
||||
private final StockInfo stock;
|
||||
private final int score;
|
||||
|
||||
private ScoredStock(StockInfo stock, int score)
|
||||
{
|
||||
this.stock = stock;
|
||||
this.score = score;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -33,8 +33,8 @@
|
||||
<if test="areaId != null">and si.area_id = #{areaId}</if>
|
||||
<if test="productId != null">and si.product_id = #{productId}</if>
|
||||
<if test="areaName != null and areaName != ''">and wa.area_name like concat('%', #{areaName}, '%')</if>
|
||||
<if test="productName != null and productName != ''">
|
||||
and lower(concat_ws(' ', pi.product_name, pi.brand, pi.category, pi.spec, pi.unit))
|
||||
<if test="productName != null and productName != '' and (params.keywordTokens == null or params.keywordTokens.size() == 0)">
|
||||
and lower(concat_ws(' ', pi.product_name, pi.brand, pi.category, pi.spec, pi.unit, wa.area_name))
|
||||
like concat('%', lower(#{productName}), '%')
|
||||
</if>
|
||||
</where>
|
||||
|
||||
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.warehouse.mapper.WmsBatchItemMapper">
|
||||
<resultMap type="WmsBatchItem" id="WmsBatchItemResult">
|
||||
<id property="id" column="id"/>
|
||||
<result property="jobId" column="job_id"/>
|
||||
<result property="rowNo" column="row_no"/>
|
||||
<result property="actionType" column="action_type"/>
|
||||
<result property="rawText" column="raw_text"/>
|
||||
<result property="parsedJson" column="parsed_json"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="errorMsg" column="error_msg"/>
|
||||
<result property="warningMsg" column="warning_msg"/>
|
||||
<result property="refId" column="ref_id"/>
|
||||
<result property="areaName" column="area_name"/>
|
||||
<result property="productName" column="product_name"/>
|
||||
<result property="brand" column="brand"/>
|
||||
<result property="category" column="category"/>
|
||||
<result property="spec" column="spec"/>
|
||||
<result property="unit" column="unit"/>
|
||||
<result property="quantity" column="quantity"/>
|
||||
<result property="operatorName" column="operator_name"/>
|
||||
<result property="typeCode" column="type_code"/>
|
||||
<result property="sourceNo" column="source_no"/>
|
||||
<result property="remark" column="remark"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectWmsBatchItemVo">
|
||||
select id, job_id, row_no, action_type, raw_text, parsed_json, status, error_msg, warning_msg, ref_id,
|
||||
area_name, product_name, brand, category, spec, unit, quantity, operator_name, type_code,
|
||||
source_no, remark, create_time, update_time
|
||||
from wms_batch_item
|
||||
</sql>
|
||||
|
||||
<select id="selectWmsBatchItemList" parameterType="WmsBatchItem" resultMap="WmsBatchItemResult">
|
||||
<include refid="selectWmsBatchItemVo"/>
|
||||
<where>
|
||||
<if test="jobId != null">and job_id = #{jobId}</if>
|
||||
<if test="status != null">and status = #{status}</if>
|
||||
<if test="actionType != null and actionType != ''">and action_type = #{actionType}</if>
|
||||
</where>
|
||||
order by row_no asc, id asc
|
||||
</select>
|
||||
|
||||
<select id="selectWmsBatchItemById" parameterType="Long" resultMap="WmsBatchItemResult">
|
||||
<include refid="selectWmsBatchItemVo"/> where id = #{id}
|
||||
</select>
|
||||
|
||||
<select id="countByJobIdAndStatus" parameterType="WmsBatchItem" resultType="int">
|
||||
select count(1) from wms_batch_item where job_id = #{jobId} and status = #{status}
|
||||
</select>
|
||||
|
||||
<insert id="insertWmsBatchItem" parameterType="WmsBatchItem" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into wms_batch_item(job_id, row_no, action_type, raw_text, parsed_json, status, error_msg, warning_msg,
|
||||
ref_id, area_name, product_name, brand, category, spec, unit, quantity,
|
||||
operator_name, type_code, source_no, remark, create_time)
|
||||
values(#{jobId}, #{rowNo}, #{actionType}, #{rawText}, #{parsedJson}, #{status}, #{errorMsg}, #{warningMsg},
|
||||
#{refId}, #{areaName}, #{productName}, #{brand}, #{category}, #{spec}, #{unit}, #{quantity},
|
||||
#{operatorName}, #{typeCode}, #{sourceNo}, #{remark}, sysdate())
|
||||
</insert>
|
||||
|
||||
<update id="updateWmsBatchItem" parameterType="WmsBatchItem">
|
||||
update wms_batch_item
|
||||
<set>
|
||||
row_no = #{rowNo},
|
||||
action_type = #{actionType},
|
||||
raw_text = #{rawText},
|
||||
parsed_json = #{parsedJson},
|
||||
status = #{status},
|
||||
error_msg = #{errorMsg},
|
||||
warning_msg = #{warningMsg},
|
||||
ref_id = #{refId},
|
||||
area_name = #{areaName},
|
||||
product_name = #{productName},
|
||||
brand = #{brand},
|
||||
category = #{category},
|
||||
spec = #{spec},
|
||||
unit = #{unit},
|
||||
quantity = #{quantity},
|
||||
operator_name = #{operatorName},
|
||||
type_code = #{typeCode},
|
||||
source_no = #{sourceNo},
|
||||
remark = #{remark},
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
|
||||
<delete id="deleteWmsBatchItemById" parameterType="Long">
|
||||
delete from wms_batch_item where id = #{id}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteWmsBatchItemByJobId" parameterType="Long">
|
||||
delete from wms_batch_item where job_id = #{value}
|
||||
</delete>
|
||||
</mapper>
|
||||
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.warehouse.mapper.WmsBatchJobMapper">
|
||||
<resultMap type="WmsBatchJob" id="WmsBatchJobResult">
|
||||
<id property="id" column="id"/>
|
||||
<result property="jobNo" column="job_no"/>
|
||||
<result property="sourceType" column="source_type"/>
|
||||
<result property="bizType" column="biz_type"/>
|
||||
<result property="fileName" column="file_name"/>
|
||||
<result property="filePath" column="file_path"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="progress" column="progress"/>
|
||||
<result property="totalCount" column="total_count"/>
|
||||
<result property="successCount" column="success_count"/>
|
||||
<result property="failCount" column="fail_count"/>
|
||||
<result property="errorMsg" column="error_msg"/>
|
||||
<result property="createBy" column="create_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
</resultMap>
|
||||
|
||||
<sql id="selectWmsBatchJobVo">
|
||||
select id, job_no, source_type, biz_type, file_name, file_path, status, progress,
|
||||
total_count, success_count, fail_count, error_msg, create_by, create_time, update_time
|
||||
from wms_batch_job
|
||||
</sql>
|
||||
|
||||
<select id="selectWmsBatchJobList" parameterType="WmsBatchJob" resultMap="WmsBatchJobResult">
|
||||
<include refid="selectWmsBatchJobVo"/>
|
||||
<where>
|
||||
<if test="jobNo != null and jobNo != ''">and job_no like concat('%', #{jobNo}, '%')</if>
|
||||
<if test="sourceType != null and sourceType != ''">and source_type = #{sourceType}</if>
|
||||
<if test="bizType != null and bizType != ''">and biz_type = #{bizType}</if>
|
||||
<if test="status != null">and status = #{status}</if>
|
||||
</where>
|
||||
order by id desc
|
||||
</select>
|
||||
|
||||
<select id="selectWmsBatchJobById" parameterType="Long" resultMap="WmsBatchJobResult">
|
||||
<include refid="selectWmsBatchJobVo"/> where id = #{id}
|
||||
</select>
|
||||
|
||||
<insert id="insertWmsBatchJob" parameterType="WmsBatchJob" useGeneratedKeys="true" keyProperty="id">
|
||||
insert into wms_batch_job(job_no, source_type, biz_type, file_name, file_path, status, progress,
|
||||
total_count, success_count, fail_count, error_msg, create_by, create_time)
|
||||
values(#{jobNo}, #{sourceType}, #{bizType}, #{fileName}, #{filePath}, #{status}, #{progress},
|
||||
#{totalCount}, #{successCount}, #{failCount}, #{errorMsg}, #{createBy}, sysdate())
|
||||
</insert>
|
||||
|
||||
<update id="updateWmsBatchJob" parameterType="WmsBatchJob">
|
||||
update wms_batch_job
|
||||
<set>
|
||||
<if test="sourceType != null">source_type = #{sourceType},</if>
|
||||
<if test="bizType != null">biz_type = #{bizType},</if>
|
||||
<if test="fileName != null">file_name = #{fileName},</if>
|
||||
<if test="filePath != null">file_path = #{filePath},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
<if test="progress != null">progress = #{progress},</if>
|
||||
<if test="totalCount != null">total_count = #{totalCount},</if>
|
||||
<if test="successCount != null">success_count = #{successCount},</if>
|
||||
<if test="failCount != null">fail_count = #{failCount},</if>
|
||||
error_msg = #{errorMsg},
|
||||
update_time = sysdate()
|
||||
</set>
|
||||
where id = #{id}
|
||||
</update>
|
||||
</mapper>
|
||||
21
ruoyi-ui/src/api/warehouse/agent.js
Normal file
21
ruoyi-ui/src/api/warehouse/agent.js
Normal file
@ -0,0 +1,21 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function analyzeAgent(data) {
|
||||
return request({
|
||||
url: '/warehouse/agent/analyze',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
repeatSubmit: false
|
||||
},
|
||||
timeout: 45000
|
||||
})
|
||||
}
|
||||
|
||||
export function executeAgent(data) {
|
||||
return request({
|
||||
url: '/warehouse/agent/execute',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
46
ruoyi-ui/src/api/warehouse/batchImport.js
Normal file
46
ruoyi-ui/src/api/warehouse/batchImport.js
Normal file
@ -0,0 +1,46 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function listBatchJob(query) {
|
||||
return request({ url: '/warehouse/batch/list', method: 'get', params: query })
|
||||
}
|
||||
|
||||
export function uploadBatchFile(file) {
|
||||
const data = new FormData()
|
||||
data.append('file', file)
|
||||
return request({
|
||||
url: '/warehouse/batch/upload',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: { 'Content-Type': 'multipart/form-data', repeatSubmit: false },
|
||||
timeout: 60000
|
||||
})
|
||||
}
|
||||
|
||||
export function getBatchJob(id) {
|
||||
return request({ url: '/warehouse/batch/' + id, method: 'get' })
|
||||
}
|
||||
|
||||
export function listBatchItems(jobId) {
|
||||
return request({ url: '/warehouse/batch/' + jobId + '/items', method: 'get' })
|
||||
}
|
||||
|
||||
export function updateBatchItem(data) {
|
||||
return request({ url: '/warehouse/batch/item', method: 'put', data })
|
||||
}
|
||||
|
||||
export function updateBatchItemAction(jobId, data) {
|
||||
return request({ url: '/warehouse/batch/' + jobId + '/items/action', method: 'put', data })
|
||||
}
|
||||
|
||||
export function deleteBatchItem(id) {
|
||||
return request({ url: '/warehouse/batch/item/' + id, method: 'delete' })
|
||||
}
|
||||
|
||||
export function confirmBatchJob(jobId) {
|
||||
return request({
|
||||
url: '/warehouse/batch/' + jobId + '/confirm',
|
||||
method: 'post',
|
||||
timeout: 120000,
|
||||
headers: { repeatSubmit: false }
|
||||
})
|
||||
}
|
||||
@ -67,6 +67,19 @@ export const constantRoutes = [
|
||||
hidden: true,
|
||||
meta: { title: '移动库存' }
|
||||
},
|
||||
{
|
||||
path: '/warehouse/batch-import',
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/warehouse/batchImport/index'),
|
||||
name: 'WmsBatchImport',
|
||||
meta: { title: '批量导入' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Layout,
|
||||
|
||||
77
ruoyi-ui/src/utils/imageCompress.js
Normal file
77
ruoyi-ui/src/utils/imageCompress.js
Normal file
@ -0,0 +1,77 @@
|
||||
function isImageFile(file) {
|
||||
return file && file.type && file.type.indexOf('image/') === 0
|
||||
}
|
||||
|
||||
function canvasToBlob(canvas, type, quality) {
|
||||
return new Promise(resolve => {
|
||||
if (!canvas.toBlob) {
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
canvas.toBlob(blob => resolve(blob), type, quality)
|
||||
})
|
||||
}
|
||||
|
||||
function loadImage(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = event => {
|
||||
const image = new Image()
|
||||
image.onload = () => resolve(image)
|
||||
image.onerror = reject
|
||||
image.src = event.target.result
|
||||
}
|
||||
reader.onerror = reject
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
export async function compressImageForUpload(file, options = {}) {
|
||||
const maxSize = options.maxSize || 3 * 1024 * 1024
|
||||
const maxWidth = options.maxWidth || 1600
|
||||
const maxHeight = options.maxHeight || 1600
|
||||
const outputType = options.outputType || 'image/jpeg'
|
||||
const minQuality = options.minQuality || 0.55
|
||||
|
||||
if (!isImageFile(file) || typeof FileReader === 'undefined' || typeof document === 'undefined') {
|
||||
return file
|
||||
}
|
||||
|
||||
try {
|
||||
const image = await loadImage(file)
|
||||
let scale = Math.min(maxWidth / image.width, maxHeight / image.height, 1)
|
||||
let quality = options.quality || 0.82
|
||||
let bestBlob = null
|
||||
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const width = Math.max(1, Math.round(image.width * scale))
|
||||
const height = Math.max(1, Math.round(image.height * scale))
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
const context = canvas.getContext('2d')
|
||||
context.fillStyle = '#ffffff'
|
||||
context.fillRect(0, 0, width, height)
|
||||
context.drawImage(image, 0, 0, width, height)
|
||||
|
||||
const blob = await canvasToBlob(canvas, outputType, quality)
|
||||
if (!blob) {
|
||||
return file
|
||||
}
|
||||
bestBlob = blob
|
||||
if (blob.size <= maxSize) {
|
||||
break
|
||||
}
|
||||
scale = scale * 0.82
|
||||
quality = Math.max(minQuality, quality - 0.08)
|
||||
}
|
||||
|
||||
if (!bestBlob || bestBlob.size >= file.size) {
|
||||
return file
|
||||
}
|
||||
const fileName = file.name ? file.name.replace(/\.[^.]+$/, '.jpg') : 'upload.jpg'
|
||||
return new File([bestBlob], fileName, { type: outputType, lastModified: Date.now() })
|
||||
} catch (error) {
|
||||
return file
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,77 @@
|
||||
<h1>库存管理系统</h1>
|
||||
<p>面向库区、产品、入库、出库和盘库的日常库存作业台。</p>
|
||||
</div>
|
||||
<el-button type="primary" icon="el-icon-mobile-phone" @click="$router.push('/mobile')">移动端入口</el-button>
|
||||
<div class="hero-actions">
|
||||
<el-button type="primary" icon="el-icon-upload2" @click="$router.push('/warehouse/batch-import')">批量导入</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-mobile-phone" @click="$router.push('/mobile')">移动端入口</el-button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="agent-panel">
|
||||
<div class="agent-title">
|
||||
<div>
|
||||
<h2>智能库存助手</h2>
|
||||
<p>输入一句话,系统先生成操作草稿,确认后再真正执行。</p>
|
||||
</div>
|
||||
<el-tag size="small" type="info">第一阶段</el-tag>
|
||||
</div>
|
||||
<div class="agent-input">
|
||||
<el-input
|
||||
v-model="agent.text"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
resize="none"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="例如:销售出库 10 个某产品,库区 A,出库人张三"
|
||||
@keyup.enter.native="analyzeAgentText"
|
||||
/>
|
||||
<el-button icon="el-icon-delete" :disabled="!agent.text && !agent.plan" @click="clearAgent">清空</el-button>
|
||||
<el-button type="primary" icon="el-icon-magic-stick" :loading="agent.loading" @click="analyzeAgentText">分析</el-button>
|
||||
</div>
|
||||
<div v-if="agent.plan" class="agent-result">
|
||||
<div class="agent-plan">
|
||||
<div>
|
||||
<el-tag size="small" :type="agent.plan.executable ? 'success' : 'warning'">{{ actionLabel(agent.plan.action) }}</el-tag>
|
||||
<strong>{{ agent.plan.summary || agent.plan.message || '已生成操作草稿' }}</strong>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="agent.plan.needConfirm"
|
||||
type="danger"
|
||||
size="small"
|
||||
icon="el-icon-check"
|
||||
:disabled="!agent.plan.executable"
|
||||
:loading="agent.executing"
|
||||
@click="executeAgentPlan"
|
||||
>确认执行</el-button>
|
||||
</div>
|
||||
<el-descriptions v-if="planItems.length" :column="3" size="small" border>
|
||||
<el-descriptions-item v-for="item in planItems" :key="item.label" :label="item.label">{{ item.value }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-alert v-if="agent.plan.message" :title="agent.plan.message" type="info" show-icon :closable="false" />
|
||||
<div v-if="agent.plan.warnings && agent.plan.warnings.length" class="agent-warnings">
|
||||
<el-tag v-for="item in agent.plan.warnings" :key="item" type="warning" size="small">{{ item }}</el-tag>
|
||||
</div>
|
||||
<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>
|
||||
<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">
|
||||
<div class="product-name">{{ scope.row.productName || '-' }}</div>
|
||||
<div class="product-meta">{{ productMeta(scope.row) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" prop="stockNum" align="right" width="110" />
|
||||
</el-table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="summary-grid">
|
||||
@ -85,6 +155,7 @@ import { listStock } from '@/api/warehouse/stock'
|
||||
import { listInRecord } from '@/api/warehouse/inRecord'
|
||||
import { listOutRecord } from '@/api/warehouse/outRecord'
|
||||
import { listStockCheck } from '@/api/warehouse/stockCheck'
|
||||
import { analyzeAgent, executeAgent } from '@/api/warehouse/agent'
|
||||
|
||||
export default {
|
||||
name: 'Index',
|
||||
@ -96,13 +167,142 @@ export default {
|
||||
pendingCheck: 0,
|
||||
outTotal: 0
|
||||
},
|
||||
stockList: []
|
||||
stockList: [],
|
||||
agent: {
|
||||
text: '',
|
||||
loading: false,
|
||||
executing: false,
|
||||
contextResults: [],
|
||||
plan: null
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
planItems() {
|
||||
const plan = this.agent.plan
|
||||
if (!plan) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{ label: '库区', value: plan.areaName },
|
||||
{ label: '产品', value: plan.productName },
|
||||
{ label: '品牌', value: plan.brand },
|
||||
{ label: '类别', value: plan.category },
|
||||
{ label: '规格', value: plan.spec },
|
||||
{ label: '单位', value: plan.unit },
|
||||
{ label: '数量', value: plan.quantity },
|
||||
{ label: '操作人', value: plan.operatorName },
|
||||
{ label: '当前库存', value: plan.currentStock },
|
||||
{ label: '预计库存', value: plan.afterStock },
|
||||
{ label: '单号', value: plan.recordNo }
|
||||
].filter(item => item.value !== undefined && item.value !== null && item.value !== '')
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDashboard()
|
||||
},
|
||||
methods: {
|
||||
analyzeAgentText() {
|
||||
const text = (this.agent.text || '').trim()
|
||||
if (!text) {
|
||||
this.$modal.msgWarning('请输入要操作的内容')
|
||||
return
|
||||
}
|
||||
this.agent.loading = true
|
||||
analyzeAgent({ text, contextResults: this.agent.contextResults }).then(res => {
|
||||
this.agent.plan = res.data
|
||||
this.agent.text = ''
|
||||
if (res.data && res.data.results && res.data.results.length) {
|
||||
this.agent.contextResults = res.data.results
|
||||
}
|
||||
}).finally(() => {
|
||||
this.agent.loading = false
|
||||
})
|
||||
},
|
||||
executeAgentPlan() {
|
||||
if (!this.agent.plan || !this.agent.plan.executable) {
|
||||
return
|
||||
}
|
||||
this.agent.executing = true
|
||||
executeAgent({ plan: this.agent.plan }).then(res => {
|
||||
this.agent.plan = res.data
|
||||
this.$modal.msgSuccess(res.data.message || '操作完成')
|
||||
this.loadDashboard()
|
||||
this.agent.text = ''
|
||||
}).finally(() => {
|
||||
this.agent.executing = false
|
||||
})
|
||||
},
|
||||
clearAgent() {
|
||||
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: '查询库存',
|
||||
create_in_record: '入库登记',
|
||||
confirm_in_record: '确认入库',
|
||||
create_out_record: '出库',
|
||||
create_stock_check: '盘库登记',
|
||||
create_product: '新增产品',
|
||||
create_area: '新增库区'
|
||||
}
|
||||
return map[action] || '未识别'
|
||||
},
|
||||
loadDashboard() {
|
||||
listStock({ pageNum: 1, pageSize: 8 }).then(res => {
|
||||
this.stockList = res.rows || []
|
||||
@ -140,6 +340,12 @@ export default {
|
||||
border: 1px solid #e6ebf2;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.hero-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.hero h1 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
@ -150,6 +356,76 @@ export default {
|
||||
margin: 6px 0 0;
|
||||
color: #7a8794;
|
||||
}
|
||||
.agent-panel {
|
||||
margin-bottom: 16px;
|
||||
padding: 18px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e6ebf2;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.agent-title {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.agent-title h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
line-height: 26px;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
.agent-title p {
|
||||
margin: 4px 0 0;
|
||||
color: #7a8794;
|
||||
}
|
||||
.agent-input {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 88px 96px;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.agent-input .el-button {
|
||||
height: 54px;
|
||||
}
|
||||
.agent-result {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.agent-plan {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #f7f9fc;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.agent-plan strong {
|
||||
margin-left: 8px;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
.agent-warnings {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.agent-candidates {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.agent-candidates span {
|
||||
padding: 6px 10px;
|
||||
color: #606266;
|
||||
background: #f4f6f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.agent-table {
|
||||
width: 100%;
|
||||
}
|
||||
.summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@ -222,6 +498,16 @@ export default {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
.agent-input {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.agent-input .el-button {
|
||||
height: 40px;
|
||||
}
|
||||
.agent-plan {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
.summary-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
415
ruoyi-ui/src/views/warehouse/batchImport/index.vue
Normal file
415
ruoyi-ui/src/views/warehouse/batchImport/index.vue
Normal file
@ -0,0 +1,415 @@
|
||||
<template>
|
||||
<div class="app-container batch-page">
|
||||
<el-card shadow="never" class="batch-card">
|
||||
<div slot="header" class="card-head">
|
||||
<div>
|
||||
<span class="title">智能批量导入</span>
|
||||
<span class="subtitle">支持 Excel 和图片,解析后先确认明细,再写入库存业务表。</span>
|
||||
</div>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="loadJobs">刷新任务</el-button>
|
||||
</div>
|
||||
|
||||
<el-upload
|
||||
class="upload-box"
|
||||
drag
|
||||
action="#"
|
||||
accept=".xls,.xlsx,.jpg,.jpeg,.png,.bmp"
|
||||
:show-file-list="false"
|
||||
:http-request="handleUpload"
|
||||
:disabled="uploading"
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">拖入 Excel 或图片,或 <em>点击上传</em></div>
|
||||
<div slot="tip" class="el-upload__tip">Excel 支持 xls/xlsx;图片支持 jpg/png/bmp。图片会调用大模型识别,耗时可能更久。</div>
|
||||
</el-upload>
|
||||
|
||||
<div v-if="currentJob" class="job-panel">
|
||||
<div class="job-main">
|
||||
<div>
|
||||
<div class="job-no">{{ currentJob.jobNo }}</div>
|
||||
<div class="job-meta">{{ currentJob.fileName }} | {{ statusLabel(currentJob.status) }}</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-check"
|
||||
:loading="confirming"
|
||||
:disabled="currentJob.status !== 2 || currentJob.failCount > 0 || !items.length"
|
||||
@click="handleConfirm"
|
||||
>确认执行</el-button>
|
||||
</div>
|
||||
<el-progress :percentage="currentJob.progress || 0" :status="progressStatus(currentJob)" />
|
||||
<div class="job-stats">
|
||||
<el-tag size="small">总数 {{ currentJob.totalCount || 0 }}</el-tag>
|
||||
<el-tag size="small" type="success">可执行 {{ currentJob.successCount || 0 }}</el-tag>
|
||||
<el-tag size="small" type="danger">异常 {{ currentJob.failCount || 0 }}</el-tag>
|
||||
<el-alert v-if="currentJob.errorMsg" :title="currentJob.errorMsg" type="warning" show-icon :closable="false" />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="16">
|
||||
<el-col :xs="24" :lg="7">
|
||||
<el-card shadow="never" class="batch-card">
|
||||
<div slot="header" class="card-head">
|
||||
<span class="title">最近任务</span>
|
||||
</div>
|
||||
<el-table v-loading="jobLoading" :data="jobList" size="mini" height="420" @row-click="selectJob">
|
||||
<el-table-column label="任务号" prop="jobNo" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="来源" width="72">
|
||||
<template slot-scope="scope">{{ sourceLabel(scope.row.sourceType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="86">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="statusType(scope.row.status)" size="mini">{{ statusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="进度" prop="progress" width="70" align="right" />
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :xs="24" :lg="17">
|
||||
<el-card shadow="never" class="batch-card">
|
||||
<div slot="header" class="card-head">
|
||||
<span class="title">待确认明细</span>
|
||||
<span class="subtitle">异常明细需要修改或删除后才能确认执行。</span>
|
||||
</div>
|
||||
<div class="batch-actions">
|
||||
<el-select v-model="batchActionType" size="small" placeholder="批量设置操作类型" clearable>
|
||||
<el-option label="新增库区" value="create_area" />
|
||||
<el-option label="新增产品" value="create_product" />
|
||||
<el-option label="入库登记" value="create_in_record" />
|
||||
<el-option label="出库" value="create_out_record" />
|
||||
<el-option label="盘库登记" value="create_stock_check" />
|
||||
</el-select>
|
||||
<el-button size="small" type="primary" plain :loading="batchSaving" :disabled="!canEditItems || !batchActionType || !items.length" @click="applyBatchAction(false)">应用到全部</el-button>
|
||||
<el-button size="small" type="primary" plain :loading="batchSaving" :disabled="!canEditItems || !batchActionType || !selectedItems.length" @click="applyBatchAction(true)">应用到已选 {{ selectedItems.length }}</el-button>
|
||||
</div>
|
||||
<el-table v-loading="itemLoading" :data="items" size="mini" height="520" @selection-change="handleItemSelectionChange">
|
||||
<el-table-column type="selection" width="45" />
|
||||
<el-table-column label="行" prop="rowNo" width="58" />
|
||||
<el-table-column label="操作" width="110">
|
||||
<template slot-scope="scope">{{ actionLabel(scope.row.actionType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" width="92">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="itemStatusType(scope.row.status)" size="mini">{{ itemStatusLabel(scope.row.status) }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库区" prop="areaName" width="110" show-overflow-tooltip />
|
||||
<el-table-column label="产品" min-width="190" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<div>{{ scope.row.productName || '-' }}</div>
|
||||
<div class="meta">{{ productMeta(scope.row) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数量" prop="quantity" width="90" align="right" />
|
||||
<el-table-column label="人员" prop="operatorName" width="100" show-overflow-tooltip />
|
||||
<el-table-column label="提示/错误" min-width="220" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span class="error-text" v-if="scope.row.errorMsg">{{ scope.row.errorMsg }}</span>
|
||||
<span v-else>{{ scope.row.warningMsg || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="mini" @click="openEdit(scope.row)">修改</el-button>
|
||||
<el-button type="text" size="mini" class="danger-link" @click="removeItem(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-dialog title="修改明细" :visible.sync="editOpen" width="620px" append-to-body>
|
||||
<el-form ref="editForm" :model="editForm" label-width="90px">
|
||||
<el-form-item label="操作类型">
|
||||
<el-select v-model="editForm.actionType" placeholder="请选择操作类型" style="width: 100%">
|
||||
<el-option label="新增库区" value="create_area" />
|
||||
<el-option label="新增产品" value="create_product" />
|
||||
<el-option label="入库登记" value="create_in_record" />
|
||||
<el-option label="出库" value="create_out_record" />
|
||||
<el-option label="盘库登记" value="create_stock_check" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="库区"><el-input v-model="editForm.areaName" /></el-form-item>
|
||||
<el-form-item label="产品名称"><el-input v-model="editForm.productName" /></el-form-item>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12"><el-form-item label="品牌"><el-input v-model="editForm.brand" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="类别"><el-input v-model="editForm.category" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12"><el-form-item label="规格"><el-input v-model="editForm.spec" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="单位"><el-input v-model="editForm.unit" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12"><el-form-item label="数量"><el-input-number v-model="editForm.quantity" :precision="3" :min="0" style="width: 100%" /></el-form-item></el-col>
|
||||
<el-col :span="12"><el-form-item label="类型编码"><el-input-number v-model="editForm.typeCode" :min="1" :max="4" style="width: 100%" /></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="操作人"><el-input v-model="editForm.operatorName" /></el-form-item>
|
||||
<el-form-item label="备注"><el-input v-model="editForm.remark" type="textarea" /></el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="savingItem" @click="saveItem">保 存</el-button>
|
||||
<el-button @click="editOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listBatchJob, uploadBatchFile, getBatchJob, listBatchItems, updateBatchItem, updateBatchItemAction, deleteBatchItem, confirmBatchJob } from '@/api/warehouse/batchImport'
|
||||
|
||||
export default {
|
||||
name: 'WmsBatchImport',
|
||||
data() {
|
||||
return {
|
||||
uploading: false,
|
||||
confirming: false,
|
||||
jobLoading: false,
|
||||
itemLoading: false,
|
||||
savingItem: false,
|
||||
batchSaving: false,
|
||||
batchActionType: undefined,
|
||||
jobList: [],
|
||||
currentJob: null,
|
||||
items: [],
|
||||
selectedItems: [],
|
||||
pollTimer: null,
|
||||
editOpen: false,
|
||||
editForm: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadJobs()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopPolling()
|
||||
},
|
||||
methods: {
|
||||
handleUpload(option) {
|
||||
this.uploading = true
|
||||
uploadBatchFile(option.file).then(res => {
|
||||
this.currentJob = res.data
|
||||
this.items = []
|
||||
this.$modal.msgSuccess('上传成功,正在后台解析,请留意进度')
|
||||
this.startPolling(res.data.id)
|
||||
this.loadJobs()
|
||||
}).finally(() => {
|
||||
this.uploading = false
|
||||
})
|
||||
},
|
||||
loadJobs() {
|
||||
this.jobLoading = true
|
||||
listBatchJob({ pageNum: 1, pageSize: 20 }).then(res => {
|
||||
this.jobList = res.rows || []
|
||||
}).finally(() => {
|
||||
this.jobLoading = false
|
||||
})
|
||||
},
|
||||
selectJob(row) {
|
||||
this.currentJob = row
|
||||
this.loadItems(row.id)
|
||||
if ([0, 1, 3].includes(row.status)) {
|
||||
this.startPolling(row.id)
|
||||
}
|
||||
},
|
||||
startPolling(jobId) {
|
||||
this.stopPolling()
|
||||
this.pollTimer = setInterval(() => {
|
||||
this.refreshJob(jobId)
|
||||
}, 1500)
|
||||
this.refreshJob(jobId)
|
||||
},
|
||||
stopPolling() {
|
||||
if (this.pollTimer) {
|
||||
clearInterval(this.pollTimer)
|
||||
this.pollTimer = null
|
||||
}
|
||||
},
|
||||
refreshJob(jobId) {
|
||||
getBatchJob(jobId).then(res => {
|
||||
this.currentJob = res.data
|
||||
if (this.currentJob && [2, 4, 5, 6].includes(this.currentJob.status)) {
|
||||
this.stopPolling()
|
||||
this.loadItems(jobId)
|
||||
this.loadJobs()
|
||||
}
|
||||
})
|
||||
},
|
||||
loadItems(jobId) {
|
||||
this.itemLoading = true
|
||||
listBatchItems(jobId).then(res => {
|
||||
this.items = res.data || []
|
||||
this.selectedItems = []
|
||||
}).finally(() => {
|
||||
this.itemLoading = false
|
||||
})
|
||||
},
|
||||
handleItemSelectionChange(selection) {
|
||||
this.selectedItems = selection
|
||||
},
|
||||
openEdit(row) {
|
||||
this.editForm = Object.assign({}, row)
|
||||
this.editOpen = true
|
||||
},
|
||||
saveItem() {
|
||||
this.savingItem = true
|
||||
updateBatchItem(this.editForm).then(res => {
|
||||
this.$modal.msgSuccess('明细已更新')
|
||||
this.editOpen = false
|
||||
this.loadItems(this.currentJob.id)
|
||||
this.refreshJob(this.currentJob.id)
|
||||
}).finally(() => {
|
||||
this.savingItem = false
|
||||
})
|
||||
},
|
||||
removeItem(row) {
|
||||
this.$modal.confirm('确认删除第 ' + row.rowNo + ' 行明细?').then(() => deleteBatchItem(row.id)).then(() => {
|
||||
this.$modal.msgSuccess('删除成功')
|
||||
this.loadItems(this.currentJob.id)
|
||||
this.refreshJob(this.currentJob.id)
|
||||
})
|
||||
},
|
||||
applyBatchAction(onlySelected) {
|
||||
const rows = onlySelected ? this.selectedItems : this.items
|
||||
if (!rows.length || !this.batchActionType) {
|
||||
return
|
||||
}
|
||||
const label = this.actionLabel(this.batchActionType)
|
||||
this.$modal.confirm('确认将' + (onlySelected ? '已选' : '全部') + '明细的操作类型设置为“' + label + '”?').then(() => {
|
||||
this.batchSaving = true
|
||||
const itemIds = onlySelected ? rows.map(item => item.id) : undefined
|
||||
return updateBatchItemAction(this.currentJob.id, { actionType: this.batchActionType, itemIds })
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess('批量设置完成')
|
||||
this.loadItems(this.currentJob.id)
|
||||
this.refreshJob(this.currentJob.id)
|
||||
}).finally(() => {
|
||||
this.batchSaving = false
|
||||
})
|
||||
},
|
||||
handleConfirm() {
|
||||
this.$modal.confirm('确认执行当前批量任务?执行后会真正写入库存业务数据。').then(() => {
|
||||
this.confirming = true
|
||||
return confirmBatchJob(this.currentJob.id)
|
||||
}).then(res => {
|
||||
this.currentJob = res.data
|
||||
this.$modal.msgSuccess('执行完成')
|
||||
this.loadItems(this.currentJob.id)
|
||||
this.loadJobs()
|
||||
}).finally(() => {
|
||||
this.confirming = false
|
||||
})
|
||||
},
|
||||
statusLabel(status) {
|
||||
return { 0: '已上传', 1: '解析中', 2: '待确认', 3: '执行中', 4: '完成', 5: '失败', 6: '已取消' }[status] || '-'
|
||||
},
|
||||
statusType(status) {
|
||||
return { 2: 'warning', 4: 'success', 5: 'danger', 6: 'info' }[status] || 'info'
|
||||
},
|
||||
sourceLabel(sourceType) {
|
||||
return { excel: 'Excel', image: '图片', file: '文件' }[sourceType] || sourceType || '-'
|
||||
},
|
||||
itemStatusLabel(status) {
|
||||
return { 1: '可执行', 2: '异常', 3: '已执行', 4: '失败' }[status] || '-'
|
||||
},
|
||||
itemStatusType(status) {
|
||||
return { 1: 'success', 2: 'danger', 3: 'success', 4: 'danger' }[status] || 'info'
|
||||
},
|
||||
progressStatus(job) {
|
||||
if (!job) return undefined
|
||||
if (job.status === 4) return 'success'
|
||||
if (job.status === 5) return 'exception'
|
||||
return undefined
|
||||
},
|
||||
actionLabel(action) {
|
||||
return {
|
||||
create_area: '新增库区',
|
||||
create_product: '新增产品',
|
||||
create_in_record: '入库登记',
|
||||
create_out_record: '出库',
|
||||
create_stock_check: '盘库登记',
|
||||
unknown: '未识别'
|
||||
}[action] || action || '-'
|
||||
},
|
||||
productMeta(row) {
|
||||
return [row.brand, row.category, row.spec, row.unit].filter(Boolean).join(' / ')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canEditItems() {
|
||||
return this.currentJob && ![3, 4].includes(this.currentJob.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.batch-page {
|
||||
background: #f5f7fb;
|
||||
}
|
||||
.batch-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
.title {
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
.subtitle {
|
||||
margin-left: 10px;
|
||||
color: #7a8794;
|
||||
font-size: 12px;
|
||||
}
|
||||
.upload-box {
|
||||
max-width: 620px;
|
||||
}
|
||||
.job-panel {
|
||||
margin-top: 16px;
|
||||
padding: 14px;
|
||||
border: 1px solid #e6ebf2;
|
||||
border-radius: 6px;
|
||||
background: #fbfcfe;
|
||||
}
|
||||
.job-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.job-no {
|
||||
font-weight: 700;
|
||||
color: #1f2d3d;
|
||||
}
|
||||
.job-meta,
|
||||
.meta {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
.job-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.batch-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.error-text,
|
||||
.danger-link {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
@ -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() },
|
||||
|
||||
@ -6,8 +6,8 @@
|
||||
<el-option v-for="item in areaOptions" :key="item.id" :label="areaLabel(item)" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="产品信息" prop="productName">
|
||||
<el-input v-model="queryParams.productName" placeholder="名称/品牌/类别/规格/单位" clearable @keyup.enter.native="handleQuery" />
|
||||
<el-form-item label="关键词" prop="productName">
|
||||
<el-input v-model="queryParams.productName" placeholder="名称/规格/品牌/类别/单位/库区,如:电机 20" clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
|
||||
49
sql/wms_batch_import.sql
Normal file
49
sql/wms_batch_import.sql
Normal file
@ -0,0 +1,49 @@
|
||||
-- 批量导入任务表。执行一次即可。
|
||||
create table if not exists wms_batch_job (
|
||||
id bigint not null auto_increment comment '主键',
|
||||
job_no varchar(64) not null comment '任务号',
|
||||
source_type varchar(20) not null comment '来源类型 excel/image/file',
|
||||
biz_type varchar(30) default 'mixed' comment '业务类型',
|
||||
file_name varchar(255) default null comment '原始文件名',
|
||||
file_path varchar(500) default null comment '服务器文件路径',
|
||||
status tinyint not null default 0 comment '状态 0上传完成 1解析中 2待确认 3执行中 4完成 5失败 6取消',
|
||||
progress int not null default 0 comment '进度',
|
||||
total_count int not null default 0 comment '总条数',
|
||||
success_count int not null default 0 comment '成功条数',
|
||||
fail_count int not null default 0 comment '失败条数',
|
||||
error_msg varchar(1000) default null comment '错误信息',
|
||||
create_by varchar(64) default null comment '创建人',
|
||||
create_time datetime default null comment '创建时间',
|
||||
update_time datetime default null comment '更新时间',
|
||||
primary key (id),
|
||||
unique key uk_wms_batch_job_no (job_no)
|
||||
) engine=innodb default charset=utf8mb4 comment='WMS批量导入任务';
|
||||
|
||||
create table if not exists wms_batch_item (
|
||||
id bigint not null auto_increment comment '主键',
|
||||
job_id bigint not null comment '任务ID',
|
||||
row_no int default null comment 'Excel行号',
|
||||
action_type varchar(40) not null comment '操作类型',
|
||||
raw_text text comment '原始内容',
|
||||
parsed_json text comment '解析JSON',
|
||||
status tinyint not null default 1 comment '状态 1校验通过 2校验失败 3已执行 4执行失败',
|
||||
error_msg varchar(1000) default null comment '错误信息',
|
||||
warning_msg varchar(1000) default null comment '提示信息',
|
||||
ref_id bigint default null comment '执行后业务ID',
|
||||
area_name varchar(100) default null comment '库区名称',
|
||||
product_name varchar(200) default null comment '产品名称',
|
||||
brand varchar(100) default null comment '品牌',
|
||||
category varchar(100) default null comment '类别',
|
||||
spec varchar(200) default null comment '规格',
|
||||
unit varchar(50) default null comment '单位',
|
||||
quantity decimal(16,3) default null comment '数量',
|
||||
operator_name varchar(100) default null comment '操作人',
|
||||
type_code tinyint default null comment '入库/出库类型',
|
||||
source_no varchar(100) default null comment '来源单据号',
|
||||
remark varchar(500) default null comment '备注',
|
||||
create_time datetime default null comment '创建时间',
|
||||
update_time datetime default null comment '更新时间',
|
||||
primary key (id),
|
||||
key idx_wms_batch_item_job (job_id),
|
||||
key idx_wms_batch_item_status (status)
|
||||
) engine=innodb default charset=utf8mb4 comment='WMS批量导入明细';
|
||||
Loading…
Reference in New Issue
Block a user