You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

720 lines
29 KiB

1 year ago
1 year ago
11 months ago
11 months ago
1 year ago
11 months ago
1 year ago
10 months ago
1 year ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
12 months ago
1 year ago
12 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
1 year ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
9 months ago
11 months ago
12 months ago
9 months ago
12 months ago
8 months ago
12 months ago
11 months ago
11 months ago
12 months ago
7 months ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
11 months ago
11 months ago
7 months ago
11 months ago
10 months ago
11 months ago
10 months ago
11 months ago
1 year ago
11 months ago
1 year ago
11 months ago
1 year ago
11 months ago
1 year ago
1 year ago
10 months ago
2 days ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
12 months ago
11 months ago
11 months ago
1 year ago
1 year ago
7 months ago
6 months ago
7 months ago
6 months ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
12 months ago
1 year ago
1 year ago
1 year ago
11 months ago
11 months ago
3 days ago
12 months ago
2 days ago
12 months ago
11 months ago
1 year ago
11 months ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
10 months ago
7 months ago
10 months ago
7 months ago
10 months ago
1 year ago
11 months ago
12 months ago
11 months ago
12 months ago
11 months ago
10 months ago
7 months ago
1 year ago
1 year ago
12 months ago
1 year ago
7 months ago
1 year ago
11 months ago
7 months ago
11 months ago
7 months ago
6 months ago
7 months ago
11 months ago
7 months ago
6 months ago
11 months ago
1 year ago
1 year ago
7 months ago
6 months ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
12 months ago
11 months ago
12 months ago
1 year ago
12 months ago
1 year ago
1 year ago
10 months ago
1 year ago
11 months ago
11 months ago
11 months ago
1 year ago
3 days ago
12 months ago
1 year ago
12 months ago
1 year ago
12 months ago
11 months ago
11 months ago
9 months ago
1 year ago
12 months ago
1 year ago
12 months ago
12 months ago
12 months ago
1 year ago
12 months ago
1 year ago
11 months ago
1 year ago
11 months ago
11 months ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
12 months ago
1 year ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
11 months ago
1 year ago
1 year ago
11 months ago
1 year ago
  1. <template>
  2. <div>
  3. <div class="pda-container" v-loading.fullscreen.lock="fullscreenLoading"
  4. element-loading-background="rgba(255, 255, 255, 0.3)"
  5. element-loading-spinner="el-icon-loading"
  6. :element-loading-text="loadingText">
  7. <div class="status-bar">
  8. <div class="goBack" @click="handleBack"><i class="el-icon-arrow-left"></i>上一页</div>
  9. <div class="goBack">登记到达</div>
  10. <div class="network" style="color: #fff" @click="$router.push({ path: '/' })">🏠首页</div>
  11. </div>
  12. <div style="overflow-y: auto">
  13. <!-- Step 1: 扫描 -->
  14. <div v-if="processFlag === 1">
  15. <div class="scan-box" style="margin: 2px;">
  16. <el-input clearable v-model="scanCode" placeholder="扫描PO条码或输入PO号"
  17. @keyup.enter.native="searchPoList" ref="scanCodeRef" />
  18. </div>
  19. <div class="item-list" v-if="poList.length > 0" style="margin: 2px;">
  20. <el-form label-position="top" style="margin: 3px;">
  21. <el-row :gutter="5"
  22. v-for="(poDetail, index) in poList" :key="index" :class="index < poList.length - 1 ? 'bottom-line-row' : ''">
  23. <el-col :span="8">
  24. <el-form-item label="商品编码"><span>{{ poDetail.partNo }}</span></el-form-item>
  25. </el-col>
  26. <el-col :span="8">
  27. <el-form-item label="行号/下达号"><span>{{ poDetail.lineNo }}/{{ poDetail.releaseNo }}</span></el-form-item>
  28. </el-col>
  29. <el-col :span="8">
  30. <el-form-item label="">
  31. <el-button type="text" class="recvButton" @click="recvLine(poDetail)"
  32. style="margin-top: 10px;margin-left: 20px" size="small">接收</el-button>
  33. </el-form-item>
  34. </el-col>
  35. <el-col :span="24">
  36. <el-form-item label="商品描述"><span>{{ poDetail.description }}</span></el-form-item>
  37. </el-col>
  38. <el-col :span="6" :class="{ mt10: getTextWidth(poDetail.description) > 34 }">
  39. <el-form-item label="订单数量"><span>{{ poDetail.purchaseQty }}</span></el-form-item>
  40. </el-col>
  41. <el-col :span="6" :class="{ mt10: getTextWidth(poDetail.description) > 34 }">
  42. <el-form-item label="待收数量"><span>{{ poDetail.qtyToReceive }}</span></el-form-item>
  43. </el-col>
  44. <el-col :span="6" :class="{ mt10: getTextWidth(poDetail.description) > 34 }">
  45. <el-form-item label="计划数量"><span>{{ poDetail.invQtyToReceive }}</span></el-form-item>
  46. </el-col>
  47. <el-col :span="6" :class="{ mt10: getTextWidth(poDetail.description) > 34 }">
  48. <el-form-item style="margin-left: 20px" label="单位"><span>{{ poDetail.purchaseUOM }}</span></el-form-item>
  49. </el-col>
  50. </el-row>
  51. </el-form>
  52. </div>
  53. </div>
  54. <!-- Step 2: 收货明细 -->
  55. <div v-if="processFlag === 2">
  56. <el-form label-position="top" class="form-section" style="margin: 5px;">
  57. <el-row :gutter="20">
  58. <el-col :span="12"><el-form-item label="PO号码"><el-input v-model="recvItem.orderNo" disabled /></el-form-item></el-col>
  59. <el-col :span="12"><el-form-item label="行号/下达号"><el-input v-model="displayLineWdr" disabled /></el-form-item></el-col>
  60. <el-col :span="12"><el-form-item label="商品编码"><el-input v-model="recvItem.partNo" disabled /></el-form-item></el-col>
  61. <el-col :span="12"><el-form-item label="计量单位"><el-input v-model="recvItem.purchaseUOM" disabled /></el-form-item></el-col>
  62. <el-col :span="24"><el-form-item label="商品名称"><el-input v-model="recvItem.description" disabled /></el-form-item></el-col>
  63. <el-col :span="12"><el-form-item label="订单数量"><el-input v-model="recvItem.purchaseQty" disabled /></el-form-item></el-col>
  64. <el-col :span="12"><el-form-item label="待收数量"><el-input v-model="recvItem.qtyToReceive" disabled /></el-form-item></el-col>
  65. <el-col :span="12">
  66. <el-form-item label="此次接收数量">
  67. <el-input v-model="recvItem.transQty" />
  68. </el-form-item>
  69. </el-col>
  70. <el-col :span="12">
  71. <el-form-item label=" ">
  72. <el-button type="text" @click.stop="handlingUnitStep"
  73. style="font-size: 16px;margin-top: -5px" size="small">包装记录</el-button>
  74. </el-form-item>
  75. </el-col>
  76. <el-col :span="6"> <el-form-item label="高度(mm)">
  77. <el-input v-model="recvItem.height" />
  78. </el-form-item></el-col>
  79. <el-col :span="18"><el-form-item label="送货单信息">
  80. <el-input v-model="recvItem.deliveryNote" />
  81. </el-form-item></el-col>
  82. <el-col :span="12"><el-form-item label="供应商生产日期">
  83. <el-date-picker v-model="recvItem.supplierManufactureDate" type="date" format="yyyy-MM-dd"
  84. value-format="yyyy-MM-dd" placeholder="请选择" style="width: 100%"
  85. inputmode="none"
  86. autocomplete="off"
  87. autocorrect="off"
  88. spellcheck="false" />
  89. </el-form-item></el-col>
  90. <el-col :span="12"><el-form-item label="供应商批次号">
  91. <el-input v-model="recvItem.supplierBatchNo" placeholder="请输入供应商批次号" />
  92. </el-form-item></el-col>
  93. <el-col :span="12"><el-form-item label="WDR">
  94. <el-input v-model="recvItem.wdr" disabled placeholder="请输入WDR" />
  95. </el-form-item></el-col>
  96. <el-col :span="12"><el-form-item label="到达日期">
  97. <el-date-picker v-model="recvItem.arrivalDate" type="datetime" format="yyyy-MM-dd HH:mm:ss"
  98. value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择到达日期" style="width: 100%"
  99. inputmode="none"
  100. autocomplete="off"
  101. autocorrect="off"
  102. spellcheck="false" />
  103. </el-form-item></el-col>
  104. <el-col :span="12"><el-form-item label="库位">
  105. <el-input v-model="recvItem.locationNo" placeholder="请输入库位" /></el-form-item></el-col>
  106. <el-col :span="12"><el-form-item label="批号"><el-input v-model="recvItem.batchNo" placeholder="请输入批号" /></el-form-item></el-col>
  107. <el-col :span="8" style="margin-top: 10px"><el-form-item><el-button type="text" style="font-size: 16px;margin-left: 30px" @click="processFlag = 1">回退</el-button></el-form-item></el-col>
  108. <el-col :span="8" style="margin-top: 10px"><el-form-item><el-button type="text" style="font-size: 16px;margin-left: 20px" @click="receivePo">保存</el-button></el-form-item></el-col>
  109. <el-col :span="8" style="margin-top: 10px"><el-form-item><el-button type="text" style="font-size: 16px;margin-left: 10px" @click="$router.push('/')">退出</el-button></el-form-item></el-col>
  110. </el-row>
  111. </el-form>
  112. </div>
  113. <!-- Step 3: 包装记录 -->
  114. <div v-if="processFlag === 3">
  115. <el-form label-position="top" class="form-section" style="margin: 5px;">
  116. <el-row :gutter="20">
  117. <el-col :span="12"><el-form-item label="商品编码"><el-input v-model="recvItem.partNo" disabled /></el-form-item></el-col>
  118. <el-col :span="12"><el-form-item label="待收数量"><el-input v-model="recvItem.qtyToReceive" disabled /></el-form-item></el-col>
  119. <el-col :span="24"><el-form-item label="商品名称"><el-input v-model="recvItem.description" disabled /></el-form-item></el-col>
  120. <!-- 自动计算开关 -->
  121. <el-col :span="24" style="margin-bottom: 10px;">
  122. <el-form-item>
  123. <el-switch
  124. v-model="autoCalculate"
  125. active-text="自动计算模式"
  126. inactive-text="手动输入模式"
  127. active-color="#17B3A3"
  128. style="display: block;"
  129. >
  130. </el-switch>
  131. <span v-if="autoCalculate" style="font-size: 13px; color: #17B3A3; margin-top: 5px; display: block; font-weight: bold;">
  132. 💡 输入单包装数量后按回车将根据待收数量自动分配包装
  133. </span>
  134. <span v-else style="font-size: 12px; color: #999; margin-top: 5px; display: block;">
  135. 手动输入单包装数量和包装数然后点击"创建"按钮
  136. </span>
  137. </el-form-item>
  138. </el-col>
  139. <el-col :span="8" style="margin-top: 5px"><el-form-item label="单包装数量">
  140. <el-input
  141. v-model="hanlingItem.perQty"
  142. type="number"
  143. :placeholder="autoCalculate ? '输入完按回车或失去焦点' : '请输入单包装数量'"
  144. @blur="autoCalculate && autoCreateHandlingUnits()"
  145. @keyup.enter.native="autoCalculate && autoCreateHandlingUnits()"
  146. />
  147. </el-form-item></el-col>
  148. <el-col :span="8" style="margin-top: 5px" v-if="!autoCalculate"><el-form-item label="包装数">
  149. <el-input v-model="hanlingItem.packageQty" type="number" placeholder="请输入包装数" />
  150. </el-form-item></el-col>
  151. <el-col :span="8" v-if="!autoCalculate" style="margin-top: 16px"><el-form-item>
  152. <el-button type="text" @click="createHandlingUnit" style="font-size: 16px">创建</el-button></el-form-item></el-col>
  153. <el-col :span="8" v-else style="margin-top: 16px"><el-form-item>
  154. <el-button type="text" style="font-size: 16px">确定</el-button></el-form-item></el-col>
  155. <el-table :data="handlingUnit" :row-style="{ height: '30px' }" style="width: 94%; margin-left: 10px;" highlight-current-row>
  156. <el-table-column prop="code" label="序号" />
  157. <el-table-column prop="packageQty" label="包装数" width="80" />
  158. <el-table-column prop="perQty" label="单包装数量" width="100" />
  159. <el-table-column label="操作" v-if="!autoCalculate">
  160. <template slot-scope="scope">
  161. <a @click="removeItem(scope.$index)">删除</a>
  162. </template>
  163. </el-table-column>
  164. </el-table>
  165. <el-col :span="24" v-if="autoCalculate && handlingUnit.length > 0" style="margin-top: 10px;">
  166. <span style="font-size: 12px; color: #999;">
  167. 💡 如需修改请重新输入单包装数量
  168. </span>
  169. </el-col>
  170. <el-col :span="24"><el-form-item><span>合计</span><span style="margin-left: 38%">{{ totalQty }}</span></el-form-item></el-col>
  171. <el-col :span="12"><el-form-item><el-button type="text" @click="processFlag = 2" style="font-size: 18px;margin-left: 60px">回退</el-button></el-form-item></el-col>
  172. <el-col :span="12"><el-form-item><el-button type="text" @click="processFlag = 2" style="font-size: 18px;">确定</el-button></el-form-item></el-col>
  173. </el-row>
  174. </el-form>
  175. </div>
  176. </div>
  177. </div>
  178. </div>
  179. </template>
  180. <script>
  181. import { getPoList, receivePo, printLabel, getNextItemNo, validateLocationForReceiveCase,getPartAttributeInfo } from "@/api/po/po.js";
  182. import { filterRowsByAuthorizedSubSite, getAuthorizedSubSiteCodeList } from "@/utils/subSiteAuth.js";
  183. export default {
  184. data() {
  185. return {
  186. processFlag: 1,
  187. scanCode: '',
  188. poList: [],
  189. recvItem: {},
  190. handlingUnit: [],
  191. hanlingItem: { code: '', qty: '', perQty: '', packageQty: '' },
  192. site:localStorage.getItem('site'),
  193. warehouseId:localStorage.getItem('selectedWarehouse'),
  194. fullscreenLoading: false, // 控制全屏loading
  195. loadingText: '加载中...', // 动态loading文本
  196. autoCalculate: true // 自动计算包装数开关
  197. };
  198. },
  199. computed: {
  200. totalQty() {
  201. // 使用整数运算避免浮点数精度问题
  202. const sumInCents = this.handlingUnit.reduce((sum, item) => {
  203. return sum + Math.round(Number(item.qty) * 10000);
  204. }, 0);
  205. const sum = sumInCents / 10000;
  206. this.recvItem.transQty = sum;
  207. return sum;
  208. },
  209. huKey() {
  210. return `hu_${this.recvItem.poNumber}_${this.recvItem.lineNo}`;
  211. },
  212. displayLineWdr() {
  213. return `${this.recvItem.lineNo || ''}/${this.recvItem.releaseNo}`;
  214. }
  215. },
  216. watch: {
  217. // 监听自动计算开关变化
  218. autoCalculate(newVal, oldVal) {
  219. // 切换模式时清空输入框和HU列表
  220. this.hanlingItem.perQty = '';
  221. this.hanlingItem.packageQty = '';
  222. this.handlingUnit = [];
  223. localStorage.setItem(this.huKey, JSON.stringify(this.handlingUnit));
  224. if (newVal) {
  225. this.$message.success('已切换到自动创建HU模式');
  226. } else {
  227. this.$message.success('已切换到手动输入模式');
  228. }
  229. }
  230. },
  231. methods: {
  232. // 计算“显示宽度”
  233. getTextWidth(text) {
  234. if (!text) return 0
  235. let len = 0
  236. for (let char of text) {
  237. // 中文、全角符号
  238. if (/[\u4e00-\u9fa5\u3000-\u303F\uFF00-\uFFEF]/.test(char)) {
  239. len += 2
  240. } else {
  241. len += 1
  242. }
  243. }
  244. return len
  245. },
  246. handleBack() {
  247. if (this.processFlag === 1) this.$router.back();
  248. else if (this.processFlag === 3) this.processFlag = 2;
  249. else this.processFlag = 1;
  250. },
  251. searchPoList() {
  252. if (!this.scanCode) return this.poList = [];
  253. // 开始搜索时显示loading
  254. this.loadingText = '搜索中...';
  255. this.fullscreenLoading = true;
  256. getPoList({ poNumber: this.scanCode,site: this.site+'%' }).then(({ data }) => {
  257. if (data.code === 0) {
  258. const sourceRows = data.rows || [];
  259. const authorizedSubSiteCodeList = getAuthorizedSubSiteCodeList(this.$store.state.user.subSiteCodeList);
  260. // 接口按site前缀查询会返回同前缀下的所有子site,这里按当前用户授权子site做二次过滤
  261. this.poList = filterRowsByAuthorizedSubSite(sourceRows, authorizedSubSiteCodeList, ['contract']);
  262. } else {
  263. this.$message.error(data.msg || '操作失败');
  264. }
  265. // 搜索完成后让输入框失去焦点
  266. this.$nextTick(() => {
  267. if (this.$refs.scanCodeRef) {
  268. this.$refs.scanCodeRef.blur();
  269. }
  270. });
  271. }).catch(error => {
  272. console.error('搜索失败:', error);
  273. this.$message.error('搜索失败,请重试');
  274. }).finally(() => {
  275. // 搜索完成后关闭loading
  276. this.fullscreenLoading = false;
  277. });
  278. },
  279. async recvLine(row) {
  280. if (row.authorizationRequired=='TRUE') {
  281. return this.$message.warning("该采购订单需要审核,无法接收");
  282. }
  283. if (row.receiveCaseDB!='INVDIR' && row.receiveCaseDB!='QAINV' && row.receiveCaseDB!='ARRINV') {
  284. return this.$message.warning("该采购订单行的收货方式为"+row.receiveCase+",无法接收");
  285. }
  286. if (row.poStatus === 'Stopped' || row.poStatus === 'Closed' || row.poStatus === 'Cancelled' || row.poStatus === 'Planned') {
  287. return this.$message.warning("该采购订单状态为"+row.poStatus+",无法接收");
  288. }
  289. if (row.status === 'Stopped' || row.status === 'Closed' || row.status === 'Cancelled') {
  290. return this.$message.warning("该采购订单行状态为"+row.status+",无法接收");
  291. }
  292. if (row.convFactor !== 1) {
  293. return this.$message.warning("采购计量单位和库存计量单位不一致,无法接收");
  294. }
  295. // 获取下一个itemNo
  296. let nextItemNo = 1;
  297. try {
  298. const { data } = await getNextItemNo({
  299. orderNo: row.orderNo,
  300. lineNo: row.lineNo,
  301. releaseNo: row.releaseNo || ''
  302. });
  303. if (data.code === 0) {
  304. nextItemNo = data.data;
  305. }
  306. } catch (error) {
  307. console.error('获取itemNo失败:', error);
  308. // 失败时使用默认值1
  309. }
  310. let site = this.site;
  311. let partNo = row.partNo;
  312. let height = '';
  313. let locationNo = '';
  314. try {
  315. const { data } = await getPartAttributeInfo({ site, partNo });
  316. if (data.code === 0) {
  317. height = data.data.height;
  318. if (data.partDefLoc && data.partDefLoc.length > 0) {
  319. locationNo = data.partDefLoc[0].warehouse==='INK'?data.partDefLoc[0].locationNo:'';
  320. }
  321. }
  322. } catch (error) {
  323. console.error('获取物料高度失败:', error);
  324. }
  325. this.recvItem = {
  326. ...row,
  327. poNo: row.orderNo || this.scanCode,
  328. dueinQty: row.qtyToReceive || row.invQtyToReceive,
  329. transQty: '',
  330. itemNo: nextItemNo,
  331. batchNo: row.orderNo+'-'+row.lineNo+'-'+row.releaseNo+'-'+nextItemNo,
  332. deliveryDate: row.plannedDeliveryDate || '',
  333. arrivalDate: this.getCurrentDate(),
  334. supplierManufactureDate: row.supplierManufactureDate || '',
  335. supplierBatchNo: row.supplierBatchNo || '',
  336. deliveryNote: row.deliveryNote || '',
  337. wdr:"*",
  338. height: height,
  339. locationNo: locationNo
  340. };
  341. this.processFlag = 2;
  342. },
  343. // 获取当前日期
  344. getCurrentDate() {
  345. const now = new Date();
  346. return now.getFullYear() + '-' +
  347. String(now.getMonth() + 1).padStart(2, '0') + '-' +
  348. String(now.getDate()).padStart(2, '0') + ' ' +
  349. String(now.getHours()).padStart(2, '0') + ':' +
  350. String(now.getMinutes()).padStart(2, '0') + ':' +
  351. String(now.getSeconds()).padStart(2, '0');
  352. },
  353. handlingUnitStep() {
  354. this.processFlag = 3;
  355. this.autoCalculate = false; // 默认使用手动输入模式
  356. const saved = localStorage.getItem(this.huKey);
  357. this.handlingUnit = saved ? JSON.parse(saved) : [];
  358. },
  359. /**
  360. * 自动创建HU列表自动模式
  361. */
  362. autoCreateHandlingUnits() {
  363. const perQty = this.hanlingItem.perQty;
  364. // 验证输入
  365. if (!perQty || isNaN(perQty) || parseFloat(perQty) <= 0) {
  366. this.$message.warning('请输入有效的单包装数量');
  367. return;
  368. }
  369. // 优先使用"此次接收数量",如果没有则使用"待收数量"
  370. let qtyToReceive = parseFloat(this.recvItem.transQty) || 0;
  371. if (qtyToReceive <= 0) {
  372. qtyToReceive = parseFloat(this.recvItem.qtyToReceive) || 0;
  373. }
  374. if (qtyToReceive <= 0) {
  375. this.$message.warning('待收数量为0,无法自动计算包装');
  376. this.hanlingItem.perQty = '';
  377. return;
  378. }
  379. const perQtyValue = parseFloat(perQty);
  380. // 计算需要多少个完整包装
  381. const packageQty = Math.floor(qtyToReceive / perQtyValue);
  382. // 计算余数(最后一个包装的数量)- 使用整数运算避免浮点数精度问题
  383. const remainderInCents = Math.round(qtyToReceive * 10000) - Math.round(perQtyValue * 10000) * packageQty;
  384. const remainder = remainderInCents / 10000;
  385. // 清空现有的HU列表
  386. this.handlingUnit = [];
  387. // 创建完整包装的HU
  388. if (packageQty > 0) {
  389. for (let i = 1; i <= packageQty; i++) {
  390. const qty = perQtyValue;
  391. const code = String(i);
  392. this.handlingUnit.push({
  393. code: code,
  394. perQty: perQtyValue,
  395. packageQty: 1,
  396. qty: qty
  397. });
  398. }
  399. }
  400. // 如果有余数,创建最后一个HU
  401. if (remainder > 0) {
  402. const code = String(this.handlingUnit.length + 1);
  403. this.handlingUnit.push({
  404. code: code,
  405. perQty: remainder,
  406. packageQty: 1,
  407. qty: remainder
  408. });
  409. }
  410. // 保存到localStorage
  411. localStorage.setItem(this.huKey, JSON.stringify(this.handlingUnit));
  412. // 清空输入框
  413. this.hanlingItem.perQty = '';
  414. this.hanlingItem.packageQty = '';
  415. this.$message.success(`自动创建了${this.handlingUnit.length}个HU,合计数量:${qtyToReceive}`);
  416. },
  417. /**
  418. * 手动创建HU手动模式
  419. */
  420. createHandlingUnit() {
  421. const { perQty, packageQty } = this.hanlingItem;
  422. if (!perQty || !packageQty || isNaN(perQty) || isNaN(packageQty)) {
  423. return this.$message.warning("请填写有效的包装信息");
  424. }
  425. // 使用整数运算避免浮点数精度问题
  426. const qty = Math.round(parseFloat(perQty) * 10000) * parseInt(packageQty) / 10000;
  427. const code = String(this.handlingUnit.length + 1);
  428. const newItem = { ...this.hanlingItem, qty, code };
  429. this.handlingUnit.push(newItem);
  430. localStorage.setItem(this.huKey, JSON.stringify(this.handlingUnit));
  431. // 创建HU后清空包装数和单包装数量
  432. this.hanlingItem.perQty = '';
  433. this.hanlingItem.packageQty = '';
  434. },
  435. removeItem(index) {
  436. this.handlingUnit.splice(index, 1);
  437. localStorage.setItem(this.huKey, JSON.stringify(this.handlingUnit));
  438. },
  439. // 清除所有handlingUnit缓存
  440. clearAllHandlingUnitCache() {
  441. this.hanlingItem = { code: '', qty: '', perQty: '', packageQty: '' };
  442. const keys = Object.keys(localStorage);
  443. keys.forEach(key => {
  444. if (key.startsWith('hu_')) {
  445. localStorage.removeItem(key);
  446. }
  447. });
  448. },
  449. // 校验库位
  450. async validateLocation() {
  451. if (!this.recvItem.locationNo || !this.recvItem.receiveCaseDB) {
  452. return;
  453. }
  454. try {
  455. const { data } = await validateLocationForReceiveCase({
  456. site: this.site,
  457. locationId: this.recvItem.locationNo,
  458. receiveCaseDB: this.recvItem.receiveCaseDB
  459. });
  460. if (data.code !== 0) {
  461. this.$message.error(data.msg || '库位校验失败');
  462. // 清空库位输入
  463. this.recvItem.locationNo = '';
  464. }
  465. } catch (error) {
  466. console.error('库位校验失败:', error);
  467. this.$message.error('库位校验失败,请重试');
  468. // 清空库位输入
  469. this.recvItem.locationNo = '';
  470. }
  471. },
  472. async receivePo() {
  473. const item = this.recvItem;
  474. if (item.transQty > item.qtyToReceive) {
  475. try {
  476. await this.$confirm(
  477. `本次入库数量(${item.transQty}) ,超过待收数量(${item.qtyToReceive}),是否继续?`,
  478. '数量超出提示',
  479. {
  480. confirmButtonText: '继续',
  481. cancelButtonText: '取消',
  482. type: 'warning'
  483. }
  484. );
  485. } catch (error) {
  486. // 用户取消
  487. return;
  488. }
  489. }
  490. if (this.fullscreenLoading) return; // 防止重复点
  491. this.loadingText = '提交中...';
  492. this.fullscreenLoading = true;
  493. if (!item.transQty || !item.locationNo || !item.batchNo) {
  494. this.fullscreenLoading = false;
  495. return this.$message.error("请填写完整信息");
  496. }
  497. if(item.qtyToReceive>item.qtyToReceive){
  498. this.fullscreenLoading = false;
  499. return this.$message.error("接收数量不能大于待收数量");
  500. }
  501. // 提交前再次校验库位
  502. try {
  503. const { data: validationData } = await validateLocationForReceiveCase({
  504. site: this.site,
  505. locationId: item.locationNo,
  506. receiveCaseDB: item.receiveCaseDB
  507. });
  508. if (validationData.code !== 0) {
  509. this.fullscreenLoading = false;
  510. return this.$message.error(validationData.msg || '库位校验失败');
  511. }
  512. } catch (error) {
  513. console.error('库位校验失败:', error);
  514. this.fullscreenLoading = false;
  515. return this.$message.error('库位校验失败,请重试');
  516. }
  517. // 构建符合服务端TransDetailDto结构的数据
  518. const receiveData = {
  519. // 基本字段
  520. site: item.contract,
  521. warehouseId: this.warehouseId,
  522. partNo: item.partNo,
  523. partDesc: item.description,
  524. transQty: item.transQty,
  525. batchNo: item.batchNo,
  526. locationNo: item.locationNo,
  527. itemNo: item.itemNo,
  528. wdr: item.wdr || '*',
  529. deliveryDate: item.deliveryDate,
  530. arrivalDate: item.arrivalDate,
  531. supplierBatchNo: item.supplierBatchNo,
  532. deliveryNote: item.deliveryNote,
  533. samplePercent: item.samplePercent || 0,
  534. sampleQty: item.sampleQty || 0,
  535. qtyToReceive: item.qtyToReceive,
  536. engChgLevel: item.engChgLevel,
  537. // PO相关字段
  538. poNo: item.poNumber || item.poNo,
  539. orderNo: item.orderNo,
  540. lineNo: item.lineNo,
  541. releaseNo: item.releaseNo,
  542. receiptNo: item.receiptNo,
  543. orderRef1: item.orderNo,
  544. supplierNo: item.supplierNo,
  545. purchaseUOM: item.purchaseUOM,
  546. receiveCase: item.receiveCase,
  547. receiveCaseDB: item.receiveCaseDB,
  548. inventoryPartDB: item.inventoryPartDB,
  549. // 业务控制字段
  550. needHandlingUnit: item.needHandlingUnit ,
  551. needCheck: item.needCheck ,
  552. warehouseType: item.warehouseType ,
  553. // 日期字段
  554. supplierManufactureDate: item.supplierManufactureDate,
  555. height: item.height,
  556. // 处理单元列表
  557. handlingUnitList: this.handlingUnit.map(hu => ({
  558. perQty: hu.perQty,
  559. packageQty: hu.packageQty
  560. }))
  561. };
  562. receivePo(receiveData).then(({ data }) => {
  563. if (data.code === 0) {
  564. this.$message.success("操作成功");
  565. this.clearAllHandlingUnitCache();
  566. let printLabelType;
  567. if (item.partNo && item.partNo.startsWith("80")) {
  568. printLabelType = '库存成品标签';
  569. } else {
  570. printLabelType = 'BIL标签';
  571. }
  572. this.printViaServer(data.data,item.needCheck,printLabelType); // 调用打印
  573. this.processFlag = 1;
  574. this.scanCode = '';
  575. this.poList = [];
  576. this.recvItem = {};
  577. this.handlingUnit = [];
  578. } else {
  579. this.$message.error(data.msg || '操作失败');
  580. }
  581. }).catch(error => {
  582. console.error('接收失败:', error);
  583. this.$message.error('网络错误,请重试');
  584. }).finally(() => {
  585. this.fullscreenLoading = false; // 结束后关闭loading
  586. });
  587. },
  588. /**
  589. * 通过服务器打印
  590. */
  591. async printViaServer(receiptNo,needCheck,printLabelType) {
  592. this.$emit('print-start')
  593. try {
  594. const printRequest = {
  595. reportId: this.reportId,
  596. zplCode: this.zplCode,
  597. paperSize: this.paperSize,
  598. orientation: this.orientation,
  599. dpi: this.dpi,
  600. userId: localStorage.getItem('userName'),
  601. username: localStorage.getItem('userName'),
  602. site: localStorage.getItem('site'),
  603. receiptNo: receiptNo,
  604. needCheck:needCheck,
  605. labelType:printLabelType
  606. }
  607. const { data } = await printLabel(printRequest)
  608. if (data.code === 200) {
  609. this.$message.success(`打印任务已发送!`)
  610. }
  611. } catch (error) {
  612. console.error('服务器打印失败:', error)
  613. this.$message.error(`打印失败: ${error.message || error}`)
  614. }
  615. },
  616. },
  617. mounted() {
  618. this.$nextTick(() => this.$refs.scanCodeRef.focus());
  619. },
  620. };
  621. </script>
  622. <style scoped>
  623. .mt10 {
  624. margin-top: 10px;
  625. }
  626. .scan-box input {
  627. width: 100%;
  628. padding: 12px;
  629. font-size: 16px;
  630. }
  631. .item-list {
  632. flex: 1;
  633. overflow-y: auto;
  634. margin: 10px 0;
  635. border: 1px solid rgba(200, 200, 200, 0.8);
  636. }
  637. .item-list span {
  638. color: #000;
  639. font-size: 15px;
  640. }
  641. .bottom-line-row {
  642. border-bottom: 1px solid rgba(200, 200, 200, 0.8);
  643. }
  644. .recvButton {
  645. font-size: 16px;
  646. border-radius: 3px;
  647. color: #17b3a3;
  648. }
  649. .item-list .el-row {
  650. cursor: pointer;
  651. transition: background 0.3s;
  652. }
  653. .item-list .el-row:hover {
  654. background: #f5f7fa;
  655. }
  656. .disabled-button {
  657. color: #ccc !important;
  658. cursor: not-allowed !important;
  659. }
  660. .form-section >>> .el-col {
  661. margin-bottom: 2px;
  662. }
  663. .status-bar {
  664. display: flex;
  665. justify-content: space-between;
  666. align-items: center;
  667. background: #17b3a3;
  668. color: white;
  669. }
  670. /* 自定义loading样式 */
  671. .pda-container >>> .el-loading-mask {
  672. background-color: rgba(255, 255, 255, 0.3) !important;
  673. }
  674. .pda-container >>> .el-loading-spinner {
  675. margin-top: -25px;
  676. }
  677. .pda-container >>> .el-loading-spinner .circular {
  678. width: 35px;
  679. height: 35px;
  680. }
  681. .pda-container >>> .el-loading-text {
  682. color: #17b3a3 !important;
  683. font-size: 14px;
  684. font-weight: 500;
  685. margin-top: 10px;
  686. }
  687. </style>