WareHouseAddForm.vue 38 KB

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