producePlan.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874
  1. <template>
  2. <ele-modal
  3. :visible.sync="visible"
  4. :closed="cancel"
  5. :title="`${type == 'add' ? '创建' : '编辑'}采购计划`"
  6. custom-class="ele-dialog-form"
  7. :close-on-click-modal="false"
  8. :close-on-press-escape="false"
  9. width="80%"
  10. :maxable="true"
  11. >
  12. <el-form
  13. :model="formData"
  14. ref="formRef"
  15. label-width="120px"
  16. class="ele-body"
  17. :rules="rules"
  18. >
  19. <el-row :gutter="24">
  20. <el-col :span="8">
  21. <el-form-item label="需求类型" prop="demandType">
  22. <el-select
  23. v-model="formData.demandType"
  24. placeholder="请选择"
  25. style="width: 100%"
  26. :disabled="type == 'detail'"
  27. >
  28. <el-option
  29. v-for="item in demandTypeList"
  30. :key="item.dictCode"
  31. :label="item.dictValue"
  32. :value="item.dictCode"
  33. >
  34. </el-option>
  35. </el-select>
  36. </el-form-item>
  37. </el-col>
  38. <el-col :span="8">
  39. <el-form-item label="计划名称" prop="name">
  40. <el-input
  41. placeholder="请选择"
  42. v-model="formData.name"
  43. :readonly="type == 'detail'"
  44. ></el-input>
  45. </el-form-item>
  46. </el-col>
  47. <el-col :span="8">
  48. <el-form-item label="备注" prop="remark">
  49. <el-input
  50. placeholder="备注"
  51. v-model="formData.remark"
  52. :readonly="type == 'detail'"
  53. ></el-input>
  54. </el-form-item>
  55. </el-col>
  56. </el-row>
  57. </el-form>
  58. <el-form :model="formData" ref="tableForm">
  59. <ele-pro-table
  60. ref="table"
  61. :needPage="false"
  62. :columns="columns"
  63. row-key="id"
  64. >
  65. <template v-slot:toolbar>
  66. <el-button
  67. size="small"
  68. type="primary"
  69. icon="el-icon-plus"
  70. class="ele-btn-icon"
  71. @click="produceAdd"
  72. v-if="type != 'detail'"
  73. >
  74. 添加生产计划
  75. </el-button>
  76. </template>
  77. <template v-slot:code="{ row }">
  78. <el-input
  79. placeholder="请输入"
  80. readonly
  81. :value="row.code || row.salesOrderCode"
  82. ></el-input>
  83. </template>
  84. <template v-slot:productionPlanId="{ row }">
  85. <el-link
  86. type="primary"
  87. v-if="!row.productionPlanId"
  88. :underline="false"
  89. @click.native="openVersion(row)"
  90. :disabled="type == 'detail'"
  91. >选择</el-link
  92. >
  93. </template>
  94. <template v-slot:action="{ row, $index }" v-if="type != 'detail'">
  95. <template>
  96. <el-link
  97. type="primary"
  98. :underline="false"
  99. @click="categorySelect(row)"
  100. >
  101. 添加物料
  102. </el-link>
  103. <el-popconfirm
  104. class="ele-action"
  105. title="确定要删除此销售订单吗?"
  106. @confirm="remove(row, $index)"
  107. >
  108. <template v-slot:reference>
  109. <el-link type="danger" :underline="false" icon="el-icon-delete">
  110. 删除
  111. </el-link>
  112. </template>
  113. </el-popconfirm>
  114. </template>
  115. </template>
  116. <!-- 展开内容 -->
  117. <template v-slot:expand="{ row, $index }">
  118. <div
  119. style="
  120. width: calc(100% - 95px);
  121. min-height: 60px;
  122. margin-left: 95px;
  123. "
  124. v-if="row.materialList.length > 0"
  125. >
  126. <ele-pro-table
  127. :toolbar="false"
  128. toolsTheme="none"
  129. ref="table2"
  130. :need-page="false"
  131. :datasource="row.materialList"
  132. :columns="columns2"
  133. :key="row.categoryId + '-' + $index"
  134. >
  135. <template v-slot:sort="{ $index }">
  136. {{ $index }}
  137. </template>
  138. <template v-slot:unit="{ row }">
  139. {{ row.unit }}
  140. </template>
  141. <template v-slot:demandQuantity="{ row }">
  142. <div>
  143. <!-- 需求数量 -->
  144. <el-input
  145. v-model="row.demandQuantity"
  146. placeholder="请输入"
  147. :readonly="type == 'detail'"
  148. @input="
  149. (value) =>
  150. (row.demandQuantity = value.replace(
  151. /^(-)*(\d+)\.(\d\d\d\d\d\d).*$/,
  152. '$1$2.$3'
  153. ))
  154. "
  155. ></el-input>
  156. </div>
  157. </template>
  158. <!-- <template v-slot:purchasingCycle="{ row }">
  159. <el-input
  160. v-model="row.purchasingCycle"
  161. placeholder="请输入"
  162. :readonly="type == 'detail'"
  163. @input="
  164. (value) =>
  165. (row.purchasingCycle = value.replace(
  166. /^(-)*(\d+)\.(\d\d\d\d\d\d).*$/,
  167. '$1$2.$3'
  168. ))
  169. "
  170. >
  171. <template slot="append" v-if="row.purchasingCycle">
  172. {{ row.purchasingCycleUnit }}
  173. </template>
  174. </el-input>
  175. </template> -->
  176. <template v-slot:purchasingCycle="{ row }">
  177. {{ row.purchasingCycle }}{{ row.purchasingCycleUnit }}
  178. </template>
  179. <template v-slot:purchaseQuantity="{ row }">
  180. <el-input
  181. v-model="row.purchaseQuantity"
  182. placeholder="请输入"
  183. :readonly="type == 'detail'"
  184. @input="
  185. (value) =>
  186. (row.purchaseQuantity = value.replace(
  187. /^(-)*(\d+)\.(\d\d\d\d\d\d).*$/,
  188. '$1$2.$3'
  189. ))
  190. "
  191. ></el-input>
  192. </template>
  193. <template v-slot:deliveryMethod="{ row }">
  194. <el-select
  195. clearable
  196. class="ele-block"
  197. v-model="row.deliveryMethod"
  198. placeholder="请选择"
  199. :disabled="type == 'detail'"
  200. >
  201. <el-option
  202. label="一次性到货"
  203. :value="1"
  204. @click.native="row.timeList = null"
  205. />
  206. <el-option
  207. label="分批到货"
  208. :value="2"
  209. @click.native="
  210. handleMethod(row);
  211. row.requireDeliveryTime = null;
  212. "
  213. />
  214. </el-select>
  215. </template>
  216. <template v-slot:requireDeliveryTime="{ row }">
  217. <el-date-picker
  218. style="width: 100%"
  219. clearable
  220. v-model="row.requireDeliveryTime"
  221. type="date"
  222. v-if="row.deliveryMethod == 1"
  223. value-format="yyyy-MM-dd"
  224. placeholder="请选择日期"
  225. :readonly="type == 'detail'"
  226. :pickerOptions="{
  227. disabledDate: (time) =>
  228. time.getTime() <
  229. new Date(new Date().setHours(0, 0, 0, 0)).getTime()
  230. }"
  231. >
  232. </el-date-picker>
  233. <el-link
  234. type="primary"
  235. :underline="false"
  236. v-if="row.deliveryMethod == 2"
  237. @click.native="handleMethod(row)"
  238. >
  239. 设置分批时间
  240. </el-link>
  241. </template>
  242. <template v-slot:imgUrl="{ row }">
  243. <fileUpload
  244. v-model="row.imgUrl"
  245. module="main"
  246. :showLib="false"
  247. :limit="1"
  248. :disabled="type == 'detail'"
  249. />
  250. </template>
  251. <template v-slot:files="{ row }">
  252. <fileUpload
  253. v-model="row.files"
  254. module="main"
  255. :showLib="false"
  256. :limit="1"
  257. :disabled="type == 'detail'"
  258. />
  259. </template>
  260. <template v-slot:action="{ row }" v-if="type != 'detail'">
  261. <el-popconfirm
  262. class="ele-action"
  263. title="确定要删除当前物料吗?"
  264. @confirm="remove2(row)"
  265. >
  266. <template v-slot:reference>
  267. <el-link
  268. type="danger"
  269. :underline="false"
  270. icon="el-icon-delete"
  271. >
  272. 删除
  273. </el-link>
  274. </template>
  275. </el-popconfirm>
  276. </template>
  277. </ele-pro-table>
  278. </div>
  279. </template>
  280. </ele-pro-table>
  281. </el-form>
  282. <div slot="footer" v-if="type != 'detail'">
  283. <el-button @click="cancel">取消</el-button>
  284. <el-button type="primary" @click="confirm">保存</el-button>
  285. </div>
  286. <produceOrder
  287. ref="produceOrderRef"
  288. @chooseOrder="chooseOrder"
  289. ></produceOrder>
  290. <ProductModal ref="productRefs" @chooseModal="chooseModal" />
  291. <ProductionVersion
  292. ref="versionRefs"
  293. @changeProduct="changeProduct"
  294. ></ProductionVersion>
  295. <timeDialog ref="timeDialogRef" @chooseTime="chooseTime"></timeDialog>
  296. </ele-modal>
  297. </template>
  298. <script>
  299. import produceOrder from './produceOrder.vue';
  300. import ProductModal from './ProductModal.vue';
  301. import {
  302. listBomByPlanIdsOverride,
  303. listBomBySalesOrderId,
  304. save,
  305. getById
  306. } from '@/api/materialPlan/index';
  307. import ProductionVersion from '@/components/CreatePlan/ProductionVersion2.vue';
  308. import fileUpload from '@/components/upload/fileUpload';
  309. import timeDialog from './timeDialog';
  310. import dictMixins from '@/mixins/dictMixins';
  311. export default {
  312. components: {
  313. produceOrder,
  314. ProductModal,
  315. ProductionVersion,
  316. fileUpload,
  317. timeDialog
  318. },
  319. mixins: [dictMixins],
  320. data() {
  321. return {
  322. visible: false,
  323. type: 'add',
  324. tableData: [],
  325. xsId: null,
  326. rules: {
  327. demandType: [
  328. {
  329. required: true,
  330. message: '请选择需求类型',
  331. trigger: ['blur', 'change']
  332. }
  333. ],
  334. name: [
  335. {
  336. required: true,
  337. message: '请输入配料计划名称',
  338. trigger: ['blur', 'change']
  339. }
  340. ]
  341. },
  342. formData: {
  343. demandType: '',
  344. name: '',
  345. remark: '',
  346. detailRemoveIds: [],
  347. materialRemoveIds: [],
  348. orderType: 2
  349. },
  350. demandTypeList: [
  351. {
  352. dictCode: '1',
  353. dictValue: '生产性物资采购'
  354. },
  355. {
  356. dictCode: '6',
  357. dictValue: '外协自供料采购'
  358. },
  359. {
  360. dictCode: '7',
  361. dictValue: '外协客供料采购'
  362. }
  363. ]
  364. };
  365. },
  366. computed: {
  367. // 表格列配置
  368. columns() {
  369. const list = [
  370. {
  371. width: 45,
  372. type: 'expand',
  373. columnKey: 'materialList',
  374. align: 'center',
  375. slot: 'expand'
  376. },
  377. {
  378. width: 50,
  379. label: '序号',
  380. type: 'index',
  381. align: 'center',
  382. slot: 'index'
  383. },
  384. {
  385. prop: 'code',
  386. label: '销售订单号',
  387. slot: 'code',
  388. showOverflowTooltip: true,
  389. align: 'center',
  390. minWidth: 170,
  391. sortable: true
  392. },
  393. {
  394. prop: 'customerName',
  395. label: '客户名称',
  396. align: 'center',
  397. showOverflowTooltip: true
  398. },
  399. {
  400. prop: 'deliveryNum',
  401. label: '客户代号',
  402. align: 'center',
  403. showOverflowTooltip: true
  404. },
  405. {
  406. prop: 'productCode',
  407. label: '编码',
  408. align: 'center',
  409. showOverflowTooltip: true,
  410. minWidth: 140
  411. },
  412. {
  413. prop: 'productName',
  414. label: '名称',
  415. align: 'center',
  416. minWidth: 120
  417. },
  418. {
  419. prop: 'model',
  420. label: '型号',
  421. align: 'center',
  422. minWidth: 120
  423. },
  424. {
  425. prop: 'brandNo',
  426. label: '牌号',
  427. align: 'center'
  428. },
  429. {
  430. prop: 'deliveryTime',
  431. label: '交付日期',
  432. align: 'center',
  433. showOverflowTooltip: true
  434. },
  435. {
  436. prop: 'contractNum',
  437. label: '订单数量',
  438. align: 'center'
  439. },
  440. {
  441. prop: 'lackNum',
  442. label: '欠交数量',
  443. align: 'center'
  444. },
  445. {
  446. prop: 'productionPlanId',
  447. label: '工艺路线',
  448. slot: 'productionPlanId',
  449. align: 'center',
  450. minWidth: 110
  451. },
  452. this.type != 'detail'
  453. ? {
  454. columnKey: 'action',
  455. label: '操作',
  456. width: 150,
  457. align: 'center',
  458. resizable: false,
  459. slot: 'action',
  460. showOverflowTooltip: true
  461. }
  462. : ''
  463. ].filter(Boolean);
  464. return list;
  465. },
  466. columns2() {
  467. const list = [
  468. {
  469. width: 50,
  470. label: '序号',
  471. prop: 'sort',
  472. slot: 'sort',
  473. align: 'center'
  474. },
  475. {
  476. label: '物料名称',
  477. prop: 'name',
  478. align: 'center',
  479. minWidth: 120
  480. },
  481. {
  482. label: '物料编码',
  483. prop: 'code',
  484. align: 'center',
  485. minWidth: 120
  486. },
  487. {
  488. label: '牌号',
  489. prop: 'brandNum',
  490. align: 'center'
  491. },
  492. {
  493. prop: 'specification',
  494. label: '规格',
  495. align: 'center',
  496. showOverflowTooltip: true,
  497. minWidth: 110
  498. },
  499. {
  500. label: '型号',
  501. prop: 'modelType',
  502. align: 'center'
  503. },
  504. {
  505. prop: 'inventoryQuantity',
  506. label: '库存',
  507. showOverflowTooltip: true
  508. },
  509. {
  510. prop: 'unit',
  511. label: '计量单位',
  512. showOverflowTooltip: true,
  513. action: 'unit',
  514. slot: 'unit'
  515. },
  516. {
  517. label: '需求数量',
  518. slot: 'demandQuantity',
  519. action: 'demandQuantity',
  520. align: 'center',
  521. minWidth: 120
  522. },
  523. {
  524. label: '采购数量',
  525. slot: 'purchaseQuantity',
  526. action: 'purchaseQuantity',
  527. align: 'center',
  528. minWidth: 120
  529. },
  530. {
  531. label: '到货方式',
  532. slot: 'deliveryMethod',
  533. action: 'deliveryMethod',
  534. align: 'center',
  535. minWidth: 140
  536. },
  537. {
  538. label: '采购周期',
  539. slot: 'purchasingCycle',
  540. action: 'purchasingCycle',
  541. align: 'center',
  542. minWidth: 160
  543. },
  544. {
  545. label: '要求到货时间',
  546. slot: 'requireDeliveryTime',
  547. action: 'requireDeliveryTime',
  548. align: 'center',
  549. minWidth: 180
  550. },
  551. {
  552. label: '图纸',
  553. slot: 'imgUrl',
  554. action: ' imgUrl',
  555. align: 'center',
  556. minWidth: 140
  557. },
  558. {
  559. label: '附件',
  560. slot: 'files',
  561. action: ' files',
  562. align: 'center',
  563. minWidth: 140
  564. },
  565. this.type != 'detail'
  566. ? {
  567. columnKey: 'action',
  568. label: '操作',
  569. width: 70,
  570. align: 'center',
  571. resizable: false,
  572. slot: 'action',
  573. showOverflowTooltip: true
  574. }
  575. : ''
  576. ].filter(Boolean);
  577. return list;
  578. }
  579. },
  580. methods: {
  581. async open(type, row) {
  582. this.type = type;
  583. if (type == 'add') {
  584. this.formData.demandType = '';
  585. this.formData.name = '';
  586. this.formData.remark = '';
  587. }
  588. this.formData.orderType = 2;
  589. this.visible = true;
  590. if (row) {
  591. this.getDetail(row.id);
  592. } else {
  593. this.$nextTick(() => {
  594. this.$refs.table.setData([]);
  595. });
  596. }
  597. },
  598. getDetail(id) {
  599. getById(id).then((res) => {
  600. this.$set(this.formData, 'demandType', res.demandType);
  601. this.$set(this.formData, 'name', res.name);
  602. this.$set(this.formData, 'remark', res.remark);
  603. this.formData['id'] = res.id;
  604. if (res.salesOrderList) {
  605. this.$refs.table.setData([...res.salesOrderList]);
  606. // 展开所有行
  607. this.expandedRowKeys = this.$refs.table
  608. .getData()
  609. ?.map((item) => item.id);
  610. }
  611. this.$nextTick(() => {
  612. this.$refs.table.toggleRowExpansionAll();
  613. this.$forceUpdate();
  614. });
  615. });
  616. },
  617. confirm() {
  618. this.$refs.formRef.validate(async (value) => {
  619. if (value) {
  620. let _arr = this.$refs.table.getData() ?? [];
  621. if (_arr.length == 0) {
  622. this.$message.info('请添加销售订单');
  623. return false;
  624. }
  625. let _arr2 = [];
  626. _arr2 = _arr.map((m) => {
  627. if (
  628. Object.prototype.hasOwnProperty.call(m, 'salesOrderId') &&
  629. m.salesOrderId
  630. ) {
  631. } else {
  632. m.salesOrderId = m.id;
  633. m.salesOrderCode = m.code;
  634. delete m.id;
  635. delete m.code;
  636. }
  637. return {
  638. ...m
  639. };
  640. });
  641. this.formData['type'] = 1;
  642. this.formData['salesOrderList'] = _arr2;
  643. this.formData.baitingType = 2;
  644. await save(this.formData);
  645. this.$message.success('保存成功!');
  646. this.$emit('success');
  647. this.cancel();
  648. }
  649. });
  650. },
  651. cancel() {
  652. this.visible = false;
  653. this.formData = {};
  654. this.formData.detailRemoveIds = [];
  655. this.formData.materialRemoveIds = [];
  656. this.$refs.table.setData([]);
  657. this.$refs.formRef.resetFields();
  658. },
  659. datasource({}) {
  660. return [];
  661. },
  662. reload() {
  663. this.$refs.table.reload();
  664. },
  665. produceAdd() {
  666. this.tableData = this.$refs.table.getData();
  667. this.$refs.produceOrderRef.open(this.tableData);
  668. },
  669. chooseOrder(list) {
  670. let planIds = [];
  671. list.map((m) => {
  672. planIds.push(m.id);
  673. delete m.id;
  674. return {
  675. ...m
  676. };
  677. });
  678. if (planIds.length > 0) {
  679. listBomByPlanIdsOverride({ planIds: planIds }).then((res) => {
  680. res.forEach((m) => {
  681. if (m.materialList.length > 0) {
  682. m.materialList.forEach((p) => {
  683. p.detailId = m.id;
  684. });
  685. }
  686. });
  687. this.$refs.table.setData([...this.tableData, ...res]);
  688. this.$nextTick(() => {
  689. this.$refs.table.toggleRowExpansionAll();
  690. this.$forceUpdate();
  691. });
  692. });
  693. }
  694. },
  695. remove(row, index) {
  696. this.formData.detailRemoveIds.push(row.id);
  697. let _arr = this.$refs.table.getData() || [];
  698. _arr.splice(index, 1);
  699. this.$refs.table.setData([..._arr]);
  700. },
  701. categorySelect(row) {
  702. this.$refs.productRefs.open(row.materialList, row, this.type);
  703. },
  704. chooseModal(data, current) {
  705. data.map((m) => {
  706. m.detailId = current.id;
  707. delete m.id;
  708. return {
  709. ...m
  710. };
  711. });
  712. let tableList = [];
  713. tableList = this.$refs.table.getData();
  714. tableList.forEach((e) => {
  715. if (e.id == current.id) {
  716. if (e.materialList.length == 0) {
  717. e.materialList = data;
  718. } else {
  719. e.materialList = [...e.materialList, ...data];
  720. }
  721. }
  722. });
  723. console.log(tableList);
  724. this.$refs.table.setData([...tableList]);
  725. this.$forceUpdate();
  726. },
  727. remove2(row) {
  728. const data = this.$refs.table.getData() ?? [];
  729. this.formData.materialRemoveIds.push(row.categoryId);
  730. data.forEach((e) => {
  731. if (row.detailId == e.id) {
  732. e.materialList = e.materialList.filter(
  733. (d) => d.categoryId !== row.categoryId
  734. );
  735. }
  736. });
  737. this.$refs.table.setData([...data]);
  738. this.$forceUpdate();
  739. },
  740. openVersion(row) {
  741. this.xsId = row.id;
  742. this.$refs.versionRefs.open();
  743. },
  744. changeProduct(data) {
  745. let param = {
  746. salesOrderIds: [this.xsId],
  747. produceRoutingId: data.id
  748. };
  749. listBomBySalesOrderId(param).then((res) => {
  750. if (!res) return;
  751. const tableList = this.$refs.table.getData();
  752. tableList
  753. .forEach((e) => {
  754. if (e.id == this.xsId) {
  755. res.map((m) => {
  756. m.detailId = this.xsId;
  757. return {
  758. ...m
  759. };
  760. });
  761. e.materialList = res;
  762. this.$nextTick(() => {
  763. this.$refs.table.setData([...tableList]);
  764. this.$refs.table.toggleRowExpansionAll();
  765. });
  766. }
  767. })
  768. .catch((error) => {
  769. console.error('Failed to change product:', error);
  770. });
  771. });
  772. },
  773. handleMethod(row) {
  774. this.$refs.timeDialogRef.open(row);
  775. },
  776. chooseTime(current, timeList) {
  777. let tableList = [];
  778. tableList = this.$refs.table.getData();
  779. tableList.forEach((e) => {
  780. if (e.id == current.detailId) {
  781. e.materialList.forEach((m) => {
  782. if (m.categoryId == current.categoryId) {
  783. m.timeList = timeList || [];
  784. }
  785. });
  786. }
  787. });
  788. this.$refs.table.setData([...tableList]);
  789. this.$forceUpdate();
  790. }
  791. }
  792. };
  793. </script>
  794. <style lang="scss" scoped>
  795. :deep(.el-table__expanded-cell) {
  796. padding-bottom: 30px !important;
  797. border-bottom: 12px solid #ccffcc !important;
  798. }
  799. </style>