WareHouseAddForm.vue 39 KB

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