index.blade.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. @extends('layouts.app')
  2. @section('title')点收一体@endsection
  3. @section('content')
  4. <div class="d-none" id="container">
  5. <audio src="{{asset('sound/warning_otherBarcode.mp3')}}" controls="controls" preload id="soundWarning" hidden>
  6. </audio>
  7. <audio src="{{asset('sound/ding.mp3')}}" controls="controls" preload id="soundDing" hidden>
  8. </audio>
  9. <div class="row">
  10. <div class="col-4 p-0 m-0" id="countGoods">
  11. <div class="col-12 text-center bg-white mt-2">
  12. <h4 class="font-weight-bold text-dark">清点模式</h4>
  13. </div>
  14. <div class="card-body">
  15. <div class="row pl-5">
  16. <div class="col-6">
  17. <label class="text-left font-weight-bold text-muted">条码</label>
  18. <input type="text" id="barcode" class="form-control" :disabled="status.barcodeDisable" placeholder="扫入条码"
  19. @focusin="focusOutDocument" @focusout="focusDocument" v-model="inputting.barcode">
  20. </div>
  21. <div v-if="inputMode==='sweepModel'" class="col-6">
  22. <label class="text-left font-weight-bold text-muted">已扫数量</label>
  23. <input type="number" id="amount" class="form-control mt-0" placeholder="" :disabled="status.amountDisable"
  24. v-model="inputting.amount" style='height: 40px;font-size: 1.6em;color:blue;font-weight: bolder;padding: 3px;text-align: center'>
  25. </div>
  26. </div>
  27. <p class="card-text text-muted mt-3 mb-n3 text-center">已完成:</p>
  28. <hr>
  29. <table class="table table-sm table-striped table-bordered" v-if="goodses.length>0">
  30. <tr>
  31. <th class="text-center">条码</th>
  32. <th class="text-center">数量</th>
  33. <th class="text-center">箱规</th>
  34. <th class="text-center">操作</th>
  35. </tr>
  36. <tr v-for="(goods,i) in goodses" :id="'tr_'+i">
  37. <td class="text-center">@{{ goods.barcode }}</td>
  38. <td class="text-center">@{{ goods.amount }}</td>
  39. <td class="text-center">
  40. <span v-if="goods.cast_number">第@{{ goods.cast_number }}箱</span>
  41. </td>
  42. <td class="text-center">
  43. <span v-if="goods.countGoodStatus">
  44. <button class="btn btn-outline-info btn-sm" @click="countGoods(i,goods.barcode)">完成清点</button>
  45. </span>
  46. <button class="btn btn-outline-danger btn-sm" @click="removeGoods($event,goods.barcode)">删</button>
  47. </td>
  48. </tr>
  49. </table>
  50. <hr>
  51. <span class="btn btn-outline-dark btn form-control" style="cursor: pointer" @click="submitExcelData">结束清点</span>
  52. </div>
  53. </div>
  54. <div class="col-8 p-0 m-0" id="receive">
  55. <div class="col-12 text-center bg-white mt-2">
  56. <h4 class="font-weight-bold text-dark">收货模式</h4>
  57. </div>
  58. <div class="card-body m-0">
  59. <div class="col-8 offset-2 row">
  60. <label for="asnno" class="font-weight-bold text-muted ">输入ASN</label>
  61. <input type="text" id="asnno" class="form-control" @focusin="focusOutDocument" autocomplete="off"
  62. @change="getReceiveTaskByAsnNoAndBarcodes()" disabled="true" placeholder="ASN单号" v-model="asnno">
  63. </div>
  64. <table class="table table-sm table-striped" v-if="regroupGoods.length>0">
  65. <hr>
  66. <tr>
  67. <th class="text-center">货主</th>
  68. <th class="text-center">条码</th>
  69. <th class="text-center">清点数量</th>
  70. <th class="text-center">ASN数量</th>
  71. <th class="text-center">差值</th>
  72. <th class="text-center">容器号</th>
  73. <th class="text-center">生产日期</th> <!--lotatt01-->
  74. <th class="text-center">失效日期</th> <!--lotatt02-->
  75. <th class="text-center">批号</th> <!--lotatt04-->
  76. <th class="text-center">属性仓</th> <!--lotatt05-->
  77. <th class="text-center">质量状态</th> <!--lotatt08-->
  78. <th class="text-center">入库日期</th> <!--lotatt03-->
  79. <th class="text-center">操作</th>
  80. </tr>
  81. <tr v-for="(goods,i) in regroupGoods">
  82. <td class="text-center">@{{ goods.customerid }}</td>
  83. <td class="text-center">@{{ goods.sku }}</td>
  84. <td class="text-center">@{{ goods.amount }}</td>
  85. <td class="text-center">@{{ goods.asn_amount }}</td>
  86. <td class="text-center">@{{ goods.diff_val }}</td>
  87. <td class="text-center">
  88. <span v-if="goods.receiveStatus">
  89. <input type="text" autocomplete="off" class="form-control form-control-sm" :class="errors.trackNumber ? 'is-invalid' : ''"
  90. id="trackNumber" v-model="goods.trackNumber">
  91. <span class="invalid-feedback offset-3" role="alert" v-if="errors.trackNumber">
  92. <strong>@{{ errors.trackNumber[0] }}</strong>
  93. </span>
  94. </span>
  95. </td>
  96. <td class="text-center">
  97. <span v-if="goods.basSku.lot_id">
  98. <span v-if="goods.basSku.lot_id.lotkey01==='Y'">
  99. <input type="date" autocomplete="off" class="form-control form-control-sm input-sm" :class="errors.lotatt01 ? 'is-invalid' : ''"
  100. id="lotatt01" v-model="goods.lotatt01">
  101. <span class="invalid-feedback offset-3" role="alert" v-if="errors.lotatt01">
  102. <strong>@{{ errors.lotatt01[0] }}</strong>
  103. </span>
  104. </span>
  105. </span>
  106. </td>
  107. <td class="text-center">
  108. <span v-if="goods.basSku.lot_id">
  109. <span v-if="goods.basSku.lot_id.lotkey02==='Y'">
  110. <input type="date" autocomplete="off" class="form-control form-control-sm input-sm" :class="errors.lotatt02 ? 'is-invalid' : ''"
  111. id="lotatt02" v-model="goods.lotatt02">
  112. <span class="invalid-feedback offset-3" role="alert" v-if="errors.lotatt02">
  113. <strong>@{{ errors.lotatt02[0] }}</strong>
  114. </span>
  115. </span>
  116. </span>
  117. </td>
  118. <td class="text-center">
  119. <span v-if="goods.basSku.lot_id">
  120. <span v-if="goods.basSku.lot_id.lotkey04==='Y'">
  121. <input type="text" autocomplete="off" class="form-control form-control-sm input-sm" :class="errors.lotatt04 ? 'is-invalid' : ''"
  122. id="lotatt04" v-model="goods.lotatt04">
  123. <span class="invalid-feedback offset-3" role="alert" v-if="errors.lotatt04">
  124. <strong>@{{ errors.lotatt04[0] }}</strong>
  125. </span>
  126. </span>
  127. </span>
  128. </td>
  129. <td class="text-center">
  130. <span v-if="goods.basSku.lot_id">
  131. <span v-if="goods.basSku.lot_id.lotatt05==='Y'">
  132. <select class="form-control form-control-sm" :disabled="goods.basSku.lot_id.lotkey05==='N'"
  133. id="lotatt05" v-model="goods.lotatt05" :class="errors.lotatt05 ? 'is-invalid' : ''">
  134. <option v-for="(attributeLocation,i) in attributeLocations"
  135. :value="attributeLocation.code">@{{ attributeLocation.codename_c }}</option>
  136. </select>
  137. <span class="invalid-feedback offset-3" role="alert" v-if="errors.lotatt05">
  138. <strong>@{{ errors.lotatt05[0] }}</strong>
  139. </span>
  140. </span>
  141. </span>
  142. </td>
  143. <td class="text-center">
  144. <span v-if="goods.basSku.lot_id">
  145. <span v-if="goods.basSku.lot_id.lotkey08==='Y'">
  146. <select class="form-control form-control-sm" :class="errors.lotatt08 ? 'is-invalid' : ''"
  147. id="lotatt08" v-model="goods.lotatt08">
  148. <option v-for="(quality,i) in qualityStatus" :value="quality.code">@{{ quality.codename_c }}</option>
  149. </select>
  150. <span class="invalid-feedback offset-3" role="alert" v-if="errors.lotatt08">
  151. <strong>@{{ errors.lotatt08[0] }}</strong>
  152. </span>
  153. </span>
  154. </span>
  155. </td>
  156. <td class="text-center">
  157. <span v-if="goods.basSku.lot_id">
  158. <span v-if="goods.basSku.lot_id.lotkey03==='Y'">
  159. <input type="date" autocomplete="off" class="form-control form-control-sm input-sm" :class="errors.lotatt03 ? 'is-invalid' : ''"
  160. :disabled="goods.basSku.lot_id.lotkey03==='N'" id="lotatt03" v-model="goods.lotatt03">
  161. <span class="invalid-feedback offset-3" role="alert" v-if="errors.lotatt03">
  162. <strong>@{{ errors.lotatt03[0] }}</strong>
  163. </span>
  164. </span>
  165. </span>
  166. </td>
  167. <td class="text-center">
  168. <span v-if="goods.receiveStatus&&goods.diff_val>=0">
  169. <button class="btn btn-outline-info btn-sm" @click="receiveGoods(goods)">收</button>
  170. </span>
  171. </td>
  172. </tr>
  173. </table>
  174. <span class="btn btn-outline-dark btn form-control mt-5" v-if="status.finishReceiveButton"
  175. style="cursor: pointer" @click="finishReceive()">结束收货</span>
  176. </div>
  177. </div>
  178. </div>
  179. </div>
  180. @endsection
  181. @section('lastScript')
  182. <script>
  183. new Vue({
  184. el: '#app',
  185. data:{
  186. qualityStatus:{!! $qualityStatus !!},
  187. attributeLocations:{!! $attributeLocations !!},
  188. focusing:'document',
  189. lastScannedBarcode:'',
  190. inputMode:'sweepModel',
  191. inputting:{
  192. barcode:'',amount:'',fromIncreasing:true,cast_number:'',countGoodStatus:true
  193. },
  194. status:{
  195. scanEndInputted:false,barcodeDisable:true,amountDisable:true,finishReceiveButton:false,
  196. },
  197. asnno:'',
  198. basSku:{
  199. lot_id:'',
  200. },
  201. goodses:[],
  202. initGoods:[],
  203. regroupGoods:[],
  204. errors:{},
  205. },
  206. mounted() {
  207. if (navigator.userAgent.indexOf("Android")!==-1)this.isAndroid = true;
  208. this.pageInit();
  209. $("#container").removeClass("d-none");
  210. this.scanListening();
  211. },
  212. methods: {
  213. //页面初始化
  214. pageInit(){
  215. if (!this.isAndroid)return;
  216. let element = document.getElementById("navbarSupportedContent").parentElement;
  217. element.className = "row";
  218. element.children[0].className += " col-5";
  219. element.innerHTML = element.children[0].outerHTML;
  220. let e1 = document.getElementById("menu");
  221. let e2 = document.getElementById("demand-div");
  222. if (e1)e1.remove();
  223. if (e2)e2.remove();
  224. },
  225. focusDocument: function () {
  226. this.focusing = 'document';
  227. },
  228. focusOutDocument: function () {
  229. this.focusing = '';
  230. },
  231. scanListening: function () {
  232. let data = this;
  233. $(document).on('keypress', function (e) {
  234. if(data.focusing!=='document'){return}
  235. if (e.keyCode !== 13) {
  236. if(data.status.scanEndInputted){
  237. data.lastScannedBarcode=data.inputting.barcode;
  238. data.inputting.barcode='';
  239. data.status.scanEndInputted=false;
  240. }
  241. data.inputting.barcode += String.fromCharCode(e.keyCode);
  242. } else {
  243. if(data.inputting.barcode.length<=1){
  244. window.tempTip.setDuration(4500);
  245. window.tempTip.show('未扫入条码,请检查扫码枪设置,尝试调至“直接键盘输出”模式');
  246. return;
  247. }
  248. data.status.scanEndInputted = true;
  249. switch(data.inputMode){
  250. case 'sweepModel': data.commitGoodsOnSweepModel();break;
  251. }
  252. }
  253. });
  254. },
  255. commitGoodsOnSweepModel(){
  256. let repeatedBarcode=this.repeatedIncreasingBarcodeFromSaved();
  257. if(!repeatedBarcode){
  258. this.focusOutDocument();
  259. this.alertVibrate();
  260. this.inputting.amount=1;
  261. this.goodses.unshift(JSON.parse(JSON.stringify(this.inputting)));
  262. window.tempTip.setDuration(500);
  263. window.tempTip.showSuccess('保存成功');
  264. this.focusDocument();
  265. this.audioDing();
  266. }else{
  267. repeatedBarcode.amount++;
  268. this.inputting.amount=repeatedBarcode.amount;
  269. window.tempTip.setDuration(500);
  270. window.tempTip.showSuccess(repeatedBarcode.amount);
  271. this.focusDocument();
  272. this.audioDing();
  273. }
  274. },
  275. audioDing: function () {
  276. let audio = document.getElementById('soundDing');
  277. audio.currentTime = 0;//重新播放
  278. audio.play();// 播放
  279. function startVibrate(duration) {
  280. if (navigator.vibrate) {
  281. navigator.vibrate(duration);
  282. } else if (navigator.webkitVibrate) {
  283. navigator.webkitVibrate(duration);
  284. }
  285. }
  286. startVibrate(500);
  287. },
  288. alertVibrate: function () {
  289. function startVibrate(duration) {
  290. if (navigator.vibrate) {
  291. navigator.vibrate(duration);
  292. } else if (navigator.webkitVibrate) {
  293. navigator.webkitVibrate(duration);
  294. }
  295. }
  296. let vibrateInterval = setInterval(function() {
  297. startVibrate(150);
  298. }, 50);
  299. setTimeout(function() {
  300. clearInterval(vibrateInterval)
  301. }, 2000);
  302. },
  303. repeatedIncreasingBarcodeFromSaved: function () {
  304. let data = this;
  305. let repeatedGoods=null;
  306. data.goodses.every(function(goods){
  307. if(goods.barcode===data.inputting.barcode && goods.fromIncreasing && goods.countGoodStatus){
  308. repeatedGoods=goods;
  309. return false;
  310. }
  311. return true;
  312. });
  313. return repeatedGoods;
  314. },
  315. cleanInputs: function () {
  316. this.inputting.barcode='';
  317. this.inputting.amount='';
  318. this.inputting.cast_number='';
  319. this.lastScannedBarcode='';
  320. },
  321. countGoods: function (i,barcode) {
  322. if(!confirm('确定要结束清点'+barcode+'的记录吗'))return;
  323. let data = this;
  324. let cast_number=0;
  325. data.goodses.forEach(function (goods) {
  326. if (goods.barcode===barcode)cast_number++
  327. });
  328. data.goodses.every(function(goods,i){
  329. if(goods.barcode===barcode &&goods.countGoodStatus){
  330. goods.countGoodStatus=false;
  331. goods.cast_number=cast_number;
  332. return false;
  333. }
  334. return true;
  335. })
  336. $('#tr_'+i).attr("class",'alert alert-dark');
  337. },
  338. removeGoods: function ($e,barcode) {
  339. if(!confirm('确定要删除条码为'+barcode+'的记录吗'))return;
  340. let data = this;
  341. data.goodses.every(function(goods,i){
  342. if(goods.barcode===barcode){
  343. data.goodses.splice(i,1)
  344. return false;
  345. }
  346. return true;
  347. })
  348. },
  349. regroupArr(arr){
  350. let a =JSON.parse(JSON.stringify(arr));
  351. return a.reduce((total, cur, index) => {
  352. let hasValue = total.findIndex(current => {
  353. return current.barcode === cur.barcode
  354. })
  355. hasValue === -1 && total.push(cur)
  356. hasValue !== -1 && (total[hasValue].amount = total[hasValue].amount + cur.amount)
  357. return total
  358. }, []);
  359. },
  360. assignRegroupGoods(){
  361. let data=this;
  362. data.initGoods=data.regroupArr(data.goodses);
  363. data.initGoods.forEach(function (obj){
  364. delete obj.fromIncreasing;
  365. delete obj.cast_number;
  366. delete obj.countGoodStatus;
  367. obj.asnno='';obj.customerid='';
  368. obj.location='';obj.asn_amount='';
  369. obj.diff_val='';obj.receiveStatus=false;
  370. obj.basSku=data.basSku;obj.asnlineno='';
  371. obj.lotatt01='';obj.lotatt02='';
  372. obj.lotatt03='';obj.lotatt04='';
  373. obj.lotatt05='';obj.lotatt08='';
  374. })
  375. data.initGoods=JSON.parse(JSON.stringify(data.initGoods).replace(/barcode/g,"sku"));
  376. data.regroupGoods=data.initGoods;
  377. },
  378. receiveGoods(goods){
  379. let data=this;
  380. this.errors={};
  381. this.checkGood(goods);
  382. if (JSON.stringify(this.errors)!=='{}')return;
  383. goods.asnno=data.asnno;
  384. tempTip.setDuration(99999);
  385. tempTip.waitingTip('提交中');
  386. let url='{{url("store/countGoodsAndReceive/fluxReceive")}}';
  387. axios.post(url,{good:goods})
  388. .then(function(res){
  389. if(res.data.success){
  390. data.regroupGoods.forEach(function (good,i){
  391. if (goods.sku==good.sku){
  392. data.regroupGoods.splice(i,1)
  393. }
  394. })
  395. tempTip.setDuration(2000);
  396. tempTip.cancelWaitingTip();
  397. tempTip.showSuccess(res.data.data);
  398. }else{
  399. tempTip.setDuration(3000);
  400. tempTip.cancelWaitingTip();
  401. tempTip.show(res.data.data);
  402. data.alertVibrate()
  403. }
  404. })
  405. .catch(function (err) {
  406. tempTip.setDuration(2000);
  407. tempTip.cancelWaitingTip();
  408. tempTip.show("网络错误:"+err);
  409. data.alertVibrate()
  410. });
  411. },
  412. checkGood(good){
  413. let error = {};
  414. if (!good.trackNumber)error.trackNumber = ["容器号必填"];
  415. if (good.basSku.lot_id && good.basSku.lot_id.lotkey01==='Y' && !good.lotatt01) error.lotatt01=["生产日期为选"];
  416. if (good.basSku.lot_id && good.basSku.lot_id.lotkey02==='Y' && !good.lotatt02) error.lotatt02=["失效日期为选"];
  417. if (good.basSku.lot_id && good.basSku.lot_id.lotkey03==='Y' && !good.lotatt03) error.lotatt03=["入库日期为选"];
  418. if (good.basSku.lot_id && good.basSku.lot_id.lotkey04==='Y' && !good.lotatt04) error.lotatt04=["批号未填"];
  419. if (good.basSku.lot_id && good.basSku.lot_id.lotkey05==='Y' && !good.lotatt05) error.lotatt05=["属性仓未选"];
  420. if (good.basSku.lot_id && good.basSku.lot_id.lotkey08==='Y' && !good.lotatt08) error.lotatt08=["质量状态未选"];
  421. if (JSON.stringify(error)!=='{}'){this.errors = error;}
  422. },
  423. getReceiveTaskByAsnNoAndBarcodes(){
  424. let data=this;
  425. data.regroupGoods=data.initGoods;//验证asn下商品时 初始商品数据
  426. if(data.asnno===''||data.asnno===null||data.asnno===undefined) window.tempTip.show('请先输入ASN单号!');
  427. if (data.asnno && data.asnno.indexOf('ASN')===-1)window.tempTip.show('无效ASN号!');
  428. let url='{{url("store/countGoodsAndReceive/getReceiveTaskByAsnNoAndBarcodes")}}';
  429. window.axios.post(url,{asnno:data.asnno,goods:data.initGoods})
  430. .then(res=>{
  431. if (res.data.success){
  432. data.regroupGoods=res.data.data;
  433. this.$forceUpdate()
  434. }else {
  435. tempTip.setDuration(3000);
  436. tempTip.show(res.data.data);
  437. }
  438. }).catch(err=>{
  439. window.tempTip.setDuration(2000);
  440. window.tempTip.show("网络错误:"+err);
  441. })
  442. },
  443. removeDisable(){
  444. let asnNo=$('#asnno');
  445. asnNo.removeAttr('disabled');
  446. this.status.finishReceiveButton=true;
  447. asnNo.focus();
  448. },
  449. submitExcelData: function () {
  450. let data = this;
  451. data.focusOutDocument();
  452. if(data.goodses.length===0){
  453. window.tempTip.show('请先录入数据再提交收货');return;
  454. }
  455. window.tempTip.confirm('请检查表格,确定全部完成。',
  456. function () {
  457. window.tempTip.setInputType('input');
  458. window.tempTip.setIndex(999999);
  459. window.tempTip.inputVal('收货前将上传excel文件,请输入文件名:',function(val){
  460. if(val===''){
  461. window.tempTip.setDuration(2500);
  462. window.tempTip.show('失败!文件名不能为空,请重新点击生成');
  463. return;
  464. }
  465. window.tempTip.setDuration(15500);
  466. window.tempTip.waitingTip('提交Excel中...');
  467. let expireHandler = setTimeout(function () {
  468. window.tempTip.cancelWaitingTip();
  469. window.tempTip.setDuration(1500);
  470. window.tempTip.show('响应超时! 请检查网络,确保可以连接后再重试。');
  471. },9000);
  472. let url='{{url("store/countGoodsAndReceive/createExcel")}}';
  473. axios.post(url,{'goodses':data.goodses,'filename':val})
  474. .then(function(response){
  475. if(response.data.result==='success'){
  476. data.removeDisable();//收货解锁asnno input标签
  477. data.assignRegroupGoods();//结束清点,重组收货所需数据
  478. data.goodses=[];
  479. data.cleanInputs();
  480. window.tempTip.cancelWaitingTip();
  481. window.tempTip.setDuration(1500);
  482. window.tempTip.showSuccess('成功生成EXCEL,可在列表页查看');
  483. }else{
  484. window.tempTip.setDuration(1500);
  485. window.tempTip.show('生成EXCEL失败');
  486. data.focusDocument();
  487. data.alertVibrate()
  488. }
  489. clearInterval(expireHandler);
  490. })
  491. .catch(function (err) {
  492. window.tempTip.setDuration(3500);
  493. window.tempTip.show('网络或系统错误,请将以下信息提交给开发者:'+err);
  494. clearInterval(expireHandler);
  495. data.alertVibrate();
  496. });
  497. });
  498. },function () {
  499. data.focusDocument();
  500. })
  501. },
  502. finishReceive(){
  503. if(!confirm('确定要结束收货吗?'))return;
  504. setTimeout(function (){window.location.reload()},10)
  505. }
  506. },
  507. });
  508. </script>
  509. @endsection