ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue
@@ -27,13 +27,25 @@ style="float: right; height: 20px" @click="handleOpenPicking" >æ£é</el-link> <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" @click="handleOpenBatchPicking" >åæ¹æ£é</el-link> <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" @click="outbound" >ç´æ¥åºåº</el-link > <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" @click="outboundbatch" >åæ¹åºåº</el-link > <el-link type="primary" @@ -113,8 +125,9 @@ import SelectedStock from "./SelectedStock.vue"; import NoStockOut from "./NoStockOut.vue"; import { h,createVNode, render,reactive } from 'vue'; import { ElDialog , ElForm, ElFormItem, ElSelect,ElOption, ElButton, ElMessage } from 'element-plus'; import { ElDialog , ElForm, ElFormItem, ElSelect,ElOption, ElButton, ElInput, ElMessage } from 'element-plus'; import { th } from 'element-plus/es/locale'; export default { components: { VolBox, VolForm, StockSelect, SelectedStock,NoStockOut}, data() { @@ -344,6 +357,11 @@ query: { orderId: this.row.id ,orderNo:this.row.orderNo} }) }, handleOpenBatchPicking() { this.$router.push({ path: '/outbound/batchpicking', query: { orderId: this.row.id ,orderNo:this.row.orderNo}}) }, outbound() { if (this.selection.length === 0) { return this.$message.error("è¯·éæ©åæ®æç»"); @@ -481,6 +499,192 @@ vnode.appContext = this.$.appContext; render(vnode, mountNode); }, outboundbatch() { if (this.selection.length === 0) { return this.$message.error("è¯·éæ©åæ®æç»"); } if (this.selection.length>1) { return this.$message.error("åªè½éæ©ä¸æ¡åæ®æç»è¿è¡åæ¹åºåº"); } const platformOptions = [{label:'ç«å°2',value:'2-1'},{label:'ç«å°3',value:'3-1'}]; const mountNode = document.createElement('div'); document.body.appendChild(mountNode); // 2. è¡¨åæ°æ®ï¼é»è®¤éä¸ç«å°3ï¼æ°å¢å°æ°åæ®µï¼ const formData = reactive({ selectedPlatform: platformOptions[0].value, // é»è®¤ç»å®ãç«å°3ãçvalue outboundDecimal: '' // æ°å¢ï¼å°æ°è¾å ¥æ¡å段 }); // 3. 卿å建弹çªç»ä»¶ const vnode = createVNode(ElDialog, { title: 'åºåºæä½ - éæ©åºåºç«å°', width: '500px', modelValue: true, appendToBody: true, 'onUpdate:modelValue': (isVisible) => { if (!isVisible) { render(null, mountNode); document.body.removeChild(mountNode); } }, style: { padding: '20px 0', borderRadius: '8px' } }, { default: () => h(ElForm, { model: formData, rules: { selectedPlatform: [ { required: true, message: 'è¯·éæ©åºåºç«å°', trigger: 'change' } ], // æ°å¢ï¼å°æ°å段éªè¯è§åï¼å¿ å¡« + ææå°æ°æ ¼å¼ï¼ outboundDecimal: [ { required: true, message: '请è¾å ¥å°æ°æ°å¼', trigger: 'blur' }, { validator: (rule, value, callback) => { // éªè¯è§åï¼æ£æ°ãæ¯æå°æ°ç¹åæå¤2ä½ï¼å¯æ ¹æ®éæ±è°æ´å°æ°ä½æ°ï¼ const decimalReg = /^(([1-9]\d*)|0)(\.\d{1,2})?$/; if (value && !decimalReg.test(value)) { callback(new Error('请è¾å ¥ææçå°æ°ï¼æ£æ°ï¼æå¤2ä½å°æ°ï¼')); } else { callback(); } }, trigger: 'blur' } ] }, ref: 'outboundForm', labelWidth: '100px', style: { padding: '0 30px' } }, [ // åºåºç«å°éæ©é¡¹ï¼æ ¸å¿è¡¨åé¡¹ï¼ h(ElFormItem, { label: 'åºåºç«å°', prop: 'selectedPlatform', style: { marginBottom: '24px' } }, [ h(ElSelect, { placeholder: 'è¯·éæ©åºåºç«å°ï¼3-12ï¼', modelValue: formData.selectedPlatform, 'onUpdate:modelValue': (val) => { formData.selectedPlatform = val; }, style: { width: '100%', height: '40px', borderRadius: '4px', borderColor: '#dcdfe6' } }, platformOptions.map(platform => h(ElOption, { label: platform.label, value: platform.value }) )) ]), // æ°å¢ï¼å°æ°è¾å ¥æ¡è¡¨å项 h(ElFormItem, { label: 'åºåºæ°', // 坿 ¹æ®ä¸å¡éæ±ä¿®æ¹æ ç¾åï¼å¦âåºåºæ°éââééâçï¼ prop: 'outboundDecimal', style: { marginBottom: '24px' } }, [ h(ElInput, { type: 'number', // æ°åç±»åï¼åçæ¯æå°æ°è¾å ¥ placeholder: '请è¾å ¥å°æ°æ°å¼ï¼æå¤2ä½å°æ°ï¼', modelValue: formData.outboundDecimal, 'onUpdate:modelValue': (val) => { formData.outboundDecimal = val; }, style: { width: '100%', height: '40px', borderRadius: '4px', borderColor: '#dcdfe6' }, step: '0.01', // æ¥é¿0.01ï¼ç¹å»ä¸ä¸ç®å¤´æ¶æ0.01å¢å precision: 2, // éå¶æå¤è¾å ¥2ä½å°æ°ï¼Element Plus屿§ï¼ min: 0.01, // å¯éï¼éå¶æå°å¼ä¸º0.01ï¼é¿å è¾å ¥0æè´æ° }) ]), // åºé¨æé®åº h('div', { style: { textAlign: 'right', marginTop: '8px', paddingRight: '4px' } }, [ h(ElButton, { type: 'text', onClick: () => { render(null, mountNode); document.body.removeChild(mountNode); ElMessage.info('åæ¶åæ¹åºåºæä½'); }, style: { marginRight: '8px', color: '#606266' } }, 'åæ¶'), h(ElButton, { type: 'primary', onClick: async () => { const formRef = vnode.component.refs.outboundForm; try { // è¡¨åæ ¡éªï¼ä¼åæ¶æ ¡éªåºåºç«å°åå°æ°åæ®µï¼ await formRef.validate(); } catch (err) { return; } // 4. æé 请æ±åæ°ï¼æ°å¢å°æ°åæ®µï¼ const keys = this.selection.map((item) => item.id); const requestParams = { taskIds: keys, outboundPlatform: formData.selectedPlatform, // åºåºç«å° outboundDecimal: formData.outboundDecimal // æ°å¢ï¼å°æ°åæ®µä¼ ç»å端 }; // 5. è°ç¨åºåºæ¥å£ this.http .post("api/Task/ ", requestParams, "æ°æ®å¤çä¸") .then((x) => { if (!x.status) return ElMessage.error(x.message); ElMessage.success("æä½æå"); this.showDetialBox = false; // å ³éè¯¦æ æ¡ this.$emit("parentCall", ($vue) => { $vue.getData(); // éç¥ç¶ç»ä»¶å·æ° }); // å ³éå¼¹çª render(null, mountNode); document.body.removeChild(mountNode); }) .catch(() => { ElMessage.error('请æ±å¤±è´¥ï¼è¯·ç¨åéè¯'); }); }, style: { borderRadius: '4px', padding: '8px 20px' } }, 'ç¡®å®åæ¹åºåº') ]) ]) }); // ç»å®appä¸ä¸æï¼ç¡®ä¿Elç»ä»¶æ£å¸¸å·¥ä½ vnode.appContext = this.$.appContext; render(vnode, mountNode); }, setCurrent(row) { this.$refs.singleTable.setCurrentRow(row); }, ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/router/viewGird.js
@@ -79,7 +79,14 @@ name: 'PickingConfirm', component: () => import('@/views/outbound/PickingConfirm.vue'), meta: { title: 'æ£é确认', keepAlive: false } },{ }, { path: '/outbound/batchpicking', name: 'BatchPickingConfirm', component: () => import('@/views/outbound/BatchPickingConfirm.vue'), meta: { title: 'æ£é确认', keepAlive: false } }, { path: '/stockInfo', name: 'stockInfo', component: () => import('@/views/stock/stockInfo.vue') ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/BatchPickingConfirm.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,1066 @@ <template> <div class="OutboundPicking-container"> <div class="page-header"> <el-page-header @back="goBack"> <template #content> <span class="title">åºåºæ£é确认 - {{ this.$route.query.orderNo }}</span> <el-tag v-if="currentBatchNo" type="success" style="margin-left: 10px;"> å½åæ¹æ¬¡: {{ currentBatchNo }} </el-tag> </template> </el-page-header> </div> <!-- æ¹æ¬¡æä½åºå --> <div class="batch-operations"> <el-card> <div class="batch-actions"> <el-button type="primary" @click="openBatchAllocateDialog">åæ¹åé </el-button> <el-select v-model="selectedBatchNo" placeholder="éæ©æ¹æ¬¡" @change="onBatchChange" style="width: 200px; margin-left: 10px;"> <el-option v-for="batch in batchList" :key="batch.batchNo" :label="`${batch.batchNo} (${batch.batchStatusText})`" :value="batch.batchNo"> </el-option> </el-select> <el-button type="info" @click="refreshBatchList">å·æ°æ¹æ¬¡</el-button> </div> </el-card> </div> <!-- æ«ç åºå --> <div class="scanner-area"> <el-card> <div class="scanner-form"> <el-input ref="palletInput" v-model="scanData.palletCode" placeholder="æ«ææçç " @change="onPalletScan" @keyup.enter.native="onPalletScan"> </el-input> <el-input ref="barcodeInput" v-model="scanData.barcode" placeholder="æ«æç©ææ¡ç " @change="onBarcodeScan" @keyup.enter.native="onBarcodeScan"> </el-input> <el-button type="success" @click="confirmPicking">确认æ£é</el-button> <el-button type="warning" @click="openSplitDialog">æå </el-button> <el-button type="info" @click="openRevertSplitDialog">æ¤éæå </el-button> <el-button type="info" @click="handleEmptyPallet">å空箱</el-button> <el-button type="primary" @click="openBatchReturnDialog">ååº</el-button> </div> </el-card> </div> <!-- æ¹æ¬¡æ±æ»ä¿¡æ¯ --> <div class="batch-summary-area" v-if="currentBatchNo"> <el-card> <div class="batch-summary-info"> <el-tag type="info">æ¹æ¬¡å·: {{ batchSummary.batchNo }}</el-tag> <el-tag :type="getBatchStatusType(batchSummary.batchStatus)"> {{ batchSummary.batchStatusText }} </el-tag> <el-tag type="warning">åé æ°é: {{ batchSummary.batchQuantity }}</el-tag> <el-tag type="success">宿æ°é: {{ batchSummary.completedQuantity }}</el-tag> <el-tag type="danger">å©ä½æ°é: {{ batchSummary.remainingQuantity }}</el-tag> </div> </el-card> </div> <!-- æ±æ»ä¿¡æ¯ --> <div class="summary-area"> <el-card> <div class="summary-info"> <el-tag type="warning">æªæ£éæ¡æ°: {{summary.unpickedCount}}</el-tag> <el-tag type="danger">æªæ£éæ°é: {{summary.unpickedQuantity}}</el-tag> <el-tag type="info">æçç¶æ: {{palletStatus}}</el-tag> </div> </el-card> </div> <!-- æ°æ®å表 --> <div class="content-area"> <el-row :gutter="20"> <el-col :span="12"> <el-card header="æªæ£éå表"> <el-table :data="unpickedList" border height="440"> <el-table-column prop="materielCode" label="ç©æç¼ç " width="120"></el-table-column> <el-table-column prop="assignQuantity" label="åé æ°é" width="100"></el-table-column> <el-table-column prop="pickedQty" label="å·²æ£æ°é" width="100"></el-table-column> <el-table-column prop="remainQuantity" label="å©ä½æ°é" width="100"></el-table-column> <el-table-column prop="locationCode" label="è´§ä½" width="100"></el-table-column> <el-table-column prop="currentBarcode" label="æ¡ç "></el-table-column> </el-table> </el-card> </el-col> <el-col :span="12"> <el-card header="å·²æ£éå表"> <div class="table-actions"> <el-button size="mini" type="danger" :disabled="selectedPickedRows.length === 0" @click="batchCancelSelected"> åæ¶æ£é </el-button> <span class="selection-count">已鿩 {{selectedPickedRows.length}} 项</span> </div> <el-table :data="pickedList" border height="400" style="width: 100%" @selection-change="handlePickedSelectionChange"> <el-table-column type="selection" width="55"></el-table-column> <el-table-column prop="materielCode" label="ç©æç¼ç " width="120"></el-table-column> <el-table-column prop="pickedQty" label="å·²æ£æ°é" width="100"></el-table-column> <el-table-column prop="locationCode" label="è´§ä½" width="100"></el-table-column> <el-table-column prop="currentBarcode" label="æ¡ç "></el-table-column> </el-table> </el-card> </el-col> </el-row> </div> <!-- åæ¹åé å¼¹çª --> <div v-if="showBatchAllocateDialog" class="custom-dialog-overlay"> <div class="custom-dialog-wrapper"> <div class="custom-dialog"> <div class="custom-dialog-header"> <h3>åæ¹åé åºå</h3> <el-button type="text" @click="closeBatchAllocateDialog" class="close-button">Ã</el-button> </div> <div class="custom-dialog-body"> <el-form :model="batchAllocateForm" :rules="batchAllocateFormRules" ref="batchAllocateFormRef" label-width="100px"> <el-form-item label="订åç¼å·"> <el-input v-model="batchAllocateForm.orderNo" disabled></el-input> </el-form-item> <el-form-item label="ç©ææç»" prop="orderDetailId"> <el-select v-model="batchAllocateForm.orderDetailId" placeholder="éæ©ç©ææç»" style="width: 100%"> <el-option v-for="detail in allocatableDetails" :key="detail.id" :label="`${detail.materielCode} - éæ±:${detail.needOutQuantity} å·²åé :${detail.allocatedQuantity} å¯åé :${detail.availableQuantity}`" :value="detail.id"> </el-option> </el-select> </el-form-item> <el-form-item label="åé æ°é" prop="batchQuantity"> <el-input-number v-model="batchAllocateForm.batchQuantity" :min="0.01" :precision="2" :step="1" style="width: 100%" placeholder="è¾å ¥åé æ°é"> </el-input-number> </el-form-item> <el-form-item label="å¯åé æ°é"> <el-input :value="getAvailableQuantity()" disabled></el-input> </el-form-item> </el-form> </div> <div class="custom-dialog-footer"> <el-button @click="closeBatchAllocateDialog">åæ¶</el-button> <el-button type="primary" @click="handleBatchAllocate" :loading="batchAllocateLoading">确认åé </el-button> </div> </div> </div> </div> <!-- æå å¼¹çª --> <div v-if="showCustomSplitDialog" class="custom-dialog-overlay"> <div class="custom-dialog-wrapper"> <div class="custom-dialog"> <div class="custom-dialog-header"> <h3>æå æä½</h3> <el-button type="text" @click="closeCustomSplitDialog" class="close-button">X</el-button> </div> <div class="custom-dialog-body"> <el-form :model="splitForm" :rules="splitFormRules" ref="splitFormRef" label-width="100px"> <el-form-item label="订åç¼å·"> <el-input v-model="splitForm.orderNo" disabled></el-input> </el-form-item> <el-form-item label="æ¹æ¬¡ç¼å·"> <el-input v-model="splitForm.batchNo" disabled></el-input> </el-form-item> <el-form-item label="æçç¼å·"> <el-input v-model="splitForm.palletCode" disabled></el-input> </el-form-item> <el-form-item label="åæ¡ç " prop="originalBarcode"> <el-input v-model="splitForm.originalBarcode" placeholder="æ«æåæ¡ç " @keyup.enter.native="onSplitBarcodeScan" @change="onSplitBarcodeScan" clearable> </el-input> </el-form-item> <el-form-item label="ç©æç¼ç "> <el-input v-model="splitForm.materielCode" disabled></el-input> </el-form-item> <el-form-item label="å©ä½æ°é"> <el-input v-model="splitForm.maxQuantity" disabled></el-input> </el-form-item> <el-form-item label="æå æ°é" prop="splitQuantity"> <el-input-number v-model="splitForm.splitQuantity" :min="1" :max="splitForm.maxQuantity" :precision="2" :step="1" style="width: 100%"> </el-input-number> </el-form-item> </el-form> </div> <div class="custom-dialog-footer"> <el-button @click="closeCustomSplitDialog">åæ¶</el-button> <el-button type="primary" @click="handleSplitPackage" :loading="splitLoading">确认æå </el-button> </div> </div> </div> </div> <!-- æ¤éæå å¼¹çª --> <div v-if="showRevertSplitDialog" class="custom-dialog-overlay"> <div class="custom-dialog-wrapper"> <div class="custom-dialog"> <div class="custom-dialog-header"> <h3>æ¤éæå </h3> <el-button type="text" @click="closeRevertSplitDialog" class="close-button">Ã</el-button> </div> <div class="custom-dialog-body"> <el-form :model="revertSplitForm" :rules="revertSplitFormRules" ref="revertSplitFormRef" label-width="100px"> <el-form-item label="æ°æ¡ç " prop="newBarcode"> <el-input v-model="revertSplitForm.newBarcode" placeholder="æ«ææ°æ¡ç " @keyup.enter.native="onRevertSplitBarcodeScan" @change="onRevertSplitBarcodeScan" clearable> </el-input> </el-form-item> </el-form> </div> <div class="custom-dialog-footer"> <el-button @click="closeRevertSplitDialog">åæ¶</el-button> <el-button type="primary" @click="handleRevertSplit" :loading="revertSplitLoading">确认æ¤é</el-button> </div> </div> </div> </div> <!-- æ¹éååºå¼¹çª --> <div v-if="showBatchReturnDialog" class="custom-dialog-overlay"> <div class="custom-dialog-wrapper"> <div class="custom-dialog"> <div class="custom-dialog-header"> <h3>æ¹æ¬¡ååº</h3> <el-button type="text" @click="closeBatchReturnDialog" class="close-button">Ã</el-button> </div> <div class="custom-dialog-body"> <el-form :model="batchReturnForm" :rules="batchReturnFormRules" ref="batchReturnFormRef" label-width="100px"> <el-form-item label="订åç¼å·"> <el-input v-model="batchReturnForm.orderNo" disabled></el-input> </el-form-item> <el-form-item label="æ¹æ¬¡ç¼å·"> <el-input v-model="batchReturnForm.batchNo" disabled></el-input> </el-form-item> <el-form-item label="æªæ£éæ°é"> <el-input v-model="batchReturnForm.unpickedQuantity" disabled></el-input> </el-form-item> <el-form-item label="æªæ£éæ¡æ°"> <el-input v-model="batchReturnForm.unpickedCount" disabled></el-input> </el-form-item> </el-form> </div> <div class="custom-dialog-footer"> <el-button @click="closeBatchReturnDialog">åæ¶</el-button> <el-button type="primary" @click="handleBatchReturnConfirm" :loading="batchReturnLoading">确认ååº</el-button> </div> </div> </div> </div> <!-- åèµ°ç©ºç®±å¼¹çª --> <div v-if="showEmptyPalletDialog" class="custom-dialog-overlay"> <div class="custom-dialog-wrapper"> <div class="custom-dialog"> <div class="custom-dialog-header"> <h3>å走空箱</h3> <el-button type="text" @click="closeEmptyPalletDialog" class="close-button">Ã</el-button> </div> <div class="custom-dialog-body"> <el-form :model="emptypalletOutForm" :rules="emptypalletOutFormRules" ref="emptypalletOutFormRef" label-width="100px"> <el-form-item label="订åç¼å·"> <el-input v-model="emptypalletOutForm.orderNo" disabled></el-input> </el-form-item> <el-form-item label="æçç¼å·" prop="palletCode"> <el-input v-model="emptypalletOutForm.palletCode" placeholder="æ«ææçç " @keyup.enter.native="onEmptyPalletScan" @change="onEmptyPalletScan" clearable> </el-input> </el-form-item> </el-form> </div> <div class="custom-dialog-footer"> <el-button @click="closeEmptyPalletDialog">åæ¶</el-button> <el-button type="primary" @click="handleEmptyPalletConfirm" :loading="emptypalletOutLoading">确认å走空箱</el-button> </div> </div> </div> </div> <print-view ref="childs" @parentcall="parentcall"></print-view> </div> </template> <script> import http from '@/api/http.js' import { ref, defineComponent } from "vue"; import { ElMessage } from 'element-plus' import { useRoute } from 'vue-router' import printView from "@/extension/outbound/extend/printView.vue" export default defineComponent({ name: 'BatchOutboundPicking', components: {printView}, data() { // éªè¯è§åå®ä¹... const validateBatchQuantity = (rule, value, callback) => { if (value === null || value === undefined || value === '') { callback(new Error('请è¾å ¥åé æ°é')); } else if (value <= 0) { callback(new Error('åé æ°éå¿ é¡»å¤§äº0')); } else { callback(); } }; const validateOrderDetailId = (rule, value, callback) => { if (!value) { callback(new Error('è¯·éæ©ç©ææç»')); } else { callback(); } }; return { scanData: { orderNo: '', palletCode: '', barcode: '', batchNo: '' }, currentBatchNo: '', // å½åæ¹æ¬¡å· batchList: [], // æ¹æ¬¡å表 selectedBatchNo: '', // éä¸çæ¹æ¬¡å· batchSummary: {}, // æ¹æ¬¡æ±æ»ä¿¡æ¯ unpickedList: [], pickedList: [], selectedPickedRows: [], summary: { unpickedCount: 0, unpickedQuantity: 0, pickedCount: 0 }, palletStatus: 'æªç¥', // å¼¹çªç¶æ showBatchAllocateDialog: false, showCustomSplitDialog: false, showRevertSplitDialog: false, showBatchReturnDialog: false, showEmptyPalletDialog: false, // å è½½ç¶æ batchAllocateLoading: false, splitLoading: false, revertSplitLoading: false, batchReturnLoading: false, emptypalletOutLoading: false, // è¡¨åæ°æ® batchAllocateForm: { orderNo: '', orderDetailId: '', batchQuantity: 0 }, allocatableDetails: [], // å¯åé ç订åæç» splitForm: { orderNo: '', batchNo: '', palletCode: '', originalBarcode: '', materielCode: '', splitQuantity: 0, maxQuantity: 0 }, revertSplitForm: { newBarcode: '' }, batchReturnForm: { orderNo: '', batchNo: '', unpickedCount: 0, unpickedQuantity: 0 }, emptypalletOutForm: { orderNo: '', palletCode: '' }, // éªè¯è§å batchAllocateFormRules: { orderDetailId: [ { required: true, validator: validateOrderDetailId, trigger: 'change' } ], batchQuantity: [ { required: true, validator: validateBatchQuantity, trigger: 'blur' } ] }, // å ¶ä»éªè¯è§å... isProcessing: false } }, mounted() { if (this.$route.query.orderNo) { this.scanData.orderNo = this.$route.query.orderNo; this.batchAllocateForm.orderNo = this.$route.query.orderNo; this.loadBatchList(); } this.$nextTick(() => { this.$refs.palletInput.focus(); }); }, methods: { goBack(){ this.$router.back() }, // æ¹æ¬¡ç¸å ³æ¹æ³ async loadBatchList() { try { const res = await http.post('/api/BatchOutbound/order-batch-list', { orderNo: this.scanData.orderNo }); if (res.status) { this.batchList = res.data || []; if (this.batchList.length > 0) { this.selectedBatchNo = this.batchList[0].batchNo; this.currentBatchNo = this.selectedBatchNo; this.scanData.batchNo = this.selectedBatchNo; this.loadBatchData(); } } } catch (error) { this.$message.error('å è½½æ¹æ¬¡å表失败'); } }, async refreshBatchList() { await this.loadBatchList(); this.$message.success('æ¹æ¬¡åè¡¨å·²å·æ°'); }, onBatchChange(batchNo) { this.currentBatchNo = batchNo; this.scanData.batchNo = batchNo; this.loadBatchData(); }, async loadBatchData() { if (!this.currentBatchNo) return; await this.loadBatchSummary(); await this.loadUnpickedList(); await this.loadPickedList(); }, async loadBatchSummary() { try { const res = await http.post('/api/BatchOutbound/batch-summary', { orderNo: this.scanData.orderNo, batchNo: this.currentBatchNo }); if (res.status) { this.batchSummary = res.data || {}; } } catch (error) { this.$message.error('å è½½æ¹æ¬¡æ±æ»å¤±è´¥'); } }, async loadUnpickedList() { try { const res = await http.post('/api/BatchOutbound/batch-unpicked-list', { orderNo: this.scanData.orderNo, batchNo: this.currentBatchNo }); this.unpickedList = res.data || []; this.summary.unpickedCount = this.unpickedList.length; this.summary.unpickedQuantity = this.unpickedList.reduce((sum, item) => sum + (item.remainQuantity || 0), 0); } catch (error) { this.$message.error('å è½½æªæ£éå表失败'); } }, async loadPickedList() { try { const res = await http.post('/api/BatchOutbound/batch-picked-list', { orderNo: this.scanData.orderNo, batchNo: this.currentBatchNo }); this.pickedList = res.data || []; this.summary.pickedCount = this.pickedList.length; } catch (error) { this.$message.error('å 载已æ£éå表失败'); } }, getBatchStatusType(status) { const statusMap = { 0: 'info', // åé ä¸ 1: 'warning', // æ§è¡ä¸ 2: 'success', // 已宿 3: 'danger' // å·²ååº }; return statusMap[status] || 'info'; }, // åæ¹åé ç¸å ³æ¹æ³ async openBatchAllocateDialog() { this.showBatchAllocateDialog = true; await this.loadAllocatableDetails(); this.batchAllocateForm.orderDetailId = ''; this.batchAllocateForm.batchQuantity = 0; }, async loadAllocatableDetails() { try { const res = await http.post('/api/BatchOutbound/allocatable-order-details', { orderNo: this.scanData.orderNo }); if (res.status) { this.allocatableDetails = res.data || []; } } catch (error) { this.$message.error('å è½½å¯åé æç»å¤±è´¥'); } }, getAvailableQuantity() { const detail = this.allocatableDetails.find(d => d.id === this.batchAllocateForm.orderDetailId); return detail ? detail.availableQuantity : 0; }, async handleBatchAllocate() { if (this.$refs.batchAllocateFormRef) { this.$refs.batchAllocateFormRef.validate(async (valid) => { if (valid) { this.batchAllocateLoading = true; try { const res = await http.post('/api/BatchOutbound/batch-allocate-stock', this.batchAllocateForm); if (res.status) { this.$message.success('åæ¹åé æå'); this.showBatchAllocateDialog = false; await this.loadBatchList(); // å·æ°æ¹æ¬¡å表 } else { this.$message.error(res.message || 'åæ¹åé 失败'); } } catch (error) { this.$message.error('åæ¹åé 失败'); } finally { this.batchAllocateLoading = false; } } }); } }, closeBatchAllocateDialog() { this.showBatchAllocateDialog = false; }, // 忣ç¸å ³æ¹æ³ async confirmPicking() { if (this.isProcessing) return; if (!this.scanData.orderNo || !this.scanData.palletCode || !this.scanData.barcode) { this.$message.warning('è¯·å æ«ææçç åç©ææ¡ç '); this.focusBarcodeInput(); return; } if (!this.currentBatchNo) { this.$message.warning('请å éæ©æ¹æ¬¡'); return; } this.isProcessing = true; try { const res = await http.post('/api/BatchOutbound/confirm-picking', this.scanData); if (res.status) { this.$message.success('æ£é确认æå'); this.scanData.barcode = ''; await this.loadBatchData(); if(res.data && res.data.splitResults && res.data.splitResults.length>0){ this.$refs.childs.open(res.data.splitResults); } this.$nextTick(() => { this.$refs.barcodeInput.focus(); }); } else { this.$message.error(res.message || 'æ£é确认失败'); this.focusBarcodeInput(true); } } catch (error) { this.$message.error('æ£é确认失败: ' + (error.message || 'ç½ç»é误')); this.focusBarcodeInput(true); } finally { this.isProcessing = false; } }, // æå ç¸å ³æ¹æ³ openSplitDialog() { if (!this.scanData.palletCode) { this.$message.warning('è¯·å æ«ææçç '); return; } if (!this.currentBatchNo) { this.$message.warning('请å éæ©æ¹æ¬¡'); return; } this.showCustomSplitDialog = true; this.resetSplitForm(); this.splitForm.orderNo = this.scanData.orderNo; this.splitForm.batchNo = this.currentBatchNo; this.splitForm.palletCode = this.scanData.palletCode; }, async onSplitBarcodeScan() { if (!this.splitForm.originalBarcode) return; this.splitForm.originalBarcode = this.splitForm.originalBarcode.replace(/\n/g, '').trim(); try { const res = await http.post('/api/BatchOutbound/split-package-info', { orderNo: this.splitForm.orderNo, batchNo: this.splitForm.batchNo, barcode: this.splitForm.originalBarcode }); if (res.status) { this.splitForm.materielCode = res.data.materielCode; this.splitForm.maxQuantity = res.data.remainQuantity; this.splitForm.splitQuantity = Math.min(1, this.splitForm.maxQuantity); } else { this.$message.error(res.message || 'è·åæå ä¿¡æ¯å¤±è´¥'); } } catch (error) { this.$message.error('è·åæå ä¿¡æ¯å¤±è´¥'); } }, async handleSplitPackage() { if (this.$refs.splitFormRef) { this.$refs.splitFormRef.validate(async (valid) => { if (valid) { this.splitLoading = true; try { const res = await http.post('/api/BatchOutbound/manual-split-package', this.splitForm); if (res.status) { this.$message.success('æå æå'); this.showCustomSplitDialog = false; await this.loadBatchData(); } else { this.$message.error(res.message || 'æå 失败'); } } catch (error) { this.$message.error('æå 失败'); } finally { this.splitLoading = false; } } }); } }, // æ¤éæå async onRevertSplitBarcodeScan() { if (!this.revertSplitForm.newBarcode) return; this.revertSplitForm.newBarcode = this.revertSplitForm.newBarcode.replace(/\n/g, '').trim(); }, async handleRevertSplit() { if (this.$refs.revertSplitFormRef) { this.$refs.revertSplitFormRef.validate(async (valid) => { if (valid) { this.revertSplitLoading = true; try { const res = await http.post('/api/BatchOutbound/cancel-split-package', { orderNo: this.scanData.orderNo, batchNo: this.currentBatchNo, newBarcode: this.revertSplitForm.newBarcode }); if (res.status) { this.$message.success('æ¤éæå æå'); this.showRevertSplitDialog = false; await this.loadBatchData(); } else { this.$message.error(res.message || 'æ¤éæå 失败'); } } catch (error) { this.$message.error('æ¤éæå 失败'); } finally { this.revertSplitLoading = false; } } }); } }, // ååºç¸å ³æ¹æ³ openBatchReturnDialog() { if (!this.currentBatchNo) { this.$message.warning('请å éæ©æ¹æ¬¡'); return; } this.showBatchReturnDialog = true; this.batchReturnForm.orderNo = this.scanData.orderNo; this.batchReturnForm.batchNo = this.currentBatchNo; this.batchReturnForm.unpickedCount = this.summary.unpickedCount; this.batchReturnForm.unpickedQuantity = this.summary.unpickedQuantity; }, async handleBatchReturnConfirm() { this.batchReturnLoading = true; try { const res = await http.post('/api/BatchOutbound/batch-return-stock', { orderNo: this.scanData.orderNo, batchNo: this.currentBatchNo }); if (res.status) { this.$message.success('æ¹æ¬¡ååºæå'); this.showBatchReturnDialog = false; await this.loadBatchData(); } else { this.$message.error(res.message || 'æ¹æ¬¡ååºå¤±è´¥'); } } catch (error) { this.$message.error('æ¹æ¬¡ååºå¤±è´¥'); } finally { this.batchReturnLoading = false; } }, // åç©ºç®±æ¹æ³ async handleEmptyPalletConfirm() { this.emptypalletOutLoading = true; try { const res = await http.post('/api/BatchOutbound/remove-empty-pallet', this.emptypalletOutForm); if (res.status) { this.$message.success('å走空箱æå'); this.showEmptyPalletDialog = false; await this.loadBatchData(); } else { this.$message.error(res.message || 'å走空箱失败'); } } catch (error) { this.$message.error('å走空箱失败'); } finally { this.emptypalletOutLoading = false; } }, // å ¶ä»åææ¹æ³... onPalletScan() { this.scanData.palletCode = this.scanData.palletCode.replace(/\n/g, '').trim(); if (!this.scanData.palletCode) return; this.loadActiveBatch(); this.$nextTick(() => { this.$refs.barcodeInput.focus(); }); }, async loadActiveBatch() { try { const res = await http.post('/api/BatchOutbound/active-batch', { orderNo: this.scanData.orderNo, palletCode: this.scanData.palletCode }); if (res.status && res.data) { this.currentBatchNo = res.data.batchNo; this.scanData.batchNo = res.data.batchNo; this.selectedBatchNo = res.data.batchNo; await this.loadBatchData(); } } catch (error) { console.log('è·åæ´»è·æ¹æ¬¡å¤±è´¥ï¼å¯è½æç没æå ³èæ¹æ¬¡'); } }, onBarcodeScan() { this.scanData.barcode = this.scanData.barcode.replace(/\n/g, '').trim(); if (!this.scanData.barcode) return; this.confirmPicking(); }, focusBarcodeInput(selectText = false) { this.$nextTick(() => { const input = this.$refs.barcodeInput; if (input && input.$el && input.$el.querySelector('input')) { const inputEl = input.$el.querySelector('input'); inputEl.focus(); if (selectText) { inputEl.select(); } } }); }, handlePickedSelectionChange(selection) { this.selectedPickedRows = selection; }, async batchCancelSelected() { if (this.selectedPickedRows.length === 0) { this.$message.warning('请å éæ©è¦åæ¶ç项'); return; } this.$confirm(`ç¡®å®è¦åæ¶éä¸ç ${this.selectedPickedRows.length} 项åï¼`, 'æç¤º', { confirmButtonText: 'ç¡®å®', cancelButtonText: 'åæ¶', type: 'warning' }).then(async () => { try { for (const row of this.selectedPickedRows) { try { const res = await http.post('/api/BatchOutbound/cancel-picking', { orderNo: this.scanData.orderNo, batchNo: this.currentBatchNo, palletCode: this.scanData.palletCode, barcode: row.currentBarcode }); if (!res.status) { this.$message.warning(`åæ¶æ£é失败: ${row.currentBarcode} - ${res.message}`); } } catch (error) { this.$message.warning(`åæ¶æ£é失败: ${row.currentBarcode} - ${error.message}`); } } this.$message.success('æ¹é忶宿'); await this.loadBatchData(); this.selectedPickedRows = []; } catch (error) { this.$message.error('æ¹éåæ¶æä½å¤±è´¥'); } }); }, // éç½®æ¹æ³ resetSplitForm() { this.splitForm.originalBarcode = ''; this.splitForm.materielCode = ''; this.splitForm.splitQuantity = 0; this.splitForm.maxQuantity = 0; }, closeCustomSplitDialog() { this.showCustomSplitDialog = false; this.resetSplitForm(); }, openRevertSplitDialog() { this.showRevertSplitDialog = true; this.revertSplitForm.newBarcode = ''; }, closeRevertSplitDialog() { this.showRevertSplitDialog = false; this.revertSplitForm.newBarcode = ''; }, closeBatchReturnDialog() { this.showBatchReturnDialog = false; }, openEmptyPalletDialog() { this.showEmptyPalletDialog = true; this.emptypalletOutForm.orderNo = this.scanData.orderNo; this.emptypalletOutForm.palletCode = ''; }, closeEmptyPalletDialog() { this.showEmptyPalletDialog = false; this.emptypalletOutForm.palletCode = ''; }, onEmptyPalletScan() { if (!this.emptypalletOutForm.palletCode) return; this.emptypalletOutForm.palletCode = this.emptypalletOutForm.palletCode.replace(/\n/g, '').trim(); } } }) </script> <style scoped> .OutboundPicking-container { padding: 20px; } .batch-operations { margin-bottom: 15px; } .batch-actions { display: flex; align-items: center; gap: 10px; } .batch-summary-area { margin-bottom: 15px; } .batch-summary-info { display: flex; gap: 15px; flex-wrap: wrap; } .scanner-form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } .scanner-form .el-input { width: 200px; } .summary-info { display: flex; gap: 20px; flex-wrap: wrap; } .table-actions { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; padding: 0 10px; } .selection-count { font-size: 12px; color: #909399; } /* èªå®ä¹å¼¹çªæ ·å¼ */ .custom-dialog-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; } .custom-dialog-wrapper { position: relative; z-index: 10000; } .custom-dialog { background: white; border-radius: 4px; width: 500px; max-width: 90vw; max-height: 90vh; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); overflow: auto; } .custom-dialog-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 20px 10px; border-bottom: 1px solid #ebeef5; } .custom-dialog-header h3 { margin: 0; color: #303133; } .close-button { font-size: 18px; color: #909399; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; } .close-button:hover { color: #409EFF; background-color: transparent; } .custom-dialog-body { padding: 20px; } .custom-dialog-footer { padding: 10px 20px 20px; text-align: right; border-top: 1px solid #ebeef5; } .custom-dialog-footer .el-button { margin-left: 10px; } @media (max-width: 768px) { .custom-dialog { width: 95vw; margin: 10px; } .scanner-form { flex-direction: column; align-items: stretch; } .scanner-form .el-input { width: 100%; } } </style> ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-shmBinary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-shmBinary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/OrderEnum/OutboundOrderEnum.cs
@@ -21,12 +21,18 @@ [Description("åºåºä¸")] åºåºä¸ = 1, /// <summary> /// åºåºå®æ /// </summary> [Description("åºåºå®æ")] åºåºå®æ = 2, [Description("é¨å宿")] é¨å宿 =3, /// <summary> /// å ³é /// </summary> ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/OutLockStockStatusEnum.cs
@@ -24,7 +24,8 @@ æ§è¡ä¸ = 1, 已宿 = 2, å·²ååº = 3, 已忶 = 4 已忶 = 4, } public enum SplitPackageStatusEnum @@ -63,6 +64,9 @@ [Description("å·²ååº")] å·²ååº =8, [Description("已鿾")] 已鿾 =9, [Description("æ¤é")] æ¤é = 99 } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundBatchPickingService.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,17 @@ using WIDESEA_Core; using WIDESEA_Core.BaseRepository; using WIDESEA_Model.Models; namespace WIDESEA_IOutboundService { public interface IOutboundBatchPickingService { IRepository<Dt_PickingRecord> Repository { get; } Task<WebResponseContent> BatchReturnStock(string orderNo, string palletCode); Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode); Task<WebResponseContent> CancelSplitPackage(string orderNo, string palletCode, string newBarcode); Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string palletCode, string barcode); Task<WebResponseContent> ManualSplitPackage(string orderNo, string palletCode, string originalBarcode, decimal splitQuantity); } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundOrderService.cs
@@ -96,7 +96,7 @@ item.Unit = purchaseToStockResult.Unit; item.OrderQuantity = purchaseToStockResult.Quantity; } if (model.OrderType != InOrderTypeEnum.Allocat.ObjToInt()) if (model.OrderType != InOrderTypeEnum.AllocatInbound.ObjToInt()) { model.InboundOrderNo = CreateCodeByRule(nameof(RuleCodeEnum.InboundOrderRule)); } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs
@@ -44,7 +44,13 @@ public int StockId { get; set; } public string BatchNo { get; set; } public bool IsCancelled { get; set; } public DateTime? CancelTime { get; set; } public string CancelOperator { get; set; } public string FactoryArea { get; set; } } @@ -107,6 +113,7 @@ public DateTime RevertTime { get; set; } public string RevertOperator { get; set; } public int PreviousSplitRecordId { get; set; } [SugarColumn(IsNullable = true)] ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundBatchPickingService.cs
@@ -21,7 +21,7 @@ namespace WIDESEA_OutboundService { public class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>> public class OutboundBatchPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundBatchPickingService { @@ -86,72 +86,327 @@ /// <summary> /// åæ¹åæ£ç¡®è®¤ /// </summary> public async Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string batchNo, string palletCode, string barcode, decimal actualPickedQty) public async Task<WebResponseContent> ConfirmBatchPicking(string orderNo, string palletCode, string barcode) { try { _unitOfWorkManage.BeginTran(); // 1. éªè¯åæ£è¯·æ± var validationResult = await ValidateBatchPickingRequest(orderNo, batchNo, palletCode, barcode, actualPickedQty); var validationResult = await ValidatePickingRequest(orderNo, palletCode, barcode); if (!validationResult.IsValid) return WebResponseContent.Instance.Error(validationResult.ErrorMessage); var (lockInfo, orderDetail, stockDetail) = validationResult.Data; var (lockInfo, orderDetail, stockDetail, batch) = validationResult.Data; // 使ç¨éå®ä¿¡æ¯çåé æ°éä½ä¸ºå®é 忣æ°é var actualPickedQty = lockInfo.AssignQuantity; // 2. æ§è¡åæ£é»è¾ var pickingResult = await ExecuteBatchPickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty); var pickingResult = await ExecutePickingLogic(lockInfo, orderDetail, stockDetail, actualPickedQty); // 3. æ´æ°æ¹æ¬¡å®ææ°é await UpdateBatchCompletedQuantity(batchNo, actualPickedQty); // 3. æ´æ°æ¹æ¬¡åè®¢åæ°æ® await UpdateBatchAndOrderData(batch, orderDetail, actualPickedQty, orderNo); // 4. æ´æ°è®¢åç¸å ³æ°æ® await UpdateOrderRelatedData(orderDetail.Id, actualPickedQty, orderNo); // 5. è®°å½æ£éåå² await RecordPickingHistory(pickingResult, orderNo, palletCode, batchNo); // 4. è®°å½æ£éåå² await RecordPickingHistory(pickingResult, orderNo, palletCode); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("忹忣æå"); return WebResponseContent.Instance.OK("忣æå", new { PickedQuantity = actualPickedQty, Barcode = barcode, MaterialCode = lockInfo.MaterielCode }); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"åæ¹åæ£å¤±è´¥ - OrderNo: {orderNo}, BatchNo: {batchNo}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"åæ¹åæ£å¤±è´¥ï¼{ex.Message}"); _logger.LogError($"åæ£å¤±è´¥ - OrderNo: {orderNo}, PalletCode: {palletCode}, Barcode: {barcode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"åæ£å¤±è´¥ï¼{ex.Message}"); } } /// <summary> /// 忶忣 /// </summary> public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode) { try { _unitOfWorkManage.BeginTran(); // æ¥æ¾åæ£è®°å½ var pickingRecord = await Db.Queryable<Dt_PickingRecord>() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && x.Barcode == barcode && !x.IsCancelled) .OrderByDescending(x => x.PickTime) .FirstAsync(); if (pickingRecord == null) return WebResponseContent.Instance.Error("æªæ¾å°åæ£è®°å½"); // æ¢å¤éå®ä¿¡æ¯ååºå await RevertPickingData(pickingRecord); //æ´æ°æ¹æ¬¡åè®¢åæ°æ® await RevertBatchAndOrderData(pickingRecord); // æ è®°åæ£è®°å½ä¸ºå·²åæ¶ pickingRecord.IsCancelled = true; pickingRecord.CancelTime = DateTime.Now; pickingRecord.CancelOperator = App.User.UserName; await Db.Updateable(pickingRecord).ExecuteCommandAsync(); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("忶忣æå"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"åæ¶åæ£å¤±è´¥ - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"åæ¶åæ£å¤±è´¥ï¼{ex.Message}"); } } private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>> ValidateBatchPickingRequest( string orderNo, string batchNo, string palletCode, string barcode, decimal actualPickedQty) #endregion #region æå¨æå /// <summary> /// æå¨æå /// </summary> public async Task<WebResponseContent> ManualSplitPackage(string orderNo, string palletCode, string originalBarcode, decimal splitQuantity) { // æ¥æ¾æ¹æ¬¡éå®ä¿¡æ¯ try { _unitOfWorkManage.BeginTran(); // éªè¯æå è¯·æ± var validationResult = await ValidateSplitRequest(orderNo, palletCode, originalBarcode, splitQuantity); if (!validationResult.IsValid) return WebResponseContent.Instance.Error(validationResult.ErrorMessage); var (lockInfo, stockDetail) = validationResult.Data; // . æ§è¡æå é»è¾ var splitResult = await ExecuteSplitLogic(lockInfo, stockDetail, splitQuantity, palletCode); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("æå¨æå æå", new { NewBarcode = splitResult.NewBarcode, OriginalBarcode = originalBarcode, SplitQuantity = splitQuantity }); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"æå¨æå 失败 - OrderNo: {orderNo}, Barcode: {originalBarcode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"æå¨æå 失败ï¼{ex.Message}"); } } #endregion #region åæ¶æå /// <summary> /// åæ¶æå /// </summary> public async Task<WebResponseContent> CancelSplitPackage(string orderNo, string palletCode, string newBarcode) { try { _unitOfWorkManage.BeginTran(); // æ¥æ¾æå è®°å½å¹¶éªè¯ var validationResult = await ValidateCancelSplitRequest(orderNo, palletCode, newBarcode); if (!validationResult.IsValid) return WebResponseContent.Instance.Error(validationResult.ErrorMessage); var (splitRecord, newLockInfo, newStockDetail) = validationResult.Data; // æ§è¡åæ¶æå é»è¾ await ExecuteCancelSplitLogic(splitRecord, newLockInfo, newStockDetail); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("åæ¶æå æå"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"åæ¶æå 失败 - OrderNo: {orderNo}, Barcode: {newBarcode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"åæ¶æå 失败ï¼{ex.Message}"); } } #endregion #region åæ¹ååº /// <summary> /// åæ¹ååº - éæ¾æªæ£éçåºå /// </summary> public async Task<WebResponseContent> BatchReturnStock(string orderNo, string palletCode) { try { _unitOfWorkManage.BeginTran(); // æ¥æ¾æç䏿ªå®æçéå®è®°å½ var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && x.Status == (int)OutLockStockStatusEnum.åºåºä¸) .ToListAsync(); if (!unfinishedLocks.Any()) return WebResponseContent.Instance.Error("该æçæ²¡ææªå®æçéå®è®°å½"); // ææ¹æ¬¡åç»å¤ç var batchGroups = unfinishedLocks.GroupBy(x => x.BatchNo); foreach (var batchGroup in batchGroups) { var batchNo = batchGroup.Key; var batchLocks = batchGroup.ToList(); // éæ¾åºååéå®è®°å½ foreach (var lockInfo in batchLocks) { await ReleaseLockAndStock(lockInfo); } // æ´æ°æ¹æ¬¡ç¶æ await UpdateBatchStatusForReturn(batchNo, batchLocks); // æ´æ°è®¢åæç»çå·²åé æ°é await UpdateOrderDetailAfterReturn(batchLocks); } _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("åæ¹ååºæå"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"åæ¹ååºå¤±è´¥ - OrderNo: {orderNo}, PalletCode: {palletCode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"åæ¹ååºå¤±è´¥ï¼{ex.Message}"); } } #endregion #region éªè¯æ¹æ³ private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>> ValidatePickingRequest( string orderNo, string palletCode, string barcode) { // æ¥æ¾éå®ä¿¡æ¯ var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(x => x.OrderNo == orderNo && x.BatchNo == batchNo && x.PalletCode == palletCode && x.CurrentBarcode == barcode && x.Status == (int)OutLockStockStatusEnum.åºåºä¸) .FirstAsync(); if (lockInfo == null) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("æªæ¾å°ææçæ¹æ¬¡éå®ä¿¡æ¯"); return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("æªæ¾å°ææçéå®ä¿¡æ¯"); if (actualPickedQty <= 0 || actualPickedQty > lockInfo.AssignQuantity - lockInfo.PickedQty) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Error("忣æ°éæ æ"); // æ£æ¥æ¯å¦å·²ç»åæ£å®æ if (lockInfo.PickedQty >= lockInfo.AssignQuantity) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error("该æ¡ç 已忣宿"); // è·å订åæç»ååºåæç» // è·åå ³èæ°æ® var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>() .FirstAsync(x => x.Id == lockInfo.OrderDetailId); var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>() .FirstAsync(x => x.Barcode == barcode && x.StockId == lockInfo.StockId); return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail)>.Success((lockInfo, orderDetail, stockDetail)); // éªè¯åºåæ°é if (stockDetail.StockQuantity < lockInfo.AssignQuantity) return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Error( $"åºåæ°éä¸è¶³ï¼éè¦ï¼{lockInfo.AssignQuantity}ï¼å®é ï¼{stockDetail.StockQuantity}"); var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>() .FirstAsync(x => x.BatchNo == lockInfo.BatchNo); return ValidationResult<(Dt_OutStockLockInfo, Dt_OutboundOrderDetail, Dt_StockInfoDetail, Dt_OutboundBatch)>.Success((lockInfo, orderDetail, stockDetail, batch)); } private async Task<PickingResult> ExecuteBatchPickingLogic( private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateSplitRequest( string orderNo, string palletCode, string originalBarcode, decimal splitQuantity) { var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode && x.CurrentBarcode == originalBarcode && x.Status == (int)OutLockStockStatusEnum.åºåºä¸) .FirstAsync(); if (lockInfo == null) return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æªæ¾å°ææçéå®ä¿¡æ¯"); var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>() .FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId); if (stockDetail.StockQuantity < splitQuantity) return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æå æ°éä¸è½å¤§äºåºåæ°é"); if (lockInfo.AssignQuantity - lockInfo.PickedQty < splitQuantity) return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æå æ°éä¸è½å¤§äºæªæ£éæ°é"); return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail)); } private async Task<ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateCancelSplitRequest( string orderNo, string palletCode, string newBarcode) { var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>() .Where(x => x.NewBarcode == newBarcode && x.OrderNo == orderNo && !x.IsReverted) .FirstAsync(); if (splitRecord == null) return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æªæ¾å°æå è®°å½"); var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(x => x.CurrentBarcode == newBarcode && x.PalletCode == palletCode && x.OrderNo == orderNo) .FirstAsync(); if (newLockInfo == null) return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æªæ¾å°æ°éå®ä¿¡æ¯"); // æ£æ¥æ°æ¡ç æ¯å¦å·²è¢«åæ£ var pickingRecord = await Db.Queryable<Dt_PickingRecord>() .Where(x => x.Barcode == newBarcode && !x.IsCancelled) .FirstAsync(); if (pickingRecord != null) return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("该æ¡ç å·²è¢«åæ£ï¼æ æ³åæ¶æå "); var newStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>() .FirstAsync(x => x.Barcode == newBarcode); return ValidationResult<(Dt_SplitPackageRecord, Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((splitRecord, newLockInfo, newStockDetail)); } #endregion #region æ ¸å¿é»è¾æ¹æ³ private async Task<PickingResult> ExecutePickingLogic( Dt_OutStockLockInfo lockInfo, Dt_OutboundOrderDetail orderDetail, Dt_StockInfoDetail stockDetail, decimal actualPickedQty) { @@ -180,86 +435,34 @@ }; } private async Task UpdateBatchCompletedQuantity(string batchNo, decimal pickedQty) private async Task<RevertPickingResult> RevertPickingData(Dt_PickingRecord pickingRecord) { await _outboundBatchRepository.Db.Updateable<Dt_OutboundBatch>() .SetColumns(x => x.CompletedQuantity == x.CompletedQuantity + pickedQty) .Where(x => x.BatchNo == batchNo) .ExecuteCommandAsync(); // æ£æ¥æ¹æ¬¡æ¯å¦å®æ var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>() .FirstAsync(x => x.BatchNo == batchNo); if (batch.CompletedQuantity >= batch.BatchQuantity) { batch.BatchStatus = (int)BatchStatusEnum.已宿; await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync(); } } #endregion #region æå¨æå /// <summary> /// æå¨æå /// </summary> public async Task<WebResponseContent> ManualSplitPackage(string orderNo, string batchNo, string originalBarcode, decimal splitQuantity) { try { _unitOfWorkManage.BeginTran(); // 1. éªè¯æå è¯·æ± var validationResult = await ValidateManualSplitRequest(orderNo, batchNo, originalBarcode, splitQuantity); if (!validationResult.IsValid) return WebResponseContent.Instance.Error(validationResult.ErrorMessage); var (lockInfo, stockDetail) = validationResult.Data; // 2. æ§è¡æå é»è¾ var splitResult = await ExecuteManualSplit(lockInfo, stockDetail, splitQuantity, batchNo); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("æå¨æå æå", new { NewBarcode = splitResult.NewBarcode }); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"æå¨æå 失败 - OrderNo: {orderNo}, BatchNo: {batchNo}, Barcode: {originalBarcode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"æå¨æå 失败ï¼{ex.Message}"); } } private async Task<ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>> ValidateManualSplitRequest( string orderNo, string batchNo, string originalBarcode, decimal splitQuantity) { // æ¢å¤éå®ä¿¡æ¯ var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(x => x.OrderNo == orderNo && x.BatchNo == batchNo && x.CurrentBarcode == originalBarcode && x.Status == (int)OutLockStockStatusEnum.åºåºä¸) .FirstAsync(); .FirstAsync(x => x.Id == pickingRecord.OutStockLockId); if (lockInfo == null) return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æªæ¾å°ææçéå®ä¿¡æ¯"); lockInfo.PickedQty -= pickingRecord.PickQuantity; lockInfo.Status = (int)OutLockStockStatusEnum.åºåºä¸; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // æ¢å¤åºå var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>() .FirstAsync(x => x.Barcode == originalBarcode && x.StockId == lockInfo.StockId); .FirstAsync(x => x.Barcode == pickingRecord.Barcode); if (stockDetail.StockQuantity < splitQuantity) return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æå æ°éä¸è½å¤§äºåºåæ°é"); stockDetail.StockQuantity += pickingRecord.PickQuantity; stockDetail.OutboundQuantity -= pickingRecord.PickQuantity; stockDetail.Status = (int)StockStatusEmun.åºåºéå®; await _stockInfoDetailService.Db.Updateable(stockDetail).ExecuteCommandAsync(); if (lockInfo.AssignQuantity - lockInfo.PickedQty < splitQuantity) return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Error("æå æ°éä¸è½å¤§äºæªæ£éæ°é"); return ValidationResult<(Dt_OutStockLockInfo, Dt_StockInfoDetail)>.Success((lockInfo, stockDetail)); return new RevertPickingResult { LockInfo = lockInfo, StockDetail = stockDetail }; } private async Task<SplitResultDto> ExecuteManualSplit(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal splitQuantity, string batchNo) private async Task<SplitResultDto> ExecuteSplitLogic(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail, decimal splitQuantity, string palletCode) { // çææ°æ¡ç string newBarcode = await GenerateNewBarcode(); @@ -289,14 +492,14 @@ { OrderNo = lockInfo.OrderNo, OrderDetailId = lockInfo.OrderDetailId, BatchNo = batchNo, OutboundBatchNo = lockInfo.OutboundBatchNo, MaterielCode = lockInfo.MaterielCode, StockId = lockInfo.StockId, OrderQuantity = splitQuantity, AssignQuantity = splitQuantity, PickedQty = 0, LocationCode = lockInfo.LocationCode, PalletCode = lockInfo.PalletCode, PalletCode = palletCode, Status = (int)OutLockStockStatusEnum.åºåºä¸, CurrentBarcode = newBarcode, Operator = App.User.UserName, @@ -305,6 +508,7 @@ // æ´æ°åéå®ä¿¡æ¯ lockInfo.AssignQuantity -= splitQuantity; lockInfo.OrderQuantity -= splitQuantity; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); // è®°å½æå åå² @@ -313,34 +517,8 @@ return new SplitResultDto { NewBarcode = newBarcode }; } #endregion #region åæ¶æå /// <summary> /// åæ¶æå /// </summary> public async Task<WebResponseContent> CancelSplitPackage(string orderNo, string batchNo, string newBarcode) private async Task ExecuteCancelSplitLogic(Dt_SplitPackageRecord splitRecord, Dt_OutStockLockInfo newLockInfo, Dt_StockInfoDetail newStockDetail) { try { _unitOfWorkManage.BeginTran(); // æ¥æ¾æå è®°å½åæ°éå®ä¿¡æ¯ var splitRecord = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>() .Where(x => x.NewBarcode == newBarcode && x.OrderNo == orderNo && !x.IsReverted) .FirstAsync(); if (splitRecord == null) return WebResponseContent.Instance.Error("æªæ¾å°æå è®°å½"); var newLockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(x => x.CurrentBarcode == newBarcode && x.BatchNo == batchNo) .FirstAsync(); if (newLockInfo == null) return WebResponseContent.Instance.Error("æªæ¾å°æ°éå®ä¿¡æ¯"); // æ¢å¤ååºå var originalStock = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>() .FirstAsync(x => x.Barcode == splitRecord.OriginalBarcode && x.StockId == splitRecord.StockId); @@ -353,11 +531,12 @@ .FirstAsync(x => x.Id == splitRecord.OutStockLockInfoId); originalLockInfo.AssignQuantity += splitRecord.SplitQty; originalLockInfo.OrderQuantity += splitRecord.SplitQty; await _outStockLockInfoService.Db.Updateable(originalLockInfo).ExecuteCommandAsync(); // å 餿°åºåæç» await _stockInfoDetailService.Db.Deleteable<Dt_StockInfoDetail>() .Where(x => x.Barcode == newBarcode) .Where(x => x.Barcode == newLockInfo.CurrentBarcode) .ExecuteCommandAsync(); // å 餿°éå®ä¿¡æ¯ @@ -368,66 +547,53 @@ // æ è®°æå è®°å½ä¸ºå·²æ¤é splitRecord.IsReverted = true; splitRecord.RevertTime = DateTime.Now; splitRecord.Operator = App.User.UserName; splitRecord.RevertOperator = App.User.UserName; await _splitPackageService.Db.Updateable(splitRecord).ExecuteCommandAsync(); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("åæ¶æå æå"); } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"åæ¶æå 失败 - OrderNo: {orderNo}, BatchNo: {batchNo}, Barcode: {newBarcode}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"åæ¶æå 失败ï¼{ex.Message}"); } } #endregion #region åæ¹ååº #region æ°æ®æ´æ°æ¹æ³ /// <summary> /// åæ¹ååº - éæ¾æªæ£éçåºå /// </summary> public async Task<WebResponseContent> BatchReturnStock(string orderNo, string batchNo) private async Task UpdateBatchAndOrderData(Dt_OutboundBatch batch, Dt_OutboundOrderDetail orderDetail, decimal pickedQty, string orderNo) { try // æ´æ°æ¹æ¬¡å®ææ°é batch.CompletedQuantity += pickedQty; if (batch.CompletedQuantity >= batch.BatchQuantity) { _unitOfWorkManage.BeginTran(); batch.BatchStatus = (int)BatchStatusEnum.已宿; } await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync(); // 1. æ¥æ¾æ¹æ¬¡æªå®æçéå®è®°å½ var unfinishedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(x => x.OrderNo == orderNo && x.BatchNo == batchNo && x.Status == (int)OutLockStockStatusEnum.åºåºä¸) .ToListAsync(); // æ´æ°è®¢åæç» orderDetail.OverOutQuantity += pickedQty; orderDetail.AllocatedQuantity -= pickedQty; await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync(); if (!unfinishedLocks.Any()) return WebResponseContent.Instance.Error("è¯¥æ¹æ¬¡æ²¡ææªå®æçéå®è®°å½"); // 2. éæ¾åºååéå®è®°å½ foreach (var lockInfo in unfinishedLocks) { await ReleaseLockAndStock(lockInfo); // æ£æ¥è®¢åç¶æ await CheckAndUpdateOrderStatus(orderNo); } // 3. æ´æ°æ¹æ¬¡ç¶æ await UpdateBatchStatusForReturn(batchNo); // 4. æ´æ°è®¢åæç»çå·²åé æ°é await UpdateOrderDetailAfterReturn(unfinishedLocks); _unitOfWorkManage.CommitTran(); return WebResponseContent.Instance.OK("åæ¹ååºæå"); } catch (Exception ex) private async Task RevertBatchAndOrderData(Dt_PickingRecord pickingRecord) { _unitOfWorkManage.RollbackTran(); _logger.LogError($"åæ¹ååºå¤±è´¥ - OrderNo: {orderNo}, BatchNo: {batchNo}, Error: {ex.Message}"); return WebResponseContent.Instance.Error($"åæ¹ååºå¤±è´¥ï¼{ex.Message}"); } // æ¢å¤æ¹æ¬¡å®ææ°é var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>() .FirstAsync(x => x.BatchNo == pickingRecord.BatchNo); batch.CompletedQuantity -= pickingRecord.PickQuantity; batch.BatchStatus = (int)BatchStatusEnum.æ§è¡ä¸; await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync(); // æ¢å¤è®¢åæç» var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>() .FirstAsync(x => x.Id == pickingRecord.OrderDetailId); orderDetail.OverOutQuantity -= pickingRecord.PickQuantity; orderDetail.AllocatedQuantity += pickingRecord.PickQuantity; await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync(); // éæ°æ£æ¥è®¢åç¶æ await CheckAndUpdateOrderStatus(pickingRecord.OrderNo); } private async Task ReleaseLockAndStock(Dt_OutStockLockInfo lockInfo) @@ -443,20 +609,30 @@ } // æ´æ°éå®è®°å½ç¶æä¸ºååº lockInfo.Status = (int)OutLockStockStatusEnum.ååºä¸; lockInfo.Status = (int)OutLockStockStatusEnum.å·²ååº; await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync(); } private async Task UpdateBatchStatusForReturn(string batchNo) private async Task UpdateBatchStatusForReturn(string batchNo, List<Dt_OutStockLockInfo> returnedLocks) { await _outboundBatchRepository.Db.Updateable<Dt_OutboundBatch>() .SetColumns(x => new Dt_OutboundBatch var batch = await _outboundBatchRepository.Db.Queryable<Dt_OutboundBatch>() .FirstAsync(x => x.BatchNo == batchNo); // 计ç®ååºæ°é var returnedQty = returnedLocks.Sum(x => x.AssignQuantity - x.PickedQty); batch.CompletedQuantity -= returnedQty; if (batch.CompletedQuantity <= 0) { BatchStatus = (int)BatchStatusEnum.å·²ååº, Operator = App.User.UserName }) .Where(x => x.BatchNo == batchNo) .ExecuteCommandAsync(); batch.BatchStatus = (int)BatchStatusEnum.å·²ååº; } else { batch.BatchStatus = (int)BatchStatusEnum.æ§è¡ä¸; } batch.Operator = App.User.UserName; await _outboundBatchRepository.Db.Updateable(batch).ExecuteCommandAsync(); } private async Task UpdateOrderDetailAfterReturn(List<Dt_OutStockLockInfo> returnedLocks) @@ -482,7 +658,7 @@ private async Task<string> GenerateNewBarcode() { var seq = await _dailySequenceService.GetNextSequenceAsync(); return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString()?.PadLeft(5, '0'); return "WSLOT" + DateTime.Now.ToString("yyyyMMdd") + seq.ToString().PadLeft(5, '0'); } private async Task RecordSplitHistory(Dt_OutStockLockInfo lockInfo, Dt_StockInfoDetail stockDetail,decimal splitQty, string newBarcode) @@ -503,12 +679,12 @@ await _splitPackageService.Db.Insertable(splitHistory).ExecuteCommandAsync(); } private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode, string batchNo) private async Task RecordPickingHistory(PickingResult result, string orderNo, string palletCode) { var pickingRecord = new Dt_PickingRecord { OrderNo = orderNo, // BatchNo = batchNo, // BatchNo = result.FinalLockInfo.BatchNo, OrderDetailId = result.FinalLockInfo.OrderDetailId, PalletCode = palletCode, Barcode = result.FinalLockInfo.CurrentBarcode, @@ -516,47 +692,60 @@ PickQuantity = result.ActualPickedQty, PickTime = DateTime.Now, Operator = App.User.UserName, OutStockLockId = result.FinalLockInfo.Id OutStockLockId = result.FinalLockInfo.Id, // IsCancelled = false }; await Db.Insertable(pickingRecord).ExecuteCommandAsync(); } private async Task UpdateOrderRelatedData(int orderDetailId, decimal pickedQty, string orderNo) { // æ´æ°è®¢åæç»çå·²åºåºæ°é await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>() .SetColumns(x => new Dt_OutboundOrderDetail { OverOutQuantity = x.OverOutQuantity + pickedQty, AllocatedQuantity = x.AllocatedQuantity - pickedQty }) .Where(x => x.Id == orderDetailId) .ExecuteCommandAsync(); // æ£æ¥è®¢åç¶æ await CheckAndUpdateOrderStatus(orderNo); } private async Task CheckAndUpdateOrderStatus(string orderNo) { var orderDetails = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>() .LeftJoin<Dt_OutboundOrder>((o, item) => o.OrderId == item.Id) .Where((o, item) => item.OrderNo == orderNo) .Select((o, item) => o) .LeftJoin<Dt_OutboundOrder>((detail, order) => detail.OrderId == order.Id) .Where((detail, order) => order.OrderNo == orderNo) .Select((detail, order) => detail) .ToListAsync(); bool allCompleted = orderDetails.All(x => x.OverOutQuantity >= x.NeedOutQuantity); if (allCompleted) { var orderStatus = allCompleted ? (int)OutOrderStatusEnum.åºåºå®æ : (int)OutOrderStatusEnum.åºåºä¸; await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>() .SetColumns(x => new Dt_OutboundOrder { OrderStatus = (int)OutOrderStatusEnum.åºåºå®æ }) .SetColumns(x => x.OrderStatus == orderStatus) .Where(x => x.OrderNo == orderNo) .ExecuteCommandAsync(); } #endregion #region DTOç±» public class PickingResult { public Dt_OutStockLockInfo FinalLockInfo { get; set; } public decimal ActualPickedQty { get; set; } } public class RevertPickingResult { public Dt_OutStockLockInfo LockInfo { get; set; } public Dt_StockInfoDetail StockDetail { get; set; } } public class SplitResultDto { public string NewBarcode { get; set; } } public class ValidationResult<T> { public bool IsValid { get; set; } public string ErrorMessage { get; set; } public T Data { get; set; } public static ValidationResult<T> Success(T data) => new ValidationResult<T> { IsValid = true, Data = data }; public static ValidationResult<T> Error(string message) => new ValidationResult<T> { IsValid = false, ErrorMessage = message }; } #endregion ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -66,6 +66,11 @@ var outboundOrder = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>() .First(x => x.Id == orderId); if (!CanReassignOrder(outboundOrder)) { throw new Exception($"订å{outboundOrder.OrderNo}ç¶æä¸å è®¸éæ°åé åºå"); } List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>(); List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>(); List<Dt_LocationInfo> locationInfos = new List<Dt_LocationInfo>(); @@ -79,7 +84,7 @@ BatchNo = x.Key.BatchNo, SupplyCode = x.Key.SupplyCode, Details = x.ToList(), TotalNeedQuantity = x.Sum(v => v.OrderQuantity - v.OverOutQuantity - v.LockQuantity - v.MoveQty) TotalNeedQuantity = CalculateReassignNeedQuantity(x.ToList()) }) .Where(x => x.TotalNeedQuantity > 0) .ToList(); @@ -128,6 +133,39 @@ return (outStocks, outboundOrderDetails, outStockLockInfos, locationInfos); } /// <summary> /// æ£æ¥è®¢åæ¯å¦å è®¸éæ°åé /// </summary> private bool CanReassignOrder(Dt_OutboundOrder outboundOrder) { // å è®¸éæ°åé çç¶æ var allowedStatus = new[] {OutOrderStatusEnum.æªå¼å§, OutOrderStatusEnum.åºåºä¸,OutOrderStatusEnum.é¨å宿}; return allowedStatus.Contains((OutOrderStatusEnum)outboundOrder.OrderStatus); } /// <summary> /// éæ°è®¡ç®éæ±æ°éï¼èèéæ°åé çåºæ¯ï¼ /// </summary> private decimal CalculateReassignNeedQuantity(List<Dt_OutboundOrderDetail> details) { decimal totalNeed = 0; foreach (var detail in details) { // å ³é®ä¿®æ¹ï¼éæ°åé æ¶ï¼åªèèæªåºåºçé¨åï¼å¿½ç¥é宿°é // å 为é宿°éå¨ååºæ¶å·²ç»è¢«éç½® decimal remainingNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.MoveQty; if (remainingNeed > 0) { totalNeed += remainingNeed; } } return totalNeed; } private (bool success, string message) DistributeLockQuantityByFIFO( List<Dt_OutboundOrderDetail> details, List<Dt_StockInfo> assignStocks, ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderService.cs
@@ -72,7 +72,7 @@ item.MoveQty = moveissueoStockResult.Quantity; } if (model.OrderType != InOrderTypeEnum.Allocat.ObjToInt() || model.OrderType != InOrderTypeEnum.InternalAllocat.ObjToInt()) if (model.OrderType != InOrderTypeEnum.AllocatOutbound.ObjToInt() || model.OrderType != InOrderTypeEnum.InternalAllocat.ObjToInt()) { model.OrderNo = CreateCodeByRule(nameof(RuleCodeEnum.OutboundOrderRule)); } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -295,6 +295,8 @@ //æ§è¡ååºæä½ await ExecuteReturnOperations(orderNo, palletCode, stockInfo, task, statusAnalysis); await ReleaseAllLocksForReallocation(orderNo, palletCode, statusAnalysis); _unitOfWorkManage.CommitTran(); @@ -1307,6 +1309,116 @@ await UpdateStockInfoStatus(stockInfo); } /// <summary> /// å®å ¨éæ¾éå®ï¼å è®¸éæ°åé åºå /// </summary> private async Task ReleaseAllLocksForReallocation(string orderNo, string palletCode, PalletStatusAnalysis analysis) { _logger.LogInformation($"å¼å§éæ¾éå®ä»¥ä¾¿éæ°åé - 订å: {orderNo}, æç: {palletCode}"); // 1. å¤çæªåæ£çåºåºéå®è®°å½ - å®å ¨éæ¾ if (analysis.HasRemainingLocks) { await ReleaseRemainingLocks(analysis.RemainingLocks); } // 2. å¤çå·²ååºçéå®è®°å½ - å 餿æ è®°ä¸ºæ æ await CleanupReturnedLocks(orderNo, palletCode); // 3. é置订åæç»çé宿°é await ResetOrderDetailLockQuantities(analysis); _logger.LogInformation($"éå®éæ¾å®æ - 订å: {orderNo}, æç: {palletCode}"); } /// <summary> /// éæ¾æªåæ£çéå®è®°å½ /// </summary> private async Task ReleaseRemainingLocks(List<Dt_OutStockLockInfo> remainingLocks) { var lockIds = remainingLocks.Select(x => x.Id).ToList(); // å°éå®è®°å½ç¶ææ¹ä¸º"已鿾"ï¼æè ç´æ¥å é¤ // æ è®°ä¸ºå·²éæ¾ await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>() .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.已鿾, // éè¦æ°å¢è¿ä¸ªç¶æ // ReleaseTime = DateTime.Now, Operator = App.User.UserName }) .Where(it => lockIds.Contains(it.Id)) .ExecuteCommandAsync(); // ç´æ¥å é¤ï¼æ´å½»åºï¼ // await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>() // .Where(it => lockIds.Contains(it.Id)) // .ExecuteCommandAsync(); _logger.LogInformation($"éæ¾{remainingLocks.Count}æ¡æªåæ£éå®è®°å½"); } /// <summary> /// æ¸ çå·²ååºçéå®è®°å½ /// </summary> private async Task CleanupReturnedLocks(string orderNo, string palletCode) { // æ¥æ¾ææç¶æä¸ºååºä¸çéå®è®°å½å¹¶éæ¾ var returnedLocks = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>() .Where(it => it.OrderNo == orderNo && it.PalletCode == palletCode && it.Status == (int)OutLockStockStatusEnum.ååºä¸) .ToListAsync(); if (returnedLocks.Any()) { var returnedLockIds = returnedLocks.Select(x => x.Id).ToList(); await _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>() .SetColumns(it => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.已鿾, //ReleaseTime = DateTime.Now, Operator = App.User.UserName }) .Where(it => returnedLockIds.Contains(it.Id)) .ExecuteCommandAsync(); _logger.LogInformation($"æ¸ ç{returnedLocks.Count}æ¡ååºä¸éå®è®°å½"); } } /// <summary> /// é置订åæç»çé宿°é /// </summary> private async Task ResetOrderDetailLockQuantities(PalletStatusAnalysis analysis) { // æ¶éææåå½±åç订åæç»ID var affectedOrderDetailIds = new HashSet<int>(); if (analysis.HasRemainingLocks) { foreach (var lockInfo in analysis.RemainingLocks) { affectedOrderDetailIds.Add(lockInfo.OrderDetailId); } } // éç½®è¿äºè®¢åæç»çé宿°é foreach (var orderDetailId in affectedOrderDetailIds) { await _outboundOrderDetailService.Db.Updateable<Dt_OutboundOrderDetail>() .SetColumns(it => new Dt_OutboundOrderDetail { LockQuantity = 0, // éç½®é宿°é OrderDetailStatus = OrderDetailStatusEnum.New.ObjToInt() // éç½®ç¶æä¸ºæ°å»º }) .Where(it => it.Id == orderDetailId) .ExecuteCommandAsync(); } _logger.LogInformation($"éç½®{affectedOrderDetailIds.Count}个订åæç»çé宿°é"); } private async Task HandleRemainingLocksReturn(List<Dt_OutStockLockInfo> remainingLocks, int stockId) { var lockIds = remainingLocks.Select(x => x.Id).ToList(); ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -384,6 +384,13 @@ return WebResponseContent.Instance.OK(); } public async Task<WebResponseContent> OutAllocateTaskCompleted(Dt_Task task) { _logger.LogInformation($"TaskService OutAllocateTaskCompleted: {task.TaskNum}"); return await OutboundTaskCompleted(task); } public async Task<WebResponseContent> OutboundTaskCompleted(Dt_Task task) { _logger.LogInformation($"TaskService OutboundTaskCompleted: {task.TaskNum}"); ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService_Outbound.cs
@@ -143,14 +143,22 @@ { throw new Exception("æªæ¾å°åºåºåæç»ä¿¡æ¯"); } if (outboundOrderDetails.FirstOrDefault(x => x.OrderDetailStatus > OrderDetailStatusEnum.New.ObjToInt() && x.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt()) != null) //if (outboundOrderDetails.FirstOrDefault(x => x.OrderDetailStatus > OrderDetailStatusEnum.New.ObjToInt() && x.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt()) != null) //{ // throw new Exception("æéåºåºåæç»åå¨åºåºä¸æå·²å®æ"); //} if (outboundOrderDetails.FirstOrDefault(x => x.OrderDetailStatus > OrderDetailStatusEnum.Outbound.ObjToInt() && x.OrderDetailStatus != OrderDetailStatusEnum.AssignOverPartial.ObjToInt()) != null) { throw new Exception("æéåºåºåæç»åå¨åºåºä¸æå·²å®æ"); throw new Exception("æéåºåºåæç»åå¨å·²å®æç¶æï¼æ æ³éæ°åé "); } List<Dt_StockInfo>? stockInfos = null; List<Dt_OutboundOrderDetail>? orderDetails = null; List<Dt_OutStockLockInfo>? outStockLockInfos = null; List<Dt_LocationInfo>? locationInfos = null; CleanupPreviousInvalidLocks(outboundOrderDetails); (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) result = _outboundOrderDetailService.AssignStockOutbound(outboundOrderDetails); if (result.Item1 != null && result.Item1.Count > 0) @@ -188,7 +196,31 @@ } return (tasks, stockInfos, orderDetails, outStockLockInfos, locationInfos); } /// <summary> /// æ¸ çä¹åçæ æéå®è®°å½ /// </summary> private void CleanupPreviousInvalidLocks(List<Dt_OutboundOrderDetail> orderDetails) { var orderIds = orderDetails.Select(x => x.OrderId).Distinct().ToList(); var orderNos = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>() .Where(x => orderIds.Contains(x.Id)) .Select(x => x.OrderNo) .ToList(); // æ¸ çç¶æä¸º"已鿾"æ"ååºä¸"çæ§éå®è®°å½ foreach (var orderNo in orderNos) { _outStockLockInfoService.Db.Updateable<Dt_OutStockLockInfo>() .SetColumns(x => new Dt_OutStockLockInfo { Status = (int)OutLockStockStatusEnum.已鿾 }) .Where(x => x.OrderNo == orderNo && (x.Status == (int)OutLockStockStatusEnum.ååºä¸ || x.Status == (int)OutLockStockStatusEnum.已鿾)) .ExecuteCommand(); } } /// <summary> /// çæåºåºä»»å¡åæ°æ®æ´æ°å°æ°æ®åº ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundBatchPickingController.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,23 @@ using Microsoft.AspNetCore.Mvc; using WIDESEA_Core.BaseController; using WIDESEA_IOutboundService; using WIDESEA_Model.Models; namespace WIDESEA_WMSServer.Controllers.Outbound { [Route("api/OutboundBatchPicking")] [ApiController] public class OutboundBatchPickingController : ApiBaseController<IOutboundBatchPickingService, Dt_PickingRecord> { private readonly ISplitPackageService _splitPackageService; private readonly IOutStockLockInfoService _outStockLockInfoService; public OutboundBatchPickingController(IOutboundBatchPickingService service, ISplitPackageService splitPackageService, IOutStockLockInfoService outStockLockInfoService) : base(service) { _splitPackageService = splitPackageService; _outStockLockInfoService = outStockLockInfoService; } } }