CollectForm.vue 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. <template>
  2. <view>
  3. <cu-custom :backUrl="'/pages/index/index'" :isBack="true" bgColor="bg-gradual-blue">
  4. <block slot="content">领用申请</block>
  5. </cu-custom>
  6. <u--form :model="inputForm" labelWidth="100px" class="u-form" labelPosition="left" :rules="rules"
  7. ref="inputForm">
  8. <u-form-item label="领用编号" borderBottom prop="collectNo">
  9. <u--input placeholder="自动生成" v-model="inputForm.collectNo" disabled></u--input>
  10. </u-form-item>
  11. <u-form-item label="经办人" borderBottom prop="handledBy">
  12. <u--input v-model="inputForm.handledBy" disabled></u--input>
  13. </u-form-item>
  14. <u-form-item label="经办部门" borderBottom prop="handledByOfficeName">
  15. <u--input v-model="inputForm.handledByOfficeName" disabled></u--input>
  16. </u-form-item>
  17. <u-form-item label="领用时间" borderBottom prop="collectDate" :required="true">
  18. <el-date-picker v-model="inputForm.collectDate" type="date" placeholder="请选择领用时间" style="width:100%"
  19. placement="bottom-start" clearable :disabled="nodeFlag">
  20. </el-date-picker>
  21. </u-form-item>
  22. <u-form-item label="备注" borderBottom prop="remarks">
  23. <u--textarea placeholder="请填写备注信息" :rows="4" :maxlength="500" v-model="inputForm.remarks"
  24. :disabled="nodeFlag"></u--textarea>
  25. </u-form-item>
  26. <view class="section-wrap">
  27. <view class="section-title">领用详情</view>
  28. <view class="section-tip"></view>
  29. <view v-for="(item, index) in inputForm.detailInfos" :key="item.id || index" class="detail-card">
  30. <view class="detail-card-title">领用详情 {{ index + 1 }}</view>
  31. <u-form-item label="领用人" :prop="'detailInfos[' + index + '].recipientAgent'" :required="true">
  32. <u--input v-model="inputForm.detailInfos[index].recipientAgent" placeholder="请选择领用人"
  33. :disabled="nodeFlag" @focus="openUserPullForm(index)" clearable></u--input>
  34. </u-form-item>
  35. <u-form-item label="领用人部门" :prop="'detailInfos[' + index + '].recipientOffice'">
  36. <u--input v-model="inputForm.detailInfos[index].recipientOffice" disabled></u--input>
  37. </u-form-item>
  38. <u-form-item label="领用类型" :prop="'detailInfos[' + index + '].collectType'" :required="true">
  39. <u--input v-model="inputForm.detailInfos[index].collectType" placeholder="请选择领用类型"
  40. :disabled="nodeFlag" @focus="openTypePicker(index)" clearable></u--input>
  41. </u-form-item>
  42. <!-- <u-form-item label="物品名称" :prop="'detailInfos[' + index + '].goodsName'" :required="true"> -->
  43. <!-- <u--input :value="inputForm.detailInfos[index].goodsName" placeholder="请选择物品名称"
  44. :disabled="nodeFlag" @focus="openPicker(index)"></u--input> -->
  45. <!-- </u-form-item> -->
  46. <collect-goods-selector ref="goodsSelect" :rowIndex="index" :inputForm="inputForm"
  47. :disabled="nodeFlag" @selected="handleGoodsSelected"></collect-goods-selector>
  48. <u-form-item label="领用数量" :prop="'detailInfos[' + index + '].collectNumber'" :required="true">
  49. <u--input v-model="inputForm.detailInfos[index].collectNumber" placeholder="请输入领用数量"
  50. :disabled="nodeFlag" @blur="handleCollectNumberBlur(index)"></u--input>
  51. </u-form-item>
  52. <u-form-item label="库存数量" :prop="'detailInfos[' + index + '].kc'">
  53. <u--input v-model="inputForm.detailInfos[index].kc" disabled></u--input>
  54. </u-form-item>
  55. <u-form-item label="单位" :prop="'detailInfos[' + index + '].company'">
  56. <u--input v-model="inputForm.detailInfos[index].company" placeholder="请输入单位"
  57. :disabled="true"></u--input>
  58. </u-form-item>
  59. <u-form-item label="包装规格" :prop="'detailInfos[' + index + '].spec'">
  60. <u--input v-model="inputForm.detailInfos[index].spec" disabled></u--input>
  61. </u-form-item>
  62. <u-form-item label="备注" :prop="'detailInfos[' + index + '].remarks'">
  63. <u--input v-model="inputForm.detailInfos[index].remarks" placeholder="请输入备注"
  64. :disabled="nodeFlag"></u--input>
  65. </u-form-item>
  66. <u-form-item label="附件上传">
  67. <UploadComponent :uploadUrl="`${uploadUrl}/public-modules-server/oss/file/webUpload/upload`"
  68. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, index, fileIndex, 'detail')"
  69. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, index, 'detail')"
  70. :fileList="inputForm.detailInfos[index].fileInfoLost" :limit="3" :isDelete="nodeFlag"
  71. :isUpload="nodeFlag">
  72. </UploadComponent>
  73. </u-form-item>
  74. <u-form-item label="" v-if="!nodeFlag">
  75. <el-button style="width: 100%" type="danger" plain @click="removeDetail(index)">
  76. 删除领用详情
  77. </el-button>
  78. </u-form-item>
  79. </view>
  80. <u-form-item label="" v-if="!nodeFlag">
  81. <el-button style="width: 100%" type="primary" @click="addDetail()" plain>新增领用详情</el-button>
  82. </u-form-item>
  83. </view>
  84. <view class="section-wrap" v-if="nodeFlag">
  85. <view class="section-title">领用分配详情</view>
  86. <view v-for="(item, index) in inputForm.recordList" :key="item.id || index" class="detail-card">
  87. <view class="detail-card-title">领用分配详情 {{ index + 1 }}</view>
  88. <u-form-item label="领用人" :prop="'recordList[' + index + '].recipientAgent'" :required="true">
  89. <u--input v-model="inputForm.recordList[index].recipientAgent" placeholder="请选择领用人"
  90. :disabled="nodeFlag" @focus="openUserPullForm(index)" clearable></u--input>
  91. </u-form-item>
  92. <u-form-item label="领用人部门" :prop="'recordList[' + index + '].recipientOffice'">
  93. <u--input v-model="inputForm.recordList[index].recipientOffice" disabled></u--input>
  94. </u-form-item>
  95. <u-form-item label="领用类型" :prop="'recordList[' + index + '].collectType'" :required="true">
  96. <u--input v-model="inputForm.recordList[index].collectType" placeholder="请选择领用类型"
  97. :disabled="nodeFlag" @focus="openTypePicker(index)" clearable></u--input>
  98. </u-form-item>
  99. <u-form-item label="物品名称" :prop="'recordList[' + index + '].goodsName'" :required="true">
  100. <u--input v-model="inputForm.recordList[index].goodsName" placeholder="请选择物品名称"
  101. :disabled="nodeFlag" clearable></u--input>
  102. </u-form-item>
  103. <u-form-item label="分配生产日期" :prop="'recordList[' + index + '].fpDate'" :required="true">
  104. <u--input v-model="inputForm.recordList[index].fpDate" placeholder="请选择分配生产日期"
  105. :disabled="nodeFlag" clearable></u--input>
  106. </u-form-item>
  107. <u-form-item label="领用数量" :prop="'recordList[' + index + '].collectNumber'" :required="true">
  108. <u--input v-model="inputForm.recordList[index].collectNumber" placeholder="请输入领用数量"
  109. :disabled="nodeFlag" @blur="handleCollectNumberBlur(index)"></u--input>
  110. </u-form-item>
  111. <u-form-item label="库存数量" :prop="'recordList[' + index + '].kc'">
  112. <u--input v-model="inputForm.recordList[index].kc" disabled></u--input>
  113. </u-form-item>
  114. </view>
  115. </view>
  116. </u--form>
  117. <user-select ref="userPicker" @input="handleUserSelected"></user-select>
  118. <ba-tree-picker ref="treePicker" :multiple="false" @select-change="selectTypeChange" title="类型选择"
  119. :localdata="listData" :selectedData="typeSelectedData" valueKey="value" textKey="label"
  120. childrenKey="children" />
  121. </view>
  122. </template>
  123. <script>
  124. import { mapState } from 'vuex'
  125. import userSelect from '@/components/user-select/user-select-radio.vue'
  126. import baTreePicker from '@/components/ba-tree-picker/ba-tree-picker.vue'
  127. import UploadComponent from '@/pages/common/UploadComponent.vue'
  128. import upload from '@/utils/upload.js'
  129. import MaterialTypeService from '@/api/psi/MaterialTypeService'
  130. import WareHouseService from '@/api/psi/WareHouseService'
  131. import CollectService from '@/api/psi/CollectService'
  132. import CommonApi from '@/api/common/CommonApi'
  133. import CollectGoodsSelector from './CollectGoodsSelector.vue'
  134. export default {
  135. name: 'PsiCollectForm',
  136. components: {
  137. userSelect,
  138. baTreePicker,
  139. UploadComponent,
  140. CollectGoodsSelector
  141. },
  142. computed: mapState({
  143. userInfo: (state) => state.user.userInfo
  144. }),
  145. props: {
  146. businessId: {
  147. type: String,
  148. default: ''
  149. },
  150. formReadOnly: {
  151. type: Boolean,
  152. default: false
  153. },
  154. status: {
  155. type: String,
  156. default: ''
  157. }
  158. },
  159. data() {
  160. return {
  161. uploadUrl: upload.UPLOAD_URL,
  162. nodeFlag: false,
  163. loading: false,
  164. listData: [],
  165. materialList: [],
  166. typeSelectedData: [],
  167. inputForm: this.createInputForm(),
  168. rules: {
  169. collectDate: [
  170. {
  171. required: true,
  172. message: '领用时间不能为空',
  173. trigger: ['change']
  174. }
  175. ]
  176. }
  177. }
  178. },
  179. materialTypeService: null,
  180. wareHouseService: null,
  181. collectService: null,
  182. commonApi: null,
  183. created() {
  184. this.ensureServices()
  185. this.loadMaterialTypes()
  186. this.fillUserInfo()
  187. },
  188. watch: {
  189. businessId: {
  190. handler() {
  191. if (this.businessId) {
  192. this.init(this.businessId)
  193. } else {
  194. this.resetForm()
  195. this.nodeFlag = this.formReadOnly || this.status === 'taskFormDetail' || this.status === 'testSee'
  196. }
  197. },
  198. immediate: true,
  199. deep: false
  200. }
  201. },
  202. methods: {
  203. createInputForm() {
  204. return {
  205. id: '',
  206. procInsId: '',
  207. processDefinitionId: '',
  208. status: '',
  209. collectNo: '',
  210. collectDate: '',
  211. remarks: '',
  212. handledBy: '',
  213. handledById: '',
  214. handledByOffice: '',
  215. handledByOfficeName: '',
  216. userId: '',
  217. files: [],
  218. detailInfos: [],
  219. recordList: [],
  220. returnId: '',
  221. statusReturn: '',
  222. procInsIdReturn: '',
  223. processDefinitionIdReturn: '',
  224. returnCause: '',
  225. returnFiles: []
  226. }
  227. },
  228. createDetailRow() {
  229. return {
  230. id: '',
  231. recipientAgent: (this.userInfo && this.userInfo.name) || '',
  232. recipientAgentId: (this.userInfo && this.userInfo.id) || '',
  233. recipientOffice: (this.userInfo && this.userInfo.officeDTO && this.userInfo.officeDTO.name) || '',
  234. collectType: '',
  235. collectTypeId: '',
  236. goodsName: '',
  237. surplusNumber: '',
  238. currentInventory: '',
  239. collectNumber: '',
  240. company: '',
  241. remarks: '',
  242. spec: '',
  243. produceDate: '',
  244. shelfLife: '',
  245. shelfLifeUnit: '',
  246. fileInfoLost: [],
  247. isReturn: '0'
  248. }
  249. },
  250. ensureServices() {
  251. if (!this.materialTypeService) {
  252. this.materialTypeService = new MaterialTypeService()
  253. }
  254. if (!this.wareHouseService) {
  255. this.wareHouseService = new WareHouseService()
  256. }
  257. if (!this.collectService) {
  258. this.collectService = new CollectService()
  259. }
  260. if (!this.commonApi) {
  261. this.commonApi = new CommonApi()
  262. }
  263. },
  264. async loadMaterialTypes() {
  265. try {
  266. const data = await this.materialTypeService.cgList()
  267. this.materialList = data || []
  268. this.listData = this.buildTree(this.materialList)
  269. } catch (e) {
  270. this.materialList = []
  271. this.listData = []
  272. }
  273. },
  274. fillUserInfo() {
  275. if (!this.userInfo) {
  276. return
  277. }
  278. this.inputForm.handledBy = this.inputForm.handledBy || this.userInfo.name
  279. this.inputForm.handledById = this.inputForm.handledById || this.userInfo.id
  280. this.inputForm.userId = this.inputForm.userId || this.userInfo.id
  281. this.inputForm.handledByOffice = this.inputForm.handledByOffice || (this.userInfo.officeDTO && this.userInfo.officeDTO.id) || ''
  282. this.inputForm.handledByOfficeName = this.inputForm.handledByOfficeName || (this.userInfo.officeDTO && this.userInfo.officeDTO.name) || ''
  283. },
  284. resetForm() {
  285. this.inputForm = this.createInputForm()
  286. this.fillUserInfo()
  287. },
  288. async init(id) {
  289. this.ensureServices()
  290. this.resetForm()
  291. this.inputForm.id = id
  292. this.loading = true
  293. try {
  294. const data = await this.collectService.findById(id)
  295. this.inputForm = this.normalizeFormData(this.recover(this.createInputForm(), data))
  296. this.inputForm.detailInfos.forEach(detail => {
  297. if (detail.surplusNumber && detail.spec) {
  298. detail.kc = this.formatNumber(Number(detail.surplusNumber) * Number(detail.spec))
  299. }
  300. })
  301. this.inputForm.recordList.forEach(record => {
  302. if (record.surplusNumber && record.spec) {
  303. record.kc = this.formatNumber(Number(record.surplusNumber) * Number(record.spec))
  304. }
  305. record.fpDate = `${record.goodsName}(生产日期:${record.produceDate})`
  306. })
  307. this.fillUserInfo()
  308. this.nodeFlag = await this.resolveNodeFlag(data)
  309. } catch (e) {
  310. this.nodeFlag = true
  311. } finally {
  312. this.loading = false
  313. }
  314. },
  315. async resolveNodeFlag(data) {
  316. if ((this.formReadOnly || this.status === 'taskFormDetail' || this.status === 'testSee')) {
  317. return true
  318. }
  319. try {
  320. const taskName = await this.commonApi.getTaskNameByProcInsId((data && data.procInsId) || this.inputForm.procInsId)
  321. if (this.isNotEmpty(taskName)) {
  322. return taskName !== '发起人重新发起申请'
  323. }
  324. } catch (e) {
  325. }
  326. return false
  327. },
  328. normalizeFormData(data) {
  329. const form = data || this.createInputForm()
  330. if (this.isNotEmpty(form.collectDate)) {
  331. form.collectDate = new Date(form.collectDate)
  332. }
  333. form.files = form.files || []
  334. form.detailInfos = (form.detailInfos || []).map(item => ({
  335. ...item,
  336. fileInfoLost: item.fileInfoLost || [],
  337. produceDate: this.isNotEmpty(item.produceDate) ? new Date(item.produceDate) : '',
  338. surplusNumber: this.isNotEmpty(item.surplusNumber) ? this.formatNumber(item.surplusNumber) : '',
  339. currentInventory: this.isNotEmpty(item.currentInventory) ? this.formatNumber(item.currentInventory) : ''
  340. }))
  341. form.recordList = form.recordList || []
  342. return form
  343. },
  344. buildTree(nodes, parentId = '0') {
  345. const tree = []
  346. for (const node of nodes || []) {
  347. if (node.parentId === parentId) {
  348. const children = this.buildTree(nodes, node.id)
  349. if (children.length) {
  350. node.children = children
  351. }
  352. tree.push(node)
  353. }
  354. }
  355. return tree
  356. },
  357. isEmpty(value) {
  358. if (value === null || value === undefined) {
  359. return true
  360. }
  361. if (typeof value === 'string' && value.trim() === '') {
  362. return true
  363. }
  364. if (Array.isArray(value) && value.length === 0) {
  365. return true
  366. }
  367. return false
  368. },
  369. isNotEmpty(value) {
  370. return !this.isEmpty(value)
  371. },
  372. formatDate(date) {
  373. if (this.isEmpty(date)) {
  374. return ''
  375. }
  376. const dateValue = new Date(date)
  377. if (Number.isNaN(dateValue.getTime())) {
  378. return ''
  379. }
  380. const year = dateValue.getFullYear()
  381. const month = `${dateValue.getMonth() + 1}`.padStart(2, '0')
  382. const day = `${dateValue.getDate()}`.padStart(2, '0')
  383. return `${year}-${month}-${day}`
  384. },
  385. normalizeDateFieldsBeforeValidate() {
  386. if (this.isNotEmpty(this.inputForm.collectDate)) {
  387. this.inputForm.collectDate = this.formatDate(this.inputForm.collectDate)
  388. }
  389. },
  390. formatNumber(value) {
  391. const numberValue = Number(value || 0)
  392. if (Number.isNaN(numberValue)) {
  393. return ''
  394. }
  395. return numberValue % 1 === 0 ? String(numberValue) : numberValue.toFixed(2).replace(/\.?0+$/, '')
  396. },
  397. formatNumberInput(inputValue, decimalLimit = 2) {
  398. if (this.isEmpty(inputValue)) {
  399. return ''
  400. }
  401. const valueText = String(inputValue)
  402. if (!/^\d*\.?\d*$/.test(valueText)) {
  403. return ''
  404. }
  405. let value = valueText.replace(/[^\d.]/g, '')
  406. const dotIndex = value.indexOf('.')
  407. if (dotIndex !== -1) {
  408. const substr = value.substr(dotIndex + 1)
  409. if (substr.indexOf('.') !== -1) {
  410. value = value.substr(0, dotIndex + 1) + substr.replace(/\./g, '')
  411. }
  412. }
  413. if (dotIndex !== -1) {
  414. const integerPart = value.substring(0, dotIndex)
  415. const decimalPart = value.substring(dotIndex + 1, dotIndex + 1 + decimalLimit)
  416. value = integerPart + '.' + decimalPart
  417. }
  418. return value
  419. },
  420. addDetail() {
  421. this.inputForm.detailInfos.push(this.createDetailRow())
  422. },
  423. removeDetail(index) {
  424. this.inputForm.detailInfos.splice(index, 1)
  425. },
  426. patchDetail(index, patch) {
  427. const detail = (this.inputForm.detailInfos || [])[index] || {}
  428. this.$set(this.inputForm.detailInfos, index, {
  429. ...detail,
  430. ...patch
  431. })
  432. },
  433. openUserPullForm(index) {
  434. if (this.nodeFlag) {
  435. return
  436. }
  437. this.$refs.userPicker.open(index, 'detail')
  438. },
  439. handleUserSelected(data, index, type) {
  440. if (type !== 'detail') {
  441. return
  442. }
  443. this.patchDetail(index, {
  444. recipientAgentId: data.id,
  445. recipientAgent: data.label,
  446. recipientOffice: data.parentLabel
  447. })
  448. },
  449. openTypePicker(index) {
  450. if (this.nodeFlag) {
  451. return
  452. }
  453. const detail = (this.inputForm.detailInfos || [])[index] || {}
  454. this.typeSelectedData = detail.collectTypeId ? [detail.collectTypeId] : []
  455. this.$nextTick(() => {
  456. const treePicker = this.$refs.treePicker
  457. if (!treePicker) {
  458. return
  459. }
  460. if (treePicker._initTree) {
  461. treePicker._initTree()
  462. }
  463. treePicker._show(index)
  464. })
  465. },
  466. selectTypeChange(ids, names, index) {
  467. this.patchDetail(index, {
  468. collectType: names,
  469. collectTypeId: ids[0],
  470. goodsName: '',
  471. surplusNumber: '',
  472. currentInventory: '',
  473. collectNumber: '',
  474. company: '',
  475. kc: '',
  476. spec: '',
  477. produceDate: '',
  478. shelfLife: '',
  479. shelfLifeUnit: ''
  480. })
  481. },
  482. async handleGoodsSelected({ index, item }) {
  483. this.patchDetail(index, {
  484. goodsName: item.tradeName || '',
  485. kc: this.formatNumber(Number(item.tradeNumber) * Number(item.spec))
  486. })
  487. await this.syncDetailStock(index)
  488. },
  489. async syncDetailStock(index) {
  490. const detail = (this.inputForm.detailInfos || [])[index]
  491. if (!detail || this.isEmpty(detail.goodsName) || this.isEmpty(detail.collectTypeId)) {
  492. return
  493. }
  494. try {
  495. const data = await this.wareHouseService.getByProduceDateNotMerge({
  496. current: 1,
  497. size: 1000,
  498. tradeName: detail.goodsName,
  499. wareHouseType: detail.collectTypeId
  500. })
  501. const records = (data && data.records) || []
  502. const totalBottle = records.reduce((sum, item) => {
  503. const currentInventory = Number(item.currentInventory || 0)
  504. const spec = Number(item.spec || 1)
  505. return sum + currentInventory * (spec > 0 ? spec : 1)
  506. }, 0)
  507. const firstRecord = records[0] || {}
  508. this.patchDetail(index, {
  509. surplusNumber: this.formatNumber(totalBottle),
  510. currentInventory: this.formatNumber(totalBottle),
  511. company: firstRecord.company || detail.company || '',
  512. spec: firstRecord.spec || '',
  513. produceDate: this.isNotEmpty(firstRecord.produceDate) ? new Date(firstRecord.produceDate) : '',
  514. shelfLife: firstRecord.shelfLife || '',
  515. shelfLifeUnit: firstRecord.shelfLifeUnit || ''
  516. })
  517. } catch (e) {
  518. this.patchDetail(index, {
  519. surplusNumber: '',
  520. currentInventory: ''
  521. })
  522. uni.showToast({
  523. title: '库存数量加载失败',
  524. icon: 'none'
  525. })
  526. }
  527. },
  528. handleCollectNumberBlur(index) {
  529. const detail = (this.inputForm.detailInfos || [])[index]
  530. if (!detail) {
  531. return
  532. }
  533. detail.collectNumber = this.formatNumberInput(detail.collectNumber)
  534. if (this.isNotEmpty(detail.surplusNumber) && this.isNotEmpty(detail.collectNumber) &&
  535. Number(detail.collectNumber) > Number(detail.surplusNumber)) {
  536. uni.showToast({
  537. title: '领用数量不能大于库存数量',
  538. icon: 'none'
  539. })
  540. detail.collectNumber = ''
  541. }
  542. },
  543. handleUploadSuccess(file, fileList, index, type) {
  544. if (type === 'detail') {
  545. this.inputForm.detailInfos[index].fileInfoLost = fileList
  546. } else {
  547. this.inputForm.files = fileList
  548. }
  549. },
  550. handleRemove(file, fileList, lineIndex, fileIndex, type) {
  551. if (type === 'detail') {
  552. this.inputForm.detailInfos[lineIndex].fileInfoLost.splice(fileIndex, 1)
  553. } else {
  554. this.inputForm.files.splice(fileIndex, 1)
  555. }
  556. },
  557. validateDetailInfos() {
  558. if (this.isEmpty(this.inputForm.detailInfos)) {
  559. this.$message.error('至少填写一条领用详情信息')
  560. return false
  561. }
  562. for (let i = 0; i < this.inputForm.detailInfos.length; i++) {
  563. const detail = this.inputForm.detailInfos[i]
  564. const lineNo = i + 1
  565. if (this.isEmpty(detail.recipientAgentId)) {
  566. uni.showToast({
  567. title: `领用详情第${lineNo}行请选择领用人`,
  568. icon: 'none'
  569. })
  570. return false
  571. }
  572. if (this.isEmpty(detail.collectTypeId)) {
  573. uni.showToast({
  574. title: `领用详情第${lineNo}行请选择领用类型`,
  575. icon: 'none'
  576. })
  577. return false
  578. }
  579. if (this.isEmpty(detail.goodsName)) {
  580. uni.showToast({
  581. title: `领用详情第${lineNo}行请选择物品名称`,
  582. icon: 'none'
  583. })
  584. return false
  585. }
  586. if (this.isEmpty(detail.collectNumber)) {
  587. uni.showToast({
  588. title: `领用详情第${lineNo}行请输入领用数量`,
  589. icon: 'none'
  590. })
  591. return false
  592. }
  593. if (this.isNotEmpty(detail.surplusNumber) && Number(detail.collectNumber) > Number(detail.surplusNumber)) {
  594. uni.showToast({
  595. title: `领用详情第${lineNo}行领用数量不能大于库存数量`,
  596. icon: 'none'
  597. })
  598. return false
  599. }
  600. }
  601. return true
  602. },
  603. toSubmitData() {
  604. const data = JSON.parse(JSON.stringify(this.inputForm))
  605. data.collectDate = this.formatDate(this.inputForm.collectDate)
  606. data.detailInfos = (this.inputForm.detailInfos || []).map(item => ({
  607. ...JSON.parse(JSON.stringify(item)),
  608. produceDate: this.formatDate(item.produceDate)
  609. }))
  610. return data
  611. },
  612. async saveForm(callback) {
  613. return new Promise((resolve, reject) => {
  614. // 表单规则验证
  615. // ...
  616. let errors = [];
  617. if (this.isNotEmpty(this.inputForm.collectDate)) {
  618. this.inputForm.collectDate = this.formatDate(this.inputForm.collectDate);
  619. }
  620. if (this.isEmpty(this.inputForm.detailInfos)) {
  621. errors.push('至少填写一条领用详情信息');
  622. } else {
  623. let i = this.inputForm.detailInfos.length;
  624. for (let j = 0; j < i; j++) {
  625. let k = j + 1;
  626. if (this.isEmpty(this.inputForm.detailInfos[j].recipientAgent)) {
  627. errors.push('领用详情第' + k + '行请选择领用人');
  628. } else if (this.isEmpty(this.inputForm.detailInfos[j].collectType)) {
  629. errors.push('领用详情第' + k + '行请选择领用类型');
  630. } else if (this.isEmpty(this.inputForm.detailInfos[j].goodsName)) {
  631. errors.push('领用详情第' + k + '行请选择物品名称');
  632. } else if (this.isEmpty(this.inputForm.detailInfos[j].collectNumber)) {
  633. errors.push('领用详情第' + k + '行请输入领用数量');
  634. }
  635. if (parseFloat(this.inputForm.detailInfos[j].collectNumber) > parseFloat(this.inputForm.detailInfos[j].surplusNumber) * parseFloat(this.inputForm.detailInfos[j].spec)) {
  636. errors.push('领用详情第' + k + '行请输入领用数量');
  637. }
  638. }
  639. }
  640. if (errors.length > 0) {
  641. // 存在错误,显示提示信息
  642. errors.forEach(error => {
  643. uni.showToast({
  644. title: error,
  645. icon: 'none',
  646. duration: 2000
  647. });
  648. });
  649. reject('Form validation failed');
  650. } else {
  651. // 所有验证通过,执行保存操作
  652. this.$refs.inputForm.validate().then(async () => {
  653. uni.showLoading();
  654. try {
  655. const submitData = this.toSubmitData()
  656. submitData.status = '2'
  657. this.inputForm.status = '2'
  658. const data = await this.collectService.save(submitData)
  659. callback(data.businessTable, data.businessId, submitData)
  660. resolve('Form saved successfully');
  661. } finally {
  662. reject('Save operation failed');
  663. }
  664. }).catch(() => {
  665. reject('Form validation failed');
  666. });
  667. }
  668. });
  669. },
  670. async startForm(callback) {
  671. this.loading = true
  672. try {
  673. if (this.isNotEmpty(this.inputForm.id)) {
  674. const data = await this.collectService.findById(this.inputForm.id)
  675. if (this.isNotEmpty(data.status) && !['0', '1', '3'].includes(data.status)) {
  676. uni.showToast({
  677. title: '任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在',
  678. icon: 'none'
  679. })
  680. throw new Error('invalid status')
  681. }
  682. }
  683. await this.startFormTrue(callback)
  684. } finally {
  685. this.loading = false
  686. }
  687. },
  688. async startFormTrue(callback) {
  689. this.normalizeDateFieldsBeforeValidate()
  690. await this.$refs.inputForm.validate()
  691. if (!this.validateDetailInfos()) {
  692. return
  693. }
  694. const submitData = this.toSubmitData()
  695. submitData.status = '2'
  696. this.inputForm.status = '2'
  697. const data = await this.collectService.save(submitData)
  698. this.inputForm.id = data.businessId
  699. callback(data.businessTable, data.businessId, submitData)
  700. },
  701. async reapplyForm(callback) {
  702. this.loading = true
  703. try {
  704. const data = await this.collectService.findById(this.inputForm.id)
  705. if (data.status !== '4') {
  706. uni.showToast({
  707. title: '任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在',
  708. icon: 'none'
  709. })
  710. throw new Error('invalid status')
  711. }
  712. await this.startFormTrue(callback)
  713. } finally {
  714. this.loading = false
  715. }
  716. },
  717. async agreeForm(callback) {
  718. this.loading = true
  719. try {
  720. this.normalizeDateFieldsBeforeValidate()
  721. await this.$refs.inputForm.validate()
  722. if (!this.validateDetailInfos()) {
  723. return
  724. }
  725. const submitData = this.toSubmitData()
  726. try {
  727. const taskName = await this.commonApi.getTaskNameByProcInsId(this.inputForm.procInsId)
  728. if (this.isNotEmpty(taskName) && taskName === '仓库管理员审核') {
  729. submitData.status = '5'
  730. this.inputForm.status = '5'
  731. }
  732. } catch (e) {
  733. }
  734. const data = await this.collectService.save(submitData)
  735. callback(data.businessTable, data.businessId, submitData)
  736. } finally {
  737. this.loading = false
  738. }
  739. },
  740. async updateStatusById(type, callback) {
  741. this.loading = true
  742. try {
  743. if (type === 'reject' || type === 'reback') {
  744. const data = await this.collectService.findById(this.inputForm.id)
  745. if (data.status !== '2') {
  746. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  747. throw new Error('invalid status')
  748. }
  749. const nextStatus = type === 'reject' ? '4' : '3'
  750. this.inputForm.status = nextStatus
  751. await this.collectService.updateStatusById({
  752. status: nextStatus,
  753. id: this.inputForm.id
  754. })
  755. callback()
  756. } else if (type === 'hold') {
  757. const data = await this.collectService.findById(this.inputForm.id)
  758. if (data.status !== '4') {
  759. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  760. throw new Error('invalid status')
  761. }
  762. this.inputForm.status = '1'
  763. await this.collectService.updateStatusById({
  764. status: '1',
  765. id: this.inputForm.id
  766. })
  767. callback()
  768. }
  769. } finally {
  770. this.loading = false
  771. }
  772. },
  773. async openPicker(index) {
  774. if (this.isEmpty(this.inputForm.detailInfos[index].collectTypeId)) {
  775. uni.showToast({
  776. title: '请先选择领用类型',
  777. icon: 'none'
  778. })
  779. return
  780. }
  781. await this.$refs.goodsSelect.loadGoodsList()
  782. },
  783. }
  784. }
  785. </script>
  786. <style scoped>
  787. .section-wrap {
  788. margin-top: 24rpx;
  789. }
  790. .section-title {
  791. margin-bottom: 8rpx;
  792. padding-left: 8rpx;
  793. font-size: 30rpx;
  794. font-weight: 700;
  795. color: #1f2937;
  796. border-left: 6rpx solid #2979ff;
  797. }
  798. .section-tip {
  799. margin-bottom: 16rpx;
  800. font-size: 24rpx;
  801. color: #64748b;
  802. }
  803. .detail-card {
  804. margin-bottom: 20rpx;
  805. padding: 24rpx;
  806. background: #fff;
  807. border-radius: 20rpx;
  808. box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
  809. }
  810. .detail-card-title {
  811. margin-bottom: 12rpx;
  812. font-size: 28rpx;
  813. font-weight: 600;
  814. color: #0f172a;
  815. }
  816. </style>