CollectForm.vue 29 KB

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