heshaofeng
7 天以前 ca3e4977395bc02c5d147dffdff7381333fdfbca
空箱跨区域移库
已添加1个文件
已修改9个文件
440 ■■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/config/buttons.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/stock/extend/CrossAreaRelocationDialog.vue 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/stock/stockView.js 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/inbound/inboundOrder.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Common/TaskEnum/TaskTypeEnum.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/InboundService.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/config/buttons.js
@@ -363,6 +363,15 @@
    type: 'danger',
    onClick: function () {
    }
},
,{
    name: "库存跨区域移库",
    icon: '',
    class: '',
    value: 'SelectStockAreaIn',
    type: 'danger',
    onClick: function () {
    }
}
]
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/stock/extend/CrossAreaRelocationDialog.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
<template>
  <vol-box v-model="show" title="跨区域移库" :width="800" :height="1200">
    <template #content>
      <el-form ref="form" :model="form" label-width="100px">
        <el-form-item label="目标货位类型:">
          <el-select v-model="targetLocationType" placeholder="请选择目标货位类型">
            <el-option
              v-for="item in locationTypes"
              :key="item.locationType"
              :label="item.locationTypeDesc.toString()"
              :value="item.locationType">
            </el-option>
          </el-select>
        </el-form-item>
      </el-form>
    </template>
    <template #footer>
      <div>
        <el-button
          type="danger"
          size="small"
          plain
          @click="submit"
          :loading="isSubmitting"
          :disabled="isSubmitting">
          <i class="el-icon-check">确认移库</i>
        </el-button>
        <el-button
          size="small"
          type="primary"
          plain
          @click="() => { this.show = false }">
          <i class="el-icon-close">关闭</i>
        </el-button>
      </div>
    </template>
  </vol-box>
</template>
<script>
import VolBox from '@/components/basic/VolBox.vue'
export default {
  components: {
    'vol-box': VolBox
  },
  data() {
    return {
      show: false,
      locationTypes: [],
      targetLocationType: "",
      isSubmitting: false,
      form:{}
    }
  },
  methods: {
    open() {
      this.show = true
      this.getData();
    },
    submit() {
      // 1. éªŒè¯å¿…填项
      if (!this.targetLocationType) {
        this.$message.warning('请选择目标货位类型');
        return;
      }
      // 2. ç¦ç”¨æŒ‰é’®
      this.isSubmitting = true;
      this.$emit('parentCall', ($vue) => {
        // é€‰ä¸­çš„表格数据
        let stockViews = $vue.$refs.table.getSelected();
        this.http.post(
          `/api/Task/CrossAreaOutbound?targetLocationType=${this.targetLocationType}`,
          stockViews,
          '跨区域移库处理中...'
        )
        .then((x) => {
          if (!x.status) {
            this.$message.error(x.message)
          } else {
            this.show = false
            this.$message.success(x.message || '跨区域移库任务生成成功')
            $vue.refresh();
          }
        })
        .catch((error) => {
          this.$message.error('请求失败,请稍后重试');
          console.error('跨区域移库失败:', error);
        })
        .finally(() => {
          this.isSubmitting = false;
        });
      })
    },
    getData() {
      this.http.post("api/LocationInfo/GetLocationTypes", null, "加载货位类型中")
        .then((x) => {
          this.locationTypes = x.data;
        })
    },
  }
}
</script>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/stock/stockView.js
@@ -1,9 +1,10 @@
import { createVNode, render, h, reactive } from 'vue';
import { ElDialog, ElForm, ElFormItem, ElSelect, ElOption, ElButton, ElMessage, ElLoading } from 'element-plus';
import gridHeader from './extend/CrossAreaRelocationDialog.vue'
let extension = {
  components: {
    //查询界面扩展组件
    gridHeader: '',
    gridHeader: gridHeader,
    gridBody: '',
    gridFooter: '',
    //新建、编辑弹出框扩展组件
@@ -335,6 +336,16 @@
            });
        }
      }
      var SelectStockAreaIn = this.buttons.find(x => x.value == "SelectStockAreaIn");
      if (SelectStockAreaIn != null) {
        SelectStockAreaIn.onClick = () => {
          let rows = this.$refs.table.getSelected();
          if (rows.length == 0) {
              return this.$message.error("请先选择需要移库的数据!");
            }
          this.$refs.gridHeader.open();
        }
      }
    },
    onInited() {
      //框架初始化配置后
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/inbound/inboundOrder.vue
@@ -274,7 +274,6 @@
          width: 150,
          align: "left",
          edit: { type: "" },
          required: true,
        },
        {
          field: "batchNo",
@@ -300,6 +299,7 @@
          type: "decimal",
          width: 90,
          align: "left",
          edit: { type: "" },
          required: true
        },
        {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/TaskEnum/TaskTypeEnum.cs
@@ -153,7 +153,13 @@
        /// åŒåŒºåŸŸç§»åº“
        /// </summary>
        [Description("同区域移库")]
        AreaRelocation = 910
        AreaRelocation = 910,
        /// <summary>
        /// è·¨åŒºåŸŸç§»åº“
        /// </summary>
        [Description("跨区域移库")]
        CrossAreaRelocation = 920
    }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs
@@ -81,5 +81,7 @@
        Task<WebResponseContent> TaskCancel(List<int> taskCodes);
        Task<WebResponseContent> AreaOutbound(List<StockViewDTO> stockViews);
        Task<WebResponseContent> CrossAreaOutbound(List<StockViewDTO> stockViews, int targetLocationType);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundService.cs
@@ -80,7 +80,8 @@
            _allocateOrderRepository = allocateOrderRepository;
        }
        public async Task<WebResponseContent> GroupPallet(GroupPalletDto palletDto)
        public async Task<WebResponseContent>
            GroupPallet(GroupPalletDto palletDto)
        {
            WebResponseContent content = new WebResponseContent();
            try
@@ -180,6 +181,20 @@
                    stockInfo = new Dt_StockInfo() { PalletType = (int)PalletTypeEnum.None, LocationType = Convert.ToInt32(palletDto.locationType) };
                    stockInfo.Details = new List<Dt_StockInfoDetail>();
                }
                //else
                //{
                //      var allowStatus = new[]
                //  {
                //      (int)StockStatusEmun.组盘暂存,
                //      (int)StockStatusEmun.智仓入智仓组盘暂存,
                //      (int)StockStatusEmun.手动组盘暂存
                //  };
                //    if (!allowStatus.Contains(stockInfo.StockStatus))
                //    {
                //        return content.Error($"该托盘{stockInfo.PalletCode}状态不允许组盘");
                //    }
                //}
                if (inboundOrder.BusinessType != MESDocumentType.PurchaseInbound.ToString() && stockInfo != null && stockInfo.Details.Count > 0 && stockInfo.Details.FirstOrDefault()?.WarehouseCode != palletDto.WarehouseType)
                {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -311,7 +311,7 @@
            stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
            stockInfo.Details.ForEach(x =>
            {
                x.Status = StockStatusEmun.入库确认.ObjToInt();
                x.Status = inboundOrders.FirstOrDefault().CreateType == (int)OrderCreateTypeEnum.CreateInSystem ? StockStatusEmun.入库完成.ObjToInt():StockStatusEmun.入库确认.ObjToInt();
            });
            _stockService.StockInfoService.Repository.UpdateData(stockInfo);
            _stockService.StockInfoDetailService.Repository.UpdateData(stockInfo.Details);
@@ -2711,6 +2711,138 @@
                return await Task.FromResult(WebResponseContent.Instance.Error($"移库任务处理失败:{ex.Message}"));
            }
        }
        /// <summary>
        /// è·¨åŒºåŸŸç§»åº“任务完成
        /// </summary>
        /// <param name="task"></param>
        /// <returns></returns>
        public async Task<WebResponseContent> CrossAreaRelocationTaskCompleted(Dt_Task task)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                if (task == null || string.IsNullOrEmpty(task.PalletCode) || string.IsNullOrEmpty(task.TargetAddress))
                {
                    return WebResponseContent.Instance.Error("跨区域移库任务信息不完整(托盘号/目标货位为空)");
                }
                // 2. æŸ¥è¯¢æ‰˜ç›˜åº“存信息
                Dt_StockInfo stockInfo = await _stockRepository.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == task.PalletCode)
                    .FirstAsync();
                if (stockInfo == null)
                {
                    return WebResponseContent.Instance.Error($"未找到托盘[{task.PalletCode}]对应的库存信息");
                }
                // éžç©ºæ‰˜ç›˜å¿…须有明细
                if (stockInfo.Details.Count == 0 && stockInfo.PalletType != PalletTypeEnum.Empty.ObjToInt())
                {
                    _logger.LogInformation($"CrossAreaRelocationTaskCompleted: æœªæ‰¾åˆ°è¯¥æ‰˜ç›˜åº“存明细信息.{task.TaskNum}");
                    return WebResponseContent.Instance.Error($"未找到该托盘[{task.PalletCode}]库存明细信息");
                }
                // 3. æŸ¥è¯¢ç›®æ ‡è´§ä½ + åŽŸè´§ä½ä¿¡æ¯
                Dt_LocationInfo targetLocationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == task.TargetAddress);
                if (targetLocationInfo == null)
                {
                    return content.Error($"未找到对应的终点货位[{task.TargetAddress}]信息");
                }
                // åŽŸè´§ä½ä¿¡æ¯
                Dt_LocationInfo oldLocationInfo = null;
                if (!string.IsNullOrEmpty(stockInfo.LocationCode))
                {
                    oldLocationInfo = _locationInfoService.Repository.QueryFirst(x => x.LocationCode == stockInfo.LocationCode);
                    if (oldLocationInfo == null)
                    {
                        return content.Error($"未找到原货位[{stockInfo.LocationCode}]信息");
                    }
                }
                // 4. è´§ä½çŠ¶æ€æ ¡éªŒ
                if (targetLocationInfo.LocationStatus == LocationStatusEnum.InStock.ObjToInt())
                {
                    return WebResponseContent.Instance.Error($"目标货位[{task.TargetAddress}]状态不正确(当前为已占用)");
                }
                // 5. å¼€å¯äº‹åŠ¡å¤„ç†æ ¸å¿ƒé€»è¾‘
                _unitOfWorkManage.BeginTran();
                // 5.1 ç›®æ ‡è´§ä½æ›´æ–°ä¸ºå ç”¨
                var beforeTargetLocationStatus = targetLocationInfo.LocationStatus;
                targetLocationInfo.LocationStatus = stockInfo.PalletType == PalletTypeEnum.Empty.ObjToInt()
                        ? LocationStatusEnum.Pallet.ObjToInt()
                        : LocationStatusEnum.InStock.ObjToInt();
                _locationInfoService.Repository.UpdateData(targetLocationInfo);
                // 5.2 åŽŸè´§ä½é‡Šæ”¾ç©ºé—²
                int beforeOldLocationStatus = 0;
                if (oldLocationInfo != null)
                {
                    beforeOldLocationStatus = oldLocationInfo.LocationStatus;
                    oldLocationInfo.LocationStatus = LocationStatusEnum.Free.ObjToInt();
                    _locationInfoService.Repository.UpdateData(oldLocationInfo);
                }
                // 5.3 æ›´æ–°åº“存:绑定新货位 + æ¢å¤æ­£å¸¸çŠ¶æ€
                string oldLocationCode = stockInfo.LocationCode;
                stockInfo.LocationCode = targetLocationInfo.LocationCode;
                stockInfo.StockStatus = StockStatusEmun.入库完成.ObjToInt();
                stockInfo.LocationType = targetLocationInfo.LocationType;
                _stockRepository.UpdateData(stockInfo);
                // 5.4 ä»»åŠ¡æ ‡è®°ä¸ºå®Œæˆ
                task.TaskStatus = TaskStatusEnum.Finish.ObjToInt();
                var result = _task_HtyService.DeleteAndMoveIntoHty(task, OperateTypeEnum.自动完成);
                // æäº¤äº‹åŠ¡
                _unitOfWorkManage.CommitTran();
                // ä»»åŠ¡å½’æ¡£å¤±è´¥åˆ™åˆ é™¤
                if (!result)
                {
                    await Db.Deleteable(task).ExecuteCommandAsync();
                }
                // è®°å½•货位状态变更日志
                try
                {
                    _locationStatusChangeRecordService.AddLocationStatusChangeRecord(
                        targetLocationInfo,
                        beforeTargetLocationStatus,
                        StockChangeType.Inbound.ObjToInt(),
                        $"跨区域移库入库(原货位:{oldLocationCode})",
                        task.TaskNum);
                    if (oldLocationInfo != null)
                    {
                        _locationStatusChangeRecordService.AddLocationStatusChangeRecord(
                            oldLocationInfo,
                            beforeOldLocationStatus,
                            StockChangeType.Outbound.ObjToInt(),
                            $"跨区域移库出库(目标货位:{targetLocationInfo.LocationCode})",
                            task.TaskNum);
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogInformation($"CrossAreaRelocationTaskCompleted è®°å½•日志异常:{ex.Message}");
                }
                return content.OK("跨区域移库任务完成");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                _logger.LogError($"CrossAreaRelocationTaskCompleted å¤„理失败:{ex.Message}", ex);
                return await Task.FromResult(WebResponseContent.Instance.Error($"跨区域移库任务处理失败:{ex.Message}"));
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs
@@ -1441,6 +1441,149 @@
                }
            }
        }
        /// <summary>
        /// é€‰å®šåº“存跨区域移库
        /// </summary>
        /// <param name="stockViews"></param>
        /// <param name="targetLocationType">目标货位类型</param>
        /// <returns></returns>
        public async Task<WebResponseContent> CrossAreaOutbound(List<StockViewDTO> stockViews, int targetLocationType)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                if(targetLocationType == (int)LocationTypeEnum.Electronic)
                {
                    return content.Error("电子仓不允许跨区域移库");
                }
                List<int> ids = stockViews.Select(x => x.StockId).ToList();
                List<Dt_StockInfo> stockInfos = _stockRepository.Db.Queryable<Dt_StockInfo>().Where(x => ids.Contains(x.Id)).Includes(x => x.Details).ToList();
                if (stockInfos.Count != stockViews.Count)
                {
                    StockViewDTO? stockViewDTO = stockViews.FirstOrDefault(x => !stockInfos.Select(x => x.PalletCode).Contains(x.PalletCode));
                    return content.Error($"未找到{stockViewDTO?.PalletCode}库存");
                }
                List<string> locStrs = stockInfos.Select(x => x.LocationCode).ToList();
                List<Dt_LocationInfo> locationInfos = _locationInfoService.Db.Queryable<Dt_LocationInfo>().Where(x => locStrs.Contains(x.LocationCode)).ToList();
                if (stockInfos.Count != locationInfos.Count)
                {
                    string? locStr = locStrs.FirstOrDefault(x => !locationInfos.Select(x => x.LocationCode).Contains(x));
                    return content.Error($"未找到{locStr}货位数据");
                }
                foreach (var item in stockInfos)
                {
                    if (item.PalletType != PalletTypeEnum.Empty.ObjToInt())
                    {
                        return content.Error($"托盘【{item.PalletCode}】非空箱,仅空箱允许跨区域移库!");
                    }
                    Dt_LocationInfo? locationInfo = locationInfos.FirstOrDefault(x => x.LocationCode == item.LocationCode);
                    if (locationInfo == null || locationInfo.EnableStatus != EnableStatusEnum.Normal.ObjToInt() || item.StockStatus != StockStatusEmun.入库完成.ObjToInt())
                    {
                        return content.Error($"{item.PalletCode}货位或库存状态不满足出库条件");
                    }
                }
                List<Dt_Task> tasks = CrossAreaGetTasks(stockInfos, targetLocationType, TaskTypeEnum.CrossAreaRelocation);
                if (tasks == null || tasks.Count <= 0)
                {
                    return content.Error("生成跨区域移库任务失败");
                }
                stockInfos.ForEach(x =>
                {
                    x.StockStatus = StockStatusEmun.出库锁定.ObjToInt();
                });
                tasks.ForEach(x =>
                {
                    x.OrderNo = "跨区域移库";
                });
                locationInfos.ForEach(x =>
                {
                    x.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                });
                _unitOfWorkManage.BeginTran();
                _stockRepository.UpdateData(stockInfos);
                BaseDal.AddData(tasks);
                _locationInfoService.UpdateData(locationInfos);
                _unitOfWorkManage.CommitTran();
                content.OK();
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return await Task.FromResult(WebResponseContent.Instance.Error(ex.Message));
            }
            return content;
        }
        /// <summary>
        /// ç”Ÿæˆè·¨åŒºåŸŸç§»åº“任务
        /// </summary>
        /// <param name="stockInfos">库存列表</param>
        /// <param name="targetAreaCode">目标区域编码</param>
        /// <param name="taskType">任务类型</param>
        /// <returns></returns>
        public List<Dt_Task> CrossAreaGetTasks(List<Dt_StockInfo> stockInfos, int targetLocationType, TaskTypeEnum taskType)
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required,
                new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
            {
                try
                {
                    List<Dt_Task> tasks = new List<Dt_Task>();
                    foreach (var stockInfo in stockInfos)
                    {
                        Dt_LocationInfo newLocation = _locationInfoService.AssignLocation(targetLocationType);
                        if (newLocation == null)
                        {
                            throw new Exception($"{stockInfo.PalletCode} æ²¡æœ‰ç©ºé—²è´§ä½å¯è¿›è¡Œè·¨åŒºåŸŸç§»åº“");
                        }
                        if (!tasks.Exists(x => x.PalletCode == stockInfo.PalletCode))
                        {
                            Dt_Task task = new()
                            {
                                CurrentAddress = stockInfo.LocationCode,
                                Grade = 0,
                                PalletCode = stockInfo.PalletCode,
                                NextAddress = "",
                                Roadway = newLocation.RoadwayNo,
                                SourceAddress = stockInfo.LocationCode,
                                TargetAddress = newLocation.LocationCode,
                                TaskStatus = TaskStatusEnum.New.ObjToInt(),
                                TaskType = taskType.ObjToInt(),
                                PalletType = stockInfo.PalletType,
                                WarehouseId = stockInfo.WarehouseId,
                            };
                            tasks.Add(task);
                        }
                        newLocation.LocationStatus = LocationStatusEnum.Lock.ObjToInt();
                        _locationInfoService.UpdateData(newLocation);
                    }
                    scope.Complete();
                    return tasks;
                }
                catch (Exception ex)
                {
                    throw new Exception(ex.Message);
                }
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
@@ -144,5 +144,12 @@
        {
            return await Service.AreaOutbound(stockViews);
        }
        [HttpPost, HttpGet, Route("CrossAreaOutbound"), AllowAnonymous]
        public async Task<WebResponseContent> CrossAreaOutbound([FromBody] List<StockViewDTO> stockViews,int targetLocationType)
        {
            return await Service.CrossAreaOutbound(stockViews,targetLocationType);
        }
    }
}