Browse Source

海柔快上-初始化

zhaohuanhuan 5 months ago
parent
commit
625a2d6b74
4 changed files with 574 additions and 0 deletions
  1. 16 0
      src/api/robot/index.ts
  2. 5 0
      src/hooks/basic/menu.js
  3. 34 0
      src/types/robot.ts
  4. 519 0
      src/views/robot/putaway/index.vue

+ 16 - 0
src/api/robot/index.ts

@@ -0,0 +1,16 @@
+// @ts-ignore
+import request from '@/utils/request'
+// @ts-ignore
+import { bindAllocateWallType } from '@/types/robot'
+/**
+ * 提交预上架信息
+ * @param data
+ */
+export function bindAllocateWall(data:bindAllocateWallType) {
+  return request({
+    url: '/api/device/check/putaway/bindAllocateWall',
+    method: 'post',
+    data
+  })
+}
+

+ 5 - 0
src/hooks/basic/menu.js

@@ -104,6 +104,11 @@ export default function() {
               icon: 'newspaper-o',
               path: 'robot-take-delivery',
             },
+            {
+              title: '海柔快上',
+              icon: 'newspaper-o',
+              path: 'robot-putaway',
+            }
           ],
         },
         {

+ 34 - 0
src/types/robot.ts

@@ -0,0 +1,34 @@
+/**
+ * 提交预上架信息
+ */
+export interface bindAllocateWallType {
+  /**
+   * 商品条码
+   */
+  barcode: string;
+  /**
+   * 格口号
+   */
+  bin: string;
+  /**
+   * 收货容器号
+   */
+  container: string;
+  /**
+   * 分拨墙
+   */
+  equipment: string;
+  /**
+   * 库位
+   */
+  location: string;
+  /**
+   * 批次号
+   */
+  lotNum?: string;
+  /**
+   * 系统版本
+   */
+  sysVersion: string;
+  [property: string]: any;
+}

+ 519 - 0
src/views/robot/putaway/index.vue

@@ -0,0 +1,519 @@
+<template>
+  <div class="container">
+    <van-nav-bar
+      title="海柔快上" left-arrow fixed placeholder @click-left="goBack">
+      <template #left>
+        <van-icon name="arrow-left" size="25" />
+        <div style="color: #fff">返回</div>
+      </template>
+    </van-nav-bar>
+    <div class="allocation">
+      <div class="code">
+        <div class="code-title">
+          <div><span style="font-size: 12px">容器:</span>{{ containerNo || '--' }}</div>
+          <div class="code-tips">
+            <van-notice-bar :background="'none'" :speed="50" :text="tips" />
+          </div>
+          <van-button plain size="mini" type="primary" @click="_setContainerNo()">切换容器</van-button>
+        </div>
+        <div class="code-input">
+          <van-search
+            v-model="wallNo"
+            placeholder="请扫描二次分拨墙号"
+            label="二次分拨墙号:"
+            @search="scanType=2"
+            left-icon=""
+            :class="[scanType===3?'search-input-barcode':'','van-hairline--bottom']"
+            @focus="scanType=3"
+            autocomplete="off"
+          >
+          </van-search>
+          <van-search
+            ref="searchRef"
+            v-model="searchBarcode"
+            placeholder="请扫描商品条码"
+            @search="_handlerScan(searchBarcode)"
+            label="商品条码:"
+            left-icon=""
+            :class="[scanType===2?'search-input-barcode':'','van-hairline--bottom']"
+            @focus="scanType=2"
+            autocomplete="off"
+          >
+          </van-search>
+          <van-search
+            class="code-bin"
+            ref="searchRef"
+            v-model="bin"
+            placeholder="分配格口"
+            label="分配格口:"
+            readonly
+            left-icon=""
+          >
+          </van-search>
+        </div>
+      </div>
+      <van-divider :style="{ color: '#333', borderColor: '#1989fa', padding: '0 16px',margin:'5px 0' }">上架库位
+      </van-divider>
+      <div class="move-stock-list">
+        <table class="task-table">
+          <thead>
+          <tr>
+            <th>库位</th>
+            <th>类型</th>
+            <th>数量</th>
+            <th style="width: 80px"></th>
+          </tr>
+          </thead>
+          <tbody>
+          <tr v-for="(item, index) in locationActive" :key="index" v-if="locationActive.length>0">
+            <td>{{ item.location }}</td>
+            <td>{{ locationType[item.type] || item.type }}</td>
+            <td>{{ item.quantity || 0 }}</td>
+            <td>
+              <!--              <van-button type="primary" plain size="mini" style="width:100%" @click="setLocation(item)" v-if="!locationActive.location">选择</van-button>-->
+              <div>
+                <van-icon size="24" color="#07c160" name="success" />
+              </div>
+            </td>
+          </tr>
+          <tr v-else>
+            <td colspan="4">
+              <div>暂无数据</div>
+            </td>
+          </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+    <!-- 条码输入组件 -->
+    <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
+    <!--  单据选择-->
+    <van-action-sheet v-model:show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次"
+                      close-on-click-action>
+      <van-cell-group>
+        <van-cell v-for="item in lotBarcodeList"
+                  @click="_getRecommendedLocation(item.lotNumber,item.owner);barcodeActive=item;lotBarcodeTrueFalseBy=false">
+          <template #title>
+            {{ item.barcode }}({{ item.quantity }}件)
+          </template>
+          <template #label>
+            生产日期:{{ item.lotAtt01 || '--' }}-失效日期:{{ item.lotAtt02 || '--' }}
+          </template>
+        </van-cell>
+      </van-cell-group>
+    </van-action-sheet>
+  </div>
+</template>
+<script setup lang="ts">
+import { onMounted, onUnmounted, ref } from 'vue'
+import { showConfirmDialog, showNotify, showToast } from 'vant'
+import { androidFocus, getHeader, goBack, playVoiceBin, scanError, scanSuccess } from '@/utils/android'
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
+import { useStore } from '@/store/modules/user'
+import { finishTask, getRecommendedLocation, getWaitPutawayList, setBindAllocateWall } from '@/api/haikang'
+import { barcodeToUpperCase } from '@/utils/dataType'
+import { closeLoading, showLoading } from '@/utils/loading'
+import { getWaitPutawayInfo, getWaitPutawayListNew } from '@/api/putaway'
+import { bindAllocateWall } from '@/api/robot'
+try {
+  getHeader()
+  androidFocus()
+} catch (error) {
+}
+// 页面初始化
+onMounted(() => {
+  openListener()
+  scanInit(_handlerScan)
+  loadData()
+})
+const store = useStore()
+const warehouse = store.warehouse
+//收货容器号
+const containerNo = ref('')
+//分拨墙号
+const wallNo = ref('')
+//扫描类型
+const scanType = ref(3)
+//条码
+const searchBarcode = ref('')
+// 错误提示
+const tips = ref('')
+//强制返回
+const back = ref(true)
+//输入框类型
+const inputBarcodeRef = ref(null)
+const inputBarcodeType = ref('barcode')
+const location=ref('')
+//库位类型
+const locationType = {
+  'EA': '件拣货库位',
+  'AP': '补充拣货位',
+  'CS': '箱拣货库位',
+  'HP': '快拣补货位',
+  'PC': '箱/件合并拣货库位',
+  'PT': '播种库位',
+  'RS': '存储库位',
+  'SS': '理货站',
+  'ST': '过渡库位',
+  'WB': '组装工作区',
+}
+// 格口数据
+const bin = ref('') //当前格口
+// lotNumber与格口号的映射关系
+const lotNumberToBinMap = new Map()
+//推荐库位
+const locationList = ref([])
+const dataMap = ref({})
+
+// 设置容器号
+const setBarcode = (code) => {
+  if (inputBarcodeType.value === 'barcode') {
+    _handlerScan(code)
+    return
+  }
+  const params = { warehouseId:warehouse, containerId: code }
+  getWaitPutawayListNew(params).then(res => {
+    if (res.data.asnToShelfList.length==0 && res.data.noAsnToShelfList.length==0 ) {
+      scanError()
+      inputBarcodeRef.value?.show('', '请扫描收货容器号', '暂未查询到待上架任务,请切换容器')
+    } else {
+      scanSuccess()
+      bin.value = ''
+      searchBarcode.value=''
+      wallNo.value=''
+      const noAsnToShelfList = res.data.noAsnToShelfList.map(item => {
+        return {
+          ...item,
+          lotNumber: item.lotNum,
+          lotAtt01: item.productDate,
+          lotAtt02: item.expireDate,
+          lotAtt04: item.productNum,
+          lotAtt08: item.quality,
+          quantity: item.remainShelfQty,
+          docNo: item.businessNo,
+          taskLineNo: item.id,
+          owner: item.customer,
+        }
+      })
+      const list = [...res.data.asnToShelfList, ...noAsnToShelfList]
+      dataMap.value = groupedData(list)
+      scanType.value = 3
+      containerNo.value = code
+    }
+  }).catch(err => {
+    scanError()
+    inputBarcodeRef.value?.show('', '请扫描退货收货容器号', err.message)
+  })
+
+}
+//切换容器
+const _setContainerNo = () => {
+  inputBarcodeType.value = 'container'
+  back.value = false
+  inputBarcodeRef.value?.show('', '请扫描退货收货容器号', '')
+}
+//批次数据
+const lotBarcodeList = ref([])
+const lotBarcodeTrueFalseBy = ref(false)
+const barcodeActive = ref({})
+// 扫描条码监听
+const _handlerScan = (code) => {
+  if (code) {
+    if (scanType.value == 2) {
+      if (!wallNo.value) {
+        tips.value = `请先扫描二次分拨墙号`
+        showToast({ duration: 3000, message: '请先扫描二次分拨墙号' })
+        scanType.value = 3
+        searchBarcode.value = ''
+        bin.value = ''
+        locationActive.value = []
+        scanError()
+        return
+      }
+      scanSuccess()
+      searchBarcode.value = code
+      barcodeActive.value = {}
+      lotBarcodeList.value = matchingBarcodeItem(dataMap.value, code)
+      locationActive.value = []
+      bin.value = ''
+      if (lotBarcodeList.value.length > 0) {
+        if (lotBarcodeList.value.length == 1) {
+          barcodeActive.value = lotBarcodeList.value[0]
+          _getRecommendedLocation(lotBarcodeList.value[0].lotNumber, lotBarcodeList.value[0].owner)
+        } else if (lotBarcodeList.value.length > 1) {
+          locationList.value = []
+          lotBarcodeTrueFalseBy.value = true
+        }
+      } else {
+        scanError()
+        tips.value = `${code}-商品条码不匹配,请重新扫描`
+        showNotify({ type: 'danger', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
+        searchBarcode.value = ''
+        locationList.value = []
+      }
+    } else if (scanType.value == 3) {
+      wallNo.value = code
+      scanType.value = 2
+    }
+  }
+}
+//匹配待上架列表数据
+const matchingBarcodeItem = (data, barcode) => {
+  const matchingItems = []
+  for (const key in data) {
+    const barcodeList = key.match(/\((.*?)\)/)[1].split('、')
+    if (data.hasOwnProperty(key)) {
+      if (barcodeList.some(item => barcodeToUpperCase(item) === barcodeToUpperCase(barcode))) {
+        matchingItems.push(data[key])
+      }
+    }
+  }
+  return matchingItems.length > 0 ? matchingItems : []
+}
+// 获取库存数据
+const _getRecommendedLocation = async (lotNum, owner) => {
+  try {
+    const params = { warehouse, lotNum, owner,zoneGroup:'WH01-02-01' }
+    const res = await getRecommendedLocation(params)
+    locationList.value = res.data
+    //  'EA'数据
+    const eaItems = res.data.filter(item => item.type === 'EA')
+    // 获取 quantity < 300 的
+    let result = eaItems.find(item => item.quantity !== null && item.quantity < 1000)
+    // 获取 quantity 为 null 的
+    if (!result) {
+      result = eaItems.find(item => item.quantity === null)
+    }
+    if (result) {
+      if(result.quantity===null){
+        scanError()
+        tips.value = `${searchBarcode.value}:库区内无商品库存,请调空料箱进行入库`
+        showNotify({ type: 'danger', duration: 3000, message: `${searchBarcode.value}:库区内无商品库存,请调空料箱进行入库` })
+        searchBarcode.value = ''
+        scanType.value=2
+        return
+      }
+      setLocation([result])
+    }
+  } catch (err) {
+    console.error(err)
+  }
+}
+// 设置库位
+const locationActive = ref([])
+const setLocation = (item) => {
+    locationActive.value = item
+
+    // 根据lotNumber分配固定格口
+    const lotNumber = barcodeActive.value.lotNumber
+    let isNewBin = false // 标记是否是新分配的格口
+
+    // 如果这个lotNumber已经有分配的格口,使用现有的
+    if (lotNumberToBinMap.has(lotNumber)) {
+      bin.value = lotNumberToBinMap.get(lotNumber).toString()
+    } else {
+      // 如果没有,分配下一个可用的格口
+      const nextBinNumber = lotNumberToBinMap.size + 1
+      if (nextBinNumber > 60) {
+        showToast({ duration: 3000, message: '格口号超过60,不进行后续操作' })
+        scanError()
+        return
+      }
+      bin.value = nextBinNumber.toString()
+      lotNumberToBinMap.set(lotNumber, nextBinNumber)
+      isNewBin = true // 标记为新分配的格口
+    }
+
+    const data = {
+      warehouse,
+      equipment: wallNo.value,
+      container: containerNo.value,
+      barcode: barcodeActive.value.barcode,
+      bin:bin.value,
+      location: locationActive.value[0].location,
+      lotNum: barcodeActive.value.lotNumber,
+      sysVersion:'V6'
+    }
+    showLoading()
+    setBindAllocateWall(data).then(res => {
+      tips.value = `请将${searchBarcode.value},放入分拨墙-${wallNo.value}-《${data.bin}》格口`
+      playVoiceBin(Number(bin.value))
+      closeLoading()
+    }).catch(err => {
+      // 如果是新分配的格口且接口请求失败,删除刚刚分配的格口信息
+      if (isNewBin) {
+        lotNumberToBinMap.delete(lotNumber)
+      }
+      searchBarcode.value = ''
+      locationActive.value = []
+      bin.value = ''
+      tips.value = err.message
+      scanError()
+      closeLoading()
+    })
+}
+// 数据刷新
+const loadData = () => {
+  if (!containerNo.value) {
+    inputBarcodeType.value = 'container'
+    inputBarcodeRef.value?.show('', '请扫描退货收货容器号', '')
+    return
+  } else {
+   setBarcode(containerNo.value)
+  }
+}
+//根据条码批次分组数据
+const groupedData = (data) => {
+  return data.reduce((acc, item) => {
+    const key = `(${item.barcode}、${item.barcodeAs}、${item.sku})-${item.lotNumber}`
+    if (acc[key]) {
+      acc[key].quantity += item.quantity
+    } else {
+      acc[key] = { ...item }
+    }
+    return acc
+  }, {})
+}
+
+onUnmounted(() => {
+  closeListener()
+})
+
+
+
+//删除分拨
+const onClickRight = () => {
+  showConfirmDialog({
+    title: '温馨提示',
+    message:'您正在进行释放分拨墙是否继续?',
+    keyboardEnabled:false
+  }).then(() => {
+    showLoading()
+    const params={warehouse,container:containerNo.value}
+    finishTask(params).then(res=>{
+      showNotify({ type: 'success', duration: 3000, message: `解绑成功` })
+      scanSuccess()
+      reset()
+      loadData()
+    }).catch(err=>{
+      scanError()
+    }).finally(() => {
+      closeLoading()
+    })
+  }).catch(() => {})
+}
+const reset=()=>{
+  containerNo.value=''
+  wallNo.value=''
+  scanType.value=3
+  searchBarcode.value = ''
+  bin.value=''
+  locationActive.value = []
+  tips.value='请扫描容器号'
+  lotNumberToBinMap.clear() // 清空lotNumber与格口的映射关系
+}
+window.onRefresh = loadData
+</script>
+<style scoped lang="sass">
+.container
+  background: #e9f4ff
+
+  .allocation
+    .code
+      box-sizing: border-box
+      padding: 8px 0
+
+      .code-title
+        display: flex
+        justify-content: space-between
+        padding: 0 15px 8px 15px
+
+      .code-input
+        ::v-deep(.van-search)
+          padding: 0
+
+        ::v-deep(.van-search__label)
+          font-size: 16px
+
+        ::v-deep(.van-search__field)
+          border-bottom: 2px solid #efefef
+          font-size: 16px
+
+        ::v-deep(.van-search__content)
+          background: #fff
+
+        .search-input-barcode
+          ::v-deep(.van-search__field)
+            border-bottom: 2px solid #0077ff
+            z-index: 2
+        .code-bin
+          ::v-deep(.van-field__control)
+            font-size: 20px
+            color: #ff0d00
+
+
+      .code-tips
+        color: #ed6a0c
+        flex: 1
+
+      .code-count
+        font-size: 16px
+        font-weight: bold
+
+        span
+          color: #0077ff
+
+    .location
+      background: #fff
+      text-align: left
+      padding: 10px 15px
+      margin-bottom: 10px
+      font-size: 14px
+
+    .move-stock-list
+      width: 100%
+      overflow-y: auto
+      max-height: 60vh
+      min-height: 100px
+
+      .move-button
+        background: #1989fa
+        color: #fff
+        width: 100%
+        height: 30px
+        font-size: 15px
+        line-height: 30px
+        font-weight: bold
+
+      .task-table, .task-table-bin, .task-table-box
+        width: 100%
+        table-layout: fixed
+        border-collapse: collapse
+        font-size: 15px
+
+      .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
+        text-align: center
+        border: 1px solid #ccc
+        word-wrap: break-word
+        word-break: break-all
+
+      .task-table thead, .task-table-bin thead, .task-table-box thead
+        background-color: #3f8dff
+        position: sticky
+        top: 0
+        color: white
+        font-size: 15px
+
+      .task-table-bin thead
+        background-color: #3f8dff
+
+      .task-table-bin tbody
+        background: #cde7ff
+
+  .nav-right
+    padding: 14px 0 12px 5px
+    color: #fff
+
+
+</style>