WareHouseAddForm.vue 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  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 + '].tradeNumber'" :required="true" labelWidth="68px">
  101. <u--input v-model="inputForm.wareHouse[index].tradeNumber" placeholder="请输入商品数量"
  102. :disabled="nodeFlag" @blur="onRowInputBlur(index, 'tradeNumber')"></u--input>
  103. </u-form-item>
  104. <u-form-item label="品牌" :prop="'wareHouse[' + index + '].brand'" labelWidth="44px">
  105. <u--input v-model="inputForm.wareHouse[index].brand" disabled></u--input>
  106. </u-form-item>
  107. <u-form-item label="规格" :prop="'wareHouse[' + index + '].specification'" labelWidth="44px">
  108. <u--input v-model="inputForm.wareHouse[index].specification" disabled></u--input>
  109. </u-form-item>
  110. <u-form-item label="单位" :prop="'wareHouse[' + index + '].company'" labelWidth="44px">
  111. <u--input v-model="inputForm.wareHouse[index].company" placeholder="" disabled
  112. @blur="calculateWareHouseTotals"></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'" labelWidth="44px">
  147. <u--input v-model="inputForm.wareHouse[index].remarks" placeholder="请输入备注" :disabled="nodeFlag">
  148. </u--input>
  149. </u-form-item>
  150. <u-form-item label="附件上传" v-if="false">
  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="附件" v-if="false">
  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. const goodsId = item.id || ''
  502. const exists = this.inputForm.wareHouse.some((detail, detailIndex) => {
  503. if (String(detailIndex) === String(index)) {
  504. return false
  505. }
  506. if (goodsId) {
  507. return String(detail.wareHouseTypeId || '') === String(goodsId)
  508. }
  509. return String(detail.tradeName || '') === String(tradeName || '')
  510. })
  511. if (exists) {
  512. uni.showToast({
  513. title: '同种商品只能选择一次',
  514. icon: 'none'
  515. })
  516. return
  517. }
  518. this.patchWareHouseRow(index, {
  519. wareHouseType: tradeName,
  520. wareHouseTypeId: item.id || '',
  521. tradeName,
  522. goodsName: tradeName,
  523. brand: item.brand || '',
  524. specification: item.specification || '',
  525. company: item.company || '',
  526. spec: '1'
  527. })
  528. },
  529. createWareHouseRow(row = {}) {
  530. const office = (this.userInfo && (this.userInfo.officeDTO || this.userInfo.office)) || {}
  531. return {
  532. id: '',
  533. wareHouseManId: this.userInfo && (this.userInfo.id || this.userInfo.userId),
  534. wareHouseMan: this.userInfo && (this.userInfo.name || this.userInfo.realname),
  535. wareHouseManOffice: office.name || (this.userInfo && this.userInfo.officeName),
  536. deptId: office.id || (this.userInfo && this.userInfo.officeId),
  537. wareHouseType: '',
  538. wareHouseTypeId: '',
  539. tradeName: '',
  540. tradeNumber: '',
  541. company: '',
  542. brand: '',
  543. specification: '',
  544. spec: '1',
  545. tradePrice: '',
  546. priceSum: '',
  547. produceDate: '',
  548. shelfLife: '',
  549. shelfLifeUnit: '月',
  550. actualPrice: '',
  551. remarks: '',
  552. fileInfoLost: [],
  553. ...row
  554. }
  555. },
  556. addWareHouseRow(row) {
  557. this.inputForm.wareHouse.push(this.createWareHouseRow(row))
  558. this.calculateWareHouseTotals()
  559. },
  560. wareHouseAdd(index) {
  561. const detail = (this.inputForm.detailInfos || [])[index]
  562. if (!detail) {
  563. return
  564. }
  565. const row = {
  566. wareHouseTypeId: detail.procurementTypeId || '',
  567. wareHouseType: detail.procurementType || '',
  568. tradeName: detail.tradeName || '',
  569. tradeNumber: detail.tradeNumber || '',
  570. tradePrice: detail.tradePrice || '',
  571. company: detail.company || '',
  572. brand: detail.brand || '',
  573. specification: detail.specification || '',
  574. priceSum: this.computePriceSum(detail.tradePrice, detail.tradeNumber),
  575. spec: '1',
  576. produceDate: '',
  577. shelfLife: '',
  578. shelfLifeUnit: '月'
  579. }
  580. this.addWareHouseRow(row)
  581. },
  582. patchWareHouseRow(index, patch) {
  583. const row = (this.inputForm.wareHouse || [])[index] || {}
  584. const nextRow = {
  585. ...row,
  586. ...patch
  587. }
  588. if (Object.prototype.hasOwnProperty.call(patch, 'tradePrice') || Object.prototype.hasOwnProperty.call(patch, 'tradeNumber')) {
  589. nextRow.priceSum = this.computePriceSum(nextRow.tradePrice, nextRow.tradeNumber)
  590. }
  591. this.$set(this.inputForm.wareHouse, index, nextRow)
  592. this.calculateWareHouseTotals()
  593. },
  594. removeWareHouseRow(index) {
  595. this.inputForm.wareHouse.splice(index, 1)
  596. this.calculateWareHouseTotals()
  597. },
  598. onRowInputBlur(index, field) {
  599. const row = (this.inputForm.wareHouse || [])[index]
  600. if (!row) {
  601. return
  602. }
  603. let value = row[field]
  604. if (['tradeNumber', 'tradePrice', 'actualPrice'].includes(field)) {
  605. value = this.formatNumberInput(value)
  606. }
  607. if (field === 'spec') {
  608. value = this.formatIntegerInput(value)
  609. if (this.isNotEmpty(value) && parseInt(value, 10) <= 0) {
  610. value = ''
  611. this.$message.warning('请输入除0以外的正整数')
  612. }
  613. }
  614. this.patchWareHouseRow(index, {
  615. [field]: value
  616. })
  617. },
  618. formatNumberInput(inputValue, decimalLimit = 2) {
  619. if (this.isEmpty(inputValue)) {
  620. return ''
  621. }
  622. const valueText = String(inputValue)
  623. if (!/^\d*\.?\d*$/.test(valueText)) {
  624. return ''
  625. }
  626. let value = valueText.replace(/[^\d.]/g, '')
  627. const dotIndex = value.indexOf('.')
  628. if (dotIndex !== -1) {
  629. const substr = value.substr(dotIndex + 1)
  630. if (substr.indexOf('.') !== -1) {
  631. value = value.substr(0, dotIndex + 1) + substr.replace(/\./g, '')
  632. }
  633. const integerPart = value.substring(0, dotIndex)
  634. const decimalPart = value.substring(dotIndex + 1, dotIndex + 1 + decimalLimit)
  635. value = integerPart + '.' + decimalPart
  636. }
  637. return value
  638. },
  639. formatIntegerInput(inputValue) {
  640. if (this.isEmpty(inputValue)) {
  641. return ''
  642. }
  643. return String(inputValue).replace(/[^\d]/g, '')
  644. },
  645. handleUploadSuccess(file, fileList, index, type) {
  646. if (type === 'detail') {
  647. this.inputForm.wareHouse[index].fileInfoLost = fileList
  648. } else {
  649. this.inputForm.files = fileList
  650. }
  651. },
  652. handleRemove(file, fileList, lineIndex, fileIndex, type) {
  653. if (type === 'detail') {
  654. this.inputForm.wareHouse[lineIndex].fileInfoLost.splice(fileIndex, 1)
  655. } else {
  656. this.inputForm.files.splice(fileIndex, 1)
  657. }
  658. },
  659. formatDate(date) {
  660. if (this.isEmpty(date)) {
  661. return ''
  662. }
  663. const dateNew = new Date(date)
  664. if (Number.isNaN(dateNew.getTime())) {
  665. return ''
  666. }
  667. const year = dateNew.getFullYear()
  668. const month = `${dateNew.getMonth() + 1}`.padStart(2, '0')
  669. const day = `${dateNew.getDate()}`.padStart(2, '0')
  670. return `${year}-${month}-${day}`
  671. },
  672. toSubmitData() {
  673. const data = JSON.parse(JSON.stringify(this.inputForm))
  674. data.wareHouseDate = this.formatDate(this.inputForm.wareHouseDate)
  675. data.tradeTotalPrice = this.normalizeOptionalNumber(data.tradeTotalPrice)
  676. data.wareHouseTotalPrice = this.normalizeOptionalNumber(data.wareHouseTotalPrice)
  677. data.wareHouseActualPrice = this.normalizeOptionalNumber(data.wareHouseActualPrice)
  678. data.wareHouse = (this.inputForm.wareHouse || []).map(item => ({
  679. ...JSON.parse(JSON.stringify(item)),
  680. produceDate: this.formatDate(item.produceDate),
  681. priceSum: this.normalizeOptionalNumber(this.computePriceSum(item.tradePrice, item.tradeNumber)),
  682. tradeTotalPrice: this.normalizeOptionalNumber(item.tradeTotalPrice),
  683. actualPrice: this.normalizeOptionalNumber(item.actualPrice)
  684. }))
  685. return data
  686. },
  687. normalizeOptionalNumber(value) {
  688. if (this.isEmpty(value)) {
  689. return null
  690. }
  691. return value
  692. },
  693. validateBaseForm() {
  694. if (this.isEmpty(this.inputForm.wareHouseDate)) {
  695. this.$message.error('入库时间不能为空')
  696. return false
  697. }
  698. return true
  699. },
  700. validateWareHouseRows() {
  701. if (this.isEmpty(this.inputForm.wareHouse)) {
  702. this.$message.error('至少填写一条入库详情信息')
  703. return false
  704. }
  705. const rows = this.inputForm.wareHouse
  706. for (let i = 0; i < rows.length; i++) {
  707. const row = rows[i]
  708. const lineNo = i + 1
  709. if (this.isEmpty(row.wareHouseMan)) {
  710. this.$message.error(`入库详情第${lineNo}行请选择入库人`)
  711. return false
  712. }
  713. if (this.isEmpty(row.wareHouseTypeId) || this.isEmpty(row.tradeName)) {
  714. this.$message.error(`入库详情第${lineNo}行请选择物品名称`)
  715. return false
  716. }
  717. if (this.isEmpty(row.tradeNumber)) {
  718. this.$message.error(`入库详情第${lineNo}行请填写商品数量`)
  719. return false
  720. }
  721. if (this.isEmpty(row.spec)) {
  722. this.$message.error(`入库详情第${lineNo}行请填写包装规格`)
  723. return false
  724. }
  725. }
  726. for (let i = 0; i < rows.length; i++) {
  727. for (let j = i + 1; j < rows.length; j++) {
  728. if (rows[i].wareHouseTypeId === rows[j].wareHouseTypeId &&
  729. rows[i].tradeName === rows[j].tradeName) {
  730. this.$message.warning('入库详情中,同一物品只能输入一条')
  731. return false
  732. }
  733. }
  734. }
  735. return true
  736. },
  737. confirmShelfLifeRows() {
  738. const rows = this.inputForm.wareHouse || []
  739. const index = rows.findIndex(row => {
  740. return (this.isEmpty(row.produceDate) && this.isNotEmpty(row.shelfLife)) ||
  741. (this.isNotEmpty(row.produceDate) && this.isEmpty(row.shelfLife))
  742. })
  743. if (index === -1) {
  744. return Promise.resolve(true)
  745. }
  746. const row = rows[index]
  747. return new Promise(resolve => {
  748. uni.showModal({
  749. title: '提示',
  750. content: `入库详情第${index + 1}行的${row.tradeName || '商品'}的生产日期或保质期未填写,是否继续提交?`,
  751. success: res => {
  752. resolve(!!res.confirm)
  753. },
  754. fail: () => {
  755. resolve(false)
  756. }
  757. })
  758. })
  759. },
  760. async submitWithStatus(status, functionFlag, callback) {
  761. this.fillUserInfo()
  762. await this.$refs.inputForm.validate()
  763. if (!this.validateBaseForm()) {
  764. return
  765. }
  766. if (!this.validateWareHouseRows()) {
  767. return
  768. }
  769. const confirmed = await this.confirmShelfLifeRows()
  770. if (!confirmed) {
  771. return
  772. }
  773. this.loading = true
  774. try {
  775. this.calculateDetailTotalPrice()
  776. this.calculateWareHouseTotals()
  777. const submitData = this.toSubmitData()
  778. submitData.status = status
  779. submitData.functionFlag = functionFlag
  780. this.inputForm.status = status
  781. this.inputForm.functionFlag = functionFlag
  782. const data = await this.wareHouseService.save(submitData)
  783. this.inputForm.id = data.businessId
  784. if (callback) {
  785. callback(data.businessTable, data.businessId, submitData)
  786. }
  787. return data
  788. } finally {
  789. this.loading = false
  790. }
  791. },
  792. saveForm(callback) {
  793. return this.submitWithStatus('1', '', callback)
  794. },
  795. async handlePageSubmit() {
  796. const data = await this.submitWithStatus('0', '')
  797. if (!data) {
  798. return
  799. }
  800. uni.showToast({
  801. title: '提交成功',
  802. icon: 'success'
  803. })
  804. setTimeout(() => {
  805. uni.navigateBack()
  806. }, 800)
  807. },
  808. startForm(callback) {
  809. return this.startFormTrue(callback)
  810. },
  811. startFormTrue(callback) {
  812. return this.submitWithStatus('2', 'start', callback)
  813. },
  814. reapplyForm(callback) {
  815. return this.startFormTrue(callback)
  816. },
  817. agreeForm(callback) {
  818. return this.submitWithStatus('5', 'agree', callback)
  819. }
  820. }
  821. }
  822. </script>
  823. <style scoped>
  824. .page-wrap {
  825. padding-bottom: 120rpx;
  826. }
  827. .section-wrap {
  828. margin-top: 24rpx;
  829. }
  830. .section-title {
  831. margin-bottom: 16rpx;
  832. padding-left: 8rpx;
  833. font-size: 30rpx;
  834. font-weight: 700;
  835. color: #1f2937;
  836. border-left: 6rpx solid #2979ff;
  837. }
  838. .detail-card {
  839. margin-bottom: 20rpx;
  840. padding: 24rpx;
  841. background: #fff;
  842. border-radius: 20rpx;
  843. box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.05);
  844. }
  845. .warehouse-card {
  846. padding-bottom: 16rpx;
  847. }
  848. .detail-card-title {
  849. margin-bottom: 12rpx;
  850. font-size: 28rpx;
  851. font-weight: 600;
  852. color: #0f172a;
  853. }
  854. .detail-grid {
  855. display: grid;
  856. grid-template-columns: repeat(2, minmax(0, 1fr));
  857. gap: 16rpx;
  858. }
  859. .detail-item {
  860. display: flex;
  861. flex-direction: column;
  862. padding: 18rpx 20rpx;
  863. background: #f8fafc;
  864. border-radius: 16rpx;
  865. }
  866. .detail-label {
  867. font-size: 22rpx;
  868. color: #64748b;
  869. }
  870. .detail-value {
  871. margin-top: 8rpx;
  872. font-size: 26rpx;
  873. line-height: 1.5;
  874. color: #0f172a;
  875. word-break: break-all;
  876. }
  877. .shelf-life-wrap {
  878. display: flex;
  879. width: 100%;
  880. gap: 16rpx;
  881. }
  882. .shelf-life-input {
  883. flex: 1;
  884. min-width: 0;
  885. }
  886. .shelf-life-unit {
  887. flex: 0 0 128rpx;
  888. width: 128rpx;
  889. }
  890. .submit-bar {
  891. position: fixed;
  892. left: 0;
  893. right: 0;
  894. bottom: 0;
  895. z-index: 20;
  896. padding: 18rpx 28rpx calc(18rpx + env(safe-area-inset-bottom));
  897. background: #fff;
  898. box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.08);
  899. }
  900. .submit-button {
  901. width: 100%;
  902. }
  903. @media (max-width: 768px) {
  904. .detail-grid {
  905. grid-template-columns: repeat(1, minmax(0, 1fr));
  906. }
  907. }
  908. </style>