unqualifiedProduct.vue 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  1. <template>
  2. <div class="ele-body">
  3. <el-card shadow="never" v-loading="loading">
  4. <ele-pro-table
  5. ref="table"
  6. :columns="columns"
  7. :datasource="displayPoList"
  8. :selection.sync="selection"
  9. :needPage="false"
  10. row-key="uniqueKey"
  11. max-height="500px"
  12. >
  13. <!-- 操作列 -->
  14. <template v-slot:toolbar v-if="showBtn">
  15. <el-button type="primary" slot="reference" @click="add"
  16. >新增</el-button
  17. >
  18. <el-button
  19. type="primary"
  20. slot="reference"
  21. :disabled="selection.length == 0"
  22. @click="disposeFn(1)"
  23. >批量处置</el-button
  24. >
  25. <el-popconfirm
  26. class="ele-action"
  27. title="确定要删除吗?"
  28. @confirm="remove"
  29. style="margin-left: 10px"
  30. >
  31. <template v-slot:reference>
  32. <el-button :disabled="selection.length == 0" type="danger"
  33. >批量删除</el-button
  34. >
  35. </template>
  36. </el-popconfirm>
  37. </template>
  38. <template v-slot:action="{ row }">
  39. <el-link
  40. slot="reference"
  41. type="primary"
  42. :underline="false"
  43. icon="el-icon-edit"
  44. v-if="row.disposalStatus != 2"
  45. @click="disposeFn(2, row)"
  46. >
  47. 处置
  48. </el-link>
  49. </template>
  50. <template v-slot:badTypeName="{ row, $index }">
  51. <el-select
  52. v-model="row.badTypeId"
  53. placeholder="请选择不良类型"
  54. size="small"
  55. style="width: 100%"
  56. remote
  57. filterable
  58. clearable
  59. >
  60. <el-option
  61. v-for="item in badTypeList"
  62. :key="item.id"
  63. :label="item.name"
  64. :value="item.id"
  65. @click="row.badTypeName = item.name"
  66. >
  67. </el-option>
  68. </el-select>
  69. </template>
  70. <template v-slot:badNameName="{ row, $index }">
  71. <el-select
  72. v-model="row.badNameId"
  73. placeholder="请选择不良名称"
  74. size="small"
  75. style="width: 100%"
  76. remote
  77. filterable
  78. clearable
  79. >
  80. <el-option
  81. v-for="item in badNameList"
  82. :key="item.id"
  83. :label="item.name"
  84. :value="item.id"
  85. @click="row.badNameName = item.name"
  86. >
  87. </el-option>
  88. </el-select>
  89. </template>
  90. <template v-slot:reasonTypeName="{ row, $index }">
  91. <el-select
  92. v-model="row.reasonTypeId"
  93. placeholder="请选择原因类型"
  94. size="small"
  95. style="width: 100%"
  96. remote
  97. filterable
  98. clearable
  99. >
  100. <el-option
  101. v-for="item in reasonTypeList"
  102. :key="item.id"
  103. :label="item.name"
  104. :value="item.id"
  105. @click="row.reasonTypeName = item.name"
  106. >
  107. </el-option>
  108. </el-select>
  109. </template>
  110. <template v-slot:unqualifiedReason="{ row, $index }">
  111. <el-input v-model="row.unqualifiedReason"></el-input>
  112. </template>
  113. </ele-pro-table>
  114. </el-card>
  115. <ele-modal
  116. :visible.sync="dialogVisible"
  117. width="35%"
  118. @close="close"
  119. append-to-body
  120. title="处置"
  121. >
  122. <el-form
  123. ref="disposeForm"
  124. :model="disposeForm"
  125. label-width="150px"
  126. :rules="rules"
  127. >
  128. <el-form-item label="处置方式:" prop="disposeType">
  129. <el-select
  130. v-model="disposeForm.disposeType"
  131. placeholder="请选择"
  132. style="width: 100%"
  133. @change="disposeTypeChange"
  134. >
  135. <el-option
  136. v-for="item in disposeList"
  137. :key="item.value"
  138. :label="item.label"
  139. :value="item.value"
  140. >
  141. </el-option>
  142. </el-select>
  143. </el-form-item>
  144. <template
  145. v-if="disposeForm.disposeType == 1 || disposeForm.disposeType == 2"
  146. >
  147. <el-form-item label="回流工序" prop="taskId" align="center">
  148. <el-select
  149. style="width: 100%"
  150. v-model="disposeForm.taskId"
  151. clearable
  152. >
  153. <el-option
  154. v-for="item in refluxTaskList"
  155. :key="item.taskId"
  156. :value="item.taskId"
  157. :label="item.taskTypeName"
  158. ></el-option>
  159. </el-select>
  160. </el-form-item>
  161. </template>
  162. <template v-if="disposeForm.disposeType == 6">
  163. <el-row>
  164. <el-col :span="24">
  165. <el-form-item
  166. label="留样数量:"
  167. prop="keepSampleQuantity"
  168. align="center"
  169. >
  170. <el-input
  171. v-model="disposeForm.keepSampleQuantity"
  172. placeholder="请输入"
  173. style="width: 100%"
  174. >
  175. <template slot="append">{{
  176. current?.measureUnit
  177. }}</template></el-input
  178. >
  179. </el-form-item>
  180. </el-col>
  181. <el-col :span="6"> </el-col>
  182. </el-row>
  183. <el-row style="margin-top: 12px">
  184. <el-col :span="24">
  185. <el-form-item label="留样日期:" prop="sampleDate" align="center">
  186. <el-date-picker
  187. class="w100"
  188. v-model="disposeForm.sampleDate"
  189. type="date"
  190. value-format="yyyy-MM-dd"
  191. placeholder="请输入"
  192. ></el-date-picker>
  193. </el-form-item>
  194. </el-col>
  195. <el-col :span="6"> </el-col>
  196. </el-row>
  197. <el-row style="margin-top: 12px">
  198. <el-col :span="24">
  199. <el-form-item
  200. label="留样条件:"
  201. prop="sampleCondition"
  202. align="center"
  203. >
  204. <el-input
  205. v-model="disposeForm.sampleCondition"
  206. placeholder="请输入"
  207. style="width: 100%"
  208. ></el-input>
  209. </el-form-item>
  210. </el-col>
  211. </el-row>
  212. <el-row style="margin-top: 12px">
  213. <el-col :span="24">
  214. <el-form-item
  215. label="生产商/受托生产:"
  216. prop="producerManufacturer"
  217. align="center"
  218. >
  219. <el-input
  220. v-model="disposeForm.producerManufacturer"
  221. placeholder="请输入"
  222. style="width: 100%"
  223. ></el-input>
  224. </el-form-item>
  225. </el-col>
  226. </el-row>
  227. <el-row style="margin-top: 12px">
  228. <el-col :span="24">
  229. <el-form-item
  230. label="留样地点:"
  231. prop="samplePlace"
  232. align="center"
  233. >
  234. <el-input
  235. type="textarea"
  236. v-model="disposeForm.samplePlace"
  237. placeholder="请输入"
  238. style="width: 100%"
  239. ></el-input>
  240. </el-form-item>
  241. </el-col>
  242. </el-row>
  243. <el-row style="margin-top: 12px">
  244. <el-col :span="24">
  245. <el-form-item
  246. label="留样备注:"
  247. prop="sampleRemark"
  248. align="center"
  249. >
  250. <el-input
  251. type="textarea"
  252. v-model="disposeForm.sampleRemark"
  253. placeholder="请输入"
  254. style="width: 100%"
  255. ></el-input>
  256. </el-form-item>
  257. </el-col>
  258. </el-row>
  259. <el-row style="margin-top: 12px">
  260. <el-col :span="24"> </el-col>
  261. </el-row>
  262. </template>
  263. <template
  264. v-if="
  265. disposeForm.disposeType == 3 ||
  266. disposeForm.disposeType == 6 ||
  267. disposeForm.disposeType == 9
  268. "
  269. >
  270. <el-form-item label="入库仓库:" prop="depotId" align="center">
  271. <el-select style="width: 100%" v-model="disposeForm.depotId">
  272. <el-option
  273. v-for="item in warehouseList"
  274. :key="item.id"
  275. :value="item.id"
  276. :label="item.name"
  277. @click.native="chooseWarehouse(item)"
  278. ></el-option>
  279. </el-select>
  280. </el-form-item>
  281. </template>
  282. </el-form>
  283. <!-- <DictSelection
  284. dictName="不良品处理类型"
  285. :filterArr="all ? ['返工返修'] : []"
  286. v-model="current.disposalStatus"
  287. /> -->
  288. <div slot="footer" class="dialog-footer">
  289. <el-button @click="dialogVisible = false">取 消 </el-button>
  290. <el-button type="primary" @click="handleDispose">确 定</el-button>
  291. </div>
  292. </ele-modal>
  293. <!-- <Edit ref="edit" @success="save" /> -->
  294. <ele-modal
  295. :visible.sync="addDialogVisible"
  296. width="60%"
  297. append-to-body
  298. title="选择"
  299. @close="closeAdd"
  300. >
  301. <el-table
  302. ref="addTable"
  303. :data="inventoryList"
  304. @selection-change="handleAddSelectionChange"
  305. row-key="id"
  306. >
  307. <el-table-column
  308. type="selection"
  309. width="55"
  310. align="center"
  311. :selectable="
  312. (row, index) => {
  313. return (
  314. !poList.map((item) => item.sourceId).includes(row.sourceId) ||
  315. inventoryList.length == 1
  316. );
  317. }
  318. "
  319. ></el-table-column>
  320. <el-table-column
  321. type="index"
  322. label="序号"
  323. width="55"
  324. align="center"
  325. ></el-table-column>
  326. <el-table-column
  327. prop="sampleCode"
  328. label="样品编码"
  329. width="150"
  330. align="center"
  331. ></el-table-column>
  332. <el-table-column
  333. prop="categoryCode"
  334. label="物品编码"
  335. width="150"
  336. align="center"
  337. ></el-table-column>
  338. <el-table-column
  339. prop="categoryName"
  340. label="物品名称"
  341. width="150"
  342. align="center"
  343. show-overflow-tooltip
  344. ></el-table-column>
  345. <el-table-column
  346. prop="specification"
  347. label="规格"
  348. width="120"
  349. align="center"
  350. ></el-table-column>
  351. <el-table-column prop="measureQuantity" label="计量数量" align="center">
  352. <template slot-scope="scope">
  353. <template v-if="inventoryList.length === 1">
  354. <el-input-number
  355. v-model="singleQuantity"
  356. :min="0"
  357. :max="availableTotal"
  358. style="width: 120px"
  359. ></el-input-number>
  360. </template>
  361. <template v-else>
  362. {{ scope.row.measureQuantity }}
  363. </template>
  364. </template>
  365. </el-table-column>
  366. <el-table-column
  367. prop="measureUnit"
  368. label="计量单位"
  369. align="center"
  370. ></el-table-column>
  371. </el-table>
  372. <div slot="footer" class="dialog-footer">
  373. <el-button @click="addDialogVisible = false">取 消</el-button>
  374. <el-button type="primary" @click="handleAddSave">确 定</el-button>
  375. </div>
  376. </ele-modal>
  377. </div>
  378. </template>
  379. <script>
  380. import Edit from './edit.vue';
  381. import { getWarehouseList } from '@/api/bpm/components/saleManage/saleorder.js';
  382. import dictMixins from '@/mixins/dictMixins';
  383. import {
  384. queryQualitySamplContent,
  385. queryQualityInventory,
  386. getById,
  387. refluxTask,
  388. getBadnameList,
  389. getBadtypeList,
  390. getReasontypeList
  391. } from '@/api/bpm/components/qualityReportApproval/qualityReportApproval';
  392. export default {
  393. components: {
  394. Edit
  395. },
  396. props: {
  397. total: {
  398. type: Number,
  399. default: 0
  400. },
  401. businessType: {
  402. type: String,
  403. default: ''
  404. }
  405. },
  406. mixins: [dictMixins],
  407. data() {
  408. return {
  409. cacheKeyUrl: 'qsm-c2e9664a-unqualifiedList-detailList',
  410. key: '',
  411. warehouseList: [],
  412. activeComp: 'main',
  413. addDialogVisible: false,
  414. rules: {
  415. disposeType: [
  416. { required: true, message: '请选择处置方式', trigger: 'change' }
  417. ],
  418. depotId: [
  419. { required: true, message: '请选择仓库', trigger: 'change' }
  420. ],
  421. keepSampleQuantity: [
  422. {
  423. validator: (rule, value, callback) => {
  424. if (
  425. this.disposeForm.disposeType === 6 &&
  426. this.current &&
  427. this.current.measureQuantity !== undefined
  428. ) {
  429. const inputVal = Number(value);
  430. const maxVal = Number(this.current.measureQuantity);
  431. if (inputVal > maxVal) {
  432. return callback(new Error(`不能超过原计量数量${maxVal}`));
  433. }
  434. }
  435. callback();
  436. },
  437. trigger: 'blur'
  438. }
  439. ]
  440. },
  441. disposeType: '',
  442. // 批量处置标识:一次处置弹窗「确定」对应一个标识,同批明细共享,
  443. // 审批通过时按「处置类型 + 该标识」合并生成一张处置入库单
  444. disposeBatchNo: '',
  445. disposeForm: {
  446. disposeType: '',
  447. sampleCondition: '',
  448. sampleDate: '',
  449. samplePlace: '',
  450. sampleRemark: '',
  451. producerManufacturer: '',
  452. depotId: '',
  453. depotName: '',
  454. taskId: '',
  455. keepSampleQuantity: '',
  456. disposeBatchNo: ''
  457. },
  458. loading: false,
  459. selection: [],
  460. poList: [],
  461. id: '',
  462. dialogVisible: false,
  463. current: {},
  464. formData: {},
  465. all: false,
  466. qualityType: null,
  467. allList: [
  468. { value: 1, label: '返工' },
  469. { value: 2, label: '返修' },
  470. { value: 3, label: '报废' },
  471. { value: 4, label: '降级使用' },
  472. { value: 5, label: '让步接收' },
  473. // { value: 6, label: '留样' },
  474. // { value: 7, label: '消耗' },
  475. // { value: 8, label: '回用/归批' },
  476. { value: 9, label: '转试销' },
  477. { value: 10, label: '退货' }
  478. ],
  479. //来料
  480. disposalStatusListType: [
  481. {
  482. value: 5,
  483. label: '让步接收'
  484. },
  485. {
  486. value: 9,
  487. label: '退货'
  488. }
  489. ],
  490. //生产
  491. disposalStatusList: [
  492. {
  493. value: 1,
  494. label: '返工'
  495. },
  496. {
  497. value: 2,
  498. label: '返修'
  499. },
  500. {
  501. value: 3,
  502. label: '报废'
  503. },
  504. {
  505. value: 4,
  506. label: '降级使用'
  507. },
  508. {
  509. value: 5,
  510. label: '让步接收'
  511. },
  512. {
  513. value: 6,
  514. label: '留样'
  515. },
  516. {
  517. value: 7,
  518. label: '消耗'
  519. },
  520. {
  521. value: 8,
  522. label: '回用'
  523. }
  524. ],
  525. workOrderCode: '',
  526. qualityWorkerId: '',
  527. qualityResults: '',
  528. refluxTaskList: [],
  529. sampleList: [],
  530. inventoryList: [],
  531. oldList: [],
  532. addSelection: [],
  533. retainedSampleQuantity: 0,
  534. retainedSampleUnqualified: 0,
  535. lossNumber: 0,
  536. lossNumberUnqualified: 0,
  537. badTypeList: [],
  538. badNameList: [],
  539. reasonTypeList: []
  540. };
  541. },
  542. computed: {
  543. showBtn() {
  544. return true;
  545. },
  546. disposeList() {
  547. if (this.qualityType == 1) {
  548. return this.allList.filter((item) => [5, 10].includes(item.value));
  549. }
  550. if (this.qualityType == 2) {
  551. return this.allList.filter((item) => item.value !== 8);
  552. }
  553. if (this.qualityType == 2 || this.qualityType == 3) {
  554. return this.allList.filter(
  555. (item) => item.value !== 8 && item.value !== 10
  556. );
  557. } else {
  558. return this.allList;
  559. }
  560. },
  561. // 可用总数 = 总数 - 留样数 - 消耗数
  562. availableTotal() {
  563. if (!this.sampleList.length) return this.total;
  564. const usedQuantity = this.sampleList
  565. .filter((item) => item.disposeType == 6 || item.disposeType == 7)
  566. .reduce((sum, item) => sum + (item.measureQuantity || 0), 0);
  567. return this.total - usedQuantity;
  568. },
  569. // 表格列配置
  570. columns() {
  571. const arr = [
  572. {
  573. width: 45,
  574. type: 'selection',
  575. columnKey: 'selection',
  576. reserveSelection: true,
  577. align: 'center',
  578. // 已处置的行只做展示,不允许再被勾选去「批量处置 / 批量删除」
  579. selectable: (row) => row.disposalStatus != 2
  580. },
  581. {
  582. columnKey: 'index',
  583. label: '序号',
  584. type: 'index',
  585. width: 55,
  586. align: 'center',
  587. showOverflowTooltip: true,
  588. fixed: 'left'
  589. },
  590. {
  591. label: '样品编码',
  592. prop: 'sampleCode',
  593. width: '150',
  594. align: 'center',
  595. showOverflowTooltip: true
  596. },
  597. {
  598. prop: 'categoryCode',
  599. label: '物品编码',
  600. width: '150',
  601. align: 'center',
  602. showOverflowTooltip: true
  603. },
  604. {
  605. prop: 'categoryName',
  606. label: '物品名称',
  607. width: '150',
  608. align: 'center',
  609. showOverflowTooltip: true
  610. },
  611. {
  612. label: '规格',
  613. prop: 'specification',
  614. width: '120',
  615. align: 'center',
  616. showOverflowTooltip: true
  617. },
  618. {
  619. prop: 'brandNum',
  620. label: '牌号',
  621. align: 'center'
  622. },
  623. {
  624. prop: 'modelType',
  625. label: '型号',
  626. align: 'center',
  627. width: '120',
  628. showOverflowTooltip: true
  629. },
  630. {
  631. label: '机型',
  632. prop: 'modelKey',
  633. align: 'center',
  634. showOverflowTooltip: true
  635. },
  636. {
  637. label: '颜色',
  638. prop: 'colorKey',
  639. align: 'center',
  640. showOverflowTooltip: true
  641. },
  642. {
  643. prop: 'batchNo',
  644. label: '批次号',
  645. align: 'center',
  646. width: '120',
  647. showOverflowTooltip: true
  648. },
  649. {
  650. prop: 'weight',
  651. label: '重量',
  652. align: 'center',
  653. width: '120',
  654. showOverflowTooltip: true
  655. },
  656. {
  657. prop: 'weightUnit',
  658. label: '重量单位',
  659. align: 'center',
  660. width: '120',
  661. showOverflowTooltip: true
  662. },
  663. {
  664. prop: 'engrave',
  665. label: '刻码',
  666. align: 'center'
  667. },
  668. { label: '计量数量', prop: 'measureQuantity', align: 'center' },
  669. { label: '计量单位', prop: 'measureUnit', align: 'center' },
  670. {
  671. prop: 'produceRoutingName',
  672. label: '工艺路线',
  673. align: 'center'
  674. },
  675. {
  676. prop: 'produceTaskName',
  677. label: '工序',
  678. align: 'center'
  679. },
  680. {
  681. prop: 'badTypeName',
  682. label: '不良类型',
  683. slot: 'badTypeName',
  684. width: '180',
  685. align: 'center'
  686. },
  687. {
  688. prop: 'badNameName',
  689. slot: 'badNameName',
  690. label: '不良名称',
  691. width: '180',
  692. align: 'center'
  693. },
  694. {
  695. slot: 'reasonTypeName',
  696. prop: 'reasonTypeName',
  697. label: '原因类型',
  698. width: '180',
  699. align: 'center'
  700. },
  701. {
  702. slot: 'unqualifiedReason',
  703. prop: 'unqualifiedReason',
  704. label: '原因',
  705. width: '180',
  706. align: 'center'
  707. },
  708. {
  709. prop: 'disposeTime',
  710. label: '处置时间',
  711. align: 'center',
  712. width: '180',
  713. showOverflowTooltip: true
  714. },
  715. {
  716. prop: 'disposalStatus',
  717. label: '处置状态',
  718. align: 'center',
  719. fixed: 'right',
  720. width: '120',
  721. showOverflowTooltip: true,
  722. filters: [
  723. { value: 0, text: '待处置' },
  724. { value: 1, text: '处置中' },
  725. { value: 2, text: '处置完成' }
  726. ],
  727. filterMethod: this.disposalStatusFilter,
  728. formatter: (row, column, cellValue) => {
  729. return this.disposalStatustList[cellValue ? cellValue : 0];
  730. }
  731. },
  732. {
  733. prop: 'disposeType',
  734. label: '处置类型',
  735. align: 'center',
  736. fixed: 'right',
  737. width: '120',
  738. showOverflowTooltip: true,
  739. formatter: (row, column, cellValue) => {
  740. console.log(this.allList.find((item) => item.value == cellValue));
  741. return this.allList.find((item) => item.value == cellValue)
  742. ?.label;
  743. }
  744. },
  745. {
  746. columnKey: 'action',
  747. label: '操作',
  748. width: 100,
  749. align: 'center',
  750. resizable: false,
  751. fixed: 'right',
  752. slot: 'action',
  753. showOverflowTooltip: true
  754. }
  755. ];
  756. return arr;
  757. },
  758. disposalStatustList() {
  759. return { 0: '待处置', 1: '处置中', 2: '处置完成' };
  760. },
  761. // 兜底去重,防止 uniqueKey 极端情况下仍重复
  762. // 注意:不再过滤掉「已处置」的行(disposalStatus == 2),
  763. // 台账明细要展示全部样品(含已处置),否则审批人看到的条数和实际不合格数对不上
  764. displayPoList() {
  765. const seen = new Set();
  766. return this.poList.filter((item) => {
  767. const key = item.uniqueKey || item.id;
  768. if (!key || seen.has(key)) return false;
  769. seen.add(key);
  770. return true;
  771. });
  772. }
  773. },
  774. created() {
  775. this.getBadTypeList();
  776. this.getBadNameList();
  777. this.getReasonTypeList();
  778. },
  779. methods: {
  780. // 为每行生成前端唯一 key:已保存的使用 's_'+id,新增/拆分的生成 'n_'+时间戳+随机数
  781. ensureUniqueKey(item) {
  782. if (item.uniqueKey) return item.uniqueKey;
  783. if (item.id) {
  784. item.uniqueKey = `s_${item.id}`;
  785. } else {
  786. item.uniqueKey = `n_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  787. }
  788. return item.uniqueKey;
  789. },
  790. disposalStatusFilter(value, row, column) {
  791. if (value == row.disposalStatus) {
  792. return row;
  793. }
  794. },
  795. // 查询不良类型
  796. async getBadTypeList() {
  797. const res = await getBadtypeList({
  798. pageNum: 1,
  799. size: 999
  800. });
  801. this.badTypeList = res.list;
  802. },
  803. // 查询不良名称
  804. async getBadNameList() {
  805. const res = await getBadnameList({
  806. pageNum: 1,
  807. size: 999
  808. });
  809. this.badNameList = res.list;
  810. },
  811. // 查询原因类型
  812. async getReasonTypeList() {
  813. const res = await getReasontypeList({
  814. pageNum: 1,
  815. size: 999
  816. });
  817. this.reasonTypeList = res.list;
  818. },
  819. async init(row) {
  820. this.qualityType = row.qualityType;
  821. this.id = row.id;
  822. console.log(row, 'row');
  823. this.workOrderCode = row.workOrderCode;
  824. this.qualityWorkerId = row.qualityWorkerId;
  825. this.qualityResults = row.qualityResults;
  826. this.warehouseList = await getWarehouseList();
  827. await this.getQueryQualityInventory();
  828. await this.queryQualitySamplContent();
  829. if (this.id) {
  830. await this.datasource();
  831. }
  832. },
  833. // fromSelect:结论是用户手动选的,父组件只回填数量、不得按数量反推结论,
  834. // 否则「整单合格 / 整单让步接收 / 部分合格」会被下面的推导逻辑立刻改回「整单不合格」
  835. setQualifiedNumber(fromSelect = false) {
  836. let isQualifiedNumber = true;
  837. let noQualifiedNumber = this.poList
  838. .filter((item) => ![5, 6, 7].includes(item.disposeType))
  839. .reduce((acc, cur) => acc + cur.measureQuantity, 0);
  840. if (
  841. this.poList
  842. .filter((item) => item.disposeType == 5)
  843. .reduce((acc, cur) => acc + cur.measureQuantity, 0) ==
  844. this.availableTotal
  845. ) {
  846. isQualifiedNumber = false;
  847. }
  848. this.$emit('setQualifiedNumber', {
  849. noQualifiedNumber,
  850. qualifiedNumber: this.availableTotal - noQualifiedNumber,
  851. isQualifiedNumber,
  852. fromSelect
  853. });
  854. },
  855. // 切换结论时重建明细的来源:质检工单场景用「待处置库存」;
  856. // 手工新增台账没有工单库存(qualityWorkerId 为空),用台账自身未处置的行,
  857. // 否则选「整单不合格 / 整单让步接收」会把明细清空
  858. baseDisposeList() {
  859. if (this.qualityWorkerId) {
  860. return this.inventoryList;
  861. }
  862. return this.oldList.filter((item) => item.disposalStatus != 2);
  863. },
  864. selectResult(qualityResults) {
  865. if (qualityResults == 1) {
  866. this.poList = JSON.parse(JSON.stringify(this.oldList));
  867. this.poList = this.poList.map((item) => {
  868. this.ensureUniqueKey(item);
  869. if (item.disposalStatus != 2) {
  870. item.disposalStatus = 1;
  871. item.disposeType = 5;
  872. }
  873. return item;
  874. });
  875. }
  876. if (qualityResults == 4) {
  877. this.poList = [
  878. ...JSON.parse(JSON.stringify(this.baseDisposeList())),
  879. ...JSON.parse(
  880. JSON.stringify(
  881. this.oldList.filter((item) => item.disposalStatus == 2)
  882. )
  883. )
  884. ];
  885. this.poList = this.poList.map((item) => {
  886. this.ensureUniqueKey(item);
  887. if (item.disposalStatus != 2) {
  888. item.disposalStatus = 1;
  889. item.disposeType = 5;
  890. }
  891. return item;
  892. });
  893. }
  894. if (qualityResults == 2) {
  895. this.poList = [
  896. ...JSON.parse(JSON.stringify(this.baseDisposeList())),
  897. ...JSON.parse(
  898. JSON.stringify(
  899. this.oldList.filter((item) => item.disposalStatus == 2)
  900. )
  901. )
  902. ];
  903. this.poList = this.poList.map((item) => {
  904. this.ensureUniqueKey(item);
  905. return item;
  906. });
  907. }
  908. if (qualityResults == 3) {
  909. this.poList = JSON.parse(JSON.stringify(this.oldList));
  910. this.poList.forEach((item) => {
  911. this.ensureUniqueKey(item);
  912. });
  913. }
  914. this.qualityResults = qualityResults;
  915. // 用户手动选结论:只让父组件刷新数量,结论保持用户选的值
  916. this.setQualifiedNumber(true);
  917. },
  918. disposeTypeChange() {
  919. if (this.disposeForm.disposeType == 6) {
  920. this.disposeForm.keepSampleQuantity =
  921. this.current?.measureQuantity || '';
  922. }
  923. },
  924. handleAddSelectionChange(selection) {
  925. this.addSelection = selection;
  926. },
  927. handleAddSave() {
  928. if (this.addSelection.length === 0) {
  929. this.$message.warning('请选择数据');
  930. return;
  931. }
  932. const poListTotal = this.poList
  933. .filter((item) => ![5, 6, 7].includes(item.disposeType))
  934. .reduce(
  935. (sum, item) => sum + Number(item.measureQuantity || 0),
  936. 0
  937. );
  938. let selectedTotal = 0;
  939. if (this.inventoryList.length === 1) {
  940. selectedTotal =
  941. this.addSelection.length * Number(this.singleQuantity || 0);
  942. } else {
  943. selectedTotal = this.addSelection.reduce(
  944. (sum, item) => sum + Number(item.measureQuantity || 0),
  945. 0
  946. );
  947. }
  948. if (poListTotal + selectedTotal > this.availableTotal) {
  949. this.$message.error('选中的计量数量之和加上已有数量不能超过总数');
  950. return;
  951. }
  952. this.addSelection.forEach((item) => {
  953. const newItem = JSON.parse(JSON.stringify(item));
  954. // 拆分行:始终生成新的 uniqueKey(基于时间戳+随机数)
  955. newItem.uniqueKey = `n_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
  956. // 拆分行不带后端 id,清掉避免误传
  957. delete newItem.id;
  958. if (this.inventoryList.length === 1) {
  959. newItem.measureQuantity = Number(this.singleQuantity);
  960. }
  961. this.poList.push(newItem);
  962. });
  963. this.addDialogVisible = false;
  964. this.setQualifiedNumber();
  965. },
  966. add() {
  967. this.addDialogVisible = true;
  968. this.$nextTick(() => {
  969. this.$refs.addTable?.clearSelection();
  970. });
  971. if (this.inventoryList.length === 1) {
  972. this.singleQuantity = this.inventoryList[0].measureQuantity || 0;
  973. }
  974. },
  975. closeAdd() {
  976. this.addSelection = [];
  977. this.$nextTick(() => {
  978. this.$refs.addTable?.clearSelection();
  979. });
  980. },
  981. // /* 表格数据源 */
  982. async datasource() {
  983. const arr = await getById(this.id);
  984. this.oldList = JSON.parse(JSON.stringify(arr.poList || []));
  985. this.poList = (arr.poList || []).map((item) => {
  986. this.ensureUniqueKey(item);
  987. return item;
  988. });
  989. // 回流工序:处置方式选「返工 / 返修」时要选工序,和台账列表页取的是同一份数据。
  990. // 台账记录里的工单编码就是 sourceCode,拿它调 /mes/workorder/refluxTask。
  991. if (arr.sourceCode) {
  992. this.workOrderCode = arr.sourceCode;
  993. await this.getRefluxTask();
  994. } else {
  995. console.warn(
  996. '[不良品台账] 台账记录未返回工单编码(sourceCode),处置方式选「返工 / 返修」时无法选择回流工序',
  997. arr
  998. );
  999. }
  1000. // 合格数 / 不合格数的数据源是「质检工单详情」接口(父组件 detailDialog 用 getDetailById 回填);
  1001. // 台账 getById 从不返回这两个字段(内外网实测一致),
  1002. // 用 poList 反推会把「让步接收(5)/留样(6)/消耗(7)」这些处置行排除掉,数也对不上。
  1003. // 其他业务类型(检测报告单等)保持原有计算逻辑不变。
  1004. const hasApiNumber =
  1005. arr.qualifiedNumber !== undefined &&
  1006. arr.qualifiedNumber !== null &&
  1007. arr.noQualifiedNumber !== undefined &&
  1008. arr.noQualifiedNumber !== null;
  1009. if (this.businessType?.includes('不良品处置')) {
  1010. if (hasApiNumber) {
  1011. this.$emit('setQualifiedNumber', {
  1012. qualifiedNumber: arr.qualifiedNumber,
  1013. noQualifiedNumber: arr.noQualifiedNumber,
  1014. // 标记来自接口:父组件只更新数量,不改写台账已保存的「结论」
  1015. fromApi: true
  1016. });
  1017. } else {
  1018. // 不做兜底:台账接口没给就保持父组件用工单数据回填的值,别拿 poList 反推
  1019. console.warn(
  1020. '[不良品台账] 台账 getById 未返回 qualifiedNumber / noQualifiedNumber,合格数/不合格数以质检工单详情回填为准',
  1021. arr.unqualifiedProductsCode
  1022. );
  1023. }
  1024. } else {
  1025. this.setQualifiedNumber();
  1026. }
  1027. },
  1028. /**
  1029. * 回流工序:处置方式选「返工 / 返修」时,工序下拉的数据源
  1030. * 与不合格品台账列表页一致,用工单编码查询
  1031. */
  1032. async getRefluxTask() {
  1033. if (!this.workOrderCode) return;
  1034. try {
  1035. const res = await refluxTask({
  1036. workOrderCode: this.workOrderCode
  1037. });
  1038. this.refluxTaskList = res || [];
  1039. } catch (error) {
  1040. this.refluxTaskList = [];
  1041. this.$message.error(error.message || '回流工序加载失败');
  1042. }
  1043. },
  1044. remove() {
  1045. let ids = this.selection.map((item) => item.id);
  1046. console.log(ids, 'ids');
  1047. this.poList = this.poList.filter((item) => !ids.includes(item.id));
  1048. this.setQualifiedNumber();
  1049. },
  1050. close() {
  1051. this.dialogVisible = false;
  1052. this.disposeType = '';
  1053. this.all = false;
  1054. this.formData.disposalStatus = '';
  1055. this.disposeBatchNo = '';
  1056. this.disposeForm.disposeBatchNo = '';
  1057. },
  1058. /**
  1059. * 生成处置批次标识
  1060. * 规则:DB + yyyyMMddHHmmss + 4 位随机数,例如 DB202611101530251234
  1061. * 一次处置弹窗「确定」生成一个,批量处置写入全部选中行,单条处置只写当前行;
  1062. * 同一条明细被重新处置时标识覆盖为最新一次,保证以最终处置结果为准。
  1063. */
  1064. buildDisposeBatchNo() {
  1065. const d = new Date();
  1066. const pad = (n) => String(n).padStart(2, '0');
  1067. const ts =
  1068. d.getFullYear() +
  1069. pad(d.getMonth() + 1) +
  1070. pad(d.getDate()) +
  1071. pad(d.getHours()) +
  1072. pad(d.getMinutes()) +
  1073. pad(d.getSeconds());
  1074. const rand = String(Math.floor(Math.random() * 10000)).padStart(4, '0');
  1075. return `DB${ts}${rand}`;
  1076. },
  1077. // // 处置
  1078. disposeFn(type, row) {
  1079. this.current = {};
  1080. if (type == 1) {
  1081. this.all = true;
  1082. } else {
  1083. this.current = row;
  1084. }
  1085. this.formData = { ...row };
  1086. // 每次打开处置弹窗都生成新的批次标识:批量处置时全部选中行共享同一个标识,
  1087. // 单条处置时该行使用自己的标识,审批通过后按标识把同批明细合并成一张处置入库单
  1088. this.disposeBatchNo = this.buildDisposeBatchNo();
  1089. this.disposeForm.disposeBatchNo = this.disposeBatchNo;
  1090. this.dialogVisible = true;
  1091. },
  1092. async getQueryQualityInventory() {
  1093. const res = await queryQualityInventory({
  1094. qualityWorkerId: this.qualityWorkerId,
  1095. size: -1
  1096. });
  1097. if (res.list.length > 0) {
  1098. this.inventoryList = res.list;
  1099. }
  1100. return;
  1101. },
  1102. async queryQualitySamplContent() {
  1103. const res = await queryQualitySamplContent({
  1104. qualityWorkerId: this.qualityWorkerId,
  1105. size: 1000
  1106. });
  1107. this.sampleList = res.list;
  1108. [
  1109. 'retainedSampleQuantity',
  1110. 'retainedSampleUnqualified',
  1111. 'lossNumber',
  1112. 'lossNumberUnqualified'
  1113. ].forEach((key) => {
  1114. this[key] = 0;
  1115. });
  1116. if (this.sampleList.length > 0 && this.inventoryList.length > 0) {
  1117. const processedList = this.sampleList.filter(
  1118. (item) => item.disposeType == 6 || item.disposeType == 7
  1119. );
  1120. console.log(
  1121. processedList.filter((item) => item.disposeType == 6),
  1122. 'qwewqe'
  1123. );
  1124. console.log(
  1125. processedList.filter((item) => item.disposeType == 7),
  1126. '123123'
  1127. );
  1128. processedList.forEach((sampleItem) => {
  1129. if (sampleItem.disposeType == 6) {
  1130. sampleItem.qualityResults == 2
  1131. ? (this.retainedSampleUnqualified += sampleItem.measureQuantity)
  1132. : (this.retainedSampleQuantity += sampleItem.measureQuantity);
  1133. } else {
  1134. sampleItem.qualityResults == 2
  1135. ? (this.lossNumberUnqualified += sampleItem.measureQuantity)
  1136. : (this.lossNumber += sampleItem.measureQuantity);
  1137. }
  1138. const inventoryItem = this.inventoryList.find(
  1139. (item) => item.sourceId === sampleItem.sourceId
  1140. );
  1141. if (inventoryItem) {
  1142. inventoryItem.measureQuantity = Math.max(
  1143. 0,
  1144. (inventoryItem.measureQuantity || 0) -
  1145. (sampleItem.measureQuantity || 0)
  1146. );
  1147. }
  1148. });
  1149. }
  1150. this.$emit('setLossNumber', {
  1151. lossNumber: this.lossNumber,
  1152. lossNumberUnqualified: this.lossNumberUnqualified,
  1153. retainedSampleQuantity: this.retainedSampleQuantity,
  1154. retainedSampleUnqualified: this.retainedSampleUnqualified
  1155. });
  1156. if (this.inventoryList.length == 1 && !this.inventoryList[0].sourceId) {
  1157. this.inventoryList[0].measureQuantity =
  1158. this.inventoryList[0].measureQuantity -
  1159. this.lossNumber -
  1160. this.lossNumberUnqualified -
  1161. this.retainedSampleQuantity -
  1162. this.retainedSampleUnqualified;
  1163. }
  1164. },
  1165. async handleDispose() {
  1166. this.$refs['disposeForm'].validate(async (valid) => {
  1167. // let ids = this.selection.map((item) => item.id);
  1168. let ids = this.current?.id
  1169. ? [this.current.id]
  1170. : this.selection.map((item) => item.id);
  1171. this.poList.forEach((item, index) => {
  1172. if (ids.includes(item.id)) {
  1173. this.$set(this.poList[index], 'disposalStatus', 1);
  1174. for (const key in this.disposeForm) {
  1175. this.$set(this.poList[index], key, this.disposeForm[key]);
  1176. }
  1177. }
  1178. });
  1179. this.dialogVisible = false;
  1180. this.$refs.table.clearSelection();
  1181. this.setQualifiedNumber();
  1182. });
  1183. },
  1184. validate() {},
  1185. getValue() {
  1186. let unqualifiedProducts = { id: this.id };
  1187. let isNodisposeType = false;
  1188. if (this.poList.length > 0) {
  1189. unqualifiedProducts.poList = this.poList.map((item) => {
  1190. if (!item.disposeType && this.businessType.includes('不良品处置')) {
  1191. isNodisposeType = true;
  1192. }
  1193. item.qualityResults = 1;
  1194. item.qualityStatus = 1;
  1195. item.qualityWorkOrderId = this.qualityWorkerId;
  1196. return item;
  1197. });
  1198. [
  1199. 'batchNo',
  1200. 'brandNum',
  1201. 'categoryCode',
  1202. 'categoryId',
  1203. 'categoryName',
  1204. 'factoriesId',
  1205. 'measureUnit',
  1206. 'modelType',
  1207. 'produceRoutingId',
  1208. 'produceRoutingName',
  1209. 'productCategory'
  1210. ].forEach((item) => {
  1211. unqualifiedProducts[item] = this.poList[0][item];
  1212. });
  1213. unqualifiedProducts.qualityType = this.qualityType;
  1214. unqualifiedProducts.qualityWorkOrderId = this.qualityWorkerId;
  1215. unqualifiedProducts.sourceCode = this.workOrderCode;
  1216. unqualifiedProducts.quantity = this.poList.reduce(
  1217. (total, item) => total + item.measureQuantity,
  1218. 0
  1219. );
  1220. unqualifiedProducts.sourceType = this.workOrderId ? 1 : 0;
  1221. return isNodisposeType ? 'isNodisposeType' : unqualifiedProducts;
  1222. }
  1223. return null;
  1224. },
  1225. chooseWarehouse(item) {
  1226. // console.log(item);
  1227. this.disposeForm.depotId = item.id;
  1228. this.disposeForm.depotName = item.name;
  1229. }
  1230. },
  1231. watch: {
  1232. 'disposeForm.disposeType': {
  1233. handler(newVal, oldVal) {
  1234. if (newVal == 6) {
  1235. this.rules.depotId = [];
  1236. } else {
  1237. this.rules.depotId = [
  1238. { required: true, message: '请选择仓库', trigger: 'change' }
  1239. ];
  1240. }
  1241. this.disposeForm.sampleCondition = '';
  1242. this.disposeForm.sampleDate = '';
  1243. this.disposeForm.samplePlace = '';
  1244. this.disposeForm.sampleRemark = '';
  1245. this.disposeForm.producerManufacturer = '';
  1246. this.disposeForm.depotId = '';
  1247. this.disposeForm.depotName = '';
  1248. this.disposeForm.taskId = '';
  1249. }
  1250. }
  1251. }
  1252. };
  1253. </script>
  1254. <style lang="scss" scoped>
  1255. .unacceptedProductSelect {
  1256. display: flex;
  1257. flex-direction: column;
  1258. align-items: center;
  1259. }
  1260. </style>