selectStockLedgerDialog.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. <template>
  2. <ele-modal
  3. title="库存台账"
  4. :visible.sync="visible"
  5. v-if="visible"
  6. :close-on-click-modal="false"
  7. :before-close="handleClose"
  8. :close-on-press-escape="false"
  9. destroy-on-close
  10. top="5vh"
  11. append-to-body
  12. width="70%"
  13. :maxable="true"
  14. >
  15. <el-card shadow="never">
  16. <item-search @search="reload" ref="refSeavch"></item-search>
  17. <ele-split-layout
  18. width="244px"
  19. allow-collapse
  20. :right-style="{ overflow: 'hidden' }"
  21. >
  22. <div class="ele-border-lighter split-layout-right-content">
  23. <el-tree
  24. :data="treeList"
  25. :props="defaultProps"
  26. ref="treeRef"
  27. :default-expanded-keys="
  28. curNodeData && curNodeData.id ? [curNodeData.id] : []
  29. "
  30. :highlight-current="true"
  31. node-key="id"
  32. :expand-on-click-node="false"
  33. @node-click="handleNodeClick"
  34. ><span class="custom-tree-node" slot-scope="{ node, data }">
  35. <span
  36. >{{ data.factoryName ? data.factoryName + '-' : ''
  37. }}{{ data.name }}</span
  38. >
  39. </span></el-tree
  40. >
  41. </div>
  42. <!-- 表格 -->
  43. <template v-slot:content>
  44. <ele-pro-table
  45. ref="table"
  46. :columns="columns"
  47. :datasource="datasource"
  48. row-key="id"
  49. height="calc(100vh - 480px)"
  50. class="dict-table"
  51. @cell-click="cellClick"
  52. :selection.sync="selection"
  53. :cache-key="cacheKeyUrl"
  54. :page-size="20"
  55. @columns-change="handleColumnChange"
  56. @header-dragend="handleHeaderDragend"
  57. >
  58. <template v-slot:saleCount="{ row }">
  59. <el-input
  60. v-model="row.saleCount"
  61. style="width: 100%"
  62. placeholder="请输入"
  63. type="number"
  64. :min="0"
  65. @input="handleSaleCountInput(row)"
  66. ></el-input>
  67. </template>
  68. <template v-slot:action="{ row }">
  69. <el-radio class="radio" v-model="radio" :label="row.id"
  70. ><i></i
  71. ></el-radio>
  72. </template>
  73. </ele-pro-table>
  74. </template>
  75. </ele-split-layout>
  76. </el-card>
  77. <div slot="footer">
  78. <el-button v-if="!isView" type="primary" size="small" @click="selected"
  79. >选择</el-button
  80. >
  81. <el-button size="small" @click="handleClose">关闭</el-button>
  82. </div>
  83. </ele-modal>
  84. </template>
  85. <script>
  86. import {
  87. getOutindetailtwoList,
  88. getTreeByGroup,
  89. getBatchList,
  90. getCategoryPackageDisposition
  91. } from '@/api/wms';
  92. import ItemSearch from './item-search.vue';
  93. import { contactQueryByCategoryIdsAPI } from '@/api/saleManage/contact';
  94. import tabMixins from '@/mixins/tableColumnsMixin';
  95. export default {
  96. mixins: [tabMixins],
  97. components: { ItemSearch },
  98. props: {
  99. /*已选择的产品*/
  100. data: {
  101. type: Array,
  102. default: () => []
  103. },
  104. /*是否需要供应商*/
  105. isSupplier: {
  106. type: Boolean,
  107. default: false
  108. },
  109. /*是否显示销售数量*/
  110. showSaleCount: {
  111. type: Boolean,
  112. default: false
  113. },
  114. // 是否按库存余额和锁库数量展示可用数量
  115. showStockCount: {
  116. type: Boolean,
  117. default: false
  118. },
  119. cacheKeyUrl: {
  120. type: String,
  121. default: 'eom-sales-selectStockLedgerDialog-202605131603'
  122. },
  123. isView: {
  124. type: Boolean,
  125. default: false
  126. }
  127. },
  128. data() {
  129. return {
  130. visible: false,
  131. currentIndex: null,
  132. radio: null,
  133. dictList: {},
  134. curNodeData: {},
  135. selection: [],
  136. treeData: [],
  137. dimension: '2',
  138. defaultProps: {
  139. children: 'children',
  140. label: 'name'
  141. },
  142. treeList: [],
  143. treeLoading: false,
  144. // cacheKeyUrl: '',
  145. columnsVersion: 1,
  146. tabMixinsInit: false
  147. };
  148. },
  149. watch: {},
  150. computed: {
  151. columns() {
  152. const version = this.columnsVersion;
  153. let data = null;
  154. if (this.currentIndex != -1) {
  155. data = {
  156. action: 'action',
  157. slot: 'action',
  158. align: 'center',
  159. label: '选择',
  160. reserveSelection: true
  161. };
  162. }
  163. if (this.currentIndex == -1) {
  164. data = {
  165. label: '选择',
  166. width: 45,
  167. type: 'selection',
  168. columnKey: 'selection',
  169. align: 'center',
  170. reserveSelection: true,
  171. selectable: (row) => {
  172. if (this.showStockCount) {
  173. return this.getAvailableStockCount(row) != 0;
  174. }
  175. const measureQuantity = parseFloat(row.measureQuantity) || 0;
  176. const lockQuantity = parseFloat(row.lockQuantity) || 0;
  177. const available = measureQuantity - lockQuantity;
  178. return available != 0;
  179. }
  180. };
  181. }
  182. const tempData = [
  183. data,
  184. {
  185. columnKey: 'index',
  186. type: 'index',
  187. width: 50,
  188. align: 'center',
  189. showOverflowTooltip: true,
  190. label: '序号'
  191. },
  192. {
  193. prop: 'categoryCode',
  194. label: '编码',
  195. align: 'center',
  196. showOverflowTooltip: true,
  197. minWidth: 110
  198. },
  199. {
  200. prop: 'categoryName',
  201. label: '名称',
  202. align: 'center',
  203. showOverflowTooltip: true,
  204. minWidth: 110
  205. },
  206. {
  207. prop: 'saleCount',
  208. slot: 'saleCount',
  209. label: '订单数量',
  210. showOverflowTooltip: true,
  211. align: 'center',
  212. minWidth: 120,
  213. hide: !this.showSaleCount
  214. },
  215. {
  216. prop: 'brandNum',
  217. align: 'center',
  218. label: '牌号',
  219. showOverflowTooltip: true
  220. },
  221. this.dimension == '2'
  222. ? {
  223. prop: 'batchNo',
  224. label: '批次号',
  225. align: 'center',
  226. showOverflowTooltip: true
  227. }
  228. : {
  229. width: 1
  230. },
  231. this.dimension == '2'
  232. ? {
  233. prop: 'systemBatchNo',
  234. label: '系统内部批次号',
  235. align: 'center',
  236. minWidth: 180,
  237. showOverflowTooltip: true,
  238. formatter: (row) => row.systemBatchNo || '-'
  239. }
  240. : {
  241. width: 1
  242. },
  243. {
  244. prop: 'categoryModel',
  245. label: '型号',
  246. align: 'center',
  247. showOverflowTooltip: true
  248. },
  249. {
  250. prop: 'specification',
  251. label: '规格',
  252. align: 'center',
  253. showOverflowTooltip: true
  254. },
  255. {
  256. prop: 'colorKey',
  257. label: '颜色',
  258. align: 'center',
  259. showOverflowTooltip: true
  260. },
  261. {
  262. prop: 'modelKey',
  263. label: '机型',
  264. align: 'center',
  265. showOverflowTooltip: true
  266. },
  267. {
  268. prop: 'warehouseName',
  269. label: '仓库',
  270. showOverflowTooltip: true,
  271. align: 'center',
  272. minWidth: 90
  273. },
  274. {
  275. prop: 'inventoryCycle',
  276. label: '存货周期(天)',
  277. align: 'center',
  278. width: 150,
  279. sortable: true,
  280. showOverflowTooltip: true,
  281. hide: this.dimension == 1
  282. },
  283. {
  284. prop: 'measureQuantity',
  285. label: '库存',
  286. showOverflowTooltip: true,
  287. align: 'center',
  288. minWidth: 90,
  289. formatter: (row) =>
  290. this.showStockCount
  291. ? this.getStockCount(row)
  292. : row.measureQuantity
  293. },
  294. {
  295. prop: 'lockQuantity',
  296. label: '锁库数量',
  297. showOverflowTooltip: true,
  298. align: 'center',
  299. minWidth: 90
  300. },
  301. {
  302. prop: 'useQuantity',
  303. label: '可用数量',
  304. showOverflowTooltip: true,
  305. align: 'center',
  306. minWidth: 90,
  307. formatter: (row) => {
  308. if (this.showStockCount) {
  309. return this.getAvailableStockCount(row);
  310. }
  311. // 使用toFixed处理浮点数精度问题,保留4位小数
  312. const measureQuantity = parseFloat(row.measureQuantity) || 0;
  313. const lockQuantity = parseFloat(row.lockQuantity) || 0;
  314. const available = measureQuantity - lockQuantity;
  315. // 使用toFixed避免精度丢失,然后去除末尾多余的0
  316. return available.toFixed(4).replace(/\.?0+$/, '');
  317. }
  318. },
  319. {
  320. prop: 'measureUnit',
  321. label: '单位',
  322. showOverflowTooltip: true,
  323. align: 'center',
  324. minWidth: 90
  325. },
  326. {
  327. prop: 'weight',
  328. label: '重量',
  329. showOverflowTooltip: true,
  330. align: 'center',
  331. minWidth: 90
  332. },
  333. {
  334. prop: 'weightUnit',
  335. label: '重量单位',
  336. showOverflowTooltip: true,
  337. align: 'center',
  338. minWidth: 90
  339. },
  340. {
  341. prop: 'packingUnit',
  342. align: 'center',
  343. label: '包装单位',
  344. showOverflowTooltip: true
  345. },
  346. {
  347. prop: 'reelSpecification',
  348. label: '盘具',
  349. showOverflowTooltip: true,
  350. align: 'center'
  351. }
  352. ];
  353. return this.applyColumnWidth(tempData.filter((item) => !item.hide));
  354. }
  355. },
  356. methods: {
  357. getDictV(code, val) {
  358. if (!this.dictList[code]) return '';
  359. return this.dictList[code].find((item) => item.value == val)?.label;
  360. },
  361. open(item, currentIndex) {
  362. console.log(item, currentIndex);
  363. this.currentIndex = currentIndex;
  364. this.visible = true;
  365. this.getTreeData();
  366. },
  367. async getTreeData() {
  368. try {
  369. this.treeLoading = true;
  370. let res = null;
  371. res = await getTreeByGroup({ type: 2 });
  372. this.treeLoading = false;
  373. if (res?.code === '0') {
  374. this.treeList = res.data;
  375. return this.treeList;
  376. }
  377. } catch (error) {
  378. console.log(error);
  379. }
  380. this.treeLoading = false;
  381. },
  382. /* 表格数据源 */
  383. async datasource({ page, limit, where, order }) {
  384. this.dimension = where.dimension || '2';
  385. let api = where.dimension == '1' ? getOutindetailtwoList : getBatchList;
  386. const data = await api({
  387. ...where,
  388. ...order,
  389. pageNum: page,
  390. size: limit
  391. });
  392. this.getTabColumns();
  393. return data;
  394. },
  395. /* 刷新表格 */
  396. reload(where) {
  397. this.$refs.table.reload({ pageNum: 1, where: where });
  398. },
  399. handleNodeClick(data, node) {
  400. this.curNodeData = data;
  401. if (data.id == this.currentId) {
  402. this.$refs.treeRef.setCurrentKey(null);
  403. this.reload();
  404. } else {
  405. this.reload({ categoryLevelId: data.id });
  406. }
  407. },
  408. async getSupplierObj(productList, queryName) {
  409. try {
  410. let categoryIds = productList
  411. .filter((item) => item.id)
  412. .map((item) => item.id);
  413. if (categoryIds.length > 0) {
  414. return await contactQueryByCategoryIdsAPI({
  415. categoryIds,
  416. isQueryEE: 1
  417. });
  418. } else {
  419. return Promise.resolve({});
  420. }
  421. } catch (e) {
  422. return Promise.resolve({});
  423. }
  424. },
  425. // 单击获取id
  426. cellClick(row) {
  427. if (this.currentIndex == -1) return;
  428. this.current = row;
  429. this.radio = row.id;
  430. },
  431. // 新接口返回 stockCountBase,旧接口使用 measureQuantity 表示当前库存
  432. getStockCount(row) {
  433. if (row.stockCountBase !== undefined && row.stockCountBase !== null) {
  434. return Number(row.stockCountBase);
  435. }
  436. return Number(row.measureQuantity || 0);
  437. },
  438. getAvailableStockCount(row) {
  439. const available =
  440. this.getStockCount(row) - Number(row.lockQuantity || 0);
  441. return Number(available.toFixed(4));
  442. },
  443. // 限制订单数量不超过当前可用数量
  444. handleSaleCountInput(row) {
  445. const available = this.showStockCount
  446. ? this.getAvailableStockCount(row)
  447. : (parseFloat(row.measureQuantity) || 0) -
  448. (parseFloat(row.lockQuantity) || 0);
  449. const val = parseFloat(row.saleCount);
  450. if (!isNaN(val) && val > available) {
  451. this.$message.warning(`订单数量不能超过可用数量(${available})`);
  452. this.$set(row, 'saleCount', available);
  453. }
  454. },
  455. handleClose() {
  456. this.visible = false;
  457. this.current = null;
  458. this.selection = [];
  459. this.$refs.table.clearSelection();
  460. this.radio = '';
  461. },
  462. isSameStockRecord(source, target) {
  463. if (source.batchStockId && target.id) {
  464. return source.batchStockId == target.id;
  465. }
  466. const sourceProductId = source.productId || source.categoryId;
  467. const targetProductId = target.categoryId || target.productId;
  468. const sourceProductCode =
  469. source.productCode || source.categoryCode || source.code;
  470. const targetProductCode =
  471. target.categoryCode || target.productCode || target.code;
  472. const sameProduct =
  473. sourceProductId && targetProductId
  474. ? sourceProductId == targetProductId
  475. : sourceProductCode == targetProductCode;
  476. const sameWarehouse =
  477. !source.warehouseId ||
  478. !target.warehouseId ||
  479. source.warehouseId == target.warehouseId;
  480. return (
  481. sameProduct &&
  482. sameWarehouse &&
  483. source.batchNo == target.batchNo &&
  484. (source.systemBatchNo || '') == (target.systemBatchNo || '')
  485. );
  486. },
  487. async selected() {
  488. if (!this.current && !this.selection.length) {
  489. return this.$message.warning('请至少选择一条数据');
  490. }
  491. if (this.currentIndex != -1) {
  492. if (
  493. this.data.some((item) => this.isSameStockRecord(item, this.current))
  494. ) {
  495. return this.$message.error('选择的库存记录已经存在列表了');
  496. }
  497. } else {
  498. if (
  499. this.selection.some((item) =>
  500. this.data.some((source) => this.isSameStockRecord(source, item))
  501. )
  502. ) {
  503. return this.$message.error('选择的库存记录已经存在列表了');
  504. }
  505. }
  506. let list = this.currentIndex == -1 ? this.selection : [this.current];
  507. //获取供应商
  508. if (this.isSupplier) {
  509. let supplierList = await this.getSupplierObj(list);
  510. list.forEach((item) => {
  511. item['entrustedEnterpriseIdList'] = supplierList[item.id];
  512. if (supplierList[item.id]?.length === 1) {
  513. item['entrustedEnterpriseId'] = supplierList[item.id][0].id;
  514. }
  515. });
  516. }
  517. let idList = list.map((item) => item.categoryId);
  518. // 获取包装规格
  519. let packingSpecification = await getCategoryPackageDisposition({
  520. categoryIds: idList
  521. });
  522. list.forEach((item, index) => {
  523. item.productName = item.categoryName;
  524. item.productCode = item.categoryCode;
  525. item.productCategoryName = '';
  526. item.productBrand = item.brandNum;
  527. item.provenance = item.provenance || [];
  528. if (this.showSaleCount) {
  529. item.saleCount = item.saleCount;
  530. }
  531. item['packageDispositionList'] = packingSpecification
  532. .filter(
  533. (ite) => item.categoryId == ite.categoryId && ite.conversionUnit
  534. )
  535. .sort((a, b) => a.sort - b.sort);
  536. this.$set(list[index], 'totalCount', 1);
  537. // item.totalCount = 1;
  538. item.measuringUnit = item.measureUnit;
  539. //item.weightUnit = item.weightUnit;
  540. // item.warehouseNum = item.measureQuantity;
  541. item.productId = item.categoryId;
  542. item.batchStockId = item.id;
  543. item.id = '';
  544. item.batchNo = this.dimension != 1 ? item.batchNo : '';
  545. item.systemBatchNo =
  546. this.dimension != 1 ? item.systemBatchNo || '' : '';
  547. // item.specification = item.specification;
  548. });
  549. this.$emit('changeParent', list, this.currentIndex);
  550. this.handleClose();
  551. }
  552. }
  553. };
  554. </script>
  555. <style lang="scss" scoped>
  556. .tree_col {
  557. border: 1px solid #eee;
  558. padding: 10px 0;
  559. box-sizing: border-box;
  560. height: 500px;
  561. overflow: auto;
  562. }
  563. .table_col {
  564. padding-left: 10px;
  565. ::v-deep .el-table th.el-table__cell {
  566. background: #f2f2f2;
  567. }
  568. }
  569. .pagination {
  570. text-align: right;
  571. padding: 10px 0;
  572. }
  573. .btns {
  574. text-align: center;
  575. padding: 10px 0;
  576. }
  577. .topsearch {
  578. margin-bottom: 15px;
  579. }
  580. </style>