index.vue 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904
  1. <template>
  2. <div class="container">
  3. <van-nav-bar
  4. title="复核-大件单" left-arrow fixed placeholder @click-left="goBack">
  5. <template #left>
  6. <van-icon name="arrow-left" size="25" />
  7. <div style="color: #fff">返回</div>
  8. </template>
  9. <!-- <template #right>-->
  10. <!-- <div class="nav-right" @click="onClickRight">提交任务</div>-->
  11. <!-- </template>-->
  12. </van-nav-bar>
  13. <div class="large">
  14. <div class="large-title">
  15. <!-- <div><span style="font-size: 12px">订单号:</span>{{ orderNo || '&#45;&#45;' }}</div>-->
  16. <div class="large-tips">
  17. <van-notice-bar :background="'none'" :speed="50" :text="tips" />
  18. </div>
  19. <van-button plain size="mini" type="primary" @click="_setOrderNo()">更换订单/快递</van-button>
  20. </div>
  21. <div class="scan-barcode">
  22. <van-field v-model.lazy="scanBarcode" label-align="left" placeholder="请扫描商品条码/SKU" label="商品条码:"
  23. ref="barcodeNumberRef" class="input-barcode" autocomplete="off" @keydown.enter="_handlerScan(scanBarcode)" />
  24. </div>
  25. <div class="order-detail">
  26. <div class="picking-no">
  27. <div class="picking-code">
  28. <van-icon name="stop-circle-o" color="#419bff" />
  29. <span style="padding-left: 5px">单号:{{ orderDetail.orderNo }}</span></div>
  30. <div>{{ orderDetail.carrierName || '--' }}</div>
  31. </div>
  32. <div class="picking-container ">
  33. <div style="display: flex;padding-top: 5px" @click="setPrinter()">
  34. <div>打印机:</div>
  35. <div v-if="printer">{{printer.printer}}</div>
  36. <div style="text-decoration: underline;color: #0077ff">设置打印机<van-icon name="edit" color="#0077ff"/> </div>
  37. </div>
  38. <div class="picking-order-count ">
  39. <div>已复核/总复核:
  40. <span>{{packingCount}}/{{ orderBarcodeCount+packingCount+scanOrderBarcodeCount }}</span>
  41. </div>
  42. <div>耗材数:
  43. <span>{{ scanMaterialCount }}/{{ materialCount }}</span>
  44. </div>
  45. <div>已装箱数:
  46. <span v-if="orderDetail.orderPacking && orderDetail.orderPacking.length>0" style="text-decoration: underline;color: #0077ff" @click="resetPacking" >{{ orderDetail.orderPacking.length }}<van-icon name="arrow-double-right" color="#0077ff" /></span>
  47. <span v-else>0</span>
  48. </div>
  49. </div>
  50. </div>
  51. <div class="picking-button">
  52. <div class="picking-button-item" @click="reset()">重置</div>
  53. <div class="picking-button-item" @click="print('A3001_SO_PACKINGLIST',orderDetail.orderNo)">总清单</div>
  54. <div class="picking-button-item" @click="setPacking('batch')">批量分箱</div>
  55. <div class="picking-button-item" @click="setPacking('single')">装箱</div>
  56. </div>
  57. </div>
  58. <div class="order-list-box">
  59. <van-divider style="margin: 0;padding: 5px 15px">
  60. <template #default>
  61. <div style="display: flex;align-items: center">订单明细(<div style="background: #E6A23C;height: 10px;width: 10px;margin:0 5px"></div><div>待装箱商品)</div></div>
  62. </template>
  63. </van-divider>
  64. <div class="order-list">
  65. <table class="task-table">
  66. <thead>
  67. <tr>
  68. <th style="width: 20px">#</th>
  69. <th style="width: 40%">商品条码</th>
  70. <th>数量</th>
  71. <th>剩余</th>
  72. <th>状态</th>
  73. <th v-if="isUniqueCode || isQualityCheck">标记</th>
  74. </tr>
  75. </thead>
  76. <tbody>
  77. <tr v-for="(item, index) in orderList" :key="index" v-if="orderList.length>0" :style="rowStyle(item)" >
  78. <td>{{ index + 1 }}</td>
  79. <td>{{ item.barcode }} <van-tag type="success" v-if="item.universalCode">万用</van-tag></td>
  80. <td>
  81. <span v-if="item.qtyOrdered" :style="item.qty!=item.oldQuantity && item.oldQuantity!=0 && item.qty!=0?'color:#b40a1e':''">{{ item.qty }}/{{ item.qtyOrdered }}</span>
  82. <span v-else><van-tag type="warning">耗材</van-tag></span>
  83. </td>
  84. <td>{{ item.qty }}</td>
  85. <td>{{ statusMap[item.status] || '' }}</td>
  86. <td v-if="isUniqueCode || isQualityCheck">
  87. <van-tag v-if="item.uniqueRegExp" type="warning">唯一码</van-tag>
  88. <van-tag v-if="item.imeiRegExp" type="primary">IMEI码</van-tag>
  89. <van-tag v-if="item.qualityCheck" type="warning" >质检</van-tag>
  90. </td>
  91. </tr>
  92. <tr v-else>
  93. <td colspan="5">
  94. <div>暂无数据</div>
  95. </td>
  96. </tr>
  97. </tbody>
  98. </table>
  99. </div>
  100. </div>
  101. </div>
  102. <!-- 条码输入组件 -->
  103. <input-barcode :back="back" @setBarcode="setBarcode" ref="inputBarcodeRef" />
  104. <!-- 打印面单-->
  105. <printer ref="printerRef" @onPrint="onPrint" />
  106. <!-- 批量扫描-->
  107. <van-dialog v-model:show="barcodeNumberTrueFalseBy" title="批量扫描" show-cancel-button :beforeClose="beforeClose" :keyboardEnabled="false" >
  108. <van-field v-model="barcodeNumber" autocomplete="off" center border label="数量:" placeholder="请输入数量"
  109. type="digit" name="pattern" ref="barcodeNumberRef" class="count-input" @keydown.enter="onSubmitCount"
  110. label-width="70px" label-align="center" :rules="[{ pattern, message: '请输入正确数量' }]">
  111. </van-field>
  112. <van-row :gutter="[5, 5]" style="margin: 10px 20px;font-size: 14px;text-align: left;color:#333" v-for="item in matchBarcodeList">
  113. <van-col span="12">条码:{{item.barcode}}</van-col>
  114. <van-col span="12">数量:{{item.qty}}</van-col>
  115. </van-row>
  116. </van-dialog>
  117. <!-- 装箱-->
  118. <check-packing ref="checkPackingRef" @cancelOrder="cancelOrder" @resetPackingStatus="resetPackingStatus" @getOrderPacking="_getOrderPacking" @print-picking-list="printPickingList" @print="print"/>
  119. <!-- 批量装箱-->
  120. <batch-packing ref="batchPackingRef" @print="print" @loadData="loadData" @print-picking-list="printPickingList" />
  121. <!-- 重置装箱-->
  122. <packing-list ref="packingListRef" @loadData="loadData" />
  123. </div>
  124. </template>
  125. <script setup >
  126. import { onMounted, onUnmounted, computed, ref } from 'vue'
  127. import { androidFocus, getHeader, goBack, scanError, scanSuccess } from '@/utils/android'
  128. import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
  129. import InputBarcode from '@/views/outbound/picking/components/InputBarcode.vue'
  130. import { useStore } from '@/store/modules/user'
  131. import { closeLoading, showLoading } from '@/utils/loading'
  132. import { getOrderPacking, getPendingReviewTask, listEnableCheckDocumentPrint, reversePicking } from '@/api/check'
  133. import { barcodeToUpperCase } from '@/utils/dataType'
  134. import { showConfirmDialog, showDialog, showNotify, showToast } from 'vant'
  135. import { fluxPrint } from '@/api/picking'
  136. import Printer from '@/components/Printer.vue'
  137. import CheckPacking from '@/views/outbound/check/components/CheckPacking.vue'
  138. import BatchPacking from '@/views/outbound/check/components/BatchPacking.vue'
  139. import PackingList from '@/views/outbound/check/components/PackingList.vue'
  140. // 设置波次号
  141. const store = useStore()
  142. try {
  143. getHeader()
  144. androidFocus()
  145. } catch (error) {
  146. }
  147. // 页面初始化
  148. onMounted(() => {
  149. openListener()
  150. scanInit(_handlerScan)
  151. loadData()
  152. })
  153. const warehouse = store.warehouse
  154. const orderNo = ref('')
  155. // 错误提示
  156. const tips = ref('请扫描订单/快递单号')
  157. //强制返回
  158. const back = ref(true)
  159. const scanBarcode = ref('')
  160. const inputBarcodeRef = ref(null)
  161. // 订单明细
  162. const orderDetail = ref({})
  163. //装箱明细
  164. const packingDetailMap=ref({})
  165. const isUniqueCode = ref(false)
  166. const isQualityCheck=ref(false)
  167. const statusMap = {
  168. '00': '创建',
  169. '20': '预配',
  170. '30': '部分分配',
  171. '40': '已分配',
  172. '50': '待拣货',
  173. '60': '已拣货',
  174. '61': '分拣完成',
  175. '90': '订单取消',
  176. }
  177. //订单列表
  178. const dataList = ref([])
  179. const orderList = computed(() => {
  180. return dataList.value
  181. .filter(item => item.qty !== 0 || item.isPacking)
  182. .sort((a, b) => {
  183. const statusPriority = {
  184. '60': 0,
  185. '600': 1,
  186. '50': 2,
  187. '40': 3,
  188. '30': 4,
  189. '20': 5,
  190. '00': 6,
  191. }
  192. if (statusPriority[a.status] !== statusPriority[b.status]) {
  193. return statusPriority[a.status] - statusPriority[b.status]
  194. }
  195. return 0
  196. })
  197. })
  198. //订单产品总数
  199. const orderBarcodeCount = computed(() => {
  200. return dataList.value.reduce((sum, item) => {
  201. if (item.qtyOrdered != null) {
  202. return sum + Number(item.qty)
  203. }
  204. return sum
  205. }, 0)
  206. })
  207. //装箱总数
  208. const packingCount = computed(() => {
  209. return packingList.value.reduce((sum, item) => sum + (+item.qty || 0), 0);
  210. })
  211. //
  212. //扫描订单产品总数
  213. const scanOrderBarcodeCount = computed(() => {
  214. return dataList.value.reduce((sum, item) => {
  215. if (item.qtyOrdered != null) {
  216. return sum + Number(item.quantity)
  217. }
  218. return sum
  219. }, 0)
  220. })
  221. //耗材总数
  222. const materialCount = computed(() => {
  223. return dataList.value.reduce((sum, item) => {
  224. if (item.qtyOrdered == null) {
  225. return sum + Number(item.oldQty)
  226. }
  227. return sum
  228. }, 0)
  229. })
  230. const scanMaterialCount = computed(() => {
  231. return dataList.value.reduce((sum, item) => {
  232. if (item.qtyOrdered == null) {
  233. return sum + Number(item.quantity)
  234. }
  235. return sum
  236. }, 0)
  237. })
  238. // 查询打印策略
  239. const printConfig=ref([])
  240. const getListEnableCheckDocumentPrint=()=>{
  241. listEnableCheckDocumentPrint({ 'name': '', 'owner': '' }).then(res => {
  242. printConfig.value= res.data
  243. })
  244. }
  245. getListEnableCheckDocumentPrint()
  246. //匹配条码
  247. const matchBarcodeList = ref([])
  248. //扣除数量
  249. const barcodeNumberTrueFalseBy = ref(false)
  250. const barcodeNumberRef = ref(null)
  251. const barcodeNumber=ref('')
  252. const pattern=/^[0-9]\d*$/
  253. const _handlerScan = async (code) => {
  254. if (code) {
  255. if (isUniqueCode.value) {
  256. scanError()
  257. scanBarcode.value = ''
  258. showNotify({ type: 'warning', duration: 3000, message: `此单包含唯一码/IMEI码,请到PC复核` })
  259. return
  260. }
  261. const barcode = [...new Set(
  262. orderList.value
  263. .filter(item => (item.status == '60' || item.status == '600'))
  264. .flatMap(item => [item.barcode, item.barcode2, item.sku, item.universalCode])
  265. .filter(value => value !== null && value !== '' && value !== undefined),
  266. )]
  267. const checkBarcode = barcodeToUpperCase(code)
  268. if (barcode.some(item => barcodeToUpperCase(item) === checkBarcode)) {
  269. dataList.value = await barcodeMatching(checkBarcode)
  270. matchBarcodeList.value = dataList.value.filter(item => (item.barcode === checkBarcode || item.sku === checkBarcode || item.barcode2 === checkBarcode || item.universalCode == checkBarcode) && (item.status == '60' || item.status == '600') && item.qty > 0)
  271. if (matchBarcodeList.value.length > 0) {
  272. scanBarcode.value = code
  273. const allCount = matchBarcodeList.value.reduce((sum, item) => sum + Number(item.qty), 0)
  274. if (allCount > 1) {
  275. barcodeNumberTrueFalseBy.value = true
  276. setTimeout(() => {
  277. barcodeNumber.value=''
  278. barcodeNumberRef.value?.focus()
  279. },300)
  280. return
  281. }
  282. cutBarcode([matchBarcodeList.value[0]], 1)
  283. } else {
  284. scanBarcode.value = ''
  285. scanError()
  286. tips.value = `商品条码${code},已全部扫描完成`
  287. showNotify({ type: 'warning', duration: 3000, message: `商品条码${code},已全部扫描完成` })
  288. }
  289. } else {
  290. scanBarcode.value = ''
  291. tips.value = `商品条码${code},不匹配请重新扫描!`
  292. showNotify({ type: 'warning', duration: 3000, message: `商品条码${code},不匹配请重新扫描!` })
  293. scanError()
  294. }
  295. }
  296. }
  297. const onSubmitCount=()=>{
  298. if(!barcodeNumber.value){
  299. tips.value='请输入数量'
  300. showToast({duration:5000,message:'请输入数量'})
  301. return
  302. }
  303. const allCount = matchBarcodeList.value.reduce((sum, item) => sum + Number(item.qty), 0)
  304. if(Number(barcodeNumber.value)>allCount){
  305. const message='数量不能大于最大数量'+allCount
  306. tips.value=message
  307. showToast({duration:5000,message})
  308. return
  309. }
  310. barcodeNumberTrueFalseBy.value=false
  311. cutBarcode(matchBarcodeList.value, barcodeNumber.value)
  312. }
  313. const beforeClose= (action) =>
  314. new Promise((resolve) => {
  315. if(action==='confirm'){
  316. if(!barcodeNumber.value){
  317. tips.value='请输入数量'
  318. showToast({duration:5000,message:'请输入数量'})
  319. return resolve(false)
  320. }
  321. const allCount = matchBarcodeList.value.reduce((sum, item) => sum + Number(item.qty), 0)
  322. if(Number(barcodeNumber.value)>allCount){
  323. const message='数量不能大于最大数量'+allCount
  324. tips.value=message
  325. showToast({duration:5000,message})
  326. return resolve(false)
  327. }
  328. resolve(true)
  329. cutBarcode(matchBarcodeList.value, barcodeNumber.value)
  330. }else {
  331. tips.value='您已取消扣减扫描商品,请重新扫描'
  332. scanBarcode.value=''
  333. matchBarcodeList.value=[]
  334. resolve(true)
  335. }
  336. });
  337. const cutBarcode = (list, count) => { //扣减数量
  338. let remainingCount = count // 剩余的扣减数量
  339. // 更新扫描数据的逻辑
  340. const updateData = (item) => {
  341. const itemIndex = dataList.value.findIndex(data => data.lotNum === item.lotNum)
  342. const deductedAmount = item.originalDetailAmount - item.qty // 扣减的数量
  343. if (itemIndex !== -1) {
  344. dataList.value[itemIndex] = {
  345. ...dataList.value[itemIndex],
  346. quantity: dataList.value[itemIndex].quantity + deductedAmount, // 更新实际扣减的数量
  347. oldQuantity:dataList.value[itemIndex].oldQuantity + deductedAmount, // 更新实际扣减的数量
  348. }
  349. } else {
  350. item.quantity = deductedAmount
  351. item.oldQuantity=deductedAmount
  352. dataList.value.push(item)
  353. }
  354. }
  355. list.forEach(item => {
  356. if (remainingCount <= 0) return // 如果剩余数量为0,停止扣减
  357. item.originalDetailAmount = item.qty // 保存原始数量
  358. if (item.qty > 0) {
  359. // 如果剩余数量大于当前商品的数量,则直接扣除当前商品的数量
  360. if (remainingCount >= item.qty) {
  361. remainingCount -= item.qty
  362. item.qty = 0
  363. } else {
  364. item.qty -= remainingCount // 扣减剩余数量
  365. remainingCount = 0
  366. }
  367. item.isPacking=true
  368. }
  369. if (item.qty !== item.originalDetailAmount) {
  370. updateData(item) // 更新扫描数据
  371. scanBarcode.value = ''
  372. tips.value = '请继续扫描商品'
  373. scanSuccess()
  374. }
  375. })
  376. const materiaList=orderList.value.filter(item=>item.status=='600' && item.qty>0)
  377. const endOrder=orderList.value.filter(item=>item.status=='60' && item.qty>0)
  378. if(materiaList.length ==0 &&endOrder.length==0){
  379. setPacking('single')
  380. return
  381. }
  382. }
  383. // 进行装箱
  384. const checkPackingRef=ref(null)
  385. const currPackingList=ref([])
  386. const batchPackingRef=ref(null)
  387. const setPacking=(type)=>{
  388. if(!printer.value){
  389. scanError()
  390. showNotify({ type: 'warning', duration: 3000, message: '请先设置打印机' })
  391. return
  392. }
  393. // 获取装箱的商品列表
  394. currPackingList.value = orderList.value.filter(item => item.isPacking)
  395. if (currPackingList.value.length === 0) {
  396. scanError()
  397. showNotify({ type: 'warning', duration: 3000, message: '暂无未装箱数据' })
  398. return
  399. }
  400. const materiaList = orderList.value.filter(item => item.status == '600' && item.qty > 0)
  401. if (materiaList.length > 0) {
  402. const checkBarcode = new Set(currPackingList.value.flatMap(item => item.relatedMaterial.map(material => material.barCode)))
  403. const packingBarcodes = new Set(materiaList.map(item => item.barcode))
  404. // 判断是否有装箱商品条形码与需要扫描的物料条形码重复
  405. const hasCommonBarcode = [...packingBarcodes].some(barcode => checkBarcode.has(barcode))
  406. if (hasCommonBarcode) {
  407. scanError();
  408. showNotify({ type: 'warning', duration: 3000, message: `装箱商品包含耗材${[...checkBarcode].join(',')},请先扫描耗材` })
  409. return
  410. }
  411. }
  412. if (type === 'single') { // 普通装箱
  413. checkPackingRef.value?.show(currPackingList.value, orderDetail.value)
  414. } else { // 批量装箱
  415. const list =orderList.value.filter(item => item.isPacking && item.status=='60')
  416. if (list.length > 1) {
  417. scanError()
  418. showNotify({ type: 'warning', duration: 3000, message: '不支持多个商品装箱' })
  419. return
  420. }
  421. batchPackingRef.value?.show(currPackingList.value, orderDetail.value)
  422. }
  423. }
  424. //重置装箱状态
  425. const resetPackingStatus=()=>{
  426. currPackingList.value.forEach((item) => {
  427. item.quantity=0
  428. delete item.isPacking
  429. })
  430. }
  431. //重置装箱
  432. const packingListRef=ref(null)
  433. const resetPacking=()=>{
  434. _getOrderPacking(orderDetail.value.orderNo,1)
  435. }
  436. //重新开始
  437. const reset=()=>{
  438. showConfirmDialog({
  439. title: '温馨提示',
  440. message: '您正在进行重新开始操作,是否继续?',
  441. }).then(() => {
  442. _reset()
  443. inputBarcodeRef.value?.show('', '请扫描订单/快递单号','')
  444. })
  445. }
  446. //打印清单
  447. const printerRef=ref(null)
  448. //设置打印机
  449. const setPrinter=()=>{
  450. printerRef.value?.show(warehouse)
  451. }
  452. const printer=ref(null)
  453. if(localStorage.getItem('check-print')){
  454. printer.value=JSON.parse(localStorage.getItem('check-print'))
  455. }
  456. const print=(templateCode,code)=>{
  457. if(!printer.value){
  458. scanError()
  459. showNotify({ type: 'warning', duration: 3000, message: '请先设置打印机' });
  460. return
  461. }
  462. const data = {warehouse,code,printServer: printer.value.server, printName:printer.value.printer,templateCode }
  463. showLoading()
  464. fluxPrint(data)
  465. .then(res => {
  466. if(res.code==200){
  467. scanSuccess()
  468. showNotify({ type: 'success', duration: 3000, message: '打印已发起,请检查打印情况' });
  469. printPickingList(code,currPackingList.value)
  470. }else {
  471. scanError()
  472. tips.value=res.message || '系统异常,请联系技术支持!'
  473. showNotify({ type: 'danger', duration: 3000, message: res.message || '系统异常,请联系技术支持!' });
  474. }
  475. })
  476. .catch(err => {
  477. scanError()
  478. tips.value=err.message || '系统异常,请联系技术支持!'
  479. showNotify({ type: 'danger', duration: 3000, message: err.message || '系统异常,请联系技术支持!' });
  480. }).finally(() => {
  481. closeLoading()
  482. })
  483. }
  484. // 打印装箱清单
  485. const printPickingList=(deliveryNo,curPackingList)=> {
  486. // 打印装箱清单
  487. let printData=[]
  488. const findIndex = printConfig.value.findIndex(item => item.ownerList.includes(orderDetail.value.customerId));
  489. // 检查质检数据并添加质检打印任务
  490. const hasQualityCheck = curPackingList.some(item => item.qualityCheck === true);
  491. if (hasQualityCheck) {
  492. const qcData = {
  493. warehouse: orderDetail.value.warehouseId,
  494. code: deliveryNo,
  495. printServer: printer.value.server,
  496. printName: printer.value.printer,
  497. templateCode: 'A3014_PACK_CARTON_QC'
  498. };
  499. printData.push(qcData);
  500. }
  501. if (findIndex === -1) {
  502. printRequests(0, printData);
  503. return;
  504. }
  505. printConfig.value.forEach((item,index) => {
  506. const isOwner = item.ownerList.includes(orderDetail.value.customerId);
  507. const printMap = item.printer && item.printer !== '' ? item.printer.split('+') : [printer.value.server,printer.value.printer];
  508. // 判断 店铺和承运商是否匹配
  509. const isShop = item.shop ? item.shop.includes(orderDetail.value.shop) : true;
  510. const isCarrier =item.carriers? item.carriers.includes(orderDetail.value.carrierId):true
  511. if (isOwner && isShop && isCarrier ) {
  512. const data = {
  513. warehouse: orderDetail.value.warehouseId,
  514. code: deliveryNo,
  515. printServer: printMap[0],
  516. printName: printMap[1],
  517. templateCode: item.templateCode,
  518. ignoreNoData:item.templateCode
  519. };
  520. const printCount = Math.max(item.printNum, 1)
  521. printData.push(...new Array(printCount).fill(data));
  522. }
  523. if(index==printConfig.value.length-1){
  524. printRequests(0,printData)
  525. }
  526. });
  527. }
  528. const printRequests = async(index, dataList)=> {
  529. if (index >= dataList.length) {
  530. // if (this.orderList.length == 0) {
  531. // this.getOrderList();
  532. // }
  533. return;
  534. }
  535. try {
  536. const res = await _printMode(dataList[index]);
  537. if (res) {
  538. if(res.code==200){
  539. showNotify({ type: 'success', duration: 3000, message: '打印已发起,请检查打印情况' });
  540. }else {
  541. if(dataList[index].ignoreNoData && dataList[index].ignoreNoData==true && (res.message.includes('打印数据为空') ||res.message.includes('ignoreNoData'))){
  542. return
  543. }
  544. showNotify({ type: 'danger', duration: 3000, message:res.message || '未知系统错误,请联系开发人员' });
  545. }
  546. }
  547. } catch (error) {
  548. console.error('打印请求发生错误:', error);
  549. showNotify({ type: 'danger', duration: 3000, message: error.message || '系统异常,请联系技术支持!' });
  550. } finally {
  551. closeLoading();
  552. await printRequests(index + 1, dataList);
  553. }
  554. }
  555. const _printMode=(data)=> {
  556. return new Promise((resolve, reject) => {
  557. fluxPrint(data)
  558. .then(res => resolve(res))
  559. .catch(err => reject(`打印失败: ${err}`));
  560. });
  561. }
  562. const onPrint=(code)=>{
  563. printer.value=code
  564. localStorage.setItem('check-print',JSON.stringify(code))
  565. }
  566. //设置订单号
  567. const setBarcode = (code) => {
  568. const data = { warehouse, code, activityOrderFlag: true }
  569. showLoading()
  570. packingDetailMap.value={}
  571. getPendingReviewTask(data).then(res => {
  572. if (res.data.details.length == 0) {
  573. scanError()
  574. inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '暂未查询到待复核数据,请切换单号')
  575. } else {
  576. if(res.data.releaseStatus=='H' || res.data.status=='90' ||res.data.erpCancelFlag=='Y'){
  577. orderNo.value = code
  578. orderDetail.value = res.data
  579. scanError()
  580. cancelOrder(res.data)
  581. return
  582. }
  583. const orderNos = [...new Set(res.data.details.map(item => item.orderNo))];
  584. if(orderNos.length>1){
  585. scanError()
  586. inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '仅支持单个订单复核','')
  587. return
  588. }
  589. orderNo.value = code
  590. orderDetail.value = res.data
  591. tips.value = '请扫描商品条码'
  592. _getOrderPacking(res.data.orderNo)
  593. scanSuccess()
  594. // 处理每个详情项
  595. res.data.details.forEach((item) => {
  596. item.quantity = 0
  597. item.oldQuantity = 0
  598. item.oldQty = item.qty
  599. if (item.relatedMaterial == null) {
  600. item.relatedMaterial = []
  601. }
  602. if (item.universalBarcode) {
  603. item.universalCode = '#@@@@@@#'
  604. }
  605. })
  606. const relatedMaterialList = getRelatedMaterial(res.data.details)
  607. res.data.details.push(...relatedMaterialList)
  608. dataList.value = res.data.details
  609. isUniqueCode.value = dataList.value.some(item => item.uniqueRegExp || item.imeiRegExp)
  610. isQualityCheck.value = dataList.value.some(item => item.qualityCheck === true);
  611. scanBarcode.value = ''
  612. }
  613. }).catch(err => {
  614. scanError()
  615. inputBarcodeRef.value?.show('', '请扫描订单/快递单号', err.message)
  616. }).finally(f => {
  617. closeLoading()
  618. })
  619. }
  620. const _reset=()=>{
  621. orderNo.value = ''
  622. orderDetail.value=[]
  623. dataList.value=[]
  624. tips.value = '请扫描订单/快递单号'
  625. }
  626. const containerNoMap={
  627. 'WH01':'FJ-WH01-20',
  628. 'WH02':'FJ-WH02-20',
  629. 'WH10':'FJ-WH10-1',
  630. 'WH99':'FJ-WH99-01',
  631. }
  632. //返拣容器
  633. const cancelOrder=(item,type)=>{
  634. const orderDetailStatus = orderList.value.find(item => (item.status != '60' && item.status != '600'));
  635. if(item.status=='90'){
  636. scanError()
  637. showDialog({ title: '温馨提示', message: '已取消, 暂停发货', }).then(() => {
  638. inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '上一单为冻结单,请重新扫描单号')
  639. });
  640. }else if(item.releaseStatus=='H' || type=='release'){
  641. scanError()
  642. showDialog({ title: '温馨提示', message: `订单:${orderNo.value},已冻结, 暂停发货`, }).then(() => {
  643. inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '上一单为冻结单,请重新扫描单号')
  644. });
  645. _reset()
  646. }else if(orderDetailStatus && item.erpCancelFlag=='Y' ){
  647. scanError()
  648. showDialog({ title: '温馨提示', message: '此取消单包含《待拣货》商品,请将所有商品拣货后返拣', }).then(() => {});
  649. } else if(item.erpCancelFlag=='Y' || type=='erp'){
  650. scanError()
  651. showConfirmDialog({ title: '温馨提示', message: `订单:${orderNo.value},取消单,请进入还库流程!!!`, })
  652. .then(() => {
  653. _reversePicking()
  654. })
  655. }
  656. }
  657. const _reversePicking = () => {
  658. showLoading()
  659. const data = {
  660. warehouse,
  661. reversePickingContainerNo: containerNoMap[warehouse],
  662. code: orderDetail.value.orderNo,
  663. }
  664. reversePicking(data).then(res => {
  665. showNotify({ type: 'success', duration: 5000, message: `${data.code},已进行返拣,请放置《${data.reversePickingContainerNo}》返拣容器中!` })
  666. loadData()
  667. }).finally(() => {
  668. closeLoading()
  669. })
  670. }
  671. const packingList=ref([])
  672. const _getOrderPacking=(orderNo,type)=>{
  673. showLoading()
  674. getOrderPacking({ warehouse, code:orderNo }).then(res => {
  675. packingList.value=res.data
  676. const mergedList = Object.values(
  677. res.data.reduce((acc, item) => {
  678. const { sku, traceId, qty } = item
  679. const compositeKey = `${sku}_${traceId}`
  680. if (!acc[compositeKey]) {
  681. acc[compositeKey] = { ...item }
  682. } else {
  683. acc[compositeKey].qty += qty
  684. }
  685. return acc;
  686. }, {})
  687. )
  688. packingDetailMap.value = mergedList.reduce((acc, item) => {
  689. const { traceId } = item
  690. acc[traceId] = acc[traceId] || []
  691. acc[traceId].push(item)
  692. return acc
  693. }, {});
  694. if(type && type==1){
  695. packingListRef.value?.show(packingDetailMap.value,orderDetail.value)
  696. }
  697. }).catch(err => {
  698. packingDetailMap.value = {}
  699. }).finally(f=>{
  700. closeLoading()
  701. })
  702. }
  703. const rowStyle=( row )=>{
  704. if(row.isPacking){
  705. return { background: '#E6A23C'}
  706. }
  707. if( row.status=='600'){
  708. return { background: '#fff8d9'}
  709. }
  710. if(row.status!='60' ){
  711. return { background: '#b3b3b3'}
  712. }
  713. return ''
  714. }
  715. //条码匹配放到前边
  716. const barcodeMatching = (checkBarcode) => {
  717. return dataList.value.reduce((list, item) => {
  718. if (item.status === '50') {
  719. list.push(item)
  720. return list
  721. }
  722. const itemBarcode = barcodeToUpperCase(item.barcode)
  723. const isMatchingBarcode = itemBarcode === checkBarcode || checkBarcode === item.sku || checkBarcode === item.barcodeOne
  724. if (isMatchingBarcode) {
  725. list.unshift(item) // 匹配条形码的项放到顶部
  726. } else {
  727. list.push(item) // 不匹配的项放到末尾
  728. }
  729. return list
  730. }, [])
  731. }
  732. // 格式化耗材
  733. const getRelatedMaterial = (data) => {
  734. const materialMap = {}
  735. let allocationIdCounter = 0
  736. data.forEach(item => {
  737. if (item.status == '60' && item.qty>0) {
  738. item.relatedMaterial.forEach((material, index) => {
  739. material.oldQty = material.qty * item.qty
  740. material.qty = material.qty * item.qty
  741. material.barcode = material.barCode
  742. material.barcode2 = material.barCode
  743. material.qtyOrdered = null
  744. material.skuName = material.skuDescr
  745. material.quantity = 0
  746. material.status = '600'
  747. material.lotNum = allocationIdCounter++
  748. material.relatedMaterial = []
  749. if (materialMap[material.barcode]) {
  750. materialMap[material.barcode].qty += material.qty
  751. } else {
  752. materialMap[material.barcode] = { ...material }
  753. }
  754. })
  755. }
  756. })
  757. return Object.values(materialMap)
  758. }
  759. //切换波次
  760. const _setOrderNo = () => {
  761. back.value = false
  762. inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '')
  763. }
  764. // 数据刷新
  765. const loadData = () => {
  766. if (!orderNo.value) {
  767. inputBarcodeRef.value?.show('', '请扫描订单/快递单号', '')
  768. return
  769. } else {
  770. setBarcode(orderNo.value)
  771. }
  772. }
  773. onUnmounted(() => {
  774. closeListener()
  775. })
  776. window.onRefresh = loadData
  777. </script>
  778. <style scoped lang="sass">
  779. .container
  780. background: #e9f4ff
  781. .large
  782. .large-title
  783. display: flex
  784. justify-content: space-between
  785. padding: 8px 10px
  786. .large-tips
  787. color: #ed6a0c
  788. flex: 1
  789. .scan-barcode
  790. ::v-deep(.van-cell)
  791. padding: 0 15px
  792. height: 50px
  793. min-height: 50px
  794. display: flex
  795. align-items: center
  796. ::v-deep(.van-field__control)
  797. border-bottom: 2px solid #efefef
  798. font-size: 16px
  799. line-height: 46px
  800. ::v-deep(.van-field__label)
  801. width: unset
  802. font-size: 16px
  803. .input-barcode
  804. ::v-deep(.van-field__control)
  805. border-bottom: 2px solid #0077ff
  806. z-index: 2
  807. font-size: 16px
  808. line-height: 46px
  809. .order-detail
  810. margin-top: 2px
  811. background: #fff
  812. font-size: 14px
  813. .picking-no
  814. display: flex
  815. justify-content: space-between
  816. margin: 0 15px
  817. padding: 8px 0
  818. border-bottom: 1px solid #eaeaeb
  819. .picking-container
  820. padding: 0 15px
  821. text-align: left
  822. border-bottom: 1px solid #eaeaeb
  823. .container-item
  824. line-height: 30px
  825. display: flex
  826. align-items: center
  827. .picking-order-count
  828. display: flex
  829. justify-content: space-between
  830. line-height: 30px
  831. .picking-button
  832. display: flex
  833. justify-content: space-evenly
  834. align-items: center
  835. .picking-button-item
  836. flex: 1
  837. color: #419bff
  838. font-weight: bold
  839. line-height: 35px
  840. border-right: 1px solid #eaeaeb
  841. box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1)
  842. .picking-button-item:last-child
  843. border-right: none
  844. .order-list-box
  845. background: #fff
  846. margin-top: 2px
  847. .order-list
  848. width: 100%
  849. overflow-y: auto
  850. max-height: 60vh
  851. .task-table, .task-table-bin, .task-table-box
  852. width: 100%
  853. table-layout: fixed
  854. border-collapse: collapse
  855. font-size: 15px
  856. .task-table th, .task-table-bin th, .task-table td, .task-table-bin td, .task-table-box th, .task-table-box td
  857. text-align: center
  858. border: 1px solid #ccc
  859. word-wrap: break-word
  860. word-break: break-all
  861. .task-table thead, .task-table-bin thead, .task-table-box thead
  862. background-color: #3f8dff
  863. position: sticky
  864. top: 0
  865. color: white
  866. font-size: 15px
  867. .task-table-bin thead
  868. background-color: #3f8dff
  869. .task-table-bin tbody
  870. background: #cde7ff
  871. .count-input
  872. border-bottom: 2px solid #0077ff
  873. </style>