pan
2025-11-11 c4e1a656954799267cbd61d3de3a040e8dc8e46a
提交出库
已添加9个文件
已修改28个文件
2332 ■■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/extension/inbound/inboundOrder.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/router/index.js 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/router/viewGird.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue 354 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/SplitPackageModal.vue 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-wal 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-wal 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/InvokeMESService.cs 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/MaterielToMesService.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/OutLockStockStatusEnum.cs 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Inbound/PalletSumQuantityDTO.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IBasicService/IInvokeMESService.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundOrderService.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/ISplitPackageService.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Dt_FeedbackToMes.cs 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfoDetail.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs 304 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderService.cs 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 386 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/ESSController.cs 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundOrderController.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/inboundOrder.js
@@ -72,8 +72,9 @@
    try {
      console.log('发起分批入库请求,参数:', { inboundOrderNos});
      const response = await http.post('/api/InboundOrder/BatchInbound', {
        inboundOrderNos: inboundOrderNos,
      const response = await http.post('/api/InboundOrder/BatchOrderFeedbackToMes', {
        orderNos: inboundOrderNos,
        inout:1
      });
      const { status, message, data } = response;
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue
@@ -14,13 +14,20 @@
              <span>已选中 {{ selection.length }} é¡¹</span>
            </el-col>
            <el-col :span="8">
              <el-link
          <!--     <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px"
                @click="lockstocks"
                >锁定库存</el-link
              >
                >锁定库存</el-link> -->
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px"
                @click="handleOpenPicking"
                >拣选</el-link>
              <el-link
                type="primary"
                size="small"
@@ -310,6 +317,13 @@
          });
        });
    },
    // æ‰“开拣选页面
   handleOpenPicking() {
      this.$router.push({
        path: '/outbound/picking',
        query: { orderId: this.row.id }
      })
    },
    outbound() {
      if (this.selection.length === 0) {
        return this.$message.error("请选择单据明细");
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/router/index.js
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/router/viewGird.js
@@ -73,6 +73,12 @@
    path: '/outboundOrderDetail',
    name: 'outboundOrderDetail',
    component: () => import('@/views/outbound/outboundOrderDetail.vue')
  },
  {
  path: '/outbound/picking',
  name: 'PickingConfirm',
  component: () => import('@/views/outbound/PickingConfirm.vue'),
  meta: { title: '拣选确认', keepAlive: false }
  }, {
    path: '/stockInfo',
    name: 'stockInfo',
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,354 @@
<template>
  <div class="picking-confirm">
    <div class="page-header">
      <el-page-header @back="goBack">
        <template #content>
          <span class="title">出库拣选确认 - {{ orderInfo.orderNo }}</span>
        </template>
      </el-page-header>
    </div>
    <el-row :gutter="20" class="main-content">
      <el-col :span="8">
        <div class="scan-section">
          <el-card header="扫码区域">
            <el-form label-width="100px" size="small">
              <el-form-item label="托盘条码">
                <el-input
                  v-model="scanForm.palletCode"
                  placeholder="扫描或输入托盘条码"
                  @keyup.enter="handlePalletScan"
                  clearable
                >
                  <template #append>
                    <el-button @click="handlePalletScan">确认</el-button>
                  </template>
                </el-input>
              </el-form-item>
              <el-form-item label="物料条码">
                <el-input
                  v-model="scanForm.barcode"
                  placeholder="扫描或输入物料条码"
                  @keyup.enter="handleBarcodeScan"
                  :disabled="!currentPallet"
                  clearable
                >
                  <template #append>
                    <el-button @click="handleBarcodeScan" :disabled="!currentPallet">确认</el-button>
                  </template>
                </el-input>
              </el-form-item>
              <el-form-item label="拣选数量">
                <el-input-number
                  v-model="scanForm.quantity"
                  :min="1"
                  :max="maxPickQuantity"
                  :disabled="!currentLockInfo"
                />
              </el-form-item>
            </el-form>
            <div class="current-info" v-if="currentPallet">
              <p>当前托盘: {{ currentPallet.palletCode }}</p>
              <p>货位: {{ currentPallet.locationCode }}</p>
              <p>状态: {{ currentPallet.statusText }}</p>
            </div>
          </el-card>
          <div class="action-buttons">
            <el-button
              type="warning"
              @click="handleBackToStock"
              :disabled="!currentPallet"
              style="margin-bottom: 10px;"
            >
              å›žåº“
            </el-button>
            <el-button
              type="success"
              @click="handleDirectOutbound"
              :disabled="!currentPallet"
              style="margin-bottom: 10px;"
            >
              ç›´æŽ¥å‡ºåº“
            </el-button>
            <el-button
              type="primary"
              @click="handleOpenSplit"
              :disabled="!currentLockInfo"
            >
              æ‹†åŒ…
            </el-button>
          </div>
        </div>
      </el-col>
      <el-col :span="16">
        <el-card header="拣选结果">
          <div class="summary-info">
            <el-alert
              :title="`未拣货: ${unpickedCount} æ¡, ${unpickedQuantity} ä¸ª`"
              type="warning"
              :closable="false"
            />
          </div>
          <vol-table
            :data="pickedList"
            :columns="pickedColumns"
            :pagination="false"
            :height="400"
          >
            <template #action="{ row }">
              <el-button type="text" @click="handleCancelPick(row)">撤销</el-button>
            </template>
          </vol-table>
        </el-card>
      </el-col>
    </el-row>
    <!-- æ‹†åŒ…弹窗 -->
    <vol-box
      v-model="splitVisible"
      title="拆包操作"
      :width="600"
      :height="500"
    >
      <SplitPackageModal
        v-if="splitVisible"
        :lockInfo="currentLockInfo"
        @success="handleSplitSuccess"
        @close="splitVisible = false"
      />
    </vol-box>
  </div>
</template>
<script>
import SplitPackageModal from './SplitPackageModal.vue'
export default {
  components: { SplitPackageModal },
  data() {
    return {
      orderInfo: {},
      scanForm: {
        palletCode: '',
        barcode: '',
        quantity: 1
      },
      currentPallet: null,
      currentLockInfo: null,
      pickedList: [],
      pickedColumns: [
        { field: 'barcode', title: '物料条码', width: 150 },
        { field: 'materielCode', title: '物料编码', width: 120 },
        { field: 'materielName', title: '物料名称', width: 150 },
        { field: 'pickQuantity', title: '拣选数量', width: 100 },
        { field: 'palletCode', title: '托盘编号', width: 120 },
        { field: 'pickTime', title: '拣选时间', width: 160 },
        { field: 'operator', title: '操作人', width: 100 },
        { field: 'action', title: '操作', width: 80, slot: true }
      ],
      splitVisible: false,
      maxPickQuantity: 0
    }
  },
  computed: {
    unpickedCount() {
      return this.orderInfo.unpickedCount || 0
    },
    unpickedQuantity() {
      return this.orderInfo.unpickedQuantity || 0
    }
  },
  methods: {
    goBack() {
      this.$router.back()
    },
    async loadOrderInfo() {
      const orderId = this.$route.query.orderId
      if (!orderId) return
      try {
        const result = await this.http.post(`api/OutboundOrder/GetById?id=${orderId}`)
        if (result.status) {
          this.orderInfo = result.data
        }
      } catch (error) {
        this.$message.error('加载出库单信息失败')
      }
    },
    async handlePalletScan() {
      if (!this.scanForm.palletCode) {
        this.$message.warning('请输入托盘条码')
        return
      }
      try {
        const result = await this.http.get(
          `api/OutboundPicking/GetPalletOutboundStatus?palletCode=${this.scanForm.palletCode}`
        )
        if (result.status) {
          this.currentPallet = result.data
          this.loadPalletLockInfo()
          this.$message.success(`托盘 ${this.scanForm.palletCode} è¯†åˆ«æˆåŠŸ`)
        } else {
          this.$message.error(result.message)
        }
      } catch (error) {
        this.$message.error('托盘识别失败')
      }
    },
    async loadPalletLockInfo() {
      if (!this.currentPallet) return
      try {
        const result = await this.http.get(
          `api/OutboundPicking/GetPalletLockInfos?palletCode=${this.currentPallet.palletCode}`
        )
        if (result.status && result.data.length > 0) {
          this.currentLockInfo = result.data[0]
          this.maxPickQuantity = this.currentLockInfo.assignQuantity - this.currentLockInfo.pickedQty
        }
      } catch (error) {
        console.error('加载锁定信息失败:', error)
      }
    },
    async handleBarcodeScan() {
      // å®žçŽ°æ‰«ç ç¡®è®¤é€»è¾‘
      if (!this.scanForm.barcode) {
        this.$message.warning('请输入物料条码')
        return
      }
      try {
        const request = {
          barcode: this.scanForm.barcode,
          quantity: this.scanForm.quantity,
          palletCode: this.currentPallet.palletCode,
          orderId: this.orderInfo.id
        }
        const result = await this.http.post('api/OutboundPicking/ConfirmPicking', request)
        if (result.status) {
          this.$message.success('拣选确认成功')
          this.scanForm.barcode = ''
          this.scanForm.quantity = 1
          this.loadPickedHistory()
          this.loadOrderInfo()
        } else {
          this.$message.error(result.message)
        }
      } catch (error) {
        this.$message.error('拣选确认失败')
      }
    },
    async handleBackToStock() {
      if (!this.currentPallet) return
      try {
        await this.$confirm(`确定将托盘 ${this.currentPallet.palletCode} å›žåº“吗?`, '提示', {
          type: 'warning'
        })
        const result = await this.http.post('api/BackToStock/GenerateBackToStockTask', {
          palletCode: this.currentPallet.palletCode,
          currentLocation: '拣选位'
        })
        if (result.status) {
          this.$message.success('回库任务已生成')
          this.resetCurrentPallet()
        }
      } catch (error) {
        // ç”¨æˆ·å–消
      }
    },
    async handleDirectOutbound() {
      if (!this.currentPallet) return
      try {
        await this.$confirm(`确定将托盘 ${this.currentPallet.palletCode} ç›´æŽ¥å‡ºåº“吗?`, '提示', {
          type: 'warning'
        })
        const result = await this.http.post('api/OutboundPicking/DirectOutbound', {
          palletCode: this.currentPallet.palletCode
        })
        if (result.status) {
          this.$message.success('直接出库成功')
          this.resetCurrentPallet()
          this.loadOrderInfo()
        }
      } catch (error) {
        // ç”¨æˆ·å–消
      }
    },
    handleOpenSplit() {
      if (!this.currentLockInfo) {
        this.$message.warning('请先选择锁定信息')
        return
      }
      this.splitVisible = true
    },
    handleSplitSuccess() {
      this.$message.success('拆包成功')
      this.loadPalletLockInfo()
    },
    resetCurrentPallet() {
      this.currentPallet = null
      this.currentLockInfo = null
      this.scanForm.palletCode = ''
    },
    async loadPickedHistory() {
      const orderId = this.$route.query.orderId
      if (!orderId) return
      try {
        const result = await this.http.get(`api/OutboundPicking/GetPickingHistory?orderId=${orderId}`)
        if (result.status) {
          this.pickedList = result.data
        }
      } catch (error) {
        console.error('加载拣选历史失败:', error)
      }
    },
    async handleCancelPick(row) {
      try {
        await this.$confirm('确定撤销这条拣选记录吗?', '提示', { type: 'warning' })
        const result = await this.http.post('api/OutboundPicking/CancelPicking', {
          pickingHistoryId: row.id
        })
        if (result.status) {
          this.$message.success('撤销成功')
          this.loadPickedHistory()
          this.loadOrderInfo()
        }
      } catch (error) {
        // ç”¨æˆ·å–消
      }
    }
  },
  mounted() {
    this.loadOrderInfo()
    this.loadPickedHistory()
  }
}
</script>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/SplitPackageModal.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
<template>
  <div class="split-package-modal">
    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
      <el-form-item label="原条码" prop="originalBarcode">
        <el-input v-model="form.originalBarcode" readonly />
      </el-form-item>
      <el-form-item label="物料编码" prop="materielCode">
        <el-input v-model="form.materielCode" readonly />
      </el-form-item>
      <el-form-item label="拆包数量" prop="splitQuantity" required>
        <el-input-number
          v-model="form.splitQuantity"
          :min="1"
          :max="maxSplitQuantity"
          style="width: 100%"
        />
        <div class="tip">最大可拆数量: {{ maxSplitQuantity }}</div>
      </el-form-item>
      <el-form-item label="操作人" prop="operator">
        <el-input v-model="form.operator" />
      </el-form-item>
    </el-form>
    <div class="modal-footer">
      <el-button @click="$emit('close')">取消</el-button>
      <el-button type="primary" @click="handleConfirm" :loading="loading">
        ç¡®è®¤æ‹†åŒ…
      </el-button>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    lockInfo: {
      type: Object,
      default: null
    }
  },
  data() {
    return {
      form: {
        outStockLockInfoId: 0,
        originalBarcode: '',
        materielCode: '',
        splitQuantity: 1,
        operator: ''
      },
      rules: {
        splitQuantity: [
          { required: true, message: '请输入拆包数量', trigger: 'blur' },
          { type: 'number', min: 1, message: '拆包数量必须大于0', trigger: 'blur' }
        ]
      },
      loading: false
    }
  },
  computed: {
    maxSplitQuantity() {
      if (!this.lockInfo) return 0
      return this.lockInfo.assignQuantity - this.lockInfo.pickedQty
    }
  },
  watch: {
    lockInfo: {
      immediate: true,
      handler(newVal) {
        if (newVal) {
          this.form.outStockLockInfoId = newVal.id
          this.form.originalBarcode = newVal.currentBarcode
          this.form.materielCode = newVal.materielCode
          this.form.operator = '当前用户' // å®žé™…应从用户信息获取
        }
      }
    }
  },
  methods: {
    async handleConfirm() {
      const valid = await this.$refs.formRef.validate()
      if (!valid) return
      if (this.form.splitQuantity > this.maxSplitQuantity) {
        this.$message.warning(`拆包数量不能大于${this.maxSplitQuantity}`)
        return
      }
      this.loading = true
      try {
        const result = await this.http.post('api/OutboundPicking/SplitPackage', this.form)
        if (result.status) {
          this.$message.success('拆包成功')
          this.$emit('success', result.data)
          this.$emit('close')
        } else {
          this.$message.error(result.message)
        }
      } catch (error) {
        this.$message.error('拆包操作失败')
      } finally {
        this.loading = false
      }
    }
  }
}
</script>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/CodeChunks.db-wal
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/.vs/WIDESEA_WMSServer/CopilotIndices/17.14.878.3237/SemanticSymbols.db-wal
Binary files differ
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/InvokeMESService.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Org.BouncyCastle.Asn1.Ocsp;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,10 +10,13 @@
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_DTO.Basic;
using WIDESEA_DTO.Inbound;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_Model.Models;
namespace WIDESEA_BasicService
{
@@ -22,10 +26,19 @@
        private readonly ILogger<InvokeMESService> _logger;
        private string UserName = "12312";
        private string Password = "1";
        public InvokeMESService(IHttpClientFactory httpClientFactory, ILogger<InvokeMESService> logger)
        private readonly IRepository<Dt_FeedbackToMes> _feedbacktomesRepository;
        private readonly IRepository<Dt_StockInfoDetail> _stockInfoDetailRepository;
        private readonly IRepository<Dt_StockInfo> _stockInfoRepository;
        private readonly IRepository<Dt_InboundOrder> _inboundOrderRepository;
        public InvokeMESService(IHttpClientFactory httpClientFactory, ILogger<InvokeMESService> logger, IRepository<Dt_FeedbackToMes> feedbacktomesRepository, IRepository<Dt_StockInfoDetail> stockInfoDetailRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_InboundOrder> inboundOrderRepository)
        {
            _httpClientFactory = httpClientFactory;
            _logger = logger;
            _feedbacktomesRepository = feedbacktomesRepository;
            _stockInfoDetailRepository = stockInfoDetailRepository;
            _stockInfoRepository = stockInfoRepository;
            _inboundOrderRepository = inboundOrderRepository;
        }
        public async Task<ResponseModel> FeedbackInbound(  FeedbackInboundRequestModel model)
@@ -141,6 +154,84 @@
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="orderNos"></param>
        /// <param name="inout">入库传1  å‡ºåº“ä¼ 2</param>
        /// <returns></returns>
        public async Task<WebResponseContent> BatchOrderFeedbackToMes(List<string> orderNos, int inout)
        {
            if (inout == 1)
            {
                foreach (var orderNo in orderNos)
                {
                    try
                    {
                        var stockinfos = _stockInfoRepository.Db.Queryable<Dt_StockInfo>("info").Where(info => info.StockStatus == 6)
                                        .Where(it => SqlFunc.Subqueryable<Dt_StockInfoDetail>().Where(s => s.StockId == it.Id && s.OrderNo == orderNo).Any())
                                        .ToList();
                        var feeds = _feedbacktomesRepository.Db.Queryable<Dt_FeedbackToMes>().Where(x => x.OrderNo == orderNo && x.ReportStatus == 1).Select(o => o.PalletCode).ToList();
                        var unreports = stockinfos.Where(x => !feeds.Contains(x.PalletCode)).ToList();
                        if (unreports!=null && !unreports.Any()) {
                            return WebResponseContent.Instance.Error("没有需要回传的数据");
                        }
                        foreach (var item in unreports)
                        {
                            var lists = _stockInfoDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.StockId == item.Id).ToList();
                            if (lists.Any())
                            {
                                var inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().First(x => x.InboundOrderNo == lists.FirstOrDefault().OrderNo);
                                if (inboundOrder != null)
                                {
                                    var feedmodel = new FeedbackInboundRequestModel
                                    {
                                        reqCode = Guid.NewGuid().ToString(),
                                        reqTime = DateTime.Now.ToString(),
                                        business_type = inboundOrder.BusinessType,
                                        factoryArea = inboundOrder.FactoryArea,
                                        operationType = 1,
                                        orderNo = inboundOrder.UpperOrderNo,
                                        status = inboundOrder.OrderStatus,
                                        details = new List<FeedbackInboundDetailsModel>()
                                    };
                                    var groupedData = lists.GroupBy(item => new { item.MaterielCode, item.SupplyCode, item.BatchNo, item.InboundOrderRowNo, item.Unit, item.WarehouseCode })
                                       .Select(group => new FeedbackInboundDetailsModel
                                       {
                                           materialCode = group.Key.MaterielCode,
                                           supplyCode = group.Key.SupplyCode,
                                           batchNo = group.Key.BatchNo,
                                           lineNo = group.Key.InboundOrderRowNo,
                                           // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                                           warehouseCode = "1072",
                                           unit = group.Key.Unit,
                                           barcodes = group.Select(row => new FeedbackBarcodesModel
                                           {
                                               barcode = row.Barcode,
                                               qty = row.StockQuantity
                                           }).ToList()
                                       }).ToList();
                                    feedmodel.details = groupedData;
                                    var result = await FeedbackInbound(feedmodel);
                                    if (result != null && result.code == 200)
                                    {
                                        _feedbacktomesRepository.Db.Insertable(new Dt_FeedbackToMes { OrderNo = orderNo, PalletCode = item.PalletCode, ReportStatus = 1 }).ExecuteCommand();
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        _logger.LogInformation("InvokeMESService  BatchOrderFeedbackToMes å›žå†™MES失败:  " + ex.Message);
                        return WebResponseContent.Instance.Error(ex.Message);
    }
}
            }
            return WebResponseContent.Instance.OK();
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/MaterielToMesService.cs
@@ -48,5 +48,8 @@
            
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Common/StockEnum/OutLockStockStatusEnum.cs
@@ -7,6 +7,21 @@
namespace WIDESEA_Common.StockEnum
{
    //public enum OutLockStockStatusEnum
    //{
    //    å·²åˆ†é… = 1,
    //    å‡ºåº“中 = 2,
    //    å·²æ‹£é€‰ = 3,
    //    å·²å‡ºåº“ = 4,
    //    å·²å›žåº“ = 5
    //}
    public enum SplitPackageStatusEnum
    {
        å·²æ‹†åŒ… = 1,
        å·²æ‹£é€‰ = 2,
        å·²å›žåº“ = 3
    }
    public enum OutLockStockStatusEnum
    {
        [Description("已分配")]
@@ -15,11 +30,26 @@
        [Description("出库中")]
        å‡ºåº“中 = 1,
        [Description("部分拣选")]
        éƒ¨åˆ†æ‹£é€‰ = 2,
        [Description("已拣选")]
        å·²æ‹£é€‰ =3,
        [Description("已出库")]
        å·²å‡ºåº“ = 4,
        [Description("出库完成")]
        å‡ºåº“完成 = 2,
        å‡ºåº“完成 = 5,
        [Description("拣选完成")]
        æ‹£é€‰å®Œæˆ = 3,
        æ‹£é€‰å®Œæˆ =6,
        [Description("回库中")]
        å›žåº“中 = 7,
        [Description("已回库")]
        å·²å›žåº“ =8,
        [Description("撤销")]
        æ’¤é”€ = 99
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Inbound/PalletSumQuantityDTO.cs
@@ -14,4 +14,10 @@
        public string UniqueUnit { get; set; }
    }
    public class BatchOrderFeedbackToMesDto
    {
       public List<string> orderNos { get; set; }
        public int inout { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs
@@ -12,4 +12,49 @@
        public int pageNo { get; set; }
    }
    // æ‹†åŒ…请求
    public class SplitPackageRequest
    {
        public int OutStockLockInfoId { get; set; }
        public string MaterielCode { get; set; }
        public decimal SplitQuantity { get; set; }
        public string Operator { get; set; }
    }
    public class PickingConfirmRequest
    {
        public int OrderDetailId { get; set; }
        public string Barcode { get; set; }
        public string MaterielCode { get; set; }
        public decimal PickQuantity { get; set; }
        public string LocationCode { get; set; }
        public string PalletCode { get; set; }
    }
    public class DirectOutboundRequest
    {
        public string PalletCode { get; set; }
    }
    public class CancelPickingRequest
    {
        public int PickingHistoryId { get; set; }
    }
    public class BackToStockRequest
    {
        public string PalletCode { get; set; }
        public string CurrentLocation { get; set; }
        public string TargetLocation { get; set; }
        public string Operator { get; set; }
    }
    public class BackToStockCompleteRequest
    {
        public string TaskNum { get; set; }
        public string TargetLocation { get; set; }
        public DateTime CompleteTime { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IBasicService/IInvokeMESService.cs
@@ -19,5 +19,8 @@
        Task<string> GetToken(String username, string password);
        Task<ResponseModel> NewMaterielToMes(MaterielToMesDTO model);
        Task<WebResponseContent> BatchOrderFeedbackToMes(List<string> orderNos, int inout);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs
@@ -19,7 +19,11 @@
        /// </summary>
        IRepository<Dt_OutStockLockInfo> Repository { get; }
        Task<List<Dt_OutStockLockInfo>> GetByOrderDetailId(int orderDetailId);
        Task<List<Dt_OutStockLockInfo>> GetByPalletCode(string palletCode, int? status = null);
        Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, Dt_StockInfo outStock, decimal assignQuantity, int? taskNum = null);
        Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, Dt_StockInfo outStock, decimal assignQuantity, string barcode = null, int? taskNum = null);
        Task<List<Dt_OutStockLockInfo>> GetPalletLockInfos(string palletCode);
        Task<WebResponseContent> UpdateLockInfoBarcode(int lockInfoId, string newBarcode);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundOrderService.cs
@@ -17,5 +17,7 @@
        IRepository<Dt_OutboundOrder> Repository { get; }
        WebResponseContent ReceiveOutboundOrder(Dt_OutboundOrder model, int operateType);
        Task<WebResponseContent> GetById(int id);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Outbound;
using WIDESEA_Model.Models;
namespace WIDESEA_IOutboundService
{
    public interface IOutboundPickingService : IService<Dt_PickingRecord>
    {
        IRepository<Dt_PickingRecord> Repository { get; }
        Task<WebResponseContent> CancelPicking(CancelPickingRequest request);
        Task<WebResponseContent> ConfirmPicking(PickingConfirmRequest request);
        Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId);
        Task<WebResponseContent> GetPalletOutboundStatus(string palletCode);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/ISplitPackageService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Outbound;
using WIDESEA_Model.Models;
namespace WIDESEA_IOutboundService
{
    public interface ISplitPackageService : IService<Dt_SplitPackageRecord>
    {
        IRepository<Dt_SplitPackageRecord> Repository { get; }
        Task<WebResponseContent> SplitPackage(SplitPackageRequest request);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_ITaskInfoService/ITaskService.cs
@@ -43,5 +43,8 @@
        Task<WebResponseContent> PalletOutboundTask(string endStation, string palletCode = "");
        Task<WebResponseContent> TaskCompleted(string taskNum);
        WebResponseContent GenerateOutboundTasks(int[] keys);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Dt_FeedbackToMes.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.DB.Models;
namespace WIDESEA_Model.Models
{
    // <summary>
    ///
    ///</summary>
    [SugarTable("Dt_FeedbackToMes")]
    public class Dt_FeedbackToMes : BaseEntity
    {
        /// <summary>
        /// å¤‡  æ³¨:
        /// é»˜è®¤å€¼:
        ///</summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnDescription = "主键")]
        public int Id { get; set; }
        /// <summary>
        /// å¤‡  æ³¨:
        /// é»˜è®¤å€¼:
        ///</summary>
        [SugarColumn(ColumnName = "OrderNo")]
        public string OrderNo { get; set; } = null!;
        /// <summary>
        /// å¤‡  æ³¨:
        /// é»˜è®¤å€¼:
        ///</summary>
        [SugarColumn(ColumnName = "PalletCode")]
        public string PalletCode { get; set; } = null!;
        /// <summary>
        /// å¤‡  æ³¨:
        /// é»˜è®¤å€¼:
        ///</summary>
        [SugarColumn(ColumnName = "ReportStatus")]
        public int ReportStatus { get; set; }
        /// <summary>
        /// å¤‡  æ³¨:
        /// é»˜è®¤å€¼:
        ///</summary>
        [SugarColumn(ColumnName = "Remark")]
        public string Remark { get; set; } = null!;
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs
@@ -82,6 +82,11 @@
        public decimal AssignQuantity {  get; set; }
        /// <summary>
        /// å·²æ‹£é€‰æ•°é‡
        /// </summary>
        [SugarColumn(IsNullable = false, ColumnDescription = "已拣选数量")]
        public decimal PickedQty { get; set; } //
        /// <summary>
        /// è´§ä½ç¼–号
        /// </summary>
        [SugarColumn(IsNullable = true, Length = 50, ColumnDescription = "货位编号")]
@@ -104,9 +109,21 @@
        public int? TaskNum { get; set; }
        /// <summary>
        /// çŠ¶æ€
        /// çŠ¶æ€ çŠ¶æ€ï¼š0-已分配 1-部分拣选 2-已拣选 3-已完成
        /// </summary>
        [SugarColumn(IsNullable = false, ColumnDescription = "状态")]
        public int Status { get; set; }
        [SugarColumn(Length = 100)]
        public string CurrentBarcode { get; set; } // å½“前条码(拆包后可能变化)
        public decimal OriginalLockQuantity { get; set; } // åŽŸå§‹é”å®šæ•°é‡
        public int IsSplitted { get; set; } // æ˜¯å¦å·²æ‹†åŒ… 0-否 1-是
        public int? ParentLockId { get; set; }
        [Navigate(NavigateType.OneToOne, nameof(StockInfo))]//一对一 SchoolId是StudentA类里面的
        public Dt_StockInfo StockInfo { get; set; } //不能赋值只能是null
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,95 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.DB.Models;
namespace WIDESEA_Model.Models
{
    /// <summary>
    /// æ‹£é€‰è®°å½•表
    /// </summary>
    [SugarTable(nameof(Dt_PickingRecord), "拣选记录表")]
    public class Dt_PickingRecord : BaseEntity
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public int OrderDetailId { get; set; }
        [SugarColumn(Length = 100)]
        public string Barcode { get; set; }
        public decimal PickQuantity { get; set; }
        public DateTime PickTime { get; set; } = DateTime.Now;
        [SugarColumn(Length = 50)]
        public string Operator { get; set; }
        [SugarColumn(Length = 50)]
        public string LocationCode { get; set; }
        public int StockId { get; set; }
    }
    /// <summary>
    /// å›žåº“记录表
    /// </summary>
    [SugarTable("Dt_ReturnStockRecord")]
    public class Dt_ReturnStockRecord : BaseEntity
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public long Id { get; set; }
        public string TaskNo { get; set; }
        public string PalletId { get; set; }
        public string LocationId { get; set; }
        public string NewLocationId { get; set; }
        /// <summary>
        /// å›žåº“数量
        /// </summary>
        public decimal ReturnQty { get; set; }
        public DateTime ReturnTime { get; set; }
        /// <summary>
        /// 0-待回库 1-已回库
        /// </summary>
        public int Status { get; set; }
    }
    /// <summary>
    /// æ‹†åŒ…记录表
    /// </summary>
    [SugarTable("Dt_SplitPackageRecord")]
    public class Dt_SplitPackageRecord: BaseEntity
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public int OutStockLockInfoId { get; set; } // å…³è”的出库锁定信息
        public string OriginalBarcode { get; set; } // åŽŸæ¡ç 
        public string NewBarcode { get; set; } // æ–°æ¡ç 
        /// <summary>
        /// æ‹†åˆ†æ•°é‡ï¼ˆæ–°æ¡ç æ•°é‡ï¼‰
        /// </summary>
        public decimal SplitQty { get; set; }
        public string MaterielCode { get; set; } // ç‰©æ–™ç¼–码
        public DateTime SplitTime { get; set; } = DateTime.Now;
        public string Operator { get; set; } // æ“ä½œäºº
        public int Status { get; set; } // çŠ¶æ€ï¼š1-已拆包 2-已拣选 3-已回库
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Stock/Dt_StockInfoDetail.cs
@@ -131,5 +131,8 @@
        /// </summary>
        [SugarColumn(IsNullable = true, ColumnDescription = "备注")]
        public string Remark { get; set; }
        [SugarColumn(IsIgnore = true)]
        public Dt_StockInfo StockInfo { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs
@@ -46,6 +46,7 @@
                OrderDetailId = outboundOrderDetail.Id,
                OrderNo = outboundOrder.OrderNo,
                OrderType = outboundOrder.OrderType,
                OriginalQuantity = outStock.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode).Sum(x => x.StockQuantity),
                Status = taskNum == null ? OutLockStockStatusEnum.已分配.ObjToInt() : OutLockStockStatusEnum.出库中.ObjToInt(),
                StockId = outStock.Id,
@@ -58,5 +59,137 @@
            return outStockLockInfo;
        }
        /// <summary>
        /// åˆ›å»ºå‡ºåº“锁定信息
        /// </summary>
        public Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail outboundOrderDetail, Dt_StockInfo outStock, decimal assignQuantity, string barcode = null, int? taskNum = null)
        {
            // èŽ·å–åº“å­˜æ˜Žç»†ä¸­çš„æ¡ç ä¿¡æ¯ï¼ˆå¦‚æžœæœªæŒ‡å®šæ¡ç ï¼Œä½¿ç”¨ç¬¬ä¸€ä¸ªå¯ç”¨æ¡ç ï¼‰
            var stockDetails = outStock.Details.Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode && x.StockQuantity > x.OutboundQuantity)
                .OrderBy(x => x.ProductionDate).ToList();
            if (!stockDetails.Any())
            {
                throw new Exception($"未找到物料[{outboundOrderDetail.MaterielCode}]的可用库存明细");
            }
            // ç¡®å®šæ¡ç ï¼ˆå¦‚果未指定,使用最早入库的条码)
            var targetBarcode = barcode;
            if (string.IsNullOrEmpty(targetBarcode))
            {
                targetBarcode = stockDetails.First().Barcode;
            }
            // èŽ·å–è¯¥æ¡ç çš„å¯ç”¨æ•°é‡
            var barcodeDetail = stockDetails.FirstOrDefault(x => x.Barcode == targetBarcode);
            if (barcodeDetail == null)
            {
                throw new Exception($"条码[{targetBarcode}]在库存中不存在");
            }
            return new Dt_OutStockLockInfo()
            {
                PalletCode = outStock.PalletCode,
                AssignQuantity = assignQuantity,
                MaterielCode = outboundOrderDetail.MaterielCode,
                BatchNo = outboundOrderDetail.BatchNo ?? outStock.Details.FirstOrDefault()?.BatchNo,
                LocationCode = outStock.LocationCode,
                MaterielName = outboundOrderDetail.MaterielName,
                OrderDetailId = outboundOrderDetail.Id,
                OrderNo = outboundOrder.OrderNo,
                OrderQuantity = outboundOrderDetail.OrderQuantity,
                OriginalQuantity = outStock.Details
                    .Where(x => x.MaterielCode == outboundOrderDetail.MaterielCode)
                    .Sum(x => x.StockQuantity),
                Status = taskNum == null ? (int)OutLockStockStatusEnum.已分配 : (int)OutLockStockStatusEnum.出库中,
                StockId = outStock.Id,
                TaskNum = taskNum,
                Unit = outboundOrderDetail.Unit,
                // æ–°å¢žå­—段赋值
                CurrentBarcode = targetBarcode, // å½“前分配的条码
                OriginalLockQuantity = assignQuantity, // åŽŸå§‹é”å®šæ•°é‡
                IsSplitted = 0 // åˆå§‹æœªæ‹†åŒ…
            };
        }
        /// <summary>
        /// æ ¹æ®è®¢å•明细ID获取出库锁定信息
        /// </summary>
        public async Task<List<Dt_OutStockLockInfo>> GetByOrderDetailId(int orderDetailId)
        {
            return await Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId)
                .OrderBy(x => x.Id)
                .ToListAsync();
        }
        /// <summary>
        /// æ ¹æ®æ‰˜ç›˜ç¼–号获取出库锁定信息
        /// </summary>
        public async Task<List<Dt_OutStockLockInfo>> GetByPalletCode(string palletCode, int? status = null)
        {
            var query = Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.PalletCode == palletCode);
            if (status.HasValue)
            {
                query = query.Where(x => x.Status == status.Value);
            }
            return await query.OrderBy(x => x.Id).ToListAsync();
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
        /// </summary>
        public async Task<List<Dt_OutStockLockInfo>> GetPalletLockInfos(string palletCode)
        {
            return await Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.PalletCode == palletCode && x.Status == (int)OutLockStockStatusEnum.出库中)
                .ToListAsync();
        }
        /// <summary>
        /// æ›´æ–°å‡ºåº“锁定信息的条码(用于拆包操作)
        /// </summary>
        public async Task<WebResponseContent> UpdateLockInfoBarcode(int lockInfoId, string newBarcode)
        {
            try
            {
                var lockInfo = await Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.Id == lockInfoId)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到出库锁定信息");
                // éªŒè¯æ–°æ¡ç æ˜¯å¦å­˜åœ¨
                var stockDetail = await Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == newBarcode &&
                               x.StockId == lockInfo.StockId &&
                               x.MaterielCode == lockInfo.MaterielCode)
                    .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("新条码在库存中不存在");
                // æ›´æ–°æ¡ç å’Œæ‹†åŒ…状态
                lockInfo.CurrentBarcode = newBarcode;
                lockInfo.IsSplitted = 1;
                await Db.Updateable(lockInfo).ExecuteCommandAsync();
                return WebResponseContent.Instance.OK("更新条码成功");
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"更新条码失败: {ex.Message}");
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderDetailService.cs
@@ -1,4 +1,5 @@
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
@@ -21,9 +22,10 @@
        private readonly ILocationInfoService _locationInfoService;
        private readonly IBasicService _basicService;
        private readonly IRecordService _recordService;
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly ILocationStatusChangeRecordService _locationStatusChangeRecordService;
        public OutboundOrderDetailService(IRepository<Dt_OutboundOrderDetail> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IBasicService basicService, IRecordService recordService, ILocationInfoService locationInfoService, ILocationStatusChangeRecordService locationStatusChangeRecordService) : base(BaseDal)
        public OutboundOrderDetailService(IRepository<Dt_OutboundOrderDetail> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IBasicService basicService, IRecordService recordService, ILocationInfoService locationInfoService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IOutboundOrderService outboundOrderService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockService = stockService;
@@ -32,90 +34,259 @@
            _recordService = recordService;
            _locationInfoService = locationInfoService;
            _locationStatusChangeRecordService = locationStatusChangeRecordService;
            _outboundOrderService = outboundOrderService;
        }
        /// <summary>
        ///
        /// åˆ†é…å‡ºåº“库存  æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…
        /// </summary>
        /// <param name="outboundOrderDetails"></param>
        /// <returns></returns>
        public (List<Dt_StockInfo>, List<Dt_OutboundOrderDetail>, List<Dt_OutStockLockInfo>, List<Dt_LocationInfo>) AssignStockOutbound(List<Dt_OutboundOrderDetail> outboundOrderDetails)
        {
            if (!outboundOrderDetails.Any())
            {
                throw new Exception($"未找到出库单明细信息");
                throw new Exception("未找到出库单明细信息");
            }
            if (outboundOrderDetails.GroupBy(x => x.OrderId).Count() > 1)
            {
                throw new Exception($"请勿同时操作多个单据明细");
                throw new Exception("请勿同时操作多个单据明细");
            }
            Dt_OutboundOrder outboundOrder = Repository.Db.Queryable<Dt_OutboundOrder>().Where(x => x.Id == outboundOrderDetails.FirstOrDefault().OrderId).First();
            List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>();
            List<Dt_OutboundOrderDetail> groupDetails = outboundOrderDetails.GroupBy(x => new { x.MaterielCode, x.BatchNo }).Select(x => new Dt_OutboundOrderDetail { OrderQuantity = x.Sum(v => v.OrderQuantity) - x.Sum(v => v.LockQuantity), MaterielCode = x.Key.MaterielCode, BatchNo = x.Key.BatchNo }).ToList();
            var outboundOrder = _outboundOrderService.Db.Queryable<Dt_OutboundOrder>()
                .First(x => x.Id == outboundOrderDetails.First().OrderId);
            List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>();
            List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>();
            List<Dt_LocationInfo> locationInfos = new List<Dt_LocationInfo>();
            // æŒ‰ç‰©æ–™å’Œæ‰¹æ¬¡åˆ†ç»„处理
            var groupDetails = outboundOrderDetails
                .GroupBy(x => new { x.MaterielCode, x.BatchNo })
                .Select(x => new
                {
                    MaterielCode = x.Key.MaterielCode,
                    BatchNo = x.Key.BatchNo,
                    Details = x.ToList(),
                    TotalNeedQuantity = x.Sum(v => v.OrderQuantity - v.OverOutQuantity - v.LockQuantity)
                })
                .Where(x => x.TotalNeedQuantity > 0)
                .ToList();
            foreach (var item in groupDetails)
            {
                var originalNeedQuantity = item.OrderQuantity;
                var needQuantity = item.TotalNeedQuantity;
                var needQuantity = originalNeedQuantity;
                // èŽ·å–å¯ç”¨åº“å­˜ï¼ˆæŒ‰å…ˆè¿›å…ˆå‡ºæŽ’åºï¼‰
                List<Dt_StockInfo> stockInfos = _stockService.StockInfoService.GetUseableStocks(item.MaterielCode, item.BatchNo);
                if (!stockInfos.Any())
                {
                    throw new Exception($"未找到可分配库存");
                    throw new Exception($"物料[{item.MaterielCode}]批次[{item.BatchNo}]未找到可分配库存");
                }
                List<Dt_StockInfo> autoAssignStocks = _stockService.StockInfoService.GetOutboundStocks(stockInfos, item.MaterielCode, needQuantity, out decimal residueQuantity);
                item.LockQuantity += needQuantity - residueQuantity;
                // åˆ†é…åº“存(按先进先出)
                List<Dt_StockInfo> autoAssignStocks = _stockService.StockInfoService.GetOutboundStocks(
                    stockInfos, item.MaterielCode, needQuantity, out decimal residueQuantity);
                if (residueQuantity > 0)
                {
                    throw new Exception($"物料[{item.MaterielCode}]库存不足,需要{needQuantity},可用{needQuantity - residueQuantity}");
                }
                outStocks.AddRange(autoAssignStocks);
                var assignQuantity = needQuantity - residueQuantity;
                List<Dt_OutboundOrderDetail> details = outboundOrderDetails.Where(x => !string.IsNullOrEmpty(x.BatchNo) ? x.BatchNo == item.BatchNo : true && x.MaterielCode == item.MaterielCode).ToList();
                for (int i = 0; i < details.Count; i++)
                {
                    var orderQuantity = details[i].OrderQuantity;
                    for (int j = 0; j < autoAssignStocks.Count; j++)
                    {
                        var detailAssignQuantity = outStockLockInfos.Where(x => !string.IsNullOrEmpty(x.BatchNo) ? x.BatchNo == item.BatchNo : true && x.MaterielCode == item.MaterielCode && x.OrderDetailId == details[i].Id).Sum(x => x.AssignQuantity);//出库订单明细已分配数量
                        var palletAssignQuantity = outStockLockInfos.Where(x => x.BatchNo == item.BatchNo && x.MaterielCode == item.MaterielCode && x.PalletCode == autoAssignStocks[j].PalletCode).Sum(x => x.AssignQuantity);//出库详情已分配数量
                        if (string.IsNullOrEmpty(item.BatchNo))
                        {
                            palletAssignQuantity = outStockLockInfos.Where(x => x.MaterielCode == item.MaterielCode && x.PalletCode == autoAssignStocks[j].PalletCode).Sum(x => x.AssignQuantity);//出库详情已分配数量
                        }
                        var palletOutboundQuantity = autoAssignStocks[j].Details.Sum(x => x.OutboundQuantity);
                        if (palletAssignQuantity < palletOutboundQuantity)//如果出库详情已分配数量小于托盘已分配数量,则可以继续添加该托盘出库信息
                        {
                            var orderDetailNeedQuantity = details[i].OrderQuantity - detailAssignQuantity;
                            if (orderDetailNeedQuantity > autoAssignStocks[j].Details.Sum(x => x.OutboundQuantity) - palletAssignQuantity)
                            {
                                details[i].LockQuantity += autoAssignStocks[j].Details.Sum(x => x.OutboundQuantity) - palletAssignQuantity;
                                Dt_OutStockLockInfo outStockLockInfo = _outStockLockInfoService.GetOutStockLockInfo(outboundOrder, details[i], autoAssignStocks[j], autoAssignStocks[j].Details.Sum(x => x.OutboundQuantity) - palletAssignQuantity);
                                outStockLockInfos.Add(outStockLockInfo);
                            }
                            else
                            {
                                Dt_OutStockLockInfo outStockLockInfo = _outStockLockInfoService.GetOutStockLockInfo(outboundOrder, details[i], autoAssignStocks[j], details[i].OrderQuantity - details[i].LockQuantity);
                                outStockLockInfos.Add(outStockLockInfo);
                                details[i].LockQuantity = details[i].OrderQuantity;
                                break;
                // æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
                DistributeLockQuantityByFIFO(item.Details, autoAssignStocks, outStockLockInfos, outboundOrder);
                            }
                        }
                    }
                }
                locationInfos.AddRange(_locationInfoService.GetLocationInfos(outStocks.Select(x => x.LocationCode).ToList()));
            }
            locationInfos.AddRange(_locationInfoService.GetLocationInfos(outStocks.Select(x => x.LocationCode).Distinct().ToList()));
            return (outStocks, outboundOrderDetails, outStockLockInfos, locationInfos);
        }
        /// <summary>
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…é”å®šæ•°é‡åˆ°å„ä¸ªæ˜Žç»†
        /// </summary>
        private void DistributeLockQuantityByFIFO(
            List<Dt_OutboundOrderDetail> details,
            List<Dt_StockInfo> assignStocks,
            List<Dt_OutStockLockInfo> outStockLockInfos,
            Dt_OutboundOrder outboundOrder)
        {
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序出库单明细(假设先创建的明细需要优先满足)
            var sortedDetails = details
                .OrderBy(x => x.Id) // æŒ‰ID排序,假设先创建的ID小
                .ToList();
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序库存(生产日期最早的优先)
            var sortedStockDetails = assignStocks
                .SelectMany(x => x.Details)
                .Where(x => details.Any(d => d.MaterielCode == x.MaterielCode))
                .OrderBy(x => x.ProductionDate)
                .ToList();
            // ä¸ºæ¯ä¸ªåº“存明细创建分配记录
            foreach (var stockDetail in sortedStockDetails)
            {
                var stockInfo = assignStocks.First(x => x.Id == stockDetail.StockId);
                var allocatedQuantity = stockDetail.OutboundQuantity; // è¿™ä¸ªåº“存明细分配的数量
                if (allocatedQuantity <= 0) continue;
                // æŒ‰é¡ºåºåˆ†é…ç»™å„个出库单明细
                decimal remainingAllocate = allocatedQuantity;
                foreach (var detail in sortedDetails)
                {
                    if (remainingAllocate <= 0) break;
                    // è®¡ç®—这个明细还需要分配的数量
                    var alreadyAssigned = outStockLockInfos
                        .Where(x => x.OrderDetailId == detail.Id && x.StockId == stockInfo.Id)
                        .Sum(x => x.AssignQuantity);
                    var detailNeed = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity - alreadyAssigned;
                    if (detailNeed <= 0) continue;
                    // åˆ†é…æ•°é‡
                    var assignQuantity = Math.Min(remainingAllocate, detailNeed);
                    // åˆ›å»ºå‡ºåº“锁定信息
                    var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                        outboundOrder, detail, stockInfo, assignQuantity, stockDetail.Barcode);
                    outStockLockInfos.Add(lockInfo);
                    // æ›´æ–°æ˜Žç»†çš„锁定数量
                    detail.LockQuantity += assignQuantity;
                    remainingAllocate -= assignQuantity;
                }
                // å¦‚果还有剩余分配数量,说明逻辑有误
                if (remainingAllocate > 0)
                {
                    throw new Exception($"库存分配逻辑错误,剩余未分配数量:{remainingAllocate}");
                }
            }
        }
/// <summary>
    /// åˆ›å»ºå‡ºåº“锁定信息
    /// </summary>
    private void CreateOutStockLockInfos(Dt_OutboundOrder outboundOrder,List<Dt_OutboundOrderDetail> details,
        List<Dt_StockInfo> assignStocks,List<Dt_OutStockLockInfo> outStockLockInfos)
        {
            foreach (var stock in assignStocks)
            {
                // èŽ·å–è¯¥åº“å­˜ä¸­ç›¸å…³ç‰©æ–™çš„å¯ç”¨æ¡ç ä¿¡æ¯
                var stockDetails = stock.Details
                    .Where(x => details.Any(d => d.MaterielCode == x.MaterielCode) &&
                               x.StockQuantity > x.OutboundQuantity)
                    .OrderBy(x => x.ProductionDate) // å…ˆè¿›å…ˆå‡º
                    .ToList();
                if (!stockDetails.Any()) continue;
                var stockAssignQuantity = stockDetails.Sum(x => x.OutboundQuantity);
                // æŸ¥æ‰¾è¿™ä¸ªåº“存已经分配的数量
                var existingAssign = outStockLockInfos
                    .Where(x => x.StockId == stock.Id &&
                               details.Any(d => d.Id == x.OrderDetailId))
                    .Sum(x => x.AssignQuantity);
                var availableAssign = stockAssignQuantity - existingAssign;
                if (availableAssign <= 0) continue;
                // æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…æ¡ç 
                var barcodeAllocation = AllocateBarcodes(stockDetails, availableAssign);
                // åˆ†é…ç»™å„个明细
                foreach (var detail in details.Where(d => d.LockQuantity > 0))
                {
                    var alreadyAssigned = outStockLockInfos
                        .Where(x => x.OrderDetailId == detail.Id && x.StockId == stock.Id)
                        .Sum(x => x.AssignQuantity);
                    var canAssign = Math.Min(detail.LockQuantity - alreadyAssigned, availableAssign);
                    if (canAssign > 0)
                    {
                        // ä¸ºè¿™ä¸ªåˆ†é…ç¡®å®šæ¡ç 
                        var (barcode, barcodeQuantity) = GetBarcodeForAllocation(barcodeAllocation, canAssign);
                        var lockInfo = _outStockLockInfoService.GetOutStockLockInfo(
                            outboundOrder, detail, stock, canAssign, barcode,null);
                        outStockLockInfos.Add(lockInfo);
                        availableAssign -= canAssign;
                        // æ›´æ–°æ¡ç åˆ†é…è®°å½•
                        UpdateBarcodeAllocation(barcodeAllocation, barcode, barcodeQuantity);
                    }
                    if (availableAssign <= 0) break;
                }
            }
        }
        /// <summary>
        /// æŒ‰å…ˆè¿›å…ˆå‡ºåŽŸåˆ™åˆ†é…æ¡ç 
        /// </summary>
        private Dictionary<string, decimal> AllocateBarcodes(List<Dt_StockInfoDetail> stockDetails, decimal totalQuantity)
        {
            var allocation = new Dictionary<string, decimal>();
            decimal remainingQuantity = totalQuantity;
            foreach (var detail in stockDetails.OrderBy(x => x.ProductionDate))
            {
                if (remainingQuantity <= 0) break;
                decimal available = detail.StockQuantity - detail.OutboundQuantity;
                decimal allocate = Math.Min(available, remainingQuantity);
                allocation[detail.Barcode] = allocate;
                remainingQuantity -= allocate;
            }
            return allocation;
        }
        /// <summary>
        /// ä¸ºåˆ†é…èŽ·å–åˆé€‚çš„æ¡ç 
        /// </summary>
        private (string barcode, decimal quantity) GetBarcodeForAllocation(Dictionary<string, decimal> barcodeAllocation, decimal requiredQuantity)
        {
            foreach (var (barcode, quantity) in barcodeAllocation)
            {
                if (quantity >= requiredQuantity)
                {
                    return (barcode, requiredQuantity);
                }
            }
            // å¦‚果单个条码数量不足,使用第一个条码
            var first = barcodeAllocation.First();
            return (first.Key, Math.Min(first.Value, requiredQuantity));
        }
        /// <summary>
        /// æ›´æ–°æ¡ç åˆ†é…è®°å½•
        /// </summary>
        private void UpdateBarcodeAllocation(Dictionary<string, decimal> barcodeAllocation, string barcode, decimal usedQuantity)
        {
            if (barcodeAllocation.ContainsKey(barcode))
            {
                barcodeAllocation[barcode] -= usedQuantity;
                if (barcodeAllocation[barcode] <= 0)
                {
                    barcodeAllocation.Remove(barcode);
                }
            }
        }
        /// <summary>
        /// å‡ºåº“库存分配后,更新数据库数据
        /// </summary>
@@ -126,30 +297,19 @@
        /// <param name="locationStatus"></param>
        /// <param name="tasks"></param>
        /// <returns></returns>
        public WebResponseContent LockOutboundStockDataUpdate(List<Dt_StockInfo> stockInfos, List<Dt_OutboundOrderDetail> outboundOrderDetails, List<Dt_OutStockLockInfo> outStockLockInfos, List<Dt_LocationInfo> locationInfos, LocationStatusEnum locationStatus = LocationStatusEnum.Lock, List<Dt_Task>? tasks = null)
        public WebResponseContent LockOutboundStockDataUpdate(List<Dt_StockInfo> stockInfos, List<Dt_OutboundOrderDetail> outboundOrderDetails,
            List<Dt_OutStockLockInfo> outStockLockInfos, List<Dt_LocationInfo> locationInfos,
            LocationStatusEnum locationStatus = LocationStatusEnum.Lock, List<Dt_Task>? tasks = null)
        {
            try
            {
                // æ›´æ–°åº“存状态
                stockInfos.ForEach(x => x.StockStatus = (int)StockStatusEmun.出库锁定);
                _stockService.StockInfoService.Repository.UpdateData(stockInfos);
                List<Dt_StockInfoDetail> stockInfoDetails = new List<Dt_StockInfoDetail>();
                foreach (var item in stockInfos)
                {
                    foreach (var detail in item.Details)
                    {
                        // è¿›è¡Œå®‰å…¨è½¬æ¢
                        if (detail.OutboundQuantity != null && decimal.TryParse(detail.OutboundQuantity.ToString(), out decimal outboundDecimal))
                        {
                            decimal outboundDecimal1 = Convert.ToDecimal(detail.OutboundQuantity);
                        }
                        else
                        {
                            detail.OutboundQuantity = 0; // é»˜è®¤å€¼æˆ–记录错误
                        }
                    }
                    stockInfoDetails.AddRange(item.Details);
                }
                _stockService.StockInfoDetailService.Repository.UpdateData(stockInfoDetails);
                // æ›´æ–°åº“存明细
                var stockDetails = stockInfos.SelectMany(x => x.Details).ToList();
                _stockService.StockInfoDetailService.Repository.UpdateData(stockDetails);
                BaseDal.UpdateData(outboundOrderDetails);
                List<Dt_OutStockLockInfo> addOutStockLockInfos = outStockLockInfos.Where(x => x.Id == 0).ToList();
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundOrderService.cs
@@ -1,4 +1,6 @@
using AutoMapper;
using Autofac.Core;
using AutoMapper;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
@@ -179,6 +181,20 @@
            }
        }
        /// <summary>
        /// æ ¹æ®ID获取出库单
        /// </summary>
        public async Task<WebResponseContent> GetById(int id)
        {
            var order = await Db.Queryable<Dt_OutboundOrder>().FirstAsync(o=>o.Id==id);
            if (order == null)
            {
                return WebResponseContent.Instance.Error("未找到出库单信息");
            }
            return WebResponseContent.Instance.OK(null, order);
        }
        static object lock_code = new object();
        public string CreateCodeByRule(string ruleCode)
        {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,386 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.OrderEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
namespace WIDESEA_OutboundService
{
    /// <summary>
    ///
    /// </summary>
    public class OutboundPickingService : ServiceBase<Dt_PickingRecord, IRepository<Dt_PickingRecord>>, IOutboundPickingService
    {
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        public IRepository<Dt_PickingRecord> Repository => BaseDal;
        private readonly IStockInfoService _stockInfoService;
        private readonly IStockService _stockService;
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        private readonly IStockInfoDetailService _stockInfoDetailService;
        private readonly ILocationInfoService _locationInfoService;
        private readonly IOutboundOrderDetailService _outboundOrderDetailService;
        private readonly ISplitPackageService _splitPackageService;
        public OutboundPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
            _stockService = stockService;
            _outStockLockInfoService = outStockLockInfoService;
            _stockInfoDetailService = stockInfoDetailService;
            _locationInfoService = locationInfoService;
            _outboundOrderDetailService = outboundOrderDetailService;
            _splitPackageService = splitPackageService;
        }
        /// <summary>
        /// æ‰«ç æ‹£é€‰ç¡®è®¤ - ç®€åŒ–版本
        /// åªå¤„理实际拣选的库存扣减
        /// </summary>
        public async Task<WebResponseContent> ConfirmPicking(PickingConfirmRequest request)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯æ¡ç æœ‰æ•ˆæ€§
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == request.Barcode && x.MaterielCode == request.MaterielCode)
                    .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                // 2. æ£€æŸ¥åº“存可用数量
                decimal availableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
                if (request.PickQuantity > availableQuantity)
                    return WebResponseContent.Instance.Error($"拣选数量超过可用库存,可用数量:{availableQuantity}");
                // 3. æŸ¥æ‰¾ç›¸å…³çš„出库锁定信息(支持拆包后的新条码)
                var lockInfo = await FindLockInfoByBarcode(request.OrderDetailId, request.Barcode, request.MaterielCode);
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到相关的出库锁定信息");
                // 4. æ£€æŸ¥é”å®šæ•°é‡
                decimal remainingLockQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
                if (request.PickQuantity > remainingLockQuantity)
                    return WebResponseContent.Instance.Error($"拣选数量超过锁定数量,剩余可拣选:{remainingLockQuantity}");
                // 5. æ›´æ–°é”å®šä¿¡æ¯çš„已拣选数量
                lockInfo.PickedQty += request.PickQuantity;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // 6. æ›´æ–°åº“存出库数量 - å®žé™…减少库存
                stockDetail.OutboundQuantity += request.PickQuantity;
                await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                // 7. æ›´æ–°å‡ºåº“单明细
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == request.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity += request.PickQuantity;
                orderDetail.LockQuantity -= request.PickQuantity;
                // æ£€æŸ¥æ˜¯å¦å®Œæˆå‡ºåº“
                if (Math.Abs(orderDetail.OverOutQuantity - orderDetail.OrderQuantity) < 0.001m)
                {
                    orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                    orderDetail.LockQuantity = 0;
                    // æ›´æ–°ç›¸å…³çš„锁定信息状态为已出库
                    var relatedLockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderDetailId == request.OrderDetailId &&
                                   x.Status == (int)OutLockStockStatusEnum.出库中)
                        .ToListAsync();
                    foreach (var relatedLock in relatedLockInfos)
                    {
                        relatedLock.Status = (int)OutLockStockStatusEnum.已出库;
                    }
                    await _outStockLockInfoService.Db.Updateable(relatedLockInfos).ExecuteCommandAsync();
                }
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                // 8. è®°å½•拣选历史
                var pickHistory = new Dt_PickingRecord
                {
                    OrderDetailId = request.OrderDetailId,
                    Barcode = request.Barcode,
                    PickQuantity = request.PickQuantity,
                    PickTime = DateTime.Now,
                    LocationCode = request.LocationCode,
                    StockId = stockDetail.StockId
                };
                await Db.Insertable(pickHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("拣选确认成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"拣选确认失败: {ex.Message}");
            }
        }
        /// <summary>
        /// æ ¹æ®æ¡ç æŸ¥æ‰¾é”å®šä¿¡æ¯
        /// </summary>
        private async Task<Dt_OutStockLockInfo> FindLockInfoByBarcode(int orderDetailId, string barcode, string materielCode)
        {
            return await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderDetailId == orderDetailId &&
                           x.MaterielCode == materielCode &&
                           x.CurrentBarcode == barcode &&
                           x.Status == (int)OutLockStockStatusEnum.出库中 &&
                           x.AssignQuantity > x.PickedQty)
                .FirstAsync();
        }
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// </summary>
        public async Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId)
        {
            // é€šè¿‡å‡ºåº“单ID查询相关的拣选历史
            // æ³¨æ„ï¼šDt_PickingRecord ä¸­æ²¡æœ‰ç›´æŽ¥å­˜å‚¨OrderId,需要通过出库单明细关联
            var detailIds = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .Where(d => d.OrderId == orderId)
                .Select(d => d.Id)
                .ToListAsync();
            return await Db.Queryable<Dt_PickingRecord>()
                .Where(p => detailIds.Contains(p.OrderDetailId))
                .OrderByDescending(p => p.PickTime)
                .ToListAsync();
        }
        /// <summary>
        /// æ’¤é”€æ‹£é€‰
        /// </summary>
        public async Task<WebResponseContent> CancelPicking(CancelPickingRequest request)
        {
            // å®žçŽ°æ’¤é”€æ‹£é€‰çš„é€»è¾‘ï¼Œéœ€è¦ï¼š
            // 1. æ¢å¤åº“存出库数量
            // 2. æ¢å¤é”å®šä¿¡æ¯çš„已拣选数量
            // 3. æ¢å¤å‡ºåº“单明细的已出数量和锁定数量
            // 4. åˆ é™¤æˆ–标记拣选历史记录
            // æ³¨æ„ï¼šè¿™é‡Œéœ€è¦äº‹åŠ¡å¤„ç†
            try
            {
                _unitOfWorkManage.BeginTran();
                var pickHistory = await Db.Queryable<Dt_PickingRecord>()
                    .Where(x => x.Id == request.PickingHistoryId)
                    .FirstAsync();
                if (pickHistory == null)
                    return WebResponseContent.Instance.Error("未找到拣选记录");
                // æ¢å¤åº“å­˜
                var stockDetail = await _stockInfoService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == pickHistory.Barcode && x.StockId == pickHistory.StockId)
                    .FirstAsync();
                if (stockDetail != null)
                {
                    stockDetail.OutboundQuantity -= pickHistory.PickQuantity;
                    await _stockInfoService.Db.Updateable(stockDetail).ExecuteCommandAsync();
                }
                // æ¢å¤é”å®šä¿¡æ¯
                var lockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderDetailId == pickHistory.OrderDetailId && x.StockId == pickHistory.StockId)
                    .FirstAsync();
                lockInfo.PickedQty -= pickHistory.PickQuantity;
                await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                // æ¢å¤å‡ºåº“单明细
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == pickHistory.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity -= pickHistory.PickQuantity;
                orderDetail.LockQuantity += pickHistory.PickQuantity;
                if (orderDetail.OverOutQuantity < orderDetail.OrderQuantity)
                {
                    orderDetail.OrderDetailStatus = orderDetail.LockQuantity > 0 ?
                        (int)OrderDetailStatusEnum.Outbound : (int)OrderDetailStatusEnum.AssignOverPartial;
                }
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                // åˆ é™¤æ‹£é€‰åŽ†å²è®°å½•
                await Db.Deleteable<Dt_PickingRecord>().Where(x => x.Id == request.PickingHistoryId).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("撤销成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"撤销失败: {ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€ä¿¡æ¯
        /// </summary>
        public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
        {
            // èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.PalletCode == palletCode)
                .ToListAsync();
            // èŽ·å–æ‰˜ç›˜åº“å­˜ä¿¡æ¯
            var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                .Includes(x => x.Details)
                .Where(x => x.PalletCode == palletCode)
                .FirstAsync();
            if (stockInfo == null)
                return WebResponseContent.Instance.Error("未找到托盘信息");
            // è®¡ç®—各种数量
            var totalStockQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
            var totalOutboundQuantity = stockInfo.Details.Sum(x => x.OutboundQuantity);
            var totalLockedQuantity = lockInfos.Where(x => x.Status == (int)OutLockStockStatusEnum.出库中)
                .Sum(x => x.AssignQuantity - x.PickedQty);
            var totalPickedQuantity = lockInfos.Sum(x => x.PickedQty);
            var result = new
            {
                PalletCode = palletCode,
                LocationCode = stockInfo.LocationCode,
                StockStatus = stockInfo.StockStatus,
                TotalStockQuantity = totalStockQuantity,
                TotalOutboundQuantity = totalOutboundQuantity,
                TotalLockedQuantity = totalLockedQuantity,
                TotalPickedQuantity = totalPickedQuantity,
                AvailableQuantity = totalStockQuantity - totalOutboundQuantity,
                LockInfos = lockInfos.Select(x => new
                {
                    x.Id,
                    x.MaterielCode,
                    x.OrderDetailId,
                    x.AssignQuantity,
                    x.PickedQty,
                    x.Status,
                    x.CurrentBarcode,
                    x.IsSplitted
                }).ToList(),
                StockDetails = stockInfo.Details.Select(x => new
                {
                    x.Barcode,
                    x.MaterielCode,
                    StockQuantity = x.StockQuantity,
                    OutboundQuantity = x.OutboundQuantity,
                    AvailableQuantity = x.StockQuantity - x.OutboundQuantity
                }).ToList()
            };
            return WebResponseContent.Instance.OK(null, result);
        }
        /// <summary>
        /// ç›´æŽ¥å‡ºåº“ - æ•´ä¸ªæ‰˜ç›˜å‡ºåº“,清空库存
        /// </summary>
        public async Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. èŽ·å–æ‰˜ç›˜åº“å­˜ä¿¡æ¯
                var stockInfo = await _stockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == request.PalletCode)
                    .FirstAsync();
                if (stockInfo == null)
                    return WebResponseContent.Instance.Error("未找到托盘库存信息");
                // 2. èŽ·å–ç›¸å…³çš„å‡ºåº“é”å®šä¿¡æ¯
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.PalletCode == request.PalletCode &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .ToListAsync();
                // 3. æ•´ä¸ªæ‰˜ç›˜å‡ºåº“ - è®¾ç½®å‡ºåº“数量等于库存数量
                foreach (var detail in stockInfo.Details)
                {
                    decimal outboundQuantity = detail.StockQuantity - detail.OutboundQuantity;
                    detail.OutboundQuantity = detail.StockQuantity; // å…¨éƒ¨å‡ºåº“
                    await _stockInfoDetailService.Db.Updateable(detail).ExecuteCommandAsync();
                }
                // 4. æ›´æ–°å‡ºåº“锁定信息
                foreach (var lockInfo in lockInfos)
                {
                    decimal unpicked = lockInfo.AssignQuantity - lockInfo.PickedQty;
                    lockInfo.PickedQty += unpicked; // æ ‡è®°ä¸ºå…¨éƒ¨æ‹£é€‰
                    lockInfo.Status = (int)OutLockStockStatusEnum.已出库;
                    await _outStockLockInfoService.Db.Updateable(lockInfo).ExecuteCommandAsync();
                    // æ›´æ–°å‡ºåº“单明细
                    var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                        .Where(x => x.Id == lockInfo.OrderDetailId)
                        .FirstAsync();
                    orderDetail.OverOutQuantity += unpicked;
                    orderDetail.LockQuantity -= unpicked;
                    orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.Over;
                    orderDetail.LockQuantity = 0;
                    await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                }
                // 5. æ›´æ–°æ‹†åŒ…记录状态
                var lockInfoIds = lockInfos.Select(x => x.Id).ToList();
                var splitRecords = await _splitPackageService.Db.Queryable<Dt_SplitPackageRecord>()
                    .Where(x => lockInfoIds.Contains(x.OutStockLockInfoId) &&
                               x.Status == (int)SplitPackageStatusEnum.已拆包)
                    .ToListAsync();
                foreach (var record in splitRecords)
                {
                    record.Status = (int)SplitPackageStatusEnum.已拣选;
                    await _splitPackageService.Db.Updateable(record).ExecuteCommandAsync();
                }
                // 6. æ¸…空货位
                var location = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                    .Where(x => x.LocationCode == stockInfo.LocationCode)
                    .FirstAsync();
                if (location != null)
                {
                    location.LocationStatus = (int)LocationStatusEnum.Free;
                    await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
                }
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("直接出库成功");
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"直接出库失败: {ex.Message}");
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,169 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_DTO.Outbound;
using WIDESEA_IOutboundService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
namespace WIDESEA_OutboundService
{
    internal class SplitPackageService : ServiceBase<Dt_SplitPackageRecord, IRepository<Dt_SplitPackageRecord>>, ISplitPackageService
    {
        private readonly IUnitOfWorkManage _unitOfWorkManage;
        public IRepository<Dt_SplitPackageRecord> Repository => BaseDal;
        private readonly IStockInfoService _stockInfoService;
        private readonly IStockInfoDetailService _stockInfoDetailService;
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        public SplitPackageService(IRepository<Dt_SplitPackageRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
            _outStockLockInfoService = outStockLockInfoService;
            _stockInfoDetailService = stockInfoDetailService;
        }
        /// <summary>
        /// æ‹†åŒ…拆箱操作
        /// </summary>
        public async Task<WebResponseContent> SplitPackage(SplitPackageRequest request)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯å‡ºåº“锁定信息
                var lockInfo = await Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.Id == request.OutStockLockInfoId &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                    .FirstAsync();
                if (lockInfo == null)
                    return WebResponseContent.Instance.Error("未找到有效的出库锁定信息");
                //// 2. éªŒè¯å½“前条码的可用数量
                //var currentStockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                //    .Where(x => x.Barcode == lockInfo.CurrentBarcode &&
                //               x.MaterielCode == request.MaterielCode &&
                //               x.StockId == lockInfo.StockId)
                //    .FirstAsync();
                //if (currentStockDetail == null)
                //    return WebResponseContent.Instance.Error("当前条码在库存中不存在");
                //// 3. æ£€æŸ¥å¯ç”¨æ•°é‡
                //decimal availableQuantity = currentStockDetail.StockQuantity - currentStockDetail.OutboundQuantity;
                //if (request.SplitQuantity > availableQuantity)
                //    return WebResponseContent.Instance.Error($"拆包数量不能大于可用数量,可用数量:{availableQuantity}");
                // 2. æ£€æŸ¥å‰©ä½™é”å®šæ•°é‡
                decimal remainingLockQuantity = lockInfo.AssignQuantity - lockInfo.PickedQty;
                if (request.SplitQuantity > remainingLockQuantity)
                    return WebResponseContent.Instance.Error($"拆包数量不能大于剩余锁定数量,剩余:{remainingLockQuantity}");
                // 3. ç”Ÿæˆæ–°æ¡ç 
                string newBarcode = "";
                // 4. åˆ›å»ºæ–°çš„出库锁定信息(新条码)
                var newLockInfo = new Dt_OutStockLockInfo
                {
                    OrderNo = lockInfo.OrderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    BatchNo = lockInfo.BatchNo,
                    MaterielCode = lockInfo.MaterielCode,
                    MaterielName = lockInfo.MaterielName,
                    StockId = lockInfo.StockId,
                    OrderQuantity = lockInfo.OrderQuantity,
                    OriginalQuantity = lockInfo.OriginalQuantity,
                    AssignQuantity = request.SplitQuantity, // æ–°æ¡ç åˆ†é…æ•°é‡
                    PickedQty = 0, // æ–°æ¡ç æœªæ‹£é€‰
                    LocationCode = lockInfo.LocationCode,
                    PalletCode = lockInfo.PalletCode,
                    TaskNum = lockInfo.TaskNum,
                    Status = (int)OutLockStockStatusEnum.出库中,
                    Unit = lockInfo.Unit,
                    CurrentBarcode = newBarcode, // æ–°æ¡ç 
                    OriginalLockQuantity = request.SplitQuantity,
                    IsSplitted = 1,
                    ParentLockId = lockInfo.Id // è®°å½•父级锁定ID
                };
                await Db.Insertable(newLockInfo).ExecuteCommandAsync();
                // 5. æ›´æ–°åŽŸé”å®šä¿¡æ¯çš„åˆ†é…æ•°é‡ï¼ˆå‡å°‘æ‹†åŒ…æ•°é‡ï¼‰
                lockInfo.AssignQuantity -= request.SplitQuantity;
                await Db.Updateable(lockInfo).ExecuteCommandAsync();
                // 6. è®°å½•拆包历史(用于追踪)
                var splitHistory = new Dt_SplitPackageRecord
                {
                    OutStockLockInfoId = lockInfo.Id,
                    OriginalBarcode = lockInfo.CurrentBarcode,
                    NewBarcode = newBarcode,
                    SplitQty = request.SplitQuantity,
                    MaterielCode = request.MaterielCode,
                    SplitTime = DateTime.Now,
                    Operator = request.Operator,
                    Status = (int)SplitPackageStatusEnum.已拆包
                };
                await Db.Insertable(splitHistory).ExecuteCommandAsync();
                Db.Ado.CommitTran();
                // 7. å›žä¼ æ–°æ¡ç ç»™MES
               // await SendBarcodeToMES(newBarcode, request.MaterielCode, request.SplitQuantity);
                return WebResponseContent.Instance.OK("拆包成功", new
                {
                    NewBarcode = newBarcode,
                    NewLockInfoId = newLockInfo.Id
                });
            }
            catch (Exception ex)
            {
                Db.Ado.RollbackTran();
                return WebResponseContent.Instance.Error($"拆包失败: {ex.Message}");
            }
        }
        /// <summary>
        /// èŽ·å–å¯æ‹†åŒ…çš„å‡ºåº“é”å®šä¿¡æ¯
        /// </summary>
        public async Task<WebResponseContent> GetSplitableLockInfos(int orderDetailId)
        {
            var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Includes(x => x.StockInfo)
                .Where(x => x.OrderDetailId == orderDetailId &&
                           x.Status == (int)OutLockStockStatusEnum.出库中 &&
                           x.AssignQuantity > x.PickedQty) // è¿˜æœ‰æœªæ‹£é€‰æ•°é‡
                .Select(x => new
                {
                    x.Id,
                    x.PalletCode,
                    x.LocationCode,
                    x.MaterielCode,
                    LockQuantity = x.AssignQuantity - x.PickedQty,
                    x.CurrentBarcode,
                    x.IsSplitted,
                    StockDetails = x.StockInfo.Details.Where(d => d.MaterielCode == x.MaterielCode)
                        .Select(d => new
                        {
                            d.Barcode,
                            AvailableQuantity = d.StockQuantity - d.OutboundQuantity
                        })
                        .ToList()
                })
                .ToListAsync();
            return WebResponseContent.Instance.OK(null, lockInfos);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -1,7 +1,10 @@
using AutoMapper;
using SqlSugar;
using WIDESEA_Common.LocationEnum;
using WIDESEA_Common.StockEnum;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_IBasicService;
using WIDESEA_IRecordService;
using WIDESEA_IStockService;
@@ -87,81 +90,98 @@
        public List<Dt_StockInfo> GetOutboundStocks(List<Dt_StockInfo> stockInfos, string materielCode, decimal needQuantity, out decimal residueQuantity)
        {
            List<Dt_StockInfo> outStocks = new List<Dt_StockInfo>();
            var stockTotalQuantity = stockInfos.Select(x => x.Details.Sum(v => v.StockQuantity - v.OutboundQuantity)).Sum(x => x);
            //stockInfos = stockInfos.OrderBy(x => x.Id).ToList();
            if (stockTotalQuantity >= needQuantity)//库存够
            {
                int index = 0;
                while (needQuantity > 0)
                {
                    Dt_StockInfo stockInfo = stockInfos[index];
                    // è®¡ç®—可用库存时转换为decimal
                    decimal useableStockQuantity = stockInfo.Details
                        .Where(x => x.MaterielCode == materielCode)
                        .Sum(x => (decimal)x.StockQuantity - (decimal)x.OutboundQuantity);
            // æŒ‰å…ˆè¿›å…ˆå‡ºæŽ’序(按条码的生产日期)
            var sortedStockDetails = stockInfos
                .SelectMany(x => x.Details)
                .Where(x => x.MaterielCode == materielCode && x.StockQuantity > x.OutboundQuantity)
                .OrderBy(x => x.ProductionDate).ThenBy(x => x.StockId)
                .ToList();
                    // å°†needQuantity转换为decimal进行比较
                    if (useableStockQuantity < (decimal)needQuantity && useableStockQuantity > 0)
                    {
                        stockInfo.Details.ForEach(x =>
                            x.OutboundQuantity = x.StockQuantity);
            // è®¡ç®—总可用库存
            var stockTotalQuantity = sortedStockDetails.Sum(x => x.StockQuantity - x.OutboundQuantity);
                        // ä½¿ç”¨decimal进行计算后再转回float
                        needQuantity =   needQuantity - useableStockQuantity;
            if (stockTotalQuantity < needQuantity)
            {
                residueQuantity = needQuantity - stockTotalQuantity;
                    }
                    else
                    {
                        stockInfo.Details.ForEach(x =>
                        {
                            if (x.StockQuantity > x.OutboundQuantity && x.MaterielCode == materielCode)
                            {
                                // å°†ç›¸å…³å€¼è½¬æ¢ä¸ºdecimal进行精确计算
                                decimal currentStock = (decimal)x.StockQuantity;
                                decimal currentOutbound = (decimal)x.OutboundQuantity;
                                decimal currentNeed = (decimal)needQuantity;
                                decimal available = currentStock - currentOutbound;
                residueQuantity = 0;
            }
                                if (available >= currentNeed)
            decimal remainingNeed = needQuantity;
            // æŒ‰æ¡ç åˆ†é…åº“å­˜
            foreach (var detail in sortedStockDetails)
                                {
                                    x.OutboundQuantity = currentOutbound + currentNeed;
                                    needQuantity = 0;
                                }
                                else
                if (remainingNeed <= 0) break;
                decimal availableQuantity = detail.StockQuantity - detail.OutboundQuantity;
                decimal allocateQuantity = Math.Min(availableQuantity, remainingNeed);
                // æ›´æ–°å‡ºåº“数量
                detail.OutboundQuantity += allocateQuantity;
                remainingNeed -= allocateQuantity;
                // å¦‚果这个库存还没添加到出库列表中,就添加
                var stockInfo = stockInfos.First(x => x.Id == detail.StockId);
                if (!outStocks.Contains(stockInfo))
                                {
                                    needQuantity =currentNeed - available;
                                    x.OutboundQuantity = x.StockQuantity;
                                }
                            }
                        });
                    }
                    outStocks.Add(stockInfo);
                    index++;
                }
                }
            }
            else
            {
                throw new Exception("库存不足");
            }
            residueQuantity = needQuantity;
            residueQuantity = remainingNeed;
            return outStocks;
        }
        /// <summary>
        /// æ ¹æ®æ¡ç èŽ·å–åº“å­˜ä¿¡æ¯
        /// </summary>
        public async Task<Dt_StockInfoDetail> GetStockDetailByBarcode(string barcode, string materielCode)
        {
            return await Db.Queryable<Dt_StockInfoDetail>()
                .Includes(x => x.StockInfo)
                .Where(x => x.Barcode == barcode && x.MaterielCode == materielCode)
                .FirstAsync();
        }
        /// <summary>
        /// èŽ·å–ç‰©æ–™çš„æ‰€æœ‰æ¡ç ä¿¡æ¯
        /// </summary>
        public async Task<List<Dt_StockInfoDetail>> GetMaterialBarcodes(string materielCode, string batchNo = null)
        {
            var query = Db.Queryable<Dt_StockInfoDetail>()
                .Includes(x => x.StockInfo)
                .Where(x => x.MaterielCode == materielCode && x.StockQuantity > x.OutboundQuantity);
            if (!string.IsNullOrEmpty(batchNo))
            {
                query = query.Where(x => x.BatchNo == batchNo);
            }
            return await query.OrderBy(x => x.ProductionDate).ToListAsync();
        }
        public List<Dt_StockInfo> GetStockInfos(string materielCode, string lotNo, List<string> locationCodes)
        {
            List<Dt_StockInfo> stockInfos = null;
            if (!string.IsNullOrEmpty(lotNo))
            var query = Db.Queryable<Dt_StockInfo>()
             .Where(x => locationCodes.Contains(x.LocationCode)
             //  && x.StockStatus == (int)StockStatusEmun.正常)
             ).Includes(x => x.Details);
            if (!string.IsNullOrEmpty(materielCode))
            {
                var stockSort = Db.Queryable<Dt_StockInfo>().Where(x => locationCodes.Contains(x.LocationCode)).Includes(x => x.Details).Where(x => x.Details.Any(v => v.MaterielCode == materielCode && v.BatchNo == lotNo)).ToList();
                stockInfos = stockSort.OrderBy(x => x.Details.FirstOrDefault()?.EffectiveDate).ThenBy(x => x.Details.Sum(v => v.StockQuantity)).ToList();
            }
            else
            {
                var stockSort = Db.Queryable<Dt_StockInfo>().Where(x => locationCodes.Contains(x.LocationCode)).Includes(x => x.Details).Where(x => x.Details.Any(v => v.MaterielCode == materielCode)).ToList();
                stockInfos = stockSort.OrderBy(x => x.Details.FirstOrDefault()?.EffectiveDate).ThenBy(x => x.Details.Sum(v => v.StockQuantity)).ToList();
                query = query.Where(x => x.Details.Any(d => d.MaterielCode == materielCode));
            }
            return stockInfos;
            if (!string.IsNullOrEmpty(lotNo))
            {
                query = query.Where(x => x.Details.Any(d => d.BatchNo == lotNo));
            }
            var stocks = query.ToList();
            return stocks.OrderBy(x => x.Details.Where(d => d.MaterielCode == materielCode &&
                           (string.IsNullOrEmpty(lotNo) || d.BatchNo == lotNo)).Min(d => d.ProductionDate)).ToList();
            //ISugarQueryable<Dt_LocationInfo> sugarQueryable = Db.Queryable<Dt_LocationInfo>().Where(x => locationCodes.Contains(x.LocationCode));
            //ISugarQueryable<Dt_StockInfo> sugarQueryable1 = Db.Queryable<Dt_StockInfo>().Includes(x => x.Details).Where(x => x.Details.Any(v => v.MaterielCode == materielCode));
            //return sugarQueryable.InnerJoin(sugarQueryable1, (a, b) => a.LocationCode == b.LocationCode).Select((a, b) => b).OrderBy(a => a.CreateDate).Includes(a => a.Details).ToList();
@@ -173,5 +193,11 @@
            return GetStockInfos(materielCode, batchNo, locationCodes);
        }
        public Dt_StockInfo GetPalletStockInfo(int locationType)
        {
            return Db.Queryable<Dt_StockInfo>().Where(x => x.StockStatus == StockStatusEmun.入库完成.ObjToInt() && SqlFunc.Subqueryable<Dt_LocationInfo>().Where(v => v.LocationCode == x.LocationCode && v.LocationType == locationType && v.LocationStatus == LocationStatusEnum.Pallet.ObjToInt() && (v.EnableStatus == EnableStatusEnum.OnlyOut.ObjToInt() || EnableStatusEnum.Normal.ObjToInt() == v.EnableStatus)).Any()).OrderBy(x => x.ModifyDate).First();
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -63,7 +63,7 @@
        private readonly IOutboundOrderService _outboundOrderService;
        private readonly IOutboundOrderDetailService _outboundOrderDetailService;
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        private readonly ILocationStatusChangeRecordService _locationStatusChangeRecordService;
        private readonly IESSApiService _eSSApiService;
        private readonly IStockService _stockService;
@@ -89,7 +89,7 @@
        public List<int> TaskOutboundTypes => typeof(TaskTypeEnum).GetEnumIndexList();
        public TaskService(IRepository<Dt_Task> BaseDal, IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_StockInfo> stockRepository, ILocationInfoService locationInfoService, IInboundOrderService inboundOrderService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IESSApiService eSSApiService, ILogger<TaskService> logger, IStockService stockService, IRecordService recordService, IInboundOrderDetailService inboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IInvokeMESService invokeMESService) : base(BaseDal)
        public TaskService(IRepository<Dt_Task> BaseDal, IMapper mapper, IUnitOfWorkManage unitOfWorkManage, IRepository<Dt_StockInfo> stockRepository, ILocationInfoService locationInfoService, IInboundOrderService inboundOrderService, ILocationStatusChangeRecordService locationStatusChangeRecordService, IESSApiService eSSApiService, ILogger<TaskService> logger, IStockService stockService, IRecordService recordService, IInboundOrderDetailService inboundOrderDetailService, IOutboundOrderService outboundOrderService, IOutboundOrderDetailService outboundOrderDetailService, IInvokeMESService invokeMESService, IOutStockLockInfoService outStockLockInfoService) : base(BaseDal)
        {
            _mapper = mapper;
            _unitOfWorkManage = unitOfWorkManage;
@@ -105,6 +105,7 @@
            _outboundOrderService = outboundOrderService;
            _outboundOrderDetailService = outboundOrderDetailService;
            _invokeMESService = invokeMESService;
            _outStockLockInfoService = outStockLockInfoService;
        }
        /// <summary>
@@ -264,7 +265,8 @@
                               supplyCode = group.Key.SupplyCode,
                               batchNo = group.Key.BatchNo,
                               lineNo = group.Key.lineNo,
                               warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                               // warehouseCode = group.Key.WarehouseCode=="0"?"1072": group.Key.WarehouseCode,
                               warehouseCode= "1072",
                               unit= group.Key.BarcodeUnit,
                               barcodes = group.Select(row => new FeedbackBarcodesModel
                               {
@@ -383,5 +385,150 @@
            }
        }
        /// <summary>
        /// å›žåº“完成回调 - AGV将托盘放回货位后调用
        /// </summary>
        public async Task<WebResponseContent> BackToStockComplete(Dt_Task task)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // èŽ·å–ç›¸å…³çš„å‡ºåº“é”å®šä¿¡æ¯
                var lockInfos = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.TaskNum == task.TaskNum &&
                               x.Status == (int)OutLockStockStatusEnum.回库中)
                    .ToListAsync();
                if (!lockInfos.Any())
                    return WebResponseContent.Instance.Error("未找到回库中的锁定信息");
                //  æ¢å¤åº“存出库数量(回库的部分)
                await RestoreStockOutboundQuantity(lockInfos);
                //  æ›´æ–°å‡ºåº“单明细的锁定数量
                var orderDetailGroups = lockInfos.GroupBy(x => x.OrderDetailId);
                foreach (var group in orderDetailGroups)
                {
                    var orderDetailId = group.Key;
                    var totalUnpicked = group.Sum(x => x.AssignQuantity - x.PickedQty);
                    if (totalUnpicked > 0)
                    {
                        var orderDetail = await _outboundOrderService.Db.Queryable<Dt_OutboundOrderDetail>()
                            .Where(x => x.Id == orderDetailId)
                            .FirstAsync();
                        orderDetail.LockQuantity -= totalUnpicked;
                        // æ¢å¤çŠ¶æ€
                        if (orderDetail.LockQuantity <= 0 && orderDetail.OverOutQuantity <= 0)
                        {
                            orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.New;
                        }
                        else if (orderDetail.OverOutQuantity > 0)
                        {
                            orderDetail.OrderDetailStatus = (int)OrderDetailStatusEnum.AssignOverPartial;
                        }
                        await _outboundOrderService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                    }
                }
                //  æ›´æ–°é”å®šä¿¡æ¯çŠ¶æ€ä¸ºå·²å›žåº“
                foreach (var lockInfo in lockInfos)
                {
                    lockInfo.Status = (int)OutLockStockStatusEnum.已回库;
                }
                await _outStockLockInfoService.Db.Updateable(lockInfos).ExecuteCommandAsync();
                // 6. æ›´æ–°åº“存状态
                var stockIds = lockInfos.Select(x => x.StockId).Distinct().ToList();
                var stocks = await _stockService.StockInfoService.Db.Queryable<Dt_StockInfo>()
                    .Where(x => stockIds.Contains(x.Id))
                    .ToListAsync();
                foreach (var stock in stocks)
                {
                    stock.StockStatus = (int)StockStatusEmun.入库完成;
                    stock.LocationCode = task.TargetAddress; // æ›´æ–°è´§ä½
                }
                await _stockService.StockInfoService.Db.Updateable(stocks).ExecuteCommandAsync();
                // 7. æ›´æ–°è´§ä½çŠ¶æ€
                var location = await _locationInfoService.Db.Queryable<Dt_LocationInfo>()
                    .Where(x => x.LocationCode == task.TargetAddress)
                    .FirstAsync();
                if (location != null)
                {
                    location.LocationStatus = (int)LocationStatusEnum.InStock;
                    await _locationInfoService.Db.Updateable(location).ExecuteCommandAsync();
                }
                //  æ›´æ–°ä»»åŠ¡çŠ¶æ€ä¸ºå·²å®Œæˆ
                task.TaskStatus = (int)TaskStatusEnum.Finish;
                await Db.Updateable(task).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("回库完成", new
                {
                    TaskNum = task.TaskNum,
                    PalletCode = task.PalletCode,
                    RestoredQuantity = lockInfos.Sum(x => x.AssignQuantity - x.PickedQty)
                });
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                return WebResponseContent.Instance.Error($"回库完成处理失败: {ex.Message}");
            }
        }
        /// <summary>
        /// æ¢å¤åº“存出库数量(回库的部分)
        /// </summary>
        private async Task RestoreStockOutboundQuantity(List<Dt_OutStockLockInfo> lockInfos)
        {
            // æŒ‰åº“å­˜ID和物料分组
            var stockGroups = lockInfos.GroupBy(x => new { x.StockId, x.MaterielCode });
            foreach (var group in stockGroups)
            {
                var stockId = group.Key.StockId;
                var materielCode = group.Key.MaterielCode;
                var totalUnpicked = group.Sum(x => x.AssignQuantity - x.PickedQty);
                if (totalUnpicked <= 0) continue;
                // èŽ·å–è¯¥ç‰©æ–™åœ¨åº“å­˜ä¸­çš„æ‰€æœ‰æ¡ç 
                var stockDetails = await _stockService.StockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.StockId == stockId && x.MaterielCode == materielCode)
                    .ToListAsync();
                if (!stockDetails.Any()) continue;
                // æŒ‰å‡ºåº“数量的比例分配恢复数量
                var totalOutbound = stockDetails.Sum(x => x.OutboundQuantity);
                if (totalOutbound <= 0) continue;
                foreach (var detail in stockDetails)
                {
                    if (detail.OutboundQuantity <= 0) continue;
                    decimal ratio = detail.OutboundQuantity / totalOutbound;
                    decimal restoreAmount = Math.Min(detail.OutboundQuantity, totalUnpicked * ratio);
                    detail.OutboundQuantity -= restoreAmount;
                    await _stockService.StockInfoDetailService.Db.Updateable(detail).ExecuteCommandAsync();
                }
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/ESSController.cs
@@ -43,7 +43,7 @@
        public async Task<IActionResult> ContainerArrivalReport([FromBody] ContainerArrivalRequest request)
        {
            //这里要判断出库的时候,料箱会到扫码处。也会请求这个接口。
            _logger.LogInformation(" ESSController  ContainerArrivalReport : CallId={CallId},ContainerCode={ContainerCode},SlotCode={SlotCode}", request.CallId, request.ContainerCode, request.SlotCode);
            var response = new ApiResponse<ContainerArrivalResponseData>
            {
                Code = 0,
@@ -105,6 +105,8 @@
                    response.Code = 1;
                    response.Msg = "error";
                    response.Data.direction = "0";
                    _logger.LogError(" ESSController  ContainerArrivalReport  Error: Message={Message}", result.Message);
                    return Ok(response);
                }                                 
            }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundOrderController.cs
@@ -79,7 +79,7 @@
            //await _eSSApiService.MoveContainerAsync(new WIDESEA_DTO.Basic.MoveContainerRequest
            //{
            //    slotCode = "3-5",
            //    containerCode = "A000008002"
            //    containerCode = "A000008009"
            //});
            //await erpApiService.GetSuppliersAsync();
@@ -200,5 +200,11 @@
            return Service.UnPalletQuantity(orderNo);
        }
        [HttpPost, Route("BatchOrderFeedbackToMes"), AllowAnonymous]
        public async Task<WebResponseContent> BatchOrderFeedbackToMes([FromBody]  BatchOrderFeedbackToMesDto request)
        {
           return await _invokeMESService.BatchOrderFeedbackToMes(request.orderNos, request.inout);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundOrderController.cs
@@ -58,5 +58,19 @@
            if (content.Status) return WebResponseContent.Instance.OK(200);
            else return WebResponseContent.Instance.Error(content.Message);
        }
        /// <summary>
        /// æ ¹æ®ID获取出库单
        /// </summary>ss
        [HttpPost, Route("GetById"), AllowAnonymous, MethodParamsValidate]
        public async Task<WebResponseContent> GetById(int id)
        {
            var order = await Service.GetById(id);
            if (order == null)
            {
                return WebResponseContent.Instance.Error("未找到出库单信息");
            }
            return WebResponseContent.Instance.OK(null, order);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core;
using WIDESEA_Core.BaseController;
using WIDESEA_DTO.Outbound;
using WIDESEA_IOutboundService;
using WIDESEA_Model.Models;
namespace WIDESEA_WMSServer.Controllers.Outbound
{
    [Route("api/OutboundPicking")]
    [ApiController]
    public class OutboundPickingController : ApiBaseController<IOutboundPickingService, Dt_PickingRecord>
    {
        private readonly ISplitPackageService _splitPackageService;
        private readonly IOutStockLockInfoService _outStockLockInfoService;
        public OutboundPickingController(IOutboundPickingService service, ISplitPackageService splitPackageService, IOutStockLockInfoService outStockLockInfoService) : base(service)
        {
            _splitPackageService = splitPackageService;
            _outStockLockInfoService = outStockLockInfoService;
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„å‡ºåº“çŠ¶æ€
        /// </summary>
        [HttpGet("GetPalletOutboundStatus")]
        public async Task<WebResponseContent> GetPalletOutboundStatus(string palletCode)
        {
            return await Service.GetPalletOutboundStatus(palletCode);
        }
        /// <summary>
        /// èŽ·å–æ‰˜ç›˜çš„é”å®šä¿¡æ¯
        /// </summary>
        [HttpGet("GetPalletLockInfos")]
        public async Task<WebResponseContent> GetPalletLockInfos(string palletCode)
        {
            var lockInfos = await _outStockLockInfoService.GetPalletLockInfos(palletCode);
            return WebResponseContent.Instance.OK(null, lockInfos);
        }
        /// <summary>
        /// æ‹£é€‰ç¡®è®¤
        /// </summary>
        [HttpPost("ConfirmPicking")]
        public async Task<WebResponseContent> ConfirmPicking([FromBody] PickingConfirmRequest request)
        {
            return await Service.ConfirmPicking(request);
        }
        /// <summary>
        /// æ‹†åŒ…操作
        /// </summary>
        [HttpPost("SplitPackage")]
        public async Task<WebResponseContent> SplitPackage([FromBody] SplitPackageRequest request)
        {
            return await _splitPackageService.SplitPackage(request);
        }
        ///// <summary>
        ///// ç›´æŽ¥å‡ºåº“
        ///// </summary>
        //[HttpPost("DirectOutbound")]
        //public async Task<WebResponseContent> DirectOutbound([FromBody] DirectOutboundRequest request)
        //{
        //    return await Service.DirectOutbound(request);
        //}
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// </summary>
        [HttpGet("GetPickingHistory")]
        public async Task<WebResponseContent> GetPickingHistory(int orderId)
        {
            var history = await Service.GetPickingHistory(orderId);
            return WebResponseContent.Instance.OK(null, history);
        }
        /// <summary>
        /// æ’¤é”€æ‹£é€‰
        /// </summary>
        [HttpPost("CancelPicking")]
        public async Task<WebResponseContent> CancelPicking([FromBody] CancelPickingRequest request)
        {
            return await Service.CancelPicking(request);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
@@ -33,5 +33,17 @@
            return result;
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务
        /// </summary>
        /// <param name="keys"></param>
        /// <returns></returns>
        [HttpPost, HttpGet, Route("GenerateOutboundTasks"), AllowAnonymous]
        public WebResponseContent GenerateOutboundTasks([FromBody] int[] keys)
        {
            return Service.GenerateOutboundTasks(keys);
        }
    }
}