salesToProduction.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. <template>
  2. <div class="ele-body">
  3. <el-card shadow="never">
  4. <div class="body-title">
  5. <div class="title-left">销售订单转生产计划</div>
  6. <div class="title-right">
  7. <el-button @click="cancel">取消</el-button>
  8. <el-button type="primary" @click="toSubmit" :loading="loading">
  9. 提交计划
  10. </el-button>
  11. </div>
  12. </div>
  13. <el-form
  14. ref="form"
  15. :model="form"
  16. :rules="rules"
  17. label-width="90px"
  18. class="formbox"
  19. >
  20. <el-row :gutter="24">
  21. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  22. <el-form-item label="计划类型:">
  23. <DictSelection
  24. dictName="订单计划类型"
  25. clearable
  26. v-model="form.planType"
  27. disabled
  28. >
  29. </DictSelection>
  30. </el-form-item>
  31. </el-col>
  32. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  33. <el-form-item label="工艺路线:" prop="produceRoutingName">
  34. <el-input
  35. @click.native="openVersion"
  36. placeholder="请选择工艺路线"
  37. readonly
  38. v-model="form.produceRoutingName"
  39. ></el-input>
  40. </el-form-item>
  41. </el-col>
  42. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  43. <el-form-item label="使用改型:" prop="modification">
  44. <el-checkbox
  45. v-model="form.modification"
  46. :true-label="1"
  47. :false-label="0"
  48. ></el-checkbox>
  49. </el-form-item>
  50. </el-col>
  51. </el-row>
  52. <el-row :gutter="24" class="row-intro">
  53. <el-col v-bind="styleResponsive ? { lg: 3, md: 12 } : { span: 3 }">
  54. <el-form-item label="销售单数量:">{{ form.codeNum }}</el-form-item>
  55. </el-col>
  56. <el-col v-bind="styleResponsive ? { lg: 3, md: 12 } : { span: 3 }">
  57. <el-form-item label="订单总数量:">{{
  58. form.contractNum
  59. }}</el-form-item>
  60. </el-col>
  61. <el-col v-bind="styleResponsive ? { lg: 4, md: 12 } : { span: 4 }">
  62. <el-form-item label="订单总重量:">{{
  63. form.sumOrderWeight
  64. }}</el-form-item>
  65. </el-col>
  66. <el-col v-bind="styleResponsive ? { lg: 5, md: 12 } : { span: 5 }">
  67. <el-form-item label="物料编码:">{{
  68. form.productCode
  69. }}</el-form-item>
  70. </el-col>
  71. <el-col v-bind="styleResponsive ? { lg: 6, md: 12 } : { span: 6 }">
  72. <el-form-item label="牌号|型号:"
  73. >{{ form.brandNo }}|{{ form.model }}</el-form-item
  74. >
  75. </el-col>
  76. <el-col v-bind="styleResponsive ? { lg: 3, md: 12 } : { span: 3 }">
  77. <el-form-item label="库存数量:">{{
  78. form.stockCountBase
  79. }}</el-form-item>
  80. </el-col>
  81. </el-row>
  82. <el-table :data="form.salesOrders" border>
  83. <el-table-column label="序号" align="center" width="60">
  84. <template slot-scope="scope">
  85. <span>{{ scope.$index + 1 }}</span>
  86. </template>
  87. </el-table-column>
  88. <el-table-column
  89. label="销售订单号"
  90. align="center"
  91. prop="code"
  92. width="120"
  93. >
  94. </el-table-column>
  95. <el-table-column label="行号" align="center" prop="lineNumber">
  96. </el-table-column>
  97. <el-table-column label="合同数量" align="center" prop="contractNum">
  98. </el-table-column>
  99. <el-table-column
  100. label="合同重量"
  101. align="center"
  102. prop="productSumWeight"
  103. >
  104. </el-table-column>
  105. <el-table-column label="欠交数量" align="center" prop="lackNum">
  106. </el-table-column>
  107. <el-table-column
  108. label="计划生产数量"
  109. align="center"
  110. prop="planProductNum"
  111. width="120"
  112. >
  113. <template slot-scope="scope">
  114. <el-form-item
  115. label-width="0px"
  116. :prop="'salesOrders.' + scope.$index + '.planProductNum'"
  117. :rules="{
  118. required: true,
  119. message: '请输入计划生产数量',
  120. trigger: 'blur'
  121. }"
  122. class="table-item"
  123. >
  124. <el-input
  125. v-model.number="scope.row.planProductNum"
  126. size="small"
  127. oninput="value=value.replace(/[^\d]/g,'')"
  128. style="width: 100%"
  129. placeholder="输入数量"
  130. ></el-input>
  131. </el-form-item>
  132. </template>
  133. </el-table-column>
  134. <el-table-column
  135. label="要求成型数量"
  136. align="center"
  137. prop="requiredFormingNum"
  138. width="120"
  139. >
  140. <template slot-scope="scope">
  141. <el-form-item
  142. label-width="0px"
  143. :prop="'salesOrders.' + scope.$index + '.requiredFormingNum'"
  144. :rules="{
  145. required: true,
  146. message: '请输入要求成型数量',
  147. trigger: 'blur'
  148. }"
  149. class="table-item"
  150. >
  151. <el-input
  152. v-model.number="scope.row.requiredFormingNum"
  153. size="small"
  154. oninput="value=value.replace(/[^\d]/g,'')"
  155. style="width: 100%"
  156. placeholder="输入数量"
  157. ></el-input>
  158. </el-form-item>
  159. </template>
  160. </el-table-column>
  161. <el-table-column
  162. label="按单按库"
  163. align="center"
  164. prop="orderLibraryType"
  165. >
  166. <template slot-scope="{ row }">
  167. {{ getDictValue('按单按库', row.orderLibraryType) }}
  168. </template>
  169. </el-table-column>
  170. <el-table-column label="订单类型" align="center" prop="orderType">
  171. <template slot-scope="{ row }">
  172. {{ getDictValue('订单类型', row.orderType) }}
  173. </template>
  174. </el-table-column>
  175. <el-table-column
  176. label="交付日期"
  177. align="center"
  178. prop="deliveryTime"
  179. width="160"
  180. >
  181. </el-table-column>
  182. <el-table-column
  183. label="要求成型日期"
  184. align="center"
  185. prop="reqMoldTime"
  186. width="180"
  187. >
  188. <template slot-scope="scope">
  189. <el-form-item
  190. label-width="0px"
  191. :prop="'salesOrders.' + scope.$index + '.reqMoldTime'"
  192. :rules="{
  193. required: true,
  194. message: '请选择要求成型日期',
  195. trigger: 'blur'
  196. }"
  197. class="table-item"
  198. >
  199. <el-date-picker
  200. style="width: 100%"
  201. v-model="scope.row.reqMoldTime"
  202. :pickerOptions="{
  203. disabledDate: (time) =>
  204. time.getTime() <
  205. new Date(new Date().setHours(0, 0, 0, 0)).getTime()
  206. }"
  207. type="date"
  208. placeholder="选择日期"
  209. value-format="yyyy-MM-dd"
  210. >
  211. </el-date-picker>
  212. <!-- <el-date-picker
  213. style="width: 100%;"
  214. v-model="scope.row.reqMoldTime"
  215. type="datetime"
  216. placeholder="选择日期"
  217. value-format="yyyy-MM-dd HH:mm:ss">
  218. </el-date-picker> -->
  219. </el-form-item>
  220. </template>
  221. </el-table-column>
  222. <el-table-column label="客户名称" align="center" prop="customerName">
  223. </el-table-column>
  224. <el-table-column label="业务员" align="center" prop="salesman">
  225. </el-table-column>
  226. <el-table-column
  227. label="交付要求"
  228. align="center"
  229. prop="deliveryRequirements"
  230. >
  231. <template slot-scope="{ row }">
  232. {{ getDictValue('交付要求', row.deliveryRequirements) }}
  233. </template>
  234. </el-table-column>
  235. <el-table-column
  236. prop="priority"
  237. label="优先级"
  238. width="100"
  239. align="center"
  240. >
  241. <template slot-scope="{ row }"
  242. >{{ row.priority }}
  243. <div class="sort-wrap">
  244. <i class="el-icon-caret-top" @click="sortTop(row)"></i>
  245. <i class="el-icon-caret-bottom" @click="sortBottom(row)"></i>
  246. </div>
  247. </template>
  248. </el-table-column>
  249. <el-table-column label="操作" align="center" width="70" fixed="right">
  250. <template slot-scope="scope">
  251. <el-button type="text" @click="handleDeleteItem(scope.$index)"
  252. >删除</el-button
  253. >
  254. </template>
  255. </el-table-column>
  256. </el-table>
  257. <div class="add-product" @click="addEquipment">
  258. <i class="el-icon-circle-plus-outline"></i>
  259. </div>
  260. <el-row :gutter="24">
  261. <el-col v-bind="styleResponsive ? { lg: 24, md: 24 } : { span: 24 }">
  262. <el-form-item label="计划备注:">
  263. <el-input
  264. type="textarea"
  265. :rows="4"
  266. v-model="form.notes"
  267. resize="none"
  268. ></el-input>
  269. </el-form-item>
  270. </el-col>
  271. </el-row>
  272. </el-form>
  273. <AdditionalOrder
  274. ref="additionalRefs"
  275. :productCode="form.productCode"
  276. :selectList="form.salesOrders"
  277. @choose="confirmChoose"
  278. ></AdditionalOrder>
  279. <ProductionVersion
  280. ref="versionRefs"
  281. @changeProduct="changeProduct"
  282. ></ProductionVersion>
  283. <PlanSubmit
  284. ref="submitRefs"
  285. :type="$route.query.type"
  286. :info="form"
  287. @publish="publishData"
  288. ></PlanSubmit>
  289. </el-card>
  290. </div>
  291. </template>
  292. <script>
  293. import AdditionalOrder from './components/AdditionalOrder.vue';
  294. import PlanSubmit from './components/plan-submit.vue';
  295. import ProductionVersion from '@/components/CreatePlan/ProductionVersion2.vue';
  296. import {
  297. productionToPlan,
  298. saveSaleToPlan,
  299. updateSaleToPlan,
  300. releaseSave,
  301. getInventory,
  302. getUpdateInfoById,
  303. getProductVersion
  304. } from '@/api/saleOrder';
  305. import dictMixins from '@/mixins/dictMixins';
  306. import { deepClone } from '@/utils/index';
  307. import { getRouteTabKey, removePageTab } from '@/utils/page-tab-util';
  308. import { getCode } from '@/api/codeManagement';
  309. import dayjs from 'dayjs';
  310. export default {
  311. mixins: [dictMixins],
  312. components: {
  313. AdditionalOrder,
  314. ProductionVersion,
  315. PlanSubmit
  316. },
  317. data() {
  318. return {
  319. type: this.$route.query.type,
  320. form: {
  321. planType: 1,
  322. produceRoutingId: '',
  323. stockCountBase: '',
  324. salesOrders: [],
  325. produceRoutingName: ''
  326. },
  327. // 表单验证规则
  328. rules: {
  329. produceRoutingName: [
  330. { required: true, message: '请选择生产版本', trigger: 'change' }
  331. ]
  332. },
  333. // selection: [],
  334. loading: false
  335. };
  336. },
  337. computed: {
  338. // 是否开启响应式布局
  339. styleResponsive() {
  340. return this.$store.state.theme.styleResponsive;
  341. }
  342. },
  343. created() {
  344. this.requestDict('按单按库');
  345. this.requestDict('订单类型');
  346. this.requestDict('交付要求');
  347. if (this.type == 'edit') {
  348. this.getPlanInfo(this.$route.query.id);
  349. } else {
  350. const list = JSON.parse(this.$route.query.selection);
  351. this.getSaleInfo(list);
  352. }
  353. },
  354. methods: {
  355. async getPlanInfo(id) {
  356. const data = await getUpdateInfoById(id);
  357. this.form = data;
  358. },
  359. async _getInventory() {
  360. const res = await getInventory(
  361. this.form.productCode,
  362. this.form.planType
  363. );
  364. this.form.stockCountBase = res;
  365. },
  366. getSaleInfo(params) {
  367. productionToPlan(params).then((res) => {
  368. this.form = deepClone(res);
  369. this.form.salesOrders.map((item, index) => {
  370. item.priority = index + 1;
  371. item.planProductNum = item.lackNum;
  372. item.requiredFormingNum = item.lackNum;
  373. item.reqMoldTime = dayjs(
  374. new Date(item.deliveryTime).getTime() - 3600 * 1000 * 24 * 10
  375. ).format('YYYY-MM-DD');
  376. });
  377. if (this.form.salesOrders.every((itm) => itm.orderType == 2)) {
  378. this.form.planType = 2;
  379. } else if (this.form.salesOrders.every((itm) => itm.orderType == 1)) {
  380. this.form.planType = 1;
  381. } else {
  382. this.form.planType = 3;
  383. }
  384. this._getInventory();
  385. });
  386. },
  387. cancel() {
  388. const key = getRouteTabKey();
  389. this.$router.go(-1);
  390. removePageTab({ key });
  391. },
  392. toSubmit() {
  393. this.$refs.form.validate((valid) => {
  394. if (valid) {
  395. this.mapList();
  396. this.$refs.submitRefs.open();
  397. }
  398. });
  399. },
  400. // 对比日期,计算要求成型重量
  401. mapList() {
  402. var requiredFormingWeight = 0;
  403. var requiredFormingNum = 0;
  404. var productNum = 0;
  405. this.form.salesOrders.map((item, index) => {
  406. requiredFormingWeight =
  407. requiredFormingWeight +
  408. Number(item.productUnitWeight) * Number(item.requiredFormingNum);
  409. requiredFormingNum = requiredFormingNum + item.requiredFormingNum;
  410. productNum += Number(item.planProductNum);
  411. });
  412. this.form.productNum = productNum;
  413. this.form.productUnitWeight =
  414. this.form.salesOrders[0]?.productUnitWeight;
  415. this.form.requiredFormingWeight = requiredFormingWeight.toFixed(2);
  416. this.form.requiredFormingNum = requiredFormingNum;
  417. const collection = deepClone(this.form.salesOrders);
  418. const sortedCollection = collection.sort(
  419. (a, b) => new Date(b.reqMoldTime) - new Date(a.reqMoldTime)
  420. );
  421. let latestData = {};
  422. for (let i = 0; i < sortedCollection.length; i++) {
  423. const data = sortedCollection[i];
  424. if (
  425. !latestData.reqMoldTime ||
  426. new Date(data.reqMoldTime) >= new Date(latestData.reqMoldTime)
  427. ) {
  428. latestData = data;
  429. }
  430. }
  431. this.form.reqMoldTime = latestData.reqMoldTime;
  432. },
  433. sortTop(row) {
  434. if (row.priority <= 1) {
  435. return;
  436. }
  437. let rowN = this.form.salesOrders.find(
  438. (n) => n.priority == row.priority - 1
  439. );
  440. rowN.priority += 1;
  441. row.priority -= 1;
  442. this.form.salesOrders.sort((a, b) => {
  443. return a.priority - b.priority;
  444. });
  445. },
  446. sortBottom(row) {
  447. if (row.priority >= this.form.salesOrders.length) {
  448. return;
  449. }
  450. let rowN = this.form.salesOrders.find(
  451. (n) => n.priority == row.priority + 1
  452. );
  453. rowN.priority -= 1;
  454. row.priority += 1;
  455. this.form.salesOrders.sort((a, b) => {
  456. return a.priority - b.priority;
  457. });
  458. },
  459. againSort() {
  460. this.form.salesOrders.forEach((n, index) => {
  461. n.priority = index + 1;
  462. });
  463. },
  464. // 删除产品
  465. handleDeleteItem(index) {
  466. this.form.salesOrders.splice(index, 1);
  467. },
  468. addEquipment() {
  469. this.$refs.additionalRefs.open(this.form.planType);
  470. },
  471. openVersion() {
  472. this.$refs.versionRefs.open();
  473. },
  474. changeProduct(data) {
  475. this.$set(this.form, 'produceRoutingName', data.name);
  476. this.$set(this.form, 'produceRoutingId', data.id);
  477. },
  478. confirmChoose(list) {
  479. // 取出在弹窗中选中并且不在表格中的数据
  480. const result = list.filter(
  481. (i) => this.form.salesOrders.findIndex((p) => p.id === i.id) === -1
  482. );
  483. // 取出在表格中并且不在弹窗中选中的数据 即取消选中的数据
  484. const del = this.form.salesOrders.filter(
  485. (i) => list.findIndex((p) => p.id === i.id) === -1
  486. );
  487. for (let i = this.form.salesOrders.length - 1; i >= 0; i--) {
  488. for (let j in del) {
  489. if (this.form.salesOrders[i].id === del[j].id) {
  490. this.form.salesOrders.splice(i, 1);
  491. }
  492. }
  493. }
  494. let priority =
  495. this.form.salesOrders[this.form.salesOrders.length - 1]?.priority ||
  496. 0;
  497. this.form.salesOrders = this.form.salesOrders.concat(
  498. result.map((item, index) => {
  499. item.priority = ++priority;
  500. item.planProductNum = item.lackNum;
  501. item.requiredFormingNum = item.lackNum;
  502. item.reqMoldTime = dayjs(
  503. new Date(item.deliveryTime).getTime() - 3600 * 1000 * 24 * 10
  504. ).format('YYYY-MM-DD');
  505. return item;
  506. })
  507. );
  508. this.changeData();
  509. },
  510. changeData() {
  511. var planProductNum = 0;
  512. var productWeight = 0;
  513. this.form.salesOrders.map((item, index) => {
  514. item.priority = index + 1;
  515. planProductNum = planProductNum + item.contractNum;
  516. productWeight = productWeight + Number(item.productSumWeight);
  517. });
  518. this.$set(this.form, 'codeNum', this.form.salesOrders.length);
  519. this.$set(this.form, 'contractNum', planProductNum);
  520. this.$set(this.form, 'sumOrderWeight', productWeight.toFixed(2));
  521. },
  522. async publishData(type) {
  523. const key = getRouteTabKey();
  524. let params = deepClone(this.form);
  525. params.categoryId = params.salesOrders[0]?.categoryId;
  526. if (this.$route.query.type != 'edit') {
  527. delete params.id;
  528. }
  529. if (type === 2) {
  530. this.$confirm('发布工单后不可撤回,确定发布吗?', '发布确认').then(
  531. async () => {
  532. const loading = this.$loading({
  533. lock: true,
  534. fullscreen: true,
  535. text: '工单发布中...'
  536. });
  537. try {
  538. const code = await getCode('product_order_code');
  539. const data = {
  540. productionPlan: params,
  541. workOrder: {
  542. productionPlanCode: params.code,
  543. code: code,
  544. formingNum: params.contractNum,
  545. formingWeight: params.sumOrderWeight,
  546. produceRoutingId: params.produceRoutingId,
  547. status: 4,
  548. model: params.model,
  549. brandNo: params.brandNo,
  550. categoryId: params.categoryId,
  551. productCode: params.productCode,
  552. productName: params.productName
  553. }
  554. };
  555. if (this.$route.query.type == 'edit') {
  556. data.workOrder.productionPlanId = params.id;
  557. }
  558. await releaseSave(data)
  559. .then((res) => {
  560. if (res === 1) {
  561. this.$message.success('工单已发布!');
  562. this.$router.push({
  563. path: '/productionPlan'
  564. });
  565. } else {
  566. this.$confirm(
  567. '生产计划创建成功,但工单发布失败。请前往【生产计划】列表【重新发布】工单',
  568. '提示',
  569. {
  570. confirmButtonText: '返回',
  571. cancelButtonText: '立即前往',
  572. type: 'warning'
  573. }
  574. )
  575. .then(() => {
  576. this.$router.push({
  577. path: '/productionPlan'
  578. });
  579. })
  580. .catch(() => {
  581. this.$router.go(-1);
  582. });
  583. }
  584. removePageTab({ key });
  585. })
  586. .catch(() => {
  587. this.$message.error('发布失败,请重新发布!');
  588. });
  589. } catch (error) {}
  590. loading.close();
  591. }
  592. );
  593. } else {
  594. let request =
  595. this.$route.query.type == 'edit'
  596. ? updateSaleToPlan
  597. : saveSaleToPlan;
  598. request(params)
  599. .then(async (res) => {
  600. // 提交
  601. this.$router.push({
  602. path: '/productionPlan'
  603. });
  604. removePageTab({ key });
  605. })
  606. .catch(() => {
  607. this.$message.error('提交失败,请重新提交!');
  608. });
  609. }
  610. }
  611. }
  612. };
  613. </script>
  614. <style lang="scss" scoped>
  615. .ele-body {
  616. background: #fff;
  617. }
  618. .body-title {
  619. width: 100%;
  620. display: flex;
  621. align-items: center;
  622. justify-content: space-between;
  623. }
  624. .title-left {
  625. font-size: 20px;
  626. color: #333;
  627. }
  628. .formbox {
  629. margin: 20px auto;
  630. }
  631. .row-intro {
  632. border-bottom: 1px dashed #ccc;
  633. margin-bottom: 20px;
  634. }
  635. .sort-wrap {
  636. i {
  637. font-size: 30px;
  638. cursor: pointer;
  639. }
  640. .el-icon-caret-top {
  641. color: red;
  642. }
  643. .el-icon-caret-bottom {
  644. color: #157a2c;
  645. }
  646. }
  647. .add-product {
  648. width: 100%;
  649. display: flex;
  650. align-items: center;
  651. justify-content: flex-end;
  652. font-size: 30px;
  653. color: #1890ff;
  654. margin: 10px 0;
  655. cursor: pointer;
  656. }
  657. .table-item {
  658. margin-bottom: 0;
  659. }
  660. </style>