index.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. <template>
  2. <div class="container">
  3. <van-nav-bar title="宝时丰收" left-arrow fixed placeholder @click-left="goBack">
  4. <template #left>
  5. <van-icon name="arrow-left" size="25" />
  6. <div style="color: #fff">返回</div>
  7. </template>
  8. </van-nav-bar>
  9. <div class="take-delivery">
  10. <div class="take-info">
  11. <div class="take-info-no">
  12. <div class="info-no-title">
  13. <div>任务号:{{ taskInfo.taskNo || '--' }}</div>
  14. <div>
  15. <van-button type="primary" size="mini" plain @click="switchTask">切换任务</van-button>
  16. </div>
  17. </div>
  18. <div class="info-no-tips">
  19. <div>货主:<span style="color: #333;font-weight: bold;">{{ taskInfo.customerName || '--' }}</span></div>
  20. <div>任务数:<span style="color: #0077ff;font-weight: bold;">{{ taskInfo.receivedQty || 0 }}/{{ taskInfo.expectedQty || 0}}</span></div>
  21. </div>
  22. </div>
  23. <div class="take-info-number">
  24. <div class="info-number-left">
  25. <div class="number-left-box">
  26. <div>开始时间</div>
  27. <div class="left-box-title">{{ currentTime }}</div>
  28. </div>
  29. <div class="number-left-box">
  30. <div>已用时</div>
  31. <div class="left-box-title">{{ formattedTime }}</div>
  32. </div>
  33. </div>
  34. <div class="info-number-right">
  35. <div>容器号</div>
  36. <div>
  37. <van-search
  38. ref="containerNoRef"
  39. v-model="containerNo"
  40. left-icon=""
  41. placeholder="请扫描容器号"
  42. autocomplete="off"
  43. @search="isCheck()"
  44. @focus="scanType=5"
  45. @blur="scanType=2"
  46. ></van-search>
  47. </div>
  48. </div>
  49. </div>
  50. </div>
  51. <van-progress v-if="taskInfo.receivedQty/taskInfo.expectedQty>=0" :percentage="((taskInfo.receivedQty/taskInfo.expectedQty)*100).toFixed(2)" stroke-width="4" />
  52. <div class="take-barcode">
  53. <div class="barcode-input">
  54. <van-search
  55. ref="searchRef"
  56. v-model="searchBarcode"
  57. placeholder="请扫描商品条码"
  58. @search="_handlerScan(searchBarcode)"
  59. label="商品条码:"
  60. left-icon=""
  61. :class="[scanType===2?'search-input-barcode':'','van-hairline--bottom']"
  62. @focus="scanType=2"
  63. autocomplete="off"
  64. @input="onAsnCancel"
  65. @clear="reset"
  66. >
  67. </van-search>
  68. </div>
  69. <div class="barcode-input">
  70. <van-search
  71. ref="numberRef"
  72. v-model="searchCount"
  73. placeholder="请输入收货数量"
  74. type="number"
  75. label="收货数量:"
  76. left-icon=""
  77. autocomplete="off"
  78. show-action
  79. :min="1"
  80. :max="asnInfo.asnNo?asnInfo.expectedQuantity-asnInfo.receivedQuantity:10000"
  81. @search="isCheck()"
  82. :class="[scanType===4?'search-input-barcode':'','van-hairline--bottom']"
  83. @focus="scanType=4"
  84. >
  85. <template #action>
  86. <div style="display: flex; align-items: center;flex-direction: column;margin-left: 20px" v-if="asnInfo.asnNo">
  87. <div style="height: 20px;font-size: 12px">已收/预计</div>
  88. <div style="font-size: 16px;font-weight: bold">{{ asnInfo.receivedQuantity}}/{{ asnInfo.expectedQuantity }}
  89. </div>
  90. </div>
  91. </template>
  92. </van-search>
  93. </div>
  94. </div>
  95. <div class="take-lot" v-if="lotData.length>0">
  96. <van-cell-group>
  97. <div class="take-lot-title">批次信息</div>
  98. <van-cell v-for="(item,i) in lotData" :key="i" :is-link="item.field!=='lotAtt05'"
  99. @click="onLot(item)">
  100. <template #title>
  101. <van-icon v-if="item.require" name="warning-o" color="#ed6a0c" />
  102. <span class="custom-title">{{ item.label }}</span>
  103. </template>
  104. <template #value>
  105. <div>{{ item.mapping }}</div>
  106. </template>
  107. </van-cell>
  108. </van-cell-group>
  109. </div>
  110. <div class="take-button">
  111. <van-button type="primary" size="large" round style="height: 36px" @click="onConfirm">完成收货</van-button>
  112. </div>
  113. </div>
  114. </div>
  115. <!-- 条码输入组件 -->
  116. <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
  117. <!-- 单据选择-->
  118. <van-action-sheet v-model:show="asnDetailsTrueFalseBy" cancel-text="取消" description="请选择具体单据" close-on-click-action>
  119. <van-cell-group>
  120. <van-cell v-for="item in asnDetailsList" @click="onDetailActive(item)">
  121. <template #title>
  122. {{ item.asnNo }}({{ item.customerId }}-{{ item.expectedQuantity }}件)
  123. </template>
  124. </van-cell>
  125. </van-cell-group>
  126. </van-action-sheet>
  127. <!-- 商品物理属性-->
  128. <attribute ref="attributeRef" @set-attribute="setAttribute" />
  129. <!-- 商品批次属性-->
  130. <lot-date ref="lotDateRef" @select-lot-date="selectLotDate" />
  131. <!-- 唯一码-->
  132. <unique-code-input ref="uniqueCodeRef"
  133. v-model:uniqueCodeList="uniqueCodeList"
  134. v-model:scanType="scanType"
  135. v-model:checkAllType="checkAllType"
  136. :searchCount="searchCount"
  137. :asnInfo="asnInfo"
  138. @setUniqueCode="onConfirm"
  139. />
  140. <van-action-sheet
  141. v-model:show="lotQualityTrueFalseBy"
  142. cancel-text="取消"
  143. close-on-click-action
  144. description="请选择质量状态"
  145. >
  146. <van-cell-group>
  147. <van-cell v-for="(value,key) in lotQualityMap" @click="onSelectLotQuality(key)">
  148. <template #title>
  149. {{value}}
  150. </template>
  151. </van-cell>
  152. </van-cell-group>
  153. </van-action-sheet>
  154. </template>
  155. <script setup>
  156. import { onMounted, onUnmounted, ref, computed } from 'vue'
  157. import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
  158. import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
  159. import { closeListener, openListener, scanInit } from '@/utils/keydownListener.js'
  160. import { useRouter } from 'vue-router'
  161. import {
  162. calculateShelfLife, getCommodityRule,
  163. getIReceivingTask,
  164. getProductAttribute, getProductLot,
  165. getReceivingAsnDetails,
  166. setProductAttribute, setReceiving,
  167. } from '@/api/takeDelivery/index'
  168. import { closeLoading, showLoading } from '@/utils/loading'
  169. import { useStore } from '@/store/modules/user'
  170. import { showNotify, showToast } from 'vant'
  171. import { isAttribute } from '@/views/inbound/TakeDelivery/task/hooks/attribute'
  172. import Attribute from '@/views/inbound/TakeDelivery/components/Attribute.vue'
  173. import LotDate from '@/views/inbound/TakeDelivery/components/LotDate.vue'
  174. import UniqueCodeInput from '@/views/inbound/TakeDelivery/components/UniqueCodeInput.vue'
  175. import { barcodeToUpperCase, toMap } from '@/utils/dataType.js'
  176. import { getCurrentTime } from '@/utils/date'
  177. const router = useRouter()
  178. const store = useStore()
  179. try {
  180. getHeader()
  181. androidFocus()
  182. } catch (error) {
  183. router.push('/login')
  184. }
  185. const warehouse = store.warehouse
  186. //开单任务号
  187. const taskNo = ref('')
  188. //容器号
  189. const containerNo = ref('')
  190. //商品条码
  191. const searchBarcode = ref('')
  192. //收货数量
  193. const searchCount = ref('')
  194. //收货详情
  195. const taskInfo = ref({ receivedQty: 0, expectedQty: 0 })
  196. //开始时间
  197. const currentTime = ref('--')
  198. const scanType = ref(2)
  199. const type=localStorage.getItem('checkAllType')?JSON.parse(localStorage.getItem('checkAllType')):true
  200. const checkAllType=ref(type)
  201. // 页面初始化
  202. onMounted(() => {
  203. openListener()
  204. scanInit(_handlerScan)
  205. loadData()
  206. })
  207. /**
  208. * 计算时分秒
  209. */
  210. // 时器的总秒数
  211. let totalSeconds = ref(0)
  212. //时分秒
  213. const formattedTime = ref('00:00:00')
  214. let windowTimer = null // 计时器的引用
  215. const updateFormattedTime = () => {
  216. let hours = Math.floor(totalSeconds.value / 3600)
  217. let minutes = Math.floor((totalSeconds.value % 3600) / 60)
  218. let seconds = totalSeconds.value % 60
  219. formattedTime.value = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
  220. }
  221. // 启动计时器
  222. const startTimer = () => {
  223. if (!windowTimer) {
  224. windowTimer = setInterval(() => {
  225. totalSeconds.value++
  226. updateFormattedTime()
  227. }, 1000)
  228. }
  229. }
  230. // 停止计时器
  231. const stopTimer = () => {
  232. if (windowTimer) {
  233. clearInterval(windowTimer)
  234. windowTimer = null
  235. }
  236. }
  237. const back = ref(true)
  238. const inputBarcodeType = ref('task')
  239. //输入框组件
  240. const inputBarcodeRef = ref(null)
  241. const oldSearchBarcode = ref('')
  242. // 设置容器号
  243. const setBarcode = (code, type) => {
  244. if (inputBarcodeType.value === 'lot') {
  245. lotData.value.forEach((lot) => {
  246. if (lot.field == lotField.value) {
  247. lot.mapping = code
  248. }
  249. })
  250. return
  251. }
  252. showLoading()
  253. if(!type){ //切换任务时初始化计时器
  254. stopTimer()
  255. formattedTime.value="00:00:00"
  256. totalSeconds.value=0
  257. }
  258. getIReceivingTask({ taskNo:code,version:'V6' }).then(res=>{
  259. back.value = true
  260. if(res.data.receivedQty==res.data.expectedQty && res.data.expectedQty>0 ){
  261. if(type){
  262. reset()
  263. taskNo.value=''
  264. taskInfo.value={}
  265. switchTask()
  266. }else {
  267. taskInfo.value=res.data
  268. taskNo.value=code
  269. }
  270. containerNo.value=''
  271. stopTimer()
  272. }else {
  273. if(!type){//切换任务成功重启计时器
  274. currentTime.value=getCurrentTime()
  275. startTimer()
  276. containerNo.value=''
  277. }
  278. taskInfo.value=res.data
  279. taskNo.value=code
  280. }
  281. scanType.value=2
  282. uniqueCodeList.value=[]
  283. scanSuccess()
  284. }).catch(err=>{
  285. inputBarcodeRef.value?.show('', '请扫描开单任务号',err.message)
  286. scanError()
  287. }).finally(()=>{
  288. reset()
  289. closeLoading()
  290. })
  291. }
  292. // setBarcode('BSSH20250605000006')
  293. //切换任务
  294. const switchTask = () => {
  295. inputBarcodeType.value = 'switchTask'
  296. back.value = false
  297. inputBarcodeRef.value?.show('', `请扫描开单任务号`,'')
  298. }
  299. //asn单多条明细展示
  300. const asnDetailsTrueFalseBy = ref(false)
  301. const asnDetailsList = ref([])
  302. const asnInfo = ref({})
  303. const reset = () => {
  304. asnInfo.value = {}
  305. lotData.value = []
  306. searchCount.value = ''
  307. searchBarcode.value = ''
  308. oldSearchBarcode.value = ''
  309. uniqueCodeList.value = []
  310. }
  311. // 选择单据
  312. const onDetailActive = (item) => {
  313. asnInfo.value = item
  314. asnDetailsTrueFalseBy.value = false
  315. searchCount.value=1
  316. // searchCount.value = asnInfo.value.expectedQuantity - asnInfo.value.receivedQuantity
  317. _getProductAttribute(item)
  318. _getProductLot(item)
  319. _getCommodityRule(item)
  320. }
  321. const onAsnCancel = () => {
  322. if (searchBarcode.value === '' || (oldSearchBarcode.value.length != searchBarcode.value.length && oldSearchBarcode.value != '')) {
  323. asnInfo.value = {}
  324. lotData.value = []
  325. searchCount.value = ''
  326. }
  327. }
  328. const uniqueCodeList = ref([])
  329. // 扫描条码监听
  330. const _handlerScan = (code) => {
  331. if (scanType.value == 2) {
  332. searchBarcode.value = code
  333. oldSearchBarcode.value = code
  334. const params = { warehouse, barcode: code, asnNos: taskInfo.value?.asnNos.join(',') }
  335. showLoading()
  336. getReceivingAsnDetails(params).then(res => {
  337. uniqueCodeList.value=[]
  338. asnDetailsList.value = res.data
  339. if (res.data.length > 0) {
  340. scanSuccess()
  341. closeLoading()
  342. if (res.data.length == 1) {
  343. const item = res.data[0]
  344. asnInfo.value = item
  345. // searchCount.value = item.expectedQuantity - item.receivedQuantity
  346. searchCount.value=1
  347. _getProductAttribute(item)
  348. _getProductLot(item)
  349. _getCommodityRule(item)
  350. }
  351. if (res.data.length > 1) {
  352. asnInfo.value = {}
  353. lotData.value = []
  354. searchCount.value = ''
  355. uniqueCodeList.value = []
  356. asnDetailsTrueFalseBy.value = true
  357. }
  358. } else {
  359. scanError()
  360. showNotify({ type: 'danger', duration: 3000, message: `暂未查询到条码《${code}》信息请重试` })
  361. reset()
  362. closeLoading()
  363. }
  364. }).catch(() => {
  365. scanError()
  366. closeLoading()
  367. })
  368. } else if (scanType.value == 3) {
  369. if (code) {
  370. const uniqueCodeScanType = uniqueCodeRef.value?.uniqueCodeScanType
  371. if (checkAllType.value && uniqueCodeScanType === 'barcode') {
  372. const barcode = Array.from(new Set([asnInfo.value.barcode, asnInfo.value.barcode2, asnInfo.value.sku].filter(Boolean)));
  373. if (barcode.some(item => barcodeToUpperCase(item) === barcodeToUpperCase(code))) {
  374. scanSuccess();
  375. uniqueCodeRef.value.uniqueCodeScanType = 'unique'
  376. uniqueCodeRef.value.uniqueBarcode = code
  377. return;
  378. } else {
  379. showNotify({ type: 'danger', duration: 3000, message: `${code}-商品条码不匹配,请重新扫描` })
  380. uniqueCodeRef.value.uniqueBarcode = ''
  381. scanError();
  382. return;
  383. }
  384. }
  385. // 如果是通过唯一码扫描,并且没有扫描条码
  386. if (uniqueCodeScanType === 'unique' && ( uniqueCodeRef.value.uniqueBarcode == '' && checkAllType.value)) {
  387. showNotify({ type: 'danger', duration: 3000, message: '请先扫描商品条码' });
  388. uniqueCodeRef.value.uniqueCodeScanType = 'barcode';
  389. scanError();
  390. return;
  391. }
  392. const uniqueRegExp = uniqueRuleMap.value['sku'] ? uniqueRuleMap.value['sku'] : uniqueRuleMap.value['all']
  393. const isValidCode = new RegExp(uniqueRegExp).test(code)
  394. if (!isValidCode) {
  395. scanError()
  396. showNotify({ type: 'danger', duration: 3000, message: `唯一码《${code}》不符合规则、请重新扫描` })
  397. return
  398. }
  399. if (uniqueCodeList.value.includes(code)) {
  400. scanError()
  401. showNotify({ type: 'danger', duration: 3000, message: `唯一码《${code}》已存在列表内请重新扫描` })
  402. return
  403. }
  404. scanSuccess()
  405. uniqueCodeList.value.unshift(code)
  406. uniqueCodeRef.value.uniqueBarcode=''
  407. if(checkAllType.value){
  408. uniqueCodeRef.value.uniqueCodeScanType='barcode'
  409. }else {
  410. uniqueCodeRef.value.uniqueCodeScanType='unique'
  411. }
  412. }
  413. }else if(scanType.value==5){
  414. containerNo.value=code
  415. scanType.value=2
  416. }
  417. }
  418. /**
  419. * 物理属性
  420. */
  421. const attributeRef = ref(null)
  422. const attributeMap = ref({})
  423. const attributeTrueFalseBy = ref(true)
  424. // 获取商品物理属性
  425. const _getProductAttribute = (item) => {
  426. const params = { warehouse: item.warehouse, owner: item.customerId, barcode: item.sku }
  427. getProductAttribute(params).then(res => {
  428. attributeMap.value = res.data
  429. const isAttributeInfo = isAttribute(res.data)
  430. if (isAttributeInfo.length > 0) {
  431. attributeTrueFalseBy.value = false
  432. attributeRef.value?.show(isAttributeInfo, res.data)
  433. } else {
  434. attributeTrueFalseBy.value = true
  435. }
  436. })
  437. }
  438. // 设置商品物理属性
  439. const setAttribute = (data) => {
  440. const params = { warehouse, owner: taskInfo.value.customerId, sku: attributeMap.value.sku }
  441. showLoading()
  442. setProductAttribute(params, { ...attributeMap.value, ...data }).then(res => {
  443. showToast({ duration: 3000, message: '商品物理属性设置成功' })
  444. attributeTrueFalseBy.value = true
  445. scanSuccess()
  446. }).catch(err => {
  447. closeLoading()
  448. scanError()
  449. })
  450. }
  451. /**
  452. * 物理属性 end
  453. */
  454. /**
  455. * 商品批次属性
  456. */
  457. // 获取商品批次属性
  458. const lotData = ref([])
  459. const _getProductLot = (item) => {
  460. const params = { warehouse: item.warehouse, owner: item.customerId, barcode: item.sku }
  461. getProductLot(params).then(res => {
  462. res.data.forEach((lot) => {
  463. const lotField = lot.field
  464. if (lotField.startsWith('lotAtt') && lotField.length === 8) {
  465. lot.mapping = item[lotField]
  466. }
  467. })
  468. lotData.value = res.data
  469. _calculateShelfLife(item, lotData.value)
  470. })
  471. }
  472. // 计算效期
  473. const _calculateShelfLife = (item, lotData) => {
  474. if ((item.lotAtt01 && item.lotAtt02) || (!item.lotAtt01 && !item.lotAtt02)) return
  475. const params = {
  476. customer: item.customerId,
  477. barcode: item.sku,
  478. date: item.lotAtt01 || item.lotAtt02,
  479. isExpiryDate: item.lotAtt01 ? true : false,
  480. }
  481. calculateShelfLife(params).then(date => {
  482. lotData.forEach((lot) => {
  483. if ((lot.field === 'lotAtt01' && !params.isExpiryDate) ||
  484. (lot.field === 'lotAtt02' && params.isExpiryDate)) {
  485. lot.mapping = date.data
  486. }
  487. })
  488. })
  489. }
  490. //日期选择
  491. const lotField = ref('')
  492. const lotDateRef = ref(null)
  493. const lotQualityTrueFalseBy=ref(false)
  494. const lotQualityMap=ref({})
  495. const onLot = (item) => {
  496. lotField.value = item.field
  497. if (item.field == 'lotAtt05' ) return
  498. if ( item.field == 'lotAtt08'){
  499. lotQualityMap.value=JSON.parse(item.format)
  500. lotQualityTrueFalseBy.value = true
  501. return
  502. }
  503. if (item.type == 'Date') {
  504. lotDateRef.value?.show(item.label, item.mapping)
  505. return
  506. }
  507. if (item.type == 'String') {
  508. inputBarcodeType.value = 'lot'
  509. back.value = false
  510. inputBarcodeRef.value?.show('', `请扫描${item.label}`,'')
  511. return
  512. }
  513. }
  514. //设置质量状态
  515. const onSelectLotQuality=(key)=>{
  516. lotData.value.forEach((lot) => {
  517. if (lot.field == lotField.value) {
  518. lot.mapping = key
  519. }
  520. })
  521. lotQualityTrueFalseBy.value=false
  522. }
  523. // 设置日期
  524. const selectLotDate = (date) => {
  525. const lotMap = toMap(lotData.value, 'field', 'mapping')
  526. if ((lotField.value === 'lotAtt01' || lotField.value === 'lotAtt02') &&
  527. lotMap['lotAtt01'] == '' || lotMap['lotAtt01'] == null && lotMap['lotAtt02'] == '' || lotMap['lotAtt02'] == null) {
  528. const params = {
  529. customer: asnInfo.value.customerId,
  530. barcode: asnInfo.value.sku,
  531. date,
  532. isExpiryDate: lotField.value === 'lotAtt01' ? true : false,
  533. }
  534. calculateShelfLife(params).then(res => {
  535. if(res.data){
  536. lotData.value.forEach((lot) => {
  537. if (lot.field === 'lotAtt01') {
  538. lot.mapping = (lotField.value === 'lotAtt01') ? date : res.data
  539. } else if (lot.field === 'lotAtt02') {
  540. lot.mapping = (lotField.value === 'lotAtt02') ? date : res.data
  541. }
  542. })
  543. }else {
  544. lotData.value.forEach((lot) => {
  545. if (lot.field === lotField.value) {
  546. lot.mapping = date
  547. }
  548. })
  549. }
  550. })
  551. }
  552. lotData.value.forEach((lot) => {
  553. if (lot.field === lotField.value) {
  554. lot.mapping = date
  555. }
  556. })
  557. inputBarcodeType.value = 'task'
  558. }
  559. /**
  560. * 商品批次属性end
  561. */
  562. /**
  563. * 唯一码
  564. */
  565. const uniqueCodeRef = ref(null)
  566. //规则列表
  567. const uniqueRuleList = ref([])
  568. const uniqueRuleMap = ref({})
  569. // 获取商品规则
  570. const _getCommodityRule = (item) => {
  571. const params = { customer: item.customerId, sku: item.sku, input: 'RECEIVING' }
  572. getCommodityRule(params).then(res => {
  573. uniqueRuleList.value = res.data
  574. res.data.forEach((item, index) => {
  575. if (item.sku == '') {
  576. item.type = 'all'
  577. } else {
  578. item.type = 'sku'
  579. }
  580. })
  581. uniqueRuleMap.value = toMap(res.data, 'type', 'uniqueRegExp')
  582. })
  583. }
  584. /**
  585. * 唯一码end
  586. */
  587. const containerNoRef = ref(null)
  588. const numberRef = ref(null)
  589. // 完成收货校验
  590. const isCheck = () => {
  591. if (!asnInfo.value.asnNo) {
  592. scanError()
  593. showToast({ duration: 3000, message: '请先查询商品收货信息' })
  594. return false
  595. }
  596. //商品物理属性判断
  597. if (!attributeTrueFalseBy.value) {
  598. const isAttributeInfo = isAttribute(attributeMap.value)
  599. if (isAttributeInfo.length > 0) {
  600. attributeTrueFalseBy.value = false
  601. attributeRef.value?.show(isAttributeInfo, attributeMap.value)
  602. }
  603. return false
  604. }
  605. // //商品批次属性判断
  606. const incompleteLot = lotData.value.find(lot => lot.require && !lot.mapping)
  607. if (incompleteLot) {
  608. scanError()
  609. showToast({ duration: 3000, message: `请先补充${incompleteLot.label}` })
  610. return false
  611. }
  612. const lotMap = toMap(lotData.value, 'field', 'mapping')
  613. const productionDate = lotMap['lotAtt01'] ? new Date(lotMap['lotAtt01']) : null
  614. const expirationDate = lotMap['lotAtt02'] ? new Date(lotMap['lotAtt02']) : null
  615. const currentDate = new Date()
  616. // 如果存在失效日期
  617. if (expirationDate) {
  618. // 如果有生产日期,进行有效性检查
  619. if (productionDate && expirationDate <= productionDate) {
  620. scanError()
  621. showToast({ duration: 3000, message: `失效日期不能小于等于生产日期` })
  622. return false
  623. }
  624. // 检查失效日期是否小于当前日期
  625. if (expirationDate <= currentDate) {
  626. scanError()
  627. showToast({ duration: 3000, message: `失效日期不能小于等于当前日期` })
  628. return false
  629. }
  630. }
  631. if(productionDate){
  632. // 如果有生产日期,进行有效性检查
  633. if (expirationDate && productionDate >= expirationDate) {
  634. scanError()
  635. showToast({ duration: 3000, message: `生产日期不能大于失效日期` })
  636. return false
  637. }
  638. // 检查生产日期是否小于当前日期
  639. if (productionDate >= currentDate) {
  640. scanError()
  641. showToast({ duration: 3000, message: `生产日期不能大于等于当前日期` })
  642. return false
  643. }
  644. }
  645. if (searchCount.value == '') {
  646. numberRef.value?.focus()
  647. scanError()
  648. showToast({ duration: 3000, message: '请先输入收货数量' })
  649. return false
  650. }
  651. if (containerNo.value == '') {
  652. scanError()
  653. containerNoRef.value?.focus()
  654. showToast({ duration: 3000, message: '请先输入容器号' })
  655. return false
  656. }
  657. // 唯一码收集
  658. if (uniqueRuleList.value.length > 0 && uniqueCodeList.value.length != searchCount.value) {
  659. scanType.value = 3
  660. uniqueCodeRef.value?.show('', '请扫描唯一码', `收货数量:${searchCount.value}`, uniqueRuleMap.value)
  661. return false
  662. }
  663. return true
  664. }
  665. // 收货
  666. const onConfirm = () => {
  667. if(isCheck()){
  668. const lotMap = toMap(lotData.value, 'field', 'mapping')
  669. const { asnLineNo, asnNo, warehouse } = asnInfo.value
  670. const data = {
  671. asnLineNo,
  672. asnNo,
  673. containerId: containerNo.value,
  674. quantity: searchCount.value,
  675. warehouse,
  676. serialNos: uniqueCodeList.value.length > 0 ? uniqueCodeList.value : undefined,
  677. ...lotMap,
  678. }
  679. showLoading()
  680. inputBarcodeType.value='task'
  681. setReceiving(data).then(res => {
  682. scanSuccess()
  683. showNotify({ type: 'success', duration: 3000, message: `${searchBarcode.value}收货完成,请继续收货!`})
  684. setBarcode(taskNo.value, '2')
  685. reset()
  686. taskInfo.value={}
  687. scanType.value=2
  688. closeLoading()
  689. }).catch(err => {
  690. if(err.message.includes('序列号已存在')){
  691. scanType.value = 3
  692. uniqueCodeRef.value?.show('', '请扫描唯一码', `收货数量:${searchCount.value},${err.message}`, uniqueRuleMap.value)
  693. }
  694. scanError()
  695. closeLoading()
  696. })
  697. }
  698. }
  699. // 数据刷新
  700. const loadData = () => {
  701. if (!taskNo.value) {
  702. inputBarcodeRef.value?.show('', '请扫描开单任务号','')
  703. return
  704. } else {
  705. // currentTime.value=getCurrentTime()
  706. // startTimer()
  707. }
  708. }
  709. onUnmounted(() => {
  710. closeListener()
  711. stopTimer()
  712. })
  713. window.onRefresh = loadData
  714. </script>
  715. <style scoped lang="scss">
  716. .take-delivery {
  717. .take-info {
  718. padding: 6px 10px;
  719. background: linear-gradient(160deg, #cfe1ff 20%, white 50%, white 100%);
  720. display: flex;
  721. flex-direction: column;
  722. text-align: left;
  723. .take-info-no {
  724. flex: 1;
  725. .info-no-title {
  726. font-size: 19px;
  727. font-weight: 500;
  728. display: flex;
  729. justify-content: space-between;
  730. align-items: center;
  731. }
  732. .info-no-tips {
  733. font-size: 14px;
  734. color: #666666;
  735. display: flex;
  736. justify-content: space-between;
  737. padding: 6px 0;
  738. }
  739. }
  740. .take-info-number {
  741. flex: 1;
  742. border-top: 1.5px solid #efefef;
  743. display: flex;
  744. justify-content: space-between;
  745. gap: 10px;
  746. color: #666;
  747. font-size: 14px;
  748. padding-top: 10px;
  749. .info-number-left {
  750. flex: 1;
  751. display: flex;
  752. justify-content: space-evenly;
  753. align-items: center;
  754. .number-left-box {
  755. flex: 1;
  756. display: flex;
  757. flex-direction: column;
  758. align-items: center;
  759. .left-box-title {
  760. font-size: 14px;
  761. font-weight: bold;
  762. color: #000;
  763. line-height: 34px;
  764. }
  765. }
  766. }
  767. .info-number-right {
  768. width: 40%;
  769. text-align: center;
  770. .van-search {
  771. padding: 0;
  772. }
  773. }
  774. }
  775. }
  776. .take-barcode {
  777. margin-top: 10px;
  778. text-align: left;
  779. background: #ffffff;
  780. .barcode-input {
  781. height: 50px;
  782. ::v-deep(.van-search) {
  783. padding: 0;
  784. }
  785. ::v-deep(.van-search__field) {
  786. border-bottom: 2px solid #ffffff;
  787. height: 50px;
  788. display: flex;
  789. align-items: center;
  790. }
  791. ::v-deep(.van-search__content) {
  792. background: #fff;
  793. height: 50px;
  794. display: flex;
  795. align-items: center;
  796. }
  797. ::v-deep(.van-search__label) {
  798. font-size: 16px;
  799. font-weight: bold;
  800. line-height: 50px;
  801. height: 50px;
  802. display: flex;
  803. align-items: center;
  804. }
  805. ::v-deep(.van-field__control) {
  806. font-size: 16px;
  807. font-weight: bold;
  808. height: 50px;
  809. line-height: 50px;
  810. display: flex;
  811. align-items: center;
  812. }
  813. .search-input-barcode {
  814. ::v-deep(.van-search__field) {
  815. border-bottom: 2px solid #0077ff;
  816. z-index: 2;
  817. }
  818. }
  819. }
  820. }
  821. .take-lot {
  822. text-align: left;
  823. margin-top: 5px;
  824. ::v-deep(.van-cell) {
  825. padding: 5px 8px;
  826. }
  827. .take-lot-title {
  828. font-size: 15px;
  829. font-weight: bold;
  830. padding: 0 5px;
  831. border-left: 3px solid #1989fa;
  832. color: #333;
  833. margin-bottom: 3px;
  834. }
  835. }
  836. .take-button {
  837. padding: 20px;
  838. }
  839. }
  840. </style>