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