Commit aea41fc3 LN

NLM料架扫码入库功能。合并库位可配置。

1 个父辈 8d8ec34d
......@@ -217,8 +217,8 @@ public class DataInitManager {
materialBox.setHidden(true);
outSet.setHidden(true);
posOut.setHidden(true);
// zhuanruMenu.setHidden(true);
// singleMenu.setHidden(true);
zhuanruMenu.setHidden(true);
singleMenu.setHidden(true);
// orderSet.setHidden(true);
menus.addAll(createMenus(poutOut, menuOrder, out,posOut,groupOut,materialBox,outSet,inOrderMenu,putinMenu,zhuanruMenu,singleMenu));
......@@ -257,6 +257,7 @@ public class DataInitManager {
Menu menuLog = new Menu(new ArrayList<Menu>(), 1, "taskLog", "物料日志", 1, "taskLog", "neolight/taskLog/index", "", 0, "education");
Menu msgLog = new Menu(new ArrayList<Menu>(), 1, "message", "消息查询", 1, "message", "neolight/message/index", "", 0, "messagefind");
Menu exceptionLog = new Menu(new ArrayList<Menu>(), 1, "interfaceException", "接口异常", 1, "interfaceException", "neolight/interfaceException/index", "", 0, "messagefind");
exceptionLog.setHidden(true);
menus.addAll(createMenus(pMenuLog, menuLog,msgLog,exceptionLog));
//报表:出入库、库存
......
package com.neotel.smfcore.core.device.handler.impl;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.neotel.smfcore.common.bean.ResultBean;
import com.neotel.smfcore.common.exception.ValidateException;
import com.neotel.smfcore.core.barcode.bean.CodeBean;
import com.neotel.smfcore.core.barcode.service.po.Barcode;
import com.neotel.smfcore.core.device.api.IOpAuthApi;
import com.neotel.smfcore.core.device.bean.StatusBean;
import com.neotel.smfcore.core.inList.util.InListCache;
import com.neotel.smfcore.core.order.service.manager.ILiteOrderManager;
import com.neotel.smfcore.core.storage.enums.DeviceType;
import com.neotel.smfcore.core.storage.service.po.Storage;
import com.neotel.smfcore.core.storage.service.po.StoragePos;
import com.neotel.smfcore.core.system.util.DevicesStatusUtil;
import com.neotel.smfcore.security.TokenProvider;
import com.neotel.smfcore.security.annotation.AnonymousAccess;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@Api(tags = "SHELF: NLM(移动料架/自动推荐库位/合并库位)")
@RestController
@Slf4j
public class NLMShelfHandler extends BaseDeviceHandler {
public NLMShelfHandler(List<IOpAuthApi> apiList) {
super(apiList);
}
@Autowired
private TokenProvider tokenProvider;
@Autowired
private ILiteOrderManager liteOrderManager;
@Autowired
private InListCache inListCache;
@Override
public StatusBean handleClientRequest(StatusBean statusBean, HttpServletRequest request) {
statusBean.setClientIp(request.getRemoteHost());
handleMsg(statusBean);
statusBean = saveAlarmAndHumidity(statusBean);
if(statusBean != null){
Map<String, String> opMap = DevicesStatusUtil.getAndRemoveOp(statusBean.getCid());
statusBean.addOp(opMap);
}
return statusBean;
}
/**
* 操作库位灯(开灯,或关灯),如果有关联的合并库位,一起开关灯
* @param opKey
* @param storage
* @param pos
* @param colorStr
*/
private void opPosLight(String opKey, Storage storage, StoragePos pos, String colorStr){
List<String> relatedPosNames = pos.getMergePosList();
if(relatedPosNames == null || relatedPosNames.isEmpty()){
relatedPosNames = new ArrayList<>();
relatedPosNames.add(pos.getPosName());
log.info("操作库位["+pos.getPosName()+"]" + opKey);
}else{
log.info("操作合并库位["+pos.getPosName()+"]" + opKey);
}
for(String posName : relatedPosNames){
String opStr = posName;
if(!Strings.isNullOrEmpty(colorStr)){
opStr =opStr+ "=" + colorStr;
}
DevicesStatusUtil.appendOp(storage.getCid(), opKey , opStr);
log.info(opKey + " : " + opStr);
}
}
/**
* 开灯, 等6秒后关闭
*/
private void openAndCloseLights(final Storage storage, final List<String> posNameList, final long delayCloseTime, String color){
final String cid = storage.getCid();
if(posNameList == null ){
return;
}
for (final String posName : posNameList) {
String lightOnStr = posName + "=" + color;
DevicesStatusUtil.appendOp(cid,"open", lightOnStr);
log.info("点亮库位:" + lightOnStr);
//5秒后灭灯
Thread closeTask = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(delayCloseTime);
log.info(storage.getName()+"["+cid+"]库位["+posName+"]灭灯");
DevicesStatusUtil.appendOp(cid,"close", posName);
}catch (Exception e){
}
}
});
closeTask.start();
}
}
//上一次入库的库位,用于扫下一条码时灭灯,key=storageId
private static Map<String,StoragePos> lastPutinPosMap = new ConcurrentHashMap<>();
/**
* 查找下一个空位,并且点亮灯
*/
@RequestMapping("/api/nlmShelf/putInCode")
@ResponseBody
public ResultBean putInCode(@RequestBody Map<String, String> mapValues,HttpServletRequest request) {
String code = mapValues.get("code").trim();
String groupId = mapValues.get("group");
String storageId = mapValues.get("storageId");
if (ObjectUtils.isEmpty(code)) {
throw new ValidateException("smfcore.valueCanotNull", "{0}不能为空", new String[]{"code"});
}
if (groupId == null && storageId == null) {
throw new ValidateException("smfcore.valueCanotNull", "{0}不能为空", new String[]{"group"});
}
Storage currentStorage = null;
if (groupId == null && storageId != null) {
currentStorage = dataCache.getStorageById(storageId);
if (currentStorage == null || !currentStorage.isNLMShelf()) {
return ResultBean.newErrorResult(1, "smfcore.shelf.nlm.notFound", "未找到移动料架{0}",new String[]{storageId});
}
StoragePos lastPutinPos = lastPutinPosMap.get(currentStorage.getId());
if (lastPutinPos != null) {
log.info("扫码时关闭上一个库位:" + lastPutinPos.getPosName());
opPosLight("close", currentStorage, lastPutinPos, "");
}
groupId = currentStorage.getGroupId();
}
if (groupId != null && groupId.equals("-1")) {
groupId = "";
}
if (!Strings.isNullOrEmpty(code)) {
code = code.replace("。", ".");
}
CodeBean codeBean = codeResolve.resolveSingleCode(code);
if (!codeBean.isValid()) {
throw new ValidateException("smfcore.error.barcode.invalid", "{0}不是有效的条码",new String[]{code});
}
Barcode barcode = codeBean.getBarcode();
Date expireDate = barcode.getExpireDate();
if (expireDate != null && System.currentTimeMillis() > expireDate.getTime()) {
throw new ValidateException("smfcore.error.barcode.expired", "物料已过期,无法入库.");
}
if (barcode.getPlateSize() <= 1) {
//长宽为1的需要弹框设置尺寸
throw new ValidateException("smfcore.error.barcode.errorSize", "条码未设置尺寸");
}
try {
StoragePos inPos = storagePosManager.getByBarcodeId(barcode.getId());
if (inPos != null) {
String posName = inPos.getPosName();
List<String> mergePosList = inPos.getMergePosList();
if (mergePosList != null && !mergePosList.isEmpty()) {
posName = "";
for (String posStr : mergePosList) {
posName = posName + "," + posStr;
}
posName = posName.substring(1);
}
return ResultBean.newErrorResult(99, "smfcore.shelf.msg.alreadyInPos", "该物料已在库位[" + posName + "]中", new String[]{posName});
}
List<Storage> storages=new ArrayList<>();
//料仓是否为空
StoragePos pos =null;
if(currentStorage!=null) {
storages.add(currentStorage);
pos = storagePosManager.getEmptyPosByStorage(currentStorage, barcode, taskService.excludePosIds());
}else {
List<String> cids = dataCache.getCidsByGroupId(groupId, true);
for (String cid :
cids) {
Storage storage = dataCache.getStorage(cid);
if (storage.isNLMShelf()) {
storages.add(storage);
pos = storagePosManager.getEmptyPosByStorage(storage, barcode, taskService.excludePosIds());
if (pos != null) {
currentStorage=storage;
break;
}
}
}
}
int delayCloseTime = 8000;
String color = "red";
if (pos != null) {
log.info(barcode.getPartNumber() + " [ " + barcode.getBarcode() + " ] " + "入库到:" + currentStorage.getName() + "[" + currentStorage.getCid() + "] " + pos.getPosName());
taskService.addTaskToFinished(pos, barcode, currentStorage.getName() + "-op");
openAndCloseLights(currentStorage, Lists.<String>newArrayList(pos.getPosName()), delayCloseTime, color);
lastPutinPosMap.put(currentStorage.getCid(), pos);
pos.setCanCheckOutTime(System.currentTimeMillis() + delayCloseTime);
log.info( ":条码[" + code + "]入库操作成功,请放入库位 [" + pos.getPosName() + "]" );
return ResultBean.newOkResult("smfcore.shelf.msg.inOk", "操作成功,请放入库位[" + pos.getPosName() + "]", new String[]{pos.getPosName()} );
} else {
//库位没找到,查找同尺寸空的库位进行合并
List<StoragePos> continuitEmptyPosList =null;
for (Storage storage :
storages) {
continuitEmptyPosList = storagePosManager.getSameSizeContinuityEmptyPosList(storage, barcode);
if (continuitEmptyPosList != null && (continuitEmptyPosList.size() > 0)) {
currentStorage = storage;
break;
}
}
if (continuitEmptyPosList!=null && (continuitEmptyPosList.size()>0) ) {
//将第一个库位作为主库位,其他库位合并入此库位中
StoragePos mainPos = continuitEmptyPosList.get(0);
List<String> mergePosNameList = new ArrayList<>();
String mergePosStr = "";
for (StoragePos storagePos : continuitEmptyPosList) {
log.info("设置库位[" + storagePos.getPosName() + "]使用状态为:true");
storagePos.setUsed(true);
storagePosManager.save(storagePos);
mergePosNameList.add(storagePos.getPosName());
mergePosStr = mergePosStr + " , " + storagePos.getPosName();
}
mainPos.setUsed(true);
mainPos.setMergePosList(mergePosNameList);
storagePosManager.save(mainPos);
pos = mainPos;
lastPutinPosMap.put(currentStorage.getCid(), pos);
log.info("合并库位[" + mergePosStr + "]到" + pos.getPosName() + "中");
log.info(barcode.getPartNumber() + " [ " + barcode.getBarcode() + " ] " + "入库到:" + currentStorage.getName() + "[" + currentStorage.getCid() + "] " + pos.getPosName());
taskService.addTaskToFinished(pos, barcode, currentStorage.getName() + "-op");
openAndCloseLights(currentStorage, mergePosNameList, delayCloseTime, color);
return ResultBean.newOkResult("smfcore.shelf.msg.inMergeOk", "操作成功,请合并库位[{0}]并放入料盘", new String[]{pos.getPosName()},"");
} else {
String sizeInfo = barcode.getPlateSize() + " x " + barcode.getHeight();
String msg = "未找到适合[" + sizeInfo + "]的库位";
log.info(msg);
return ResultBean.newErrorResult(99,"smfcore.shelf.msg.inError","未找到适合[" + sizeInfo + "]的库位", new String[]{sizeInfo} );
}
}
} catch (ValidateException e) {
log.error("入库出错:" + e.getMessage());
return ResultBean.newErrorResult(1, e.getMessage(), e.getMessage(), e.getMsgParam());
}
}
/**
* 获取料架的库位占用情况
*/
@RequestMapping("/service/store/nlmShelf/hasReelPosList")
@ResponseBody
@AnonymousAccess
public ResultBean checkAll(HttpServletRequest request){
String cid = request.getParameter("cid");
Storage storage = dataCache.getStorage(cid);
List<StoragePos> allPos = storagePosManager.findNotEmptyByStorageId(storage.getId());
List<String> posList = new ArrayList<>();
for (StoragePos pos : allPos) {
posList.add(pos.getPosName());
}
return ResultBean.newOkResult(posList);
}
@Override
public DeviceType getDeviceType() {
return DeviceType.NLM;
}
}
......@@ -60,4 +60,8 @@ public interface IStoragePosManager extends IBaseManager<StoragePos> {
public void updateBarcodeMsd(String pn,String msl,String thickness);
void download(List<StoragePos> storagePos, HttpServletResponse response, Locale locale) throws IOException;
List<StoragePos> findPosList(String storageId, List<String> posNames);
List<StoragePos> getSameSizeContinuityEmptyPosList(Storage storage, Barcode barcode) throws ValidateException;
}
......@@ -2,6 +2,7 @@ package com.neotel.smfcore.core.storage.service.manager.impl;
import cn.hutool.core.util.ObjectUtil;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.neotel.smfcore.common.bean.PageData;
import com.neotel.smfcore.common.exception.ValidateException;
......@@ -37,6 +38,7 @@ import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Service
@Slf4j
......@@ -179,8 +181,34 @@ public class StoragePosManagerImpl implements IStoragePosManager {
}
@Override
public StoragePos save(StoragePos object) throws ValidateException {
return storagePosDao.save(object);
public List<StoragePos> findPosList(String storageId, List<String> posNames){
if(posNames == null || posNames.isEmpty()){
return Lists.newArrayList();
}
Criteria c = Criteria.where("storageId").is(storageId)
.and("posName").in(posNames);
Query q = new Query(c);
return storagePosDao.findByQuery(q);
}
@Override
public StoragePos save(StoragePos storagePos) throws ValidateException {
if(!storagePos.isUsed()){
//出库
List<String> mergePosNames = storagePos.getMergePosList();
List<StoragePos> mergePosList = findPosList(storagePos.getStorageId(), mergePosNames);
//解除相关合并库位的占用状态
for(StoragePos mergePos : mergePosList){
mergePos.setUsed(false);
mergePos.setMergePosList(null);
storagePosDao.save(mergePos);
log.info("设置合并的关联库位["+mergePos.getPosName()+"]使用状态为:false");
}
storagePos.setMergePosList(null);
}
storagePos = storagePosDao.save(storagePos);
//reSortStoragePosList(storage.getId());
log.debug("Storage " + storagePos.getId() + " was added new slot with id: " + storagePos.getId());
return storagePos;
}
@Override
......@@ -499,5 +527,84 @@ public class StoragePosManagerImpl implements IStoragePosManager {
}
FileUtil.downloadExcel(list, response);
}
/**
* 查找同尺寸连续的库位,合并库位放置料盘,仅用于智能料架
* @param storage
* @param barcode
* @return
*/
@Override
public List<StoragePos> getSameSizeContinuityEmptyPosList(Storage storage, Barcode barcode) throws ValidateException {
if(storage == null||(!storage.isNLMShelf())){
//只适合移动料架NLM
throw new ValidateException( "smfcore.shelf.nlm.notFound", "未找到移动料架{0}",new String[]{storage.getId()});
}
if(!storage.isMergePos()){
return new ArrayList<>();
}
Criteria c = Criteria.where("storageId").is(storage.getId());
if(barcode.getPlateSize() > 7){
c = c.and("w").gte(barcode.getPlateSize());
}else{
c = c.and("w").is(barcode.getPlateSize());
}
c = c.and("h").gte(0);//宽度大于等于料盘宽度,高度大于等于料盘高度
c = c.and("enabled").is(true)//可用
.and("used").is(false);//未使用
Query query = new Query(c);
//优先放入最合适的位置(根据尺寸),相同尺寸按优先级排序
query.with(Sort.by(Sort.Direction.ASC, "posName"));
List<StoragePos> posList = storagePosDao.findByQuery(query);
//把不连续的过滤掉
Map<String,StoragePos> posMap = new ConcurrentHashMap<>();
for(StoragePos pos : posList){
posMap.put(pos.getPosName(),pos);
}
Set<String> posNames = posMap.keySet();
for (String posName : posNames) {
StoragePos currentPos = posMap.get(posName);
int totalHeight = currentPos.getH();
List<StoragePos> emptyPosList = new ArrayList<>();
emptyPosList.add(currentPos);
String nextPosName = posName;
while (true){
if(totalHeight >= barcode.getHeight()){
return emptyPosList;
}
nextPosName = getNextPosName(nextPosName);
StoragePos nextPos = posMap.get(nextPosName);
if(nextPos == null){
break;
}
emptyPosList.add(nextPos);
totalHeight = totalHeight + currentPos.getH();
}
}
return new ArrayList<>();
}
/**
* 获取下一库位的库位名(后缀数字+1)
*/
private String getNextPosName(String posName){
String posIndexStr = "";
String prefixStr = "";
for(int i=1;i<=posName.length();i++){
char c = posName.charAt(posName.length() - i);
if(!Character.isDigit(c)){
prefixStr = posName.substring(0, posName.length() - i + 1);
break;
}
posIndexStr = c + posIndexStr;
}
if(posIndexStr.isEmpty()){
return "";
}
int posIndex = Integer.valueOf(posIndexStr);
int nextPosIndex = posIndex + 1;
return prefixStr +String.format("%0"+posIndexStr.length()+"d",nextPosIndex);
}
}
......@@ -17,3 +17,13 @@
2.库位页面调整。
3.登录页面增加语言切换。
4.共享文件夹页面调整。
20220304:
个人中心增加进入调试模式功能。
20220307:
启用禁用库位增加操作日志。
20220314:
增加移动料架:NLM,入库时自动推荐库位,无合适库位时自动合并库位。
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!