LossForm.vue 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  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="lossNo">
  9. <u--input placeholder="自动生成" v-model="inputForm.lossNo" 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="lossDate" :required="true">
  18. <el-date-picker v-model="inputForm.lossDate" 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. <loss-goods-selector :rowIndex="index" :inputForm="inputForm" :disabled="nodeFlag"
  39. @selected="handleGoodsSelected"></loss-goods-selector>
  40. <u-form-item label="品牌" :prop="'detailInfos[' + index + '].brand'">
  41. <u--input v-model="inputForm.detailInfos[index].brand" disabled></u--input>
  42. </u-form-item>
  43. <u-form-item label="规格" :prop="'detailInfos[' + index + '].specification'">
  44. <u--input v-model="inputForm.detailInfos[index].specification" disabled></u--input>
  45. </u-form-item>
  46. <u-form-item label="单位" :prop="'detailInfos[' + index + '].company'">
  47. <u--input v-model="inputForm.detailInfos[index].company" disabled></u--input>
  48. </u-form-item>
  49. <u-form-item label="入库批次" :prop="'detailInfos[' + index + '].wareHouseNumber'">
  50. <u--input v-model="inputForm.detailInfos[index].wareHouseNumber" disabled></u--input>
  51. </u-form-item>
  52. <u-form-item label="入库日期" :prop="'detailInfos[' + index + '].wareHouseDate'">
  53. <u--input :value="formatDisplayDate(inputForm.detailInfos[index].wareHouseDate)" disabled>
  54. </u--input>
  55. </u-form-item>
  56. <u-form-item label="批次库存" :prop="'detailInfos[' + index + '].surplusNumber'">
  57. <u--input v-model="inputForm.detailInfos[index].surplusNumber" disabled></u--input>
  58. </u-form-item>
  59. <u-form-item label="报损数量" :prop="'detailInfos[' + index + '].lossNumber'" :required="true">
  60. <u--input v-model="inputForm.detailInfos[index].lossNumber" placeholder="请输入报损数量"
  61. type="number" :disabled="nodeFlag" @input="handleLossNumberInput(index)"
  62. @blur="handleLossNumberBlur(index)"></u--input>
  63. </u-form-item>
  64. <u-form-item label="报损原因" :prop="'detailInfos[' + index + '].lossReason'" :required="true">
  65. <u--textarea v-model="inputForm.detailInfos[index].lossReason" placeholder="请输入报损原因"
  66. :disabled="nodeFlag" :rows="3" :maxlength="200"></u--textarea>
  67. </u-form-item>
  68. <u-form-item label="附件上传">
  69. <UploadComponent :uploadUrl="`${uploadUrl}/public-modules-server/oss/file/webUpload/upload`"
  70. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, index, fileIndex, 'detail')"
  71. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, index, 'detail')"
  72. :fileList="inputForm.detailInfos[index].fileInfoLost" :limit="3" :isDelete="nodeFlag"
  73. :isUpload="nodeFlag">
  74. </UploadComponent>
  75. </u-form-item>
  76. <u-form-item label="" v-if="!nodeFlag">
  77. <el-button style="width: 100%" type="danger" plain @click="removeDetail(index)">
  78. 删除报损详情
  79. </el-button>
  80. </u-form-item>
  81. </view>
  82. <u-form-item label="" v-if="!nodeFlag">
  83. <el-button style="width: 100%" type="primary" @click="addDetail()" plain>新增报损详情</el-button>
  84. </u-form-item>
  85. </view>
  86. <u-form-item label="附件">
  87. <UploadComponent :uploadUrl="`${uploadUrl}/public-modules-server/oss/file/webUpload/upload`"
  88. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, '', fileIndex, '')"
  89. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, '', '')"
  90. :fileList="inputForm.files" :limit="3" :isDelete="nodeFlag" :isUpload="nodeFlag">
  91. </UploadComponent>
  92. </u-form-item>
  93. </u--form>
  94. <user-select ref="userPicker" @input="handleUserSelected"></user-select>
  95. <ba-tree-picker ref="treePicker" :multiple="false" @select-change="selectTypeChange" title="类型选择"
  96. :localdata="listData" valueKey="value" textKey="label" childrenKey="children" />
  97. </view>
  98. </template>
  99. <script>
  100. import { mapState } from 'vuex'
  101. import userSelect from '@/components/user-select/user-select-radio.vue'
  102. import baTreePicker from '@/components/ba-tree-picker/ba-tree-picker.vue'
  103. import UploadComponent from '@/pages/common/UploadComponent.vue'
  104. import upload from '@/utils/upload.js'
  105. import MaterialTypeService from '@/api/psi/MaterialTypeService'
  106. import WareHouseService from '@/api/psi/WareHouseService'
  107. import LossService from '@/api/psi/LossService'
  108. import CommonApi from '@/api/common/CommonApi'
  109. import LossGoodsSelector from './LossGoodsSelector.vue'
  110. export default {
  111. name: 'PsiLossForm',
  112. components: {
  113. userSelect,
  114. baTreePicker,
  115. UploadComponent,
  116. LossGoodsSelector
  117. },
  118. computed: mapState({
  119. userInfo: (state) => state.user.userInfo
  120. }),
  121. props: {
  122. businessId: {
  123. type: String,
  124. default: ''
  125. },
  126. formReadOnly: {
  127. type: Boolean,
  128. default: false
  129. },
  130. status: {
  131. type: String,
  132. default: ''
  133. },
  134. isCc: {
  135. type: Boolean,
  136. default: false
  137. }
  138. },
  139. data() {
  140. return {
  141. uploadUrl: upload.UPLOAD_URL,
  142. nodeFlag: false,
  143. loading: false,
  144. listData: [],
  145. materialList: [],
  146. inputForm: this.createInputForm(),
  147. rules: {
  148. lossDate: [
  149. {
  150. required: true,
  151. message: '报损时间不能为空',
  152. trigger: ['change']
  153. }
  154. ]
  155. }
  156. }
  157. },
  158. materialTypeService: null,
  159. wareHouseService: null,
  160. lossService: null,
  161. commonApi: null,
  162. created() {
  163. this.ensureServices()
  164. this.loadMaterialTypes()
  165. this.fillUserInfo()
  166. },
  167. watch: {
  168. businessId: {
  169. handler() {
  170. if (this.businessId) {
  171. this.init(this.businessId)
  172. } else {
  173. this.resetForm()
  174. this.nodeFlag = this.formReadOnly || this.status === 'taskFormDetail' || this.status === 'testSee'
  175. }
  176. },
  177. immediate: true,
  178. deep: false
  179. }
  180. },
  181. methods: {
  182. createInputForm() {
  183. return {
  184. id: '',
  185. procInsId: '',
  186. processDefinitionId: '',
  187. status: '',
  188. lossNo: '',
  189. lossDate: '',
  190. remarks: '',
  191. handledBy: '',
  192. handledById: '',
  193. handledByOffice: '',
  194. handledByOfficeName: '',
  195. userId: '',
  196. files: [],
  197. detailInfos: []
  198. }
  199. },
  200. createDetailRow() {
  201. return {
  202. id: '',
  203. recipientAgent: (this.userInfo && this.userInfo.name) || '',
  204. recipientAgentId: (this.userInfo && this.userInfo.id) || '',
  205. recipientOffice: (this.userInfo && this.userInfo.officeDTO && this.userInfo.officeDTO.name) || '',
  206. collectType: '',
  207. collectTypeId: '',
  208. goodsName: '',
  209. inventoryId: '',
  210. lossNumber: '',
  211. lossReason: '',
  212. company: '',
  213. brand: '',
  214. specification: '',
  215. wareHouseNumber: '',
  216. wareHouseDate: '',
  217. produceDate: '',
  218. shelfLife: '',
  219. shelfLifeUnit: '',
  220. spec: '1',
  221. surplusNumber: '',
  222. currentInventory: '',
  223. fileInfoLost: []
  224. }
  225. },
  226. ensureServices() {
  227. if (!this.materialTypeService) {
  228. this.materialTypeService = new MaterialTypeService()
  229. }
  230. if (!this.wareHouseService) {
  231. this.wareHouseService = new WareHouseService()
  232. }
  233. if (!this.lossService) {
  234. this.lossService = new LossService()
  235. }
  236. if (!this.commonApi) {
  237. this.commonApi = new CommonApi()
  238. }
  239. },
  240. async loadMaterialTypes() {
  241. try {
  242. const data = await this.materialTypeService.cgList()
  243. this.materialList = data || []
  244. this.listData = this.buildTree(this.materialList)
  245. } catch (e) {
  246. this.materialList = []
  247. this.listData = []
  248. }
  249. },
  250. fillUserInfo() {
  251. if (!this.userInfo) {
  252. return
  253. }
  254. this.inputForm.handledBy = this.inputForm.handledBy || this.userInfo.name
  255. this.inputForm.handledById = this.inputForm.handledById || this.userInfo.id
  256. this.inputForm.userId = this.inputForm.userId || this.userInfo.id
  257. this.inputForm.handledByOffice = this.inputForm.handledByOffice || (this.userInfo.officeDTO && this.userInfo.officeDTO.id) || ''
  258. this.inputForm.handledByOfficeName = this.inputForm.handledByOfficeName || (this.userInfo.officeDTO && this.userInfo.officeDTO.name) || ''
  259. },
  260. resetForm() {
  261. this.inputForm = this.createInputForm()
  262. this.fillUserInfo()
  263. },
  264. async init(id) {
  265. this.ensureServices()
  266. this.resetForm()
  267. this.inputForm.id = id
  268. this.loading = true
  269. try {
  270. const data = await this.lossService.findById(id)
  271. this.inputForm = this.normalizeFormData(this.recover(this.createInputForm(), data))
  272. this.fillUserInfo()
  273. this.nodeFlag = await this.resolveNodeFlag(data)
  274. } catch (e) {
  275. this.nodeFlag = true
  276. } finally {
  277. this.loading = false
  278. }
  279. },
  280. async resolveNodeFlag(data) {
  281. if (this.formReadOnly || this.status === 'taskFormDetail' || this.status === 'testSee') {
  282. return true
  283. }
  284. try {
  285. const taskName = await this.commonApi.getTaskNameByProcInsId((data && data.procInsId) || this.inputForm.procInsId)
  286. if (this.isNotEmpty(taskName)) {
  287. return taskName !== '发起人重新发起申请'
  288. }
  289. } catch (e) {
  290. }
  291. return false
  292. },
  293. normalizeFormData(data) {
  294. const form = data || this.createInputForm()
  295. if (this.isNotEmpty(form.lossDate)) {
  296. form.lossDate = new Date(form.lossDate)
  297. }
  298. form.files = form.files || []
  299. form.detailInfos = (form.detailInfos || []).map(item => ({
  300. ...item,
  301. fileInfoLost: item.fileInfoLost || [],
  302. produceDate: this.isNotEmpty(item.produceDate) ? new Date(item.produceDate) : '',
  303. surplusNumber: this.isNotEmpty(item.surplusNumber) ? this.formatNumber(item.surplusNumber) : ''
  304. }))
  305. return form
  306. },
  307. buildTree(nodes, parentId = '0') {
  308. const tree = []
  309. for (const node of nodes || []) {
  310. if (node.parentId === parentId) {
  311. const children = this.buildTree(nodes, node.id)
  312. if (children.length) {
  313. node.children = children
  314. }
  315. tree.push(node)
  316. }
  317. }
  318. return tree
  319. },
  320. isEmpty(value) {
  321. if (value === null || value === undefined) {
  322. return true
  323. }
  324. if (typeof value === 'string' && value.trim() === '') {
  325. return true
  326. }
  327. if (Array.isArray(value) && value.length === 0) {
  328. return true
  329. }
  330. return false
  331. },
  332. isNotEmpty(value) {
  333. return !this.isEmpty(value)
  334. },
  335. formatDate(date) {
  336. if (this.isEmpty(date)) {
  337. return ''
  338. }
  339. const dateValue = new Date(date)
  340. if (Number.isNaN(dateValue.getTime())) {
  341. return ''
  342. }
  343. const year = dateValue.getFullYear()
  344. const month = `${dateValue.getMonth() + 1}`.padStart(2, '0')
  345. const day = `${dateValue.getDate()}`.padStart(2, '0')
  346. return `${year}-${month}-${day}`
  347. },
  348. formatDisplayDate(date) {
  349. return this.formatDate(date)
  350. },
  351. normalizeDateFieldsBeforeValidate() {
  352. if (this.isNotEmpty(this.inputForm.lossDate)) {
  353. this.inputForm.lossDate = this.formatDate(this.inputForm.lossDate)
  354. }
  355. },
  356. formatNumber(value) {
  357. const numberValue = Number(value || 0)
  358. if (Number.isNaN(numberValue)) {
  359. return ''
  360. }
  361. return numberValue % 1 === 0 ? String(numberValue) : numberValue.toFixed(2).replace(/\.?0+$/, '')
  362. },
  363. formatBatchStock(currentInventory, spec) {
  364. const current = Number(currentInventory || 0)
  365. const specNumber = Number(spec || 1)
  366. if (Number.isNaN(current) || Number.isNaN(specNumber)) {
  367. return '0'
  368. }
  369. return this.formatNumber(current * (specNumber > 0 ? specNumber : 1))
  370. },
  371. formatNumberInput(inputValue) {
  372. if (this.isEmpty(inputValue)) {
  373. return ''
  374. }
  375. let value = String(inputValue).replace(/[^\d.]/g, '')
  376. if (value.endsWith('.')) {
  377. return ''
  378. }
  379. const dotIndex = value.indexOf('.')
  380. if (dotIndex !== -1) {
  381. const integerPart = value.substring(0, dotIndex)
  382. const decimalPart = value.substring(dotIndex + 1).replace(/\./g, '').replace(/[1-9]/g, '')
  383. value = integerPart + '.' + decimalPart
  384. }
  385. value = value.replace(/^0+(\d)/, '$1')
  386. return value === '.' ? '' : value
  387. },
  388. addDetail() {
  389. this.inputForm.detailInfos.push(this.createDetailRow())
  390. },
  391. removeDetail(index) {
  392. this.inputForm.detailInfos.splice(index, 1)
  393. },
  394. patchDetail(index, patch) {
  395. const detail = (this.inputForm.detailInfos || [])[index] || {}
  396. this.$set(this.inputForm.detailInfos, index, {
  397. ...detail,
  398. ...patch
  399. })
  400. },
  401. openUserPullForm(index) {
  402. if (this.nodeFlag) {
  403. return
  404. }
  405. this.$refs.userPicker.open(index, 'detail')
  406. },
  407. handleUserSelected(data, index, type) {
  408. if (type !== 'detail') {
  409. return
  410. }
  411. this.patchDetail(index, {
  412. recipientAgentId: data.id,
  413. recipientAgent: data.label,
  414. recipientOffice: data.parentLabel
  415. })
  416. },
  417. openTypePicker(index) {
  418. if (this.nodeFlag) {
  419. return
  420. }
  421. this.$refs.treePicker._show(index)
  422. },
  423. selectTypeChange(ids, names, index) {
  424. this.patchDetail(index, {
  425. collectType: names,
  426. collectTypeId: ids[0],
  427. goodsName: '',
  428. inventoryId: '',
  429. company: '',
  430. brand: '',
  431. specification: '',
  432. wareHouseNumber: '',
  433. wareHouseDate: '',
  434. produceDate: '',
  435. shelfLife: '',
  436. shelfLifeUnit: '',
  437. spec: '1',
  438. surplusNumber: '',
  439. currentInventory: '',
  440. lossNumber: ''
  441. })
  442. },
  443. async handleGoodsSelected({ index, item }) {
  444. const stock = this.formatBatchStock(item.currentInventory, item.spec)
  445. this.patchDetail(index, {
  446. inventoryId: item.id || '',
  447. collectType: item.tradeName || '',
  448. collectTypeId: item.wareHouseType || '',
  449. goodsName: item.tradeName || '',
  450. company: item.company || '',
  451. brand: item.brand || '',
  452. specification: item.specification || '',
  453. wareHouseNumber: item.wareHouseNumber || '',
  454. wareHouseDate: this.isNotEmpty(item.wareHouseDate) ? new Date(item.wareHouseDate) : '',
  455. produceDate: this.isNotEmpty(item.produceDate) ? new Date(item.produceDate) : '',
  456. shelfLife: item.shelfLife || '',
  457. shelfLifeUnit: item.shelfLifeUnit || '',
  458. spec: item.spec || '1',
  459. currentInventory: item.currentInventory || '',
  460. surplusNumber: stock
  461. })
  462. },
  463. async syncDetailStock(index) {
  464. const detail = (this.inputForm.detailInfos || [])[index]
  465. if (!detail || this.isEmpty(detail.goodsName) || this.isEmpty(detail.collectTypeId)) {
  466. return
  467. }
  468. try {
  469. const data = await this.wareHouseService.getByProduceDateNotMerge({
  470. current: 1,
  471. size: 1000,
  472. tradeName: detail.goodsName,
  473. wareHouseType: detail.collectTypeId
  474. })
  475. const records = (data && data.records) || []
  476. const total = records.reduce((sum, item) => {
  477. const currentInventory = Number(item.currentInventory || 0)
  478. const spec = Number(item.spec || 1)
  479. return sum + currentInventory * (spec > 0 ? spec : 1)
  480. }, 0)
  481. this.$set(this.inputForm.detailInfos[index], 'surplusNumber', this.formatNumber(total))
  482. } catch (e) {
  483. this.$set(this.inputForm.detailInfos[index], 'surplusNumber', '')
  484. uni.showToast({
  485. title: '库存数量加载失败',
  486. icon: 'none'
  487. })
  488. }
  489. },
  490. handleUploadSuccess(file, fileList, index, type) {
  491. if (type === 'detail') {
  492. this.inputForm.detailInfos[index].fileInfoLost = fileList
  493. } else {
  494. this.inputForm.files = fileList
  495. }
  496. },
  497. handleRemove(file, fileList, lineIndex, fileIndex, type) {
  498. if (type === 'detail') {
  499. this.inputForm.detailInfos[lineIndex].fileInfoLost.splice(fileIndex, 1)
  500. } else {
  501. this.inputForm.files.splice(fileIndex, 1)
  502. }
  503. },
  504. handleLossNumberBlur(index) {
  505. const detail = (this.inputForm.detailInfos || [])[index]
  506. if (!detail) {
  507. return
  508. }
  509. detail.lossNumber = this.formatNumberInput(detail.lossNumber)
  510. if (this.isNotEmpty(detail.surplusNumber) && this.isNotEmpty(detail.lossNumber) &&
  511. Number(detail.lossNumber) > Number(detail.surplusNumber)) {
  512. this.$message.error('报损数量不能大于当前库存')
  513. detail.lossNumber = ''
  514. }
  515. },
  516. handleLossNumberInput(index) {
  517. const detail = (this.inputForm.detailInfos || [])[index]
  518. if (!detail) {
  519. return
  520. }
  521. detail.lossNumber = this.formatNumberInput(detail.lossNumber)
  522. },
  523. validateDetailInfos(checkStock = true) {
  524. if (this.isEmpty(this.inputForm.detailInfos)) {
  525. this.$message.error('请填写报损明细')
  526. return false
  527. }
  528. for (let index = 0; index < this.inputForm.detailInfos.length; index++) {
  529. const detail = this.inputForm.detailInfos[index]
  530. const lineNo = index + 1
  531. if (this.isEmpty(detail.recipientAgentId)) {
  532. this.$message.error(`报损明细第${lineNo}行请选择报损人`)
  533. return false
  534. }
  535. if (this.isEmpty(detail.collectTypeId)) {
  536. this.$message.error(`报损明细第${lineNo}行请选择报损类型`)
  537. return false
  538. }
  539. if (this.isEmpty(detail.goodsName)) {
  540. this.$message.error(`报损明细第${lineNo}行请选择物品名称`)
  541. return false
  542. }
  543. if (this.isEmpty(detail.inventoryId)) {
  544. this.$message.error(`报损明细第${lineNo}行请选择报损库存批次`)
  545. return false
  546. }
  547. if (this.isEmpty(detail.lossNumber)) {
  548. this.$message.error(`报损明细第${lineNo}行请输入报损数量`)
  549. return false
  550. }
  551. if (!/^[1-9]\d*(?:\.0+)?$/.test(String(detail.lossNumber))) {
  552. this.$message.error(`报损明细第${lineNo}行报损数量请输入正整数`)
  553. return false
  554. }
  555. if (this.isEmpty(detail.lossReason)) {
  556. this.$message.error(`报损明细第${lineNo}行请输入报损原因`)
  557. return false
  558. }
  559. if (checkStock && this.isNotEmpty(detail.surplusNumber) && Number(detail.lossNumber) > Number(detail.surplusNumber)) {
  560. this.$message.error(`报损明细第${lineNo}行报损数量不能大于当前库存`)
  561. return false
  562. }
  563. }
  564. return true
  565. },
  566. toSubmitData() {
  567. const data = JSON.parse(JSON.stringify(this.inputForm))
  568. data.lossDate = this.formatDate(this.inputForm.lossDate)
  569. data.detailInfos = (this.inputForm.detailInfos || []).map(item => ({
  570. ...JSON.parse(JSON.stringify(item)),
  571. produceDate: this.formatDate(item.produceDate)
  572. }))
  573. return data
  574. },
  575. async saveForm(callback) {
  576. this.loading = true
  577. try {
  578. const submitData = this.toSubmitData()
  579. submitData.status = '1'
  580. this.inputForm.status = '1'
  581. const data = await this.lossService.save(submitData)
  582. callback(data.businessTable, data.businessId, submitData)
  583. } finally {
  584. this.loading = false
  585. }
  586. },
  587. async startForm(callback) {
  588. this.loading = true
  589. try {
  590. if (this.isNotEmpty(this.inputForm.id)) {
  591. const data = await this.lossService.findById(this.inputForm.id)
  592. if (this.isNotEmpty(data.status) && !['0', '1', '3'].includes(data.status)) {
  593. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  594. throw new Error('invalid status')
  595. }
  596. }
  597. await this.startFormTrue(callback)
  598. } finally {
  599. this.loading = false
  600. }
  601. },
  602. async startFormTrue(callback) {
  603. this.normalizeDateFieldsBeforeValidate()
  604. await this.$refs.inputForm.validate()
  605. if (!this.validateDetailInfos(true)) {
  606. return
  607. }
  608. const submitData = this.toSubmitData()
  609. submitData.status = '2'
  610. this.inputForm.status = '2'
  611. const data = await this.lossService.save(submitData)
  612. this.inputForm.id = data.businessId
  613. callback(data.businessTable, data.businessId, submitData)
  614. },
  615. async reapplyForm(callback) {
  616. this.loading = true
  617. try {
  618. const data = await this.lossService.findById(this.inputForm.id)
  619. if (data.status !== '4') {
  620. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  621. throw new Error('invalid status')
  622. }
  623. await this.startFormTrue(callback)
  624. } finally {
  625. this.loading = false
  626. }
  627. },
  628. async agreeForm(callback) {
  629. this.loading = true
  630. try {
  631. this.normalizeDateFieldsBeforeValidate()
  632. await this.$refs.inputForm.validate()
  633. if (!this.validateDetailInfos(false)) {
  634. return
  635. }
  636. const submitData = this.toSubmitData()
  637. submitData.status = '5'
  638. this.inputForm.status = '5'
  639. const data = await this.lossService.save(submitData)
  640. callback(data.businessTable, data.businessId, submitData)
  641. } finally {
  642. this.loading = false
  643. }
  644. },
  645. async updateStatusById(type, callback) {
  646. this.loading = true
  647. try {
  648. if (type === 'reject' || type === 'reback') {
  649. const data = await this.lossService.findById(this.inputForm.id)
  650. if (data.status !== '2') {
  651. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  652. throw new Error('invalid status')
  653. }
  654. const nextStatus = type === 'reject' ? '4' : '3'
  655. this.inputForm.status = nextStatus
  656. await this.lossService.updateStatusById({
  657. status: nextStatus,
  658. id: this.inputForm.id
  659. })
  660. callback()
  661. } else if (type === 'hold') {
  662. const data = await this.lossService.findById(this.inputForm.id)
  663. if (data.status !== '4') {
  664. this.$message.error('任务数据已发生改变或不存在,请在待办任务中确认此任务是否存在')
  665. throw new Error('invalid status')
  666. }
  667. this.inputForm.status = '1'
  668. await this.lossService.updateStatusById({
  669. status: '1',
  670. id: this.inputForm.id
  671. })
  672. callback()
  673. }
  674. } finally {
  675. this.loading = false
  676. }
  677. }
  678. }
  679. }
  680. </script>
  681. <style scoped>
  682. .section-wrap {
  683. margin-top: 24rpx;
  684. }
  685. .section-title {
  686. margin-bottom: 8rpx;
  687. padding-left: 8rpx;
  688. font-size: 30rpx;
  689. font-weight: 700;
  690. color: #1f2937;
  691. border-left: 6rpx solid #2979ff;
  692. }
  693. .section-tip {
  694. margin-bottom: 16rpx;
  695. font-size: 24rpx;
  696. color: #64748b;
  697. }
  698. .detail-card {
  699. margin-bottom: 20rpx;
  700. padding: 24rpx;
  701. background: #fff;
  702. border-radius: 20rpx;
  703. box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
  704. }
  705. .detail-card-title {
  706. margin-bottom: 12rpx;
  707. font-size: 28rpx;
  708. font-weight: 600;
  709. color: #0f172a;
  710. }
  711. </style>