pan
2025-11-15 4476740c214edb7ab667c48fcab00488fbdd9879
提交
已添加3个文件
已修改20个文件
2003 ■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/Picking.vue 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/PickingConfirm.vue 294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/SplitPackage.vue 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/router/viewGird.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue 777 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/vue.config.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_BasicService/WarehouseService.cs 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Basic/SupplierDTO.cs 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/ISplitPackageService.cs 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundOrderDetail.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutStockLockInfoService.cs 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs 114 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutStockLockInfoController.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/Picking.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,190 @@
<template>
  <div class="picking-operation">
    <div class="operation-toolbar">
      <el-button type="primary" @click="showPickingConfirm">
        åˆ†æ‹£ç¡®è®¤
      </el-button>
      <el-button
        type="warning"
        @click="showSplitDialog"
        :disabled="selectedRows.length !== 1"
      >
        æ‹†åŒ…
      </el-button>
      <el-button type="success" @click="batchDirectOutbound">
        ç›´æŽ¥å‡ºåº“
      </el-button>
      <el-button type="info" @click="batchReturnToWarehouse">
        å›žåº“
      </el-button>
    </div>
    <!-- å‡ºåº“详情列表 -->
    <div class="lock-info-table">
      <base-table
        ref="lockInfoTable"
        :table-config="tableConfig"
        :height="300"
        selection
        @selection-change="handleSelectionChange"
      />
    </div>
    <!-- åˆ†æ‹£ç¡®è®¤å¼¹çª— -->
    <el-dialog
      v-model="pickingConfirmVisible"
      title="分拣确认"
      width="90%"
      top="5vh"
      destroy-on-close
    >
      <picking-confirm
        v-if="pickingConfirmVisible"
        :order-no="orderDetail.OrderNo"
        @confirm="handlePickingConfirm"
        @close="pickingConfirmVisible = false"
      />
    </el-dialog>
    <!-- æ‹†åŒ…弹窗 -->
    <el-dialog
      v-model="splitVisible"
      title="拆包操作"
      width="600px"
      destroy-on-close
    >
      <split-package
        v-if="splitVisible && selectedLockInfo"
        :lock-info="selectedLockInfo"
        @confirm="handleSplitConfirm"
        @close="splitVisible = false"
      />
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import BaseTable from '@/components/base/BaseTable.vue'
import PickingConfirm from './PickingConfirm.vue'
import SplitPackage from './SplitPackage.vue'
const props = defineProps({
  orderDetail: {
    type: Object,
    required: true
  }
})
const emit = defineEmits(['close'])
const lockInfoTable = ref()
const pickingConfirmVisible = ref(false)
const splitVisible = ref(false)
const selectedRows = ref([])
const selectedLockInfo = ref(null)
const tableConfig = reactive({
  url: '/api/outbound/getOutStockLockInfo',
  query: { orderDetailId: props.orderDetail.Id },
  columns: [
    { type: 'selection', width: 55 },
    { prop: 'BatchNo', label: '批次号', width: 120 },
    { prop: 'MaterielCode', label: '物料条码', width: 150 },
    { prop: 'AssignQuantity', label: '分配数量', width: 100 },
    { prop: 'PickedQty', label: '已拣数量', width: 100 },
    { prop: 'LocationCode', label: '库位编号', width: 120 },
    { prop: 'PalletCode', label: '托盘编号', width: 120 },
    { prop: 'Status', label: '状态', width: 100 }
  ]
})
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
const showPickingConfirm = () => {
  pickingConfirmVisible.value = true
}
const showSplitDialog = async () => {
  if (selectedRows.value.length !== 1) {
    ElMessage.warning('请选择一条要拆包的记录')
    return
  }
  try {
    // èŽ·å–é€‰ä¸­è®°å½•çš„è¯¦ç»†ä¿¡æ¯
    const result = await $http.get('/api/outbound/getLockInfoDetail', {
      params: { lockInfoId: selectedRows.value[0].Id }
    })
    if (result.success) {
      selectedLockInfo.value = result.data
      splitVisible.value = true
    } else {
      ElMessage.error('获取拆包记录详情失败')
    }
  } catch (error) {
    ElMessage.error('获取拆包记录详情失败')
  }
}
const batchDirectOutbound = async () => {
  try {
    await ElMessageBox.confirm('确定要对整个订单执行直接出库吗?', '提示', {
      type: 'warning'
    })
    const result = await $http.post('/api/outbound/batchDirectOutbound', {
      orderNo: props.orderDetail.OrderNo
    })
    if (result.success) {
      ElMessage.success('批量直接出库成功')
      lockInfoTable.value.refresh()
    } else {
      ElMessage.error(result.message)
    }
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('批量直接出库失败')
    }
  }
}
const batchReturnToWarehouse = async () => {
  try {
    await ElMessageBox.confirm('确定要对未拣选货物执行回库吗?', '提示', {
      type: 'warning'
    })
    const result = await $http.post('/api/outbound/batchReturnToWarehouse', {
      orderNo: props.orderDetail.OrderNo
    })
    if (result.success) {
      ElMessage.success('批量回库成功')
      lockInfoTable.value.refresh()
    } else {
      ElMessage.error(result.message)
    }
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('批量回库失败')
    }
  }
}
const handlePickingConfirm = () => {
  pickingConfirmVisible.value = false
  lockInfoTable.value.refresh()
}
const handleSplitConfirm = () => {
  splitVisible.value = false
  selectedLockInfo.value = null
  lockInfoTable.value.refresh()
}
</script>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/PickingConfirm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,294 @@
<template>
  <div class="picking-confirm">
    <div class="scan-section">
      <el-alert
        title="请使用扫码枪扫描托盘码和物料条码,扫码枪带回车功能,扫完物料条码自动确认"
        type="info"
        :closable="false"
        class="scan-alert"
      />
      <el-form :model="scanForm" label-width="100px" class="scan-form">
        <el-form-item label="托盘码" required>
          <el-input
            ref="palletInput"
            v-model="scanForm.palletCode"
            placeholder="请扫描托盘码"
            @keyup.enter="handlePalletScan"
            @blur="loadPalletSummary"
            clearable
          />
        </el-form-item>
        <el-form-item label="物料条码" required>
          <el-input
            ref="materialInput"
            v-model="scanForm.materialBarcode"
            placeholder="请扫描物料条码"
            :disabled="!scanForm.palletCode"
            @keyup.enter="handleMaterialScan"
            clearable
          />
        </el-form-item>
      </el-form>
      <!-- æ‰˜ç›˜æ‹£è´§ç»Ÿè®¡ -->
      <div v-if="palletSummary" class="pallet-summary">
        <el-card header="托盘拣货统计">
          <el-descriptions :column="3" border>
            <el-descriptions-item label="托盘号">
              {{ scanForm.palletCode }}
            </el-descriptions-item>
            <el-descriptions-item label="未拣货条数">
              <el-text type="warning">{{ palletSummary.unpickedCount }}</el-text>
            </el-descriptions-item>
            <el-descriptions-item label="未拣货总数">
              <el-text type="danger">{{ palletSummary.unpickedTotal }}</el-text>
            </el-descriptions-item>
          </el-descriptions>
        </el-card>
      </div>
      <div class="action-buttons">
        <el-button type="primary" @click="handleConfirm" :loading="confirmLoading">
          æ‰‹åŠ¨ç¡®è®¤
        </el-button>
        <el-button @click="handleReset">重置</el-button>
        <el-button @click="$emit('close')">取消</el-button>
      </div>
    </div>
    <!-- å·²åˆ†æ‹£è®°å½•列表 -->
    <div class="picked-records">
      <el-card header="已分拣记录">
        <base-table
          ref="pickedTable"
          :table-config="pickedTableConfig"
          :height="300"
        />
      </el-card>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import BaseTable from '@/components/base/BaseTable.vue'
const props = defineProps({
  orderNo: {
    type: String,
    required: true
  }
})
const emit = defineEmits(['confirm', 'close'])
const palletInput = ref()
const materialInput = ref()
const pickedTable = ref()
const scanForm = reactive({
  palletCode: '',
  materialBarcode: ''
})
const palletSummary = ref(null)
const confirmLoading = ref(false)
const pickedTableConfig = reactive({
  url: '/api/outbound/getPickingRecords',
  query: { orderNo: props.orderNo },
  columns: [
    { prop: 'TaskNo', label: '任务号', width: 150 },
    { prop: 'Barcode', label: '物料条码', width: 150 },
    { prop: 'MaterielName', label: '物料名称', width: 150 },
    { prop: 'PickQuantity', label: '拣货数量', width: 100 },
    { prop: 'LocationCode', label: '货位', width: 120 },
    { prop: 'CreateTime', label: '拣货时间', width: 180 }
  ]
})
onMounted(() => {
  nextTick(() => {
    if (palletInput.value) {
      palletInput.value.focus()
    }
  })
})
const loadOrderInfo = async () => {
  const orderId = route.query.orderId
  if (!orderId) return
  try {
    const response = await http.get(`/api/OutboundOrder/GetById?id=${orderId}`)
    if (response.status) {
      orderInfo.value = response.data
    }
  } catch (error) {
    ElMessage.error('加载出库单信息失败')
  }
}
const handlePalletScan = async () => {
  if (scanForm.palletCode) {
    ElMessage.success(`已扫描托盘: ${scanForm.palletCode}`)
    await loadPalletSummary()
    nextTick(() => {
      if (materialInput.value) {
        materialInput.value.focus()
      }
    })
  }
}
const handleMaterialScan = async () => {
  if (!scanForm.palletCode) {
    ElMessage.warning('请先扫描托盘码')
    palletInput.value.focus()
    return
  }
  if (!scanForm.materialBarcode) {
    ElMessage.warning('请扫描物料条码')
    return
  }
  await executePickingConfirm()
}
const loadPalletSummary = async () => {
  if (!scanForm.palletCode) {
    palletSummary.value = null
    return
  }
  try {
    const result = await $http.get('/api/outbound/getPalletPickingSummary', {
      params: {
        orderNo: props.orderNo,
        palletCode: scanForm.palletCode
      }
    })
    if (result.success) {
      // å¤„理统计信息
      const summary = result.data
      const assigned = summary.find(x => x.Status === '已分配') || { TotalAssignQty: 0, TotalPickedQty: 0 }
      const picked = summary.find(x => x.Status === '已拣选') || { TotalPickedQty: 0 }
      palletSummary.value = {
        unpickedCount: assigned.TotalAssignQty > 0 ? 1 : 0, // ç®€åŒ–计算
        unpickedTotal: assigned.TotalAssignQty - assigned.TotalPickedQty
      }
    }
  } catch (error) {
    console.error('加载托盘统计失败:', error)
  }
}
const handleConfirm = async () => {
  if (!scanForm.palletCode || !scanForm.materialBarcode) {
    ElMessage.warning('请填写完整的扫码信息')
    return
  }
  await executePickingConfirm()
}
const executePickingConfirm = async () => {
  confirmLoading.value = true
  try {
    // å…ˆæ‰¾åˆ°å¯¹åº”的出库锁定信息
    const lockInfoResult = await $http.get('/api/outbound/getOutStockLockInfo', {
      params: {
        orderNo: props.orderNo,
        palletCode: scanForm.palletCode,
        materialBarcode: scanForm.materialBarcode
      }
    })
    if (!lockInfoResult.success || !lockInfoResult.data || lockInfoResult.data.length === 0) {
      ElMessage.error('未找到对应的出库锁定信息')
      return
    }
    const lockInfo = lockInfoResult.data[0]
    const request = {
      outStockLockId: lockInfo.Id,
      taskNo: `TASK_${Date.now()}`,
      palletCode: scanForm.palletCode,
      materialBarcode: scanForm.materialBarcode,
      locationCode: lockInfo.LocationCode
    }
    const result = await $http.post('/api/outbound/pickingConfirm', request)
    if (result.success) {
      ElMessage.success('分拣确认成功')
      handleReset()
      emit('confirm')
      // åˆ·æ–°å·²åˆ†æ‹£è®°å½•
      if (pickedTable.value) {
        pickedTable.value.refresh()
      }
      // é‡æ–°åŠ è½½æ‰˜ç›˜ç»Ÿè®¡
      await loadPalletSummary()
    } else {
      ElMessage.error(result.message)
    }
  } catch (error) {
    ElMessage.error('分拣确认失败')
  } finally {
    confirmLoading.value = false
  }
}
const handleReset = () => {
  scanForm.materialBarcode = ''
  nextTick(() => {
    if (materialInput.value) {
      materialInput.value.focus()
    }
  })
}
</script>
<style scoped>
.picking-confirm {
  display: flex;
  flex-direction: column;
  height: 70vh;
}
.scan-section {
  flex-shrink: 0;
  margin-bottom: 16px;
}
.scan-alert {
  margin-bottom: 16px;
}
.scan-form {
  max-width: 500px;
}
.pallet-summary {
  margin: 16px 0;
}
.action-buttons {
  margin-top: 16px;
}
.picked-records {
  flex: 1;
  min-height: 300px;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/SplitPackage.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,176 @@
<template>
  <div class="split-package">
    <el-alert
      title="拆包操作将把选中的物料包分成两个包,系统会自动生成新条码"
      type="info"
      :closable="false"
      class="split-alert"
    />
    <!-- åŽŸç‰©æ–™ä¿¡æ¯å±•ç¤º -->
    <el-card header="原物料信息" class="original-info">
      <el-descriptions :column="2" border>
        <el-descriptions-item label="旧条码">
          <el-text type="primary">{{ lockInfo.MaterielCode }}</el-text>
        </el-descriptions-item>
        <el-descriptions-item label="物料名称">
          {{ lockInfo.MaterielName }}
        </el-descriptions-item>
        <el-descriptions-item label="批次号">
          {{ lockInfo.BatchNo }}
        </el-descriptions-item>
        <el-descriptions-item label="托盘编号">
          {{ lockInfo.PalletCode }}
        </el-descriptions-item>
        <el-descriptions-item label="库位编号">
          {{ lockInfo.LocationCode }}
        </el-descriptions-item>
        <el-descriptions-item label="任务号">
          {{ taskNo }}
        </el-descriptions-item>
        <el-descriptions-item label="分配数量">
          <el-text type="info">{{ lockInfo.AssignQuantity }}</el-text>
        </el-descriptions-item>
        <el-descriptions-item label="已拣数量">
          <el-text type="warning">{{ lockInfo.PickedQty }}</el-text>
        </el-descriptions-item>
        <el-descriptions-item label="可拆包数量">
          <el-text type="success">{{ availableQuantity }}</el-text>
        </el-descriptions-item>
      </el-descriptions>
    </el-card>
    <!-- æ‹†åŒ…信息输入 -->
    <el-card header="拆包信息" class="split-info">
      <el-form :model="splitForm" label-width="120px">
        <el-form-item label="拆包数量" required>
          <el-input-number
            v-model="splitForm.splitQuantity"
            :min="1"
            :max="availableQuantity"
            :precision="2"
            placeholder="请输入拆包数量"
            style="width: 200px"
          />
          <div class="form-tips">
            å¯æ‹†åŒ…数量: {{ availableQuantity }} {{ lockInfo.Unit }}
          </div>
        </el-form-item>
        <el-form-item label="新条码" v-if="newBarcode">
          <el-text type="success">{{ newBarcode }}</el-text>
          <div class="form-tips">系统自动生成的新条码</div>
        </el-form-item>
      </el-form>
    </el-card>
    <div class="action-buttons">
      <el-button
        type="primary"
        @click="handleConfirm"
        :loading="confirmLoading"
        :disabled="!splitForm.splitQuantity"
      >
        ç¡®è®¤æ‹†åŒ…
      </el-button>
      <el-button @click="$emit('close')">取消</el-button>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { ElMessage } from 'element-plus'
const props = defineProps({
  lockInfo: {
    type: Object,
    required: true
  }
})
const emit = defineEmits(['confirm', 'close'])
const splitForm = reactive({
  splitQuantity: 1
})
const confirmLoading = ref(false)
const newBarcode = ref('')
const taskNo = ref(`SPLIT_${Date.now()}`)
// è®¡ç®—可拆包数量
const availableQuantity = computed(() => {
  return props.lockInfo.AssignQuantity - props.lockInfo.PickedQty
})
const handleConfirm = async () => {
  if (!splitForm.splitQuantity || splitForm.splitQuantity <= 0) {
    ElMessage.warning('请输入有效的拆包数量')
    return
  }
  if (splitForm.splitQuantity >= availableQuantity.value) {
    ElMessage.warning('拆包数量必须小于可拆包数量')
    return
  }
  confirmLoading.value = true
  try {
    const request = {
      lockInfoId: props.lockInfo.Id,
      taskNo: taskNo.value,
      splitQuantity: splitForm.splitQuantity
    }
    const result = await $http.post('/api/outbound/splitPackage', request)
    if (result.success) {
      newBarcode.value = result.data?.newBarcode || '生成成功'
      ElMessage.success(`拆包操作成功!新条码: ${newBarcode.value}`)
      // å»¶è¿Ÿå…³é—­ï¼Œè®©ç”¨æˆ·çœ‹åˆ°æ–°æ¡ç 
      setTimeout(() => {
        emit('confirm')
      }, 2000)
    } else {
      ElMessage.error(result.message)
    }
  } catch (error) {
    ElMessage.error('拆包操作失败')
  } finally {
    confirmLoading.value = false
  }
}
</script>
<style scoped>
.split-package {
  max-height: 70vh;
  overflow-y: auto;
}
.split-alert {
  margin-bottom: 16px;
}
.original-info {
  margin-bottom: 16px;
}
.split-info {
  margin-bottom: 16px;
}
.form-tips {
  font-size: 12px;
  color: #909399;
  margin-top: 4px;
}
.action-buttons {
  text-align: center;
  margin-top: 20px;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue
@@ -329,7 +329,7 @@
   handleOpenPicking() {
      this.$router.push({
        path: '/outbound/picking',
        query: { orderId: this.row.id }
        query: { orderId: this.row.id ,orderNo:this.row.orderNo}
      })
    },
    outbound() {
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js
@@ -18,7 +18,7 @@
    },
    tableAction: '', //指定某张表的权限(这里填写表名,默认不用填写)
    buttons: { view: [
       {
       /* {
        name: '出库',
        type: 'primary',
        value: '出库',
@@ -42,7 +42,7 @@
            createDate: selectedRows[0].createDate || new Date().toLocaleDateString()  // å‡ºåº“日期
          });
        }
      },
      }, */
      {
  name: '空托盘出库',
  type: 'primary',
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/router/viewGird.js
@@ -76,7 +76,7 @@
  }, 
  {
  path: '/outbound/picking',
  name: 'PickingConfirm',
  name: 'PickingConfirm',
  component: () => import('@/views/outbound/PickingConfirm.vue'),
  meta: { title: '拣选确认', keepAlive: false }
},{
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/views/outbound/PickingConfirm.vue
@@ -3,516 +3,375 @@
    <div class="page-header">
      <el-page-header @back="goBack">
        <template #content>
          <span class="title">出库拣选确认 - {{ orderInfo.orderNo }}</span>
          <span class="title">出库拣选确认 - {{ this.$route.query.orderNo }}</span>
        </template>
      </el-page-header>
    </div>
    <el-row :gutter="20" class="main-content">
      <el-col :span="8">
    <div class="content-layout">
      <!-- å·¦ä¾§ï¼šæ‰«ç åŒºåŸŸ -->
      <div class="left-section">
        <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-alert
            title="请使用扫码枪扫描托盘码和物料条码,扫码枪带回车功能,扫完物料条码自动确认"
            type="info"
            :closable="false"
            class="scan-alert"
          />
              <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 :model="scanForm" label-width="100px" class="scan-form">
            <el-form-item label="托盘码" required>
              <el-input
                ref="palletInput"
                v-model="scanForm.palletCode"
                placeholder="请扫描托盘码"
                @keyup.enter="handlePalletScan"
                @blur="loadPalletSummary"
                clearable
              />
            </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-item label="物料条码" required>
              <el-input
                ref="materialInput"
                v-model="scanForm.materialBarcode"
                placeholder="请扫描物料条码"
                :disabled="!scanForm.palletCode"
                @keyup.enter="handleMaterialScan"
                clearable
              />
            </el-form-item>
          </el-form>
              <!-- ç‰©æ–™ä¿¡æ¯æ˜¾ç¤º -->
              <el-form-item label="物料编码" v-if="currentMaterialInfo">
                <el-input v-model="currentMaterialInfo.materielCode" readonly />
              </el-form-item>
              <el-form-item label="物料名称" v-if="currentMaterialInfo">
                <el-input v-model="currentMaterialInfo.materielName" readonly />
              </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 v-if="palletSummary" class="pallet-summary">
            <el-card header="托盘拣货统计">
              <el-descriptions :column="3" border>
                <el-descriptions-item label="托盘号">
                  {{ scanForm.palletCode }}
                </el-descriptions-item>
                <el-descriptions-item label="未拣货条数">
                  <el-text type="warning">{{ palletSummary.unpickedCount }}</el-text>
                </el-descriptions-item>
                <el-descriptions-item label="未拣货总数">
                  <el-text type="danger">{{ palletSummary.unpickedTotal }}</el-text>
                </el-descriptions-item>
              </el-descriptions>
            </el-card>
          </div>
          <div class="action-buttons">
            <el-button
              type="warning"
              @click="handleBackToStock"
              :disabled="!currentPallet"
              style="margin-bottom: 10px;"
            >
              å›žåº“
            <el-button type="primary" @click="handleConfirm" :loading="confirmLoading">
              æ‰‹åŠ¨ç¡®è®¤
            </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>
            <el-button @click="handleReset">重置</el-button>
            <el-button @click="$emit('close')">取消</el-button>
          </div>
        </div>
      </el-col>
      </div>
      <el-col :span="16">
        <el-card header="拣选结果">
          <div class="summary-info">
            <el-alert
              :title="`未拣货: ${unpickedCount} æ¡, ${unpickedQuantity} ä¸ª`"
              type="warning"
              :closable="false"
            />
          </div>
      <!-- å³ä¾§ï¼šå‡ºåº“详情列表 -->
      <div class="right-section">
        <el-card class="outbound-details-card" header="出库详情">
          <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>
            ref="outboundTable"
            :table-config="outboundTableConfig"
            :height="300"
          />
        </el-card>
      </el-col>
    </el-row>
      </div>
    </div>
    <!-- æ‹†åŒ…弹窗 -->
    <vol-box
      v-model="splitVisible"
      title="拆包操作"
      :width="600"
      :height="500"
    >
      <SplitPackageModal
        v-if="splitVisible"
        :lockInfo="currentLockInfo"
        @success="handleSplitSuccess"
        @close="splitVisible = false"
      />
    </vol-box>
    <!-- å·²åˆ†æ‹£è®°å½•列表 -->
    <div class="picked-records">
      <el-card header="已分拣记录">
        <vol-table
          ref="pickedTable"
          :table-config="pickedTableConfig"
          :height="300"
        />
      </el-card>
    </div>
  </div>
</template>
<script>
import SplitPackageModal from './SplitPackageModal.vue'
import http from '@/api/http.js'
import { ref, defineComponent } from "vue";
import { ElMessage } from "element-plus";
import { useRoute } from 'vue-router'
export default {
  components: { SplitPackageModal },
export default defineComponent({
  name: 'PickingConfirm',
  components: {
  },
  props: {
    orderNo: {
      type: String,
      required: true
    }
  },
  emits: ['confirm', 'close'],
  data() {
    return {
      orderInfo: {},
      scanForm: {
        palletCode: '',
        barcode: '',
        quantity: 1
        materialBarcode: ''
      },
      currentPallet: null,
      currentLockInfo: null,
      currentMaterialInfo: 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,
      allLockInfos: [] // æ–°å¢žï¼šä¿å­˜æ‰€æœ‰é”å®šä¿¡æ¯ï¼Œç”¨äºŽæ¡ç åŒ¹é…
    }
  },
  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.get(`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
          await 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) {
          this.allLockInfos = result.data
          // é»˜è®¤é€‰æ‹©ç¬¬ä¸€ä¸ªé”å®šä¿¡æ¯
          if (this.allLockInfos.length > 0) {
            this.currentLockInfo = this.allLockInfos[0]
            this.currentMaterialInfo = {
              materielCode: this.currentLockInfo.materielCode,
              materielName: this.currentLockInfo.materielName
            }
            this.maxPickQuantity = this.currentLockInfo.assignQuantity - this.currentLockInfo.pickedQty
          }
        }
      } catch (error) {
        console.error('加载锁定信息失败:', error)
      }
    },
    // æ ¹æ®æ¡ç æŸ¥æ‰¾å¯¹åº”的锁定信息和物料信息
    findLockInfoByBarcode(barcode) {
      if (!this.allLockInfos || this.allLockInfos.length === 0) {
        return null
      }
      // é¦–先精确匹配当前条码
      let lockInfo = this.allLockInfos.find(x => x.currentBarcode === barcode)
      if (lockInfo) {
        return lockInfo
      }
      // å¦‚果没有精确匹配,查找该条码对应的物料是否在锁定信息中
      // è¿™é‡Œéœ€è¦è°ƒç”¨åŽç«¯æŽ¥å£éªŒè¯æ¡ç å¯¹åº”的物料
      return null
    },
    async handleBarcodeScan() {
      if (!this.scanForm.barcode) {
        this.$message.warning('请输入物料条码')
        return
      }
      if (!this.currentPallet) {
        this.$message.warning('请先扫描托盘条码')
        return
      }
      if (this.scanForm.quantity <= 0) {
        this.$message.warning('请输入有效的拣选数量')
        return
      }
      try {
        // éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
        const materialInfo = await this.validateBarcode(this.scanForm.barcode)
        if (!materialInfo) {
          this.$message.error('无效的物料条码')
          return
        }
        // æŸ¥æ‰¾å¯¹åº”的锁定信息
        const targetLockInfo = this.findLockInfoByBarcodeAndMaterial(this.scanForm.barcode, materialInfo.materielCode)
        if (!targetLockInfo) {
          this.$message.error('该物料条码不在当前托盘的锁定信息中')
          return
        }
        // æ£€æŸ¥æ‹£é€‰æ•°é‡
        const availableQuantity = targetLockInfo.assignQuantity - targetLockInfo.pickedQty
        if (this.scanForm.quantity > availableQuantity) {
          this.$message.error(`拣选数量超过可用数量,剩余可拣选:${availableQuantity}`)
          return
        }
        // å‡†å¤‡è¯·æ±‚数据
        const request = {
          orderDetailId: targetLockInfo.orderDetailId,
          barcode: this.scanForm.barcode,
          materielCode: materialInfo.materielCode, // ä¼ é€’物料编码
          pickQuantity: this.scanForm.quantity,
          locationCode: this.currentPallet.locationCode,
          palletCode: this.currentPallet.palletCode,
          stockId: targetLockInfo.stockId,
          outStockLockInfoId: targetLockInfo.id // ä¼ é€’锁定信息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.currentMaterialInfo = null
          // åˆ·æ–°æ•°æ®
          this.loadOrderInfo()
          this.loadPickedHistory()
          this.loadPalletLockInfo()
        } else {
          this.$message.error(result.message)
        }
      } catch (error) {
        this.$message.error('拣选确认失败: ' + (error.message || '未知错误'))
      }
    },
    // æ ¹æ®æ¡ç å’Œç‰©æ–™ç¼–码查找锁定信息
    findLockInfoByBarcodeAndMaterial(barcode, materielCode) {
      if (!this.allLockInfos || this.allLockInfos.length === 0) {
        return null
      }
      // é¦–先尝试精确匹配条码
      let lockInfo = this.allLockInfos.find(x =>
        x.currentBarcode === barcode && x.materielCode === materielCode
      )
      if (lockInfo) {
        return lockInfo
      }
      // å¦‚果精确匹配失败,只匹配物料编码(允许从同一物料的不同条码拣选)
      lockInfo = this.allLockInfos.find(x =>
        x.materielCode === materielCode &&
        (x.assignQuantity - x.pickedQty) > 0
      )
      return lockInfo
    },
    // éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
    async validateBarcode(barcode) {
      try {
        const result = await this.http.get(`api/OutboundPicking/ValidateBarcode?barcode=${barcode}`)
        if (result.status) {
          return result.data
        } else {
          this.$message.error(result.message)
          return null
        }
      } catch (error) {
        this.$message.error('条码验证失败')
        return null
      }
    },
    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: '拣选位',
          operator: '当前用户'
        })
        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.currentMaterialInfo = null
      this.allLockInfos = []
      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) {
        // ç”¨æˆ·å–消
      }
      palletSummary: null,
      confirmLoading: false,
      pickedTableConfig: {
        url: '/api/outbound/getPickingRecords',
        query: { orderNo: this.orderNo },
        columns: [
          { prop: 'TaskNo', label: '任务号', width: 150 },
          { prop: 'Barcode', label: '物料条码', width: 150 },
          { prop: 'MaterielName', label: '物料名称', width: 150 },
          { prop: 'PickQuantity', label: '拣货数量', width: 100 },
          { prop: 'LocationCode', label: '货位', width: 120 },
          { prop: 'CreateTime', label: '拣货时间', width: 180 }
        ]
      },
      // å‡ºåº“详情表格配置
      outboundTableConfig: {
        url: '/api/outbound/getOutboundDetails',
        query: { orderNo: this.orderNo },
        columns: [
          { prop: 'OrderNo', label: '出库单号', width: 150 },
          { prop: 'MaterialCode', label: '物料编号', width: 120 },
          { prop: 'MaterialBarcode', label: '物料条码', width: 150 },
          { prop: 'BatchNo', label: '批次号', width: 120 },
          { prop: 'AssignQuantity', label: '分配出库量', width: 100 },
          { prop: 'PalletCode', label: '托盘编号', width: 120 },
          { prop: 'Unit', label: '单位', width: 80 }
        ]
      },
      orderInfo: {orderNo:''}
    }
  },
  mounted() {
    this.loadOrderInfo()
    this.loadPickedHistory()
    this.loadOrderInfo();
    this.$nextTick(() => {
      if (this.$refs.palletInput) {
        this.$refs.palletInput.focus()
      }
    })
  },
  methods: {
    loadOrderInfo() {
      const orderId = this.$route.query.orderId
      if (!orderId) return
      try {
        this.http.get(`/api/OutboundOrder/GetById?id=${orderId}`).then(response => {debugger;
          if (response.status) {
            this.orderInfo = response.data
          }
        })
      } catch (error) {
        ElMessage.error('加载出库单信息失败')
      }
    },
     goBack() {
      this.$router.back()
    },
    async handlePalletScan() {
      if (this.scanForm.palletCode) {
        ElMessage.success(`已扫描托盘: ${this.scanForm.palletCode}`)
        await this.loadPalletSummary()
        this.$nextTick(() => {
          if (this.$refs.materialInput) {
            this.$refs.materialInput.focus()
          }
        })
      }
    },
    async handleMaterialScan() {
      if (!this.scanForm.palletCode) {
        ElMessage.warning('请先扫描托盘码')
        this.$refs.palletInput.focus()
        return
      }
      if (!this.scanForm.materialBarcode) {
        ElMessage.warning('请扫描物料条码')
        return
      }
      await this.executePickingConfirm()
    },
    async loadPalletSummary() {
      if (!this.scanForm.palletCode) {
        this.palletSummary = null
        return
      }
      try {
        const result = await http.get('/api/outbound/getPalletPickingSummary', {
          params: {
            orderNo: this.orderNo,
            palletCode: this.scanForm.palletCode
          }
        })
        if (result.success) {
          // å¤„理统计信息
          const summary = result.data
          const assigned = summary.find(x => x.Status === '已分配') || { TotalAssignQty: 0, TotalPickedQty: 0 }
          const picked = summary.find(x => x.Status === '已拣选') || { TotalPickedQty: 0 }
          this.palletSummary = {
            unpickedCount: assigned.TotalAssignQty > 0 ? 1 : 0, // ç®€åŒ–计算
            unpickedTotal: assigned.TotalAssignQty - assigned.TotalPickedQty
          }
        }
      } catch (error) {
        console.error('加载托盘统计失败:', error)
      }
    },
    async handleConfirm() {
      if (!this.scanForm.palletCode || !this.scanForm.materialBarcode) {
        ElMessage.warning('请填写完整的扫码信息')
        return
      }
      await this.executePickingConfirm()
    },
    async executePickingConfirm() {
      this.confirmLoading = true
      try {
        // å…ˆæ‰¾åˆ°å¯¹åº”的出库锁定信息
        const lockInfoResult = await this.http.get('/api/outbound/getOutStockLockInfo', {
          params: {
            orderNo: this.orderNo,
            palletCode: this.scanForm.palletCode,
            materialBarcode: this.scanForm.materialBarcode
          }
        })
        if (!lockInfoResult.success || !lockInfoResult.data || lockInfoResult.data.length === 0) {
          ElMessage.error('未找到对应的出库锁定信息')
          return
        }
        const lockInfo = lockInfoResult.data[0]
        const request = {
          outStockLockId: lockInfo.Id,
          taskNo: `TASK_${Date.now()}`,
          palletCode: this.scanForm.palletCode,
          materialBarcode: this.scanForm.materialBarcode,
          locationCode: lockInfo.LocationCode
        }
        const result = await this.http.post('/api/outbound/pickingConfirm', request)
        if (result.success) {
          ElMessage.success('分拣确认成功')
          this.handleReset()
          this.$emit('confirm')
          // åˆ·æ–°è¡¨æ ¼
          if (this.$refs.pickedTable) {
            this.$refs.pickedTable.refresh()
          }
          // åˆ·æ–°å‡ºåº“详情表格
          if (this.$refs.outboundTable) {
            this.$refs.outboundTable.refresh()
          }
          // é‡æ–°åŠ è½½æ‰˜ç›˜ç»Ÿè®¡
          await this.loadPalletSummary()
        } else {
          ElMessage.error(result.ElMessage)
        }
      } catch (error) {
        ElMessage.error('分拣确认失败')
      } finally {
        this.confirmLoading = false
      }
    },
    handleReset() {
      this.scanForm.materialBarcode = ''
      this.$nextTick(() => {
        if (this.$refs.materialInput) {
          this.$refs.materialInput.focus()
        }
      })
    }
  }
}
})
</script>
<style scoped>
.picking-confirm {
  padding: 20px;
  display: flex;
  flex-direction: column;
  height: 70vh;
}
.page-header {
  margin-bottom: 20px;
.content-layout {
  display: flex;
  gap: 16px;
  margin-bottom: 16px;
  flex: 1;
  min-height: 0; /* é‡è¦ï¼šé˜²æ­¢flex子元素溢出 */
}
.title {
  font-size: 18px;
  font-weight: bold;
.left-section {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.right-section {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.scan-section {
  margin-bottom: 20px;
  flex-shrink: 0;
}
.scan-alert {
  margin-bottom: 16px;
}
.scan-form {
  max-width: 500px;
}
.pallet-summary {
  margin: 16px 0;
}
.action-buttons {
  margin-top: 16px;
}
.outbound-details-card {
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.action-buttons .el-button {
  width: 100%;
.outbound-details-card :deep(.el-card__body) {
  flex: 1;
  padding: 0;
}
.current-info {
  margin-top: 15px;
  padding: 10px;
  background-color: #f5f7fa;
  border-radius: 4px;
.picked-records {
  flex-shrink: 0;
  height: 300px;
}
.current-info p {
  margin: 5px 0;
  font-size: 14px;
}
.summary-info {
  margin-bottom: 15px;
.picked-records :deep(.el-card__body) {
  padding: 0;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/vue.config.js
@@ -1,4 +1,3 @@
// const webpack = require("webpack");
module.exports = {
  productionSourceMap: false,
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_BasicService/WarehouseService.cs
@@ -89,17 +89,28 @@
            var lists = _warehouseArearepository.Db.Queryable<Dt_WarehouseArea>().ToList();
            foreach (var item in models)
            {
                var dbfirst = lists.FirstOrDefault(x => x.Code == item.Code);
                if (dbfirst != null)
                if (item.IsDelete == 1)
                {
                    dbfirst.Code = item.Code;
                    dbfirst.Name = item.Name;
                    dbfirst.FactoryArea = item.FactoryArea;
                    _warehouseArearepository.UpdateData(dbfirst);
                  var first=  _warehouseArearepository.Db.Queryable<Dt_WarehouseArea>().First(x => x.Code == item.Code);
                    if(first != null)
                    {
                        _warehouseArearepository.DeleteData(first);
                    }
                }
                else
                {
                    _warehouseArearepository.AddData(new Dt_WarehouseArea { Code=item.Code,Name=item.Name,FactoryArea=item.FactoryArea});
                    var dbfirst = lists.FirstOrDefault(x => x.Code == item.Code);
                    if (dbfirst != null)
                    {
                        dbfirst.Code = item.Code;
                        dbfirst.Name = item.Name;
                        dbfirst.FactoryArea = item.FactoryArea;
                        _warehouseArearepository.UpdateData(dbfirst);
                    }
                    else
                    {
                        _warehouseArearepository.AddData(new Dt_WarehouseArea { Code = item.Code, Name = item.Name, FactoryArea = item.FactoryArea });
                    }
                }
            }
            return WebResponseContent.Instance.OK();
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Basic/SupplierDTO.cs
@@ -70,7 +70,7 @@
        /// åº“存组织
        /// </summary>
        [PropertyValidate("库存组织", NotNullAndEmpty = true)]
        public string InvOrgId { get; set; } = "淮安特创二厂";
        public string InvOrgId { get; set; } = "";
        /// <summary>
        /// æ“ä½œç±»åž‹
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/Outbound/OutboundOrderGetDTO.cs
@@ -4,6 +4,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Model.Models;
namespace WIDESEA_DTO.Outbound
{
@@ -22,7 +23,12 @@
        public decimal SplitQuantity { get; set; }
        public string Operator { get; set; }
    }
    public class ConfirmPickingDto
    {
        public string OrderNo { get; set; }
        public string PalletCode { get; set; }
        public string Barcode { get; set; }
    }
    public class PickingConfirmRequest
    {
@@ -115,7 +121,11 @@
        public string CurrentBarcode { get; set; } = string.Empty;
        public decimal AssignQuantity { get; set; }
    }
    public class LockInfoDetailDto : Dt_OutStockLockInfo
    {
        public string MaterielName { get; set; }
        public string Unit { get; set; }
    }
    public class MaterielBarcodeValidateOutput : OutputDto
    {
        public bool IsValid { get; set; }
@@ -213,4 +223,24 @@
        public decimal pickQuantity { get; set; }
        public string pickTime { get; set; } = string.Empty;
    }
    public class CancelPickingDto
    {
        public string OrderNo { get; set; }
        public string PalletCode { get; set; }
        public string Barcode { get; set; }
    }
    public class SplitPackageDto
    {
        public string OrderNo { get; set; }
        public string PalletCode { get; set; }
        public string OriginalBarcode { get; set; }
        public decimal SplitQuantity { get; set; }
    }
    public class RevertSplitDto
    {
        public string OriginalBarcode { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutStockLockInfoService.cs
@@ -8,6 +8,7 @@
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_DTO.Outbound;
using WIDESEA_Model.Models;
namespace WIDESEA_IOutboundService
@@ -21,8 +22,8 @@
 
        Task<List<Dt_OutStockLockInfo>> GetByOrderDetailId(int orderDetailId);
        Task<List<Dt_OutStockLockInfo>> GetByPalletCode(string palletCode, int? status = null);
        Task<LockInfoDetailDto> GetLockInfoDetail(int lockInfoId);
        Dt_OutStockLockInfo GetOutStockLockInfo(Dt_OutboundOrder outboundOrder,Dt_OutboundOrderDetail outboundOrderDetail,Dt_StockInfo outStock, decimal assignQuantity, string barcode = null);
        Task<List<Dt_OutStockLockInfo>> GetPalletLockInfos(string palletCode);
        Task<WebResponseContent> UpdateLockInfoBarcode(int lockInfoId, string newBarcode);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundPickingService.cs
@@ -16,9 +16,17 @@
        IRepository<Dt_PickingRecord> Repository { get; }
        Task<WebResponseContent> CancelPicking(CancelPickingRequest request);
        Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> ConfirmPicking(PickingConfirmRequest request);
        Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId);
         Task<WebResponseContent> ValidateBarcode(string barcode);
        Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> DirectOutbound(DirectOutboundRequest request);
        Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo);
        Task<WebResponseContent> GetPalletOutboundStatus(string palletCode);
        Task GetPalletPickingSummary(string orderNo, string palletCode);
        Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode);
        Task<List<Dt_PickingRecord>> GetPickingHistory(int orderId);
        Task<object> GetPickingSummary(string orderNo);
        Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode);
        Task<WebResponseContent> ValidateBarcode(string barcode);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/ISplitPackageService.cs
@@ -15,6 +15,11 @@
    {
        IRepository<Dt_SplitPackageRecord> Repository { get; }
        Task<WebResponseContent> SplitPackage(SplitPackageRequest request);
        Task<WebResponseContent> SplitPackage(SplitPackageDto request);
          Task<WebResponseContent> RevertSplitPackage(string originalBarcode);
        Task<WebResponseContent> GetSplitPackageInfo(string orderNo, string palletCode, string barcode);
        Task<WebResponseContent> GetSplitableLockInfos(int orderDetailId);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundLockInfo.cs
@@ -127,5 +127,8 @@
        [Navigate(NavigateType.OneToOne, nameof(StockInfo))]//一对一 SchoolId是StudentA类里面的
        public Dt_StockInfo StockInfo { get; set; } //不能赋值只能是null
        [SugarColumn(IsIgnore = true)]
        public decimal RemainQuantity => AssignQuantity - PickedQty;
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_OutboundOrderDetail.cs
@@ -111,5 +111,8 @@
        /// </summary>
        [SugarColumn(IsNullable = true, ColumnDescription = "备注")]
        public string Remark { get; set; }
        [SugarColumn(IsIgnore = true)]
        public decimal NeedOutQuantity => OrderQuantity - MoveQty;
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_Model/Models/Outbound/Dt_PickingRecord.cs
@@ -19,8 +19,13 @@
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string OrderNo { get; set; }
        public int OrderDetailId { get; set; }
        public string PalletCode { get; set; }
        public int OutStockLockId { get; set; }
        public string MaterielCode { get; set; }
        [SugarColumn(Length = 100)]
        public string Barcode { get; set; }
@@ -37,8 +42,8 @@
        public int StockId { get; set; }
    }
    /// <summary>
    /// å›žåº“记录表
    /// </summary>
@@ -73,7 +78,10 @@
    {
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
        public string OrderNo { get; set; }
        public string PalletCode { get; set; }
        public string StockId { get; set; }
        public bool IsReverted { get; set; } = false;
        public int OutStockLockInfoId { get; set; } // å…³è”的出库锁定信息
        public string OriginalBarcode { get; set; } // åŽŸæ¡ç 
        public string NewBarcode { get; set; } // æ–°æ¡ç 
@@ -82,14 +90,14 @@
        /// æ‹†åˆ†æ•°é‡ï¼ˆæ–°æ¡ç æ•°é‡ï¼‰
        /// </summary>
        public decimal SplitQty { get; set; }
        public decimal RemainQuantity { 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_OutboundService/OutStockLockInfoService.cs
@@ -1,4 +1,5 @@
using System;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -10,6 +11,7 @@
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Enums;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Outbound;
using WIDESEA_IRecordService;
using WIDESEA_IStockService;
using WIDESEA_Model.Models;
@@ -117,6 +119,37 @@
                .ToListAsync();
        }
        public async Task<LockInfoDetailDto> GetLockInfoDetail(int lockInfoId)
        {
            var lockInfo = await Db.Queryable<Dt_OutStockLockInfo>()
                .LeftJoin<Dt_OutboundOrderDetail>((lockInfo, detail) => lockInfo.OrderDetailId == detail.Id)
                .Where((lockInfo, detail) => lockInfo.Id == lockInfoId)
                .Select((lockInfo, detail) => new LockInfoDetailDto
                {
                    Id = lockInfo.Id,
                    OrderNo = lockInfo.OrderNo,
                    OrderDetailId = lockInfo.OrderDetailId,
                    BatchNo = lockInfo.BatchNo,
                    MaterielCode = lockInfo.MaterielCode,
                    StockId = lockInfo.StockId,
                    OrderQuantity = lockInfo.OrderQuantity,
                    OriginalQuantity = lockInfo.OriginalQuantity,
                    AssignQuantity = lockInfo.AssignQuantity,
                    PickedQty = lockInfo.PickedQty,
                    LocationCode = lockInfo.LocationCode,
                    PalletCode = lockInfo.PalletCode,
                    Status = lockInfo.Status,
                    IsSplitted = lockInfo.IsSplitted,
                    ParentLockId = lockInfo.ParentLockId,
                    MaterielName = detail.MaterielName,
                    Unit = detail.Unit
                })
                .FirstAsync();
            return lockInfo;
        }
        /// <summary>
        /// æ ¹æ®æ‰˜ç›˜ç¼–号获取出库锁定信息
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundPickingService.cs
@@ -1,4 +1,6 @@
using System;
using Microsoft.AspNetCore.Http;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -9,6 +11,7 @@
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
using WIDESEA_Core.Helper;
using WIDESEA_DTO.Outbound;
using WIDESEA_IBasicService;
using WIDESEA_IOutboundService;
@@ -31,10 +34,11 @@
        private readonly IStockInfoDetailService _stockInfoDetailService;
        private readonly ILocationInfoService _locationInfoService;
        private readonly IOutboundOrderDetailService _outboundOrderDetailService;
        private readonly IOutboundOrderService _outboundOrderService;
        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)
        public OutboundPickingService(IRepository<Dt_PickingRecord> BaseDal, IUnitOfWorkManage unitOfWorkManage, IStockInfoService stockInfoService, IStockService stockService, IOutStockLockInfoService outStockLockInfoService, IStockInfoDetailService stockInfoDetailService, ILocationInfoService locationInfoService, IOutboundOrderDetailService outboundOrderDetailService, ISplitPackageService splitPackageService, IOutboundOrderService outboundOrderService) : base(BaseDal)
        {
            _unitOfWorkManage = unitOfWorkManage;
            _stockInfoService = stockInfoService;
@@ -44,9 +48,10 @@
            _locationInfoService = locationInfoService;
            _outboundOrderDetailService = outboundOrderDetailService;
            _splitPackageService = splitPackageService;
            _outboundOrderService = outboundOrderService;
        }
        #region æŸ¥è¯¢å‡ºåº“详情列表
        public async Task<List<OutStockLockListResp>> GetOutStockLockListAsync(string orderNo)
        {
@@ -57,13 +62,13 @@
            return locks.Select(t => new OutStockLockListResp
            {
                Id = t.Id,
               // TaskNum = t.TaskNum,
                // TaskNum = t.TaskNum,
                PalletCode = t.PalletCode,
                CurrentBarcode = t.CurrentBarcode,
                AssignQuantity = t.AssignQuantity,
                PickedQty = t.PickedQty,
                Status = t.Status,
              //  IsSplitted = t.IsSplitted
                //  IsSplitted = t.IsSplitted
            }).ToList();
        }
        #endregion
@@ -87,13 +92,13 @@
                    return WebResponseContent.Instance.Error("条码不存在");
                }
                var result = new
                {
                    Barcode = barcode,
                    MaterielCode = stockDetail.MaterielCode,
                    BatchNo = stockDetail.BatchNo,
                    AvailableQuantity = stockDetail.StockQuantity - stockDetail.OutboundQuantity,
                    LocationCode = stockDetail.StockInfo?.LocationCode,
@@ -203,6 +208,162 @@
            }
        }
        public async Task<WebResponseContent> ConfirmPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯æ¡ç æœ‰æ•ˆæ€§
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                        .Where(x => x.Barcode == barcode)
                        .FirstAsync();
                if (stockDetail == null)
                    return WebResponseContent.Instance.Error("无效的条码或物料编码");
                // 2. æ£€æŸ¥åº“存可用数量
                var availableQty = stockDetail.StockQuantity - stockDetail.OutboundQuantity;
                if (availableQty <= 0)
                    return WebResponseContent.Instance.Error("库存数量不足");
                // 3. æŸ¥æ‰¾ç›¸å…³çš„出库详情信息
                var outStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.OrderNo == orderNo &&
                               x.PalletCode == palletCode &&
                               x.CurrentBarcode == barcode &&
                               x.Status == 0 &&
                               x.RemainQuantity > 0)
                    .FirstAsync();
                if (outStockInfo == null)
                    return WebResponseContent.Instance.Error("未找到对应的拣选信息或已拣选完成");
                // 4. æ£€æŸ¥å‡ºåº“详情锁定数量
                if (outStockInfo.RemainQuantity <= 0)
                    return WebResponseContent.Instance.Error("该条码已无剩余可拣选数量");
                // 5. æ›´æ–°å‡ºåº“详情的已拣选数量
                outStockInfo.PickedQty = outStockInfo.AssignQuantity;
                outStockInfo.Status = 1;
                await _outStockLockInfoService.Db.Updateable(outStockInfo).ExecuteCommandAsync();
                // 6. æ›´æ–°åº“存出库数量
                await _stockInfoDetailService.Db.Updateable<Dt_StockInfoDetail>()
                    .SetColumns(x => x.OutboundQuantity == x.OutboundQuantity + outStockInfo.AssignQuantity)
                    .Where(x => x.Id == stockDetail.Id)
                    .ExecuteCommandAsync();
                // 7. æ›´æ–°å‡ºåº“单明细
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == outStockInfo.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity += outStockInfo.AssignQuantity;
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                // 8. æ£€æŸ¥æ˜¯å¦å®Œæˆå‡ºåº“
                await CheckAndUpdateOrderStatus(orderNo);
                // 9. è®°å½•拣选历史
                var pickingHistory = new Dt_PickingRecord
                {
                    OrderNo = orderNo,
                    PalletCode = palletCode,
                    Barcode = barcode,
                    MaterielCode = outStockInfo.MaterielCode,
                    PickQuantity = outStockInfo.AssignQuantity,
                    PickTime = DateTime.Now,
                    OutStockLockId = outStockInfo.Id
                };
                await Db.Insertable(pickingHistory).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("拣选确认成功");
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"拣选确认失败:{ex.Message}");
            }
        }
        // æ£€æŸ¥å¹¶æ›´æ–°è®¢å•状态
        private async Task CheckAndUpdateOrderStatus(string orderNo)
        {
            var orderDetails = await _stockInfoDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                .Where(x => x.OrderId == orderNo.ObjToInt())
                .ToListAsync();
            bool allCompleted = true;
            foreach (var detail in orderDetails)
            {
                if (detail.OverOutQuantity < detail.NeedOutQuantity)
                {
                    allCompleted = false;
                    break;
                }
            }
            if (allCompleted)
            {
                await _outboundOrderService.Db.Updateable<Dt_OutboundOrder>()
                    .SetColumns(x => x.OrderStatus == 2) // å·²å®Œæˆ
                    .Where(x => x.OrderNo == orderNo)
                    .ExecuteCommandAsync();
            }
        }
        // å–消拣选功能
        public async Task<WebResponseContent> CancelPicking(string orderNo, string palletCode, string barcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æ‹£é€‰è®°å½•
                var outStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.OrderNo == orderNo &&
                                   x.PalletCode == palletCode &&
                                   x.CurrentBarcode == barcode &&
                                   x.Status == 1)
                        .FirstAsync();
                if (outStockInfo == null)
                    return WebResponseContent.Instance.Error("未找到已拣选记录");
                // è¿˜åŽŸå‡ºåº“è¯¦æƒ…çŠ¶æ€
                outStockInfo.PickedQty = 0;
                outStockInfo.Status = 0;
                await _outStockLockInfoService.Db.Updateable(outStockInfo).ExecuteCommandAsync();
                // è¿˜åŽŸåº“å­˜å‡ºåº“æ•°é‡
                await _stockInfoDetailService.Db.Updateable<Dt_StockInfoDetail>()
                        .SetColumns(x => x.OutboundQuantity == x.OutboundQuantity - outStockInfo.AssignQuantity)
                        .Where(x => x.Barcode == barcode)
                        .ExecuteCommandAsync();
                // è¿˜åŽŸå‡ºåº“å•æ˜Žç»†
                var orderDetail = await _outboundOrderDetailService.Db.Queryable<Dt_OutboundOrderDetail>()
                    .Where(x => x.Id == outStockInfo.OrderDetailId)
                    .FirstAsync();
                orderDetail.OverOutQuantity -= outStockInfo.AssignQuantity;
                await _outboundOrderDetailService.Db.Updateable(orderDetail).ExecuteCommandAsync();
                // åˆ é™¤æ‹£é€‰åŽ†å²
                await Db.Deleteable<Dt_PickingRecord>()
                    .Where(x => x.OutStockLockId == outStockInfo.Id)
                    .ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("取消拣选成功");
            }
            catch (Exception ex)
            {
                return WebResponseContent.Instance.Error($"取消拣选失败:{ex.Message}");
            }
        }
        /// <summary>
        /// æ ¹æ®æ¡ç æŸ¥æ‰¾é”å®šä¿¡æ¯
        /// </summary>
@@ -217,6 +378,45 @@
                .FirstAsync();
        }
        // èŽ·å–æœªæ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetUnpickedList(string orderNo, string palletCode)
        {
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 0 &&
                       x.RemainQuantity > 0)
                .ToListAsync();
            return list;
        }
        // èŽ·å–å·²æ‹£é€‰åˆ—è¡¨
        public async Task<List<Dt_OutStockLockInfo>> GetPickedList(string orderNo, string palletCode)
        {
            var list = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.Status == 1)
                .ToListAsync();
            return list;
        }
        // èŽ·å–æ‹£é€‰æ±‡æ€»
        public async Task<object> GetPickingSummary(string orderNo)
        {
            var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.Status == 0)
                .GroupBy(x => new { x.PalletCode, x.MaterielCode })
                .Select(x => new
                {
                    PalletCode = x.PalletCode,
                    MaterielCode = x.MaterielCode,
                    UnpickedCount = SqlFunc.AggregateCount(x.Id),
                    UnpickedQuantity = SqlFunc.AggregateSum(x.RemainQuantity)
                })
                .ToListAsync();
            return  summary;
        }
        /// <summary>
        /// èŽ·å–æ‹£é€‰åŽ†å²
        /// </summary>
@@ -235,6 +435,25 @@
                .ToListAsync();
        }
        public async Task GetPalletPickingSummary(string orderNo, string palletCode)
        {
            var summary = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo && x.PalletCode == palletCode)
                .GroupBy(x => new { x.PalletCode, x.Status })
                .Select(x => new
                {
                    PalletCode = x.PalletCode,
                    Status = x.Status,
                    TotalAssignQty = SqlFunc.AggregateSum(x.AssignQuantity),
                    TotalPickedQty = SqlFunc.AggregateSum(x.PickedQty)
                })
                .ToListAsync();
            //   return summary;
        }
        /// <summary>
        /// æ’¤é”€æ‹£é€‰
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/SplitPackageService.cs
@@ -1,4 +1,5 @@
using System;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -34,40 +35,31 @@
        /// <summary>
        /// æ‹†åŒ…拆箱操作
        /// </summary>
        public async Task<WebResponseContent> SplitPackage(SplitPackageRequest request)
        public async Task<WebResponseContent> SplitPackage(SplitPackageDto request)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. éªŒè¯å‡ºåº“锁定信息
                var lockInfo = await Db.Queryable<Dt_OutStockLockInfo>()
                    .Where(x => x.Id == request.OutStockLockInfoId &&
                               x.Status == (int)OutLockStockStatusEnum.出库中)
                var lockInfo = await _stockInfoDetailService.Db.Queryable<Dt_OutStockLockInfo>()
                          .Where(x => x.OrderNo == request.OrderNo &&
                           x.PalletCode == request.PalletCode &&
                           x.CurrentBarcode == request.OriginalBarcode &&
                           x.Status == 0)
                    .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}");
                var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
               .Where(x => x.Barcode == request.OriginalBarcode)
               .FirstAsync();
                // 3. ç”Ÿæˆæ–°æ¡ç 
                string newBarcode = "";
@@ -109,9 +101,11 @@
                    OriginalBarcode = lockInfo.CurrentBarcode,
                    NewBarcode = newBarcode,
                    SplitQty = request.SplitQuantity,
                    MaterielCode = request.MaterielCode,
                    RemainQuantity = lockInfo.RemainQuantity - request.SplitQuantity,
                    MaterielCode = lockInfo.MaterielCode,
                    SplitTime = DateTime.Now,
                    Operator = request.Operator,
                    OrderNo = request.OrderNo,
                    PalletCode = request.PalletCode,
                    Status = (int)SplitPackageStatusEnum.已拆包
                };
                await Db.Insertable(splitHistory).ExecuteCommandAsync();
@@ -134,6 +128,80 @@
            }
        }
        // æ’¤é”€æ‹†åŒ…
        public async Task<WebResponseContent> RevertSplitPackage(string originalBarcode)
        {
            try
            {
                _unitOfWorkManage.BeginTran();
                // æŸ¥æ‰¾æœ€è¿‘的未撤销拆包记录
                var splitPackage = await Db.Queryable<Dt_SplitPackageRecord>()
                        .Where(x => x.OriginalBarcode == originalBarcode && !x.IsReverted)
                        .OrderByDescending(x => x.CreateDate)
                        .FirstAsync();
                    if (splitPackage == null)
                        return  WebResponseContent.Instance.Error("未找到拆包记录");
                    // æ£€æŸ¥æ–°æ¡ç æ˜¯å¦å·²æ‹£é€‰
                    var newOutStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.CurrentBarcode == splitPackage.NewBarcode)
                        .FirstAsync();
                    if (newOutStockInfo.Status == 1)
                        return  WebResponseContent.Instance.Error("新条码已拣选,无法撤销拆包");
                    // è¿˜åŽŸåŽŸå‡ºåº“è¯¦æƒ…æ•°é‡
                    var originalOutStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                        .Where(x => x.CurrentBarcode == originalBarcode)
                        .FirstAsync();
                    originalOutStockInfo.AssignQuantity += splitPackage.SplitQty;
                    await _outStockLockInfoService.Db.Updateable(originalOutStockInfo).ExecuteCommandAsync();
                    // åˆ é™¤æ–°å‡ºåº“详情记录
                    await _outStockLockInfoService.Db.Deleteable<Dt_OutStockLockInfo>()
                        .Where(x => x.CurrentBarcode == splitPackage.NewBarcode)
                        .ExecuteCommandAsync();
                    // æ ‡è®°æ‹†åŒ…记录为已撤销
                    splitPackage.IsReverted = true;
                    await Db.Updateable(splitPackage).ExecuteCommandAsync();
                _unitOfWorkManage.CommitTran();
                    return  WebResponseContent.Instance.OK("撤销拆包成功");
            }
            catch (Exception ex)
            {
                return  WebResponseContent.Instance.Error($"撤销拆包失败:{ex.Message}");
            }
        }
        // èŽ·å–æ‹†åŒ…ä¿¡æ¯
        public async Task< WebResponseContent > GetSplitPackageInfo(string orderNo, string palletCode, string barcode)
        {
            var outStockInfo = await _outStockLockInfoService.Db.Queryable<Dt_OutStockLockInfo>()
                .Where(x => x.OrderNo == orderNo &&
                           x.PalletCode == palletCode &&
                           x.CurrentBarcode == barcode &&
                           x.Status == 0)
                .FirstAsync();
            if (outStockInfo == null)
                return  WebResponseContent.Instance .Error("未找到对应的出库信息");
            var stockDetail = await _stockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.Barcode == barcode)
                .FirstAsync();
            return  WebResponseContent.Instance .OK("",new
            {
                MaterielCode = outStockInfo.MaterielCode,
                RemainQuantity = outStockInfo.RemainQuantity,
                Unit = "个" // æ ¹æ®å®žé™…情况获取单位
            });
        }
        /// <summary>
        /// èŽ·å–å¯æ‹†åŒ…çš„å‡ºåº“é”å®šä¿¡æ¯
        /// </summary>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -362,8 +362,7 @@
                    return content.Error($"未找到对应的终点货位信息");
                }
                _stockRepository.Db.Deleteable(stockInfo).ExecuteCommand();
                int beforeStatus = locationInfo.LocationStatus;
                locationInfo.LocationStatus = LocationStatusEnum.Free.ObjToInt();
@@ -374,6 +373,7 @@
                task.TaskStatus = TaskStatusEnum.Finish.ObjToInt();
                BaseDal.DeleteAndMoveIntoHty(task, App.User.UserId == 0 ? OperateTypeEnum.自动完成 : OperateTypeEnum.人工完成);
                _stockService.StockInfoService.Repository.DeleteAndMoveIntoHty(stockInfo, App.User.UserId == 0 ? OperateTypeEnum.自动完成 : OperateTypeEnum.人工完成);
                _stockRepository.Db.Deleteable(stockInfo).ExecuteCommand();
                _locationStatusChangeRecordService.AddLocationStatusChangeRecord(locationInfo, beforeStatus, StockChangeType.Outbound.ObjToInt(), stockInfo.Details.FirstOrDefault()?.OrderNo ?? "", task.TaskNum);
@@ -390,8 +390,7 @@
      
        /// <summary>
        /// å›žåº“完成回调 - AGV将托盘放回货位后调用
        /// </summary>
        /// å›žåº“完成回调
        public async Task<WebResponseContent> BackToStockComplete(Dt_Task task)
        {
            try
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutStockLockInfoController.cs
@@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Org.BouncyCastle.Utilities.Collections;
using WIDESEA_Core;
using WIDESEA_Core.BaseController;
using WIDESEA_Core.Helper;
using WIDESEA_IOutboundService;
using WIDESEA_Model.Models;
@@ -19,5 +21,14 @@
        {
        }
            [HttpGet("getOutStockLockInfo")]
    public async Task<WebResponseContent> GetOutStockLockInfo(string orderDetailId)
        {
            var list = await Service.GetByOrderDetailId(orderDetailId.ObjToInt());
            return WebResponseContent.Instance.OK(null, list);
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Outbound/OutboundPickingController.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using WIDESEA_Core;
using WIDESEA_Core.BaseController;
using WIDESEA_DTO.Outbound;
@@ -38,14 +39,43 @@
            return WebResponseContent.Instance.OK(null, lockInfos);
        }
        /// <summary>
        /// æ‹£é€‰ç¡®è®¤
        /// </summary>
        [HttpPost("ConfirmPicking")]
        public async Task<WebResponseContent> ConfirmPicking([FromBody] PickingConfirmRequest request)
        [HttpGet("unpicked-list")]
        public async Task<WebResponseContent> GetUnpickedList(string orderNo, string palletCode)
        {
            return await Service.ConfirmPicking(request);
            var lists= await Service.GetUnpickedList(orderNo, palletCode);
            return WebResponseContent.Instance.OK("", lists);
        }
        [HttpGet("picked-list")]
        public async Task<WebResponseContent> GetPickedList(string orderNo, string palletCode)
        {
            var lists = await Service.GetPickedList(orderNo, palletCode);
            return WebResponseContent.Instance.OK("", lists);
        }
        [HttpPost("confirm-picking")]
        public async Task<WebResponseContent> ConfirmPicking([FromBody] ConfirmPickingDto dto)
        {
            return await Service.ConfirmPicking(dto.OrderNo, dto.PalletCode, dto.Barcode);
        }
        [HttpGet("picking-summary")]
        public async Task<WebResponseContent> GetPickingSummary(string orderNo)
        {
            var data = await Service.GetPickingSummary(orderNo);
            return WebResponseContent.Instance.OK("", data);
        }
        ///// <summary>
        ///// æ‹£é€‰ç¡®è®¤
        ///// </summary>
        //[HttpPost("ConfirmPicking")]
        //public async Task<WebResponseContent> ConfirmPicking([FromBody] PickingConfirmRequest request)
        //{
        //    return await Service.ConfirmPicking(request);
        //}
        /// <summary>
        /// éªŒè¯æ¡ç å¹¶èŽ·å–ç‰©æ–™ä¿¡æ¯
        /// </summary>
@@ -58,7 +88,7 @@
        /// æ‹†åŒ…操作
        /// </summary>
        [HttpPost("SplitPackage")]
        public async Task<WebResponseContent> SplitPackage([FromBody] SplitPackageRequest request)
        public async Task<WebResponseContent> SplitPackage([FromBody] SplitPackageDto request)
        {
            return await _splitPackageService.SplitPackage(request);
        }