ÏîÄ¿´úÂë/WIDESEA_WMSClient/.claude/settings.local.json
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,12 @@ { "permissions": { "allow": [ "Bash(npm run serve:*)", "Bash(npm install:*)", "Bash(SET NODE_OPTIONS=--openssl-legacy-provider)", "Bash(export NODE_OPTIONS=--openssl-legacy-provider:*)", "Bash(node:*)", "Bash(npm --version:*)" ] } } ÏîÄ¿´úÂë/WIDESEA_WMSClient/.eslintrc.js
ÎļþÒÑɾ³ý ÏîÄ¿´úÂë/WIDESEA_WMSClient/README.md
@@ -39,3 +39,4 @@ "lint": "vue-cli-service lint" } SET NODE_OPTIONS=--openssl-legacy-provider && npm run serve ÏîÄ¿´úÂë/WIDESEA_WMSClient/package-lock.json
ÎļþÌ«´ó ÏîÄ¿´úÂë/WIDESEA_WMSClient/pnpm-lock.yaml
¶Ô±ÈÐÂÎļþ ÎļþÌ«´ó ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail copy.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,821 @@ <template> <div> <vol-box v-model="showDetialBox" :lazy="true" width="75%" :padding="15" title="åæ®æç»ä¿¡æ¯" > <div class="box-head"> <el-alert :closable="false" style="width: 100%"> <el-row> <el-col :span="16"> <span>å·²éä¸ {{ selection.length }} 项</span> </el-col> <el-col :span="8"> <el-link type="primary" size="small" v-if="isBatch === 0" style="float: right; height: 20px" @click="handleOpenPicking" >æ£é</el-link > <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" v-if="isBatch === 1" @click="handleOpenBatchPicking" >åæ¹æ£é</el-link > <el-link type="primary" size="small" v-if="isBatch === 0" style="float: right; height: 20px; margin-right: 10px" @click="outbound" >ç´æ¥åºåº</el-link > <el-link type="primary" size="small" v-if="isBatch === 1" style="float: right; height: 20px; margin-right: 10px" @click="outboundbatch" >åæ¹åºåº</el-link > <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" @click="getData" >å·æ°</el-link > </el-col> </el-row> </el-alert> </div> <div class="box-table" style="margin-top: 1%"> <el-table ref="singleTable" :data="tableData" style="width: 100%; height: 100%" highlight-current-row @current-change="handleCurrentChange" height="500px" @row-click="handleRowClick" @selection-change="handleSelectionChange" > <el-table-column type="selection" width="55"> </el-table-column> <el-table-column label="åºå·" type="index" fixed="left" width="55" align="center" ></el-table-column> <el-table-column v-for="(item, index) in tableColumns.filter((x) => !x.hidden)" :key="index" :prop="item.prop" :label="item.title" :width="item.width" align="center" > <template #default="scoped"> <div v-if="item.type == 'icon'"> <el-tooltip class="item" effect="dark" :content="item.title" placement="bottom" > <el-link type="primary" :disabled="getButtonEnable(item.prop, scoped.row)" @click="tableButtonClick(scoped.row, item)" > <i :class="item.icon" style="font-size: 22px"></i> </el-link> </el-tooltip> </div> <div v-else-if="item.type == 'tag'"> <el-tag size="small"> {{ getDictionary(scoped.row, item) }} </el-tag> </div> <div v-else> {{ scoped.row[item.prop] }} </div> </template> </el-table-column> </el-table> </div> </vol-box> <stock-select ref="child" @parentCall="parentCall"></stock-select> <selected-stock ref="selectedStock" @parentCall="parentCall" ></selected-stock> <NoStockOut ref="NoStockOut" @parentCall="parentCall"></NoStockOut> </div> </template> <script> import VolBox from "@/components/basic/VolBox.vue"; import VolForm from "@/components/basic/VolForm.vue"; import StockSelect from "./StockSelect.vue"; import SelectedStock from "./SelectedStock.vue"; import NoStockOut from "./NoStockOut.vue"; import { h, createVNode, render, reactive } from "vue"; import { ElDialog, ElForm, ElFormItem, ElSelect, ElOption, ElButton, ElInput, ElMessage, } from "element-plus"; export default { components: { VolBox, VolForm, StockSelect, SelectedStock, NoStockOut }, data() { return { row: null, isBatch: 0, showDetialBox: false, flag: false, currentRow: null, selection: [], tableData: [], mainBusinessType: null, // æ°å¢ï¼åå¨ä¸»åæ®çbusinessType tableColumns: [ { prop: "id", title: "Id", type: "int", width: 90, hidden: true, }, { prop: "orderId", title: "åºåºå主é®", type: "string", width: 90, hidden: true, }, { prop: "materielCode", title: "ç©æç¼å·", type: "string", width: 120, }, { prop: "materielName", title: "ç©æåç§°", type: "string", width: 150, }, { prop: "batchNo", title: "æ¹æ¬¡å·", type: "string", width: 90, }, { prop: "supplyCode", title: "ä¾åºåç¼å·", type: "string", width: 90, }, { prop: "orderQuantity", title: "åæ®æ°é", type: "string", width: 90, }, { prop: "lockQuantity", title: "é宿°é", type: "int", width: 90, }, { prop: "overOutQuantity", title: "å·²åºæ°é", type: "string", width: 90, }, { prop: "moveQty", title: "æªææ°é", type: "string", width: 90, }, { prop: "unit", title: "åä½", type: "string", width: 80, }, { prop: "orderDetailStatus", title: "订åæç»ç¶æ", type: "tag", width: 90, bindKey: "orderDetailStatusEnum", }, { prop: "assignStock", title: "æå®åºå", type: "icon", width: 90, hidden: true, // é»è®¤éè icon: "el-icon-s-grid", }, { prop: "viewDetail", title: "åºåºè¯¦ç»", type: "icon", width: 90, icon: "el-icon-s-operation", }, { prop: "creater", title: "å建人", type: "string", width: 90, }, { prop: "createDate", title: "å建æ¶é´", type: "datetime", width: 160, }, { prop: "modifier", title: "ä¿®æ¹äºº", type: "string", width: 100, }, { prop: "modifyDate", title: "ä¿®æ¹æ¶é´", type: "datetime", width: 160, }, { prop: "remark", title: "夿³¨", type: "string", }, ], paginations: { sort: "id", order: "desc", Foots: "", total: 0, sizes: [30, 60, 100, 120], size: 30, Wheres: [], page: 1, rows: 30, }, searchFormOptions: [ [ { title: "åæ®ç¼å·", field: "allocation_code", type: "like", }, { title: "åæ®ç±»å", field: "allocation_type", type: "select", dataKey: "OrderType", data: [], }, { title: "åæ®ç¶æ", field: "allocation_state", type: "select", dataKey: "OrderState", data: [], }, ], ], searchFormFields: { allocation_code: "", allocation_type: "", allocation_state: "", }, dictionaryList: null, }; }, methods: { toggleAssignStockColumn() { const assignStockColumn = this.tableColumns.find( (item) => item.prop === "assignStock" ); if (assignStockColumn) { // businessType为22æ¶æ¾ç¤ºï¼å¦åéè assignStockColumn.hidden = this.mainBusinessType !=='22'; } }, open(row) { this.row = row; this.showDetialBox = true; console.log("ä¸»åæ®æ°æ®ï¼", this.row); this.isBatch = row.isBatch; this.mainBusinessType = row.businessType; this.getDictionaryData(); this.getData(); this.toggleAssignStockColumn(); }, getData() { var wheres = [{ name: "orderId", value: this.row.id }]; var param = { page: this.paginations.page, rows: this.paginations.rows, sort: this.paginations.sort, order: this.paginations.order, wheres: JSON.stringify(wheres), }; this.http .post("api/OutboundOrderDetail/GetPageData", param, "æ¥è¯¢ä¸") .then((x) => { this.tableData = x.rows; this.toggleAssignStockColumn(); // æ°æ®å è½½åéæ°ç¡®è®¤åæ¾é }); }, tableButtonClick(row, column) { if (column.prop == "assignStock") { this.$refs.child.open(row); } else if (column.prop == "NoStockOut") { this.$refs.NoStockOut.open(row); } else { this.$refs.selectedStock.open(row); } }, lockstocks() { if (this.selection.length === 0) { return this.$message.error("è¯·éæ©åæ®æç»"); } var keys = this.selection.map((item) => item.id); this.http .post("api/OutboundOrderDetail/LockOutboundStocks", keys, "æ°æ®å¤çä¸") .then((x) => { if (!x.status) return this.$message.error(x.message); this.$message.success("æä½æå"); this.showDetialBox = false; this.$emit("parentCall", ($vue) => { $vue.getData(); }); }); }, handleOpenPicking() { this.$router.push({ path: "/outbound/picking", 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("è¯·éæ©åæ®æç»"); } const platformOptions = [ { label: "ç«å°2", value: "2-1" }, { label: "ç«å°3", value: "3-1" }, ]; const mountNode = document.createElement("div"); document.body.appendChild(mountNode); const formData = reactive({ selectedPlatform: platformOptions[0].value, }); 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" }, ], }, 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("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; } const keys = this.selection.map((item) => item.id); const requestParams = { taskIds: keys, outboundPlatform: formData.selectedPlatform, }; this.http .post( "api/Task/GenerateOutboundTasks", 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", }, }, "ç¡®å®åºåº"), ]), ]), } ); 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); const formData = reactive({ selectedPlatform: platformOptions[0].value, outboundDecimal: "", }); 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) => { 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", precision: 2, min: 0.01, }), ]), 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; } const keys = this.selection.map((item) => item.id); const requestParams = { orderDetailId: keys[0], outboundPlatform: formData.selectedPlatform, batchQuantity: formData.outboundDecimal, }; this.http .post( "api/Task/GenerateOutboundBatchTasks", 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", }, }, "ç¡®å®åæ¹åºåº"), ]), ]), } ); vnode.appContext = this.$.appContext; render(vnode, mountNode); }, setCurrent(row) { this.$refs.singleTable.setCurrentRow(row); }, handleCurrentChange(val) { this.currentRow = val; }, getButtonEnable(propName, row) { if (propName == "assignStock") { if ( row.orderDetailStatus !== 0 && row.orderDetailStatus !== 60 && row.orderDetailStatus !== 70 && row.orderDetailStatus !== 80 ) { return true; } else { return false; } } return false; }, parentCall(fun) { if (typeof fun != "function") { return console.log("æ©å±ç»ä»¶éè¦ä¼ å ¥ä¸ä¸ªåè°æ¹æ³æè½è·åç¶çº§Vue对象"); } fun(this); }, handleRowClick(row) { this.$refs.singleTable.toggleRowSelection(row); }, handleSelectionChange(val) { this.selection = val; }, getDictionaryData() { if (this.dictionaryList) { return; } var param = []; this.tableColumns.forEach((x) => { if (x.type == "tag" && x.bindKey != "") { param.push(x.bindKey); } }); this.http .post("api/Sys_Dictionary/GetVueDictionary", param, "æ¥è¯¢ä¸") .then((x) => { if (x.length > 0) { this.dictionaryList = x; } }); }, getDictionary(row, column) { if (this.dictionaryList) { var item = this.dictionaryList.find((x) => x.dicNo == column.bindKey); if (item) { var dicItem = item.data.find((x) => x.key == row[column.prop]); if (dicItem) { return dicItem.value; } else { return row[column.prop]; } } else { return row[column.prop]; } } return row[column.prop]; }, }, }; </script> <style scoped> .text-button { border: 0px; } </style> <style> .text-button:hover { background-color: #f0f9eb !important; } .el-table .warning-row { background: oldlace; } .box-table .el-table tbody tr:hover > td { background-color: #d8e0d4 !important; } .box-table .el-table tbody tr.current-row > td { background-color: #f0f9eb !important; } .el-table .success-row { background: #f0f9eb; } .box-table .el-table { border: 1px solid #ebeef5; } </style> ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue
@@ -1,12 +1,6 @@ <template> <div> <vol-box v-model="showDetialBox" :lazy="true" width="75%" :padding="15" title="åæ®æç»ä¿¡æ¯" > <vol-box v-model="showDetialBox" :lazy="true" width="75%" :padding="15" title="åæ®æç»ä¿¡æ¯"> <div class="box-head"> <el-alert :closable="false" style="width: 100%"> <el-row> @@ -14,89 +8,33 @@ <span>å·²éä¸ {{ selection.length }} 项</span> </el-col> <el-col :span="8"> <el-link type="primary" size="small" v-if="isBatch === 0" style="float: right; height: 20px" @click="handleOpenPicking" >æ£é</el-link > <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" v-if="isBatch === 1" @click="handleOpenBatchPicking" >åæ¹æ£é</el-link > <el-link type="primary" size="small" v-if="isBatch === 0" style="float: right; height: 20px; margin-right: 10px" @click="outbound" >ç´æ¥åºåº</el-link > <el-link type="primary" size="small" v-if="isBatch === 1" style="float: right; height: 20px; margin-right: 10px" @click="outboundbatch" >åæ¹åºåº</el-link > <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" @click="getData" >å·æ°</el-link > <el-link type="primary" size="small" v-if="isBatch === 0" style="float: right; height: 20px" @click="handleOpenPicking">æ£é</el-link> <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" v-if="isBatch === 1" @click="handleOpenBatchPicking">åæ¹æ£é</el-link> <el-link type="primary" size="small" v-if="isBatch === 0" style="float: right; height: 20px; margin-right: 10px" @click="outbound">ç´æ¥åºåº</el-link> <el-link type="primary" size="small" v-if="isBatch === 1" style="float: right; height: 20px; margin-right: 10px" @click="outboundbatch">åæ¹åºåº</el-link> <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px" @click="getData">å·æ°</el-link> </el-col> </el-row> </el-alert> </div> <div class="box-table" style="margin-top: 1%"> <el-table ref="singleTable" :data="tableData" style="width: 100%; height: 100%" highlight-current-row @current-change="handleCurrentChange" height="500px" @row-click="handleRowClick" @selection-change="handleSelectionChange" > <el-table ref="singleTable" :data="tableData" style="width: 100%; height: 100%" highlight-current-row @current-change="handleCurrentChange" height="500px" @row-click="handleRowClick" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55"> </el-table-column> <el-table-column label="åºå·" type="index" fixed="left" width="55" align="center" ></el-table-column> <el-table-column v-for="(item, index) in tableColumns.filter((x) => !x.hidden)" :key="index" :prop="item.prop" :label="item.title" :width="item.width" align="center" > <el-table-column label="åºå·" type="index" fixed="left" width="55" align="center"></el-table-column> <el-table-column v-for="(item, index) in tableColumns.filter((x) => !x.hidden)" :key="index" :prop="item.prop" :label="item.title" :width="item.width" align="center"> <template #default="scoped"> <div v-if="item.type == 'icon'"> <el-tooltip class="item" effect="dark" :content="item.title" placement="bottom" > <el-link type="primary" :disabled="getButtonEnable(item.prop, scoped.row)" @click="tableButtonClick(scoped.row, item)" > <el-tooltip class="item" effect="dark" :content="item.title" placement="bottom"> <el-link type="primary" :disabled="getButtonEnable(item.prop, scoped.row)" @click="tableButtonClick(scoped.row, item)"> <i :class="item.icon" style="font-size: 22px"></i> </el-link> </el-tooltip> @@ -115,10 +53,7 @@ </div> </vol-box> <stock-select ref="child" @parentCall="parentCall"></stock-select> <selected-stock ref="selectedStock" @parentCall="parentCall" ></selected-stock> <selected-stock ref="selectedStock" @parentCall="parentCall"></selected-stock> <NoStockOut ref="NoStockOut" @parentCall="parentCall"></NoStockOut> </div> </template> @@ -379,7 +314,7 @@ }, handleOpenPicking() { this.$router.push({ path: "/outbound/picking", path: "/outbound/outPicking", query: { orderId: this.row.id, orderNo: this.row.orderNo }, }); }, @@ -494,13 +429,16 @@ const keys = this.selection.map((item) => item.id); const requestParams = { taskIds: keys, outboundPlatform: formData.selectedPlatform, detailIds: keys, outboundTargetLocation: formData.selectedPlatform, outboundQuantity: 1, operator: "", orderNo: this.row.orderNo, }; this.http .post( "api/Task/GenerateOutboundTasks", "api/Outbound/ProcessPickingOutbound", requestParams, "æ°æ®å¤çä¸" ) @@ -514,9 +452,9 @@ render(null, mountNode); document.body.removeChild(mountNode); }) .catch(() => { ElMessage.error("请æ±å¤±è´¥ï¼è¯·ç¨åéè¯"); }); // .catch(() => { // ElMessage.error("请æ±å¤±è´¥ï¼è¯·ç¨åéè¯"); // }); }, style: { borderRadius: "4px", ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/router/viewGird.js
@@ -81,6 +81,12 @@ meta: { title: 'æ£é确认', keepAlive: false } }, { path: '/outbound/outPicking', name: 'outPicking', component: () => import('@/views/outbound/outPicking.vue'), meta: { title: 'æ£é确认', keepAlive: false } }, { path: '/outbound/batchpicking', name: 'BatchPickingConfirm', component: () => import('@/views/outbound/BatchPickingConfirm.vue'), ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/outPicking.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,798 @@ <template> <div class="picking-container"> <!-- é¡¶é¨è®¢åä¿¡æ¯ --> <el-card class="order-info-card" shadow="never"> <div class="order-header"> <div class="order-title"> <i class="el-icon-document"></i> <span class="order-label">订åå·ï¼</span> <span class="order-value">{{ orderNo }}</span> </div> <div class="order-status" v-if="orderInfo"> <el-tag :type="getStatusType(orderInfo.status)" size="medium"> {{ orderInfo.statusName || 'è¿è¡ä¸' }} </el-tag> </div> </div> </el-card> <!-- æ«ç æä½åºå --> <el-card class="scan-section-card" shadow="never"> <div class="scan-section"> <el-alert title="è¯·ä½¿ç¨æ«ç æªæ«æï¼æ¯æå车èªå¨ç¡®è®¤" type="info" :closable="false" show-icon class="scan-alert"> <template #default> <span>1. è¯·å æ«ææçç â 2. åæ«æç©ææ¡ç </span> </template> </el-alert> <el-form :model="scanForm" :rules="scanRules" ref="scanFormRef" class="scan-form"> <el-row :gutter="20"> <el-col :span="8"> <el-form-item label="æçç " prop="palletCode"> <el-input ref="palletInput" v-model="scanForm.palletCode" placeholder="è¯·æ«ææçç " size="large" clearable @keyup.enter="handlePalletScan"> <template #prefix> <i class="el-icon-box"></i> </template> </el-input> </el-form-item> </el-col> <el-col :span="8"> <el-form-item label="ç©ææ¡ç " prop="materialBarcode"> <el-input ref="materialInput" v-model="scanForm.materialBarcode" placeholder="è¯·æ«æç©ææ¡ç " size="large" clearable :disabled="unpickedData.length <= 0" @keyup.enter="handleMaterialScan"> <template #prefix> <i class="el-icon-s-grid"></i> </template> </el-input> </el-form-item> </el-col> <el-col :span="8"> <el-form-item class="button-form-item"> <div class="action-buttons"> <el-button type="primary" size="large" @click="handleConfirmPick" :loading="confirmLoading" :disabled="!canConfirm"> <i class="el-icon-check"></i> 确认æ£é </el-button> <el-button type="warning" size="large" @click="handleEmptyBox" :disabled="!scanForm.palletCode"> <i class="el-icon-delete"></i> å空箱 </el-button> <el-button type="success" size="large" @click="handleReturnToWarehouse" :disabled="!scanForm.palletCode"> <i class="el-icon-refresh-left"></i> ååº </el-button> </div> </el-form-item> </el-col> </el-row> </el-form> </div> </el-card> <!-- æ°æ®å表åºå --> <div class="tables-section"> <el-row :gutter="20"> <!-- æªæ£éå表 --> <el-col :span="12"> <el-card class="table-card" shadow="never"> <template #header> <div class="card-header"> <span class="card-title"> <i class="el-icon-time"></i> æªæ£éå表 </span> <el-badge :value="unpickedCount" class="badge-item" type="warning"> <el-button size="small" @click="refreshUnpickedTable" icon="el-icon-refresh"> å·æ° </el-button> </el-badge> </div> </template> <el-table ref="unpickedTable" :data="unpickedData" height="400" stripe highlight-current-row> <el-table-column type="index" label="åºå·" width="60" align="center" /> <el-table-column prop="materielCode" label="ç©æç¼ç " width="120" show-overflow-tooltip /> <el-table-column prop="materielName" label="ç©æåç§°" width="80" show-overflow-tooltip /> <el-table-column prop="batchNo" label="æ¹æ¬¡å·" width="100" /> <el-table-column prop="assignQuantity" label="忣æ°é" width="80" align="right"> <template #default="scope"> <el-text type="danger">{{ scope.row.assignQuantity }}</el-text> </template> </el-table-column> <el-table-column prop="sortedQuantity" label="已忣æ°é" width="120" align="right"> <template #default="scope"> <el-text type="danger">{{ scope.row.sortedQuantity }}</el-text> </template> </el-table-column> <el-table-column prop="unit" label="åä½" width="60" /> <el-table-column prop="locationCode" label="åºä½" /> <!-- <el-table-column label="æä½" width="80" align="center"> <template #default="scope"> <el-button type="text" size="small" @click="quickPick(scope.row)" :disabled="!scanForm.palletCode"> æ£é </el-button> </template> </el-table-column> --> </el-table> <div class="table-footer"> <el-descriptions :column="2" size="small"> <el-descriptions-item label="æ»æ¡æ°"> <el-text type="info">{{ unpickedTotal }}</el-text> </el-descriptions-item> <el-descriptions-item label="æ»æ°é"> <el-text type="warning">{{ unpickedQuantity }}</el-text> </el-descriptions-item> </el-descriptions> </div> </el-card> </el-col> <!-- å·²æ£éå表 --> <el-col :span="12"> <el-card class="table-card" shadow="never"> <template #header> <div class="card-header"> <span class="card-title"> <i class="el-icon-circle-check"></i> å·²æ£éå表 </span> <el-badge :value="pickedCount" class="badge-item" type="success"> <el-button size="small" @click="refreshPickedTable" icon="el-icon-refresh"> å·æ° </el-button> </el-badge> </div> </template> <el-table ref="pickedTable" :data="pickedData" height="400" stripe> <el-table-column type="index" label="åºå·" width="60" align="center" /> <el-table-column prop="materielCode" label="ç©æç¼ç " width="120" /> <el-table-column prop="materielName" label="ç©æåç§°" show-overflow-tooltip /> <el-table-column prop="batchNo" label="æ¹æ¬¡å·" width="100" /> <el-table-column prop="changeQuantity" label="æ£éæ°é" width="80" align="right"> <template #default="scope"> <el-text type="success">{{ 0 - scope.row.changeQuantity }}</el-text> </template> </el-table-column> <el-table-column prop="changeQuantity" label="ååºåé" width="80" align="right"> <template #default="scope"> <el-text type="success">{{ scope.row.beforeQuantity }}</el-text> </template> </el-table-column> <el-table-column prop="changeQuantity" label="æ£éååºåé" width="80" align="right"> <template #default="scope"> <el-text type="success">{{ scope.row.afterQuantity }}</el-text> </template> </el-table-column> <el-table-column prop="unit" label="åä½" width="60" /> <el-table-column prop="palletCode" label="æçç " width="100" /> <el-table-column prop="createDate" label="æ£éæ¶é´" width="160" /> <el-table-column prop="originalBarcode" label="åç©æç " width="160" /> <el-table-column prop="newBarcode" label="æ°ç©æç " width="160" /> <!-- <el-table-column label="æä½" width="80" align="center"> <template #default="scope"> <el-button type="text" size="small" @click="undoPick(scope.row)"> æ¤é </el-button> </template> </el-table-column> --> </el-table> <div class="table-footer"> <el-descriptions :column="2" size="small"> <el-descriptions-item label="æ»æ¡æ°"> <el-text type="info">{{ pickedTotal }}</el-text> </el-descriptions-item> <el-descriptions-item label="æ»æ°é"> <el-text type="success">{{ pickedQuantity }}</el-text> </el-descriptions-item> </el-descriptions> </div> </el-card> </el-col> </el-row> </div> <!-- ç¡®è®¤å¯¹è¯æ¡ --> <el-dialog v-model="confirmDialogVisible" title="æä½ç¡®è®¤" width="400px" :before-close="handleDialogClose"> <div class="confirm-content"> <p>{{ confirmMessage }}</p> </div> <template #footer> <span class="dialog-footer"> <el-button @click="confirmDialogVisible = false">åæ¶</el-button> <el-button type="primary" @click="executeConfirm" :loading="executeLoading"> ç¡®å® </el-button> </span> </template> </el-dialog> </div> </template> <script> export default { name: 'OutPicking', data() { return { orderNo: '', orderInfo: null, scanForm: { palletCode: '', materialBarcode: '' }, scanRules: { palletCode: [ { required: true, message: 'è¯·æ«ææçç ', trigger: 'blur' } ], materialBarcode: [ { required: true, message: 'è¯·æ«æç©ææ¡ç ', trigger: 'blur' } ] }, unpickedData: [], pickedData: [], unpickedCount: 0, unpickedTotal: 0, unpickedQuantity: 0, pickedCount: 0, pickedTotal: 0, pickedQuantity: 0, confirmLoading: false, confirmDialogVisible: false, confirmMessage: '', currentAction: null, executeLoading: false } }, computed: { canConfirm() { return this.scanForm.palletCode && this.scanForm.materialBarcode } }, mounted() { this.initPage() }, methods: { initPage() { // ä»è·¯ç±åæ°è·å订åå· this.orderNo = this.$route.query.orderNo || '' if (!this.orderNo) { this.$message.error('订åå·ä¸è½ä¸ºç©º') this.$router.back() return } // èªå¨èç¦å°æçç è¾å ¥æ¡ this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus() } }) }, loadPalletData() { if (!this.scanForm.palletCode) { this.unpickedData = [] return } try { this.loadUnpickedData(); this.loadPickedData(); } catch (error) { console.error('å è½½æçæ°æ®å¤±è´¥:', error) this.unpickedData = [] } }, loadUnpickedData() { try { this.http.post(`/api/Outbound/QueryPickingTasks?orderNo=${this.orderNo}&palletCode=${this.scanForm.palletCode}`, {}).then(response => { if (response.status) { if (response.data.length > 0) { this.unpickedData = response.data this.calculateUnpickedStats() // èªå¨èç¦å°ç©ææ¡ç è¾å ¥æ¡ this.$nextTick(() => { if (this.$refs.materialInput) { this.$refs.materialInput.focus() } }) } else { this.$message.warning('该æçæ æªæ£éä»»å¡') this.unpickedData = [] } } else { this.$message.error(response.message || 'è·åæçæ°æ®å¤±è´¥') this.unpickedData = [] } } ) } catch (error) { console.error('å è½½æªæ£éæ°æ®å¤±è´¥:', error) } }, loadPickedData() { try { this.http.post(`/api/Outbound/QueryPickedList?orderNo=${this.orderNo}&palletCode=${this.scanForm.palletCode}`, {}).then(response => { if (response.status) { if (response.data.length > 0) { this.pickedData = response.data this.calculatePickedStats() } else { this.$message.warning('该æçæ æªæ£éä»»å¡') this.unpickedData = [] } } else { this.$message.error(response.message || 'è·åæçæ°æ®å¤±è´¥') this.unpickedData = [] } } ) } catch (error) { console.error('å 载已æ£éæ°æ®å¤±è´¥:', error) } }, // è®¡ç®æªæ£é calculateUnpickedStats() { // æªæ£éæ¡ç®æ°é this.unpickedCount = this.unpickedData.length // è®¡ç®æªæ£éçæ»æ°éï¼åæ£æ°é - 已忣æ°éï¼ this.unpickedQuantity = this.unpickedData.reduce((sum, item) => { const assignQty = item.assignQuantity || 0 const sortedQty = item.sortedQuantity || 0 const remainingQty = Math.max(0, assignQty - sortedQty) return sum + remainingQty }, 0) // æ»æ¡ç®æ°ï¼ä¸æ¡ç®æ°éç¸åï¼è¿éä¿çåéä¸è´æ§ï¼ this.unpickedTotal = this.unpickedCount }, // 计ç®å·²æ£é calculatePickedStats() { // å·²æ£éæ¡ç®æ°é this.pickedCount = this.pickedData.length // 计ç®å·²æ£éçæ»æ°é this.pickedQuantity = 0 - this.pickedData.reduce((sum, item) => { return (sum + item.changeQuantity) }, 0) // æ»æ¡ç®æ°ï¼ä¸æ¡ç®æ°éç¸åï¼ this.pickedTotal = this.pickedCount }, handlePalletScan() { if (this.scanForm.palletCode) { // this.$message.success(`æçç : ${this.scanForm.palletCode}`) this.loadPalletData() } }, handleMaterialScan() { if (!this.scanForm.palletCode) { this.$message.warning('è¯·å æ«ææçç ') this.$refs.palletInput.focus() return } if (!this.scanForm.materialBarcode) { this.$message.warning('è¯·æ«æç©ææ¡ç ') return } // èªå¨æ§è¡ç¡®è®¤æ£é this.handleConfirmPick() }, handleConfirmPick() { if (!this.scanForm.palletCode || !this.scanForm.materialBarcode) { this.$message.warning('è¯·å æ«ææçç åç©ææ¡ç ') return } this.confirmLoading = true try { this.http.post('/api/Outbound/CompleteOutboundWithBarcode', { orderNo: this.orderNo, palletCode: this.scanForm.palletCode, barcode: this.scanForm.materialBarcode, operator: this.getUserName() }).then(response => { if (response.status) { this.$message.success('æ£é确认æå') this.resetMaterialBarcode() // this.loadUnpickedData() // this.loadPickedData() this.loadPalletData() } else { this.$message.error(response.message || 'æ£é确认失败') } }) } catch (error) { console.error('æ£é确认失败:', error) this.$message.error('æ£é确认失败') } finally { this.confirmLoading = false } }, handleEmptyBox() { if (!this.scanForm.palletCode) { this.$message.warning('è¯·å æ«ææçç ') return } this.confirmMessage = `ç¡®å®è¦å空æç ${this.scanForm.palletCode} åï¼` this.currentAction = 'emptyBox' this.confirmDialogVisible = true }, handleReturnToWarehouse() { if (!this.scanForm.palletCode) { this.$message.warning('è¯·å æ«ææçç ') return } this.confirmMessage = `ç¡®å®è¦å°æç ${this.scanForm.palletCode} ååºåï¼` this.currentAction = 'returnToWarehouse' this.confirmDialogVisible = true }, executeConfirm() { this.executeLoading = true try { let apiUrl = '' let params = { orderNo: this.orderNo, palletCode: this.scanForm.palletCode } if (this.currentAction === 'emptyBox') { apiUrl = '/api/Outbound/EmptyBox' } else if (this.currentAction === 'returnToWarehouse') { apiUrl = '/api/Outbound/ReturnToWarehouse' } this.http.post(apiUrl, params).then(response => { if (response.status) { this.$message.success('æä½æå') this.confirmDialogVisible = false this.resetForm() // this.loadUnpickedData() // this.loadPickedData() } else { this.$message.error(response.message || 'æä½å¤±è´¥') } }) } catch (error) { console.error('æä½å¤±è´¥:', error) this.$message.error('æä½å¤±è´¥') } finally { this.executeLoading = false } }, handleDialogClose() { if (!this.executeLoading) { this.confirmDialogVisible = false } }, quickPick(row) { this.scanForm.materialBarcode = row.materielCode this.handleConfirmPick() }, undoPick(row) { try { this.$confirm('ç¡®å®è¦æ¤éè¿æ¡æ£éè®°å½åï¼', 'æç¤º', { type: 'warning' }) const response = this.http.post('/api/Outbound/UndoPicking', { id: row.id }).then(response => { if (response.status) { this.$message.success('æ¤éæå') this.loadUnpickedData() this.loadPickedData() } else { this.$message.error(response.message || 'æ¤é失败') } }) } catch (error) { if (error !== 'cancel') { console.error('æ¤é失败:', error) this.$message.error('æ¤é失败') } } }, // handleUnpickedRowClick(row) { // // ç¹å»æªæ£éè¡æ¶èªå¨å¡«å ç©ææ¡ç // this.scanForm.materialBarcode = row.materielCode // }, refreshUnpickedTable() { if (this.scanForm.palletCode) { this.loadPalletData() } }, refreshPickedTable() { this.loadPickedData() }, resetMaterialBarcode() { this.scanForm.materialBarcode = '' this.$nextTick(() => { if (this.$refs.materialInput) { this.$refs.materialInput.focus() } }) }, resetForm() { this.scanForm.palletCode = '' this.scanForm.materialBarcode = '' this.unpickedData = [] this.$nextTick(() => { if (this.$refs.palletInput) { this.$refs.palletInput.focus() } }) }, getUserName() { // å°è¯ä» Vuex store è·åç¨æ·å if (this.$store && this.$store.state && this.$store.state.userInfo) { return this.$store.state.userInfo.userName || this.$store.state.userInfo.username || 'æªç»å½ç¨æ·' } // å°è¯ä» localStorage è·å try { const userInfo = localStorage.getItem('user') if (userInfo) { const user = JSON.parse(userInfo) return user.userName || user.username || 'æªç»å½ç¨æ·' } } catch (error) { console.error('è·åç¨æ·ä¿¡æ¯å¤±è´¥:', error) } return 'æªç»å½ç¨æ·' }, getStatusType(status) { const statusMap = { 0: 'info', // å¾ å¤ç 10: 'warning', // è¿è¡ä¸ 20: 'primary', // æ£éä¸ 30: 'success', // 已宿 40: 'danger' // å¼å¸¸ } return statusMap[status] || 'info' } } } </script> <style scoped> .picking-container { padding: 20px; background-color: #f5f5f5; min-height: 100vh; } /* 订åä¿¡æ¯å¡ç */ .order-info-card { margin-bottom: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; } .order-header { display: flex; justify-content: space-between; align-items: center; } .order-title { display: flex; align-items: center; font-size: 18px; font-weight: bold; } .order-title i { margin-right: 10px; font-size: 24px; } .order-label { margin-left: 10px; opacity: 0.9; } .order-value { font-size: 20px; font-weight: bold; letter-spacing: 1px; } /* æ«ç åºå */ .scan-section-card { margin-bottom: 20px; } .scan-section { padding: 10px 0; } .scan-alert { margin-bottom: 20px; } .scan-form { margin-top: 20px; } .button-form-item { margin-bottom: 0; } .button-form-item .el-form-item__content { line-height: normal; } .action-buttons { display: flex; gap: 8px; width: 100%; height: 40px; } .action-buttons .el-button { flex: 1; height: 40px; font-weight: bold; border-radius: 6px; margin: 0; } /* è¡¨æ ¼åºå */ .tables-section { margin-top: 20px; } .table-card { height: 600px; display: flex; flex-direction: column; } .card-header { display: flex; justify-content: space-between; align-items: center; } .card-title { display: flex; align-items: center; font-weight: bold; font-size: 16px; } .card-title i { margin-right: 8px; color: #409EFF; } .badge-item { margin-left: 10px; } .table-footer { margin-top: 10px; padding-top: 10px; border-top: 1px solid #ebeef5; } /* è¡¨æ ¼è¡æ ·å¼ */ .el-table tbody tr:hover>td { background-color: #f0f9ff !important; } .el-table tbody tr.current-row>td { background-color: #e1f3ff !important; } /* å¯¹è¯æ¡æ ·å¼ */ .confirm-content { padding: 20px 0; text-align: center; } .confirm-content p { font-size: 16px; color: #606266; } .dialog-footer { text-align: center; } /* ååºå¼è®¾è®¡ */ @media (max-width: 1200px) { .action-buttons .el-button { font-size: 14px; } } /* Element UI ç»ä»¶æ ·å¼è¦ç */ ::v-deep .el-card__header { padding: 18px 20px; border-bottom: 1px solid #ebeef5; } ::v-deep .el-input__inner { border-radius: 6px; } ::v-deep .el-button--primary { background: linear-gradient(135deg, #409EFF 0%, #3a8ee6 100%); border: none; } ::v-deep .el-button--warning { background: linear-gradient(135deg, #E6A23C 0%, #d9971a 100%); border: none; } ::v-deep .el-button--success { background: linear-gradient(135deg, #67C23A 0%, #5daf34 100%); border: none; } /* 徿 æ ·å¼ */ .el-icon-document, .el-icon-box, .el-icon-s-grid, .el-icon-check, .el-icon-delete, .el-icon-refresh-left, .el-icon-time, .el-icon-circle-check { font-size: 18px; } /* æè¿°åè¡¨æ ·å¼ */ ::v-deep .el-descriptions__label { font-weight: bold; color: #909399; } ::v-deep .el-descriptions__content { color: #606266; } </style> ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/CodeChunks.dbBinary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.1204.46620/SemanticSymbols.dbBinary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/MaterielOutboundCalculationDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,76 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WIDESEA_Model.Models; namespace WIDESEA_DTO.CalcOut { /// <summary> /// æç©æçåºåºè®¡ç®ç»æ /// </summary> public class MaterielOutboundCalculationDTO { /// <summary> /// ç©æç¼å· /// </summary> public string MaterielCode { get; set; } /// <summary> /// ç©æåç§° /// </summary> public string MaterielName { get; set; } /// <summary> /// æ¹æ¬¡å· /// </summary> public string BatchNo { get; set; } /// <summary> /// ä¾åºåç¼å·ï¼å¯è½ä¸ºç©ºï¼ /// </summary> public string SupplyCode { get; set; } /// <summary> /// ä»åºç¼å·ï¼å¯è½ä¸ºç©ºï¼ /// </summary> public string WarehouseCode { get; set; } /// <summary> /// è¯¥ç©æçæ»åæ®æ°é /// </summary> public decimal TotalOrderQuantity { get; set; } /// <summary> /// è¯¥ç©æçæ»å·²åºæ°é /// </summary> public decimal TotalOverOutQuantity { get; set; } /// <summary> /// è¯¥ç©æçå·²åé æ°é /// </summary> public decimal AssignedQuantity { get; set; } /// <summary> /// è¯¥ç©æçå¾ åé æ°éï¼å·²å廿ªææ°éï¼ /// </summary> public decimal UnallocatedQuantity { get; set; } /// <summary> /// æªææ°é /// </summary> public decimal MovedQuantity { get; set; } /// <summary> /// è¯¥ç©æå¯¹åºçåºåºæç»å表 /// </summary> public List<Dt_OutboundOrderDetail> Details { get; set; } = new List<Dt_OutboundOrderDetail>(); /// <summary> /// /// </summary> public List<Dt_OutStockLockInfo> OutStockLockInfos { get; set; } = new List<Dt_OutStockLockInfo>(); } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/OutboundCalculationDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,50 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WIDESEA_Model.Models; namespace WIDESEA_DTO.CalcOut { /// <summary> /// åºåºè®¡ç®ç»æDTO /// </summary> public class OutboundCalculationDTO { /// <summary> /// æ¯å¦å 许åºåº /// </summary> public bool CanOutbound { get; set; } /// <summary> /// éè¯¯æ¶æ¯ /// </summary> public string ErrorMessage { get; set; } /// <summary> /// åºåºåä¿¡æ¯ /// </summary> public Dt_OutboundOrder OutboundOrder { get; set; } /// <summary> /// éæ©çåºåºæç»å表 /// </summary> public List<Dt_OutboundOrderDetail> SelectedDetails { get; set; } /// <summary> /// æç©æåç»çåºåºè®¡ç®ç»æ /// </summary> public List<MaterielOutboundCalculationDTO> MaterielCalculations { get; set; } = new List<MaterielOutboundCalculationDTO>(); /// ååº /// </summary> public string FactoryArea { get; set; } /// <summary> /// æ¯å¦å¤æç»åºåº /// </summary> public bool IsMultiDetail { get; set; } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/OutboundCompleteRequestDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,38 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WIDESEA_DTO.CalcOut { /// <summary> /// åºåºå®æå¤ç请æ±DTO /// </summary> public class OutboundCompleteRequestDTO { /// <summary> /// åºåºåç¼å· /// </summary> [Required(ErrorMessage = "åºåºåç¼å·ä¸è½ä¸ºç©º")] public string OrderNo { get; set; } /// <summary> /// /// </summary> [Required(ErrorMessage = "æçå·ä¸è½ä¸ºç©º")] public string PalletCode { get; set; } /// <summary> /// æ«æçæ¡ç /// </summary> [Required(ErrorMessage = "æ¡ç ä¸è½ä¸ºç©º")] public string Barcode { get; set; } /// <summary> /// æä½è /// </summary> public string Operator { get; set; } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/OutboundCompleteResponseDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,40 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WIDESEA_Model.Models; namespace WIDESEA_DTO.CalcOut { /// <summary> /// åºåºå®æå¤çååºDTO /// </summary> public class OutboundCompleteResponseDTO { /// <summary> /// æ¯å¦æå /// </summary> public bool Success { get; set; } /// <summary> /// æ¶æ¯ /// </summary> public string Message { get; set; } /// <summary> /// æ«æçåºåæç»ä¿¡æ¯ /// </summary> public ScannedStockDetailDTO ScannedDetail { get; set; } /// <summary> /// æ´æ°åçåºåºåæç»ä¿¡æ¯ /// </summary> public List<Dt_OutboundOrderDetail> UpdatedDetails { get; set; } /// <summary> /// éæ°çæçæ¡ç ï¼æå æ¶ï¼ /// </summary> public string NewBarcode { get; set; } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/PickedListItemDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,30 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WIDESEA_DTO.CalcOut { public class PickedListItemDTO { public int Id { get; set; } public string OrderNo { get; set; } public int? TaskNum { get; set; } public string PalletCode { get; set; } public string MaterielCode { get; set; } public string MaterielName { get; set; } public string BatchNo { get; set; } public string OriginalBarcode { get; set; } public string NewBarcode { get; set; } public decimal ChangeQuantity { get; set; } public decimal BeforeQuantity { get; set; } public decimal AfterQuantity { get; set; } public string SupplyCode { get; set; } public string WarehouseCode { get; set; } public string Remark { get; set; } public DateTime CreateDate { get; set; } public int? OrderStatus { get; set; } public string FactoryArea { get; set; } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/PickedStockDetailDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,51 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WIDESEA_Model.Models; namespace WIDESEA_DTO.CalcOut { /// <summary> /// 已忣åºåæç»DTO /// </summary> public class PickedStockDetailDTO { /// <summary> /// æçç¼å· /// </summary> public string PalletCode { get; set; } /// <summary> /// ç©æç¼å· /// </summary> public string MaterielCode { get; set; } /// <summary> /// æ¹æ¬¡å· /// </summary> public string BatchNo { get; set; } /// <summary> /// æ¬æ¬¡åºåºæ°é /// </summary> public decimal OutboundQuantity { get; set; } /// <summary> /// å©ä½åºåæ°é /// </summary> public decimal RemainingQuantity { get; set; } /// <summary> /// /// </summary> public string LocationCode { get; set; } /// <summary> /// /// </summary> public List<Dt_OutStockLockInfo> OutStockLockInfos { get; set; } = new List<Dt_OutStockLockInfo>(); } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/PickingOutboundRequestDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,49 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WIDESEA_DTO.CalcOut { /// <summary> /// 忣åºåºè¯·æ±DTO /// </summary> public class PickingOutboundRequestDTO { /// <summary> /// åºåºåç¼å· /// </summary> [Required(ErrorMessage = "åºåºåç¼å·ä¸è½ä¸ºç©º")] public string OrderNo { get; set; } /// <summary> /// éæ©çåºåºæç»IDå表ï¼å个æå¤ä¸ªæç»ï¼ /// </summary> [Required(ErrorMessage = "è¯·éæ©è¦åºåºçæç»")] public List<int> DetailIds { get; set; } = new List<int>(); /// <summary> /// æ¬æ¬¡åºåºæ°éï¼å个æç»æ¶éè¦å¡«åï¼å¤ä¸ªæç»æ¶ä¸ºç©ºï¼ /// </summary> [Range(0.0001, double.MaxValue, ErrorMessage = "åºåºæ°éå¿ é¡»å¤§äº0")] public decimal? OutboundQuantity { get; set; } /// <summary> /// æä½è /// </summary> public string Operator { get; set; } /// <summary> /// åºåºç®æ ä½ç½® /// </summary> public string OutboundTargetLocation { get; set; } /// <summary> /// æ¯å¦å¤æç»åºåºï¼éè¿DetailIdsæ°é夿ï¼ä¹å¯æå¨æå®ï¼ /// </summary> public bool IsMultiDetail => DetailIds?.Count > 1; } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/PickingOutboundResponseDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,40 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using WIDESEA_Model.Models; namespace WIDESEA_DTO.CalcOut { /// <summary> /// 忣åºåºååºDTO /// </summary> public class PickingOutboundResponseDTO { /// <summary> /// æ¯å¦æå /// </summary> public bool Success { get; set; } /// <summary> /// æ¶æ¯ /// </summary> public string Message { get; set; } /// <summary> /// åºåºåç¶æ /// </summary> public int OrderStatus { get; set; } /// <summary> /// æ¬æ¬¡åºåºçåºåæç»ä¿¡æ¯ /// </summary> public List<PickedStockDetailDTO> PickedDetails { get; set; } /// <summary> /// ä»»å¡éå /// </summary> public List<Dt_Task> Tasks { get; set; } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/ScannedStockDetailDTO.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,64 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace WIDESEA_DTO.CalcOut { /// <summary> /// æ«æçåºåæç»DTO /// </summary> public class ScannedStockDetailDTO { /// <summary> /// åºåæç»ID /// </summary> public int StockDetailId { get; set; } /// <summary> /// æçç¼å· /// </summary> public string PalletCode { get; set; } /// <summary> /// ç©æç¼å· /// </summary> public string MaterielCode { get; set; } /// <summary> /// ç©æåç§° /// </summary> public string MaterielName { get; set; } /// <summary> /// æ¹æ¬¡å· /// </summary> public string BatchNo { get; set; } /// <summary> /// åå§æ¡ç /// </summary> public string OriginalBarcode { get; set; } /// <summary> /// åå¨åæ°é /// </summary> public decimal BeforeQuantity { get; set; } /// <summary> /// åå¨åæ°é /// </summary> public decimal AfterQuantity { get; set; } /// <summary> /// å卿°é /// </summary> public decimal ChangeQuantity { get; set; } /// <summary> /// æ¯å¦æå /// </summary> public bool IsUnpacked { get; set; } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundService.cs
@@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using WIDESEA_Core; using WIDESEA_DTO.CalcOut; namespace WIDESEA_IOutboundService { @@ -14,5 +15,23 @@ IOutboundOrderService OutboundOrderService { get; } IOutStockLockInfoService OutboundStockLockInfoService { get; } /// <summary> /// 忣åºåºæä½ /// </summary> /// <param name="request">忣åºåºè¯·æ±</param> /// <returns>忣åºåºååº</returns> WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request); /// <summary> /// åºåºå®æå¤çï¼æ«ææ¡ç æ£ååºåï¼ /// </summary> /// <param name="request">åºåºå®æè¯·æ±</param> /// <returns>åºåºå®æååº</returns> WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request); WebResponseContent QueryPickingTasks(string palletCode, string orderNo); WebResponseContent QueryPickedList(string orderNo, string palletCode); } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs
@@ -159,5 +159,32 @@ public int ReturnToMESStatus { get; set; } //æ°å¢å段 /// <summary> /// æ¬æ¬¡åé åç累计é /// </summary> [SugarColumn(IsNullable = false, ColumnDescription = "æ¬æ¬¡åé åç累计é")] public decimal AllocatedQuantity { get; set; } = 0; /// <summary> /// åºåºç®æ ä½ç½® /// </summary> [SugarColumn(IsNullable = true, Length = 100, ColumnDescription = "åºåºç®æ ä½ç½®")] public string OutboundTargetLocation { get; set; } /// <summary> /// åæ®æç»ä¸»é®ï¼æ¯æå¤ä¸ªæç»IDï¼éå·åéï¼ /// </summary> [SugarColumn(IsNullable = true, Length = 500, ColumnDescription = "åæ®æç»ä¸»é®")] public string OrderDetailIds { get; set; } /// <summary> /// /// </summary> [SugarColumn(IsNullable = true, ColumnDescription = "忣宿æ°é")] public decimal? SortedQuantity { get; set; } = 0; } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Record/Dt_StockQuantityChangeRecord.cs
@@ -118,5 +118,20 @@ /// </summary> [SugarColumn(IsNullable = true, ColumnDescription = "夿³¨")] public string Remark { get; set; } //æ°å¢å段 /// <summary> /// åå§åºåå·/ç©æç /// </summary> [SugarColumn(IsNullable = false, Length = 100, ColumnDescription = "åå§åºåå·")] public string OriginalSerilNumber { get; set; } /// <summary> /// æ°åºåå· /// </summary> [SugarColumn(IsNullable = false, Length = 100, ColumnDescription = "æ°åºåå·")] public string NewSerilNumber { get; set; } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundQueryService.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,81 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SqlSugar; using WIDESEA_Common.StockEnum; using WIDESEA_Core; using WIDESEA_DTO.CalcOut; using WIDESEA_Model.Models; namespace WIDESEA_OutboundService { public partial class OutboundService { public WebResponseContent QueryPickingTasks(string palletCode, string orderNo) { try { List<Dt_OutStockLockInfo> outStockLockInfos = _outboundLockInfoService.Repository.QueryData(x => x.PalletCode == palletCode && x.OrderNo == orderNo); return WebResponseContent.Instance.OK(data: outStockLockInfos); } catch (Exception ex) { return WebResponseContent.Instance.Error(ex.Message); } } /// <summary> /// æ¥è¯¢å·²æ£éå表ï¼éè¿åºååå¨è®°å½ï¼ /// </summary> /// <param name="request">æ¥è¯¢è¯·æ±</param> /// <returns>å·²æ£éå表</returns> public WebResponseContent QueryPickedList(string orderNo, string palletCode) { WebResponseContent content = WebResponseContent.Instance; try { // æå»ºæ¥è¯¢æ¡ä»¶ var query = _stockChangeService.Repository.Db .Queryable<Dt_StockQuantityChangeRecord>() .LeftJoin<Dt_OutboundOrder>((r, o) => r.OrderNo == o.OrderNo) .Where((r, o) => r.ChangeType == (int)StockChangeTypeEnum.Outbound) .Where((r, o) => r.OrderNo == orderNo) .Where((r, o) => r.PalleCode == palletCode) .OrderBy((r, o) => r.CreateDate, OrderByType.Desc) .Select((r, o) => new PickedListItemDTO { Id = r.Id, OrderNo = r.OrderNo, TaskNum = r.TaskNum, PalletCode = r.PalleCode, MaterielCode = r.MaterielCode, MaterielName = r.MaterielName, BatchNo = r.BatchNo, OriginalBarcode = r.OriginalSerilNumber, NewBarcode = r.NewSerilNumber, ChangeQuantity = r.ChangeQuantity, BeforeQuantity = r.BeforeQuantity, AfterQuantity = r.AfterQuantity, SupplyCode = r.SupplyCode, WarehouseCode = r.WarehouseCode, Remark = r.Remark, CreateDate = r.CreateDate, OrderStatus = o.OrderStatus, FactoryArea = o.FactoryArea }); var result = query.ToList(); return WebResponseContent.Instance.OK(data: result); } catch (Exception ex) { return WebResponseContent.Instance.Error($"æ¥è¯¢å·²æ£éå表失败ï¼{ex.Message}"); } } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -1,9 +1,24 @@ using WIDESEA_IOutboundService; using SqlSugar; using WIDESEA_Common.LocationEnum; using WIDESEA_Common.OrderEnum; using WIDESEA_Common.StockEnum; using WIDESEA_Common.TaskEnum; using WIDESEA_Core; using WIDESEA_Core.BaseRepository; using WIDESEA_Core.Helper; using WIDESEA_DTO.CalcOut; using WIDESEA_IBasicService; using WIDESEA_IOutboundService; using WIDESEA_IRecordService; using WIDESEA_IStockService; using WIDESEA_Model.Models; namespace WIDESEA_OutboundService { public class OutboundService : IOutboundService public partial class OutboundService : IOutboundService { public IUnitOfWorkManage _unitOfWorkManage { get; } public IOutboundOrderDetailService OutboundOrderDetailService { get; } @@ -11,11 +26,1130 @@ public IOutStockLockInfoService OutboundStockLockInfoService { get; } public OutboundService(IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService) private readonly ISqlSugarClient Db; private readonly IOutboundOrderDetailService _detailService; private readonly IOutboundOrderService _outboundOrderService; private readonly IOutStockLockInfoService _outboundLockInfoService; private readonly IStockInfoService _stockInfoService; private readonly IStockInfoDetailService _stockDetailService; private readonly ILocationInfoService _locationInfoService; private readonly IStockQuantityChangeRecordService _stockChangeService; private readonly IStockInfoDetail_HtyService _stockDetailHistoryService; public OutboundService(IUnitOfWorkManage unitOfWorkManage, IOutboundOrderDetailService outboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutStockLockInfoService outboundStockLockInfoService, IStockInfoService stockInfoService, IStockInfoDetailService stockDetailService, ILocationInfoService locationInfoService, IStockQuantityChangeRecordService stockQuantityChangeRecordService, IStockInfoDetail_HtyService stockDetailHistoryService) { _unitOfWorkManage = unitOfWorkManage; Db = _unitOfWorkManage.GetDbClient(); OutboundOrderDetailService = outboundOrderDetailService; OutboundOrderService = outboundOrderService; OutboundStockLockInfoService = outboundStockLockInfoService; _detailService = outboundOrderDetailService; _outboundOrderService = outboundOrderService; _outboundLockInfoService = outboundStockLockInfoService; _stockInfoService = stockInfoService; _stockDetailService = stockDetailService; _locationInfoService = locationInfoService; _stockChangeService = stockQuantityChangeRecordService; _stockDetailHistoryService = stockDetailHistoryService; } /// <summary> /// 忣åºåºæä½ /// </summary> /// <param name="request">忣åºåºè¯·æ±</param> /// <returns>忣åºåºååº</returns> public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request) { WebResponseContent content = WebResponseContent.Instance; PickingOutboundResponseDTO response = new PickingOutboundResponseDTO(); try { // 1. 计ç®åºåºæ°éé»è¾ OutboundCalculationDTO calculationResult = CalcOutboundQuantity(request); if (!calculationResult.CanOutbound) { content = WebResponseContent.Instance.Error("æ æ³å¤çæ£è´§åºåºï¼" + calculationResult.ErrorMessage); _unitOfWorkManage.RollbackTran(); return content; } // 2. è°ç¨åºåºå¤çé»è¾ï¼éå®åºåï¼çæåºåºè®°å½ç List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>(); // è·ååºåºåä¿¡æ¯ Dt_OutboundOrder outboundOrder = calculationResult.OutboundOrder; // åºåºè¯¦æ æ·»å æä¿®æ¹éå List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>(); List<Dt_OutboundOrderDetail> outboundOrderDetails = new(); List<Dt_Task> tasks = new List<Dt_Task>(); foreach (var materielCalc in calculationResult.MaterielCalculations) { (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea); foreach (var item in materielPickedDetails.OutStockLockInfo) { Dt_OutStockLockInfo? outStockLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0); if (outStockLockInfo != null) { outStockLockInfo = item; } else { materielCalc.OutStockLockInfos.Add(item); } outStockLockInfos.Add(item); } tasks.AddRange(materielPickedDetails.Tasks); pickedDetails.AddRange(materielPickedDetails.PickedDetails); // æ´æ°åºåºåæç»ï¼å¢å é宿°éï¼ä¸å¢å å·²åºæ°éï¼ foreach (var detail in materielCalc.Details) { decimal lockQuantity = (detail.OrderQuantity - detail.OverOutQuantity); detail.LockQuantity += lockQuantity; // å¢å é宿°é 䏿´æ° OverOutQuantity å OrderDetailStatusï¼å ä¸ºè¿æ²¡æå®é åºåº outboundOrderDetails.Add(detail); } } // 3. æ´æ°åºåºåç¶æä¸ºåºåºä¸ï¼è¡¨ç¤ºå·²æä»»å¡åé ï¼ UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.åºåºä¸); // 4. æ´æ°åºåºåæç»é宿°é _detailService.Repository.UpdateData(outboundOrderDetails); // 5. æ´æ°åºåç¶æ UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.åºåºéå®.ObjToInt()); // 6. æ´æ°è´§ä½ç¶æ UpdateLocationStatus(pickedDetails.Select(x => x.LocationCode).ToList(), LocationStatusEnum.Lock.ObjToInt()); // 7. æ´æ°åºå详æ UpdateOutStockLockInfo(outStockLockInfos); _unitOfWorkManage.CommitTran(); response.Success = true; response.Message = "忣任å¡åé æå"; response.Tasks = tasks; // è¿å第ä¸ä¸ªä»»å¡å· response.PickedDetails = pickedDetails; // è¿å第ä¸ä¸ªåæ£æç» content = WebResponseContent.Instance.OK("忣任å¡åé æå", response); return content; } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); content = WebResponseContent.Instance.Error("å¤çæ£è´§åºåºå¤±è´¥ï¼" + ex.Message); } return content; } /// <summary> /// 计ç®åºåºæ°éé»è¾ /// </summary> /// <param name="request"></param> /// <returns></returns> public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request) { OutboundCalculationDTO result = new(); try { Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.OrderNo == request.OrderNo); if (outboundOrder == null) { result.CanOutbound = false; result.ErrorMessage = $"åºåºå {request.OrderNo} ä¸åå¨"; return result; } result.FactoryArea = outboundOrder.FactoryArea; result.IsMultiDetail = request.IsMultiDetail; // è·åéæ©çåºåºæç» List<Dt_OutboundOrderDetail> selectedDetails = _detailService.Repository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id)); if (!selectedDetails.Any()) { result.CanOutbound = false; result.ErrorMessage = $"æªæ¾å°éæ©çåºåºæç»ä¿¡æ¯"; return result; } if (selectedDetails.Any(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty)) { List<int> selectDetailIds = selectedDetails.Where(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty).Select(x => x.Id).ToList(); result.CanOutbound = false; result.ErrorMessage = $"åºåºæç»ä¿¡æ¯{string.Join(",", selectDetailIds)}å·²åé 宿"; return result; } outboundOrder.Details = selectedDetails; result.OutboundOrder = outboundOrder; result.SelectedDetails = selectedDetails; if (request.IsMultiDetail) { // 夿ç»åºåºï¼æç©æåç»å¤ç result.MaterielCalculations = CalcMaterielOutboundQuantities(outboundOrder, selectedDetails.ToList()); } else { // åæç»åºåºï¼éªè¯è¾å ¥çåºåºæ°é if (!request.OutboundQuantity.HasValue || request.OutboundQuantity.Value <= 0) { result.CanOutbound = false; result.ErrorMessage = "åæç»åºåºæ¶å¿ é¡»æå®åºåºæ°éä¸å¤§äº0"; return result; } Dt_OutboundOrderDetail? singleDetail = selectedDetails.First(); //夿å¯åºåºæ°é if (singleDetail.OrderQuantity - singleDetail.LockQuantity - singleDetail.MoveQty <= 0) { result.CanOutbound = false; result.ErrorMessage = $"æ¬æ¬¡åºåºæ°é {request.OutboundQuantity.Value} è¶ è¿å¯åºåºæ°é {singleDetail.OrderQuantity - singleDetail.LockQuantity - singleDetail.MoveQty}"; return result; } result.MaterielCalculations = new List<MaterielOutboundCalculationDTO>() { new MaterielOutboundCalculationDTO { MaterielCode = singleDetail.MaterielCode, MaterielName = singleDetail.MaterielName, BatchNo = singleDetail.BatchNo, SupplyCode = singleDetail.SupplyCode, WarehouseCode = singleDetail.WarehouseCode, TotalOrderQuantity = singleDetail.OrderQuantity - singleDetail.MoveQty, TotalOverOutQuantity = singleDetail.OverOutQuantity, AssignedQuantity = singleDetail.LockQuantity, UnallocatedQuantity = singleDetail.OrderQuantity - singleDetail.LockQuantity - singleDetail.MoveQty, MovedQuantity = singleDetail.MoveQty, Details = new List<Dt_OutboundOrderDetail>() { singleDetail } } }; } result.CanOutbound = true; return result; } catch (Exception ex) { result.CanOutbound = false; result.ErrorMessage = ex.Message; return result; } } /// <summary> /// å¤åºåºåæç»æ¶ï¼æç©æåç»è®¡ç®åºåºæ°é /// </summary> /// <param name="selectedDetails"></param> /// <returns></returns> private List<MaterielOutboundCalculationDTO> CalcMaterielOutboundQuantities(Dt_OutboundOrder outboundOrder, List<Dt_OutboundOrderDetail> selectedDetails) { // æç©æåç»ï¼ç©æç¼å·ãæ¹æ¬¡å·ãä¾åºåç¼å·ãä»åºç¼å· List<MaterielOutboundCalculationDTO> materielGroups = selectedDetails .GroupBy(x => new { x.MaterielCode, x.MaterielName, x.BatchNo, x.SupplyCode, x.WarehouseCode }) .Select(g => new MaterielOutboundCalculationDTO { MaterielCode = g.Key.MaterielCode, MaterielName = g.Key.MaterielName, BatchNo = g.Key.BatchNo, SupplyCode = g.Key.SupplyCode, WarehouseCode = g.Key.WarehouseCode, TotalOrderQuantity = g.Sum(x => x.OrderQuantity - x.MoveQty), TotalOverOutQuantity = g.Sum(x => x.OverOutQuantity), AssignedQuantity = g.Sum(x => x.LockQuantity), UnallocatedQuantity = g.Sum(x => x.OrderQuantity - x.LockQuantity - x.MoveQty), MovedQuantity = g.Sum(x => x.MoveQty), Details = g.ToList(), OutStockLockInfos = _outboundLockInfoService.Repository.QueryData(x => x.MaterielCode == g.Key.MaterielCode && x.BatchNo == g.Key.BatchNo && x.OrderType == (int)outboundOrder.OrderType && x.OrderNo == outboundOrder.OrderNo) }) .ToList(); return materielGroups; } /// <summary> /// å¤çç©æçä»»å¡çæ /// </summary> /// <param name="outboundOrder">åºåºè®¢å</param> /// <param name="materielCalc">æç©æçåºåºè®¡ç®ç»æ</param> /// <param name="request">忣åºåºè¯·æ±</param> /// <param name="factoryArea"></param> /// <returns></returns> /// <exception cref="Exception"></exception> private (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) ProcessMaterielTaskGeneration(Dt_OutboundOrder outboundOrder, MaterielOutboundCalculationDTO materielCalc, PickingOutboundRequestDTO request, string factoryArea) { List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>(); List<Dt_Task> generatedTasks = new List<Dt_Task>(); // æå»ºåºåæ¥è¯¢æ¡ä»¶ï¼å å«åºå表ãåºåæç»ï¼ List<Dt_StockInfo> stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea); if (!stockQuery.Any()) { throw new Exception($"ç©æ {materielCalc.MaterielCode} 对åºçåºåä¸åå¨"); } // æ¹éè®¡ç®æ»å¯ç¨åºåæ°é (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) data = GetBatchAvailableStockQuantities(materielCalc, stockQuery); // å¯ç¨åºåæ°éæ å° Dictionary<int, decimal> availableStockMap = data.AvailableStockMap; // ç©ææ»å¯ç¨åºåæ°é decimal totalAvailableStock = availableStockMap.Values.Sum(); // å·²éå®åºåæ°éæ å° Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = data.LockStockMap; // éªè¯æ»å¯ç¨åºåæ¯å¦æ»¡è¶³åºåºéæ± if (totalAvailableStock < materielCalc.UnallocatedQuantity) { throw new Exception($"ç©æ {materielCalc.MaterielCode} å¯ç¨åºå {totalAvailableStock} ä¸è¶³åºåºæ°é {materielCalc.UnallocatedQuantity}"); } // éåé æ°é decimal remainingQuantity = materielCalc.UnallocatedQuantity; // å·²åé çæçå表 List<string> allocatedPallets = new List<string>(); // è·å第ä¸ä¸ªåºåºæç» Dt_OutboundOrderDetail firstDetail = materielCalc.Details.First(); // é¢å è½½åºåæç»åéå®è®°å½ List<int> stockIds = stockQuery.Select(x => x.Id).ToList(); Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = stockQuery.ToDictionary(x => x.Id, x => x.Details); // è®°å½æ¯ä¸ªæççå®é åé é Dictionary<string, decimal> palletAllocations = new Dictionary<string, decimal>(); List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>(); foreach (var stock in stockQuery) { if (remainingQuantity <= 0) break; // å½ååºåå¯ç¨æ°é decimal availableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0); if (availableQuantity <= 0) continue; decimal allocateQuantity = Math.Min(remainingQuantity, availableQuantity); (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) actualAllocated = AllocateStockQuantity(stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail, request, lockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()), stockDetailMap); decimal actualAllocatedQuantity = actualAllocated.ActualAllocatedQuantity; if (actualAllocatedQuantity > 0) { allocatedPallets.Add(stock.PalletCode); palletAllocations[stock.PalletCode] = actualAllocatedQuantity; // è®°å½å®é åé é remainingQuantity -= actualAllocatedQuantity; lockInfoList.AddRange(actualAllocated.LockInfoList); } } foreach (var palletCode in allocatedPallets) { Dt_StockInfo stock = stockQuery.First(x => x.PalletCode == palletCode); // è·åå®é åé çæ°é decimal actualAllocatedQuantity = palletAllocations.GetValueOrDefault(palletCode, 0); // 计ç®åé åå©ä½çåºåæ°é decimal originalAvailableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0); decimal remainingStockQuantity = Math.Max(0, originalAvailableQuantity - actualAllocatedQuantity); pickedDetails.Add(new PickedStockDetailDTO { PalletCode = palletCode, MaterielCode = materielCalc.MaterielCode, OutboundQuantity = actualAllocatedQuantity, // æ¬æ¬¡å®é åé çåºåºé RemainingQuantity = remainingStockQuantity, // åé åå©ä½çå¯ç¨åºå LocationCode = stock.LocationCode, OutStockLockInfos = lockInfoList }); Dt_OutStockLockInfo? outStockLockInfo = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode); int taskNum = outStockLockInfo?.TaskNum ?? Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(); Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, request.OutboundTargetLocation); if (generatedTasks.FirstOrDefault(x => x.PalletCode == stock.PalletCode) == null) generatedTasks.Add(task); } return (pickedDetails, generatedTasks, lockInfoList); } /// <summary> /// çæåºåºä»»å¡ /// </summary> /// <param name="stockInfo"></param> /// <param name="taskType"></param> /// <param name="outStation"></param> /// <returns></returns> public Dt_Task GenerationOutTask(Dt_StockInfo stockInfo, TaskTypeEnum taskType, int taskNum, string outStation) { Dt_Task task = new() { CurrentAddress = stockInfo.LocationCode, Grade = 0, PalletCode = stockInfo.PalletCode, NextAddress = outStation, Roadway = "", SourceAddress = stockInfo.LocationCode, TargetAddress = outStation, TaskStatus = TaskStatusEnum.New.ObjToInt(), TaskType = taskType.ObjToInt(), TaskNum = taskNum, PalletType = stockInfo.PalletType, WarehouseId = stockInfo.WarehouseId, }; return task; } /// <summary> /// æå»ºåºåæ¥è¯¢æ¡ä»¶ï¼å å«åºåä¿¡æ¯ååºåæç»ï¼ /// </summary> /// <param name="materielCalc"></param> /// <param name="factoryArea"></param> /// <returns></returns> private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea) { // åºç¡æ¥è¯¢æ¡ä»¶ï¼ç©æç¼å·ãæ¹æ¬¡å·ï¼å¦ææä¾ï¼ãåºåæ°é>0 ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailService.Repository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0); // æ ¹æ®æ¡ä»¶æ·»å ä¾åºåç¼å·å¹é ï¼ä¸ä¸ºç©ºæ¶æéè¦å¹é ï¼ if (!string.IsNullOrEmpty(materielCalc.SupplyCode)) { stockDetails = stockDetails.Where(x => x.SupplyCode == materielCalc.SupplyCode); } // æ ¹æ®æ¡ä»¶æ·»å ä»åºç¼å·å¹é ï¼ä¸ä¸ºç©ºæ¶æéè¦å¹é ï¼ if (!string.IsNullOrEmpty(materielCalc.WarehouseCode)) { stockDetails = stockDetails.Where(x => x.WarehouseCode == materielCalc.WarehouseCode); } // æ ¹æ®æ¡ä»¶æ·»å ååºå¹é ï¼ä¸ä¸ºç©ºæ¶æéè¦å¹é ï¼ if (!string.IsNullOrEmpty(factoryArea)) { stockDetails = stockDetails.Where(x => x.FactoryArea == factoryArea); } // æ ¹æ®æ¹æ¬¡å·è¿è¡è¿æ»¤ï¼å¦ææä¾ï¼ if (!string.IsNullOrEmpty(materielCalc.BatchNo)) { stockDetails = stockDetails.Where(x => x.BatchNo == materielCalc.BatchNo); } List<Dt_StockInfoDetail> stockDetailList = stockDetails.ToList(); // è·åå¯ç¨è´§ä½ç¼å· List<string> locationCodes = _locationInfoService.Repository.QueryData(x => (x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() /*|| x.LocationStatus == LocationStatusEnum.Lock.ObjToInt()*/) && x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).ToList(); // è·åææç¸å ³çåºåä¿¡æ¯ List<int> stockIds = stockDetailList.GroupBy(x => x.StockId).Select(x => x.Key).ToList(); List<Dt_StockInfo> stockInfos = _stockInfoService.Repository.QueryData(x => stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.å ¥åºå®æ.ObjToInt() /*|| x.StockStatus == StockStatusEmun.åºåºéå®.ObjToInt()*/) && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode)); // å¨å åä¸å ³èæ°æ® foreach (var stockInfo in stockInfos) { stockInfo.Details = new List<Dt_StockInfoDetail>(); stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList(); } return stockInfos; } /// <summary> /// æ¹éè·åæçå¯ç¨åºåä¿¡æ¯ /// </summary> /// <param name="stockInfos">åºåä¿¡æ¯å表</param> /// <param name="materielCode">ç©æç¼å·</param> /// <returns>è¿åå¼ä¸º(åºå主é®ï¼å¯ç¨æ°é)åå ¸</returns> private (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) GetBatchAvailableStockQuantities(MaterielOutboundCalculationDTO materielCalc, List<Dt_StockInfo> stockInfos) { List<int> stockIds = stockInfos.Select(x => x.Id).ToList(); Dictionary<int, decimal> availableStockMap = new Dictionary<int, decimal>(); // å¯ç¨åºåæ°é Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = new Dictionary<int, List<Dt_OutStockLockInfo>>(); // å·²éå®åºåæ°é // æ¹éæ¥è¯¢å·²åé æ°é List<Dt_OutStockLockInfo> allocatedData = materielCalc.OutStockLockInfos.Where(x => stockIds.Contains(x.StockId) && x.MaterielCode == materielCalc.MaterielCode).ToList(); foreach (var stockInfo in stockInfos) { // è®¡ç®æ»åºåæ°é decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity); List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData.Where(x => x.StockId == stockInfo.Id).ToList(); // 计ç®å·²åé æ°é decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity); availableStockMap[stockInfo.Id] = Math.Max(0, totalQuantity - allocatedQuantity); lockStockMap[stockInfo.Id] = outStockLockInfos; } return (availableStockMap, lockStockMap); } /// <summary> /// åé åºå /// </summary> /// <param name="stockInfo">åºåä¿¡æ¯</param> /// <param name="allocateQuantity">è¦åé çæ°é</param> /// <param name="availableQuantity">å¯åé çæ°é</param> /// <param name="outboundOrder">åºåºå</param> /// <param name="detail">åºåºåæç»</param> /// <param name="request"></param> /// <param name="lockInfos"></param> /// <param name="stockDetailMap"></param> /// <returns></returns> private (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) AllocateStockQuantity(Dt_StockInfo stockInfo, decimal allocateQuantity, decimal availableQuantity, Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail, PickingOutboundRequestDTO request, List<Dt_OutStockLockInfo> lockInfos, Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = null) { decimal actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity); // å®é åé æ°é List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>(); if (actualAllocatedQuantity > 0) { //æ£æ¥ç®æ ä½ç½®ä¸è´æ§ï¼å¦ææçå·²æéå®è®°å½ä¸ç®æ ä½ç½®ä¸åï¼åä¸å 许åé if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation)) { if (!string.Equals(lockInfos.First().OutboundTargetLocation, request.OutboundTargetLocation, StringComparison.OrdinalIgnoreCase)) { // æççç®æ ä½ç½®ä¸æ°è¯·æ±çç®æ ä½ç½®ä¸åï¼ä¸å 许使ç¨è¯¥æç return (0, lockInfoList); } } Dt_OutStockLockInfo? lockInfo = lockInfos.FirstOrDefault(x => x.StockId == stockInfo.Id && x.Status == OutLockStockStatusEnum.å·²åé .ObjToInt() && x.PalletCode == stockInfo.PalletCode && x.OrderNo == outboundOrder.OrderNo); if (lockInfo != null) { // 追å å½åæç»IDå°OrderDetailIdsåæ®µï¼é¿å éå¤ï¼ List<string> currentIds = lockInfo.OrderDetailIds?.Split(',').ToList() ?? new List<string>(); if (!currentIds.Contains(detail.Id.ToString())) { currentIds.Add(detail.Id.ToString()); lockInfo.OrderDetailIds = string.Join(",", currentIds); } // 计ç®è¯¥æçè¯¥ç©æçæ»ç´¯è®¡å·²åºåºæ°éï¼è·¨ææåæ®ï¼ decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode); // æ´æ°åé åºåºé decimal beforeAssignQuantity = totalAllocatedQuantity; // æ¬æ¬¡åé åçæ»ç´¯è®¡é lockInfo.AssignQuantity += actualAllocatedQuantity; // ç´¯å æ¬æ¬¡åé æ°é lockInfo.AllocatedQuantity = beforeAssignQuantity; // è®°å½æ¬æ¬¡åé åçæ»ç´¯è®¡é lockInfoList.Add(lockInfo); } else { // å建æ°çéå®è®°å½ï¼ä½¿ç¨é¢å è½½çåºåæç»ï¼ decimal originalQuantity = 0; if (stockDetailMap?.ContainsKey(stockInfo.Id) == true) { originalQuantity = stockDetailMap[stockInfo.Id].Sum(x => x.StockQuantity); } // è·åè¯¥ç©æå¨è¯¥è®¢åä¸çæææç»ID List<string> allDetailIds = (outboundOrder.Details.Where(x => x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode && x.WarehouseCode == detail.WarehouseCode)) .Select(x => x.Id.ToString()) .ToList(); // 计ç®è¯¥æçè¯¥ç©æçæ»ç´¯è®¡å·²åºåºæ°éï¼è·¨ææåæ®ï¼ decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode); lockInfo = new Dt_OutStockLockInfo { OrderNo = request.OrderNo, OrderDetailIds = string.Join(",", allDetailIds), // è®°å½è¯¥ç©æçæææç»ID OrderType = outboundOrder.OrderType, BatchNo = detail.BatchNo, MaterielCode = detail.MaterielCode, MaterielName = detail.MaterielName, StockId = stockInfo.Id, OrderQuantity = allDetailIds.SelectMany(id => allDetailIds).Count() > 1 ? (outboundOrder.Details.Where(x => x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode && x.WarehouseCode == detail.WarehouseCode)) .Sum(x => x.OrderQuantity) : detail.OrderQuantity, // å¦æåªæä¸ä¸ªæç»ï¼ä½¿ç¨æç»æ°é OriginalQuantity = originalQuantity, AssignQuantity = actualAllocatedQuantity, // æ¬æ¬¡åé æ°é AllocatedQuantity = totalAllocatedQuantity, // æ¬æ¬¡åé åçæ»ç´¯è®¡å·²åºåºæ°éï¼è·¨ææåæ®ï¼ LocationCode = stockInfo.LocationCode, PalletCode = stockInfo.PalletCode, Unit = detail.Unit, OutboundTargetLocation = request.OutboundTargetLocation, Status = OutLockStockStatusEnum.å·²åé .ObjToInt(), SupplyCode = detail.SupplyCode, WarehouseCode = detail.WarehouseCode, FactoryArea = outboundOrder.FactoryArea, TaskNum = Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(), OrderDetailId = 0 // æªå ³èå ·ä½æç»ID }; lockInfoList.Add(lockInfo); } } return (actualAllocatedQuantity, lockInfoList); } /// <summary> /// 计ç®è¯¥æç累计已åé æ°é /// </summary> /// <param name="lockInfos"></param> /// <param name="stockId"></param> /// <param name="materielCode"></param> /// <returns></returns> private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode) { // æ¥è¯¢è¯¥æçè¯¥ç©æå¨ææéå®è®°å½ä¸çæå¤§å·²åé æ°é List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoService.Repository.QueryData(x => x.StockId == stockId && x.MaterielCode == materielCode); if (lockRecords == null || !lockRecords.Any()) { return 0; } // è¿å累计已åé æ°é return lockRecords.Sum(x => x.AssignQuantity); } /// <summary> /// æ´æ°åºåºåç¶æ /// </summary> public bool UpdateOutboundOrderStatus(string orderNo, int status) { try { Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.OrderNo == orderNo); if (outboundOrder == null) return false; outboundOrder.OrderStatus = status; _outboundOrderService.Repository.UpdateData(outboundOrder); return true; } catch { return false; } } /// <summary> /// /// </summary> /// <param name="palletCodes"></param> /// <param name="status"></param> /// <returns></returns> public bool UpdateStockStatus(List<string> palletCodes, int status) { try { List<Dt_StockInfo> stockInfos = _stockInfoService.Repository.QueryData(x => palletCodes.Contains(x.PalletCode)); stockInfos.ForEach(stockInfo => { stockInfo.StockStatus = status; }); _stockInfoService.Repository.UpdateData(stockInfos); return true; } catch { return false; } } /// <summary> /// /// </summary> /// <param name="locationCodes"></param> /// <param name="status"></param> /// <returns></returns> public bool UpdateLocationStatus(List<string> locationCodes, int status) { try { List<Dt_LocationInfo> locationInfos = _locationInfoService.Repository.QueryData(x => locationCodes.Contains(x.LocationCode)); locationInfos.ForEach(x => { x.LocationStatus = status; }); _locationInfoService.Repository.UpdateData(locationInfos); return true; } catch { return false; } } /// <summary> /// /// </summary> /// <param name="outStockLockInfos"></param> /// <returns></returns> public bool UpdateOutStockLockInfo(List<Dt_OutStockLockInfo> outStockLockInfos) { try { List<Dt_OutStockLockInfo> updateData = outStockLockInfos.Where(x => x.Id > 0).ToList(); _outboundLockInfoService.Repository.UpdateData(updateData); List<Dt_OutStockLockInfo> addData = outStockLockInfos.Where(x => x.Id <= 0).ToList(); _outboundLockInfoService.Repository.AddData(addData); return true; } catch { return false; } } /// <summary> /// åºåºå®æå¤çï¼æ«ææ¡ç æ£ååºåï¼ /// </summary> /// <param name="request">åºåºå®æè¯·æ±</param> /// <returns>åºåºå®æååº</returns> public WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request) { WebResponseContent content = WebResponseContent.Instance; OutboundCompleteResponseDTO response = new(); try { // 1. æ ¹æ®æçå·æ¥æ¾åºåä¿¡æ¯ Dt_StockInfo stockInfo = _stockInfoService.Repository.QueryFirst(x => x.PalletCode == request.PalletCode); if (stockInfo == null) { response.Success = false; response.Message = $"æçå· {request.PalletCode} 对åºçåºåä¸åå¨"; return WebResponseContent.Instance.Error($"æçå· {request.PalletCode} 对åºçåºåä¸åå¨"); } // 2. æ ¹æ®æ¡ç æ¥æ¾åºåæç» Dt_StockInfoDetail stockDetail = _stockDetailService.Repository.QueryFirst(x => x.Barcode == request.Barcode); if (stockDetail == null) { response.Success = false; response.Message = $"æ¡ç {request.Barcode} 对åºçåºåæç»ä¸åå¨"; return WebResponseContent.Instance.Error($"æ¡ç {request.Barcode} 对åºçåºåæç»ä¸åå¨"); } // 3. éªè¯åºåæç»ä¸æçæ¯å¦å¹é if (stockDetail.StockId != stockInfo.Id) { response.Success = false; response.Message = $"æ¡ç {request.Barcode} ä¸å±äºæçå· {request.PalletCode} çåºåæç»"; return WebResponseContent.Instance.Error($"æ¡ç {request.Barcode} ä¸å±äºæçå· {request.PalletCode} çåºåæç»"); } // 4. æ¥æ¾åºåºåä¿¡æ¯ Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(o => o.OrderNo == request.OrderNo); if (outboundOrder == null) { response.Success = false; response.Message = $"åºåºå {request.OrderNo} ä¸åå¨"; return WebResponseContent.Instance.Error($"åºåºå {request.OrderNo} ä¸åå¨"); } // 5. æ¥æ¾åºåºåæç»ä¿¡æ¯ List<Dt_OutboundOrderDetail> outboundOrderDetails = FindMatchingOutboundDetails(outboundOrder.Id, stockDetail); if (!outboundOrderDetails.Any()) { response.Success = false; response.Message = $"æªæ¾å°å¹é çåºåºåæç»ï¼ç©æï¼{stockDetail.MaterielCode}ï¼æ¹æ¬¡ï¼{stockDetail.BatchNo}"; return WebResponseContent.Instance.Error($"æªæ¾å°å¹é çåºåºåæç»ï¼ç©æï¼{stockDetail.MaterielCode}ï¼æ¹æ¬¡ï¼{stockDetail.BatchNo}"); } // 6. æ¥æ¾éå®è®°å½ Dt_OutStockLockInfo lockInfo = _outboundLockInfoService.Repository.QueryFirst(x => x.OrderNo == request.OrderNo && x.StockId == stockInfo.Id && x.MaterielCode == stockDetail.MaterielCode && x.PalletCode == stockInfo.PalletCode); if (lockInfo == null || lockInfo.AssignQuantity <= 0) { response.Success = false; response.Message = $"该åºå没æåé åºåºéï¼æ¡ç ï¼{request.Barcode}"; return WebResponseContent.Instance.Error($"该åºå没æåé åºåºéï¼æ¡ç ï¼{request.Barcode}"); } // 7. 计ç®å®é åºåºé decimal actualOutboundQuantity = CalculateActualOutboundQuantity(stockDetail, outboundOrderDetails, lockInfo);// éåºåºé if (actualOutboundQuantity <= 0) { decimal totalAllocatedQuantity = lockInfo.AllocatedQuantity; decimal availableOutboundQuantity = lockInfo.AssignQuantity - totalAllocatedQuantity; decimal detailRemainingQuantity = outboundOrderDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty); response.Success = false; response.Message = $"æ æ³åºåºï¼æ¡ç ï¼{request.Barcode}ï¼åºåï¼{stockDetail.StockQuantity}ï¼å·²åºåºï¼{totalAllocatedQuantity}ï¼åé éï¼{lockInfo.AssignQuantity}ï¼æç»å©ä½ï¼{detailRemainingQuantity}"; return WebResponseContent.Instance.Error($"æ æ³åºåºï¼æ¡ç ï¼{request.Barcode}ï¼åºåï¼{stockDetail.StockQuantity}ï¼å·²åºåºï¼{totalAllocatedQuantity}ï¼åé éï¼{lockInfo.AssignQuantity}ï¼æç»å©ä½ï¼{detailRemainingQuantity}"); } // 8. 夿æ¯å¦éè¦æå ï¼å½åºåºæ°éå°äºåºåæ°éæ¶éè¦æå ï¼ bool isUnpacked = actualOutboundQuantity < stockDetail.StockQuantity; string newBarcode = string.Empty; // 9. å¼å¯äºå¡ _unitOfWorkManage.BeginTran(); try { decimal beforeQuantity = stockDetail.StockQuantity; // åå§åºåé // æ ¹æ®æ¯å¦æå æ§è¡ä¸åçæä½ if (isUnpacked) { newBarcode = PerformUnpackOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault()); } else { PerformFullOutboundOperation(stockDetail, stockInfo, actualOutboundQuantity, request, beforeQuantity, lockInfo.TaskNum.GetValueOrDefault()); } decimal allocatedQuantity = actualOutboundQuantity; List<Dt_OutboundOrderDetail> updateDetails = new(); foreach (var item in outboundOrderDetails) { if (allocatedQuantity <= 0) break; if (item.OrderQuantity - item.MoveQty - item.OverOutQuantity >= allocatedQuantity) { item.OverOutQuantity += allocatedQuantity; allocatedQuantity = 0; } else { allocatedQuantity -= (item.OrderQuantity - item.MoveQty - item.OverOutQuantity); item.OverOutQuantity = item.OrderQuantity - item.MoveQty; } updateDetails.Add(item); } lockInfo.SortedQuantity = lockInfo.SortedQuantity + actualOutboundQuantity; // æ´æ°éå®è®°å½ _outboundLockInfoService.Repository.UpdateData(lockInfo); // æ´æ°åºåºåæç»çå·²åºåºæ°é _detailService.Repository.UpdateData(updateDetails); // æ´æ°éå®è®°å½ç累计已åºåºæ°éï¼éè¦æ´æ°è¯¥æçè¯¥ç©æçææç¸å ³è®°å½ï¼ UpdateLockInfoAllocatedQuantity(stockInfo.Id, stockDetail.MaterielCode, stockDetail.BatchNo, actualOutboundQuantity); // æäº¤äºå¡ _unitOfWorkManage.CommitTran(); // æå»ºè¿åä¿¡æ¯ ScannedStockDetailDTO scannedDetail = new ScannedStockDetailDTO { StockDetailId = stockDetail.Id, PalletCode = stockInfo.PalletCode, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, BatchNo = stockDetail.BatchNo, OriginalBarcode = request.Barcode, BeforeQuantity = beforeQuantity, AfterQuantity = isUnpacked ? actualOutboundQuantity : 0, ChangeQuantity = -actualOutboundQuantity, IsUnpacked = isUnpacked }; response.Success = true; response.Message = isUnpacked ? $"æå åºåºå®æï¼å·²çææ°æ¡ç ï¼{newBarcode}" : "åºåºå®æ"; response.ScannedDetail = scannedDetail; response.UpdatedDetails = updateDetails; response.NewBarcode = newBarcode; // æ£æ¥åºåºåæ¯å¦å®æ if (CheckOutboundOrderCompleted(request.OrderNo)) { UpdateOutboundOrderStatus(request.OrderNo, OutOrderStatusEnum.åºåºå®æ.ObjToInt()); } } catch (Exception ex) { _unitOfWorkManage.RollbackTran(); response.Success = false; response.Message = $"åºåºå¤ç失败ï¼{ex.Message}"; return WebResponseContent.Instance.Error(ex.Message); } content = WebResponseContent.Instance.OK(data: response); } catch (Exception ex) { content = WebResponseContent.Instance.Error("å¤çåºåºå®æå¤±è´¥ï¼" + ex.Message); } return content; } /// <summary> /// /// </summary> /// <param name="orderId"></param> /// <param name="stockDetail"></param> /// <returns></returns> private List<Dt_OutboundOrderDetail> FindMatchingOutboundDetails(int orderId, Dt_StockInfoDetail stockDetail) { List<Dt_OutboundOrderDetail> details = _detailService.Repository.QueryData(x => x.OrderId == orderId && x.MaterielCode == stockDetail.MaterielCode && x.OrderQuantity - x.MoveQty > x.OverOutQuantity); // 精确å¹é ï¼å¤çnullå¼çæ¹æ¬¡ãä¾åºåãä»åº List<Dt_OutboundOrderDetail> exactMatches = details.Where(x => (string.IsNullOrEmpty(x.BatchNo)) || x.BatchNo == stockDetail.BatchNo ).Where(x => (string.IsNullOrEmpty(x.SupplyCode)) || x.SupplyCode == stockDetail.SupplyCode ).Where(x => (string.IsNullOrEmpty(x.WarehouseCode)) || x.WarehouseCode == stockDetail.WarehouseCode ).ToList(); return exactMatches; } /// <summary> /// 计ç®å®é åºåºæ°é /// </summary> private decimal CalculateActualOutboundQuantity(Dt_StockInfoDetail stockDetail, List<Dt_OutboundOrderDetail> outboundDetails, Dt_OutStockLockInfo lockInfo) { decimal availableOutboundQuantity = lockInfo.AssignQuantity; decimal detailRemainingQuantity = outboundDetails.Sum(x => x.OrderQuantity - x.OverOutQuantity - x.MoveQty);//outboundDetail.OrderQuantity - outboundDetail.OverOutQuantity; return Math.Min( Math.Min(availableOutboundQuantity, detailRemainingQuantity), stockDetail.StockQuantity); } /// <summary> /// æ§è¡æå æä½ /// </summary> /// <param name="stockDetail"></param> /// <param name="stockInfo"></param> /// <param name="actualOutboundQuantity"></param> /// <param name="request"></param> /// <param name="beforeQuantity"></param> /// <param name="taskNum"></param> /// <returns></returns> private string PerformUnpackOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo, decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum) { string newBarcode = GenerateNewBarcode(); // ä¿ååå§åºåæç»å°åå²è®°å½ Dt_StockInfoDetail_Hty originalHistoryRecord = new Dt_StockInfoDetail_Hty { SourceId = stockDetail.Id, OperateType = "æå -åå§è®°å½", InsertTime = DateTime.Now, StockId = stockDetail.StockId, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, OrderNo = stockDetail.OrderNo, BatchNo = stockDetail.BatchNo, ProductionDate = stockDetail.ProductionDate, EffectiveDate = stockDetail.EffectiveDate, SerialNumber = stockDetail.SerialNumber, StockQuantity = stockDetail.StockQuantity, OutboundQuantity = stockDetail.OutboundQuantity, Status = stockDetail.Status, Unit = stockDetail.Unit, InboundOrderRowNo = stockDetail.InboundOrderRowNo, SupplyCode = stockDetail.SupplyCode, FactoryArea = stockDetail.FactoryArea, WarehouseCode = stockDetail.WarehouseCode, Remark = $"æå ååå§è®°å½ï¼åæ¡ç ï¼{request.Barcode}ï¼åæ°éï¼{stockDetail.StockQuantity}ï¼åºåºæ°éï¼{actualOutboundQuantity}ï¼æä½è ï¼{request.Operator}" }; _stockDetailHistoryService.Repository.AddData(originalHistoryRecord); // ä¿åå©ä½é¨åå°åå²è®°å½ decimal remainingQuantity = stockDetail.StockQuantity - actualOutboundQuantity; if (remainingQuantity > 0) { // æ´æ°ååºåæç» stockDetail.StockQuantity = remainingQuantity; stockDetail.Remark = $"æå åæ´æ°ï¼åæ¡ç ï¼{request.Barcode}ï¼æ°æ°éï¼{remainingQuantity}ï¼æä½è ï¼{request.Operator}"; _stockDetailService.Repository.UpdateData(stockDetail); } // è®°å½æå åå¨ Dt_StockQuantityChangeRecord unpackChangeRecord = new Dt_StockQuantityChangeRecord { StockDetailId = stockDetail.Id, PalleCode = stockInfo.PalletCode, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, BatchNo = stockDetail.BatchNo, OriginalSerilNumber = request.Barcode, NewSerilNumber = newBarcode, OrderNo = request.OrderNo, TaskNum = taskNum, ChangeType = (int)StockChangeTypeEnum.Outbound, ChangeQuantity = -actualOutboundQuantity, BeforeQuantity = beforeQuantity, AfterQuantity = beforeQuantity - actualOutboundQuantity, SupplyCode = stockDetail.SupplyCode, WarehouseCode = stockDetail.WarehouseCode, Remark = $"æå åºåºï¼åæ¡ç ï¼{request.Barcode}ï¼æ°æ¡ç ï¼{newBarcode}ï¼åºåºæ°éï¼{actualOutboundQuantity}ï¼å©ä½ï¼{remainingQuantity}ï¼æä½è ï¼{request.Operator}" }; _stockChangeService.Repository.AddData(unpackChangeRecord); return newBarcode; } /// <summary> /// æ§è¡å®æ´åºåºæä½ï¼ä¸æå ï¼ /// </summary> private void PerformFullOutboundOperation(Dt_StockInfoDetail stockDetail, Dt_StockInfo stockInfo, decimal actualOutboundQuantity, OutboundCompleteRequestDTO request, decimal beforeQuantity, int taskNum) { // ä¿ååºåæç»å°åå²è®°å½ Dt_StockInfoDetail_Hty historyRecord = new Dt_StockInfoDetail_Hty { SourceId = stockDetail.Id, OperateType = "åºåºå®æ", InsertTime = DateTime.Now, StockId = stockDetail.StockId, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, OrderNo = stockDetail.OrderNo, BatchNo = stockDetail.BatchNo, ProductionDate = stockDetail.ProductionDate, EffectiveDate = stockDetail.EffectiveDate, SerialNumber = stockDetail.SerialNumber, StockQuantity = stockDetail.StockQuantity, OutboundQuantity = stockDetail.OutboundQuantity + actualOutboundQuantity, Status = stockDetail.Status, Unit = stockDetail.Unit, InboundOrderRowNo = stockDetail.InboundOrderRowNo, SupplyCode = stockDetail.SupplyCode, FactoryArea = stockDetail.FactoryArea, WarehouseCode = stockDetail.WarehouseCode, Remark = $"åºåºå®æå é¤ï¼æ¡ç ï¼{request.Barcode}ï¼åæ°éï¼{stockDetail.StockQuantity}ï¼åºåºæ°éï¼{actualOutboundQuantity}ï¼æä½è ï¼{request.Operator}" }; _stockDetailHistoryService.Repository.AddData(historyRecord); // å é¤åºåæç»è®°å½ _stockDetailService.Repository.DeleteData(stockDetail); // è®°å½åºååå¨ Dt_StockQuantityChangeRecord changeRecord = new Dt_StockQuantityChangeRecord { StockDetailId = stockDetail.Id, PalleCode = stockInfo.PalletCode, MaterielCode = stockDetail.MaterielCode, MaterielName = stockDetail.MaterielName, BatchNo = stockDetail.BatchNo, OriginalSerilNumber = request.Barcode, NewSerilNumber = "", OrderNo = request.OrderNo, TaskNum = taskNum, ChangeType = (int)StockChangeTypeEnum.Outbound, ChangeQuantity = -actualOutboundQuantity, BeforeQuantity = beforeQuantity, AfterQuantity = 0, SupplyCode = stockDetail.SupplyCode, WarehouseCode = stockDetail.WarehouseCode, Remark = $"åºåºå®æå é¤åºåæç»ï¼æ¡ç ï¼{request.Barcode}ï¼åºåºæ°éï¼{actualOutboundQuantity}ï¼æä½è ï¼{request.Operator}" }; _stockChangeService.Repository.AddData(changeRecord); } /// <summary> /// çææ°çæ¡ç /// </summary> /// <returns>æ°æ¡ç </returns> private string GenerateNewBarcode() { // ä½¿ç¨æ¶é´æ³åéæºæ°çæå¯ä¸æ¡ç string newBarcode = string.Empty; //todo éæ°çææ¡ç é»è¾ return newBarcode; } /// <summary> /// æ´æ°è¯¥æçè¯¥ç©æçææéå®è®°å½ç累计已åºåºæ°é /// </summary> /// <param name="stockId">åºåID</param> /// <param name="materielCode">ç©æç¼å·</param> /// <param name="batchNo">æ¹æ¬¡å·</param> /// <param name="actualOutboundQuantity">æ¬æ¬¡å®é åºåºæ°é</param> /// <returns></returns> private void UpdateLockInfoAllocatedQuantity(int stockId, string materielCode, string batchNo, decimal actualOutboundQuantity) { // æ¥è¯¢è¯¥æçè¯¥ç©æçææéå®è®°å½ List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoService.Repository.QueryData(x => x.StockId == stockId && x.MaterielCode == materielCode && x.BatchNo == batchNo); if (lockRecords != null && lockRecords.Any()) { // æ´æ°ææç¸å ³è®°å½çAllocatedQuantity foreach (var record in lockRecords) { record.AllocatedQuantity += actualOutboundQuantity; } // æ¹éæ´æ° _outboundLockInfoService.Repository.UpdateData(lockRecords); } } /// <summary> /// æ£æ¥åºåºåæ¯å¦å®æ /// </summary> public bool CheckOutboundOrderCompleted(string orderNo) { Dt_OutboundOrder outboundOrder = _outboundOrderService.Repository.QueryFirst(x => x.OrderNo == orderNo); if (outboundOrder == null) return false; List<Dt_OutboundOrderDetail> details = _detailService.Repository.QueryData(x => x.OrderId == outboundOrder.Id); // æ£æ¥æææç»çå·²åºæ°éæ¯å¦é½çäºåæ®æ°é return details.All(x => x.OverOutQuantity >= x.OrderQuantity); } } } ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundController.cs
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,73 @@  using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using WIDESEA_Core; using WIDESEA_DTO.CalcOut; namespace WIDESEA_WMSServer.Controllers.Outbound { [Route("api/[controller]")] [ApiController] public class OutboundController : ControllerBase { private readonly WIDESEA_IOutboundService.IOutboundService _outboundService; public OutboundController(WIDESEA_IOutboundService.IOutboundService outboundService) { _outboundService = outboundService; } /// <summary> /// 忣åºåºæä½ /// </summary> /// <param name="request">忣åºåºè¯·æ±</param> /// <returns>忣åºåºååº</returns> [HttpPost, Route("ProcessPickingOutbound"), AllowAnonymous] public WebResponseContent ProcessPickingOutbound([FromBody] PickingOutboundRequestDTO request) { try { if (!ModelState.IsValid) return WebResponseContent.Instance.Error(string.Join("; ", ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage))); return _outboundService.ProcessPickingOutbound(request); } catch (Exception ex) { return WebResponseContent.Instance.Error($"忣åºåºæä½å¤±è´¥: {ex.Message}"); } } [HttpPost, Route("CompleteOutboundWithBarcode"), AllowAnonymous] public WebResponseContent CompleteOutboundWithBarcode([FromBody] OutboundCompleteRequestDTO request) { try { if (!ModelState.IsValid) return WebResponseContent.Instance.Error(string.Join("; ", ModelState.Values .SelectMany(v => v.Errors) .Select(e => e.ErrorMessage))); return _outboundService.CompleteOutboundWithBarcode(request); } catch (Exception ex) { return WebResponseContent.Instance.Error($"åºåºæ«ææä½å¤±è´¥: {ex.Message}"); } } [HttpPost, Route("QueryPickingTasks"), AllowAnonymous] public WebResponseContent QueryPickingTasks(string palletCode, string orderNo) { return _outboundService.QueryPickingTasks(palletCode, orderNo); } [HttpPost, Route("QueryPickedList"), AllowAnonymous] public WebResponseContent QueryPickedList(string orderNo, string palletCode) { return _outboundService.QueryPickedList(orderNo, palletCode); } } }