CollectForm.vue 29 KB

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