| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312 |
- <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="take-delivery">
- <div class="take-info">
- <div class="take-info-no">
- <div class="info-no-title">
- <div>任务号:{{ taskInfo.taskNo || '--' }}</div>
- <div>
- <van-button type="primary" size="mini" plain @click="switchTask">切换任务</van-button>
- </div>
- </div>
- <div class="info-no-tips">
- <div>货主:<span style="color: #333;font-weight: bold;">{{ taskInfo.customerName || '--' }}</span></div>
- <div>任务数:<span style="font-weight: bold"
- ><span
- :style="{
- color: (Number(taskInfo.receivedQty || 0) + Number(taskInfo.overReceiveQuantity || 0)) > Number(taskInfo.expectedQty || 0) ? '#ee0a24' : '#0077ff',
- }"
- >{{ Number(taskInfo.receivedQty || 0) + Number(taskInfo.overReceiveQuantity || 0) }}</span><span style="color: #0077ff">/{{ taskInfo.expectedQty || 0 }}</span></span></div>
- </div>
- </div>
- <div class="take-info-number">
- <div class="info-number-left">
- <div class="number-left-box">
- <div>开始时间</div>
- <div class="left-box-title">{{ currentTime }}</div>
- </div>
- <div class="number-left-box">
- <div>已用时</div>
- <div class="left-box-title">{{ formattedTime }}</div>
- </div>
- </div>
- <div class="info-number-right">
- <div>容器号</div>
- <div>
- <van-search
- ref="containerNoRef"
- v-model="containerNo"
- left-icon=""
- placeholder="请扫描容器号"
- autocomplete="off"
- @search="isCheck()"
- @focus="scanType=5"
- @blur="scanType=2"
- ></van-search>
- </div>
- </div>
- </div>
- </div>
- <van-progress
- v-if="Number(taskInfo.expectedQty) > 0"
- :percentage="Number((((Number(taskInfo.receivedQty || 0) + Number(taskInfo.overReceiveQuantity || 0)) / Number(taskInfo.expectedQty)) * 100).toFixed(2))"
- :color="(Number(taskInfo.receivedQty || 0) + Number(taskInfo.overReceiveQuantity || 0)) / Number(taskInfo.expectedQty) * 100 > 100 ? '#ee0a24' : '#1989fa'"
- stroke-width="4"
- />
- <div v-if="showOverReceiveTag" class="over-receive-hint" role="status">
- <span class="over-receive-hint__badge">超收</span>
- <span class="over-receive-hint__text">无待收行明细的收货</span>
- </div>
- <div class="take-barcode">
- <div class="barcode-input">
- <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"
- @input="onAsnCancel"
- @clear="reset"
- >
- </van-search>
- </div>
- <div class="barcode-input">
- <van-search
- ref="numberRef"
- v-model="searchCount"
- placeholder="请输入收货数量"
- type="number"
- label="收货数量:"
- left-icon=""
- autocomplete="off"
- show-action
- :min="1"
- :max="999999"
- @search="isCheck()"
- :class="[scanType===4?'search-input-barcode':'','van-hairline--bottom']"
- @focus="scanType=4"
- >
- <!-- :max="asnInfo.asnNo?asnInfo.expectedQuantity-asnInfo.receivedQuantity:10000" -->
- <template #action>
- <div style="display: flex; align-items: center;flex-direction: column;margin-left: 20px" v-if="asnInfo.asnNo">
- <div style="height: 20px;font-size: 12px">已收/预计</div>
- <div style="font-size: 16px;font-weight: bold;color: #323233">
- <span
- :style="{
- color:
- (Number(asnInfo.receivedQuantity || 0) + Number(asnInfo.overReceiveQuantity || 0)) > Number(asnInfo.expectedQuantity || 0)
- ? '#ee0a24'
- : '#323233',
- }"
- >{{ Number(asnInfo.receivedQuantity || 0) + Number(asnInfo.overReceiveQuantity || 0) }}</span>/{{ asnInfo.expectedQuantity }}
- </div>
- </div>
- </template>
- </van-search>
- </div>
- </div>
- <div class="take-lot" v-if="lotData.length>0">
- <van-cell-group>
- <div class="take-lot-title">批次信息</div>
- <van-cell v-for="(item,i) in lotData" :key="i" :is-link="item.field!=='lotAtt05'"
- @click="onLot(item)">
- <template #title>
- <van-icon v-if="item.require" name="warning-o" color="#ed6a0c" />
- <span class="custom-title">{{ item.label }}</span>
- </template>
- <template #value>
- <div>{{ lotMap[item.mapping] || item.mapping }} </div>
- </template>
- </van-cell>
- </van-cell-group>
- </div>
- <div class="take-button">
- <van-button type="primary" size="large" round style="height: 36px" @click="onConfirm(false)">完成收货</van-button>
- </div>
- </div>
- </div>
- <!-- 条码输入组件 -->
- <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
- <!-- 单据选择-->
- <van-action-sheet
- v-model:show="asnDetailsTrueFalseBy"
- cancel-text="取消"
- description="请选择具体单据"
- close-on-click-action
- @closed="onAsnActionSheetClosed"
- >
- <van-cell-group>
- <van-cell v-for="item in asnDetailsList" @click="onDetailActive(item)">
- <template #title>
- {{ item.asnNo }}({{ item.customerId }}-{{ item.expectedQuantity }}件)
- </template>
- </van-cell>
- </van-cell-group>
- </van-action-sheet>
- <!-- 商品物理属性-->
- <attribute ref="attributeRef" @set-attribute="setAttribute" />
- <!-- 商品批次属性-->
- <lot-date ref="lotDateRef" @select-lot-date="selectLotDate" />
- <!-- 组合商品-->
- <barcode-combine ref="barcodeCombineRef" @setCombine="setCombineReceiving" @cancel="onCombineCancel" :container="containerNo" :matched-sku="combineMatchedSku" />
- <!-- 唯一码-->
- <unique-code-input ref="uniqueCodeRef"
- v-model:uniqueCodeList="uniqueCodeList"
- v-model:scanType="scanType"
- v-model:checkAllType="checkAllType"
- :searchCount="searchCount"
- :combine-set-count="combineReceivingSetCount"
- :asnInfo="asnInfo"
- :resolve-panpass-codes="isCombinePanpass ? resolvePanpassScan : undefined"
- @setUniqueCode="onConfirm(false)"
- />
- <van-action-sheet
- v-model:show="lotQualityTrueFalseBy"
- cancel-text="取消"
- close-on-click-action
- :description="'请选择'+lotTitle"
- >
- <van-cell-group>
- <van-cell v-for="(value,key) in lotQualityMap" @click="onSelectLotQuality(key)">
- <template #title>
- {{value}}
- </template>
- </van-cell>
- </van-cell-group>
- </van-action-sheet>
- </template>
- <script setup>
- import { onMounted, onUnmounted, ref, computed } from 'vue'
- import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
- import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
- import { closeListener, openListener, scanInit } from '@/utils/keydownListener.js'
- import { useRouter } from 'vue-router'
- import {
- calculateShelfLife, getCommodityRule,
- getIReceivingTask,
- getOwnerRule,
- getPanpassCodeRelation,
- getProductAttribute, getProductLot,
- getReceivingAsnDetails,
- setProductAttribute, setReceiving,
- } from '@/api/takeDelivery/index'
- import { getListCombineSku } from '@/api/picking'
- import { closeLoading, showLoading } from '@/utils/loading'
- import { useStore } from '@/store/modules/user'
- import { showNotify, showToast,showConfirmDialog} from 'vant'
- import { isAttribute } from '@/views/inbound/takeDelivery/task/hooks/attribute'
- import Attribute from '@/views/inbound/takeDelivery/components/Attribute.vue'
- import LotDate from '@/views/inbound/takeDelivery/components/LotDate.vue'
- import UniqueCodeInput from '@/views/inbound/takeDelivery/components/UniqueCodeInput.vue'
- import BarcodeCombine from '@/views/inbound/takeDelivery/components/BarcodeCombine.vue'
- import { receivingBarcodeCombine } from '@/views/inbound/takeDelivery/task/hooks/barcodeCombine'
- import { barcodeToUpperCase, toMap } from '@/utils/dataType'
- import { getCurrentTime } from '@/utils/date'
- const router = useRouter()
- const store = useStore()
- try {
- getHeader()
- androidFocus()
- } catch (error) {
- router.push('/login')
- }
- const warehouse = store.warehouse
- //开单任务号
- const taskNo = ref('')
- //容器号
- const containerNo = ref('')
- //商品条码
- const searchBarcode = ref('')
- //收货数量
- const searchCount = ref('')
- //收货详情
- const taskInfo = ref({ receivedQty: 0, expectedQty: 0 })
- //开始时间
- const currentTime = ref('--')
- const scanType = ref(2)
- //任务号下所有asn单数据
- const allAsnDetailList=ref([])
- const type=localStorage.getItem('checkAllType')?JSON.parse(localStorage.getItem('checkAllType')):true
- const checkAllType=ref(type)
- // 页面初始化
- onMounted(() => {
- openListener()
- scanInit(_handlerScan)
- loadData()
- })
- /**
- * 计算时分秒
- */
- // 时器的总秒数
- let totalSeconds = ref(0)
- //时分秒
- const formattedTime = ref('00:00:00')
- let windowTimer = null // 计时器的引用
- const updateFormattedTime = () => {
- let hours = Math.floor(totalSeconds.value / 3600)
- let minutes = Math.floor((totalSeconds.value % 3600) / 60)
- let seconds = totalSeconds.value % 60
- formattedTime.value = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
- }
- // 启动计时器
- const startTimer = () => {
- if (!windowTimer) {
- windowTimer = setInterval(() => {
- totalSeconds.value++
- updateFormattedTime()
- }, 1000)
- }
- }
- // 停止计时器
- const stopTimer = () => {
- if (windowTimer) {
- clearInterval(windowTimer)
- windowTimer = null
- }
- }
- const back = ref(true)
- const inputBarcodeType = ref('task')
- const inputBarcodeRef = ref(null)
- const oldSearchBarcode = ref('')
- // 任务号/容器号:code 为任务号时拉取任务及ASN明细
- const setBarcode = (code, type) => {
- if (inputBarcodeType.value === 'lot') {
- lotData.value.forEach((lot) => {
- if (lot.field == lotField.value) {
- lot.mapping = code
- }
- })
- return
- }
- showLoading()
- if(!type){ //切换任务时初始化计时器
- stopTimer()
- formattedTime.value="00:00:00"
- totalSeconds.value=0
- }
- getIReceivingTask({ taskNo:code,version:'V6',warehouse }).then(res=>{
- back.value = true
- const data = res.data
- const applyTaskAfterFetch = () => {
- if (!type) {
- currentTime.value = getCurrentTime()
- startTimer()
- containerNo.value = ''
- }
- taskInfo.value = data
- taskNo.value = code
- scanType.value = 2
- uniqueCodeList.value = []
- parentSerialNoMap.value = {}
- const ownerCode = data?.customerId
- if (ownerCode) {
- getOwnerRule(ownerCode).then((ruleRes) => {
- ownerPanpassEnabled.value = !!ruleRes.data?.panpassEnabled
- }).catch(() => {
- ownerPanpassEnabled.value = false
- })
- } else {
- ownerPanpassEnabled.value = false
- }
- const params = { warehouse, asnNos: taskInfo.value?.asnNos.join(',') }
- getReceivingAsnDetails(params).then((detailRes) => {
- allAsnDetailList.value = detailRes.data
- })
- scanSuccess()
- }
- if (data.receivedQty == data.expectedQty && data.expectedQty > 0) {
- showConfirmDialog({
- title: '温馨提示',
- message: `${data.taskNo}任务已完成收货,是否继续收货?`,
- })
- .then(applyTaskAfterFetch)
- .catch(() => {
- reset()
- taskNo.value = ''
- taskInfo.value = {}
- containerNo.value = ''
- stopTimer()
- allAsnDetailList.value = []
- ownerPanpassEnabled.value = false
- scanType.value = 2
- uniqueCodeList.value = []
- parentSerialNoMap.value = {}
- if (type) {
- switchTask()
- } else {
- inputBarcodeType.value = 'task'
- back.value = true
- inputBarcodeRef.value?.show('', '请扫描开单任务号', '')
- }
- scanSuccess()
- })
- return
- }
- applyTaskAfterFetch()
- }).catch(err=>{
- ownerPanpassEnabled.value = false
- inputBarcodeRef.value?.show('', '请扫描开单任务号',err.message)
- scanError()
- }).finally(()=>{
- reset()
- closeLoading()
- })
- }
- // setBarcode('BSSH20260318000003')
- const switchTask = () => {
- inputBarcodeType.value = 'switchTask'
- back.value = false
- inputBarcodeRef.value?.show('', `请扫描开单任务号`,'')
- }
- //asn单多条明细展示
- const asnDetailsTrueFalseBy = ref(false)
- const asnDetailsList = ref([])
- const asnInfo = ref({})
- // 组合商品
- const barcodeCombineRef = ref(null)
- const combineMatchedSku = ref([])
- const combineAsnSelectList = ref([])
- const isCombineSelectMode = ref(false)
- const combineReceivingData = ref([]) // 确认实收数后暂存,完成收货时提交
- /** 组合商品收货套数 */
- const combineReceivingSetCount = ref(0)
- /** 重置组合收货状态 */
- function resetCombineProductState() {
- barcodeCombineRef.value?.hide?.()
- combineMatchedSku.value = []
- combineAsnSelectList.value = []
- isCombineSelectMode.value = false
- combineReceivingData.value = []
- combineReceivingSetCount.value = 0
- asnDetailsTrueFalseBy.value = false
- }
- /** 超收:多 ASN 选单已打开(无明细造行,选单中) */
- const overReceiveSheetActive = ref(false)
- const showOverReceiveTag = computed(
- () =>
- !!asnInfo.value?.isOverReceive
- || (overReceiveSheetActive.value && !!asnDetailsTrueFalseBy.value),
- )
- const onAsnActionSheetClosed = () => {
- if (overReceiveSheetActive.value && !asnInfo.value?.asnNo) {
- overReceiveSheetActive.value = false
- }
- }
- const reset = () => {
- resetCombineProductState()
- overReceiveSheetActive.value = false
- asnInfo.value = {}
- lotData.value = []
- lotMap.value={}
- searchCount.value = ''
- searchBarcode.value = ''
- oldSearchBarcode.value = ''
- uniqueCodeList.value = []
- parentSerialNoMap.value = {}
- }
- const onDetailActive = (item) => {
- if (isCombineSelectMode.value) {
- _onCombineAsnSelected(item)
- return
- }
- asnInfo.value = item
- overReceiveSheetActive.value = false
- asnDetailsTrueFalseBy.value = false
- searchCount.value=1
- _getProductAttribute(item)
- _getProductLot(item)
- _getCommodityRule(item)
- }
- const onAsnCancel = () => {
- if (searchBarcode.value === '' || (oldSearchBarcode.value.length != searchBarcode.value.length && oldSearchBarcode.value != '')) {
- resetCombineProductState()
- asnInfo.value = {}
- lotData.value = []
- lotMap.value={}
- searchCount.value = ''
- }
- }
- const uniqueCodeList = ref([])
- /** 外箱唯一码 -> 内件唯一码 */
- const parentSerialNoMap = ref({})
- /** 货主规则组合收货需扫外箱码获取内件唯一码 */
- const ownerPanpassEnabled = ref(false)
- /** 获取内件唯一码 */
- function parsePanpassChildCodes(apiRes) {
- const outer = apiRes?.data
- const childPayload =
- outer && typeof outer === 'object' && Array.isArray(outer.childCodes)
- ? outer
- : outer?.data
- if (childPayload == null || typeof childPayload !== 'object') return null
- const fromApi = childPayload.childCodes
- if (!Array.isArray(fromApi) || fromApi.length === 0) return null
- return fromApi.map(String).filter(Boolean)
- }
- function resetUniqueCodeDialogFocus() {
- const dialogCmp = uniqueCodeRef.value
- if (!dialogCmp) return
- dialogCmp.uniqueBarcode = ''
- // 组合收货弹窗内仅展示唯一码输入(条码行隐藏),高亮须保持为 unique
- const combineOnlyUnique = Number(combineReceivingSetCount.value) > 0
- dialogCmp.uniqueCodeScanType = combineOnlyUnique || !checkAllType.value ? 'unique' : 'barcode'
- }
- async function resolvePanpassScan(code) {
- showLoading()
- try {
- const customerId = asnInfo.value.customerId || taskInfo.value.customerId
- if (!taskNo.value || !customerId) {
- showNotify({ type: 'danger', duration: 3000, message: '缺少任务号或货主,无法校验兆信唯一码' })
- scanError()
- return null
- }
- const res = await getPanpassCodeRelation({
- docNo: taskNo.value,
- uniqueCode: code,
- customer: customerId,
- })
- const newPsId = String(res?.data?.data?.newPsId ?? '').trim()
- if (newPsId) {
- const expected = barcodeToUpperCase(newPsId)
- const match = [asnInfo.value.barcode, asnInfo.value.barcode2, asnInfo.value.sku, uniqueCodeRef.value?.uniqueBarcode]
- .filter(Boolean)
- .some((c) => barcodeToUpperCase(String(c)) === expected)
- if (!match) {
- showNotify({ type: 'danger', duration: 3000, message: `商品条码不一致:${newPsId},请检查` })
- scanError()
- return null
- }
- }
- const childCodes = parsePanpassChildCodes(res)
- if (!childCodes?.length) {
- const uniqueRegExp = uniqueRuleMap.value['sku']
- ? uniqueRuleMap.value['sku']
- : uniqueRuleMap.value['all']
- if (uniqueRegExp) {
- const isValidCode = new RegExp(uniqueRegExp).test(code)
- if (!isValidCode) {
- scanError()
- showNotify({ type: 'danger', duration: 3000, message: `唯一码《${code}》不符合规则、请重新扫描` })
- return null
- }
- }
- const prev = uniqueCodeList.value
- if (prev.includes(code)) {
- showNotify({ type: 'danger', duration: 3000, message: `唯一码《${code}》已存在列表内请重新扫描` })
- scanError()
- return null
- }
- if (prev.length + 1 > Number(searchCount.value)) {
- showNotify({ type: 'danger', duration: 3000, message: '唯一码数量超过收货数量' })
- scanError()
- return null
- }
- uniqueCodeList.value = [...prev, code]
- showNotify({ type: 'warning', duration: 3000, message: '未获取到内箱唯一码' })
- scanSuccess()
- return [code]
- }
- const prev = uniqueCodeList.value
- if (childCodes.some((childCode) => prev.includes(childCode))) {
- const duplicateCode = childCodes.find((childCode) => prev.includes(childCode))
- showNotify({ type: 'danger', duration: 3000, message: `唯一码《${duplicateCode}》已存在列表内请重新扫描` })
- scanError()
- return null
- }
- const merged = prev.length + childCodes.length
- if (merged > Number(searchCount.value)) {
- showNotify({ type: 'danger', duration: 3000, message: '唯一码数量超过收货数量' })
- scanError()
- return null
- }
- uniqueCodeList.value = [...prev, ...childCodes]
- const parentCode = String(code).trim()
- const mergedMap = { ...parentSerialNoMap.value }
- for (const cc of childCodes) {
- mergedMap[String(cc)] = parentCode
- }
- parentSerialNoMap.value = mergedMap
- scanSuccess()
- return childCodes
- } catch {
- scanError()
- return null
- } finally {
- closeLoading()
- }
- }
- /** 兆信开启且当前为组合商品:外箱码获取内件唯一码 */
- const isCombinePanpass = computed(
- () => ownerPanpassEnabled.value && combineReceivingData.value.length > 0,
- )
- /**
- * 无 ASN 行明细时(allAsnDetailList 无匹配/为空)的「超收」占位行:不依赖待收货列表校验,由任务号 + 选中 ASN 构造。
- */
- const buildOverReceiveAsnLine = (asnNo, code) => {
- const wh = taskInfo.value.warehouse || warehouse
- const skuU = barcodeToUpperCase(code)
- return {
- warehouse: wh,
- customerId: taskInfo.value.customerId,
- sku: skuU,
- barcode: skuU,
- asnNo,
- expectedQuantity: 0,
- receivedQuantity: 0,
- /** 与正常 ASN 行区分,用于页面「超收」标记与逻辑 */
- isOverReceive: true,
- }
- }
- /**
- * 组合接口查不到时
- */
- const _fallbackReceiveByProductLotProbe = (code) => {
- const { asnNos = [], warehouse, customerId } = taskInfo.value
- const item = { warehouse, customerId, sku: barcodeToUpperCase(code) }
- const continueOverReceiveLine = (line) => {
- overReceiveSheetActive.value = false
- asnInfo.value = line
- searchCount.value = 1
- _getProductAttribute(line)
- _getProductLot(line)
- _getCommodityRule(line)
- closeLoading()
- scanSuccess()
- }
- _getProductLot(item)
- .then(() => {
- if (lotData.value.length === 0) {
- closeLoading()
- scanError()
- return
- }
- if (asnNos.length === 0) {
- closeLoading()
- scanError()
- showNotify({ type: 'danger', duration: 3000, message: '任务无 ASN 单,无法超收' })
- return
- }
- if (asnNos.length > 1) {
- const synthetic = asnNos.map((asnNo) => buildOverReceiveAsnLine(asnNo, code))
- asnDetailsList.value = synthetic
- asnInfo.value = {}
- lotData.value = []
- lotMap.value = {}
- searchCount.value = ''
- uniqueCodeList.value = []
- parentSerialNoMap.value = {}
- overReceiveSheetActive.value = true
- asnDetailsTrueFalseBy.value = true
- closeLoading()
- scanSuccess()
- return
- }
- continueOverReceiveLine(buildOverReceiveAsnLine(asnNos[0], code))
- })
- .catch(() => {
- closeLoading()
- scanError()
- })
- }
- // 组合商品只支持1个
- const _handleCombineProduct = (code) => {
- showLoading()
- getListCombineSku({ combineSku: barcodeToUpperCase(code), workEnvironment: 'receiving' }).then((res) => {
- const _err = (msg) => { closeLoading(); scanError(); showNotify({ type: 'danger', duration: 3000, message: msg }); reset() }
- if (!res.data?.length) return _fallbackReceiveByProductLotProbe(code)
- if (res.data.length > 1) return _err('不支持多商品组合商品')
- const combineData = res.data
- const matchedList = receivingBarcodeCombine(allAsnDetailList.value, toMap(combineData, 'barcode'))
- if (!matchedList.length) return _fallbackReceiveByProductLotProbe(code)
- const asnGroupMap = matchedList.reduce((acc, detail) => {
- const key = detail.asnNo
- if (!acc[key]) acc[key] = { asnNo: detail.asnNo, customerId: detail.customerId, expectedQuantity: 0, list: [] }
- acc[key].list.push(detail)
- acc[key].expectedQuantity += (detail.expectedQuantity || 0) - (detail.receivedQuantity || 0)
- return acc
- }, {})
- const asnOptions = Object.values(asnGroupMap)
- if (asnOptions.length > 1) {
- isCombineSelectMode.value = true
- combineAsnSelectList.value = asnOptions
- combineMatchedSku.value = matchedList
- asnDetailsList.value = asnOptions.map((opt) => ({ asnNo: opt.asnNo, customerId: opt.customerId, expectedQuantity: opt.expectedQuantity }))
- asnDetailsTrueFalseBy.value = true
- } else {
- _showCombineDialog(matchedList)
- }
- closeLoading()
- scanSuccess()
- }).catch(() => { closeLoading(); scanError() })
- }
- const _showCombineDialog = (matchedList) => {
- combineMatchedSku.value = matchedList
- asnInfo.value = matchedList[0]
- _getProductAttribute(matchedList[0])
- _getProductLot(matchedList[0])
- _getCommodityRule(matchedList[0])
- barcodeCombineRef.value?.show()
- }
- // 组合商品取消:收货数量=1套总件数
- const onCombineCancel = () => {
- const total = combineMatchedSku.value.reduce((sum, row) => sum + (row.matchedJson?.quantity || 0), 0)
- searchCount.value = total ? String(total) : '1'
- combineReceivingData.value = []
- combineReceivingSetCount.value = 0
- }
- const _onCombineAsnSelected = (item) => {
- const selected = combineAsnSelectList.value.find((opt) => opt.asnNo === item.asnNo)
- if (selected?.list) _showCombineDialog(selected.list)
- asnDetailsTrueFalseBy.value = isCombineSelectMode.value = false
- combineAsnSelectList.value = []
- }
- // 组合商品确认实收数
- const setCombineReceiving = ({ dataList, count: setCount }) => {
- if (!dataList?.length) return
- const total = dataList.reduce((sum, row) => sum + (row.quantity || 0), 0)
- searchCount.value = String(total)
- combineReceivingData.value = dataList
- const sets = Number(setCount) > 0 ? Number(setCount) : 0
- combineReceivingSetCount.value = sets
- showNotify({ type: 'success', duration: 2000, message: `收货套数:${sets},共 ${total} 件,请点击完成收货提交` })
- }
- /** 批次拉取与效期计算的代际,避免快速切换商品时异步结果错乱 */
- let productLotGeneration = 0
- // 条码扫描:scanType 2商品/4数量/3唯一码/5容器
- const _handlerScan = (code) => {
- if (scanType.value == 2) {
- searchBarcode.value = code
- oldSearchBarcode.value = code
- resetCombineProductState()
- overReceiveSheetActive.value = false
- const upperCode = barcodeToUpperCase(code) || ''
- const clientMatched =
- allAsnDetailList.value.length > 0
- ? allAsnDetailList.value.filter((detail) => {
- const bars = [detail.barcode, detail.barcode2, detail.sku].filter(Boolean)
- return bars.some((bar) => bar && barcodeToUpperCase(bar) === upperCode)
- })
- : []
- asnDetailsList.value = clientMatched
- uniqueCodeList.value=[]
- parentSerialNoMap.value = {}
- if (asnDetailsList.value.length > 0) {
- scanSuccess()
- closeLoading()
- if (asnDetailsList.value.length == 1) {
- const item = asnDetailsList.value[0]
- asnInfo.value = item
- searchCount.value = 1
- _getProductAttribute(item)
- _getProductLot(item)
- _getCommodityRule(item)
- }
- if (asnDetailsList.value.length > 1) {
- asnInfo.value = {}
- lotData.value = []
- lotMap.value={}
- searchCount.value = ''
- uniqueCodeList.value = []
- parentSerialNoMap.value = {}
- asnDetailsTrueFalseBy.value = true
- }
- } else {
- _handleCombineProduct(code)
- }
- } else if (scanType.value == 3) {
- if (code) {
- const uniqueCodeScanType = uniqueCodeRef.value?.uniqueCodeScanType
- if (checkAllType.value && uniqueCodeScanType === 'barcode') {
- const barcode = Array.from(new Set([asnInfo.value.barcode, asnInfo.value.barcode2, asnInfo.value.sku].filter(Boolean)));
- if (barcode.some((bar) => barcodeToUpperCase(bar) === barcodeToUpperCase(code))) {
- scanSuccess();
- uniqueCodeRef.value.uniqueCodeScanType = 'unique'
- uniqueCodeRef.value.uniqueBarcode = code
- return;
- } else {
- showNotify({ type: 'danger', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
- uniqueCodeRef.value.uniqueBarcode = ''
- scanError();
- return;
- }
- }
- // 唯一码扫描
- if (
- !isCombinePanpass.value &&
- uniqueCodeScanType === 'unique' &&
- uniqueCodeRef.value?.uniqueBarcode == '' &&
- checkAllType.value
- ) {
- showNotify({ type: 'danger', duration: 3000, message: '请先扫描商品条码' });
- uniqueCodeRef.value.uniqueCodeScanType = 'barcode';
- scanError();
- return;
- }
- if (isCombinePanpass.value) {
- void resolvePanpassScan(code).then((panpassResult) => panpassResult != null && resetUniqueCodeDialogFocus())
- return
- }
- const uniqueRegExp = uniqueRuleMap.value['sku'] ? uniqueRuleMap.value['sku'] : uniqueRuleMap.value['all']
- const isValidCode = new RegExp(uniqueRegExp).test(code)
- if (!isValidCode) {
- scanError()
- showNotify({ type: 'danger', duration: 3000, message: `唯一码《${code}》不符合规则、请重新扫描` })
- return
- }
- if (uniqueCodeList.value.includes(code)) {
- scanError()
- showNotify({ type: 'danger', duration: 3000, message: `唯一码《${code}》已存在列表内请重新扫描` })
- return
- }
- scanSuccess()
- uniqueCodeList.value.unshift(code)
- resetUniqueCodeDialogFocus()
- }
- }else if(scanType.value==5){
- containerNo.value=code
- scanType.value=2
- }
- }
- // 物理属性
- const attributeRef = ref(null)
- const attributeMap = ref({})
- const attributeTrueFalseBy = ref(true)
- // 获取商品物理属性
- const _getProductAttribute = (item) => {
- const params = { warehouse: item.warehouse, owner: item.customerId, barcode: item.sku }
- getProductAttribute(params).then(res => {
- attributeMap.value = res.data
- const isAttributeInfo = isAttribute(res.data)
- if (isAttributeInfo.length > 0) {
- attributeTrueFalseBy.value = false
- attributeRef.value?.show(isAttributeInfo, res.data)
- } else {
- attributeTrueFalseBy.value = true
- }
- })
- }
- // 设置商品物理属性
- const setAttribute = (data) => {
- const params = { warehouse, owner: taskInfo.value.customerId, sku: attributeMap.value.sku }
- showLoading()
- setProductAttribute(params, { ...attributeMap.value, ...data }).then(res => {
- showToast({ duration: 3000, message: '商品物理属性设置成功' })
- attributeTrueFalseBy.value = true
- scanSuccess()
- }).catch(err => {
- closeLoading()
- scanError()
- })
- }
- // 批次属性
- const lotData = ref([])
- const lotMap = ref({})
- const _getProductLot = (item) => {
- const gen = ++productLotGeneration
- lotMap.value = {}
- lotData.value = []
- const params = { warehouse: item.warehouse, owner: item.customerId, barcode: item.sku }
- return getProductLot(params).then((res) => {
- if (gen !== productLotGeneration) return
- const rows = res.data
- if (!rows?.length) {
- lotData.value = []
- return
- }
- rows.forEach((lot) => {
- const lotField = lot.field
- if (lotField.startsWith('lotAtt') && lotField.length === 8) {
- lot.mapping = item[lotField]
- }
- if (lot.format) {
- const format = JSON.parse(lot.format)
- lotMap.value = { ...lotMap.value, ...format }
- }
- })
- lotData.value = rows.filter((row) => row.lotAttFlag !== '隐藏')
- _calculateShelfLife(item, lotData.value, gen)
- })
- }
- // 计算效期
- const _calculateShelfLife = (item, lotData, generation) => {
- if ((item.lotAtt01 && item.lotAtt02) || (!item.lotAtt01 && !item.lotAtt02)) return
- const params = {
- customer: item.customerId,
- barcode: item.sku,
- date: item.lotAtt01 || item.lotAtt02,
- isExpiryDate: item.lotAtt01 ? true : false,
- }
- calculateShelfLife(params).then((date) => {
- if (generation !== productLotGeneration) return
- lotData.forEach((lot) => {
- if ((lot.field === 'lotAtt01' && !params.isExpiryDate) ||
- (lot.field === 'lotAtt02' && params.isExpiryDate)) {
- lot.mapping = date.data
- }
- })
- })
- }
- //日期选择
- const lotField = ref('')
- const lotDateRef = ref(null)
- const lotQualityTrueFalseBy=ref(false)
- const lotQualityMap=ref({})
- const lotTitle=ref('质量状态')
- const onLot = (item) => {
- lotTitle.value=item.label
- lotField.value = item.field
- if (item.field == 'lotAtt05' ) return
- if (item.type == 'Enum' ){
- lotQualityMap.value=JSON.parse(item.format)
- lotQualityTrueFalseBy.value = true
- return
- }
- if (item.type == 'Date') {
- lotDateRef.value?.show(item.label, item.mapping)
- return
- }
- if (item.type == 'String') {
- inputBarcodeType.value = 'lot'
- back.value = false
- inputBarcodeRef.value?.show('', `请扫描${item.label}`,'')
- return
- }
- }
- //设置质量状态
- const onSelectLotQuality=(qualityKey)=>{
- lotData.value.forEach((lot) => {
- if (lot.field == lotField.value) {
- lot.mapping = qualityKey
- }
- })
- lotQualityTrueFalseBy.value=false
- }
- // 设置日期
- const selectLotDate = (date) => {
- const lotMap = toMap(lotData.value, 'field', 'mapping')
- if ((lotField.value === 'lotAtt01' || lotField.value === 'lotAtt02') &&
- lotMap['lotAtt01'] == '' || lotMap['lotAtt01'] == null && lotMap['lotAtt02'] == '' || lotMap['lotAtt02'] == null) {
- const params = {
- customer: asnInfo.value.customerId,
- barcode: asnInfo.value.sku,
- date,
- isExpiryDate: lotField.value === 'lotAtt01' ? true : false,
- }
- calculateShelfLife(params).then(res => {
- if(res.data){
- lotData.value.forEach((lot) => {
- if (lot.field === 'lotAtt01') {
- lot.mapping = (lotField.value === 'lotAtt01') ? date : res.data
- } else if (lot.field === 'lotAtt02') {
- lot.mapping = (lotField.value === 'lotAtt02') ? date : res.data
- }
- })
- }else {
- lotData.value.forEach((lot) => {
- if (lot.field === lotField.value) {
- lot.mapping = date
- }
- })
- }
- })
- }
- lotData.value.forEach((lot) => {
- if (lot.field === lotField.value) {
- lot.mapping = date
- }
- })
- inputBarcodeType.value = 'task'
- }
- // 唯一码
- const uniqueCodeRef = ref(null)
- //规则列表
- const uniqueRuleList = ref([])
- const uniqueRuleMap = ref({})
- // 获取商品规则
- const _getCommodityRule = (item) => {
- const params = { customer: item.customerId, sku: item.sku, input: 'RECEIVING' }
- getCommodityRule(params).then(res => {
- uniqueRuleList.value = res.data
- res.data.forEach((item, index) => {
- if (item.sku == '') {
- item.type = 'all'
- } else {
- item.type = 'sku'
- }
- })
- uniqueRuleMap.value = toMap(res.data, 'type', 'uniqueRegExp')
- }).catch(() => {
- closeLoading()
- scanError()
- })
- }
- const containerNoRef = ref(null)
- const numberRef = ref(null)
- // 完成收货前校验
- const isCheck = () => {
- if (!asnInfo.value.asnNo) {
- scanError()
- showToast({ duration: 3000, message: '请先查询商品收货信息' })
- return false
- }
- //商品物理属性判断
- if (!attributeTrueFalseBy.value) {
- const isAttributeInfo = isAttribute(attributeMap.value)
- if (isAttributeInfo.length > 0) {
- attributeTrueFalseBy.value = false
- attributeRef.value?.show(isAttributeInfo, attributeMap.value)
- }
- return false
- }
- // //商品批次属性判断(超收不校验 lotAtt05 必填)
- const incompleteLot = lotData.value.find(
- (lot) =>
- lot.require &&
- !lot.mapping &&
- !(asnInfo.value.isOverReceive && lot.field === 'lotAtt05'),
- )
- if (incompleteLot) {
- scanError()
- showToast({ duration: 3000, message: `请先补充${incompleteLot.label}` })
- return false
- }
- const lotMap = toMap(lotData.value, 'field', 'mapping')
- const productionDate = lotMap['lotAtt01'] ? new Date(lotMap['lotAtt01']) : null
- const expirationDate = lotMap['lotAtt02'] ? new Date(lotMap['lotAtt02']) : null
- const currentDate = new Date()
- // 如果存在失效日期
- if (expirationDate) {
- // 如果有生产日期,进行有效性检查
- if (productionDate && expirationDate <= productionDate) {
- scanError()
- showToast({ duration: 3000, message: `失效日期不能小于等于生产日期` })
- return false
- }
- }
- if(productionDate){
- // 如果有生产日期,进行有效性检查
- if (expirationDate && productionDate >= expirationDate) {
- scanError()
- showToast({ duration: 3000, message: `生产日期不能大于失效日期` })
- return false
- }
- // 检查生产日期是否小于当前日期
- if (productionDate >= currentDate) {
- scanError()
- showToast({ duration: 3000, message: `生产日期不能大于等于当前日期` })
- return false
- }
- }
- if (searchCount.value == '') {
- numberRef.value?.focus()
- scanError()
- showToast({ duration: 3000, message: '请先输入收货数量' })
- return false
- }
- if (containerNo.value == '') {
- scanError()
- containerNoRef.value?.focus()
- showToast({ duration: 3000, message: '请先输入容器号' })
- return false
- }
- const needUnique = uniqueRuleList.value.length > 0 || isCombinePanpass.value
- if (needUnique && uniqueCodeList.value.length != searchCount.value) {
- scanType.value = 3
- const tips = isCombinePanpass.value
- ? `收货数量:${searchCount.value},请扫描组合外箱唯一码`
- : `收货数量:${searchCount.value}`
- uniqueCodeRef.value?.show('', '请扫描唯一码', tips, uniqueRuleMap.value)
- return false
- }
- return true
- }
- // 完成收货
- const onConfirm = (confirmOverReceive=false) => {
- if(isCheck()){
- const lotMap = toMap(lotData.value, 'field', 'mapping')
- const { asnLineNo, asnNo, warehouse,customerId,sku} = asnInfo.value
- const {taskNo: taskCode } = taskInfo.value
- const serialParentMap = parentSerialNoMap.value
- const data = {
- asnLineNo,
- asnNo,
- containerId: containerNo.value,
- quantity: searchCount.value,
- warehouse,
- customerId,
- serialNos: uniqueCodeList.value.length > 0 ? uniqueCodeList.value : undefined,
- ...(Object.keys(serialParentMap).length > 0 ? { parentSerialNoMap: { ...serialParentMap } } : {}),
- ...lotMap,
- taskNo:taskCode,
- sku,
- confirmOverReceive
- }
- showLoading()
- inputBarcodeType.value='task'
- setReceiving(data).then(res => {
- console.log('res',res)
- if(res.data.overReceive){
- scanError()
- showConfirmDialog({
- title: '温馨提示',
- message: '是否确认超收?',
- }).then(() => {
- onConfirm(true)
- }).catch(() => {
- })
- }else{
- scanSuccess()
- showNotify({ type: 'success', duration: 3000, message: `${searchBarcode.value}收货完成,请继续收货!`})
- setBarcode(taskNo.value, '2')
- reset()
- taskInfo.value={}
- scanType.value=2
- closeLoading()
- }
- }).catch(err => {
- if(err.message.includes('序列号已存在')){
- scanType.value = 3
- uniqueCodeRef.value?.show('', '请扫描唯一码', `收货数量:${searchCount.value},${err.message}`, uniqueRuleMap.value)
- }
- scanError()
- closeLoading()
- }).finally(() => {
- closeLoading()
- })
- }
- }
- // 数据刷新
- const loadData = () => {
- if (!taskNo.value) {
- inputBarcodeRef.value?.show('', '请扫描开单任务号','')
- return
- }
- }
- onUnmounted(() => {
- closeListener()
- stopTimer()
- })
- window.onRefresh = loadData
- </script>
- <style scoped lang="scss">
- .over-receive-hint {
- display: flex;
- align-items: center;
- gap: 8px;
- margin: 8px 10px 0;
- padding: 8px 10px;
- background: #fff4e5;
- border: 1px solid #f5d4a3;
- border-radius: 6px;
- font-size: 13px;
- color: #8a5a00;
- line-height: 1.4;
- }
- .over-receive-hint__badge {
- flex-shrink: 0;
- padding: 2px 8px;
- font-size: 12px;
- font-weight: 600;
- color: #fff;
- background: linear-gradient(135deg, #e67e22, #d35400);
- border-radius: 4px;
- }
- .over-receive-hint__text {
- flex: 1;
- min-width: 0;
- text-align: left;
- }
- .take-delivery {
- .take-info {
- padding: 6px 10px;
- background: linear-gradient(160deg, #cfe1ff 20%, white 50%, white 100%);
- display: flex;
- flex-direction: column;
- text-align: left;
- .take-info-no {
- flex: 1;
- .info-no-title {
- font-size: 19px;
- font-weight: 500;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
- .info-no-tips {
- font-size: 14px;
- color: #666666;
- display: flex;
- justify-content: space-between;
- padding: 6px 0;
- }
- }
- .take-info-number {
- flex: 1;
- border-top: 1.5px solid #efefef;
- display: flex;
- justify-content: space-between;
- gap: 10px;
- color: #666;
- font-size: 14px;
- padding-top: 10px;
- .info-number-left {
- flex: 1;
- display: flex;
- justify-content: space-evenly;
- align-items: center;
- .number-left-box {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- .left-box-title {
- font-size: 14px;
- font-weight: bold;
- color: #000;
- line-height: 34px;
- }
- }
- }
- .info-number-right {
- width: 40%;
- text-align: center;
- .van-search {
- padding: 0;
- }
- }
- }
- }
- .take-barcode {
- margin-top: 10px;
- text-align: left;
- background: #ffffff;
- .barcode-input {
- height: 50px;
- ::v-deep(.van-search) {
- padding: 0;
- }
- ::v-deep(.van-search__field) {
- border-bottom: 2px solid #ffffff;
- height: 50px;
- display: flex;
- align-items: center;
- }
- ::v-deep(.van-search__content) {
- background: #fff;
- height: 50px;
- display: flex;
- align-items: center;
- }
- ::v-deep(.van-search__label) {
- font-size: 16px;
- font-weight: bold;
- line-height: 50px;
- height: 50px;
- display: flex;
- align-items: center;
- }
- ::v-deep(.van-field__control) {
- font-size: 16px;
- font-weight: bold;
- height: 50px;
- line-height: 50px;
- display: flex;
- align-items: center;
- }
- .search-input-barcode {
- ::v-deep(.van-search__field) {
- border-bottom: 2px solid #0077ff;
- z-index: 2;
- }
- }
- }
- }
- .take-lot {
- text-align: left;
- margin-top: 5px;
- ::v-deep(.van-cell) {
- padding: 5px 8px;
- }
- .take-lot-title {
- font-size: 15px;
- font-weight: bold;
- padding: 0 5px;
- border-left: 3px solid #1989fa;
- color: #333;
- margin-bottom: 3px;
- }
- }
- .take-button {
- padding: 20px;
- }
- }
- </style>
|