|
|
@@ -0,0 +1,856 @@
|
|
|
+<template>
|
|
|
+ <div class="container">
|
|
|
+ <van-nav-bar
|
|
|
+ title="还库任务"
|
|
|
+ left-arrow
|
|
|
+ fixed
|
|
|
+ placeholder
|
|
|
+ @click-left="onClickLeft"
|
|
|
+ >
|
|
|
+ <template #left>
|
|
|
+ <van-icon name="arrow-left" size="25"/>
|
|
|
+ <div style="color: #fff">返回</div>
|
|
|
+ </template>
|
|
|
+ <template #right>
|
|
|
+ <div style="color: #fff;line-height: 46px " @click="onComplete">料箱回库</div>
|
|
|
+ </template>
|
|
|
+ </van-nav-bar>
|
|
|
+ <div class="move-stock">
|
|
|
+ <div class="code">
|
|
|
+ <div class="code-title">
|
|
|
+ <div>{{ containerNo || '' }}</div>
|
|
|
+ <div class="code-tips">
|
|
|
+ <van-notice-bar :background="'none'" :speed="50" :text="tips"/>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="code-input">
|
|
|
+ <van-field v-model="workBinNo" placeholder="请扫描料箱号" label="料箱编号:" left-icon="" :class="[scanType===1?'search-input-barcode':'']"
|
|
|
+ autocomplete="off" readonly @click="setBarcodeInput(workBinNo,1)"></van-field>
|
|
|
+ <van-field v-model="searchBarcode" placeholder="请扫描商品条码" label="商品条码:" left-icon="" readonly :class="[scanType===2?'search-input-barcode':'']"
|
|
|
+ autocomplete="off" @click="setBarcodeInput(searchBarcode,2)"></van-field>
|
|
|
+ <van-field ref="countRef" @click="scanType=3" v-model="count" type="number" placeholder="还库数量" label="还库数量:" left-icon="" autocomplete="off"
|
|
|
+ :class="{'search-input-barcode': scanType === 3,'input-count': barcodeActive.availableQty}" @keydown.enter="setPutaway(2)"
|
|
|
+ :max="barcodeActive.availableQty?barcodeActive.availableQty:10000"
|
|
|
+ >
|
|
|
+ <template #button>
|
|
|
+ <div v-if="barcodeActive.qty" style="color:#333;font-size: 14px;font-weight: bold">预计{{ barcodeActive.qty ||0 }}</div>
|
|
|
+ </template>
|
|
|
+ </van-field>
|
|
|
+ <van-field v-model="searchLocation" placeholder="请扫描库位" label="还库库位:" left-icon="" readonly :class="[scanType===4?'search-input-barcode':'']"
|
|
|
+ autocomplete="off" @click="setBarcodeInput(searchLocation,4)">
|
|
|
+ <template #button>
|
|
|
+ <div v-if="recommendedLocation" style="color:#666;font-size: 12px"><span style="font-size: 10px">推荐:</span>{{ recommendedLocation }}</div>
|
|
|
+ </template>
|
|
|
+ </van-field>
|
|
|
+ </div>
|
|
|
+ <div class="code-button">
|
|
|
+ <van-button type="primary" size="small" block :disabled="searchLocation!=recommendedLocation || searchLocation=='' " @click="setPutaway(2)" >提交还库</van-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="move-stock-list">
|
|
|
+ <table class="task-table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>商品条码</th>
|
|
|
+ <th>商品名称</th>
|
|
|
+ <th>格口号</th>
|
|
|
+ <th>数量</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <template v-if="binList.length>0">
|
|
|
+ <tr v-for="(item, index) in binList" :key="index"
|
|
|
+ :class="{
|
|
|
+ 'container-highlight': isContainerHighlighted(item.containerCode),
|
|
|
+ 'barcode-highlight': isBarcodeHighlighted(item.containerCode, item.lotNum)
|
|
|
+ }">
|
|
|
+ <td>{{ item.barcode }}</td>
|
|
|
+ <td>{{ item.skuName }}</td>
|
|
|
+ <td>{{ item.slot }}</td>
|
|
|
+ <td>{{item.availableQty}}</td>
|
|
|
+ </tr>
|
|
|
+ </template>
|
|
|
+ <tr v-else>
|
|
|
+ <td colspan="4">
|
|
|
+ <van-empty :image="nodataUrl" image-size="120"/>
|
|
|
+ </td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 单据选择-->
|
|
|
+ <van-action-sheet :show="lotBarcodeTrueFalseBy" cancel-text="取消" description="请选择商品批次"
|
|
|
+ close-on-click-action
|
|
|
+ @update:show="lotBarcodeTrueFalseBy = $event">
|
|
|
+ <van-cell-group>
|
|
|
+ <van-cell v-for="(item, idx) in lotBarcodeList" :key="idx"
|
|
|
+ @click="onSelectLotBarcode(item)">
|
|
|
+ <template #title>
|
|
|
+ {{item.barcode }}({{item.availableQty}}件)
|
|
|
+ </template>
|
|
|
+ <template #label>
|
|
|
+ 生产日期:{{ item.productionDate || '--' }}-失效日期:{{ item.expirationDate || '--' }}
|
|
|
+ </template>
|
|
|
+ </van-cell>
|
|
|
+ </van-cell-group>
|
|
|
+ </van-action-sheet>
|
|
|
+ <!-- 条码输入组件 -->
|
|
|
+ <input-barcode ref="inputBarcodeRef" @setBarcode="_handlerScan" />
|
|
|
+ <!-- 料箱回库组件 -->
|
|
|
+ <go-box-back ref="goBackRef" :scanType="scanType" @setGoBack="setGoBack" />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import {onMounted, onUnmounted, ref} from 'vue'
|
|
|
+import {useRouter, useRoute} from 'vue-router'
|
|
|
+import {useStore} from '@/store/modules/user'
|
|
|
+import {androidFocus, getHeader, goBack, scanError, scanSuccess} from '@/utils/android'
|
|
|
+import {closeListener, openListener, scanInit} from '@/utils/keydownListener'
|
|
|
+import {closeLoading, showLoading} from '@/utils/loading'
|
|
|
+import nodataUrl from '@/assets/nodata.png'
|
|
|
+import {showConfirmDialog, showDialog, showNotify, showToast} from 'vant'
|
|
|
+import { barcodeToUpperCase } from '@/utils/dataType'
|
|
|
+import {
|
|
|
+ getReturnTaskBinList,
|
|
|
+ updateReturnTaskUsedQty
|
|
|
+} from "@/api/returnTask/index";
|
|
|
+import { movementReturn } from '@/api/check/index'
|
|
|
+import { boxReturn } from '@/api/haikang'
|
|
|
+import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
|
|
|
+import GoBoxBack from '@/views/haikang/putaway/components/GoBoxBack.vue'
|
|
|
+
|
|
|
+const router = useRouter()
|
|
|
+const route = useRoute()
|
|
|
+const store = useStore()
|
|
|
+try {
|
|
|
+ getHeader()
|
|
|
+ androidFocus()
|
|
|
+} catch (error) {
|
|
|
+ router.push('/login')
|
|
|
+}
|
|
|
+const warehouse = store.warehouse
|
|
|
+
|
|
|
+const pattern = /^[1-9]\d*$/
|
|
|
+// 容器号和扫描类型的状态
|
|
|
+const taskNo = ref(route.query.code)
|
|
|
+const containerNo = ref(route.query.container)
|
|
|
+//扫描类型
|
|
|
+const scanType = ref(2)
|
|
|
+const searchRef = ref(null)
|
|
|
+//料箱编号
|
|
|
+const workBinNo = ref('')
|
|
|
+//扫描条码
|
|
|
+const searchBarcode = ref('')
|
|
|
+//原始扫描的条码(用于匹配binList)
|
|
|
+const originalBarcode = ref('')
|
|
|
+//还库数量
|
|
|
+const count = ref('')
|
|
|
+//扫描库位
|
|
|
+const location = ref('')
|
|
|
+//还库库位
|
|
|
+const searchLocation = ref('')
|
|
|
+//推荐库位
|
|
|
+const recommendedLocation = ref('')
|
|
|
+//提示信息
|
|
|
+const tips = ref('请扫描料箱编号')
|
|
|
+//批次数据
|
|
|
+const lotBarcodeList = ref([])
|
|
|
+const lotBarcodeTrueFalseBy = ref(false)
|
|
|
+const barcodeActive = ref({})
|
|
|
+//格口
|
|
|
+const bin = ref('-')
|
|
|
+const binList = ref([])
|
|
|
+const model = ref({})
|
|
|
+const countRef = ref(null)
|
|
|
+const back = ref(true)
|
|
|
+const goBackRef = ref(null)
|
|
|
+// 高亮状态:橘黄色高亮(containerCode匹配)
|
|
|
+const orangeHighlightSet = ref(new Set())
|
|
|
+// 高亮状态:绿色高亮(条码匹配)
|
|
|
+const greenHighlightSet = ref(new Set())
|
|
|
+
|
|
|
+// 检查是否应该高亮
|
|
|
+const isContainerHighlighted = (containerCode) => orangeHighlightSet.value?.has(containerCode)
|
|
|
+const isBarcodeHighlighted = (containerCode, lotNum) => greenHighlightSet.value?.has(`${containerCode}-${lotNum}`)
|
|
|
+
|
|
|
+// 更新高亮状态:从橘黄色变为绿色
|
|
|
+const updateHighlight = (barcode, lotNum) => {
|
|
|
+ const barcodeUpper = barcodeToUpperCase(barcode)
|
|
|
+ binList.value
|
|
|
+ .filter(item => {
|
|
|
+ // 匹配条码
|
|
|
+ const barcodeMatch = barcodeToUpperCase(item.barcode) === barcodeUpper ||
|
|
|
+ barcodeToUpperCase(item.alternateSku1) === barcodeUpper ||
|
|
|
+ barcodeToUpperCase(item.alternateSku2) === barcodeUpper
|
|
|
+ return orangeHighlightSet.value.has(item.containerCode) &&
|
|
|
+ barcodeMatch &&
|
|
|
+ item.lotNum === lotNum
|
|
|
+ })
|
|
|
+ .forEach(item => {
|
|
|
+ orangeHighlightSet.value.delete(item.containerCode)
|
|
|
+ greenHighlightSet.value.add(`${item.containerCode}-${item.lotNum}`)
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 页面初始化
|
|
|
+onMounted(() => {
|
|
|
+ openListener()
|
|
|
+ scanInit(_handlerScan)
|
|
|
+ loadData()
|
|
|
+})
|
|
|
+
|
|
|
+//设置条码
|
|
|
+const inputBarcodeRef = ref(null)
|
|
|
+const setBarcodeInput = (code, type) => {
|
|
|
+ const typeMap = { 1: '请扫描料箱编号', 2: '请扫描商品条码', 3: '请输入还库数量', 4: '请扫描还库库位' }
|
|
|
+ scanType.value = type
|
|
|
+ inputBarcodeRef.value?.show(code, typeMap[type])
|
|
|
+}
|
|
|
+
|
|
|
+// 扫描条码监听
|
|
|
+const _handlerScan = (code) => {
|
|
|
+ if (scanType.value === 1) {
|
|
|
+ workBinNo.value = barcodeToUpperCase(code)
|
|
|
+ // 先清空推荐库位、预计数量和匹配的条码
|
|
|
+ recommendedLocation.value = ''
|
|
|
+ barcodeActive.value = {}
|
|
|
+ searchBarcode.value = ''
|
|
|
+ originalBarcode.value = ''
|
|
|
+ count.value = ''
|
|
|
+ searchLocation.value = ''
|
|
|
+ // 高亮所有匹配的containerCode
|
|
|
+ orangeHighlightSet.value.clear()
|
|
|
+ greenHighlightSet.value.clear()
|
|
|
+ // 在binList中查找匹配的containerCode并高亮
|
|
|
+ const matched = binList.value.filter(item =>
|
|
|
+ item.containerCode && (item.containerCode === barcodeToUpperCase(code) || item.containerCode.startsWith(`${barcodeToUpperCase(code)}-`))
|
|
|
+ )
|
|
|
+ if (matched.length === 0) {
|
|
|
+ scanError()
|
|
|
+ tips.value = `料箱编号:${code},未找到匹配的数据`
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: `料箱编号:${code},未找到匹配的数据` })
|
|
|
+ // 清空页面
|
|
|
+ workBinNo.value = ''
|
|
|
+ searchBarcode.value = ''
|
|
|
+ originalBarcode.value = ''
|
|
|
+ searchLocation.value = ''
|
|
|
+ recommendedLocation.value = ''
|
|
|
+ count.value = ''
|
|
|
+ barcodeActive.value = {}
|
|
|
+ orangeHighlightSet.value.clear()
|
|
|
+ greenHighlightSet.value.clear()
|
|
|
+ scanType.value = 1
|
|
|
+ return
|
|
|
+ }
|
|
|
+ matched.forEach(item => orangeHighlightSet.value.add(item.containerCode))
|
|
|
+ sortBinList()
|
|
|
+ scanType.value = 2
|
|
|
+ tips.value = '请扫描商品条码'
|
|
|
+ scanSuccess()
|
|
|
+ } else if (scanType.value === 2) {
|
|
|
+ // 商品条码扫描
|
|
|
+ setTimeout(() => {
|
|
|
+ _getInventoryList(code)
|
|
|
+ }, 200)
|
|
|
+ } else if (scanType.value === 4) {
|
|
|
+ const codeUpper = barcodeToUpperCase(code)
|
|
|
+ const recommendedUpper = barcodeToUpperCase(recommendedLocation.value)
|
|
|
+ if (codeUpper !== recommendedUpper) {
|
|
|
+ tips.value = `${code}-扫描库位与推荐库位不一致,请重新扫描`
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: `${code}-扫描库位与推荐库位不一致,请重新扫描` })
|
|
|
+ scanError()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ searchLocation.value = barcodeToUpperCase(code)
|
|
|
+ scanType.value = 3
|
|
|
+ count.value = barcodeActive.value.availableQty || 1
|
|
|
+ tips.value = '请输入还库数量'
|
|
|
+ scanSuccess()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// 从binList中获取匹配到的推荐库位(containerCode)
|
|
|
+const getRecommendedLocationFromBin = (barcode, lotNum) => {
|
|
|
+ const barcodeUpper = barcodeToUpperCase(barcode)
|
|
|
+ // 获取匹配到的绿色高亮项(最终匹配到的批次)
|
|
|
+ const matchedItems = binList.value.filter(item => {
|
|
|
+ // 匹配 barcode、alternateSku1、alternateSku2 三个字段,使用 barcodeToUpperCase 进行大小写不敏感匹配
|
|
|
+ const barcodeMatch = barcodeToUpperCase(item.barcode) === barcodeUpper ||
|
|
|
+ barcodeToUpperCase(item.alternateSku1) === barcodeUpper ||
|
|
|
+ barcodeToUpperCase(item.alternateSku2) === barcodeUpper
|
|
|
+ const match = barcodeMatch && item.lotNum === lotNum
|
|
|
+ if (!match) return false
|
|
|
+ return greenHighlightSet.value?.has(`${item.containerCode}-${item.lotNum}`)
|
|
|
+ })
|
|
|
+
|
|
|
+ if (matchedItems.length > 0) {
|
|
|
+ // 返回第一个匹配项的containerCode作为推荐库位
|
|
|
+ return matchedItems[0].containerCode || ''
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+}
|
|
|
+
|
|
|
+// 选择批次后的处理
|
|
|
+const onSelectLotBarcode = (item) => {
|
|
|
+ // 使用批次对象中保存的实际 barcode(支持 alternateSku1、alternateSku2 匹配)
|
|
|
+ const actualBarcode = item.barcode || originalBarcode.value
|
|
|
+ updateHighlight(actualBarcode, item.lotNum)
|
|
|
+ // 基于最终匹配到的批次计算数量(使用可用数量 availableQty)
|
|
|
+ barcodeActive.value = item
|
|
|
+ // 从binList中获取推荐库位(containerCode)
|
|
|
+ recommendedLocation.value = getRecommendedLocationFromBin(actualBarcode, item.lotNum)
|
|
|
+ lotBarcodeTrueFalseBy.value = false
|
|
|
+ scanType.value = 4
|
|
|
+ tips.value = '请扫描还库库位'
|
|
|
+ scanSuccess()
|
|
|
+}
|
|
|
+
|
|
|
+const _getInventoryList = (barcode) => {
|
|
|
+ try {
|
|
|
+ // 检查是否有当前料箱号
|
|
|
+ if (!workBinNo.value) {
|
|
|
+ scanError()
|
|
|
+ searchBarcode.value = ''
|
|
|
+ originalBarcode.value = ''
|
|
|
+ const msg = '请先扫描料箱编号'
|
|
|
+ tips.value = msg
|
|
|
+ showNotify({duration: 5000, message: msg})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存原始条码用于匹配binList
|
|
|
+ originalBarcode.value = barcode
|
|
|
+
|
|
|
+ // 在当前料箱内查找匹配的条码数据
|
|
|
+ const scanBarcodeUpper = barcodeToUpperCase(barcode)
|
|
|
+ const matchedItems = binList.value.filter(binItem => {
|
|
|
+ const containerMatch = binItem.containerCode === workBinNo.value ||
|
|
|
+ binItem.containerCode?.startsWith(`${workBinNo.value}-`)
|
|
|
+ if (!containerMatch) return false
|
|
|
+ return barcodeToUpperCase(binItem.barcode) === scanBarcodeUpper ||
|
|
|
+ barcodeToUpperCase(binItem.alternateSku1) === scanBarcodeUpper ||
|
|
|
+ barcodeToUpperCase(binItem.alternateSku2) === scanBarcodeUpper
|
|
|
+ })
|
|
|
+
|
|
|
+ // 如果没有任何匹配的数据,提示错误
|
|
|
+ if (matchedItems.length === 0) {
|
|
|
+ scanError()
|
|
|
+ const msg = `${barcode}不在当前料箱内,当前料箱号:${workBinNo.value}`
|
|
|
+ searchBarcode.value = ''
|
|
|
+ originalBarcode.value = ''
|
|
|
+ tips.value = msg
|
|
|
+ showNotify({duration: 5000, message: msg})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据批次(lotNum)分组,生成批次列表
|
|
|
+ const lotMap = new Map()
|
|
|
+ matchedItems.forEach(item => {
|
|
|
+ const lotNum = item.lotNum || ''
|
|
|
+ if (!lotMap.has(lotNum)) {
|
|
|
+ // 获取实际匹配的条码
|
|
|
+ const matchedBarcode = barcodeToUpperCase(item.barcode) === scanBarcodeUpper ? item.barcode :
|
|
|
+ barcodeToUpperCase(item.alternateSku1) === scanBarcodeUpper ? item.barcode :
|
|
|
+ barcodeToUpperCase(item.alternateSku2) === scanBarcodeUpper ? item.barcode : barcode
|
|
|
+
|
|
|
+ // 计算可用数量
|
|
|
+ lotMap.set(lotNum, {
|
|
|
+ customerId:item.customerId,
|
|
|
+ lotNum: lotNum,
|
|
|
+ usedQty:item.usedQty,
|
|
|
+ qty:item.qty,
|
|
|
+ availableQty: item.availableQty, // 添加可用数量
|
|
|
+ productionDate: item.productionDate ,
|
|
|
+ expirationDate: item.expirationDate,
|
|
|
+ barcode: item.barcode // 使用实际匹配的条码
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ // 转换为数组
|
|
|
+ const matchedInBin = Array.from(lotMap.values())
|
|
|
+
|
|
|
+ // 如果没有任何批次,提示错误
|
|
|
+ if (matchedInBin.length === 0) {
|
|
|
+ scanError()
|
|
|
+ const msg = `${barcode}不在当前料箱内,当前料箱号:${workBinNo.value}`
|
|
|
+ searchBarcode.value = ''
|
|
|
+ originalBarcode.value = ''
|
|
|
+ tips.value = msg
|
|
|
+ showNotify({duration: 5000, message: msg})
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 只保留在当前料箱内的批次
|
|
|
+ lotBarcodeList.value = matchedInBin
|
|
|
+ searchBarcode.value = matchedInBin[0].barcode
|
|
|
+
|
|
|
+ if (matchedInBin.length === 1) {
|
|
|
+ // 只有一个批次,直接选择
|
|
|
+ const item = matchedInBin[0]
|
|
|
+ // 更新高亮状态(从橘黄色变为绿色)
|
|
|
+ updateHighlight(item.barcode, item.lotNum)
|
|
|
+ barcodeActive.value = item
|
|
|
+ // 从binList中获取推荐库位(containerCode)
|
|
|
+ recommendedLocation.value = getRecommendedLocationFromBin(item.barcode, item.lotNum)
|
|
|
+ scanType.value = 4
|
|
|
+ tips.value = '请扫描还库库位'
|
|
|
+ scanSuccess()
|
|
|
+ } else {
|
|
|
+ // 多个批次,弹出批次选择弹窗
|
|
|
+ lotBarcodeTrueFalseBy.value = true
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error(err)
|
|
|
+ scanError()
|
|
|
+ tips.value = err.message || '匹配失败'
|
|
|
+ showNotify({type: 'danger', duration: 3000, message: err.message || '匹配失败'})
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 提交还库
|
|
|
+const setPutaway = async (type) => {
|
|
|
+ if (type !== 2) return
|
|
|
+
|
|
|
+ const validations = [
|
|
|
+ { check: !searchBarcode.value, type: 2, msg: '请扫描商品条码' },
|
|
|
+ { check: !searchLocation.value, type: 4, msg: '请扫描还库库位' },
|
|
|
+ { check: barcodeToUpperCase(searchLocation.value) !== barcodeToUpperCase(recommendedLocation.value), type: 4, msg: '扫描库位与推荐库位不一致,请重新扫描' },
|
|
|
+ { check: !count.value || Number(count.value) <= 0, type: 3, msg: '请输入还库数量' },
|
|
|
+ { check: barcodeActive.value.availableQty && Number(count.value) > Number(barcodeActive.value.availableQty), type: 3, msg: '还库数量不能大于预计还库数量' }
|
|
|
+ ]
|
|
|
+
|
|
|
+ const error = validations.find(v => v.check)
|
|
|
+ if (error) {
|
|
|
+ scanType.value = error.type
|
|
|
+ tips.value = error.msg
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: error.msg })
|
|
|
+ scanError()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const {customerId,lotNum,barcode:sku}=barcodeActive.value
|
|
|
+ const data = {
|
|
|
+ warehouse: warehouse,
|
|
|
+ owner:customerId,
|
|
|
+ lotNum,
|
|
|
+ sku,
|
|
|
+ quantity: count.value,
|
|
|
+ fmLocation:containerNo.value ,
|
|
|
+ toLocation: searchLocation.value
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ showLoading()
|
|
|
+ const res = await movementReturn(data)
|
|
|
+ if(res){
|
|
|
+ // 还库成功后,更新还库任务已使用数量
|
|
|
+ try {
|
|
|
+ await updateReturnTaskUsedQty({
|
|
|
+ taskNo: taskNo.value,
|
|
|
+ lotNum: barcodeActive.value.lotNum,
|
|
|
+ moveQty: Number(count.value)
|
|
|
+ })
|
|
|
+ } catch (updateErr) {
|
|
|
+ console.error('更新已使用数量失败:', updateErr)
|
|
|
+ // 不阻断主流程,只记录错误
|
|
|
+ }
|
|
|
+ }
|
|
|
+ closeLoading()
|
|
|
+ scanSuccess()
|
|
|
+ // 保存当前还库数量,用于料箱回库弹窗显示
|
|
|
+ const lastCount = count.value
|
|
|
+ const lastBarcodeActive = { ...barcodeActive.value }
|
|
|
+ // 重置(保留料箱号,以便继续扫描同一料箱的其他商品)
|
|
|
+ searchBarcode.value = ''
|
|
|
+ originalBarcode.value = ''
|
|
|
+ searchLocation.value = ''
|
|
|
+ recommendedLocation.value = ''
|
|
|
+ count.value = ''
|
|
|
+ barcodeActive.value = {}
|
|
|
+ // 刷新binList
|
|
|
+ getTaskBinList()
|
|
|
+
|
|
|
+ // 检查当前料箱是否已完成还库
|
|
|
+ setTimeout(() => {
|
|
|
+ if (checkBinComplete()) {
|
|
|
+ // 所有商品都已还库完成,弹出料箱回库提示
|
|
|
+ goBackRef.value?.show(workBinNo.value, '1', lastCount, lastBarcodeActive)
|
|
|
+ } else {
|
|
|
+ // 还有未还库的商品,继续扫描
|
|
|
+ scanType.value = 2
|
|
|
+ tips.value = '请扫描商品条码'
|
|
|
+ showNotify({ type: 'success', duration: 3000, message: '还库成功,请继续扫描商品条码' })
|
|
|
+ }
|
|
|
+ }, 300)
|
|
|
+ } catch (err) {
|
|
|
+ closeLoading()
|
|
|
+ scanError()
|
|
|
+ tips.value = err.message || '还库失败'
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: err.message || '还库失败' })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 排序:匹配到的料箱排在最前面
|
|
|
+const sortBinList = () => {
|
|
|
+ binList.value.sort((a, b) => {
|
|
|
+ const aHighlighted = orangeHighlightSet.value.has(a.containerCode)
|
|
|
+ const bHighlighted = orangeHighlightSet.value.has(b.containerCode)
|
|
|
+ if (aHighlighted !== bHighlighted) return aHighlighted ? -1 : 1
|
|
|
+ return b.slot - a.slot
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const getTaskBinList = () => {
|
|
|
+ getReturnTaskBinList({taskNo: taskNo.value}).then(res => {
|
|
|
+ // 过滤并计算可用数量
|
|
|
+ binList.value = res.data
|
|
|
+ .filter(v => v.qty !== v.usedQty)
|
|
|
+ .map(item => ({
|
|
|
+ ...item,
|
|
|
+ availableQty: (item.qty || 0) - (item.usedQty || 0) // 计算可用数量
|
|
|
+ }))
|
|
|
+ sortBinList()
|
|
|
+ }).catch(err => {
|
|
|
+ scanError()
|
|
|
+ tips.value = `${err.message}`
|
|
|
+ showNotify({type: 'danger', duration: 3000, message: `${err.message}`})
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 检查当前料箱是否已完成还库
|
|
|
+const checkBinComplete = () => {
|
|
|
+ if (!workBinNo.value) return false
|
|
|
+ // 检查binList中是否还有匹配当前料箱号的数据
|
|
|
+ const remainingItems = binList.value.filter(item =>
|
|
|
+ item.containerCode && (item.containerCode === workBinNo.value || item.containerCode.startsWith(`${workBinNo.value}-`))
|
|
|
+ )
|
|
|
+ return remainingItems.length === 0
|
|
|
+}
|
|
|
+// 料箱回库(使用上架页面的逻辑)
|
|
|
+const onComplete = () => {
|
|
|
+ if (!workBinNo.value) {
|
|
|
+ scanType.value = 1
|
|
|
+ tips.value = '请先扫描料箱编号'
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: '请先扫描料箱编号' })
|
|
|
+ scanError()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从binList中获取container和externalCode信息
|
|
|
+ const currentBinItem = binList.value.find(item =>
|
|
|
+ item.containerCode && (item.containerCode === workBinNo.value || item.containerCode.startsWith(`${workBinNo.value}-`))
|
|
|
+ )
|
|
|
+
|
|
|
+ const container = currentBinItem?.container || containerNo.value
|
|
|
+ const externalCode = currentBinItem?.externalCode || taskNo.value
|
|
|
+
|
|
|
+ showConfirmDialog({
|
|
|
+ title: '料箱回库',
|
|
|
+ message: `${workBinNo.value},是否执行料箱回库?`,
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ showLoading()
|
|
|
+ boxReturn({ warehouse, container, boxCode: workBinNo.value, externalCode,taskType:'HIK' }).then(res => {
|
|
|
+ closeLoading()
|
|
|
+ scanSuccess()
|
|
|
+ tips.value = '料箱回库成功,请继续扫描料箱编号'
|
|
|
+ showNotify({ type: 'success', duration: 3000, message: '料箱回库成功,请继续扫描料箱编号' })
|
|
|
+ if(binList.value==0){
|
|
|
+ router.push({ name: 'ReturnList'})
|
|
|
+ }
|
|
|
+ resetAllFields()
|
|
|
+ }).catch(err => {
|
|
|
+ closeLoading()
|
|
|
+ scanError()
|
|
|
+ tips.value = err.message || '料箱回库失败'
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: err.message || '料箱回库失败' })
|
|
|
+ })
|
|
|
+ })
|
|
|
+ .catch(() => {})
|
|
|
+}
|
|
|
+
|
|
|
+// 料箱回库处理(使用上架页面的逻辑)
|
|
|
+const setGoBack = async (item) => {
|
|
|
+ if (item.active === '1') {
|
|
|
+ // 料箱回库
|
|
|
+ if (!workBinNo.value) {
|
|
|
+ scanType.value = 1
|
|
|
+ tips.value = '请先扫描料箱编号'
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: '请先扫描料箱编号' })
|
|
|
+ scanError()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const container = containerNo.value
|
|
|
+ const externalCode = taskNo.value
|
|
|
+ showLoading()
|
|
|
+ try {
|
|
|
+ await boxReturn({ warehouse, container, boxCode: workBinNo.value, externalCode,taskType:'HIK' })
|
|
|
+ closeLoading()
|
|
|
+ scanSuccess()
|
|
|
+ if(binList.value==0){
|
|
|
+ await router.push({ name: 'ReturnList'})
|
|
|
+ }
|
|
|
+ // 重置所有状态
|
|
|
+ resetAllFields()
|
|
|
+ tips.value = '料箱回库成功,请继续扫描料箱编号'
|
|
|
+ showNotify({ type: 'success', duration: 3000, message: '料箱回库成功,请继续扫描料箱编号' })
|
|
|
+ } catch (err) {
|
|
|
+ closeLoading()
|
|
|
+ scanError()
|
|
|
+ tips.value = err.message || '料箱回库失败'
|
|
|
+ showNotify({ type: 'danger', duration: 3000, message: err.message || '料箱回库失败' })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 重置所有字段
|
|
|
+const resetAllFields = () => {
|
|
|
+ workBinNo.value = ''
|
|
|
+ searchBarcode.value = ''
|
|
|
+ originalBarcode.value = ''
|
|
|
+ searchLocation.value = ''
|
|
|
+ recommendedLocation.value = ''
|
|
|
+ count.value = ''
|
|
|
+ barcodeActive.value = {}
|
|
|
+ orangeHighlightSet.value.clear()
|
|
|
+ greenHighlightSet.value.clear()
|
|
|
+ scanType.value = 1
|
|
|
+ getTaskBinList()
|
|
|
+}
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ closeListener()
|
|
|
+})
|
|
|
+
|
|
|
+const onClickLeft = () => {
|
|
|
+ router.push({name:'ReturnList'})
|
|
|
+};
|
|
|
+// 数据刷新
|
|
|
+const loadData = () => {
|
|
|
+ scanType.value = 1
|
|
|
+ tips.value = '请扫描料箱编号'
|
|
|
+ getTaskBinList()
|
|
|
+}
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ closeListener()
|
|
|
+})
|
|
|
+
|
|
|
+window.onRefresh = loadData
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.container {
|
|
|
+ .move-stock {
|
|
|
+ .code {
|
|
|
+ background: #e9f4ff;
|
|
|
+ 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-field) {
|
|
|
+ min-height: 50px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep(.van-field__body) {
|
|
|
+ min-height: 50px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep(.van-cell) {
|
|
|
+ padding: 5px 20px 0 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep(.van-field__control) {
|
|
|
+ border-bottom: 2px solid #efefef;
|
|
|
+ height: 50px;
|
|
|
+ line-height: 50px;
|
|
|
+ font-size: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep(.van-field__button) {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 50px;
|
|
|
+ }
|
|
|
+
|
|
|
+ ::v-deep(.van-field__label) {
|
|
|
+ width: unset;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input-barcode {
|
|
|
+ ::v-deep(.van-field__control) {
|
|
|
+ border-bottom: 2px solid #0077ff;
|
|
|
+ z-index: 2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .code-button{
|
|
|
+ padding: 10px 15px;
|
|
|
+ }
|
|
|
+ .code-tips {
|
|
|
+ color: #ed6a0c;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .code-count {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: bold;
|
|
|
+
|
|
|
+ span {
|
|
|
+ color: #0077ff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .nav-right {
|
|
|
+ padding: 14px 0 12px 5px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .move-stock-list {
|
|
|
+ width: 100%;
|
|
|
+ overflow-y: auto;
|
|
|
+ max-height: 280px;
|
|
|
+ min-height: 100px;
|
|
|
+
|
|
|
+ .move-button {
|
|
|
+ background: #1989fa;
|
|
|
+ color: #fff;
|
|
|
+ width: 100%;
|
|
|
+ height: 30px;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 30px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table,
|
|
|
+ .task-table-bin,
|
|
|
+ .task-table-box {
|
|
|
+ width: 100%;
|
|
|
+ table-layout: fixed;
|
|
|
+ border-collapse: collapse;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .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: 13px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table-bin thead {
|
|
|
+ background-color: #3f8dff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table-bin tbody {
|
|
|
+ background: #cde7ff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table tbody tr.pricking-active {
|
|
|
+ background-color: #d6f9e7;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table tbody tr.stock-active {
|
|
|
+ background-color: #fffadd;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table tbody tr.virtual-active {
|
|
|
+ background-color: #cacaca;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table tbody tr.container-highlight {
|
|
|
+ background-color: #ffa500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .task-table tbody tr.barcode-highlight {
|
|
|
+ background-color: #90ee90 !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .move-stock-content {
|
|
|
+ margin-bottom: 10px;
|
|
|
+
|
|
|
+ .compact-table {
|
|
|
+ width: 100%;
|
|
|
+ border-collapse: collapse;
|
|
|
+ margin: 0;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.2;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+
|
|
|
+ td {
|
|
|
+ padding: 8px 12px;
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ background: transparent;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ td:first-child {
|
|
|
+ color: #333;
|
|
|
+ font-size: 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ td:last-child {
|
|
|
+ color: #e63535;
|
|
|
+ font-size: 30px;
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.count-input {
|
|
|
+ ::v-deep(.van-field__value) {
|
|
|
+ border-bottom: 2px solid #0077ff;
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.completion {
|
|
|
+ text-align: right;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 35px;
|
|
|
+ color: #0077ff;
|
|
|
+ padding: 0 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ text-decoration: underline;
|
|
|
+}
|
|
|
+
|
|
|
+.tips {
|
|
|
+ font-size: 13px;
|
|
|
+ text-align: left;
|
|
|
+ padding: 5px 15px;
|
|
|
+}
|
|
|
+</style>
|