/** * 加工出仓单表单 mixin(新增/编辑共用) * * 1. 先填表头(加工单位 / 仓库 / 备注)提交 add,拿到 id 后可扫码 * 2. 先扫仓位(getWarehouseBin:传 qr_code + warehouse_id),校验后扫布匹二维码 * 3. 扫码走 updateFpmProcessOutOrder(arrange_type: 1 录入 / 3 删除,带 warehouse_bin_id) * 4. 手机扫码成功展示详情后自动再次调起扫码,形成连续扫 */ import util from '@/common/util'; import scanMixin from '@/common/scanMixin.js'; import { initTts, speak } from '@/common/tts.js'; import { AUDIT_STATUS_MAP, ARRANGE_TYPE, BUSINESS_UNIT_TYPE_PROCESS, WAREHOUSE_TYPE_FINISHED, OUT_ORDER_TYPE_PROCESS, normalizeOrder, mapToSelectOptions, canScanAtStatus, isLockedOrderStatus, getFormStatusActionButtons, orderToFormFields, confirmOrderStatusAction, DRAFT_AUDIT_STATUS, DRAFT_AUDIT_STATUS_NAME, getEmptyScanDetail, buildScanDetailFromScanResponse, buildScanUpdateParams, pickDefaultWarehouse, formatDisplayTime, } from '@/common/storeGoodsProcessOut'; export default { mixins: [scanMixin], data() { return { pageMode: 'add', orderId: 0, form: this.getEmptyForm(), headerSaved: false, selectShow: false, selectList: [], selectType: '', canScan: false, QRBarCode: '', BarCodeDelStatus: false, BillDataMessage: '', scanDetail: getEmptyScanDetail(), auditStatus: 0, processUnitOptions: [], warehouseOptions: [], dropdownLoading: false, /** 仓位 */ warehouseBinId: 0, warehouseBinCode: '', warehouseBinName: '', /** 手机连续扫码开关:成功后自动再调起;取消/失败则关闭 */ phoneContinuousScan: false, /** 手机扫当前目标:bin | qr */ phoneScanTarget: 'bin', }; }, computed: { isReadonly() { return this.pageMode === 'view'; }, isEditable() { return this.pageMode === 'add' || this.pageMode === 'edit'; }, headerEditable() { return this.pageMode === 'add' && !this.headerSaved; }, statusActionButtons() { return getFormStatusActionButtons(this.headerSaved, this.orderId, this.auditStatus); }, displayAuditStatus() { if (this.pageMode === 'add' && !this.headerSaved) return DRAFT_AUDIT_STATUS; return this.auditStatus; }, displayAuditStatusName() { if (this.pageMode === 'add' && !this.headerSaved) return DRAFT_AUDIT_STATUS_NAME; return AUDIT_STATUS_MAP[this.auditStatus] || ''; }, /** 录入需先选仓位;删除模式仅需单据可扫 */ canScanQr() { if (!this.canScan) return false; if (this.BarCodeDelStatus) return true; return !!this.warehouseBinId; }, /** 扫码栏在可扫状态下始终展示(含删除勾选) */ showQrScanBar() { return this.canScan; }, qrDisabledTip() { if (!this.canScan) return '请先提交保存后再扫码'; if (!this.warehouseBinId && !this.BarCodeDelStatus) return '请先扫描仓位后再扫布匹二维码'; return '当前状态不可扫码'; }, }, onShow() { this.isPageActive = true; initTts(); if (this.canScan && this.isEditable) { this.registerOrderScanBroadcast(); } }, onHide() { this.isPageActive = false; this.phoneContinuousScan = false; this.unregisterScanBroadcast(); }, onUnload() { this.isPageActive = false; this.phoneContinuousScan = false; this.unregisterScanBroadcast(); }, methods: { clearWarehouseBin() { this.warehouseBinId = 0; this.warehouseBinCode = ''; this.warehouseBinName = ''; this.phoneContinuousScan = false; }, handleBinPhoneScan() { if (!this.canScan || !this.orderId) { this.showError('请先提交保存后再扫码'); return; } this.phoneContinuousScan = false; this.phoneScanTarget = 'bin'; this.openPhoneScan({ callback: (scanResult) => { this.warehouseBinCode = String(scanResult || '').trim().replace(/[\r\n]/g, ''); this.$nextTick(() => this.resolveWarehouseBin()); }, fail: () => {}, }); }, handlePhoneScan() { if (!this.canScan || !this.orderId) { this.showError('请先提交保存后再扫码'); return; } // 删除模式可直接扫布匹码;录入模式无仓位时先扫仓位 if (!this.BarCodeDelStatus && !this.warehouseBinId) { this.handleBinPhoneScan(); return; } this.phoneScanTarget = 'qr'; this.phoneContinuousScan = true; this.openContinuousPhoneScan(); }, openContinuousPhoneScan() { if (!this.phoneContinuousScan || !this.canScanQr || !this.isPageActive) { this.phoneContinuousScan = false; return; } this.openPhoneScan({ callback: (scanResult) => { this.QRBarCode = String(scanResult || '').trim().replace(/[\r\n]/g, ''); this.$nextTick(() => this.handleScan({ fromPhone: true })); }, fail: () => { this.phoneContinuousScan = false; }, }); }, scheduleNextPhoneScan() { if (!this.phoneContinuousScan || !this.canScanQr || !this.isPageActive) { this.phoneContinuousScan = false; return; } setTimeout(() => { this.openContinuousPhoneScan(); }, 400); }, registerOrderScanBroadcast() { this.registerScanBroadcast((scanResult) => { const code = String(scanResult || '').trim().replace(/[\r\n]/g, ''); if (!code) return; // 删除模式:直接按布匹码删除;未扫仓位时枪扫优先解析为仓位 if (!this.BarCodeDelStatus && !this.warehouseBinId) { this.warehouseBinCode = code; this.$nextTick(() => this.resolveWarehouseBin()); return; } this.QRBarCode = code; this.$nextTick(() => this.handleScan()); }); }, resolveWarehouseBin() { const code = (this.warehouseBinCode || '').trim(); if (!this.canScan || !this.orderId) { this.showError('请先提交保存后再扫码'); return; } if (!code) return; if (!this.form.warehouse_id) { this.showError('单据缺少仓库信息'); return; } uni.showLoading({ title: '识别仓位...' }); return this.$u.api.warehouseBin.getByQrCode({ qr_code: code, warehouse_id: Number(this.form.warehouse_id), }) .then((data) => { uni.hideLoading(); if (!data || !data.id) { throw new Error('未识别到仓位'); } // 返回 physical_warehouse_id,与单据仓库校验 if ( data.physical_warehouse_id && String(data.physical_warehouse_id) !== String(this.form.warehouse_id) ) { this.warehouseBinId = 0; this.warehouseBinName = ''; throw new Error('该仓位不属于当前仓库'); } this.warehouseBinId = data.id; this.warehouseBinCode = data.code || data.qr_code || code; this.warehouseBinName = data.name || ''; this.BillDataMessage = ''; speak('仓位成功'); uni.showToast({ title: '仓位识别成功', icon: 'success' }); }) .catch((e) => { uni.hideLoading(); this.warehouseBinId = 0; this.warehouseBinName = ''; this.showError(e.message || '仓位识别失败'); }); }, syncCanScanFromStatus(status) { const nextCanScan = this.isEditable && canScanAtStatus(status); if (this.canScan && !nextCanScan) { this.phoneContinuousScan = false; this.unregisterScanBroadcast(); } this.canScan = nextCanScan; }, loadBaseDropdowns() { if (this.dropdownLoading) return Promise.resolve(); this.dropdownLoading = true; return Promise.all([ this.ensureProcessUnitOptions().catch((e) => { console.error('加载加工单位失败', e); return []; }), this.ensureWarehouseOptions().catch((e) => { console.error('加载仓库失败', e); return []; }), ]).finally(() => { this.dropdownLoading = false; }); }, ensureProcessUnitOptions() { if (this.processUnitOptions.length) return Promise.resolve(this.processUnitOptions); return this.$u.api.businessUnit.list({ unit_type_id: BUSINESS_UNIT_TYPE_PROCESS }) .then((res) => { this.processUnitOptions = mapToSelectOptions(res); return this.processUnitOptions; }); }, ensureWarehouseOptions() { if (this.warehouseOptions.length) return Promise.resolve(this.warehouseOptions); return this.$u.api.physicalWarehouse.getDropdownList({ warehouse_type_id: WAREHOUSE_TYPE_FINISHED }) .then((res) => { this.warehouseOptions = mapToSelectOptions(res); if (this.headerEditable && !this.form.warehouse_id) { this.applyDefaultWarehouse(); } return this.warehouseOptions; }); }, applyDefaultWarehouse() { const app = getApp(); const picked = pickDefaultWarehouse( this.warehouseOptions, app && app.globalData ? app.globalData.StoreNameID : '', app && app.globalData ? app.globalData.StoreName : '', ); if (!picked) return; this.form.warehouse_id = picked.value; this.form.warehouse_name = picked.label; }, getEmptyForm() { return { order_no: '', process_unit_id: '', process_name: '', warehouse_id: '', warehouse_name: '', remark: '', item_data: [], }; }, loadOrder(id, options = {}) { const { rejectIfLocked = false } = options; return this.$u.api.fpmProcessOutOrder.detail({ id }) .then((res) => { const order = normalizeOrder(res); if (!order) { this.showError('单据不存在'); return null; } if (rejectIfLocked && isLockedOrderStatus(order.audit_status)) { this.showError('当前状态不可编辑'); setTimeout(() => uni.navigateBack(), 1500); return null; } this.orderId = order.id; this.auditStatus = order.audit_status; this.headerSaved = true; this.form = orderToFormFields(order); this.clearWarehouseBin(); this.syncCanScanFromStatus(order.audit_status); if (this.canScan) this.registerOrderScanBroadcast(); return order; }) .catch((e) => { this.showError(e.message || '加载单据失败'); return null; }); }, pickerSelectFun(type) { if (!this.headerEditable) return; this.selectType = type; const openPicker = (list) => { if (!list.length) { this.showError('暂无可选数据'); return; } this.selectList = list; this.selectShow = true; }; if (type === '加工单位') { this.ensureProcessUnitOptions() .then(openPicker) .catch((e) => this.showError(e.message || '加载加工单位失败')); } else if (type === '仓库名称') { this.ensureWarehouseOptions() .then(openPicker) .catch((e) => this.showError(e.message || '加载仓库失败')); } }, selectConfirmFun(e) { const item = e[0]; if (this.selectType === '加工单位') { this.form.process_unit_id = item.value; this.form.process_name = item.label; } else if (this.selectType === '仓库名称') { this.form.warehouse_id = item.value; this.form.warehouse_name = item.label; } }, validateForm() { if (!this.form.process_unit_id) return '请选择加工单位'; if (!this.form.warehouse_id) return '请选择仓库名称'; const app = getApp(); const saleSystemId = app && app.globalData ? app.globalData.PlanDepartmentID : ''; if (!saleSystemId) return '缺少营销体系,请重新登录'; return ''; }, buildAddParam() { const app = getApp(); return { // OpenAPI: product.AddFpmProcessOutOrderParam out_order_type: OUT_ORDER_TYPE_PROCESS, // WarehouseGoodOutTypeProcess = 5 process_unit_id: Number(this.form.process_unit_id), warehouse_id: Number(this.form.warehouse_id), sale_system_id: Number(app.globalData.PlanDepartmentID), warehouse_out_time: formatDisplayTime(new Date(), 'YYYY-MM-DD'), remark: this.form.remark || '', item_data: [], }; }, submitSave() { if (this.headerSaved) { this.showError('表头已保存,请直接扫码'); return; } const err = this.validateForm(); if (err) { this.showError(err); return; } uni.showLoading({ title: '保存中...' }); this.$u.api.fpmProcessOutOrder.add(this.buildAddParam()) .then((res) => { this.orderId = (res && res.id) || 0; if (!this.orderId) { throw new Error('保存失败,未返回单据ID'); } return this.$u.api.fpmProcessOutOrder.detail({ id: this.orderId }); }) .then((detail) => { uni.hideLoading(); const order = normalizeOrder(detail); this.applyOrder(order); this.headerSaved = true; this.clearWarehouseBin(); this.syncCanScanFromStatus(order.audit_status); this.showSuccess('保存成功,请先扫描仓位'); this.registerOrderScanBroadcast(); }) .catch((e) => { uni.hideLoading(); this.showError(e.message || '保存失败'); }); }, applyOrder(order) { if (!order) return; this.auditStatus = order.audit_status; Object.assign(this.form, orderToFormFields(order)); }, handleScan(options = {}) { const fromPhone = !!options.fromPhone; const code = (this.QRBarCode || '').trim(); if (!this.canScan || !this.orderId) { this.showError('请先提交保存后再扫码'); this.QRBarCode = ''; this.phoneContinuousScan = false; return; } const isDelete = this.BarCodeDelStatus; if (!isDelete && !this.warehouseBinId) { this.showError('请先扫描仓位'); this.QRBarCode = ''; this.phoneContinuousScan = false; return; } if (!code) { this.QRBarCode = ''; if (fromPhone) this.scheduleNextPhoneScan(); return; } const arrangeType = isDelete ? ARRANGE_TYPE.DEL : ARRANGE_TYPE.IN; this.doScanUpdate(code, arrangeType, { fromPhone }); }, BarCodeDelChange(e) { this.BarCodeDelStatus = (e.detail.value || []).length > 0; this.BillDataMessage = this.BarCodeDelStatus ? '删除模式:扫描细码即可删除' : ''; }, getFineCodeScanCode(fineCode) { if (!fineCode) return ''; return fineCode.qr_code || fineCode.scan_code || fineCode.digital_code || fineCode.bar_code || ''; }, onDeleteFineCode(fineCode) { if (!this.canScan || !this.orderId) { this.showError('当前状态不可删除'); return; } const scanCode = this.getFineCodeScanCode(fineCode); if (!scanCode) { this.showError('该细码缺少条码,无法删除'); return; } uni.showModal({ title: '提示', content: `确认删除细码 ${scanCode}?`, success: (res) => { if (!res.confirm) return; this.doScanUpdate(scanCode, ARRANGE_TYPE.DEL); }, }); }, doScanUpdate(scanCode, arrangeType, options = {}) { const { fromPhone = false } = options; const isDelete = arrangeType === ARRANGE_TYPE.DEL; uni.showLoading({ title: isDelete ? '删除中...' : '处理中...' }); return this.$u.api.fpmProcessOutOrder.scanUpdate(buildScanUpdateParams({ id: this.orderId, scanCode, arrangeType, // 删除不强制仓位;录入带当前仓位 warehouseBinId: isDelete ? 0 : this.warehouseBinId, })) .then((scanRes) => { this.scanDetail = buildScanDetailFromScanResponse(scanRes); this.BillDataMessage = isDelete ? '删除成功' : '扫描成功'; this.QRBarCode = ''; if (isDelete) { speak('已删除'); uni.showToast({ title: '删除成功', icon: 'success' }); } else { speak('成功'); } return this.$u.api.fpmProcessOutOrder.detail({ id: this.orderId }) .then((detail) => { const order = normalizeOrder(detail); if (order) { this.form.item_data = order.item_data; this.auditStatus = order.audit_status; } }) .catch(() => null); }) .then(() => { uni.hideLoading(); if (fromPhone) this.scheduleNextPhoneScan(); }) .catch((e) => { uni.hideLoading(); this.phoneContinuousScan = false; const message = e.message || (isDelete ? '删除失败' : '扫码失败'); this.showError(message); this.QRBarCode = ''; }); }, handleStatusAction(action) { confirmOrderStatusAction(this, { orderId: this.orderId, action, onSuccess: () => this.$u.api.fpmProcessOutOrder.detail({ id: this.orderId }) .then((detail) => { uni.hideLoading(); const order = normalizeOrder(detail); if (!order) return; this.applyOrder(order); this.syncCanScanFromStatus(order.audit_status); this.showSuccess('操作成功'); }), }); }, showError(message) { util.playErrorAudio(); uni.showModal({ title: '提示', content: message, showCancel: false }); }, showSuccess(message) { util.playSuccessAudio(); uni.showToast({ title: message, icon: 'success' }); }, }, };