WareHouseAddForm.vue 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
  1. <template>
  2. <view class="page-wrap">
  3. <u--form :model="inputForm" labelWidth="100px" class="u-form" labelPosition="left" :rules="rules"
  4. ref="inputForm">
  5. <u-form-item label="入库编号" borderBottom prop="wareHouseNumber">
  6. <u--input placeholder="自动生成" v-model="inputForm.wareHouseNumber" disabled></u--input>
  7. </u-form-item>
  8. <u-form-item label="入库名称" borderBottom prop="wareHouseName" :required="true">
  9. <u--input placeholder="请填写入库名称" v-model="inputForm.wareHouseName" :disabled="nodeFlag"></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="wareHouseDate" :required="true">
  18. <el-date-picker v-model="inputForm.wareHouseDate" type="date" value-format="yyyy-MM-dd"
  19. placeholder="请选择入库时间" style="width:100%" placement="bottom-start" clearable
  20. :disabled="nodeFlag">
  21. </el-date-picker>
  22. </u-form-item>
  23. <purchase-selector :inputForm="inputForm" :disabled="nodeFlag" :required="false"
  24. @selected="handlePurchaseSelected">
  25. </purchase-selector>
  26. <u-form-item label="备注" borderBottom prop="remarks">
  27. <u--textarea placeholder="请填写备注信息" :rows="4" :maxlength="500" v-model="inputForm.remarks"
  28. :disabled="nodeFlag"></u--textarea>
  29. </u-form-item>
  30. <view v-if="inputForm.purchaseNo" class="section-wrap">
  31. <view class="section-title">采购详情</view>
  32. <view v-for="(item, index) in inputForm.detailInfos" :key="item.id || index" class="detail-card">
  33. <view class="detail-card-title">采购明细 {{ index + 1 }}</view>
  34. <view class="detail-grid">
  35. <view class="detail-item">
  36. <text class="detail-label">采购人</text>
  37. <text class="detail-value">{{ item.purchaserAgent || '-' }}</text>
  38. </view>
  39. <view class="detail-item">
  40. <text class="detail-label">采购部门</text>
  41. <text class="detail-value">{{ item.procurementOffice || '-' }}</text>
  42. </view>
  43. <view class="detail-item">
  44. <text class="detail-label">采购类型</text>
  45. <text class="detail-value">{{ item.procurementType || '-' }}</text>
  46. </view>
  47. <view class="detail-item">
  48. <text class="detail-label">供应商</text>
  49. <text class="detail-value">{{ item.supplierName || '-' }}</text>
  50. </view>
  51. <view class="detail-item">
  52. <text class="detail-label">商品名称</text>
  53. <text class="detail-value">{{ item.tradeName || '-' }}</text>
  54. </view>
  55. <view class="detail-item">
  56. <text class="detail-label">商品数量</text>
  57. <text class="detail-value">{{ item.tradeNumber || '-' }}</text>
  58. </view>
  59. <view class="detail-item">
  60. <text class="detail-label">商品单价</text>
  61. <text class="detail-value">{{ item.tradePrice || '-' }}</text>
  62. </view>
  63. <view class="detail-item">
  64. <text class="detail-label">商品总价</text>
  65. <text class="detail-value">{{ item.priceSum || '-' }}</text>
  66. </view>
  67. <view class="detail-item">
  68. <text class="detail-label">单位</text>
  69. <text class="detail-value">{{ item.company || '-' }}</text>
  70. </view>
  71. <view class="detail-item">
  72. <text class="detail-label">备注</text>
  73. <text class="detail-value">{{ item.remarks || '-' }}</text>
  74. </view>
  75. </view>
  76. <el-button v-if="!nodeFlag" style="width: 100%; margin-top: 16rpx;" type="primary" plain
  77. @click="wareHouseAdd(index)">
  78. 入库
  79. </el-button>
  80. </view>
  81. </view>
  82. <view class="section-wrap">
  83. <view class="section-title">入库详情</view>
  84. <view v-for="(item, index) in inputForm.wareHouse" :key="item.id || index"
  85. class="detail-card warehouse-card">
  86. <view class="detail-card-title">入库明细 {{ index + 1 }}</view>
  87. <u-form-item label="入库人" :prop="'wareHouse[' + index + '].wareHouseMan'" :required="true">
  88. <u--input v-model="inputForm.wareHouse[index].wareHouseMan" placeholder="请选择入库人"
  89. :disabled="nodeFlag" @focus="openUserPullForm(index)" clearable></u--input>
  90. </u-form-item>
  91. <u-form-item label="入库部门" :prop="'wareHouse[' + index + '].wareHouseManOffice'">
  92. <u--input v-model="inputForm.wareHouse[index].wareHouseManOffice" disabled></u--input>
  93. </u-form-item>
  94. <u-form-item label="入库类型" :prop="'wareHouse[' + index + '].wareHouseType'" :required="true">
  95. <u--input v-model="inputForm.wareHouse[index].wareHouseType" placeholder="请选择入库类型"
  96. :disabled="nodeFlag" @focus="openTypePicker(index)" clearable></u--input>
  97. </u-form-item>
  98. <ware-house-goods-selector :rowIndex="index" :inputForm="inputForm" :disabled="nodeFlag"
  99. @selected="handleGoodsSelected"></ware-house-goods-selector>
  100. <ware-house-supplier-selector :rowIndex="index" :inputForm="inputForm" :disabled="nodeFlag"
  101. @selected="handleSupplierSelected"></ware-house-supplier-selector>
  102. <u-form-item label="商品数量" :prop="'wareHouse[' + index + '].tradeNumber'" :required="true">
  103. <u--input v-model="inputForm.wareHouse[index].tradeNumber" placeholder="请输入商品数量"
  104. :disabled="nodeFlag" @blur="onRowInputBlur(index, 'tradeNumber')"></u--input>
  105. </u-form-item>
  106. <u-form-item label="单位" :prop="'wareHouse[' + index + '].company'">
  107. <u--input v-model="inputForm.wareHouse[index].company" placeholder="请输入单位" :disabled="nodeFlag"
  108. @blur="calculateWareHouseTotals"></u--input>
  109. </u-form-item>
  110. <u-form-item label="包装规格" :prop="'wareHouse[' + index + '].spec'" :required="true">
  111. <u--input v-model="inputForm.wareHouse[index].spec" placeholder="请输入包装规格" :disabled="nodeFlag"
  112. @blur="onRowInputBlur(index, 'spec')"></u--input>
  113. </u-form-item>
  114. <u-form-item label="商品单价(元)" :prop="'wareHouse[' + index + '].tradePrice'">
  115. <u--input v-model="inputForm.wareHouse[index].tradePrice" placeholder="请输入商品单价"
  116. :disabled="nodeFlag" @blur="onRowInputBlur(index, 'tradePrice')"></u--input>
  117. </u-form-item>
  118. <u-form-item label="商品总价" :prop="'wareHouse[' + index + '].priceSum'">
  119. <u--input v-model="inputForm.wareHouse[index].priceSum" placeholder="自动计算" disabled></u--input>
  120. </u-form-item>
  121. <u-form-item label="生产日期" :prop="'wareHouse[' + index + '].produceDate'">
  122. <el-date-picker v-model="inputForm.wareHouse[index].produceDate" type="date"
  123. placeholder="请选择生产日期" style="width:100%" placement="bottom-start" clearable
  124. :disabled="nodeFlag">
  125. </el-date-picker>
  126. </u-form-item>
  127. <u-form-item label="保质期" :prop="'wareHouse[' + index + '].shelfLife'">
  128. <view class="shelf-life-wrap">
  129. <u--input class="shelf-life-input" v-model="inputForm.wareHouse[index].shelfLife"
  130. placeholder="请输入保质期" :disabled="nodeFlag">
  131. </u--input>
  132. <jp-picker class="shelf-life-unit" placeholder="单位"
  133. v-model="inputForm.wareHouse[index].shelfLifeUnit" :disabled="nodeFlag" rangeKey="label"
  134. rangeValue="value" :range="shelfLifeUnitOptions">
  135. </jp-picker>
  136. </view>
  137. </u-form-item>
  138. <u-form-item label="实际价格" :prop="'wareHouse[' + index + '].actualPrice'">
  139. <u--input v-model="inputForm.wareHouse[index].actualPrice" placeholder="请输入实际价格"
  140. :disabled="nodeFlag" @blur="onRowInputBlur(index, 'actualPrice')"></u--input>
  141. </u-form-item>
  142. <u-form-item label="备注" :prop="'wareHouse[' + index + '].remarks'">
  143. <u--input v-model="inputForm.wareHouse[index].remarks" placeholder="请输入备注" :disabled="nodeFlag">
  144. </u--input>
  145. </u-form-item>
  146. <u-form-item label="附件上传">
  147. <UploadComponent :uploadUrl="`${uploadUrl}/public-modules-server/oss/file/webUpload/upload`"
  148. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, index, fileIndex, 'detail')"
  149. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, index, 'detail')"
  150. :fileList="inputForm.wareHouse[index].fileInfoLost" :limit="3" :isDelete="nodeFlag"
  151. :isUpload="nodeFlag">
  152. </UploadComponent>
  153. </u-form-item>
  154. <u-form-item label="" v-if="!nodeFlag">
  155. <el-button style="width: 100%" type="danger" plain @click="removeWareHouseRow(index)">
  156. 删除入库明细 {{ index + 1 }}
  157. </el-button>
  158. </u-form-item>
  159. </view>
  160. <u-form-item label="" v-if="!nodeFlag">
  161. <el-button style="width: 100%" type="primary" @click="addWareHouseRow()" plain>新增入库明细</el-button>
  162. </u-form-item>
  163. </view>
  164. <u-form-item label="附件">
  165. <UploadComponent :uploadUrl="`${uploadUrl}/public-modules-server/oss/file/webUpload/upload`"
  166. @onRemove="(file, fileList, fileIndex) => handleRemove(file, fileList, '', fileIndex, '')"
  167. @onSuccess="(file, fileList) => handleUploadSuccess(file, fileList, '', '')"
  168. :fileList="inputForm.files" :limit="3" :isDelete="nodeFlag" :isUpload="nodeFlag">
  169. </UploadComponent>
  170. </u-form-item>
  171. </u--form>
  172. <view v-if="!nodeFlag" class="submit-bar">
  173. <el-button class="submit-button" type="primary" :loading="loading" @click="handlePageSubmit">
  174. 提交
  175. </el-button>
  176. </view>
  177. <user-select ref="userPicker" @input="handleUserSelected"></user-select>
  178. <ba-tree-picker ref="treePicker" :multiple="false" @select-change="selectTypeChange" title="类型选择"
  179. :localdata="listData" valueKey="value" textKey="label" childrenKey="children" />
  180. </view>
  181. </template>
  182. <script>
  183. import { mapState } from 'vuex'
  184. import userSelect from '@/components/user-select/user-select-radio.vue'
  185. import baTreePicker from '@/components/ba-tree-picker/ba-tree-picker.vue'
  186. import UploadComponent from '@/pages/common/UploadComponent.vue'
  187. import upload from '@/utils/upload.js'
  188. import MaterialTypeService from '@/api/psi/MaterialTypeService'
  189. import MaterialManagementService from '@/api/psi/MaterialManagementService'
  190. import WareHouseService from '@/api/psi/WareHouseService'
  191. import PurchaseSelector from './PurchaseSelector.vue'
  192. import WareHouseGoodsSelector from './WareHouseGoodsSelector.vue'
  193. import WareHouseSupplierSelector from './WareHouseSupplierSelector.vue'
  194. export default {
  195. name: 'PsiWareHouseAddForm',
  196. components: {
  197. userSelect,
  198. baTreePicker,
  199. UploadComponent,
  200. PurchaseSelector,
  201. WareHouseGoodsSelector,
  202. WareHouseSupplierSelector
  203. },
  204. computed: mapState({
  205. userInfo: (state) => state.user.userInfo
  206. }),
  207. props: {
  208. businessId: {
  209. type: String,
  210. default: ''
  211. },
  212. formReadOnly: {
  213. type: Boolean,
  214. default: false
  215. },
  216. status: {
  217. type: String,
  218. default: ''
  219. },
  220. isCc: {
  221. type: Boolean,
  222. default: false
  223. }
  224. },
  225. data() {
  226. return {
  227. uploadUrl: upload.UPLOAD_URL,
  228. nodeFlag: false,
  229. loading: false,
  230. listData: [],
  231. materialList: [],
  232. currentTypeIndex: -1,
  233. inputForm: this.createInputForm(),
  234. rules: {
  235. wareHouseName: [
  236. {
  237. required: true,
  238. message: '入库名称不能为空',
  239. trigger: ['blur', 'change']
  240. }
  241. ]
  242. },
  243. shelfLifeUnitOptions: [
  244. { label: '年', value: '年' },
  245. { label: '月', value: '月' },
  246. { label: '天', value: '天' }
  247. ]
  248. }
  249. },
  250. materialTypeService: null,
  251. materialManagementService: null,
  252. wareHouseService: null,
  253. created() {
  254. this.ensureServices()
  255. this.loadMaterialTypes()
  256. this.resetForm()
  257. },
  258. watch: {
  259. userInfo: {
  260. handler() {
  261. this.fillUserInfo()
  262. },
  263. immediate: true,
  264. deep: true
  265. }
  266. },
  267. onLoad(options = {}) {
  268. this.ensureServices()
  269. this.resetForm()
  270. if (options.purchaseId) {
  271. this.loadPurchaseById(options.purchaseId)
  272. } else if (options.id) {
  273. this.init('purchaseAdd', options.id)
  274. }
  275. },
  276. methods: {
  277. createInputForm() {
  278. return {
  279. id: '',
  280. procInsId: '',
  281. processDefinitionId: '',
  282. status: '',
  283. wareHouseTotalPrice: '',
  284. wareHouseActualPrice: '',
  285. tradeTotalPrice: '',
  286. fileInfoLost: [],
  287. purchaseNo: '',
  288. purchaseId: '',
  289. contractNo: '',
  290. handledBy: '',
  291. handledById: '',
  292. handledByOffice: '',
  293. handledByOfficeName: '',
  294. wareHouseName: '',
  295. wareHouseNumber: '',
  296. userId: '',
  297. wareHouseDate: '',
  298. remarks: '',
  299. detailInfos: [],
  300. wareHouse: [],
  301. files: [],
  302. functionFlag: ''
  303. }
  304. },
  305. ensureServices() {
  306. if (!this.materialTypeService) {
  307. this.materialTypeService = new MaterialTypeService()
  308. }
  309. if (!this.materialManagementService) {
  310. this.materialManagementService = new MaterialManagementService()
  311. }
  312. if (!this.wareHouseService) {
  313. this.wareHouseService = new WareHouseService()
  314. }
  315. },
  316. async loadMaterialTypes() {
  317. try {
  318. const data = await this.materialTypeService.cgList()
  319. this.materialList = data || []
  320. this.listData = this.buildTree(this.materialList)
  321. } catch (e) {
  322. this.materialList = []
  323. this.listData = []
  324. }
  325. },
  326. fillUserInfo() {
  327. if (!this.userInfo) {
  328. return
  329. }
  330. const office = this.userInfo.officeDTO || this.userInfo.office || {}
  331. this.inputForm.handledBy = this.inputForm.handledBy || this.userInfo.name || this.userInfo.realname || ''
  332. this.inputForm.handledById = this.inputForm.handledById || this.userInfo.id || this.userInfo.userId || ''
  333. this.inputForm.userId = this.inputForm.userId || this.userInfo.id || this.userInfo.userId || ''
  334. this.inputForm.handledByOffice = this.inputForm.handledByOffice || office.id || this.userInfo.officeId || ''
  335. this.inputForm.handledByOfficeName = this.inputForm.handledByOfficeName || office.name || this.userInfo.officeName || ''
  336. },
  337. resetForm() {
  338. this.inputForm = this.createInputForm()
  339. this.fillUserInfo()
  340. this.nodeFlag = !!this.formReadOnly
  341. },
  342. init(method, id) {
  343. this.resetForm()
  344. if (method === 'purchaseAdd' && id) {
  345. this.loadPurchaseById(id)
  346. }
  347. },
  348. async loadPurchaseById(id) {
  349. try {
  350. const data = await this.materialManagementService.findById(id)
  351. this.applyPurchaseData(data, id)
  352. } catch (e) {
  353. uni.showToast({
  354. title: '采购详情加载失败',
  355. icon: 'none'
  356. })
  357. }
  358. },
  359. applyPurchaseData(data, id) {
  360. const purchase = data || {}
  361. this.inputForm.purchaseNo = purchase.purchaseNo || ''
  362. this.inputForm.purchaseId = id || purchase.id || ''
  363. this.inputForm.detailInfos = (purchase.detailInfos || []).map(item => ({
  364. ...item,
  365. fileInfoLost: item.fileInfoLost || [],
  366. priceSum: this.computePriceSum(item.tradePrice, item.tradeNumber)
  367. }))
  368. this.calculateDetailTotalPrice()
  369. this.calculateWareHouseTotals()
  370. },
  371. buildTree(nodes, parentId = '0') {
  372. const tree = []
  373. for (const node of nodes || []) {
  374. if (node.parentId === parentId) {
  375. const treeNode = {
  376. ...node
  377. }
  378. const children = this.buildTree(nodes, node.id)
  379. if (children.length) {
  380. treeNode.children = children
  381. }
  382. tree.push(treeNode)
  383. }
  384. }
  385. return tree
  386. },
  387. isEmpty(value) {
  388. if (value === null || value === undefined) {
  389. return true
  390. }
  391. if (typeof value === 'string' && value.trim() === '') {
  392. return true
  393. }
  394. if (Array.isArray(value) && value.length === 0) {
  395. return true
  396. }
  397. return false
  398. },
  399. isNotEmpty(value) {
  400. return !this.isEmpty(value)
  401. },
  402. computePriceSum(price, number) {
  403. if (this.isEmpty(price) || this.isEmpty(number)) {
  404. return ''
  405. }
  406. const amount = parseFloat(price || 0) * parseFloat(number || 0)
  407. if (Number.isNaN(amount)) {
  408. return ''
  409. }
  410. return amount.toFixed(2)
  411. },
  412. calculateDetailTotalPrice() {
  413. const total = (this.inputForm.detailInfos || []).reduce((sum, item) => {
  414. return sum + parseFloat(item.priceSum || 0)
  415. }, 0)
  416. this.inputForm.tradeTotalPrice = total ? total.toFixed(2) : ''
  417. },
  418. calculateWareHouseTotals() {
  419. const totalPrice = (this.inputForm.wareHouse || []).reduce((sum, item) => {
  420. return sum + parseFloat(item.priceSum || 0)
  421. }, 0)
  422. const totalActual = (this.inputForm.wareHouse || []).reduce((sum, item) => {
  423. return sum + parseFloat(item.actualPrice || 0)
  424. }, 0)
  425. this.inputForm.wareHouseTotalPrice = totalPrice ? totalPrice.toFixed(2) : ''
  426. this.inputForm.wareHouseActualPrice = totalActual ? totalActual.toFixed(2) : ''
  427. },
  428. openUserPullForm(index) {
  429. if (this.nodeFlag) {
  430. return
  431. }
  432. this.$refs.userPicker.open(index, 'wareHouse')
  433. },
  434. handleUserSelected(data, index, type) {
  435. if (type !== 'wareHouse') {
  436. return
  437. }
  438. this.patchWareHouseRow(index, {
  439. wareHouseManId: data.id,
  440. wareHouseMan: data.label,
  441. wareHouseManOffice: data.parentLabel,
  442. deptId: data.parentId
  443. })
  444. },
  445. openTypePicker(index) {
  446. if (this.nodeFlag) {
  447. return
  448. }
  449. this.currentTypeIndex = index
  450. this.$refs.treePicker._show(index)
  451. },
  452. selectTypeChange(ids, names, index) {
  453. this.patchWareHouseRow(index, {
  454. wareHouseType: names,
  455. wareHouseTypeId: ids[0],
  456. tradeName: '',
  457. supplierName: '',
  458. supplierId: '',
  459. isSupplier: '',
  460. tradePrice: '',
  461. tradeNumber: '',
  462. priceSum: '',
  463. company: '',
  464. spec: '',
  465. actualPrice: '',
  466. surplusNumber: ''
  467. })
  468. },
  469. async handlePurchaseSelected(row) {
  470. try {
  471. const data = await this.materialManagementService.findById(row.id)
  472. if (this.isNotEmpty(this.inputForm.purchaseNo) && this.inputForm.purchaseNo !== data.purchaseNo) {
  473. this.inputForm.wareHouse = []
  474. }
  475. this.applyPurchaseData(data, row.id)
  476. } catch (e) {
  477. uni.showToast({
  478. title: '采购详情加载失败',
  479. icon: 'none'
  480. })
  481. }
  482. },
  483. handleGoodsSelected({ index, item }) {
  484. this.patchWareHouseRow(index, {
  485. tradeName: item.tradeName || ''
  486. })
  487. },
  488. handleSupplierSelected({ index, item }) {
  489. this.patchWareHouseRow(index, {
  490. supplierName: item.name || '',
  491. supplierId: item.id || '',
  492. isSupplier: '2'
  493. })
  494. },
  495. createWareHouseRow(row = {}) {
  496. const office = (this.userInfo && (this.userInfo.officeDTO || this.userInfo.office)) || {}
  497. return {
  498. id: '',
  499. wareHouseManId: this.userInfo && (this.userInfo.id || this.userInfo.userId),
  500. wareHouseMan: this.userInfo && (this.userInfo.name || this.userInfo.realname),
  501. wareHouseManOffice: office.name || (this.userInfo && this.userInfo.officeName),
  502. deptId: office.id || (this.userInfo && this.userInfo.officeId),
  503. wareHouseType: '',
  504. wareHouseTypeId: '',
  505. tradeName: '',
  506. supplierName: '',
  507. supplierId: '',
  508. isSupplier: '',
  509. tradeNumber: '',
  510. company: '',
  511. spec: '',
  512. tradePrice: '',
  513. priceSum: '',
  514. produceDate: '',
  515. shelfLife: '',
  516. shelfLifeUnit: '月',
  517. actualPrice: '',
  518. remarks: '',
  519. fileInfoLost: [],
  520. ...row
  521. }
  522. },
  523. addWareHouseRow(row) {
  524. this.inputForm.wareHouse.push(this.createWareHouseRow(row))
  525. this.calculateWareHouseTotals()
  526. },
  527. wareHouseAdd(index) {
  528. const detail = (this.inputForm.detailInfos || [])[index]
  529. if (!detail) {
  530. return
  531. }
  532. const row = {
  533. wareHouseTypeId: detail.procurementTypeId || '',
  534. wareHouseType: detail.procurementType || '',
  535. tradeName: detail.tradeName || '',
  536. tradeNumber: detail.tradeNumber || '',
  537. tradePrice: detail.tradePrice || '',
  538. company: detail.company || '',
  539. priceSum: this.computePriceSum(detail.tradePrice, detail.tradeNumber),
  540. supplierId: '',
  541. supplierName: '',
  542. isSupplier: '',
  543. spec: '',
  544. produceDate: '',
  545. shelfLife: '',
  546. shelfLifeUnit: '月'
  547. }
  548. if (this.isNotEmpty(detail.supplierId) && this.isNotEmpty(detail.isSupplier) && detail.isSupplier === '2') {
  549. row.supplierId = detail.supplierId
  550. row.supplierName = detail.supplierName
  551. row.isSupplier = detail.isSupplier
  552. }
  553. this.addWareHouseRow(row)
  554. },
  555. patchWareHouseRow(index, patch) {
  556. const row = (this.inputForm.wareHouse || [])[index] || {}
  557. const nextRow = {
  558. ...row,
  559. ...patch
  560. }
  561. if (Object.prototype.hasOwnProperty.call(patch, 'tradePrice') || Object.prototype.hasOwnProperty.call(patch, 'tradeNumber')) {
  562. nextRow.priceSum = this.computePriceSum(nextRow.tradePrice, nextRow.tradeNumber)
  563. }
  564. this.$set(this.inputForm.wareHouse, index, nextRow)
  565. this.calculateWareHouseTotals()
  566. },
  567. removeWareHouseRow(index) {
  568. this.inputForm.wareHouse.splice(index, 1)
  569. this.calculateWareHouseTotals()
  570. },
  571. onRowInputBlur(index, field) {
  572. const row = (this.inputForm.wareHouse || [])[index]
  573. if (!row) {
  574. return
  575. }
  576. let value = row[field]
  577. if (['tradeNumber', 'tradePrice', 'actualPrice'].includes(field)) {
  578. value = this.formatNumberInput(value)
  579. }
  580. if (field === 'spec') {
  581. value = this.formatIntegerInput(value)
  582. if (this.isNotEmpty(value) && parseInt(value, 10) <= 0) {
  583. value = ''
  584. this.$message.warning('请输入除0以外的正整数')
  585. }
  586. }
  587. this.patchWareHouseRow(index, {
  588. [field]: value
  589. })
  590. },
  591. formatNumberInput(inputValue, decimalLimit = 2) {
  592. if (this.isEmpty(inputValue)) {
  593. return ''
  594. }
  595. const valueText = String(inputValue)
  596. if (!/^\d*\.?\d*$/.test(valueText)) {
  597. return ''
  598. }
  599. let value = valueText.replace(/[^\d.]/g, '')
  600. const dotIndex = value.indexOf('.')
  601. if (dotIndex !== -1) {
  602. const substr = value.substr(dotIndex + 1)
  603. if (substr.indexOf('.') !== -1) {
  604. value = value.substr(0, dotIndex + 1) + substr.replace(/\./g, '')
  605. }
  606. const integerPart = value.substring(0, dotIndex)
  607. const decimalPart = value.substring(dotIndex + 1, dotIndex + 1 + decimalLimit)
  608. value = integerPart + '.' + decimalPart
  609. }
  610. return value
  611. },
  612. formatIntegerInput(inputValue) {
  613. if (this.isEmpty(inputValue)) {
  614. return ''
  615. }
  616. return String(inputValue).replace(/[^\d]/g, '')
  617. },
  618. handleUploadSuccess(file, fileList, index, type) {
  619. if (type === 'detail') {
  620. this.inputForm.wareHouse[index].fileInfoLost = fileList
  621. } else {
  622. this.inputForm.files = fileList
  623. }
  624. },
  625. handleRemove(file, fileList, lineIndex, fileIndex, type) {
  626. if (type === 'detail') {
  627. this.inputForm.wareHouse[lineIndex].fileInfoLost.splice(fileIndex, 1)
  628. } else {
  629. this.inputForm.files.splice(fileIndex, 1)
  630. }
  631. },
  632. formatDate(date) {
  633. if (this.isEmpty(date)) {
  634. return ''
  635. }
  636. const dateNew = new Date(date)
  637. if (Number.isNaN(dateNew.getTime())) {
  638. return ''
  639. }
  640. const year = dateNew.getFullYear()
  641. const month = `${dateNew.getMonth() + 1}`.padStart(2, '0')
  642. const day = `${dateNew.getDate()}`.padStart(2, '0')
  643. return `${year}-${month}-${day}`
  644. },
  645. toSubmitData() {
  646. const data = JSON.parse(JSON.stringify(this.inputForm))
  647. data.wareHouseDate = this.formatDate(this.inputForm.wareHouseDate)
  648. data.tradeTotalPrice = this.normalizeOptionalNumber(data.tradeTotalPrice)
  649. data.wareHouseTotalPrice = this.normalizeOptionalNumber(data.wareHouseTotalPrice)
  650. data.wareHouseActualPrice = this.normalizeOptionalNumber(data.wareHouseActualPrice)
  651. data.wareHouse = (this.inputForm.wareHouse || []).map(item => ({
  652. ...JSON.parse(JSON.stringify(item)),
  653. produceDate: this.formatDate(item.produceDate),
  654. priceSum: this.normalizeOptionalNumber(this.computePriceSum(item.tradePrice, item.tradeNumber)),
  655. tradeTotalPrice: this.normalizeOptionalNumber(item.tradeTotalPrice),
  656. actualPrice: this.normalizeOptionalNumber(item.actualPrice)
  657. }))
  658. return data
  659. },
  660. normalizeOptionalNumber(value) {
  661. if (this.isEmpty(value)) {
  662. return null
  663. }
  664. return value
  665. },
  666. validateBaseForm() {
  667. if (this.isEmpty(this.inputForm.wareHouseDate)) {
  668. this.$message.error('入库时间不能为空')
  669. return false
  670. }
  671. return true
  672. },
  673. validateWareHouseRows() {
  674. if (this.isEmpty(this.inputForm.wareHouse)) {
  675. this.$message.error('至少填写一条入库详情信息')
  676. return false
  677. }
  678. const rows = this.inputForm.wareHouse
  679. for (let i = 0; i < rows.length; i++) {
  680. const row = rows[i]
  681. const lineNo = i + 1
  682. if (this.isEmpty(row.wareHouseMan)) {
  683. this.$message.error(`入库详情第${lineNo}行请选择入库人`)
  684. return false
  685. }
  686. if (this.isEmpty(row.wareHouseType)) {
  687. this.$message.error(`入库详情第${lineNo}行请选择入库类型`)
  688. return false
  689. }
  690. if (this.isEmpty(row.supplierName) || this.isEmpty(row.supplierId)) {
  691. this.$message.error(`入库详情第${lineNo}行请选择供应商`)
  692. return false
  693. }
  694. if (this.isEmpty(row.tradeName)) {
  695. this.$message.error(`入库详情第${lineNo}行请填写商品名称`)
  696. return false
  697. }
  698. if (this.isEmpty(row.tradeNumber)) {
  699. this.$message.error(`入库详情第${lineNo}行请填写商品数量`)
  700. return false
  701. }
  702. if (this.isEmpty(row.spec)) {
  703. this.$message.error(`入库详情第${lineNo}行请填写包装规格`)
  704. return false
  705. }
  706. }
  707. for (let i = 0; i < rows.length; i++) {
  708. for (let j = i + 1; j < rows.length; j++) {
  709. if (rows[i].wareHouseTypeId === rows[j].wareHouseTypeId &&
  710. rows[i].tradeName === rows[j].tradeName &&
  711. rows[i].supplierId === rows[j].supplierId) {
  712. this.$message.warning('入库详情中,同种入库类型、同一供应商的商品名称只能输入一条')
  713. return false
  714. }
  715. }
  716. }
  717. return true
  718. },
  719. confirmShelfLifeRows() {
  720. const rows = this.inputForm.wareHouse || []
  721. const index = rows.findIndex(row => {
  722. return (this.isEmpty(row.produceDate) && this.isNotEmpty(row.shelfLife)) ||
  723. (this.isNotEmpty(row.produceDate) && this.isEmpty(row.shelfLife))
  724. })
  725. if (index === -1) {
  726. return Promise.resolve(true)
  727. }
  728. const row = rows[index]
  729. return new Promise(resolve => {
  730. uni.showModal({
  731. title: '提示',
  732. content: `入库详情第${index + 1}行的${row.tradeName || '商品'}的生产日期或保质期未填写,是否继续提交?`,
  733. success: res => {
  734. resolve(!!res.confirm)
  735. },
  736. fail: () => {
  737. resolve(false)
  738. }
  739. })
  740. })
  741. },
  742. async submitWithStatus(status, functionFlag, callback) {
  743. this.fillUserInfo()
  744. await this.$refs.inputForm.validate()
  745. if (!this.validateBaseForm()) {
  746. return
  747. }
  748. if (!this.validateWareHouseRows()) {
  749. return
  750. }
  751. const confirmed = await this.confirmShelfLifeRows()
  752. if (!confirmed) {
  753. return
  754. }
  755. this.loading = true
  756. try {
  757. this.calculateDetailTotalPrice()
  758. this.calculateWareHouseTotals()
  759. const submitData = this.toSubmitData()
  760. submitData.status = status
  761. submitData.functionFlag = functionFlag
  762. this.inputForm.status = status
  763. this.inputForm.functionFlag = functionFlag
  764. const data = await this.wareHouseService.save(submitData)
  765. this.inputForm.id = data.businessId
  766. if (callback) {
  767. callback(data.businessTable, data.businessId, submitData)
  768. }
  769. return data
  770. } finally {
  771. this.loading = false
  772. }
  773. },
  774. saveForm(callback) {
  775. return this.submitWithStatus('1', '', callback)
  776. },
  777. async handlePageSubmit() {
  778. const data = await this.submitWithStatus('0', '')
  779. if (!data) {
  780. return
  781. }
  782. uni.showToast({
  783. title: '提交成功',
  784. icon: 'success'
  785. })
  786. setTimeout(() => {
  787. uni.navigateBack()
  788. }, 800)
  789. },
  790. startForm(callback) {
  791. return this.startFormTrue(callback)
  792. },
  793. startFormTrue(callback) {
  794. return this.submitWithStatus('2', 'start', callback)
  795. },
  796. reapplyForm(callback) {
  797. return this.startFormTrue(callback)
  798. },
  799. agreeForm(callback) {
  800. return this.submitWithStatus('5', 'agree', callback)
  801. }
  802. }
  803. }
  804. </script>
  805. <style scoped>
  806. .page-wrap {
  807. padding-bottom: 120rpx;
  808. }
  809. .section-wrap {
  810. margin-top: 24rpx;
  811. }
  812. .section-title {
  813. margin-bottom: 16rpx;
  814. padding-left: 8rpx;
  815. font-size: 30rpx;
  816. font-weight: 700;
  817. color: #1f2937;
  818. border-left: 6rpx solid #2979ff;
  819. }
  820. .detail-card {
  821. margin-bottom: 20rpx;
  822. padding: 24rpx;
  823. background: #fff;
  824. border-radius: 20rpx;
  825. box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
  826. }
  827. .warehouse-card {
  828. padding-bottom: 16rpx;
  829. }
  830. .detail-card-title {
  831. margin-bottom: 12rpx;
  832. font-size: 28rpx;
  833. font-weight: 600;
  834. color: #0f172a;
  835. }
  836. .detail-grid {
  837. display: grid;
  838. grid-template-columns: repeat(2, minmax(0, 1fr));
  839. gap: 16rpx;
  840. }
  841. .detail-item {
  842. display: flex;
  843. flex-direction: column;
  844. padding: 18rpx 20rpx;
  845. background: #f8fafc;
  846. border-radius: 16rpx;
  847. }
  848. .detail-label {
  849. font-size: 22rpx;
  850. color: #64748b;
  851. }
  852. .detail-value {
  853. margin-top: 8rpx;
  854. font-size: 26rpx;
  855. line-height: 1.5;
  856. color: #0f172a;
  857. word-break: break-all;
  858. }
  859. .shelf-life-wrap {
  860. display: flex;
  861. width: 100%;
  862. gap: 16rpx;
  863. }
  864. .shelf-life-input {
  865. flex: 1;
  866. min-width: 0;
  867. }
  868. .shelf-life-unit {
  869. flex: 0 0 128rpx;
  870. width: 128rpx;
  871. }
  872. .submit-bar {
  873. position: fixed;
  874. left: 0;
  875. right: 0;
  876. bottom: 0;
  877. z-index: 20;
  878. padding: 18rpx 28rpx calc(18rpx + env(safe-area-inset-bottom));
  879. background: #fff;
  880. box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.08);
  881. }
  882. .submit-button {
  883. width: 100%;
  884. }
  885. @media (max-width: 768px) {
  886. .detail-grid {
  887. grid-template-columns: repeat(1, minmax(0, 1fr));
  888. }
  889. }
  890. </style>