| | |
| | | createProductSales, |
| | | finishProductSales, |
| | | getProductSales, |
| | | previewProductSalesScan, |
| | | scanProductSales, |
| | | shippingProductSales, |
| | | stockProductSales, |
| | |
| | | |
| | | import { useFormSchema } from '../data'; |
| | | import LineList from './line-list.vue'; |
| | | import PickingBags from './picking-bags.vue'; |
| | | |
| | | const emit = defineEmits(['success']); |
| | | const formType = ref<FormType>('create'); |
| | | const formData = ref<MesWmProductSalesApi.ProductSales>(); |
| | | const scanContent = ref(''); |
| | | const scanQuantity = ref<number>(); |
| | | const scanType = ref<'BAG' | 'PALLET' | 'BATCH'>('BAG'); |
| | | const scanPreview = ref<MesWmProductSalesApi.ProductSalesScanResponse>(); |
| | | const selectedBagIds = ref<number[]>([]); |
| | | const scanLoading = ref(false); |
| | | const scanRefreshKey = ref(0); |
| | | const originalSnapshot = ref(''); // 表单原始数据快照,用于提交时跳过未变更的保存请求 |
| | |
| | | } |
| | | } |
| | | |
| | | /** 扫码拣货:扫码枪通常以回车结束输入 */ |
| | | /** 先识别对象并选择袋码,回车不会直接写入拣货。 */ |
| | | async function handleScan() { |
| | | if (!formData.value?.id || !scanContent.value.trim() || scanLoading.value) { |
| | | return; |
| | | } |
| | | scanLoading.value = true; |
| | | try { |
| | | scanPreview.value = undefined; |
| | | selectedBagIds.value = []; |
| | | const result = await previewProductSalesScan(scanContent.value.trim(), scanType.value, formData.value.id); |
| | | scanPreview.value = result; |
| | | if (result.scanType === 'BAG') selectedBagIds.value = result.bags?.filter(b => b.selectable).map(b => b.id) || []; |
| | | if (!result.scanQuantity) message.warning('当前对象没有可拣袋码,请核对质检和库存状态'); |
| | | } finally { scanLoading.value = false; } |
| | | } |
| | | |
| | | async function confirmScan() { |
| | | if (!formData.value?.id || !scanPreview.value || !selectedBagIds.value.length || scanLoading.value) return; |
| | | const source = scanPreview.value; |
| | | const bagIds = [...selectedBagIds.value]; |
| | | scanLoading.value = true; |
| | | try { await confirm(`确认将所选 ${bagIds.length} 袋加入拣货并关联追溯袋码?`); } |
| | | catch { scanLoading.value = false; return; } |
| | | try { |
| | | const result = await scanProductSales({ |
| | | quantity: scanQuantity.value, |
| | | quantity: bagIds.length, |
| | | salesId: formData.value.id, |
| | | scanContent: scanContent.value.trim(), |
| | | scanContent: source.scanContent!, |
| | | scanType: source.scanType as 'BAG' | 'PALLET' | 'BATCH', |
| | | bagIds, |
| | | }); |
| | | message[result.alreadyScanned ? 'warning' : 'success']( |
| | | result.alreadyScanned |
| | | ? `该码已扫描,明细编号:${result.detailId ?? '-'}` |
| | | : `${result.message || '扫码成功'}${result.scanType ? `(${result.scanType === 'PALLET' ? '托盘码' : '袋码'})` : ''}`, |
| | | : `已加入拣货 ${result.outboundQuantity} 袋,逐袋关联追溯码`, |
| | | ); |
| | | scanContent.value = ''; |
| | | scanQuantity.value = undefined; |
| | | scanPreview.value = undefined; |
| | | selectedBagIds.value = []; |
| | | scanRefreshKey.value += 1; |
| | | } finally { |
| | | scanLoading.value = false; |
| | |
| | | } |
| | | }, |
| | | async onOpenChange(isOpen: boolean) { |
| | | scanPreview.value = undefined; |
| | | selectedBagIds.value = []; |
| | | scanContent.value = ''; |
| | | if (!isOpen) { |
| | | formData.value = undefined; |
| | | originalSnapshot.value = ''; |
| | |
| | | <Divider>物料信息</Divider> |
| | | <div class="mx-4"> |
| | | <div v-if="isStock" class="mb-3 flex items-center gap-2 px-4"> |
| | | <label for="sales-picking-type" class="sr-only">拣货扫码类型</label> |
| | | <a-select id="sales-picking-type" v-model:value="scanType" :disabled="scanLoading" style="width:140px" @change="scanPreview = undefined; selectedBagIds = []"> |
| | | <a-select-option value="BAG">袋码</a-select-option><a-select-option value="PALLET">托盘码</a-select-option><a-select-option value="BATCH">生产批次码</a-select-option> |
| | | </a-select> |
| | | <a-input |
| | | v-model:value="scanContent" |
| | | :disabled="scanLoading" |
| | | autofocus |
| | | class="flex-1" |
| | | placeholder="请扫描袋码或托盘码,回车提交" |
| | | placeholder="扫描所选类型码,回车识别" |
| | | @press-enter="handleScan" |
| | | /> |
| | | <a-input-number |
| | | v-model:value="scanQuantity" |
| | | :disabled="scanLoading" |
| | | :min="0" |
| | | placeholder="数量(可选)" |
| | | style="width: 140px" |
| | | /> |
| | | <Button :loading="scanLoading" type="default" @click="handleScan"> |
| | | 扫码 |
| | | 识别码值 |
| | | </Button> |
| | | </div> |
| | | <section v-if="isStock && scanPreview" class="mb-4 rounded-lg border border-border p-4"> |
| | | <h3 class="font-semibold">核对拣货对象</h3><p>批次 {{scanPreview.batchCode}} · 可拣 {{scanPreview.scanQuantity}} 袋</p> |
| | | <PickingBags v-if="scanPreview.scanType !== 'BAG'" v-model="selectedBagIds" :bags="scanPreview.bags || []" :disabled="scanLoading" /> |
| | | <p v-else>袋码 {{scanPreview.bags?.[0]?.code}} · 本次 {{selectedBagIds.length}} 袋</p> |
| | | <div class="mt-3 flex gap-2"><Button type="primary" :loading="scanLoading" :disabled="!selectedBagIds.length" @click="confirmScan">确认加入拣货</Button><Button :disabled="scanLoading" @click="scanPreview = undefined">取消本次扫码</Button></div> |
| | | </section> |
| | | <LineList |
| | | :form-type="formType" |
| | | :notice-id="formData.noticeId" |
| | |
| | | > |
| | | <Button type="primary">提交</Button> |
| | | </Popconfirm> |
| | | <Button v-if="isStock" type="primary" @click="handleStock"> |
| | | <Button v-if="isStock" type="primary" :disabled="scanLoading || !!scanPreview" @click="handleStock"> |
| | | 执行拣货 |
| | | </Button> |
| | | <Popconfirm |