WareHouseAddForm.vue 37 KB

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