647556386
2026-01-12 300ca9810420efbf8468c9d6f47bd364c9c72d5f
盘点更改
已添加1个文件
已修改21个文件
3369 ■■■■■ 文件已修改
项目代码/WIDESEA_WMSClient/config/buttons.js 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/OrderStockTake.vue 337 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/StockTakeGroupPallet.vue 1124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/TakeStockSelect.vue 249 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/extend/allocateOrderDetail.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/takeStockOrder.js 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/inbound/takeStockOrderDetail.js 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/DirectOutbound.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/StockSelect.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue 133 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/PickingOutboundRequestDTO.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IInboundService/IInboundService.cs 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IInboundService/ITakeStockOrderService.cs 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundService.cs 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/InboundService.cs 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_InboundService/TakeStockOrderService.cs 262 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs 827 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderController.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundController.cs 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
项目代码/WMS无仓储版/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/TakeStockOrderController.cs 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ÏîÄ¿´úÂë/WIDESEA_WMSClient/config/buttons.js
@@ -284,7 +284,7 @@
    onClick: function () {
    }
},{
    name: "单 æ® ç›˜ ç‚¹ æ“ ä½œ",
    name: "单 æ® ç›˜ äº æ“ ä½œ",
    icon: '',
    class: '',
    value: 'OrderStockTake',
@@ -315,7 +315,7 @@
    type: 'warning',
    onClick: function () {
    }
},,{
},{
    name: "关闭单据",
    icon: '',
    class: '',
@@ -323,7 +323,23 @@
    type: 'warning',
    onClick: function () {
    }
},
},{
    name: "盘亏杂发操作",
    icon: '',
    class: '',
    value: 'OutOrderStockTake',
    type: 'warning',
    onClick: function () {
    }
},{
    name: "盘亏组盘操作",
    icon: '',
    class: '',
    value: 'StockTakeGroupPallet',
    type: 'warning',
    onClick: function () {
    }
}
]
export default buttons
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/OrderStockTake.vue
@@ -15,7 +15,7 @@
          </el-tag>
        </div>
        <!-- æ ¸å¿ƒè¾“入表单 -->
        <!-- æ ¸å¿ƒè¾“入表单:移除所有校验规则 -->
        <el-form
          :model="formData"
          ref="formRef"
@@ -23,20 +23,8 @@
          class="stock-take-form"
          @submit.prevent
        >
          <!-- æ–™ç®±å·è¾“入框 -->
          <el-form-item
            label="料箱号:"
            name="boxNo"
            :rules="[
              {
                required: true,
                message: '请扫描或输入料箱号',
                trigger: 'blur',
              },
              { validator: validateBoxNo, trigger: 'blur' },
            ]"
            class="form-item"
          >
          <!-- æ–™ç®±å·è¾“入框:移除校验规则 -->
          <el-form-item label="料箱号:" name="boxNo" class="form-item">
            <el-input
              ref="boxNoInputRef"
              v-model="formData.boxNo"
@@ -54,30 +42,19 @@
                  type="primary"
                  size="small"
                  @click="handleBoxNoScan"
                  :disabled="!formData.boxNo.trim() || loading"
                  :disabled="loading"
                  class="input-btn"
                ></el-button>
              </template>
            </el-input>
          </el-form-item>
          <!-- æ–°å¢žï¼šç«™å°é€‰æ‹©ä¸‹æ‹‰æ¡† -->
          <el-form-item
            label="回库站台:"
            name="station"
            :rules="[
              {
                required: true,
                message: '请选择回库站台',
                trigger: 'blur',
              },
            ]"
            class="form-item"
          >
          <!-- ç«™å°é€‰æ‹©ä¸‹æ‹‰æ¡†ï¼šç§»é™¤æ ¡éªŒè§„则 -->
          <el-form-item label="回库站台:" name="station" class="form-item">
            <el-select
              v-model="selectedStation"
              placeholder="请选择回库站台"
              :disabled="!formData.boxNo.trim() || loading"
              :disabled="loading"
              class="custom-input"
              :class="{ 'has-value': selectedStation }"
            >
@@ -90,23 +67,15 @@
            </el-select>
          </el-form-item>
          <!-- æ¡ç è¾“入框 -->
          <el-form-item
            label="盘点条码:"
            name="barcode"
            :rules="[
              { required: true, message: '请扫描或输入条码', trigger: 'blur' },
              { validator: validateBarcode, trigger: 'blur' },
            ]"
            class="form-item"
          >
          <!-- æ¡ç è¾“入框:移除校验规则 -->
          <el-form-item label="盘点条码:" name="barcode" class="form-item">
            <el-input
              ref="barcodeInputRef"
              v-model="formData.barcode"
              placeholder="请使用扫码枪扫描条码,或手动输入"
              clearable
              @keydown.enter="debouncedHandleBarcodeScan"
              :disabled="!formData.boxNo.trim() || loading"
              :disabled="loading"
              class="custom-input"
              :class="{ 'has-value': formData.barcode.trim() }"
            >
@@ -116,11 +85,7 @@
                  type="primary"
                  size="small"
                  @click="handleBarcodeScan"
                  :disabled="
                    !formData.boxNo.trim() ||
                    !formData.barcode.trim() ||
                    loading
                  "
                  :disabled="loading"
                  class="input-btn"
                ></el-button>
              </template>
@@ -128,11 +93,7 @@
          </el-form-item>
          <!-- åº“存数量框(只读) -->
          <el-form-item
            label="库存数量:"
            name="stockQuantity"
            class="form-item"
          >
          <el-form-item label="库存数量:" name="stockQuantity" class="form-item">
            <el-input
              v-model="formData.stockQuantity"
              placeholder="扫描条码后自动填充"
@@ -142,41 +103,25 @@
            ></el-input>
          </el-form-item>
          <!-- å®žé™…盘点数量 -->
          <el-form-item
            label="实际盘点数量:"
            name="actualQuantity"
            :rules="[
              {
                required: true,
                message: '请输入实际盘点数量',
                trigger: 'blur',
              },
              { type: 'number', message: '请输入有效的数字', trigger: 'blur' },
              { validator: validateActualQuantity, trigger: 'blur' },
            ]"
            class="form-item"
          >
          <!-- å®žé™…盘点数量:只读,默认0 -->
          <el-form-item label="实际盘点数量:" name="actualQuantity" class="form-item">
            <el-input
              v-model.number="formData.actualQuantity"
              placeholder="请输入实际盘点数量"
              type="number"
              clearable
              @keydown.enter="handleStockTakeComplete"
              :disabled="!formData.stockQuantity || loading"
              class="custom-input"
              v-model="formData.actualQuantity"
              placeholder="默认值为0"
              readonly
              class="custom-input custom-readonly-input"
              :class="{ 'has-value': formData.actualQuantity !== '' }"
            ></el-input>
          </el-form-item>
        </el-form>
        <!-- æ“ä½œæŒ‰é’®åŒºåŸŸ -->
        <!-- æ“ä½œæŒ‰é’®åŒºåŸŸï¼šç®€åŒ–禁用逻辑 -->
        <div class="action-buttons">
          <el-button
            type="info"
            size="small"
            @click="handleBoxReturn"
            :disabled="!formData.boxNo.trim() || !selectedStation || loading"
            :disabled="loading"
            class="return-btn"
          >
            <Return /> æ–™ç®±å›žåº“
@@ -185,7 +130,7 @@
            type="primary"
            size="small"
            @click="handleStockTakeComplete"
            :disabled="isFormCompleteDisabled"
            :disabled="loading"
            class="complete-btn"
          >
            <Check /> ç›˜ç‚¹å®Œæˆ
@@ -211,7 +156,6 @@
import VolBox from "@/components/basic/VolBox.vue";
import http from "@/api/http";
// æ–°å¢žï¼šå¼•入站台管理工具(和示例代码保持一致)
import { stationManager, STATION_STORAGE_KEY } from "@/../src/uitils/stationManager";
// å“åº”式变量 - å•据号
@@ -225,18 +169,18 @@
const formData = reactive({
  boxNo: "", // æ–™ç®±å·
  barcode: "", // ç›˜ç‚¹æ¡ç 
  stockQuantity: "", // åº“存数量(只读)
  actualQuantity: "", // å®žé™…盘点数量
  stockQuantity: "", // åº“存数量
  actualQuantity: 0, // å®žé™…盘点数量默认0
});
const loading = ref(false);
const formRef = ref(null);
// æ–°å¢žï¼šç«™å°ç›¸å…³å“åº”式数据(仿照示例代码)
// ç«™å°ç›¸å…³å“åº”式数据
const stations = ref([
  { label: "站台2", value: "2-1" },
  { label: "站台3", value: "3-1" },
]);
const selectedStation = ref(stationManager.getStation() || ""); // é»˜è®¤é€‰ä¸­ç¼“存的站台
const selectedStation = ref(stationManager.getStation() || "");
// æ¨¡æ¿å¼•用
const boxNoInputRef = ref(null);
@@ -247,39 +191,24 @@
  return window.innerWidth < 768;
});
// è®¡ç®—属性:盘点完成按钮是否禁用
const isFormCompleteDisabled = computed(() => {
  return (
    loading.value ||
    !formData.boxNo.trim() ||
    !formData.barcode.trim() ||
    !formData.stockQuantity ||
    formData.actualQuantity === "" ||
    formData.actualQuantity === null ||
    formData.actualQuantity === undefined ||
    Number(formData.actualQuantity) < 0 // å°äºŽ0时禁用,0是允许的
  );
});
// ç»„件挂载时聚焦到料箱号输入框
onMounted(() => {
  nextTick(() => {
    boxNoInputRef.value?.focus();
  });
  // ç›‘听窗口大小变化
  window.addEventListener("resize", () => {});
});
// ç›‘听料箱号变化,清空后续相关字段
// ç›‘听料箱号变化,清空后续相关字段(仅保留基础清空逻辑)
watch(
  () => formData.boxNo,
  (newVal) => {
    if (!newVal.trim()) {
      formData.barcode = "";
      formData.stockQuantity = "";
      formData.actualQuantity = "";
      // æ–°å¢žï¼šæ–™ç®±å·æ¸…空时,重置站台选择
      formData.actualQuantity = 0;
      selectedStation.value = stationManager.getStation() || "";
      orderNo.value = "";
    }
  },
  { immediate: true }
@@ -296,17 +225,16 @@
  };
};
// æ‰“开弹窗并接收单据号
const open = (receiptNo) => {
// æ‰“开弹窗
const open = () => {
  showStockTakeBox.value = true;
  orderNo.value = receiptNo;
  // é‡ç½®è¡¨å•
  formData.boxNo = "";
  formData.barcode = "";
  formData.stockQuantity = "";
  formData.actualQuantity = "";
  // æ–°å¢žï¼šæ‰“开弹窗时重置站台选择(默认取缓存的站台)
  formData.actualQuantity = 0;
  selectedStation.value = stationManager.getStation() || "";
  orderNo.value = "";
  nextTick(() => {
    boxNoInputRef.value?.focus();
  });
@@ -321,43 +249,6 @@
  orderNo.value = "";
};
// æ–™ç®±å·éªŒè¯ï¼ˆä¼˜åŒ–:有值时仅验证长度,无值时触发必填)
const validateBoxNo = (rule, value, callback) => {
  // æœ‰å€¼æ—¶éªŒè¯é•¿åº¦
  if (value && value.trim().length < 3) {
    callback(new Error("料箱号长度不能少于3位"));
  }
  // æ— å€¼æ—¶ç”±required规则处理,这里不重复提示
  else {
    callback();
  }
};
// æ¡ç éªŒè¯ï¼ˆä¼˜åŒ–:有值时仅验证长度)
const validateBarcode = (rule, value, callback) => {
  if (value && value.trim().length < 6) {
    callback(new Error("条码长度不能少于6位"));
  } else {
    callback();
  }
};
// å®žé™…盘点数量验证:允许0,但不允许小于0
const validateActualQuantity = (rule, value, callback) => {
  // ç©ºå€¼ç”±required规则处理,这里只验证数值合法性
  if (value === null || value === undefined || value === "") {
    callback(); // ç©ºå€¼ä¸åœ¨è¿™é‡Œæç¤ºï¼Œäº¤ç»™required规则
  }
  // éªŒè¯æ•°å€¼æ˜¯å¦å°äºŽ0
  else if (value < 0) {
    callback(new Error("实际盘点数量不能小于0"));
  }
  // éªŒè¯é€šè¿‡ï¼ˆå…è®¸0)
  else {
    callback();
  }
};
// æ–™ç®±å·å¤±ç„¦å¤„理
const handleBoxNoBlur = () => {
  if (formData.boxNo.trim() && boxNoInputRef.value?.input) {
@@ -365,39 +256,29 @@
  }
};
// æ–™ç®±å·æ‰«æéªŒè¯ï¼ˆåŽç«¯æŽ¥å£ï¼‰
const validateBoxNoApi = async (boxNo) => {
// æ–™ç®±å·æ‰«æå¤„理:移除前端校验,直接调用接口
const handleBoxNoScan = async () => {
  const boxNo = formData.boxNo.trim();
  if (!boxNo) {
    ElMessage.warning("请输入料箱号");
    return;
  }
  try {
    loading.value = true;
    // ç›´æŽ¥è°ƒç”¨æŽ¥å£ï¼Œä¸åšå‰ç«¯æ ¡éªŒ
    const res = await http.post(
      `/api/TakeStockOrder/ValidateBoxNo?orderNo=${encodeURIComponent(
        orderNo.value
      )}&boxNo=${encodeURIComponent(boxNo)}`,
      `/api/TakeStockOrder/ValidateBoxNo?orderNo=${encodeURIComponent(orderNo.value)}&boxNo=${encodeURIComponent(boxNo)}`,
      "验证料箱号中..."
    );
    if (!res.status) {
      throw new Error(res.message || "料箱号验证失败");
    }
    return res.data;
  } catch (error) {
    throw error;
    if (res.data?.takeStockOrder) {
      orderNo.value = res.data.takeStockOrder;
  }
};
// æ–™ç®±å·æ‰«æå¤„理
const handleBoxNoScan = async () => {
  if (!formRef.value) return;
  // æ‰‹åŠ¨è§¦å‘éªŒè¯ï¼ˆä»…éªŒè¯å½“å‰å­—æ®µï¼‰
  const valid = await formRef.value.validateField("boxNo");
  if (valid !== true) return;
  const boxNo = formData.boxNo.trim();
  if (!boxNo) return;
  try {
    loading.value = true;
    await validateBoxNoApi(boxNo);
    ElMessage.success(`料箱号 ${boxNo} éªŒè¯é€šè¿‡`);
    ElMessage.success(`料箱号 ${boxNo} éªŒè¯é€šè¿‡ï¼Œå•据号:${orderNo.value}`);
    nextTick(() => {
      barcodeInputRef.value?.focus();
    });
@@ -413,13 +294,20 @@
  }
};
// æ¡ç æ‰«æéªŒè¯ï¼ˆåŽç«¯æŽ¥å£ï¼‰
const validateBarcodeApi = async (boxNo, barcode) => {
// æ¡ç æ‰«æå¤„理:移除前端校验,直接调用接口
const handleBarcodeScan = async () => {
  const boxNo = formData.boxNo.trim();
  const barcode = formData.barcode.trim();
  if (!boxNo || !barcode) {
    ElMessage.warning("请输入料箱号和条码");
    return;
  }
  try {
    loading.value = true;
    // ç›´æŽ¥è°ƒç”¨æŽ¥å£ï¼Œä¸åšå‰ç«¯æ ¡éªŒ
    const res = await http.post(
      `/api/TakeStockOrder/ValidateBarcode?boxNo=${encodeURIComponent(
        boxNo
      )}&barcode=${encodeURIComponent(barcode)}`,
      `/api/TakeStockOrder/ValidateBarcode?boxNo=${encodeURIComponent(boxNo)}&barcode=${encodeURIComponent(barcode)}`,
      "验证条码中..."
    );
@@ -427,41 +315,13 @@
      throw new Error(res.message || "条码验证失败");
    }
    if (
      res.data?.stockQuantity === undefined ||
      res.data?.stockQuantity === null
    ) {
      throw new Error("未查询到该条码的库存数量");
    if (res.data?.stockQuantity !== undefined && res.data?.stockQuantity !== null) {
      formData.stockQuantity = res.data.stockQuantity;
    }
    return res.data;
  } catch (error) {
    throw error;
  }
};
// æ¡ç æ‰«æå¤„理
const handleBarcodeScan = async () => {
  if (!formRef.value) return;
  const valid = await formRef.value.validateField("barcode");
  if (valid !== true) return;
  const boxNo = formData.boxNo.trim();
  const barcode = formData.barcode.trim();
  if (!boxNo || !barcode) return;
  try {
    loading.value = true;
    const result = await validateBarcodeApi(boxNo, barcode);
    formData.stockQuantity = result.stockQuantity;
    ElMessage.success(
      `条码 ${barcode} éªŒè¯é€šè¿‡ï¼Œåº“存数量:${result.stockQuantity}`
    );
    ElMessage.success(`条码 ${barcode} éªŒè¯é€šè¿‡ï¼Œåº“存数量:${formData.stockQuantity}`);
    nextTick(() => {
      const actualQuantityInput = document.querySelector(
        ".form-item:last-child .el-input__inner"
      );
      actualQuantityInput?.focus();
      const actualQuantityInput = document.querySelector(".form-item:last-child .el-input__inner");
      actualQuantityInput?.blur();
    });
  } catch (error) {
    ElMessage.error(error.message);
@@ -475,32 +335,33 @@
  }
};
// ç›˜ç‚¹å®Œæˆæäº¤
// ç›˜ç‚¹å®Œæˆæäº¤ï¼šå®Œå…¨ç§»é™¤å‰ç«¯æ ¡éªŒï¼Œç›´æŽ¥ä¼ å€¼ç»™åŽç«¯
const handleStockTakeComplete = async () => {
  if (!formRef.value) return;
  // å…¨è¡¨å•验证
  const valid = await formRef.value.validate();
  if (!valid) return;
  // é¢å¤–检查实际盘点数量是否小于0
  if (formData.actualQuantity < 0) {
    ElMessage.error("实际盘点数量不能小于0");
  // ä»…做最基础的空提示(可选,也可以去掉)
  if (!formData.boxNo.trim()) {
    ElMessage.warning("请输入料箱号");
    return;
  }
  if (!formData.barcode.trim()) {
    ElMessage.warning("请输入条码");
    return;
  }
  // ç›´æŽ¥èŽ·å–æ‰€æœ‰å€¼ï¼Œä¸åšä»»ä½•æ ¡éªŒ
  const { boxNo, barcode, actualQuantity, stockQuantity } = formData;
  const receiptNo = orderNo.value;
  try {
    loading.value = true;
    // ç›´æŽ¥ä¼ å€¼ç»™åŽç«¯ï¼Œä¸åšä»»ä½•前端校验
    const res = await http.post(
      "/api/TakeStockOrder/CompleteStockTake",
      {
        orderNo: receiptNo,
        boxNo,
        barcode,
        actualQuantity,
        actualQuantity, // å›ºå®šä¼ 0
        stockQuantity,
      },
      {
@@ -512,7 +373,7 @@
      ElMessage.success("盘点完成,提交成功!");
      formData.barcode = "";
      formData.stockQuantity = "";
      formData.actualQuantity = "";
      formData.actualQuantity = 0;
      nextTick(() => {
        barcodeInputRef.value?.focus();
      });
@@ -527,16 +388,11 @@
  }
};
// æ–™ç®±å›žåº“功能(核心修改:增加站台验证 + ä¼ å…¥sourceAddress参数)
// æ–™ç®±å›žåº“功能:简化校验,直接传值
const handleBoxReturn = async () => {
  const boxNo = formData.boxNo.trim();
  // æ–°å¢žï¼šéªŒè¯ç«™å°æ˜¯å¦é€‰æ‹©
  if (!boxNo) {
    ElMessage.warning("请先输入或扫描料箱号");
    return;
  }
  if (!selectedStation.value) {
    ElMessage.warning("请选择回库站台");
    ElMessage.warning("请输入料箱号");
    return;
  }
@@ -552,11 +408,9 @@
    loading.value = true;
    // æ–°å¢žï¼šæ‹¼æŽ¥sourceAddress参数(站台数据)到接口请求中
    // ç›´æŽ¥ä¼ å€¼ç»™åŽç«¯ï¼Œä¸åšä¸¥æ ¼æ ¡éªŒ
    const res = await http.post(
      `/api/TakeStockOrder/ReturnBox?boxNo=${encodeURIComponent(boxNo)}&orderNo=${encodeURIComponent(
        orderNo.value
      )}&sourceAddress=${encodeURIComponent(selectedStation.value)}`, // æ–°å¢žsourceAddress参数
      `/api/TakeStockOrder/ReturnBox?boxNo=${encodeURIComponent(boxNo)}&orderNo=${encodeURIComponent(orderNo.value)}&sourceAddress=${encodeURIComponent(selectedStation.value)}`,
      "料箱回库中..."
    );
@@ -565,8 +419,9 @@
      formData.boxNo = "";
      formData.barcode = "";
      formData.stockQuantity = "";
      formData.actualQuantity = "";
      selectedStation.value = stationManager.getStation() || ""; // é‡ç½®ç«™å°é€‰æ‹©
      formData.actualQuantity = 0;
      selectedStation.value = stationManager.getStation() || "";
      orderNo.value = "";
      nextTick(() => {
        boxNoInputRef.value?.focus();
      });
@@ -603,7 +458,7 @@
</script>
<style scoped>
/* ä¸»å®¹å™¨æ ·å¼ - ä¼˜åŒ–间距和响应式 */
/* æ ·å¼å®Œå…¨ä¿ç•™ï¼Œæ— ä¿®æ”¹ */
.stock-take-container {
  width: 100%;
  display: flex;
@@ -612,7 +467,6 @@
  padding: 8px 0;
}
/* å•据信息样式 - ä¼˜åŒ–视觉层级 */
.receipt-info {
  margin-bottom: 4px;
}
@@ -633,7 +487,6 @@
  margin-left: 6px;
}
/* è¡¨å•样式 - ä¼˜åŒ–阴影和内边距 */
.stock-take-form {
  width: 100%;
  background-color: #ffffff;
@@ -647,17 +500,15 @@
  margin-bottom: 16px;
}
/* è¾“入框核心样式 - ä¼˜åŒ–状态显示 */
.custom-input {
  width: 100%;
  position: relative;
}
/* æœ‰å€¼æ—¶éšè—å ä½ç¬¦ + ä¼˜åŒ–边框 */
.custom-input.has-value :deep(.el-input__inner),
.custom-input.has-value :deep(.el-select__wrapper) {
  --el-input-placeholder-color: transparent; /* éšè—å ä½ç¬¦ */
  border-color: #8cc5ff; /* æµ…蓝边框,区分无值状态 */
  --el-input-placeholder-color: transparent;
  border-color: #8cc5ff;
  background-color: #ffffff;
}
@@ -673,7 +524,6 @@
  padding: 0 12px;
}
/* èšç„¦æ ·å¼ä¼˜åŒ– */
.custom-input :deep(.el-input__inner:focus),
.custom-input :deep(.el-select__wrapper:focus) {
  border-color: #409eff;
@@ -682,7 +532,6 @@
  outline: none;
}
/* æ¸…除按钮样式优化 */
.custom-input :deep(.el-input__clear) {
  color: #91c9f7;
  width: 18px;
@@ -693,7 +542,6 @@
  color: #409eff;
}
/* åªè¯»è¾“入框样式 - å¢žå¼ºåŒºåˆ†åº¦ */
.custom-readonly-input :deep(.el-input__inner) {
  background-color: #f0f8fb;
  color: #1989fa;
@@ -703,7 +551,6 @@
  padding-right: 12px;
}
/* è¾“入框按钮样式 - ä¼˜åŒ–尺寸和hover效果 */
.input-btn {
  border-radius: 0 8px 8px 0 !important;
  height: 42px;
@@ -720,28 +567,23 @@
  transform: translateY(-1px);
}
/* è¡¨å•验证错误样式 - å…³é”®ï¼šæœ‰å€¼æ—¶ä¸æ˜¾ç¤ºé”™è¯¯è¾¹æ¡† */
.form-item :deep(.el-form-item__error) {
  font-size: 12px;
  color: #f56c6c;
  margin-top: 4px;
  /* ä»…在输入框为空且验证失败时显示 */
  display: none;
}
/* åªæœ‰è¾“入框为空 + éªŒè¯å¤±è´¥æ—¶æ˜¾ç¤ºé”™è¯¯æç¤º */
.form-item:deep(.el-form-item--error) .custom-input:not(.has-value) + .el-form-item__error {
  display: block;
}
/* æœ‰å€¼æ—¶å³ä½¿éªŒè¯å¤±è´¥ï¼Œä¹Ÿä¸æ˜¾ç¤ºé”™è¯¯è¾¹æ¡† */
.custom-input.has-value :deep(.el-input__inner.el-input__inner--error),
.custom-input.has-value :deep(.el-select__wrapper.el-select__wrapper--error) {
  border-color: #8cc5ff;
  box-shadow: none;
}
/* æ“ä½œæŒ‰é’®åŒºåŸŸ - ä¼˜åŒ–间距和响应式 */
.action-buttons {
  display: flex;
  justify-content: flex-end;
@@ -750,7 +592,6 @@
  flex-wrap: wrap;
}
/* å›žåº“按钮样式 - ä¼˜åŒ–交互 */
.return-btn {
  border-radius: 8px;
  padding: 9px 20px;
@@ -779,7 +620,6 @@
  box-shadow: none;
}
/* ç›˜ç‚¹å®ŒæˆæŒ‰é’®æ ·å¼ - ä¼˜åŒ–交互 */
.complete-btn {
  border-radius: 8px;
  padding: 9px 20px;
@@ -806,7 +646,6 @@
  box-shadow: none;
}
/* å–消按钮样式 - ä¼˜åŒ–点击区域 */
.cancel-btn {
  color: #666666;
  font-size: 14px;
@@ -821,7 +660,6 @@
  background-color: #f0f8fb !important;
}
/* å¼¹çª—整体样式 - ä¼˜åŒ–圆角和阴影 */
.custom-vol-box {
  border-radius: 16px !important;
  overflow: hidden;
@@ -844,7 +682,6 @@
  padding: 16px 24px;
}
/* å“åº”式适配 - å°å±å¹•优化 */
@media (max-width: 768px) {
  .stock-take-form {
    padding: 16px;
@@ -869,14 +706,12 @@
</style>
<style>
/* å…¨å±€è¡¥å……样式 - ç»Ÿä¸€ä¸»é¢˜ */
.el-tag--primary {
  --el-tag-bg-color: #e8f4f8;
  --el-tag-border-color: #409eff;
  --el-tag-text-color: #1989fa;
}
/* å ä½ç¬¦æ ·å¼ - ç»Ÿä¸€é¢œè‰² */
.el-input__inner::-webkit-input-placeholder,
.el-select__placeholder {
  color: #b3d8ff;
@@ -901,7 +736,6 @@
  font-size: 13px;
}
/* ä¿¡æ¯æŒ‰é’®å…¨å±€æ ·å¼ */
.el-button--info {
  --el-button-bg-color: #e8f4f8;
  --el-button-border-color: #409eff;
@@ -911,7 +745,6 @@
  --el-button-hover-text-color: #1989fa;
}
/* ç¡®è®¤å¼¹çª—样式优化 */
.el-message-box {
  border-radius: 12px !important;
  box-shadow: 0 4px 20px rgba(64, 158, 255, 0.15);
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/StockTakeGroupPallet.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1124 @@
<template>
  <vol-box v-model="show" :title="'组盘操作 - å•据号:' + orderNo" :height="1000" :width="1100" :padding="20" :modal="true">
    <div class="barcode-scanner-container">
      <!-- ä»“库选择 - ç´§å‡‘布局 -->
      <div class="location-section compact">
        <el-form :model="form" :rules="rules" ref="locationForm" class="compact-form">
          <el-form-item label="入库仓库" prop="warehouseType" class="location-select compact-item">
            <el-select v-model="form.warehouseType" placeholder="请选择仓库" clearable filterable
              @change="handleWarehouseChange" style="width: 100%" :loading="warehouseLoading" size="medium">
              <el-option v-for="item in warehouseTypes" :key="item.warehouseType" :label="item.warehouseTypeDesc"
                :value="item.warehouseType" />
            </el-select>
          </el-form-item>
        </el-form>
      </div>
      <!-- ä»“库区域选择 - ç´§å‡‘布局 -->
      <div class="location-section compact">
        <el-form :model="form" :rules="rules" ref="locationForm" class="compact-form">
          <el-form-item label="仓库区域" prop="locationType" class="location-select compact-item">
            <el-select v-model="form.locationType" placeholder="请先选择仓库" clearable filterable
              @change="handleLocationChange" style="width: 100%" :loading="locationLoading" size="medium">
              <el-option v-for="item in locationTypes" :key="item.locationType" :label="item.locationTypeDesc"
                :value="item.locationType" />
            </el-select>
          </el-form-item>
        </el-form>
      </div>
      <!-- æ‰˜ç›˜ä¿¡æ¯æ˜¾ç¤º - ç´§å‡‘布局 -->
      <div class="tray-info compact" v-if="trayBarcode">
        <i class="el-icon-s-management"></i> å½“前料箱: {{ trayBarcode }}
        <span class="location-info" v-if="form.warehouseType">
          | ä»“库: {{ currentWarehouseName }}
        </span>
        <span class="location-info" v-if="form.locationType">
          | ä»“库区域: {{ currentLocationDesc }}
        </span>
      </div>
      <!-- æ‰«ç åŒºåŸŸ - ç´§å‡‘布局 -->
      <div class="input-section compact">
        <el-card shadow="hover" class="compact-card">
          <div slot="header" class="compact-header">
            <span><i class="el-icon-scanner"></i> æ‰«ç åŒºåŸŸ</span>
            <span class="scan-status">
              <span class="scan-indicator"></span>
              {{ form.locationType && form.warehouseType ? '扫码就绪' : '请先选择仓库和仓库区域' }}
            </span>
          </div>
          <!-- æ‰˜ç›˜æ¡ç è¾“å…¥ -->
          <div class="input-wrapper custom-input-group compact-input">
            <div class="input-label">托盘条码</div>
            <el-input ref="trayInput" v-model="trayBarcode" placeholder="请扫描或输入料箱码后按回车键" clearable
              :disabled="!form.locationType || !form.warehouseType" @keyup.enter.native="handleTraySubmit"
              @clear="handleTrayClear" @input="handleTrayInput" class="custom-input" size="medium">
              <template slot="append">
                <el-button @click="handleTraySubmit" type="primary" icon="el-icon-position"
                  :disabled="!form.locationType || !trayBarcode || !form.warehouseType" size="medium">
                  ç¡®è®¤
                </el-button>
              </template>
            </el-input>
          </div>
          <!-- ç‰©æ–™æ¡ç è¾“å…¥ -->
          <div class="input-wrapper custom-input-group compact-input">
            <div class="input-label">物料条码</div>
            <el-input ref="barcodeInput" v-model="barcode" placeholder="请扫描或输入物料条码后按回车键" clearable
              :disabled="!form.locationType || !trayBarcode || !form.warehouseType"
              @keyup.enter.native="handleBarcodeSubmit" @clear="handleClear" @input="handleBarcodeInput"
              class="custom-input" size="medium">
              <template slot="append">
                <el-button :loading="loading" @click="handleBarcodeSubmit" type="primary" icon="el-icon-search"
                  :disabled="!form.locationType || !trayBarcode || !barcode || !from.warehouseType" size="medium">
                  {{ loading ? '查询中...' : '查询' }}
                </el-button>
              </template>
            </el-input>
          </div>
          <div class="input-tips compact-tips">
            <p>提示:请先选择仓库 â†’ é€‰æ‹©ä»“库区域 â†’ è¾“入料箱码 â†’ è¾“入物料条码</p>
            <p v-if="!form.warehouseType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库</p>
            <p v-if="!form.locationType && !form.warehouseType" class="warning-text">⚠️ è¯·å…ˆé€‰æ‹©ä»“库区域</p>
            <p v-if="form.warehouseType && form.locationType && !trayBarcode" class="warning-text">⚠️ è¯·å…ˆè¾“入料箱码</p>
          </div>
        </el-card>
      </div>
      <!-- åŠ è½½çŠ¶æ€ -->
      <div v-if="loading" class="loading compact">
        <el-progress :percentage="100" status="success" :show-text="false" />
        <p>正在查询物料信息...</p>
      </div>
      <!-- é”™è¯¯æç¤º -->
      <div v-if="error" class="error-message compact">
        <el-alert :title="error" type="error" show-icon closable @close="error = ''" />
      </div>
      <!-- æœªç»„盘列表 -->
      <div class="unpallet-section compact">
        <el-card shadow="hover" class="compact-card unpallet-card">
          <div slot="header" class="compact-header">
            <span><i class="el-icon-tickets"></i> æœªç»„盘条码</span>
            <span class="list-actions">
              <el-tag type="primary" size="small">未组盘 {{ totalStockCount }}</el-tag>
            </span>
          </div>
          <div class="table-container">
            <el-table :data="unpalletMaterials" stripe style="width: 100%" height="100%" size="small"
              v-loading="unpalletBarcodesLoading">
              <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
              <el-table-column prop="barcode" label="条码" min-width="140" show-overflow-tooltip></el-table-column>
              <el-table-column prop="materielCode" label="物料编码" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="batchNo" label="批次" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="orderQuantity" label="数量" min-width="130" align="right"></el-table-column>
              <el-table-column prop="unit" label="单位" width="80" align="center"></el-table-column>
              <el-table-column prop="supplyCode" label="供应商" min-width="130" show-overflow-tooltip></el-table-column>
            </el-table>
          </div>
        </el-card>
      </div>
      <!-- ç‰©æ–™åˆ—表 - å›ºå®šé«˜åº¦å¸¦æ»šåŠ¨æ¡ -->
      <div class="material-list compact">
        <el-card shadow="hover" class="compact-card">
          <div slot="header" class="compact-header">
            <span><i class="el-icon-tickets"></i> ç»„盘数据</span>
            <span class="list-actions">
              <el-tag type="primary" size="small">共 {{ materials.length }} æ¡</el-tag>
              <el-tag type="primary" size="small">未入库数量 {{ totalStockSum }}{{ uniqueUnit }}</el-tag>
              <el-tag v-if="trayBarcode" type="success" size="small">托盘: {{ trayBarcode }}</el-tag>
              <el-tag v-if="form.warehouseType" type="info" size="small">仓库: {{ currentWarehouseName }}</el-tag>
              <el-tag v-if="form.locationType" type="info" size="small">区域: {{ currentLocationDesc }}</el-tag>
            </span>
          </div>
          <div v-if="materials.length === 0" class="empty-state compact">
            <i class="el-icon-document"></i>
            <p v-if="!form.warehouseType">请先选择仓库</p>
            <p v-if="!form.locationType">请先选择仓库区域</p>
            <p v-else-if="!trayBarcode">请先输入料箱条码</p>
            <p v-else>暂无物料数据,请扫描或输入物料条码</p>
          </div>
          <div class="table-container" v-else>
            <el-table :data="materials" stripe style="width: 100%" height="100%" size="small">
              <el-table-column type="index" label="序号" width="60" align="center"></el-table-column>
              <el-table-column prop="barcode" label="条码" min-width="140" show-overflow-tooltip></el-table-column>
              <el-table-column prop="materielCode" label="物料编码" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="batchNo" label="批次" min-width="150" show-overflow-tooltip></el-table-column>
              <el-table-column prop="stockQuantity" label="数量" min-width="130" align="right"></el-table-column>
              <el-table-column prop="unit" label="单位" width="80" align="center"></el-table-column>
              <el-table-column prop="supplyCode" label="供应商" min-width="130" show-overflow-tooltip></el-table-column>
              <el-table-column prop="warehouseType" label="仓库" min-width="120" show-overflow-tooltip></el-table-column>
            </el-table>
          </div>
        </el-card>
      </div>
    </div>
    <template #footer>
      <el-button type="danger" size="small" @click="handleDialogClose()">关闭</el-button>
    </template>
  </vol-box>
</template>
<script>
import http from '@/api/http.js';
import VolBox from '@/components/basic/VolBox.vue';
export default {
  components: { VolBox },
  data() {
    return {
      show: false,
      orderNo: "",
      palletVisible: this.visible,
      trayBarcodeReg: /^[A-Z]\d{9}$/,
      trayBarcode: '',
      barcode: '',
      materials: [],
      loading: false,
      error: '',
      debugMode: false,
      currentFocus: 'warehouse',
      unpalletBarcodes: [],
      unpalletBarcodesLoading: false,
      unpalletMaterials: [], // æœªç»„盘详细数据列表
      // æ‰«ç æžªç›¸å…³å˜é‡
      scanCode: '',
      lastKeyTime: null,
      isManualInput: false,
      isScanning: false,
      scanTimer: null,
      manualInputTimer: null,
      scanTarget: 'tray', // å½“前扫码目标: tray æˆ– material
      // åº“存统计相关变量
      totalStockSum: 0,
      totalStockCount: 0,
      uniqueUnit: '',
      sumLoading: false,
      sumError: '',
      // ä»“库相关变量
      warehouseTypes: [],
      warehouseLoading: false,
      // ä»“库区域相关变量
      locationTypes: [],
      locationLoading: false,
      form: {
        warehouseType: null,
        locationType: null
      },
      rules: {
        locationType: [
          {
            validator: this.validateLocationType,
            trigger: 'change'
          }
        ],
        trayBarcode: [
          {
            pattern: this.trayBarcodeReg,
            message: '托盘号格式错误(需为1个大写字母+9个数字,如A000008024)',
            trigger: 'blur'
          }
        ],
        warehouseType: [
          {
            message: '请选择仓库',
            trigger: 'change'
          }
        ]
      }
    }
  },
  computed: {
    // å½“前选择的仓库名称
    currentWarehouseName() {
      const warehouse = this.warehouseTypes.find(item => item.warehouseType === this.form.warehouseType);
      return warehouse ? warehouse.warehouseTypeDesc : '';
    },
    // å½“前选择的仓库区域描述
    currentLocationDesc() {
      const location = this.locationTypes.find(item => item.locationType === this.form.locationType)
      return location ? location.locationTypeDesc : ''
    }
  },
  watch: {
    visible(newVal, oldVal) {
      this.palletVisible = newVal;
      // å½“从 false å˜ä¸º true æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†æ‰“å¼€
      if (newVal === true && oldVal === false) {
        console.log('弹框打开,重置数据');
        this.resetData();
        this.$nextTick(() => {
          setTimeout(() => {
            this.fetchUnpalletMaterialDetails();
          }, 300);
        });
      }
      // å½“从 true å˜ä¸º false æ—¶ï¼Œè¡¨ç¤ºå¼¹æ¡†å…³é—­
      if (newVal === false && oldVal === true) {
        console.log('弹框关闭,重置数据');
        this.resetData();
      }
    },
    palletVisible(newVal) {
      this.$emit('update:visible', newVal);
    },
    docNo(newVal) {
      if (newVal) {
        this.palletForm = { palletCode: '', barcode: '' };
        this.backData = [];
        this.$refs.palletForm?.reset();
        this.fetchUnpalletMaterialDetails();
      }
    }
  },
  mounted() {
    document.addEventListener('keypress', this.handleKeyPress);
  },
  beforeDestroy() {
    document.removeEventListener('keypress', this.handleKeyPress);
    this.clearAllTimers();
  },
  methods: {
    open() {
      this.show = true;
      this.orderNo = "";
      this.resetData();
      this.initLocationTypes();
      this.initwarehouseTypes();
      this.fetchUnpalletMaterialDetails();
    },
    validateLocationType(rule, value, callback) {
      if (!this.form.warehouseType) {
        callback(new Error('请先选择仓库'));
      } else if (value === null || value === undefined || value === '') {
        callback(new Error('请选择仓库区域'));
      } else {
        callback();
      }
    },
    // æ ¹æ®æ¡ç åˆ—表获取详细数据
    async fetchUnpalletMaterialDetails() {
      try {
        const response = await http.post('/api/InboundOrder/UnPalletGroupBarcode?orderNo=' + this.orderNo, {});
        if (response.status && Array.isArray(response.data)) {
          this.unpalletMaterials = response.data;
          this.unpalletBarcodes = response.data.map(item => item.barcode || '');
          this.totalStockCount = response.data.length;
        } else {
          this.unpalletMaterials = [];
        }
      } catch (err) {
        this.unpalletMaterials = this.unpalletBarcodes.map(barcode => ({
          barcode: barcode,
          materielCode: '-',
          batchNo: '-',
          stockQuantity: '-',
          unit: '-',
          supplyCode: '-',
          warehouseType: '-'
        }));
      }
    },
    async initLocationTypes() {
      try {
        const { data } = await this.http.post("api/LocationInfo/GetLocationTypes")
        this.locationTypes = data
      } catch (e) {
        this.$message.error('获取区域类型失败')
      }
    },
    async initwarehouseTypes() {
      try {
        const { data } = await this.http.post("api/Warehouse/GetwarehouseTypes")
        this.warehouseTypes = data
      } catch (e) {
        this.$message.error('获取区域类型失败')
      }
    },
    async fetchStockStatistics(orderNo) {
      // å•据号为空时不查询
      if (!orderNo) {
        this.sumError = '单据号为空,无法统计';
        return;
      }
      this.sumLoading = true;
      this.sumError = '';
      try {
        const response = await http.post('/api/InboundOrder/UnPalletQuantity?orderNo=' + orderNo, {});
        if (response.data) {
          this.totalStockSum = response.data.stockSumQuantity || 0;
          this.totalStockCount = response.data.stockCount || 0;
          this.uniqueUnit = response.data.uniqueUnit || '';
        }
      } catch (err) {
        this.sumError = '统计加载失败';
        this.totalStockSum = 0;
        this.totalStockCount = 0;
      } finally {
        this.sumLoading = false;
      }
    },
    async validateForm() {
      return new Promise((resolve) => {
        if (!this.$refs.locationForm) {
          this.error = '表单未初始化';
          this.$message.warning('请先选择仓库区域');
          resolve(false);
          return;
        }
        this.$refs.locationForm.validate((valid) => {
          if (valid) {
            this.error = '';
            resolve(true);
          } else {
            if (!this.from.warehouseType) {
              this.error = '请先选择仓库';
            }
            else if (this.form.locationType === null || this.form.locationType === undefined || this.form.locationType === '') {
              this.error = '请先选择仓库区域';
            } else {
              this.error = '请检查表单填写是否正确';
            }
            resolve(false);
          }
        });
      });
    },
    // èšç„¦åˆ°æ‰˜ç›˜è¾“入框
    focusTrayInput() {
      if (this.$refs.trayInput && this.$refs.trayInput.$el) {
        const inputEl = this.$refs.trayInput.$el.querySelector('input');
        if (inputEl) {
          inputEl.focus();
          this.currentFocus = 'tray';
          this.scanTarget = 'tray';
          inputEl.select();
        }
      }
    },
    // èšç„¦åˆ°ç‰©æ–™è¾“入框
    focusBarcodeInput() {
      if (this.$refs.barcodeInput && this.$refs.barcodeInput.$el) {
        const inputEl = this.$refs.barcodeInput.$el.querySelector('input');
        if (inputEl) {
          inputEl.focus();
          this.currentFocus = 'material';
          this.scanTarget = 'material';
          inputEl.select();
        }
      }
    },
    // é‡ç½®æ‰€æœ‰æ•°æ®
    resetData() {
      this.trayBarcode = '';
      this.barcode = '';
      this.materials = [];
      this.unpalletBarcodes = [];
      this.unpalletMaterials = [];
      this.loading = false;
      this.error = '';
      this.scanCode = '';
      this.lastKeyTime = null;
      this.isManualInput = false;
      this.isScanning = false;
      this.currentFocus = 'warehouse';
      this.scanTarget = 'tray';
      this.clearAllTimers();
      this.totalStockSum = 0;
      this.totalStockCount = 0;
      this.sumLoading = false;
      this.sumError = '';
      this.form = {
        warehouseType: null,
        locationType: null
      }
      this.warehouseTypes = [];
      this.locationTypes = [];
      // æ¸…除表单验证状态
      this.$nextTick(() => {
        if (this.$refs.locationForm) {
          this.$refs.locationForm.clearValidate();
        }
      });
    },
    // æ¸…除所有计时器
    clearAllTimers() {
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
        this.manualInputTimer = null;
      }
      if (this.scanTimer) {
        clearTimeout(this.scanTimer);
        this.scanTimer = null;
      }
    },
    handleDialogClose() {
      this.show = false;
      this.resetData();
    },
    // ç¡®è®¤æŒ‰é’®
    async handleConfirm() {
      if (!await this.validateForm()) return;
      if (this.materials.length === 0) {
        this.$message.warning('请至少添加一个物料');
        return;
      }
      if (!this.trayBarcode) {
        this.$message.warning('请输入托盘条码');
        return;
      }
      const result = {
        warehouseType: this.form.warehouseType,
        warehouseName: this.currentWarehouseName,
        locationType: this.form.locationType,
        locationDesc: this.currentLocationDesc,
        trayBarcode: this.trayBarcode,
        materials: this.materials,
        docNo: this.docNo
      };
      // è§¦å‘父组件的 back-success äº‹ä»¶
      this.$emit('back-success', result);
      this.palletVisible = false;
    },
    // å¤„理托盘输入
    handleTrayInput() {
      // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
      this.isManualInput = true;
      this.isScanning = false;
      // æ¸…除之前的计时器
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
      this.manualInputTimer = setTimeout(() => {
        this.isManualInput = false;
      }, 1000);
    },
    // å¤„理物料输入
    handleBarcodeInput() {
      // æ ‡è®°ä¸ºæ‰‹åŠ¨è¾“å…¥æ¨¡å¼
      this.isManualInput = true;
      this.isScanning = false;
      // æ¸…除之前的计时器
      if (this.manualInputTimer) {
        clearTimeout(this.manualInputTimer);
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置为扫码模式
      this.manualInputTimer = setTimeout(() => {
        this.isManualInput = false;
      }, 1000);
    },
    // å¤„理托盘条码提交
    async handleTraySubmit() {
      // å…ˆç›´æŽ¥æ£€æŸ¥locationType,避免表单验证的异步问题
      if (!this.form.warehouseType) {
        this.error = '请先选择仓库';
        return;
      }
      if (!this.form.locationType) {
        this.error = '请先选择仓库区域';
        //this.$message.warning('请先选择仓库区域');
        return;
      }
      // ç„¶åŽå†è¿›è¡Œå®Œæ•´çš„表单验证
      if (!await this.validateForm()) return;
      const currentTrayBarcode = this.trayBarcode.trim();
      if (!currentTrayBarcode) {
        this.error = '请输入或扫描托盘条码';
        return;
      }
      this.error = '';
      if (!this.trayBarcodeReg.test(currentTrayBarcode)) {
        // ElMessage.warning({
        //   message: '托盘号格式错误',
        //   type: 'warning',
        //   duration: 3000
        // })
        this.$message("托盘号格式错误");
        this.focusTrayInput();
        return;
      }
      // è®¾ç½®æ‰˜ç›˜æ¡ç åŽï¼Œè‡ªåŠ¨èšç„¦åˆ°ç‰©æ–™è¾“å…¥æ¡†
      this.focusBarcodeInput();
      // this.$message({
      //   message: `托盘条码已设置: ${currentTrayBarcode}`,
      //   type: 'success',
      //   duration: 2000
      // });
      this.$message.success(`托盘条码已设置: ${currentTrayBarcode}`);
    },
    // æ¸…除托盘
    clearTray() {
      this.trayBarcode = '';
      this.materials = [];
      this.focusTrayInput();
      this.$message({
        message: '托盘条码已清除',
        type: 'info',
        duration: 2000
      });
    },
    // æ¸…空托盘输入
    handleTrayClear() {
      this.error = '';
    },
    // æ¸…空输入
    handleClear() {
      this.error = '';
      this.scanCode = '';
      this.isManualInput = false;
      this.isScanning = false;
    },
    // å¤„理物料条码提交
    async handleBarcodeSubmit() {
      if (!await this.validateForm()) return;
      const currentBarcode = this.barcode.trim();
      if (!this.trayBarcode) {
        this.error = '请先输入托盘条码';
        this.focusTrayInput();
        return;
      }
      if (!currentBarcode) {
        this.error = '请输入或扫描物料条码';
        return;
      }
      this.focusBarcodeInput();
      this.error = '';
      this.loading = true;
      try {
        // è°ƒç”¨API查询物料信息
        const materialData = await this.fetchMaterialData(currentBarcode);
        if (!materialData || materialData.length === 0) {
          return;
        }
        this.materials = [];
        materialData.forEach(item => {
          this.materials.push({
            ...item,
            trayCode: this.trayBarcode,
            locationType: this.form.locationType,
            locationDesc: this.currentLocationDesc,
            scanTime: this.formatTime(new Date())
          });
        });
        this.orderNo = materialData[0].orderNo;
        await this.fetchStockStatistics(materialData[0].orderNo);
        await this.fetchUnpalletMaterialDetails();
        // æ¸…空物料输入框并保持聚焦
        this.barcode = '';
        this.scanCode = ''; // æ¸…空扫码缓存
        this.isScanning = false;
        setTimeout(() => {
          this.focusBarcodeInput();
        }, 100);
      } catch (err) {
        this.error = err.message || '查询条码信息失败,请重试';
        this.focusBarcodeInput();
        setTimeout(() => {
          // é€‰ä¸­è¾“入框内的错误内容(确保focus完成后执行)
          const inputEl = this.$refs.barcodeInput?.$el?.querySelector('input');
          if (inputEl) inputEl.select();
        }, 100);
      } finally {
        this.loading = false;
      }
    },
    // API请求 - æ›¿æ¢ä¸ºå®žé™…çš„API调用
    async fetchMaterialData(barcode) {
      try {
        const response = await http.post('/api/Inbound/StockTakeGroupPallet',
          {
            palletCode: this.trayBarcode,
            barcode: barcode,
            locationTypeDesc: this.currentLocationDesc,
            locationType: this.form.locationType, // æ·»åŠ ä»“åº“åŒºåŸŸä¿¡æ¯
            warehouseType: this.form.warehouseType
          }
        );
        let materialData;
        if (typeof response.data === 'string') {
          try {
            materialData = JSON.parse(response.data);
          } catch (e) {
          }
        } else {
          materialData = response.data;
        }
        if (!response.status) {
          this.error = response.message || '查询条码信息失败,请重试';
        }
        return materialData;
      } catch (error) {
        console.error('API调用失败:', error);
      }
    },
    // å¤„理扫码枪输入
    handleKeyPress(event) {
      // å¦‚果是手动输入模式,不处理扫码枪逻辑
      if (this.isManualInput) {
        return;
      }
      const key = event.key;
      const currentTime = new Date().getTime();
      // å¿½ç•¥ç›´æŽ¥æŒ‰ä¸‹çš„回车键(由handleBarcodeSubmit处理)
      if (key === 'Enter') {
        if (this.scanCode.length > 0) {
          // é˜»æ­¢é»˜è®¤å›žè½¦è¡Œä¸ºï¼Œé¿å…è¡¨å•提交
          event.preventDefault();
          // æ‰«ç å®Œæˆï¼Œè‡ªåŠ¨è§¦å‘æŸ¥è¯¢
          this.isScanning = false;
          // æ ¹æ®å½“前扫码目标设置相应的输入框值
          if (this.scanTarget === 'tray') {
            this.trayBarcode = this.scanCode;
            this.handleTraySubmit();
          } else if (this.scanTarget === 'material') {
            this.barcode = this.scanCode;
            this.handleBarcodeSubmit();
          }
        }
        this.scanCode = '';
        this.lastKeyTime = null;
        return;
      }
      // æž„建扫码内容(快速连续输入视为扫码)
      if (this.lastKeyTime && currentTime - this.lastKeyTime < 50) {
        this.scanCode += key;
        this.isScanning = true;
      } else {
        this.scanCode = key;
        this.isScanning = true;
      }
      // è®¾ç½®è®¡æ—¶å™¨ï¼Œå¦‚果一段时间内没有输入,则重置扫描状态
      if (this.scanTimer) {
        clearTimeout(this.scanTimer);
      }
      this.scanTimer = setTimeout(() => {
        this.isScanning = false;
      }, 100);
      this.lastKeyTime = currentTime;
    },
    // æ ¼å¼åŒ–æ—¶é—´
    formatTime(date) {
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      const hours = String(date.getHours()).padStart(2, '0');
      const minutes = String(date.getMinutes()).padStart(2, '0');
      const seconds = String(date.getSeconds()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    }
  }
}
</script>
<style scoped>
.barcode-scanner-container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px;
  display: flex;
  flex-direction: column;
  height: 100%;
  gap: 8px;
}
/* ç´§å‡‘布局样式 */
.compact {
  margin-bottom: 0;
}
.compact-form {
  margin-bottom: 0;
}
.compact-item {
  margin-bottom: 0;
}
.compact-card {
  margin-bottom: 0;
}
.compact-card>>>.el-card__body {
  padding: 12px;
}
.compact-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 !important;
}
.compact-header>>>.el-card__header {
  padding: 8px 12px;
}
.compact-input {
  margin: 8px 0;
}
.compact-tips {
  margin-top: 8px;
  font-size: 11px;
}
/* ä»“库区域选择 - ç´§å‡‘ */
.location-section.compact {
  margin-bottom: 8px;
}
.location-section.compact>>>.el-form-item {
  margin-bottom: 0;
}
/* æ‰˜ç›˜ä¿¡æ¯ - ç´§å‡‘ */
.tray-info.compact {
  padding: 6px 10px;
  margin-bottom: 8px;
  font-size: 13px;
}
/* æ‰«ç åŒºåŸŸ - ç´§å‡‘ */
.input-section.compact {
  margin-bottom: 8px;
  flex-shrink: 0;
}
/* ç‰©æ–™åˆ—表 - å›ºå®šé«˜åº¦å¸¦æ»šåЍ */
.material-list.compact {
  flex: 1;
  min-height: 0;
  /* é‡è¦ï¼šå…è®¸flex子项收缩 */
  display: flex;
  flex-direction: column;
}
.material-list.compact>>>.el-card {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.material-list.compact>>>.el-card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 0;
  min-height: 0;
}
.table-container {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.material-list.compact>>>.el-table {
  flex: 1;
}
.material-list.compact>>>.el-table__body-wrapper {
  overflow-y: auto;
}
/* ç´§å‡‘的空状态 */
.empty-state.compact {
  padding: 20px 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.empty-state.compact i {
  font-size: 36px;
  margin-bottom: 8px;
}
.empty-state.compact p {
  font-size: 13px;
}
/* å…¶ä»–原有样式调整 */
.page-title {
  text-align: center;
  margin-bottom: 15px;
}
.scan-status {
  font-size: 12px;
  color: #67C23A;
}
.scan-indicator {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: #67C23A;
  margin-right: 5px;
  animation: pulse 1.5s infinite;
}
@keyframes pulse {
  0% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
  100% {
    opacity: 1;
  }
}
.input-wrapper {
  position: relative;
}
.input-tips {
  margin-top: 6px;
  color: #909399;
}
.warning-text {
  color: #E6A23C;
  font-weight: bold;
}
.loading.compact {
  text-align: center;
  margin: 10px 0;
  padding: 5px;
}
.loading.compact p {
  margin-top: 5px;
  color: #409EFF;
  font-size: 12px;
}
.error-message.compact {
  margin: 5px 0;
}
.error-message.compact>>>.el-alert {
  padding: 6px 12px;
}
.list-actions {
  display: flex;
  align-items: center;
  gap: 4px;
}
.list-actions>>>.el-tag {
  height: 24px;
  line-height: 22px;
  padding: 0 6px;
}
.clear-all-btn {
  margin-left: 8px;
}
.material-code {
  font-family: 'Courier New', monospace;
  font-weight: bold;
  color: #409EFF;
}
.location-info {
  color: #606266;
  font-weight: normal;
}
.debug-info {
  background: #f5f7fa;
  padding: 8px;
  border-radius: 4px;
  margin-top: 8px;
  font-size: 11px;
  color: #909399;
}
.small-button {
  padding: 6px 8px;
  font-size: 11px;
}
/* è¾“入框组样式调整 */
.custom-input-group {
  display: flex;
  align-items: center;
  width: 100%;
  margin: 8px 0;
  border: 1px solid #DCDFE6;
  border-radius: 4px;
  overflow: hidden;
  background: #fff;
}
.input-label {
  padding: 0 12px;
  background: #F5F7FA;
  border-right: 1px solid #DCDFE6;
  color: #606266;
  font-size: 13px;
  white-space: nowrap;
  height: 36px;
  line-height: 36px;
  flex-shrink: 0;
  min-width: 70px;
  text-align: center;
}
.input-container {
  display: flex;
  flex: 1;
  align-items: center;
}
.custom-input {
  flex: 1;
}
.custom-input>>>.el-input__inner {
  border: none;
  border-radius: 0;
  height: 36px;
  line-height: 36px;
  font-size: 13px;
}
/* å“åº”式调整 */
@media (max-width: 768px) {
  .barcode-scanner-container {
    padding: 5px;
  }
  .custom-input-group {
    flex-direction: column;
    border: none;
  }
  .input-label {
    width: 100%;
    border-right: none;
    border-bottom: 1px solid #DCDFE6;
    margin-bottom: 5px;
  }
  .input-container {
    width: 100%;
    border: 1px solid #DCDFE6;
    border-radius: 4px;
  }
  .unpallet-section.compact {
    margin-bottom: 8px;
    flex-shrink: 0;
  }
  .unpallet-card {
    flex-shrink: 0;
  }
  .unpallet-barcode-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 8px 0;
    max-height: 180px;
    overflow-y: auto;
  }
  .unpallet-barcode-list>>>.el-tag {
    cursor: pointer;
    max-width: calc(33.333% - 4px);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  @media (max-width: 768px) {
    .unpallet-barcode-list>>>.el-tag {
      max-width: calc(50% - 4px);
    }
  }
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/TakeStockSelect.vue
@@ -1,81 +1,30 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    title="杂收杂发平账"
    width="1000px"
    title="杂发平账"
    width="600px"
    :close-on-click-modal="false"
    :destroy-on-close="true"
    @closed="handleDialogClosed"
  >
    <div class="reconciliation-container">
      <!-- å•据基本信息 -->
      <div class="order-info">
        <div class="info-row">
          <span class="label">单据ID:</span>
          <span class="value">{{ currentRow.id || "-" }}</span>
        </div>
        <div class="info-row" v-if="selectedItem">
          <span class="label">已选订单:</span>
          <span class="value selected-order">{{ selectedItem.id }}</span>
        </div>
      <!-- ç§»é™¤å•据ID展示,仅保留条码输入框 -->
      <!-- æ¡ç è¾“入框 -->
      <div class="barcode-input-container">
        <el-input
          v-model="barcode"
          placeholder="请输入/扫描条码后平账"
          clearable
          @keyup.enter="handleEnterConfirm"
          class="barcode-input"
          :disabled="loading"
        />
      </div>
      <!-- æ•°æ®åˆ—表展示 -->
      <el-scrollbar height="400px" class="custom-scrollbar">
        <transition-group name="data-item-transition">
          <div
            class="data-item"
            v-for="(item, index) in displayData"
            :key="`${item.orderId}-${index}`"
          >
            <div class="radio-container">
              <el-radio
                v-model="selectedItem"
                :label="item"
                :value="item"
                @change="handleRadioChange(item)"
              ></el-radio>
      <!-- ç©ºæç¤ºï¼ˆä»…条码为空时显示) -->
      <div class="empty-tip" v-if="!barcode && !loading">
        <span>请输入条码后进行平账操作</span>
            </div>
            <div class="data-detail">
              <div class="detail-row">
                <span class="label">订单ID:</span>
                <span class="value">{{ item.id || "-" }}</span>
              </div>
              <div class="detail-row">
                <span class="label">物料编码:</span>
                <span class="value">{{ item.materielCode || "-" }}</span>
              </div>
              <div class="detail-row">
                <span class="label">物料名称:</span>
                <span class="value">{{ item.materielName || "-" }}</span>
              </div>
              <div class="detail-row">
                <span class="label">批次号:</span>
                <span class="value">{{ item.batchNo || "-" }}</span>
              </div>
              <div class="detail-row">
                <span class="label">订单数量:</span>
                <span class="value">{{ item.orderQuantity || 0 }}</span>
              </div>
              <div class="detail-row">
                <span class="label">单位:</span>
                <span class="value">{{ item.unit || "-" }}</span>
              </div>
              <div class="detail-row">
                <span class="label">供应商编码:</span>
                <span class="value">{{ item.supplyCode || "-" }}</span>
              </div>
              <div class="detail-row">
                <span class="label">仓库编码:</span>
                <span class="value">{{ item.warehouseCode || "-" }}</span>
              </div>
            </div>
          </div>
        </transition-group>
        <div class="empty-tip" v-if="displayData.length === 0">
          <span>暂无相关数据</span>
        </div>
      </el-scrollbar>
    </div>
    <template #footer>
@@ -83,7 +32,7 @@
      <el-button
        type="primary"
        @click="handleConfirm"
        :disabled="!selectedItem"
        :disabled="!barcode || loading"
        :loading="loading"
      >
        {{ loading ? "平账中..." : "确认平账" }}
@@ -91,7 +40,7 @@
    </template>
  </el-dialog>
  
  <!-- æ‰“印组件(必须在模板中声明,才能通过ref获取) -->
  <!-- æ‰“印组件(保留原有逻辑) -->
  <printView ref="printViewRef" @parentcall="parentcall"></printView>
</template>
@@ -103,52 +52,50 @@
// å¼¹çª—显示状态
const dialogVisible = ref(false);
// å½“前选中的行数据
const currentRow = ref({});
// è¦å±•示的数据列表
const displayData = ref([]);
// é€‰ä¸­çš„æ•°æ®é¡¹
const selectedItem = ref(null);
// æ¡ç è¾“入框值
const barcode = ref("");
// åŠ è½½çŠ¶æ€
const loading = ref(false);
// å£°æ˜Žæ‰“印组件的ref引用(关键:替代this.$refs.printView)
// æ‰“印组件ref引用
const printViewRef = ref(null);
// æ‰“开弹窗方法(供父组件调用)
const open = (row, data) => {
  currentRow.value = row;
  displayData.value = data;
  selectedItem.value = null; // é‡ç½®é€‰æ‹©
// æ‰“开弹窗方法(无参数,供父组件直接调用)
const open = () => {
  barcode.value = ""; // æ¯æ¬¡æ‰“开弹窗重置条码
  dialogVisible.value = true;
};
// å¤„理单选按钮变化
const handleRadioChange = (item) => {
  selectedItem.value = item;
};
// å¼¹çª—关闭时的处理
const handleDialogClosed = () => {
  selectedItem.value = null;
  barcode.value = "";
  loading.value = false;
};
// çˆ¶ç»„件调用的回调(如果printView需要)
// çˆ¶ç»„件调用的回调(保留原有逻辑)
const parentcall = (params) => {
  console.log("printView回调参数:", params);
};
// ç¡®è®¤å¹³è´¦æ“ä½œ
// å›žè½¦è§¦å‘平账(扫码枪自动回车时调用)
const handleEnterConfirm = async () => {
  if (!barcode.value) {
    ElMessage.warning("请输入条码后再操作");
    return;
  }
  await handleConfirm();
};
// ç¡®è®¤å¹³è´¦æ“ä½œï¼ˆæ ¸å¿ƒé€»è¾‘)
const handleConfirm = async () => {
  if (!selectedItem.value) {
    ElMessage.warning("请选择一条数据进行平账处理");
  if (!barcode.value) {
    ElMessage.warning("请输入条码进行平账处理");
    return;
  }
  try {
    // ç¡®è®¤æç¤º
    await ElMessageBox.confirm(
      `确定要对订单 ${selectedItem.value.id} è¿›è¡Œå¹³è´¦å¤„理吗?`,
      `确定要对条码 ${barcode.value} è¿›è¡Œå¹³è´¦å¤„理吗?`,
      "平账确认",
      {
        confirmButtonText: "确定",
@@ -159,10 +106,9 @@
    loading.value = true;
    // è°ƒç”¨å¹³è´¦æŽ¥å£
    // è°ƒç”¨å¹³è´¦æŽ¥å£ï¼ˆä»…传递barcode参数)
    const params = {
      id: currentRow.value.id, // è¡ŒID
      orderId: selectedItem.value.id, // é€‰æ‹©çš„单据orderId
      barcode: barcode.value,
    };
    const response = await axios.get(
@@ -172,21 +118,20 @@
      }
    );
    console.log("接口完整返回值:", response); // è°ƒè¯•用:打印完整返回
    console.log("接口完整返回值:", response); // è°ƒè¯•用
    
    // ç¬¬ä¸€æ­¥ï¼šæ ¡éªŒæœ€å¤–层状态
    // æ ¡éªŒæŽ¥å£è¿”回状态
    if (response.data?.status) {
      ElMessage.success("平账操作成功");
      
      // ç¬¬äºŒæ­¥ï¼šè§£æžæ­£ç¡®çš„scannedDetail层级(三层data)
      const thirdLayerData = response.data.data?.data; // å…³é”®ä¿®å¤ï¼šå–第三层data
      // è§£æžè¿”回数据(保留原有打印逻辑)
      const thirdLayerData = response.data.data?.data;
      const scannedDetail = thirdLayerData?.scannedDetail;
      
      console.log("解析后的scannedDetail:", scannedDetail); // è°ƒè¯•用:打印目标数据
      console.log("解析后的scannedDetail:", scannedDetail);
      
      // ç¬¬ä¸‰æ­¥ï¼šåˆ¤æ–­æ‰“印条件
      // åˆ¤æ–­æ‰“印条件并触发打印
      if (scannedDetail?.isUnpacked && scannedDetail?.materialCodes?.length > 0) {
        // ç¡®ä¿æ‰“印组件实例存在
        if (printViewRef.value) {
          console.log("触发打印方法,参数:", scannedDetail.materialCodes);
          printViewRef.value.open(scannedDetail.materialCodes);
@@ -197,21 +142,20 @@
        ElMessage.info("无需打印:未满足拆包条件或无物料编码数据");
      }
      
      dialogVisible.value = false;
      dialogVisible.value = false; // å¹³è´¦æˆåŠŸåŽå…³é—­å¼¹çª—
    } else {
      ElMessage.error(response.data?.message || "平账操作失败");
    }
  } catch (error) {
    // å®Œå–„错误处理:区分用户取消和真实错误
    // åŒºåˆ†ç”¨æˆ·å–消和真实错误
    if (error === "cancel" || error === "close") {
      ElMessage.info("已取消平账操作");
      return;
    }
    // æ‰“印错误日志,方便排查
    console.error("平账接口调用失败:", error);
    ElMessage.error(`平账操作失败:${error.message || "网络异常"}`);
  } finally {
    loading.value = false;
    loading.value = false; // æ— è®ºæˆåŠŸå¤±è´¥ï¼Œç»“æŸåŠ è½½çŠ¶æ€
  }
};
@@ -222,103 +166,24 @@
</script>
<style scoped>
/* æ ·å¼éƒ¨åˆ†ä¸å˜ï¼Œçœç•¥é‡å¤ä»£ç  */
.reconciliation-container {
  padding: 10px 0;
}
.order-info {
  margin-bottom: 20px;
  padding: 10px;
  background: #f5f7fa;
  border-radius: 4px;
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
/* æ¡ç è¾“入框样式 */
.barcode-input-container {
  margin: 20px 0;
}
.info-row {
  display: flex;
  align-items: center;
}
.label {
  font-weight: 600;
  color: #606266;
  margin-right: 5px;
  min-width: 80px;
  text-align: right;
}
.value {
  color: #303133;
}
.selected-order {
  color: #409eff;
  font-weight: 600;
}
.custom-scrollbar {
  border: 1px solid #e4e7ed;
  border-radius: 4px;
}
.data-item {
  display: flex;
  align-items: flex-start;
  padding: 15px;
  margin: 10px;
  background: #fff;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  transition: all 0.3s;
}
.data-item:hover {
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.data-item.selected {
  border-color: #409eff;
  background-color: #f0f7ff;
}
.radio-container {
  margin-right: 12px;
  margin-top: 4px;
}
.data-detail {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  flex: 1;
}
.detail-row {
  display: flex;
  align-items: center;
.barcode-input {
  width: 100%;
  font-size: 16px;
  height: 48px;
}
.empty-tip {
  text-align: center;
  padding: 50px 0;
  color: #909399;
}
.data-item-transition-enter-from,
.data-item-transition-leave-to {
  opacity: 0;
  transform: translateY(10px);
}
.data-item-transition-enter-active,
.data-item-transition-leave-active {
  transition: all 0.3s ease;
}
:deep(.el-radio__label) {
  display: none;
}
</style>
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/extend/allocateOrderDetail.vue
@@ -612,6 +612,7 @@
                        outboundQuantity: formData.outboundDecimal,
                        operator: "",
                        orderNo: this.row.orderNo,
                        stockDetailIds:[]
                      };
                      this.http
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/takeStockOrder.js
@@ -1,12 +1,14 @@
// è‡ªå®šä¹‰æ‰©å±•业务代码
import gridBody from "./extend/OrderStockTake.vue";
import gridHeader from "./extend/TakeStockSelect.vue";
import gridFooter from "./extend/StockTakeGroupPallet.vue";
let extension = {
  components: {
    // æŸ¥è¯¢ç•Œé¢æ‰©å±•组件:将自定义弹窗注册为 gridBody ç»„ä»¶
    gridHeader: '',
    gridHeader: gridHeader,
    gridBody: gridBody, // å¯¹åº”你的盘点弹窗组件
    gridFooter: '',
    gridFooter: gridFooter,
    // æ–°å»º/编辑弹出框扩展组件(此处不用,留空)
    modelHeader: '',
    modelBody: '',
@@ -16,21 +18,27 @@
  buttons: { view: [], box: [], detail: [] }, // æ‰©å±•按钮(如需额外添加可在此配置)
  methods: {
    onInit() {
      // æ‰¾åˆ° value ä¸º "OrderStockTake" çš„æŒ‰é’®ï¼ˆéœ€åœ¨æ¡†æž¶ä¸­æå‰é…ç½®è¯¥æŒ‰é’®ï¼‰
      let OrderStockTakeBtn = this.buttons.find(x => x.value === 'OrderStockTake');
      if (OrderStockTakeBtn) {
        // é‡å†™æŒ‰é’®ç‚¹å‡»äº‹ä»¶
        OrderStockTakeBtn.onClick = function () {
          // 1. èŽ·å–è¡¨æ ¼é€‰ä¸­è¡Œæ•°æ®
          let rows = this.$refs.table.getSelected();
          if (rows.length === 0) return this.$error("请选择一条盘点单据数据!");
          if (rows.length > 1) return this.$error("只能选择一条盘点单据数据!");
          this.$refs.gridBody.open();
        };
      }
      let OutOrderStockTakeBtn = this.buttons.find(x => x.value === 'OutOrderStockTake');
      if (OutOrderStockTakeBtn) {
        // é‡å†™æŒ‰é’®ç‚¹å‡»äº‹ä»¶
        OutOrderStockTakeBtn.onClick = function () {
          
          const selectedReceiptNo = rows[0].orderNo;
          if (!selectedReceiptNo) return this.$error("选中的单据缺少有效的单据号!");
          this.$refs.gridHeader.open();
        };
      }
      let StockTakeGroupPalletBtn = this.buttons.find(x => x.value === 'StockTakeGroupPallet');
      if (StockTakeGroupPalletBtn) {
        // é‡å†™æŒ‰é’®ç‚¹å‡»äº‹ä»¶
        StockTakeGroupPalletBtn.onClick = function () {
          // 3. è°ƒç”¨è‡ªå®šä¹‰å¼¹çª—çš„ open æ–¹æ³•,并传递单据号(核心:给弹窗传参)
          this.$refs.gridBody.open(selectedReceiptNo);
          this.$refs.gridFooter.open();
        };
      }
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/inbound/takeStockOrderDetail.js
@@ -18,19 +18,6 @@
  buttons: { view: [], box: [], detail: [] }, // æ‰©å±•按钮
  methods: {
    onInit() {
      // åŽŸæœ‰ç›˜ç‚¹æŒ‰é’®é€»è¾‘ä¿ç•™
      let OrderStockTakeBtn = this.buttons.find(x => x.value === 'OrderStockTake');
      if (OrderStockTakeBtn) {
        OrderStockTakeBtn.onClick = function () {
          let rows = this.$refs.table.getSelected();
          if (rows.length === 0) return this.$error("请选择一条盘点单据数据!");
          if (rows.length > 1) return this.$error("只能选择一条盘点单据数据!");
          const selectedReceiptNo = rows[0].orderNo;
          if (!selectedReceiptNo) return this.$error("选中的单据缺少有效的单据号!");
          this.$refs.gridBody.open(selectedReceiptNo);
        };
      }
      // ç›‘听原有弹窗事件(保留)
      this.$nextTick(() => {
@@ -48,128 +35,9 @@
      });
      // ========== æ–°å¢žæ“ä½œåˆ—:人工平账 + æ‚收杂发平账 ==========
      this.columns.push({
        field: 'operation',
        title: '操作',
        width: 200,
        fixed: 'right',
        align: 'center',
        formatter: (row) => {
          return `
            <span style="cursor: pointer;color: #2d8cf0;margin-right: 10px;" class="manual-reconciliation">
              <i class="el-icon-check"></i>人工平账
            </span>
            <span style="cursor: pointer;color: #1989fa;" class="misc-reconciliation">
              <i class="el-icon-edit"></i>杂收杂发平账
            </span>
          `;
        },
        click: (row, column, event) => {
          const target = event.target;
          // åŒºåˆ†ç‚¹å‡»çš„æ˜¯äººå·¥å¹³è´¦è¿˜æ˜¯æ‚收杂发平账
          if (target.closest('.manual-reconciliation')) {
            this.handleManualReconciliation(row); // äººå·¥å¹³è´¦é€»è¾‘
          } else if (target.closest('.misc-reconciliation')) {
            this.handleMiscReconciliation(row); // æ‚收杂发平账逻辑
          }
        }
      });
    },
    // ========== äººå·¥å¹³è´¦æ ¸å¿ƒé€»è¾‘ ==========
    handleManualReconciliation(row) {
      // å¼¹å‡ºç¡®è®¤æ¡†
      ElMessageBox.confirm(
        '确认要执行人工平账操作吗?',
        '操作确认',
        {
          confirmButtonText: '确认',
          cancelButtonText: '取消',
          type: 'warning'
        }
      ).then(async () => {
        // æ·»åŠ é®ç½©å±‚é˜²æ­¢é‡å¤ç‚¹å‡»
        const loading = ElLoading.service({
          lock: true,
          text: '处理中,请稍候...',
          background: 'rgba(0, 0, 0, 0.7)'
        });
        try {
          // è°ƒç”¨äººå·¥å¹³è´¦æŽ¥å£
          const response = await this.http.get(`/api/TakeStockOrder/ManualReconciliation?id=${row.id}`);
          if (response.status) {
            ElMessage.success('人工平账操作成功!');
            this.$refs.table.reload(); // åˆ·æ–°è¡¨æ ¼
          } else {
            ElMessage.error(`操作失败:${response.message || '未知错误'}`);
          }
        } catch (error) {
        } finally {
          // å…³é—­é®ç½©å±‚
          loading.close();
        }
      }).catch(() => {
        ElMessage.info('已取消人工平账操作');
      });
    },
    // ========== æ‚收杂发平账核心逻辑(修改后) ==========
    handleMiscReconciliation(row) {
      // é€‰ä¸­å½“前行
      const table = this.$refs.table.$refs.table;
      if (table) {
        table.clearSelection();
        table.toggleRowSelection(row, true);
      }
      // è°ƒç”¨æŽ¥å£èŽ·å–æ‚æ”¶æ‚å‘å¹³è´¦æ•°æ®
      const fetchMiscData = async () => {
        const loading = ElLoading.service({
          lock: true,
          text: '加载数据中...',
          background: 'rgba(0, 0, 0, 0.7)'
        });
        try {
          // è°ƒç”¨æŽ¥å£ï¼Œä¼ é€’row中的remark和id参数
          const response = await this.http.get(`/api/TakeStockOrder/SelectOrder?remark=${row.remark || ''}&id=${row.id}`);
          loading.close();
          if (response.status) {
            if (!Array.isArray(response.data) || response.data.length === 0) {
              ElMessage.warning("未查询到相关数据");
              // æ‰“开空数据的弹窗
              this.$refs.gridHeader.open(row, []);
              return;
            }
            // æå–需要展示的字段
            const displayData = response.data.map(item => ({
              id: item.id || '',
              materielCode: item.materielCode || '',
              materielName: item.materielName || '',
              batchNo: item.batchNo || '',
              orderQuantity: item.orderQuantity || 0,
              unit: item.unit || '',
              supplyCode: item.supplyCode || '',
              warehouseCode: item.warehouseCode || ''
            }));
            // æ‰“开弹窗并传递处理后的数据
            this.$refs.gridHeader.open(row, displayData);
          } else {
            ElMessage.error(`查询失败:${response.message || '未知错误'}`);
          }
        } catch (error) {
          loading.close();
          ElMessage.error(`网络异常:${error.message || '接口调用失败'}`);
        }
      };
      // æ‰§è¡Œæ•°æ®æŸ¥è¯¢å¹¶æ‰“开弹窗
      fetchMiscData();
    },
    onInited() {
      // æ¡†æž¶åˆå§‹åŒ–完成后执行
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/DirectOutbound.vue
@@ -69,6 +69,7 @@
                    outboundQuantity: this.keys.length > 1 ? 1 : this.outboundQuantity,
                    operator: "",
                    orderNo: this.orderNo,
                    stockDetailIds:[]
                };
                console.log(requestParams);
                this.http.post("api/Outbound/ProcessPickingOutbound", requestParams, '数据处理中...')
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/StockSelect.vue
@@ -3,7 +3,7 @@
    <vol-box
      v-model="showDetialBox"
      :lazy="true"
      width="60%"
      width="80%"
      :padding="15"
      title="指定库存"
    >
@@ -11,6 +11,8 @@
        <el-alert :closable="false" style="width: 100%">
          <el-row>
            <el-col :span="16">
              <span class="less-style">单据编号:{{ mainOrderNo }}</span>
              <el-divider direction="vertical"></el-divider>
              <span class="less-style">物料名称: {{ row.materielName }} </span>
              <el-divider direction="vertical"></el-divider>
              <span class="less-style">物料编号: {{ row.materielCode }} </span>
@@ -92,12 +94,10 @@
        </el-table>
      </div>
      <template #footer>
       <!--  <el-button type="primary" size="small" @click="outbound"
       <el-button type="primary" size="small" @click="outbound"
          >直接出库</el-button
        >
        <el-button type="primary" size="small" @click="lockStock"
          >锁定库存</el-button
        > -->
        <el-button type="danger" size="small" @click="showDetialBox = false"
          >关闭</el-button
        >
@@ -114,6 +114,7 @@
    return {
      row: null,
      kcname: "",
      mainOrderNo: '',
      pkcx: false,
      showDetialBox: false,
      tableData: [],
@@ -137,6 +138,12 @@
          width: 150,
        },
        {
          prop: "barcode",
          title: "条码",
          type: "string",
          width: 150,
        },
        {
          prop: "locationCode",
          title: "货位编号",
          type: "string",
@@ -147,6 +154,25 @@
          title: "可用数量",
          type: "string",
        },
        {
          prop: "supplyCode",
          title: "供应商",
          type: "string",
        },
        {
          prop: "batchNo",
          title: "批次号",
          type: "string",
        },
        {
          prop: "stockId",
          title: "库存主键",
          type: "string",
        },{
          prop: "orderDetailId",
          title: "单据明细主键",
          type: "string",
        },
      ],
      selection: [],
      selectionSum: 0,
@@ -155,13 +181,14 @@
    };
  },
  methods: {
    open(row) {
      console.log(row);
    open(row, orderNo) {
      this.row = row;
    this.mainOrderNo = orderNo; // å­˜å‚¨ä¸»å•据编号
      this.showDetialBox = true;
      this.originalQuantity = this.row.lockQuantity;
      this.selectionSum = this.row.lockQuantity;
      this.getData();
      if (this.selectionSum == this.row.orderQuantity) {
        this.selectionClass = "equle-style";
      } else if (this.selectionSum < this.row.orderQuantity) {
@@ -220,23 +247,7 @@
          this.tableData = x;
        });
    },
    revokeAssign() {
      console.log(this.row);
      this.http
        .post(
          "api/OutboundOrderDetail/RevokeLockOutboundStock?id=" + this.row.id,
          null,
          "数据处理中"
        )
        .then((x) => {
          if (!x.status) return this.$message.error(x.message);
          this.$message.success("操作成功");
          this.showDetialBox = false;
          this.$emit("parentCall", ($vue) => {
            $vue.getData();
          });
        });
    },
    handleSelectionChange(val) {
      this.selection = val;
      this.selectionSum =
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/extend/outOrderDetail.vue
@@ -1,6 +1,12 @@
<template>
  <div>
    <vol-box v-model="showDetialBox" :lazy="true" width="75%" :padding="15" title="单据明细信息">
    <vol-box
      v-model="showDetialBox"
      :lazy="true"
      width="75%"
      :padding="15"
      title="单据明细信息"
    >
      <div class="box-head">
        <el-alert :closable="false" style="width: 100%">
          <el-row>
@@ -8,33 +14,89 @@
              <span>已选中 {{ selection.length }} é¡¹</span>
            </el-col>
            <el-col :span="8">
              <el-link type="primary" size="small" v-if="isBatch === 0" style="float: right; height: 20px"
                @click="handleOpenPicking">拣选</el-link>
              <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px"
                v-if="isBatch === 1" @click="handleOpenBatchPicking">分批拣选</el-link>
              <el-link type="primary" size="small" v-if="isBatch === 0"
                style="float: right; height: 20px; margin-right: 10px" @click="outbound">直接出库</el-link>
              <el-link type="primary" size="small" v-if="isBatch === 1"
                style="float: right; height: 20px; margin-right: 10px" @click="outboundbatch">分批出库</el-link>
              <el-link type="primary" size="small" style="float: right; height: 20px; margin-right: 10px"
                @click="getData">刷新</el-link>
              <el-link
                type="primary"
                size="small"
                v-if="isBatch === 0"
                style="float: right; height: 20px"
                @click="handleOpenPicking"
                >拣选</el-link
              >
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                v-if="isBatch === 1"
                @click="handleOpenBatchPicking"
                >分批拣选</el-link
              >
              <el-link
                type="primary"
                size="small"
                v-if="isBatch === 0"
                style="float: right; height: 20px; margin-right: 10px"
                @click="outbound"
                >直接出库</el-link
              >
              <el-link
                type="primary"
                size="small"
                v-if="isBatch === 1"
                style="float: right; height: 20px; margin-right: 10px"
                @click="outboundbatch"
                >分批出库</el-link
              >
              <el-link
                type="primary"
                size="small"
                style="float: right; height: 20px; margin-right: 10px"
                @click="getData"
                >刷新</el-link
              >
            </el-col>
          </el-row>
        </el-alert>
      </div>
      <div class="box-table" style="margin-top: 1%">
        <el-table ref="singleTable" :data="tableData" style="width: 100%; height: 100%" highlight-current-row
          @current-change="handleCurrentChange" height="500px" @row-click="handleRowClick"
          @selection-change="handleSelectionChange">
        <el-table
          ref="singleTable"
          :data="tableData"
          style="width: 100%; height: 100%"
          highlight-current-row
          @current-change="handleCurrentChange"
          height="500px"
          @row-click="handleRowClick"
          @selection-change="handleSelectionChange"
        >
          <el-table-column type="selection" width="55"> </el-table-column>
          <el-table-column label="序号" type="index" fixed="left" width="55" align="center"></el-table-column>
          <el-table-column v-for="(item, index) in tableColumns.filter((x) => !x.hidden)" :key="index" :prop="item.prop"
            :label="item.title" :width="item.width" align="center">
          <el-table-column
            label="序号"
            type="index"
            fixed="left"
            width="55"
            align="center"
          ></el-table-column>
          <el-table-column
            v-for="(item, index) in tableColumns.filter((x) => !x.hidden)"
            :key="index"
            :prop="item.prop"
            :label="item.title"
            :width="item.width"
            align="center"
          >
            <template #default="scoped">
              <div v-if="item.type == 'icon'">
                <el-tooltip class="item" effect="dark" :content="item.title" placement="bottom">
                  <el-link type="primary" :disabled="getButtonEnable(item.prop, scoped.row)"
                    @click="tableButtonClick(scoped.row, item)">
                <el-tooltip
                  class="item"
                  effect="dark"
                  :content="item.title"
                  placement="bottom"
                >
                  <el-link
                    type="primary"
                    :disabled="getButtonEnable(item.prop, scoped.row)"
                    @click="tableButtonClick(scoped.row, item)"
                  >
                    <i :class="item.icon" style="font-size: 22px"></i>
                  </el-link>
                </el-tooltip>
@@ -53,9 +115,15 @@
      </div>
    </vol-box>
    <stock-select ref="child" @parentCall="parentCall"></stock-select>
    <selected-stock ref="selectedStock" @parentCall="parentCall"></selected-stock>
    <selected-stock
      ref="selectedStock"
      @parentCall="parentCall"
    ></selected-stock>
    <NoStockOut ref="NoStockOut" @parentCall="parentCall"></NoStockOut>
    <DirectOutbound ref="DirectOutbound" @parentCall="parentCall"></DirectOutbound>
    <DirectOutbound
      ref="DirectOutbound"
      @parentCall="parentCall"
    ></DirectOutbound>
  </div>
</template>
@@ -79,7 +147,14 @@
} from "element-plus";
export default {
  components: { VolBox, VolForm, StockSelect, SelectedStock, NoStockOut, DirectOutbound },
  components: {
    VolBox,
    VolForm,
    StockSelect,
    SelectedStock,
    NoStockOut,
    DirectOutbound,
  },
  data() {
    return {
      row: null,
@@ -291,13 +366,15 @@
    },
    tableButtonClick(row, column) {
      if (column.prop == "assignStock") {
        this.$refs.child.open(row);
    this.$refs.child.open(row,this.row.orderNo);
      } else if (column.prop == "NoStockOut") {
        this.$refs.NoStockOut.open(row);
      } else {
        this.$refs.selectedStock.open(row);
      }
    },
    lockstocks() {
      if (this.selection.length === 0) {
        return this.$message.error("请选择单据明细");
@@ -337,7 +414,7 @@
        outboundQuantity: 1,
        operator: "",
        orderNo: this.row.orderNo,
        isBatch: this.isBatch
        isBatch: this.isBatch,
      };
      console.log(requestParams);
      this.$refs.DirectOutbound.open(requestParams);
@@ -350,14 +427,14 @@
      const keys = this.selection.map((item) => item.id);
      const requestParams = {
        detailIds: keys,
        outboundQuantity: this.selection[0].orderQuantity - this.selection[0].lockQuantity,
        outboundQuantity:
          this.selection[0].orderQuantity - this.selection[0].lockQuantity,
        operator: "",
        orderNo: this.row.orderNo,
        isBatch: this.isBatch
        isBatch: this.isBatch,
      };
      console.log(requestParams);
      this.$refs.DirectOutbound.open(requestParams);
    },
    setCurrent(row) {
      this.$refs.singleTable.setCurrentRow(row);
ÏîÄ¿´úÂë/WIDESEA_WMSClient/src/extension/outbound/outboundOrder.js
@@ -505,6 +505,7 @@
                      outboundQuantity: 1,
                      operator: this.currentOperator || "admin", 
                      orderNo: param,
                      stockDetailIds:[]
                    };
                    try {
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_DTO/CalcOut/PickingOutboundRequestDTO.cs
@@ -39,5 +39,7 @@
        /// å‡ºåº“目标位置
        /// </summary>
        public string OutboundTargetLocation { get; set; }
        public List<int> StockDetailIds { get; set; }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IInboundService/IInboundService.cs
@@ -29,6 +29,12 @@
        /// <returns></returns>
        Task<WebResponseContent> AllcatedGroupPallet(GroupPalletDto palletDto);
        /// <summary>
        /// ç›˜ç‚¹ç»„盘
        /// </summary>
        /// <param name="palletDto"></param>
        /// <returns></returns>
        Task<WebResponseContent> StockTakeGroupPallet(GroupPalletDto palletDto);
        /// <summary>
        /// å…¥åº“分批回传
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IInboundService/ITakeStockOrderService.cs
@@ -25,7 +25,9 @@
        WebResponseContent SelectOrder(string remark, int id);
        WebResponseContent DocumentReconciliation(int orderId, int id);
        WebResponseContent DocumentReconciliation(string barcode);
        WebResponseContent StockTakeGroupPallet(string barcode, string boxNo);
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_IOutboundService/IOutboundService.cs
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/InboundService.cs
@@ -52,7 +52,8 @@
        private readonly HttpClientHelper _httpClientHelper;
        private readonly IRepository<Dt_MesReturnRecord> _mesReturnRecord;
        private readonly ILocationInfoService _locationInfoService;
        public InboundService(IUnitOfWorkManage unitOfWorkManage, IInboundOrderDetailService inboundOrderDetailService, IInboundOrderService inbounOrderService, IRepository<Dt_InboundOrder> inboundOrderRepository, IRepository<Dt_WarehouseArea> warehouseAreaRepository, IRepository<Dt_LocationType> locationTypeRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IStockService stockService, IRepository<Dt_Task> taskRepository,IRepository<Dt_AllocateMaterialInfo> allocateMaterialInfo, HttpClientHelper httpClientHelper, IRepository<Dt_MesReturnRecord> mesReturnRecord,ILocationInfoService locationInfoService)
        private readonly IRepository<Dt_TakeStockOrder> _takeStockOrder;
        public InboundService(IUnitOfWorkManage unitOfWorkManage, IInboundOrderDetailService inboundOrderDetailService, IInboundOrderService inbounOrderService, IRepository<Dt_InboundOrder> inboundOrderRepository, IRepository<Dt_WarehouseArea> warehouseAreaRepository, IRepository<Dt_LocationType> locationTypeRepository, IRepository<Dt_StockInfo> stockInfoRepository, IRepository<Dt_InboundOrderDetail> inboundOrderDetailRepository, IStockService stockService, IRepository<Dt_Task> taskRepository,IRepository<Dt_AllocateMaterialInfo> allocateMaterialInfo, HttpClientHelper httpClientHelper, IRepository<Dt_MesReturnRecord> mesReturnRecord,ILocationInfoService locationInfoService,IRepository<Dt_TakeStockOrder> takeStockOrder)
        {
            _unitOfWorkManage = unitOfWorkManage;
            InboundOrderDetailService = inboundOrderDetailService;
@@ -68,6 +69,7 @@
            _httpClientHelper = httpClientHelper;
            _mesReturnRecord = mesReturnRecord;
            _locationInfoService = locationInfoService;
            _takeStockOrder = takeStockOrder;
        }
        public async Task<WebResponseContent> GroupPallet(GroupPalletDto palletDto)
@@ -594,5 +596,125 @@
            return httpResponseResult;
        }
        public async Task<WebResponseContent> StockTakeGroupPallet(GroupPalletDto palletDto)
        {
            WebResponseContent content = new WebResponseContent();
            try
            {
                (bool, string, object?) result2 = ModelValidate.ValidateModelData(palletDto);
                if (!result2.Item1) return content.Error(result2.Item2);
                // éªŒè¯ä»“库编号是否存在
                var code = _warehouseAreaRepository.Db.Queryable<Dt_WarehouseArea>()
                    .Where(x => x.Code == palletDto.WarehouseType)
                    .Select(x => x.Code)
                    .First();
                if (string.IsNullOrEmpty(code))
                {
                    return content.Error($"仓库中没有该{palletDto.WarehouseType}编号。");
                }
                // æŸ¥è¯¢å½“前托盘的库存信息
                Dt_StockInfo? stockInfo = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == palletDto.PalletCode)
                    .FirstAsync();
                // éªŒè¯æ‰˜ç›˜æ˜¯å¦å·²ç”Ÿæˆä»»åŠ¡
                if (_taskRepository.QueryFirst(x => x.PalletCode == palletDto.PalletCode) != null)
                {
                    return content.Error($"该托盘已生成任务");
                }
                // éªŒè¯æ‰˜ç›˜æ˜¯å¦å·²ä¸Šæž¶ï¼ˆå·²ä¸Šæž¶ä¸èƒ½ç»„盘)
                if (stockInfo != null && !string.IsNullOrEmpty(stockInfo.LocationCode) && stockInfo.StockStatus != (int)StockStatusEmun.组盘暂存)
                {
                    return content.Error("已上架的托盘不能再次组盘");
                }
                Dt_StockInfoDetail stockInfoDetail = _stockService.StockInfoDetailService.Db.Queryable<Dt_StockInfoDetail>()
                    .Where(x => x.Barcode == palletDto.Barcode && x.StockId == 0)
                    .First();
                if (stockInfoDetail == null)
                {
                    return content.Error($"{palletDto.Barcode} æ¡ç å·²å…³è”其他托盘,无法组盘");
                }
                Dt_TakeStockOrder takeStockOrder = _takeStockOrder.Db.Queryable<Dt_TakeStockOrder>()
                    .Where(x => x.OrderNo == stockInfoDetail.OrderNo)
                    .First();
                if (takeStockOrder == null)
                {
                    return content.Error($"{palletDto.Barcode} ä¸å±žäºŽç›˜ç‚¹å•据中的条码,不允许盘亏组盘");
                }
                if (stockInfo == null)
                {
                    stockInfo = new Dt_StockInfo()
                    {
                        PalletType = (int)PalletTypeEnum.None,
                        LocationType = Convert.ToInt32(palletDto.locationType),
                        PalletCode = palletDto.PalletCode,
                        StockStatus = (int)StockStatusEmun.组盘暂存,
                        Details = new List<Dt_StockInfoDetail>()
                    };
                }
                if (stockInfo.Details.Count > 0 && stockInfo.Details.FirstOrDefault()?.WarehouseCode != palletDto.WarehouseType)
                {
                    return content.Error($"该托盘组盘仓库为{stockInfo.Details.FirstOrDefault()?.WarehouseCode}与当前仓库{palletDto.WarehouseType}不一致,不允许组盘");
                }
                _unitOfWorkManage.BeginTran();
                try
                {
                    if (stockInfo.Id == 0)
                    {
                        int newStockId = await _stockInfoRepository.Db.Insertable(stockInfo).ExecuteReturnIdentityAsync();
                        stockInfo.Id = newStockId;
                    }
                    stockInfoDetail.StockId = stockInfo.Id;
                    await _stockService.StockInfoDetailService.Db.Updateable(stockInfoDetail)
                        .Where(x => x.Id == stockInfoDetail.Id)
                        .ExecuteCommandAsync();
                    if (stockInfo.Id != 0 && stockInfo.Details != null && !stockInfo.Details.Contains(stockInfoDetail))
                    {
                        stockInfo.Details.Add(stockInfoDetail);
                        await _stockInfoRepository.Db.Updateable(stockInfo)
                            .IgnoreColumns(x => x.Details)
                            .ExecuteCommandAsync();
                    }
                    // æäº¤äº‹åŠ¡
                    _unitOfWorkManage.CommitTran();
                }
                catch (Exception)
                {
                    // äº‹åŠ¡å›žæ»š
                    _unitOfWorkManage.RollbackTran();
                    throw; // æŠ›ç»™å¤–层catch处理日志
                }
                // æŸ¥è¯¢æœ€æ–°çš„库存信息(包含关联的明细)
                Dt_StockInfo? NewstockInfo = await _stockInfoRepository.Db.Queryable<Dt_StockInfo>()
                    .Includes(x => x.Details)
                    .Where(x => x.PalletCode == palletDto.PalletCode)
                    .FirstAsync();
                return WebResponseContent.Instance.OK(data: NewstockInfo.Details.OrderByDescending(x => x.Id));
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                LogFactory.GetLog($"组盘信息").Info(true, $"【异常】:【{ex.Message}】{Environment.NewLine}【{ex.StackTrace}】{Environment.NewLine}{Environment.NewLine}");
                return content.Error(ex.Message);
            }
        }
    }
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_InboundService/TakeStockOrderService.cs
@@ -80,10 +80,10 @@
                {
                    return WebResponseContent.Instance.Error("该托盘处于非盘点状态,请检查盘点任务");
                }
                Dt_TakeStockOrder takeStockOrder = _takeStockOrder.QueryFirst(x=>x.OrderNo == orderNo);
                Dt_TakeStockOrder takeStockOrder = _takeStockOrder.QueryFirst(x=>x.AllPalletCode.Contains(boxNo)&& x.TakeStockStatus == TakeStockStatusEnum.盘点中.ObjToInt());
                if (takeStockOrder == null)
                {
                    return WebResponseContent.Instance.Error("未找到该盘点单据");
                    return WebResponseContent.Instance.Error("该托盘未找到该盘点单据");
                }
                if (takeStockOrder.AllPalletCode.Contains(","))
                {
@@ -103,8 +103,8 @@
                        return WebResponseContent.Instance.Error($"箱号【{boxNo}】未在盘点单箱号【{takeStockOrder.AllPalletCode}】中找到匹配项");
                    }
                }
                return WebResponseContent.Instance.OK();
                var resultData = new { takeStockOrder = takeStockOrder.OrderNo };
                return WebResponseContent.Instance.OK(data:resultData);
            }
            catch(Exception ex)
            {
@@ -148,10 +148,6 @@
        {
            try
            {
                if(completeStockTakeDTO.actualQuantity == completeStockTakeDTO.stockQuantity)
                {
                    return WebResponseContent.Instance.OK("该条码为平账,无需记录差异");
                }
                Dt_TakeStockOrder takeStockOrder = _takeStockOrder.QueryFirst(x=>x.OrderNo == completeStockTakeDTO.orderNo);
                if (takeStockOrder == null)
                {
@@ -167,7 +163,6 @@
                {
                    return WebResponseContent.Instance.Error("条码库存数据未找到匹配数据");
                }
                List<Dt_AllocateMaterialInfo> allocateMaterialInfos = new List<Dt_AllocateMaterialInfo>();
                Dt_TakeStockOrderDetail takeStockOrderDetail = new Dt_TakeStockOrderDetail()
                {
                    TakeStockId = takeStockOrder.Id,
@@ -179,7 +174,7 @@
                    Unit = stockInfoDetail.Unit,
                    SysQty = completeStockTakeDTO.stockQuantity,
                    Qty = completeStockTakeDTO.actualQuantity,
                    Remark = completeStockTakeDTO.actualQuantity - completeStockTakeDTO.stockQuantity >= 0 ? "盘盈" : "盘亏",
                    Remark = "盘亏",
                    barcode = completeStockTakeDTO.barcode,
                    WarehouseCode = stockInfoDetail.WarehouseCode ?? "",
                    FactoryArea = stockInfoDetail.FactoryArea,
@@ -188,25 +183,12 @@
                    DifferenceQty = completeStockTakeDTO.actualQuantity - completeStockTakeDTO.stockQuantity
                };
                foreach (var item in stockInfo.Details)
                {
                    Dt_AllocateMaterialInfo allocateMaterialInfo = new Dt_AllocateMaterialInfo()
                    {
                        Barcode = item.Barcode,
                        BatchNo = item.BatchNo,
                        FactoryArea = item.FactoryArea,
                        MaterialCode = item.MaterielCode,
                        MaterialName = item.MaterielName,
                        OrderId = takeStockOrder.Id,
                        OrderNo = takeStockOrder.OrderNo,
                        Quantity = item.StockQuantity,
                        SupplyCode = item.SupplyCode,
                        Unit = item.Unit
                    };
                }
                stockInfoDetail.StockId = 0;
                stockInfoDetail.OrderNo = takeStockOrder.OrderNo;
                stockInfo.StockStatus = StockStatusEmun.盘点库存完成.ObjToInt();
                _unitOfWorkManage.BeginTran();
                _takeStockOrderDetail.AddData(takeStockOrderDetail);
                _stockInfoDetailRepository.UpdateData(stockInfoDetail);
                _stockInfoRepository.UpdateData(stockInfo);
                _unitOfWorkManage.CommitTran();
                return WebResponseContent.Instance.OK("盘点完成,请取走该异常料箱进行平账处理!");
@@ -236,18 +218,14 @@
                }
                var task = _taskRepository.QueryFirst(x => x.PalletCode == boxNo);
                if (task != null)
                {
                    return content.Error($"托盘{boxNo}存在任务回库失败!");
                    return content.Error($"托盘{boxNo}存在任务,回库失败!");
                }
                if(stock.StockStatus != StockStatusEmun.盘点出库完成.ObjToInt() && stock.StockStatus != StockStatusEmun.盘点库存完成.ObjToInt())
                {
                    return content.Error("该托盘状态不对,不允许盘点入库");
                }
                stock.StockStatus = StockStatusEmun.入库确认.ObjToInt();
                var palletCodes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
                if (!string.IsNullOrEmpty(takeStockOrder.AllPalletCode))
                {
@@ -256,19 +234,14 @@
                        .Select(p => p.Trim())
                        .ToHashSet(StringComparer.OrdinalIgnoreCase);
                }
                Dt_TakeStockOrderDetail isReturn = _takeStockOrderDetail.QueryFirst(x => x.TakePalletCode == boxNo && (x.TakeDetalStatus == TakeStockDetailStatusEnum.未进行平账处理.ObjToInt() || x.TakeDetalStatus == TakeStockDetailStatusEnum.杂收杂发平账处理中.ObjToInt()));
                if(isReturn != null)
                {
                    return WebResponseContent.Instance.Error("该托盘还有条码待平账,请先处理再回库");
                }
                bool hasRelatedTasks = _taskRepository.QueryData(x => palletCodes.Contains(x.PalletCode)).Any();
                bool hasRelatedDetails = _takeStockOrderDetail.QueryData(x => palletCodes.Contains(x.TakePalletCode)).Any();
                int overPalletCodeCount = _stockInfoRepository.QueryData(x => palletCodes.Contains(x.PalletCode) && (x.StockStatus == StockStatusEmun.出库完成.ObjToInt() || x.StockStatus == StockStatusEmun.入库完成.ObjToInt() || x.StockStatus == StockStatusEmun.出库锁定.ObjToInt()|| x.StockStatus == StockStatusEmun.入库确认.ObjToInt())).Count();
                bool hasRelatedTasks = palletCodes.Count == overPalletCodeCount + 1;
                if (!hasRelatedTasks && !hasRelatedDetails)
                if (hasRelatedTasks)
                {
                    takeStockOrder.TakeStockStatus = (int)TakeStockStatusEnum.盘点完成;
                }
                stock.StockStatus = StockStatusEmun.入库确认.ObjToInt();
                // åˆ†é…æ–°è´§ä½
                var newLocation = _locationInfoService.AssignLocation(stock.LocationType);
@@ -416,128 +389,123 @@
            }
        }
        public WebResponseContent DocumentReconciliation(int orderId, int id)
        public WebResponseContent DocumentReconciliation(string barcode)
        {
            WebResponseContent webResponseContent = new WebResponseContent();
            try
            {
                Dt_TakeStockOrderDetail takeStockOrderDetail = _takeStockOrderDetail.QueryFirst(x => x.Id == id);
                Dt_TakeStockOrderDetail takeStockOrderDetail = _takeStockOrderDetail.QueryFirst(x => x.barcode == barcode);
                if(takeStockOrderDetail== null)
                {
                    return WebResponseContent.Instance.Error("未找到该盘点差异数据");
                }
                if(takeStockOrderDetail.Remark == "盘盈")
                {
                    Dt_InboundOrderDetail inboundOrderDetail = _inboundOrderDetailRepository.QueryFirst(x => x.Id == orderId);
                    if(inboundOrderDetail == null)
                    {
                        return WebResponseContent.Instance.Error("未找到选择的杂收平账单据");
                    }
                    Dt_InboundOrder inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().Where(x=>x.Id == inboundOrderDetail.OrderId).Includes(x=>x.Details).First();
                    Dt_StockInfo  stockInfo = _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Where(x=>x.PalletCode == takeStockOrderDetail.TakePalletCode && x.StockStatus == StockStatusEmun.盘点库存完成.ObjToInt()).Includes(x=>x.Details).First();
                    if(stockInfo== null)
                    {
                        return WebResponseContent.Instance.Error($"盘点托盘{takeStockOrderDetail.TakePalletCode}的库存信息未找到,或托盘状态不正确");
                    }
                    var datevaliDate = _inboundOrderRepository.Db.Queryable<Dt_MaterialExpirationDate>().Where(x => x.MaterialCode.Contains(inboundOrderDetail.MaterielCode.Substring(0, 6))).First();
                    var newStockDetail = new Dt_StockInfoDetail
                    {
                        StockId = stockInfo == null ? 0 : stockInfo.Id,
                        Barcode = inboundOrderDetail.Barcode,
                        MaterielCode = inboundOrderDetail.MaterielCode,
                        MaterielName = inboundOrderDetail.MaterielName,
                        BatchNo = inboundOrderDetail.BatchNo,
                        Unit = inboundOrderDetail.Unit,
                        InboundOrderRowNo = inboundOrderDetail.lineNo,
                        SupplyCode = inboundOrderDetail.SupplyCode,
                        WarehouseCode = inboundOrderDetail.WarehouseCode,
                        StockQuantity = inboundOrderDetail.OrderQuantity,
                        BarcodeQty = inboundOrderDetail.BarcodeQty,
                        BarcodeUnit = inboundOrderDetail.BarcodeUnit,
                        FactoryArea = inboundOrder.FactoryArea,
                        Status = 0,
                        OrderNo = inboundOrder.InboundOrderNo,
                        BusinessType = inboundOrder.BusinessType,
                        ValidDate = inboundOrder.BusinessType == BusinessTypeEnum.外部仓库调智仓.ToString() ? inboundOrderDetail.ValidDate : datevaliDate == null ? null : Convert.ToDateTime(DateTime.Now).AddDays(Convert.ToDouble(datevaliDate.ValidityDays)),
                    };
                    stockInfo.Details.Add(newStockDetail);
                //if(takeStockOrderDetail.Remark == "盘盈")
                //{
                //    Dt_InboundOrderDetail inboundOrderDetail = _inboundOrderDetailRepository.QueryFirst(x => x.Id == orderId);
                //    if(inboundOrderDetail == null)
                //    {
                //        return WebResponseContent.Instance.Error("未找到选择的杂收平账单据");
                //    }
                //    Dt_InboundOrder inboundOrder = _inboundOrderRepository.Db.Queryable<Dt_InboundOrder>().Where(x=>x.Id == inboundOrderDetail.OrderId).Includes(x=>x.Details).First();
                //    Dt_StockInfo  stockInfo = _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Where(x=>x.PalletCode == takeStockOrderDetail.TakePalletCode && x.StockStatus == StockStatusEmun.盘点库存完成.ObjToInt()).Includes(x=>x.Details).First();
                //    if(stockInfo== null)
                //    {
                //        return WebResponseContent.Instance.Error($"盘点托盘{takeStockOrderDetail.TakePalletCode}的库存信息未找到,或托盘状态不正确");
                //    }
                //    var datevaliDate = _inboundOrderRepository.Db.Queryable<Dt_MaterialExpirationDate>().Where(x => x.MaterialCode.Contains(inboundOrderDetail.MaterielCode.Substring(0, 6))).First();
                //    var newStockDetail = new Dt_StockInfoDetail
                //    {
                //        StockId = stockInfo == null ? 0 : stockInfo.Id,
                //        Barcode = inboundOrderDetail.Barcode,
                //        MaterielCode = inboundOrderDetail.MaterielCode,
                //        MaterielName = inboundOrderDetail.MaterielName,
                //        BatchNo = inboundOrderDetail.BatchNo,
                //        Unit = inboundOrderDetail.Unit,
                //        InboundOrderRowNo = inboundOrderDetail.lineNo,
                //        SupplyCode = inboundOrderDetail.SupplyCode,
                //        WarehouseCode = inboundOrderDetail.WarehouseCode,
                //        StockQuantity = inboundOrderDetail.OrderQuantity,
                //        BarcodeQty = inboundOrderDetail.BarcodeQty,
                //        BarcodeUnit = inboundOrderDetail.BarcodeUnit,
                //        FactoryArea = inboundOrder.FactoryArea,
                //        Status = 0,
                //        OrderNo = inboundOrder.InboundOrderNo,
                //        BusinessType = inboundOrder.BusinessType,
                //        ValidDate = inboundOrder.BusinessType == BusinessTypeEnum.外部仓库调智仓.ToString() ? inboundOrderDetail.ValidDate : datevaliDate == null ? null : Convert.ToDateTime(DateTime.Now).AddDays(Convert.ToDouble(datevaliDate.ValidityDays)),
                //    };
                //    stockInfo.Details.Add(newStockDetail);
                    inboundOrderDetail.ReceiptQuantity = inboundOrderDetail.OrderQuantity;
                    inboundOrderDetail.OverInQuantity = inboundOrderDetail.OrderQuantity;
                    inboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
                    int overCount = 1;
                    int moreOverCount = inboundOrder.Details.Count(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt());
                    if (inboundOrder.Details.Count() == overCount + moreOverCount)
                    {
                        inboundOrder.OrderStatus = InOrderStatusEnum.入库完成.ObjToInt();
                    }
                    else
                    {
                        inboundOrder.OrderStatus = InOrderStatusEnum.入库中.ObjToInt();
                    }
                    takeStockOrderDetail.DifferenceQty -= inboundOrderDetail.OrderQuantity;
                    if(takeStockOrderDetail.DifferenceQty > 0)
                    {
                        takeStockOrderDetail.TakeDetalStatus = TakeStockDetailStatusEnum.杂收杂发平账处理中.ObjToInt();
                    }
                    else if (takeStockOrderDetail.DifferenceQty == 0)
                    {
                        takeStockOrderDetail.TakeDetalStatus = TakeStockDetailStatusEnum.杂收杂发平账处理.ObjToInt();
                    }
                    else
                    {
                        return WebResponseContent.Instance.Error("该杂收单据明细条码数量大于待平账数量,请另选其他单据平账");
                    }
                //    inboundOrderDetail.ReceiptQuantity = inboundOrderDetail.OrderQuantity;
                //    inboundOrderDetail.OverInQuantity = inboundOrderDetail.OrderQuantity;
                //    inboundOrderDetail.OrderDetailStatus = OrderDetailStatusEnum.Over.ObjToInt();
                //    int overCount = 1;
                //    int moreOverCount = inboundOrder.Details.Count(x => x.OrderDetailStatus == OrderDetailStatusEnum.Over.ObjToInt());
                //    if (inboundOrder.Details.Count() == overCount + moreOverCount)
                //    {
                //        inboundOrder.OrderStatus = InOrderStatusEnum.入库完成.ObjToInt();
                //    }
                //    else
                //    {
                //        inboundOrder.OrderStatus = InOrderStatusEnum.入库中.ObjToInt();
                //    }
                //    takeStockOrderDetail.DifferenceQty -= inboundOrderDetail.OrderQuantity;
                //    if(takeStockOrderDetail.DifferenceQty > 0)
                //    {
                //        takeStockOrderDetail.TakeDetalStatus = TakeStockDetailStatusEnum.杂收杂发平账处理中.ObjToInt();
                //    }
                //    else if (takeStockOrderDetail.DifferenceQty == 0)
                //    {
                //        takeStockOrderDetail.TakeDetalStatus = TakeStockDetailStatusEnum.杂收杂发平账处理.ObjToInt();
                //    }
                //    else
                //    {
                //        return WebResponseContent.Instance.Error("该杂收单据明细条码数量大于待平账数量,请另选其他单据平账");
                //    }
                    
                    _unitOfWorkManage.BeginTran();
                    _inboundOrderRepository.UpdateData(inboundOrder);
                    _inboundOrderDetailRepository.UpdateData(inboundOrderDetail);
                    _takeStockOrderDetail.UpdateData(takeStockOrderDetail);
                    BaseDal.Db.Insertable(newStockDetail).ExecuteCommand();
                    _unitOfWorkManage.CommitTran();
                    List<string> barcodes = new List<string>();
                    barcodes.Add(inboundOrderDetail.Barcode);
                    _outboundPickingService.NoStockOutBatchInOrderFeedbackToMes(inboundOrder.Id, barcodes);
                //    _unitOfWorkManage.BeginTran();
                //    _inboundOrderRepository.UpdateData(inboundOrder);
                //    _inboundOrderDetailRepository.UpdateData(inboundOrderDetail);
                //    _takeStockOrderDetail.UpdateData(takeStockOrderDetail);
                //    BaseDal.Db.Insertable(newStockDetail).ExecuteCommand();
                //    _unitOfWorkManage.CommitTran();
                //    List<string> barcodes = new List<string>();
                //    barcodes.Add(inboundOrderDetail.Barcode);
                //    _outboundPickingService.NoStockOutBatchInOrderFeedbackToMes(inboundOrder.Id, barcodes);
                }
                else
                {
                    Dt_OutboundOrderDetail outboundOrderDetail = _outboundOrderDetailRepository.QueryFirst(x => x.Id == orderId);
                //}
                //else
                //{
                Dt_OutboundOrderDetail outboundOrderDetail = _outboundOrderDetailRepository.QueryFirst(x => x.Remark == takeStockOrderDetail.TakeStockNo && (x.OrderQuantity-x.LockQuantity-x.MoveQty)>0);
                    if (outboundOrderDetail == null)
                    {
                        return WebResponseContent.Instance.Error("未找到选择的杂发平账单据");
                    }
                if(outboundOrderDetail.MaterielCode != takeStockOrderDetail.MaterielCode)
                {
                    return WebResponseContent.Instance.Error("与杂发平账单据物料编码不匹配");
                }
                if (!string.IsNullOrWhiteSpace(outboundOrderDetail.BatchNo)&& outboundOrderDetail.BatchNo != takeStockOrderDetail.BatchNo)
                {
                    return WebResponseContent.Instance.Error("与杂发平账单据物料批次不匹配");
                }
                if (!string.IsNullOrWhiteSpace(outboundOrderDetail.SupplyCode) && outboundOrderDetail.SupplyCode != takeStockOrderDetail.SupplyCode)
                {
                    return WebResponseContent.Instance.Error("与杂发平账单据供应商不匹配");
                }
                    Dt_OutboundOrder outboundOrder = _outboundOrderRepository.Db.Queryable<Dt_OutboundOrder>().Where(x => x.Id == outboundOrderDetail.OrderId).Includes(x => x.Details).First();
                    Dt_StockInfo stockInfo = _stockInfoRepository.Db.Queryable<Dt_StockInfo>().Where(x => x.PalletCode == takeStockOrderDetail.TakePalletCode && x.StockStatus == StockStatusEmun.盘点库存完成.ObjToInt()).Includes(x => x.Details).First();
                    if (stockInfo == null)
                    {
                        return WebResponseContent.Instance.Error($"盘点托盘{takeStockOrderDetail.TakePalletCode}的库存信息未找到,或托盘状态不正确");
                    }
                    if(outboundOrderDetail.OrderQuantity + takeStockOrderDetail.DifferenceQty > 0)
                    {
                        return WebResponseContent.Instance.Error("该杂发单据明细发料数量大于待平账数量,请另选其他单据平账");
                    }
                    else if(outboundOrderDetail.OrderQuantity + takeStockOrderDetail.DifferenceQty < 0)
                    {
                        takeStockOrderDetail.TakeDetalStatus = TakeStockDetailStatusEnum.杂收杂发平账处理中.ObjToInt();
                    }
                    else
                    {
                        takeStockOrderDetail.TakeDetalStatus = TakeStockDetailStatusEnum.杂收杂发平账处理.ObjToInt();
                    }
                    OutboundCompleteRequestDTO request = new OutboundCompleteRequestDTO()
                    {
                        OrderNo = outboundOrder.OrderNo,
                        PalletCode = stockInfo.PalletCode,
                    PalletCode = takeStockOrderDetail.TakePalletCode,
                        Barcode = takeStockOrderDetail.barcode,
                        Operator = App.User.UserName
                    };
                    decimal stoQty = takeStockOrderDetail.SysQty;
                  webResponseContent = CompleteOutboundWithBarcode(request, stoQty, orderId);
                webResponseContent = CompleteOutboundWithBarcode(request,outboundOrderDetail.Id);
                    takeStockOrderDetail.DifferenceQty = 0;
                    _takeStockOrderDetail.UpdateData(takeStockOrderDetail);
                }
                return WebResponseContent.Instance.OK(data: webResponseContent);
            }
            catch(Exception ex)
@@ -547,7 +515,7 @@
            }
        }
        public WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request ,decimal stoQty, int orderDetailId)
        public WebResponseContent CompleteOutboundWithBarcode(OutboundCompleteRequestDTO request ,int orderDetailId)
        {
            WebResponseContent content = WebResponseContent.Instance;
@@ -572,13 +540,7 @@
                    return WebResponseContent.Instance.Error($"条码 {request.Barcode} å¯¹åº”的库存明细不存在");
                }
                // 3. éªŒè¯åº“存明细与托盘是否匹配
                if (stockDetail.StockId != stockInfo.Id)
                {
                    response.Success = false;
                    response.Message = $"条码 {request.Barcode} ä¸å±žäºŽæ‰˜ç›˜å· {request.PalletCode} çš„库存明细";
                    return WebResponseContent.Instance.Error($"条码 {request.Barcode} ä¸å±žäºŽæ‰˜ç›˜å· {request.PalletCode} çš„库存明细");
                }
                // 4. æŸ¥æ‰¾å‡ºåº“单信息
                Dt_OutboundOrder outboundOrder = _outboundOrderRepository.QueryFirst(o => o.OrderNo == request.OrderNo);
@@ -595,11 +557,11 @@
                    return WebResponseContent.Instance.Error("未找到出库单明细");
                }
                // å®žé™…出库量
                decimal actualOutboundQuantity = outboundOrderDetail.OrderQuantity;
                decimal actualOutboundQuantity = outboundOrderDetail.OrderQuantity-outboundOrderDetail.LockQuantity-outboundOrderDetail.MoveQty;
                // 8. åˆ¤æ–­æ˜¯å¦éœ€è¦æ‹†åŒ…(当出库数量小于库存数量时需要拆包)
                bool isUnpacked = outboundOrderDetail.OrderQuantity < stockDetail.StockQuantity;
                bool isUnpacked = actualOutboundQuantity < stockDetail.StockQuantity;
                List<MaterialCodeReturnDTO> returnDTOs = new List<MaterialCodeReturnDTO>();
                string newBarcode = string.Empty;
                // 9. å¼€å¯äº‹åŠ¡
@@ -744,10 +706,10 @@
                    if (!string.IsNullOrEmpty(newBarcode))
                    {
                        // ç‰©æ–™æ–°æ¡ç å›žä¼ 
                        _feedbackMesService.BarcodeFeedback(newBarcode);
                        //_feedbackMesService.BarcodeFeedback(newBarcode);
                    }
                    _feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                    //_feedbackMesService.OutboundFeedback(outboundOrder.OrderNo);
                }
                catch (Exception ex)
@@ -767,6 +729,10 @@
            return content;
        }
        public WebResponseContent StockTakeGroupPallet(string barcode, string boxNo)
        {
            throw new NotImplementedException();
        }
    }
    
}
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_OutboundService/OutboundService.cs
@@ -108,139 +108,120 @@
        public WebResponseContent ProcessPickingOutbound(PickingOutboundRequestDTO request)
        {
            WebResponseContent content = WebResponseContent.Instance;
            PickingOutboundResponseDTO response = new PickingOutboundResponseDTO();
            decimal totalNeedAllocate = 0; // æ€»éœ€æ±‚分配量
            decimal totalActualAllocate = 0; // å®žé™…总分配量
            try
            {
                _unitOfWorkManage.BeginTran();
                // 1. è®¡ç®—出库数量逻辑
                OutboundCalculationDTO calculationResult = CalcOutboundQuantity(request);
                if (!calculationResult.CanOutbound)
                {
                    content = WebResponseContent.Instance.Error("无法处理拣货出库:" + calculationResult.ErrorMessage);
                    _unitOfWorkManage.RollbackTran();
                    return content;
                }
                // è®°å½•总需求数量
                totalNeedAllocate = calculationResult.MaterielCalculations.Sum(x => x.UnallocatedQuantity);
                // 2. è°ƒç”¨å‡ºåº“处理逻辑,锁定库存,生成出库记录等
                // 2. å¤„理物料分配
                List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
                // èŽ·å–å‡ºåº“å•ä¿¡æ¯
                Dt_OutboundOrder outboundOrder = calculationResult.OutboundOrder;
                // å‡ºåº“详情添加或修改集合
                List<Dt_OutStockLockInfo> outStockLockInfos = new List<Dt_OutStockLockInfo>();
                List<Dt_OutboundOrderDetail> outboundOrderDetails = new();
                List<Dt_OutboundOrderDetail> outboundOrderDetails = new List<Dt_OutboundOrderDetail>();
                List<Dt_Task> tasks = new List<Dt_Task>();
                foreach (var materielCalc in calculationResult.MaterielCalculations)
                {
                    (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea);
                    var materielPickedDetails = ProcessMaterielTaskGeneration(outboundOrder, materielCalc, request, calculationResult.FactoryArea);
                    // è®¡ç®—当前物料实际分配量
                    decimal actualAllocatedQuantity = materielPickedDetails.PickedDetails.Sum(x => x.OutboundQuantity);
                    actualAllocatedQuantity = Math.Min(actualAllocatedQuantity, materielCalc.UnallocatedQuantity);
                    totalActualAllocate += actualAllocatedQuantity; // ç´¯åŠ è‡³æ€»å®žé™…åˆ†é…é‡
                    materielCalc.UnallocatedQuantity = materielCalc.UnallocatedQuantity - actualAllocatedQuantity;
                    // å¤„理出库锁定记录
                    foreach (var item in materielPickedDetails.OutStockLockInfo)
                    {
                        Dt_OutStockLockInfo? outStockLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0);
                        if (outStockLockInfo != null)
                        Dt_OutStockLockInfo? existLockInfo = materielCalc.OutStockLockInfos.FirstOrDefault(x => x.Id == item.Id && x.Id > 0);
                        if (existLockInfo != null)
                        {
                            outStockLockInfo = item;
                            existLockInfo = item;
                            Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                            if (task != null)
                            {
                                outStockLockInfo.TaskNum = task.TaskNum;
                            }
                            if (task != null) existLockInfo.TaskNum = task.TaskNum;
                        }
                        else
                        {
                            Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                            if (task != null)
                            {
                                item.TaskNum = task.TaskNum;
                            }
                            if (task != null) item.TaskNum = task.TaskNum;
                            materielCalc.OutStockLockInfos.Add(item);
                        }
                        outStockLockInfos.Add(item);
                    }
                    // å¤„理任务
                    foreach (var item in materielPickedDetails.Tasks)
                    {
                        Dt_Task? task = tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode);
                        if (task == null)
                        {
                        if (tasks.FirstOrDefault(x => x.PalletCode == item.PalletCode) == null)
                            tasks.Add(item);
                        }
                    }
                    // æ±‡æ€»åˆ†æ‹£æ˜Žç»†
                    pickedDetails.AddRange(materielPickedDetails.PickedDetails);
                    decimal allallocatedQuantity = Math.Min(materielCalc.UnallocatedQuantity, materielPickedDetails.PickedDetails.Sum(x => x.OutboundQuantity));
                    materielCalc.UnallocatedQuantity = allallocatedQuantity;
                    // æ›´æ–°å‡ºåº“单明细(增加锁定数量,不增加已出数量)
                    // æŒ‰å®žé™…分配量更新单据锁定数量
                    decimal remainingToLock = actualAllocatedQuantity;
                    foreach (var detail in materielCalc.Details)
                    {
                        if (allallocatedQuantity <= 0) break;
                        decimal lockQuantity = (detail.OrderQuantity - detail.OverOutQuantity);
                        if (lockQuantity < materielCalc.UnallocatedQuantity)
                        {
                            detail.LockQuantity += lockQuantity; // å¢žåŠ é”å®šæ•°é‡ ä¸æ›´æ–° OverOutQuantity å’Œ OrderDetailStatus,因为还没有实际出库
                        if (remainingToLock <= 0) break;
                        decimal maxLockableQty = detail.OrderQuantity - detail.OverOutQuantity - detail.LockQuantity;
                        if (maxLockableQty <= 0) continue;
                        decimal currentLockQty = Math.Min(remainingToLock, maxLockableQty);
                        detail.LockQuantity += currentLockQty;
                            outboundOrderDetails.Add(detail);
                            materielCalc.UnallocatedQuantity -= lockQuantity;
                        }
                        else
                        {
                            detail.LockQuantity += materielCalc.UnallocatedQuantity;
                            outboundOrderDetails.Add(detail);
                            break;
                        }
                        remainingToLock -= currentLockQty;
                    }
                }
                // 3. æ›´æ–°å‡ºåº“单状态为出库中(表示已有任务分配)
                // 3. æ‰¹é‡æ›´æ–°çŠ¶æ€ï¼ˆåŽŸæœ‰é€»è¾‘ä¸å˜ï¼‰
                UpdateOutboundOrderStatus(request.OrderNo, (int)OutOrderStatusEnum.出库中);
                // 4. æ›´æ–°å‡ºåº“单明细锁定数量
                _detailRepository.UpdateData(outboundOrderDetails);
                // 5. æ›´æ–°åº“存状态
                if (pickedDetails.Any())
                {
                UpdateStockStatus(pickedDetails.Select(x => x.PalletCode).ToList(), StockStatusEmun.出库锁定.ObjToInt());
                // 6. æ›´æ–°è´§ä½çŠ¶æ€
                UpdateLocationStatus(pickedDetails.Select(x => x.LocationCode).ToList(), LocationStatusEnum.Lock.ObjToInt());
                // 7. æ›´æ–°åº“存详情
                }
                UpdateOutStockLockInfo(outStockLockInfos);
                // 8. æ·»åŠ ä»»åŠ¡æ•°æ®
                _taskRepository.AddData(tasks);
                if (tasks.Any()) _taskRepository.AddData(tasks);
                _unitOfWorkManage.CommitTran();
                // 4. æž„造响应:区分「全部分配」和「部分分配」
                string responseMsg = totalActualAllocate == totalNeedAllocate
                    ? "分拣任务分配成功"
                    : $"分拣任务分配完成(实际分配{totalActualAllocate},需求{totalNeedAllocate},库存不足部分未分配)";
                response.Success = true;
                response.Message = "分拣任务分配成功";
                response.Tasks = tasks; // è¿”回第一个任务号
                response.PickedDetails = pickedDetails; // è¿”回第一个分拣明细
                content = WebResponseContent.Instance.OK("分拣任务分配成功", response);
                return content;
                response.Message = responseMsg;
                response.Tasks = tasks;
                response.PickedDetails = pickedDetails;
                content = WebResponseContent.Instance.OK(responseMsg, response);
            }
            catch (Exception ex)
            {
                _unitOfWorkManage.RollbackTran();
                content = WebResponseContent.Instance.Error("处理拣货出库失败:" + ex.Message);
            }
            return content;
        }
        /// <summary>
        /// è®¡ç®—出库数量逻辑
        /// è®¡ç®—出库数量逻辑(原有逻辑不变)
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        public OutboundCalculationDTO CalcOutboundQuantity(PickingOutboundRequestDTO request)
        {
            OutboundCalculationDTO result = new();
            OutboundCalculationDTO result = new OutboundCalculationDTO();
            try
            {
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == request.OrderNo);
@@ -253,23 +234,14 @@
                result.FactoryArea = outboundOrder.FactoryArea;
                List<Dt_OutboundOrderDetail> selectedDetails = new List<Dt_OutboundOrderDetail>();
                if (request.DetailIds == null || !request.DetailIds.Any())
                {
                    selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id);
                }
                else
                {
                    // èŽ·å–é€‰æ‹©çš„å‡ºåº“æ˜Žç»†
                    selectedDetails = _detailRepository.QueryData(x => x.OrderId == outboundOrder.Id && request.DetailIds.Contains(x.Id));
                }
                //if (outboundOrder.IsBatch == 1 && request.DetailIds.Count == 1)
                //{
                //    selectedDetails = _detailRepository.QueryData(x => x.OrderId == selectedDetails.First().OrderId && x.WarehouseCode == selectedDetails.First().WarehouseCode && x.MaterielCode == selectedDetails.First().MaterielCode && x.BatchNo == selectedDetails.First().BatchNo && x.SupplyCode == selectedDetails.First().SupplyCode);
                //}
                if (!selectedDetails.Any())
                {
@@ -280,23 +252,21 @@
                if (selectedDetails.Any(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty))
                {
                    List<int> selectDetailIds = selectedDetails.Where(x => x.LockQuantity > x.OrderQuantity - x.MoveQty || x.OverOutQuantity > x.OrderQuantity - x.MoveQty).Select(x => x.Id).ToList();
                    result.CanOutbound = false;
                    result.ErrorMessage = $"出库明细信息{string.Join(",", selectDetailIds)}已分配完成";
                    return result;
                }
                outboundOrder.Details = selectedDetails;
                result.OutboundOrder = outboundOrder;
                result.SelectedDetails = selectedDetails;
                if (outboundOrder.IsBatch == 0 || request.DetailIds.Count != 1)
                {
                    // å¤šæ˜Žç»†å‡ºåº“:按物料分组处理
                    result.MaterielCalculations = CalcMaterielOutboundQuantities(outboundOrder, selectedDetails.ToList());
                }
                else
                {
                    // å•明细出库:验证输入的出库数量
                    if (!request.OutboundQuantity.HasValue || request.OutboundQuantity.Value <= 0)
                    {
                        result.CanOutbound = false;
@@ -308,10 +278,8 @@
                    decimal orderQuantity = selectedDetails.Sum(x => x.OrderQuantity);
                    decimal moveQuantity = selectedDetails.Sum(x => x.MoveQty);
                    decimal overQuantity = selectedDetails.Sum(x => x.OverOutQuantity);
                    Dt_OutboundOrderDetail? singleDetail = selectedDetails.First();
                    //判断可出库数量
                    if (orderQuantity - lockQuantity - moveQuantity < request.OutboundQuantity.Value || orderQuantity - overQuantity - moveQuantity < request.OutboundQuantity.Value)
                    {
                        result.CanOutbound = false;
@@ -325,10 +293,7 @@
                    {
                        inputQuantity -= (item.OrderQuantity - item.MoveQty - item.LockQuantity);
                        outboundOrderDetails.Add(item);
                        if (inputQuantity <= 0)
                        {
                            break;
                        }
                        if (inputQuantity <= 0) break;
                    }
                    result.MaterielCalculations = new List<MaterielOutboundCalculationDTO>()
@@ -348,31 +313,24 @@
                            Details = outboundOrderDetails
                        }
                    };
                    outboundOrder.Details = outboundOrderDetails;
                }
                result.CanOutbound = true;
                return result;
            }
            catch (Exception ex)
            {
                result.CanOutbound = false;
                result.ErrorMessage = ex.Message;
            }
                return result;
            }
        }
        /// <summary>
        /// å¤šå‡ºåº“单明细时,按物料分组计算出库数量
        /// æŒ‰ç‰©æ–™åˆ†ç»„计算出库数量(原有逻辑不变)
        /// </summary>
        /// <param name="selectedDetails"></param>
        /// <returns></returns>
        private List<MaterielOutboundCalculationDTO> CalcMaterielOutboundQuantities(Dt_OutboundOrder outboundOrder, List<Dt_OutboundOrderDetail> selectedDetails)
        {
            // æŒ‰ç‰©æ–™åˆ†ç»„:物料编号、批次号、供应商编号、仓库编号
            List<MaterielOutboundCalculationDTO> materielGroups = selectedDetails
            return selectedDetails
                .GroupBy(x => new
                {
                    x.MaterielCode,
@@ -392,138 +350,397 @@
                    UnallocatedQuantity = g.Sum(x => x.OrderQuantity - x.LockQuantity - x.MoveQty),
                    MovedQuantity = g.Sum(x => x.MoveQty),
                    Details = g.ToList(),
                    OutStockLockInfos = _outboundLockInfoRepository.QueryData(x => x.MaterielCode == g.Key.MaterielCode && x.BatchNo == g.Key.BatchNo && x.OrderType == (int)outboundOrder.OrderType && x.OrderNo == outboundOrder.OrderNo)
                    OutStockLockInfos = _outboundLockInfoRepository.QueryData(x =>
                        x.MaterielCode == g.Key.MaterielCode &&
                        x.BatchNo == g.Key.BatchNo &&
                        x.OrderType == (int)outboundOrder.OrderType &&
                        x.OrderNo == outboundOrder.OrderNo)
                })
                .ToList();
            return materielGroups;
        }
        /// <summary>
        /// å¤„理物料的任务生成
        /// å¤„理物料任务生成(核心:有多少分多少+移除库存不足异常)
        /// </summary>
        /// <param name="outboundOrder">出库订单</param>
        /// <param name="materielCalc">按物料的出库计算结果</param>
        /// <param name="request">分拣出库请求</param>
        /// <param name="factoryArea"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) ProcessMaterielTaskGeneration(Dt_OutboundOrder outboundOrder, MaterielOutboundCalculationDTO materielCalc, PickingOutboundRequestDTO request, string factoryArea)
        private (List<PickedStockDetailDTO> PickedDetails, List<Dt_Task> Tasks, List<Dt_OutStockLockInfo> OutStockLockInfo) ProcessMaterielTaskGeneration(
    Dt_OutboundOrder outboundOrder,
    MaterielOutboundCalculationDTO materielCalc,
    PickingOutboundRequestDTO request,
    string factoryArea)
        {
            List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
            List<Dt_Task> generatedTasks = new List<Dt_Task>();
            List<Dt_Task> generatedTasks = new List<Dt_Task>(); // åˆå§‹ç©ºä»»åŠ¡åˆ—è¡¨
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            decimal remainingQuantity = materielCalc.UnallocatedQuantity;
            // æž„建库存查询条件(包含库存表、库存明细)
            List<Dt_StockInfo> stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea);
            if (!stockQuery.Any())
            // 1. ä¼˜å…ˆå¤„理指定库存明细
            if (request.StockDetailIds?.Any() == true)
            {
                throw new Exception($"物料 {materielCalc.MaterielCode} å¯¹åº”的库存不存在");
                var specifiedResult = AllocateSpecifiedStockDetails(outboundOrder, materielCalc, request, factoryArea, remainingQuantity);
                pickedDetails.AddRange(specifiedResult.PickedDetails);
                lockInfoList.AddRange(specifiedResult.LockInfoList);
                remainingQuantity -= specifiedResult.ActualAllocatedQuantity;
                // ===== æ ¸å¿ƒä¿®å¤ï¼šæŒ‡å®šåº“存分配后,立即生成任务(无论剩余量多少)=====
                if (specifiedResult.PickedDetails.Any()) // æœ‰æŒ‡å®šåº“存分配结果就生成任务
                {
                    var specifiedTasks = GenerateTasksForSpecifiedStock(specifiedResult.PickedDetails, request.OutboundTargetLocation);
                    generatedTasks.AddRange(specifiedTasks); // æ·»åŠ æŒ‡å®šåº“å­˜ä»»åŠ¡åˆ°æ€»ä»»åŠ¡åˆ—è¡¨
            }
            // æ‰¹é‡è®¡ç®—总可用库存数量
            (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) data = GetBatchAvailableStockQuantities(materielCalc, stockQuery);
                // å‰©ä½™é‡<=0时,直接返回(已生成指定库存任务)
                if (remainingQuantity <= 0)
                {
                    return (pickedDetails, generatedTasks, lockInfoList);
                }
            }
            // å¯ç”¨åº“存数量映射
            Dictionary<int, decimal> availableStockMap = data.AvailableStockMap;
            // 2. å¤„理剩余数量的自动分配(原有逻辑不变,自动分配的任务会追加到generatedTasks)
            List<Dt_StockInfo> stockQuery = BuildStockQueryWithInfo(materielCalc, factoryArea);
            var allocatedPalletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList();
            stockQuery = stockQuery.Where(x => !allocatedPalletCodes.Contains(x.PalletCode)).ToList();
            // ç‰©æ–™æ€»å¯ç”¨åº“存数量
            decimal totalAvailableStock = availableStockMap.Values.Sum();
            var stockData = GetBatchAvailableStockQuantities(materielCalc, stockQuery);
            decimal totalAutoAvailable = stockData.AvailableStockMap.Values.Sum();
            decimal autoAllocateQuantity = Math.Min(remainingQuantity, totalAutoAvailable);
            remainingQuantity -= autoAllocateQuantity;
            // å·²é”å®šåº“存数量映射
            Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = data.LockStockMap;
            // éªŒè¯æ€»å¯ç”¨åº“存是否满足出库需求
            //if (totalAvailableStock < materielCalc.UnallocatedQuantity)
            //{
            //    throw new Exception($"物料 {materielCalc.MaterielCode} å¯ç”¨åº“å­˜ {totalAvailableStock} ä¸è¶³å‡ºåº“数量 {materielCalc.UnallocatedQuantity}");
            //}
            // éœ€åˆ†é…æ•°é‡
            decimal remainingQuantity = Math.Min(totalAvailableStock, materielCalc.UnallocatedQuantity);
            // éœ€åˆ†é…æ•°é‡
            //decimal remainingQuantity = materielCalc.UnallocatedQuantity;
            // å·²åˆ†é…çš„æ‰˜ç›˜åˆ—表
            List<string> allocatedPallets = new List<string>();
            // èŽ·å–ç¬¬ä¸€ä¸ªå‡ºåº“æ˜Žç»†
            if (autoAllocateQuantity > 0 && stockQuery.Any())
            {
            Dt_OutboundOrderDetail firstDetail = materielCalc.Details.First();
            // é¢„加载库存明细和锁定记录
            List<int> stockIds = stockQuery.Select(x => x.Id).ToList();
            Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = stockQuery.ToDictionary(x => x.Id, x => x.Details);
            // è®°å½•每个托盘的实际分配量
            Dictionary<string, decimal> palletAllocations = new Dictionary<string, decimal>();
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            foreach (var stock in stockQuery)
            {
                if (remainingQuantity <= 0) break;
                // å½“前库存可用数量
                decimal availableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0);
                    if (autoAllocateQuantity <= 0) break;
                    decimal availableQuantity = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0);
                if (availableQuantity <= 0) continue;
                // è®¡ç®—该托盘可分配数量
                decimal allocateQuantity = Math.Min(remainingQuantity, availableQuantity);
                (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) actualAllocated = AllocateStockQuantity(stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail, request, lockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()), stockDetailMap);
                    // è‡ªåŠ¨åˆ†é…æ—¶æŸ¥è¯¢æ‰˜ç›˜æ€»åº“å­˜ï¼ˆåŽŸæœ‰ä¼˜åŒ–é€»è¾‘ä¸å˜ï¼‰
                    decimal palletMaterielTotalStock = _stockDetailRepository.QueryData(
                        x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode
                    ).Sum(x => x.StockQuantity);
                // æœ¬æ¬¡åˆ†é…çš„æ•°é‡
                decimal actualAllocatedQuantity = actualAllocated.ActualAllocatedQuantity;
                    decimal allocateQuantity = Math.Min(autoAllocateQuantity, availableQuantity);
                    var actualAllocated = AllocateStockQuantity(
                        stock, allocateQuantity, availableQuantity, outboundOrder, firstDetail,
                        request, stockData.LockStockMap.GetValueOrDefault(stock.Id, new List<Dt_OutStockLockInfo>()),
                        stockDetailMap, palletMaterielTotalStock);
                if (actualAllocatedQuantity > 0)
                    if (actualAllocated.ActualAllocatedQuantity > 0)
                {
                    allocatedPallets.Add(stock.PalletCode);
                    palletAllocations[stock.PalletCode] = actualAllocatedQuantity; // è®°å½•实际分配量
                    remainingQuantity -= actualAllocatedQuantity;
                        palletAllocations[stock.PalletCode] = actualAllocated.ActualAllocatedQuantity;
                        autoAllocateQuantity -= actualAllocated.ActualAllocatedQuantity;
                    lockInfoList.AddRange(actualAllocated.LockInfoList);
                }
            }
            foreach (var palletCode in allocatedPallets)
                // ç”Ÿæˆè‡ªåŠ¨åˆ†é…çš„ä»»åŠ¡ï¼ˆè¿½åŠ åˆ°generatedTasks,不会重复)
                foreach (var palletCode in palletAllocations.Keys)
            {
                Dt_StockInfo stock = stockQuery.First(x => x.PalletCode == palletCode);
                // èŽ·å–å®žé™…åˆ†é…çš„æ•°é‡
                decimal actualAllocatedQuantity = palletAllocations.GetValueOrDefault(palletCode, 0);
                // è®¡ç®—分配后剩余的库存数量
                decimal originalAvailableQuantity = availableStockMap.GetValueOrDefault(stock.Id, 0);
                decimal remainingStockQuantity = Math.Max(0, originalAvailableQuantity - actualAllocatedQuantity);
                    decimal actualQty = palletAllocations[palletCode];
                    decimal originalAvailable = stockData.AvailableStockMap.GetValueOrDefault(stock.Id, 0);
                pickedDetails.Add(new PickedStockDetailDTO
                {
                    PalletCode = palletCode,
                    MaterielCode = materielCalc.MaterielCode,
                    OutboundQuantity = actualAllocatedQuantity, // æœ¬æ¬¡å®žé™…分配的出库量
                    RemainingQuantity = remainingStockQuantity, // åˆ†é…åŽå‰©ä½™çš„可用库存
                        OutboundQuantity = actualQty,
                        RemainingQuantity = Math.Max(0, originalAvailable - actualQty),
                    LocationCode = stock.LocationCode,
                    OutStockLockInfos = lockInfoList
                        OutStockLockInfos = lockInfoList.Where(x => x.PalletCode == palletCode).ToList()
                });
                Dt_OutStockLockInfo? outStockLockInfo = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode);
                int taskNum = outStockLockInfo?.TaskNum ?? Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
                    int taskNum = lockInfoList.FirstOrDefault(x => x.PalletCode == palletCode)?.TaskNum ??
                                  Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
                Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, request.OutboundTargetLocation);
                if (generatedTasks.FirstOrDefault(x => x.PalletCode == stock.PalletCode) == null) generatedTasks.Add(task);
                    // è¿‡æ»¤é‡å¤ä»»åŠ¡ï¼ˆæŒ‡å®šåº“å­˜çš„æ‰˜ç›˜å·²ç”Ÿæˆï¼Œä¸ä¼šé‡å¤æ·»åŠ ï¼‰
                    if (generatedTasks.FirstOrDefault(x => x.PalletCode == palletCode) == null)
                        generatedTasks.Add(task);
                }
            }
            // è¿”回「指定库存任务+自动分配任务」的合并列表
            return (pickedDetails, generatedTasks, lockInfoList);
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务
        /// åˆ†é…æŒ‡å®šåº“存明细(核心优化:查询托盘物料总库存并传递)
        /// </summary>
        /// <param name="stockInfo"></param>
        /// <param name="taskType"></param>
        /// <param name="outStation"></param>
        /// <returns></returns>
        private (decimal ActualAllocatedQuantity, List<PickedStockDetailDTO> PickedDetails, List<Dt_OutStockLockInfo> LockInfoList) AllocateSpecifiedStockDetails(
            Dt_OutboundOrder outboundOrder,
            MaterielOutboundCalculationDTO materielCalc,
            PickingOutboundRequestDTO request,
            string factoryArea,
            decimal needAllocateQuantity)
        {
            List<PickedStockDetailDTO> pickedDetails = new List<PickedStockDetailDTO>();
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            decimal actualAllocated = 0;
            List<Dt_StockInfoDetail> specifiedStockDetails = _stockDetailRepository.QueryData(
                x => request.StockDetailIds.Contains(x.Id)
                && x.MaterielCode == materielCalc.MaterielCode
                && x.StockQuantity > 0
                && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动解锁));
            if (!specifiedStockDetails.Any())
            {
                throw new Exception($"指定库存明细ID [{string.Join(",", request.StockDetailIds)}] ä¸å­˜åœ¨æˆ–不可用");
            }
            List<int> stockIds = specifiedStockDetails.Select(x => x.StockId).Distinct().ToList();
            List<Dt_StockInfo> specifiedStocks = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id));
            Dictionary<int, Dt_StockInfo> stockMap = specifiedStocks.ToDictionary(x => x.Id);
            foreach (var stockDetail in specifiedStockDetails)
            {
                if (needAllocateQuantity <= 0) break;
                if (!stockMap.ContainsKey(stockDetail.StockId)) continue;
                Dt_StockInfo stock = stockMap[stockDetail.StockId];
                decimal availableQty = stockDetail.StockQuantity;
                decimal allocateQty = Math.Min(needAllocateQuantity, availableQty);
                if (allocateQty <= 0) continue;
                // ===== æ ¸å¿ƒä¼˜åŒ–1:查询该托盘下当前物料的总库存 =====
                decimal palletMaterielTotalStock = _stockDetailRepository.QueryData(
                    x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode
                ).Sum(x => x.StockQuantity); // è¯¥æ‰˜ç›˜è¯¥ç‰©æ–™çš„æ€»åº“存(而非本次分配量)
                var lockInfos = materielCalc.OutStockLockInfos
                    .Where(x => x.StockId == stock.Id && x.MaterielCode == materielCalc.MaterielCode)
                    .ToList();
                // ===== ä¼ é€’总库存到AllocateStockQuantity =====
                var allocateResult = AllocateStockQuantity(
                    stock, allocateQty, availableQty, outboundOrder, materielCalc.Details.First(),
                    request, lockInfos,
                    new Dictionary<int, List<Dt_StockInfoDetail>> { { stock.Id, new List<Dt_StockInfoDetail> { stockDetail } } },
                    palletMaterielTotalStock // æ–°å¢žï¼šä¼ å…¥æ‰˜ç›˜ç‰©æ–™æ€»åº“å­˜
                );
                if (allocateResult.ActualAllocatedQuantity > 0)
                {
                    pickedDetails.Add(new PickedStockDetailDTO
                    {
                        PalletCode = stock.PalletCode,
                        MaterielCode = materielCalc.MaterielCode,
                        OutboundQuantity = allocateResult.ActualAllocatedQuantity,
                        RemainingQuantity = Math.Max(0, availableQty - allocateResult.ActualAllocatedQuantity),
                        LocationCode = stock.LocationCode,
                        OutStockLockInfos = allocateResult.LockInfoList
                    });
                    actualAllocated += allocateResult.ActualAllocatedQuantity;
                    needAllocateQuantity -= allocateResult.ActualAllocatedQuantity;
                    lockInfoList.AddRange(allocateResult.LockInfoList);
                }
            }
            return (actualAllocated, pickedDetails, lockInfoList);
        }
        /// <summary>
        /// ä¸ºæŒ‡å®šåº“存生成任务(原有逻辑不变)
        /// </summary>
        private List<Dt_Task> GenerateTasksForSpecifiedStock(List<PickedStockDetailDTO> pickedDetails, string outboundTargetLocation)
        {
            List<Dt_Task> tasks = new List<Dt_Task>();
            var palletCodes = pickedDetails.Select(x => x.PalletCode).Distinct().ToList();
            foreach (var palletCode in palletCodes)
            {
                Dt_StockInfo stock = _stockInfoRepository.QueryFirst(x => x.PalletCode == palletCode);
                if (stock == null) continue;
                int taskNum = pickedDetails.First(x => x.PalletCode == palletCode)
                    .OutStockLockInfos.FirstOrDefault()?.TaskNum ??
                    Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt();
                Dt_Task task = GenerationOutTask(stock, TaskTypeEnum.Outbound, taskNum, outboundTargetLocation);
                if (tasks.FirstOrDefault(x => x.PalletCode == palletCode) == null)
                    tasks.Add(task);
            }
            return tasks;
        }
        /// <summary>
        /// æž„建库存查询条件(原有逻辑不变)
        /// </summary>
        private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
        {
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>()
                .Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0
                && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动解锁));
            if (!string.IsNullOrEmpty(materielCalc.SupplyCode))
                stockDetails = stockDetails.Where(x => x.SupplyCode == materielCalc.SupplyCode);
            if (!string.IsNullOrEmpty(materielCalc.WarehouseCode))
                stockDetails = stockDetails.Where(x => x.WarehouseCode == materielCalc.WarehouseCode);
            if (!string.IsNullOrEmpty(factoryArea))
                stockDetails = stockDetails.Where(x => x.FactoryArea == factoryArea);
            if (!string.IsNullOrEmpty(materielCalc.BatchNo))
                stockDetails = stockDetails.Where(x => x.BatchNo == materielCalc.BatchNo);
            List<Dt_StockInfoDetail> stockDetailList = stockDetails.ToList();
            List<string> locationCodes = _locationInfoRepository.QueryData(x =>
                (x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() || x.LocationStatus == LocationStatusEnum.Lock.ObjToInt())
                && x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).ToList();
            List<int> stockIds = stockDetailList.GroupBy(x => x.StockId).Select(x => x.Key).ToList();
            List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x =>
                stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt() || x.StockStatus == StockStatusEmun.出库锁定.ObjToInt())
                && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
            foreach (var stockInfo in stockInfos)
            {
                stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList();
            }
            return stockInfos;
        }
        /// <summary>
        /// æ‰¹é‡èŽ·å–æ‰˜ç›˜å¯ç”¨åº“å­˜ä¿¡æ¯ï¼ˆåŽŸæœ‰é€»è¾‘ä¸å˜ï¼‰
        /// </summary>
        private (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) GetBatchAvailableStockQuantities(
            MaterielOutboundCalculationDTO materielCalc, List<Dt_StockInfo> stockInfos)
        {
            List<int> stockIds = stockInfos.Select(x => x.Id).ToList();
            Dictionary<int, decimal> availableStockMap = new Dictionary<int, decimal>();
            Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = new Dictionary<int, List<Dt_OutStockLockInfo>>();
            List<Dt_OutStockLockInfo> allocatedData = materielCalc.OutStockLockInfos
                .Where(x => stockIds.Contains(x.StockId) && x.MaterielCode == materielCalc.MaterielCode).ToList();
            foreach (var stockInfo in stockInfos)
            {
                decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
                List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData
                    .Where(x => x.StockId == stockInfo.Id && x.MaterielCode == materielCalc.MaterielCode).ToList();
                decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity);
                availableStockMap[stockInfo.Id] = Math.Max(0, totalQuantity - allocatedQuantity);
                lockStockMap[stockInfo.Id] = outStockLockInfos;
            }
            return (availableStockMap, lockStockMap);
        }
        /// <summary>
        /// åˆ†é…åº“存(核心优化:OriginalQuantity赋值为托盘物料总库存)
        /// </summary>
        private (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) AllocateStockQuantity(
            Dt_StockInfo stockInfo, decimal allocateQuantity, decimal availableQuantity,
            Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail,
            PickingOutboundRequestDTO request, List<Dt_OutStockLockInfo> lockInfos,
            Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = null,
            decimal palletMaterielTotalStock = 0 // æ–°å¢žï¼šæ‰˜ç›˜ç‰©æ–™æ€»åº“存参数
        )
        {
            decimal actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity);
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            if (actualAllocatedQuantity > 0)
            {
                // æ£€æŸ¥ç›®æ ‡ä½ç½®ä¸€è‡´æ€§
                if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation)
                    && !string.Equals(lockInfos.First().OutboundTargetLocation, request.OutboundTargetLocation, StringComparison.OrdinalIgnoreCase))
                {
                    return (0, lockInfoList);
                }
                Dt_OutStockLockInfo? lockInfo = lockInfos.FirstOrDefault(x =>
                    x.StockId == stockInfo.Id && x.Status == OutLockStockStatusEnum.已分配.ObjToInt()
                    && x.PalletCode == stockInfo.PalletCode && x.OrderNo == outboundOrder.OrderNo);
                if (lockInfo != null)
                {
                    List<string> currentIds = lockInfo.OrderDetailIds?.Split(',').ToList() ?? new List<string>();
                    if (!currentIds.Contains(detail.Id.ToString()))
                    {
                        currentIds.Add(detail.Id.ToString());
                        lockInfo.OrderDetailIds = string.Join(",", currentIds);
                    }
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    lockInfo.AssignQuantity += actualAllocatedQuantity;
                    lockInfo.AllocatedQuantity = totalAllocatedQuantity;
                    // å­˜é‡é”å®šè®°å½•也更新OriginalQuantity为托盘总库存(可选,保持数据一致)
                    if (palletMaterielTotalStock > 0)
                        lockInfo.OriginalQuantity = palletMaterielTotalStock;
                    lockInfoList.Add(lockInfo);
                }
                else
                {
                    // ===== æ ¸å¿ƒä¼˜åŒ–2:OriginalQuantity赋值为托盘物料总库存 =====
                    decimal originalQuantity = palletMaterielTotalStock; // æ›¿ä»£åŽŸæœ‰å±€éƒ¨æ˜Žç»†æ•°é‡
                    List<string> allDetailIds = outboundOrder.Details.Where(x =>
                        x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode
                        && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode
                        && x.WarehouseCode == detail.WarehouseCode)
                        .Select(x => x.Id.ToString()).ToList();
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    lockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = request.OrderNo,
                        OrderDetailIds = string.Join(",", allDetailIds),
                        OrderType = outboundOrder.OrderType,
                        BatchNo = detail.BatchNo,
                        MaterielCode = detail.MaterielCode,
                        MaterielName = detail.MaterielName,
                        StockId = stockInfo.Id,
                        OrderQuantity = allDetailIds.Count > 1
                            ? outboundOrder.Details.Where(x =>
                                x.OrderId == outboundOrder.Id && x.MaterielCode == detail.MaterielCode
                                && x.BatchNo == detail.BatchNo && x.SupplyCode == detail.SupplyCode
                                && x.WarehouseCode == detail.WarehouseCode).Sum(x => x.OrderQuantity)
                            : detail.OrderQuantity,
                        OriginalQuantity = originalQuantity, // çŽ°åœ¨èµ‹å€¼ä¸ºæ‰˜ç›˜ç‰©æ–™æ€»åº“å­˜
                        AssignQuantity = actualAllocatedQuantity,
                        AllocatedQuantity = totalAllocatedQuantity,
                        LocationCode = stockInfo.LocationCode,
                        PalletCode = stockInfo.PalletCode,
                        Unit = detail.Unit,
                        OutboundTargetLocation = request.OutboundTargetLocation,
                        Status = OutLockStockStatusEnum.已分配.ObjToInt(),
                        SupplyCode = detail.SupplyCode,
                        WarehouseCode = detail.WarehouseCode,
                        FactoryArea = outboundOrder.FactoryArea,
                        TaskNum = Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(),
                        OrderDetailId = 0
                    };
                    lockInfoList.Add(lockInfo);
                }
            }
            return (actualAllocatedQuantity, lockInfoList);
        }
        /// <summary>
        /// è®¡ç®—该托盘累计已分配数量(原有逻辑不变)
        /// </summary>
        private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode)
        {
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
                x.StockId == stockId && x.MaterielCode == materielCode);
            return lockRecords?.Sum(x => x.AssignQuantity) ?? 0;
        }
        /// <summary>
        /// ç”Ÿæˆå‡ºåº“任务(原有逻辑不变)
        /// </summary>
        public Dt_Task GenerationOutTask(Dt_StockInfo stockInfo, TaskTypeEnum taskType, int taskNum, string outStation)
        {
            Dt_Task task = new()
            return new Dt_Task
            {
                CurrentAddress = stockInfo.LocationCode,
                Grade = 0,
@@ -538,240 +755,13 @@
                PalletType = stockInfo.PalletType,
                WarehouseId = stockInfo.WarehouseId,
            };
            return task;
        }
        /// <summary>
        /// æž„建库存查询条件(包含库存信息和库存明细)
        /// </summary>
        /// <param name="materielCalc"></param>
        /// <param name="factoryArea"></param>
        /// <returns></returns>
        private List<Dt_StockInfo> BuildStockQueryWithInfo(MaterielOutboundCalculationDTO materielCalc, string factoryArea)
        {
            // åŸºç¡€æŸ¥è¯¢æ¡ä»¶ï¼šç‰©æ–™ç¼–号、批次号(如果提供)、库存数量>0
            ISugarQueryable<Dt_StockInfoDetail> stockDetails = _stockDetailRepository.Db.Queryable<Dt_StockInfoDetail>().Where(x => x.MaterielCode == materielCalc.MaterielCode && x.StockQuantity > 0 && (x.Status == (int)StockStatusEmun.入库完成 || x.Status == (int)StockStatusEmun.手动解锁));
            // æ ¹æ®æ¡ä»¶æ·»åŠ ä¾›åº”å•†ç¼–å·åŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(materielCalc.SupplyCode))
            {
                stockDetails = stockDetails.Where(x => x.SupplyCode == materielCalc.SupplyCode);
            }
            // æ ¹æ®æ¡ä»¶æ·»åŠ ä»“åº“ç¼–å·åŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(materielCalc.WarehouseCode))
            {
                stockDetails = stockDetails.Where(x => x.WarehouseCode == materielCalc.WarehouseCode);
            }
            // æ ¹æ®æ¡ä»¶æ·»åŠ åŽ‚åŒºåŒ¹é…ï¼ˆä¸ä¸ºç©ºæ—¶æ‰éœ€è¦åŒ¹é…ï¼‰
            if (!string.IsNullOrEmpty(factoryArea))
            {
                stockDetails = stockDetails.Where(x => x.FactoryArea == factoryArea);
            }
            // æ ¹æ®æ‰¹æ¬¡å·è¿›è¡Œè¿‡æ»¤ï¼ˆå¦‚果提供)
            if (!string.IsNullOrEmpty(materielCalc.BatchNo))
            {
                stockDetails = stockDetails.Where(x => x.BatchNo == materielCalc.BatchNo);
            }
            List<Dt_StockInfoDetail> stockDetailList = stockDetails.ToList();
            // èŽ·å–å¯ç”¨è´§ä½ç¼–å·
            List<string> locationCodes = _locationInfoRepository.QueryData(x => (x.LocationStatus == LocationStatusEnum.InStock.ObjToInt() /*|| x.LocationStatus == LocationStatusEnum.Lock.ObjToInt()*/) && x.EnableStatus == EnableStatusEnum.Normal.ObjToInt()).Select(x => x.LocationCode).ToList();
            // èŽ·å–æ‰€æœ‰ç›¸å…³çš„åº“å­˜ä¿¡æ¯
            List<int> stockIds = stockDetailList.GroupBy(x => x.StockId).Select(x => x.Key).ToList();
            List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x => stockIds.Contains(x.Id) && (x.StockStatus == StockStatusEmun.入库完成.ObjToInt() /*|| x.StockStatus == StockStatusEmun.出库锁定.ObjToInt()*/) && !string.IsNullOrEmpty(x.LocationCode) && locationCodes.Contains(x.LocationCode));
            // åœ¨å†…存中关联数据
            foreach (var stockInfo in stockInfos)
            {
                stockInfo.Details = new List<Dt_StockInfoDetail>();
                stockInfo.Details = stockDetailList.Where(x => x.StockId == stockInfo.Id).ToList();
            }
            return stockInfos;
        }
        /// <summary>
        /// æ‰¹é‡èŽ·å–æ‰˜ç›˜å¯ç”¨åº“å­˜ä¿¡æ¯
        /// </summary>
        /// <param name="stockInfos">库存信息列表</param>
        /// <param name="materielCode">物料编号</param>
        /// <returns>返回值为(库存主键,可用数量)字典</returns>
        private (Dictionary<int, decimal> AvailableStockMap, Dictionary<int, List<Dt_OutStockLockInfo>> LockStockMap) GetBatchAvailableStockQuantities(MaterielOutboundCalculationDTO materielCalc, List<Dt_StockInfo> stockInfos)
        {
            List<int> stockIds = stockInfos.Select(x => x.Id).ToList();
            Dictionary<int, decimal> availableStockMap = new Dictionary<int, decimal>(); // å¯ç”¨åº“存数量
            Dictionary<int, List<Dt_OutStockLockInfo>> lockStockMap = new Dictionary<int, List<Dt_OutStockLockInfo>>(); // å·²é”å®šåº“存数量
            // æ‰¹é‡æŸ¥è¯¢å·²åˆ†é…æ•°é‡
            List<Dt_OutStockLockInfo> allocatedData = materielCalc.OutStockLockInfos.Where(x => stockIds.Contains(x.StockId) && x.MaterielCode == materielCalc.MaterielCode).ToList();
            foreach (var stockInfo in stockInfos)
            {
                // è®¡ç®—总库存数量
                decimal totalQuantity = stockInfo.Details.Sum(x => x.StockQuantity);
                List<Dt_OutStockLockInfo> outStockLockInfos = allocatedData.Where(x => x.StockId == stockInfo.Id && x.MaterielCode == materielCalc.MaterielCode).ToList();
                // è®¡ç®—已分配数量
                decimal allocatedQuantity = outStockLockInfos.Sum(x => x.AllocatedQuantity);
                availableStockMap[stockInfo.Id] = Math.Max(0, totalQuantity - allocatedQuantity);
                lockStockMap[stockInfo.Id] = outStockLockInfos;
            }
            return (availableStockMap, lockStockMap);
        }
        /// <summary>
        /// åˆ†é…åº“å­˜
        /// </summary>
        /// <param name="stockInfo">库存信息</param>
        /// <param name="allocateQuantity">要分配的数量</param>
        /// <param name="availableQuantity">可分配的数量</param>
        /// <param name="outboundOrder">出库单</param>
        /// <param name="detail">出库单明细</param>
        /// <param name="request"></param>
        /// <param name="lockInfos"></param>
        /// <param name="stockDetailMap"></param>
        /// <returns></returns>
        private (decimal ActualAllocatedQuantity, List<Dt_OutStockLockInfo> LockInfoList) AllocateStockQuantity(Dt_StockInfo stockInfo, decimal allocateQuantity, decimal availableQuantity, Dt_OutboundOrder outboundOrder, Dt_OutboundOrderDetail detail, PickingOutboundRequestDTO request, List<Dt_OutStockLockInfo> lockInfos, Dictionary<int, List<Dt_StockInfoDetail>> stockDetailMap = null)
        {
            decimal actualAllocatedQuantity = Math.Min(allocateQuantity, availableQuantity); // å®žé™…分配数量
            List<Dt_OutStockLockInfo> lockInfoList = new List<Dt_OutStockLockInfo>();
            if (actualAllocatedQuantity > 0)
            {
                //检查目标位置一致性:如果托盘已有锁定记录且目标位置不同,则不允许分配
                if (lockInfos.Any() && !string.IsNullOrEmpty(lockInfos.First().OutboundTargetLocation))
                {
                    if (!string.Equals(lockInfos.First().OutboundTargetLocation, request.OutboundTargetLocation, StringComparison.OrdinalIgnoreCase))
                    {
                        // æ‰˜ç›˜çš„目标位置与新请求的目标位置不同,不允许使用该托盘
                        return (0, lockInfoList);
                    }
                }
                Dt_OutStockLockInfo? lockInfo = lockInfos.FirstOrDefault(x => x.StockId == stockInfo.Id && x.Status == OutLockStockStatusEnum.已分配.ObjToInt() && x.PalletCode == stockInfo.PalletCode && x.OrderNo == outboundOrder.OrderNo);
                if (lockInfo != null)
                {
                    // è¿½åŠ å½“å‰æ˜Žç»†ID到OrderDetailIds字段(避免重复)
                    List<string> currentIds = lockInfo.OrderDetailIds?.Split(',').ToList() ?? new List<string>();
                    if (!currentIds.Contains(detail.Id.ToString()))
                    {
                        currentIds.Add(detail.Id.ToString());
                        lockInfo.OrderDetailIds = string.Join(",", currentIds);
                    }
                    // è®¡ç®—该托盘该物料的总累计已出库数量(跨所有单据)
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    // æ›´æ–°åˆ†é…å‡ºåº“量
                    decimal beforeAssignQuantity = totalAllocatedQuantity; // æœ¬æ¬¡åˆ†é…å‰çš„æ€»ç´¯è®¡é‡
                    lockInfo.AssignQuantity += actualAllocatedQuantity; // æœ¬æ¬¡åˆ†é…æ•°é‡
                    lockInfo.AllocatedQuantity = beforeAssignQuantity; // è®°å½•本次分配前的总累计量
                    lockInfoList.Add(lockInfo);
                }
                else
                {
                    // åˆ›å»ºæ–°çš„锁定记录(使用预加载的库存明细)
                    decimal originalQuantity = 0;
                    if (stockDetailMap?.ContainsKey(stockInfo.Id) == true)
                    {
                        originalQuantity = stockDetailMap[stockInfo.Id].Sum(x => x.StockQuantity);
                    }
                    // èŽ·å–è¯¥ç‰©æ–™åœ¨è¯¥è®¢å•ä¸­çš„æ‰€æœ‰æ˜Žç»†ID
                    List<string> allDetailIds = (outboundOrder.Details.Where(x =>
                        x.OrderId == outboundOrder.Id &&
                        x.MaterielCode == detail.MaterielCode &&
                        x.BatchNo == detail.BatchNo &&
                        x.SupplyCode == detail.SupplyCode &&
                        x.WarehouseCode == detail.WarehouseCode))
                        .Select(x => x.Id.ToString())
                        .ToList();
                    // è®¡ç®—该托盘该物料的总累计已出库数量(跨所有单据)
                    decimal totalAllocatedQuantity = CalcTotalAllocatedQuantity(lockInfos, stockInfo.Id, detail.MaterielCode);
                    lockInfo = new Dt_OutStockLockInfo
                    {
                        OrderNo = request.OrderNo,
                        OrderDetailIds = string.Join(",", allDetailIds), // è®°å½•该物料的所有明细ID
                        OrderType = outboundOrder.OrderType,
                        BatchNo = detail.BatchNo,
                        MaterielCode = detail.MaterielCode,
                        MaterielName = detail.MaterielName,
                        StockId = stockInfo.Id,
                        OrderQuantity = allDetailIds.SelectMany(id => allDetailIds).Count() > 1
                            ? (outboundOrder.Details.Where(x =>
                                x.OrderId == outboundOrder.Id &&
                                x.MaterielCode == detail.MaterielCode &&
                                x.BatchNo == detail.BatchNo &&
                                x.SupplyCode == detail.SupplyCode &&
                                x.WarehouseCode == detail.WarehouseCode))
                                .Sum(x => x.OrderQuantity)
                            : detail.OrderQuantity, // å¦‚果只有一个明细,使用明细数量
                        OriginalQuantity = originalQuantity,
                        AssignQuantity = actualAllocatedQuantity, // æœ¬æ¬¡åˆ†é…æ•°é‡
                        AllocatedQuantity = totalAllocatedQuantity, // æœ¬æ¬¡åˆ†é…å‰çš„æ€»ç´¯è®¡å·²å‡ºåº“数量(跨所有单据)
                        LocationCode = stockInfo.LocationCode,
                        PalletCode = stockInfo.PalletCode,
                        Unit = detail.Unit,
                        OutboundTargetLocation = request.OutboundTargetLocation,
                        Status = OutLockStockStatusEnum.已分配.ObjToInt(),
                        SupplyCode = detail.SupplyCode,
                        WarehouseCode = detail.WarehouseCode,
                        FactoryArea = outboundOrder.FactoryArea,
                        TaskNum = Db.Ado.GetScalar($"SELECT NEXT VALUE FOR SeqTaskNum").ObjToInt(),
                        OrderDetailId = 0 // æœªå…³è”具体明细ID
                    };
                    lockInfoList.Add(lockInfo);
                }
            }
            return (actualAllocatedQuantity, lockInfoList);
        }
        /// <summary>
        /// è®¡ç®—该托盘累计已分配数量
        /// </summary>
        /// <param name="lockInfos"></param>
        /// <param name="stockId"></param>
        /// <param name="materielCode"></param>
        /// <returns></returns>
        private decimal CalcTotalAllocatedQuantity(List<Dt_OutStockLockInfo> lockInfos, int stockId, string materielCode)
        {
            // æŸ¥è¯¢è¯¥æ‰˜ç›˜è¯¥ç‰©æ–™åœ¨æ‰€æœ‰é”å®šè®°å½•中的最大已分配数量
            List<Dt_OutStockLockInfo> lockRecords = _outboundLockInfoRepository.QueryData(x =>
                x.StockId == stockId &&
                x.MaterielCode == materielCode);
            if (lockRecords == null || !lockRecords.Any())
            {
                return 0;
            }
            // è¿”回累计已分配数量
            return lockRecords.Sum(x => x.AssignQuantity);
        }
        /// <summary>
        /// æ›´æ–°å‡ºåº“单状态
        /// </summary>
        public bool UpdateOutboundOrderStatus(string orderNo, int status)
        {
            try
            {
                Dt_OutboundOrder outboundOrder = _outboundRepository.QueryFirst(x => x.OrderNo == orderNo);
                if (outboundOrder == null) return false;
                outboundOrder.OrderStatus = status;
                _outboundRepository.UpdateData(outboundOrder);
                return true;
@@ -782,22 +772,12 @@
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="palletCodes"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public bool UpdateStockStatus(List<string> palletCodes, int status)
        {
            try
            {
                List<Dt_StockInfo> stockInfos = _stockInfoRepository.QueryData(x => palletCodes.Contains(x.PalletCode));
                stockInfos.ForEach(stockInfo =>
                {
                    stockInfo.StockStatus = status;
                });
                stockInfos.ForEach(stockInfo => stockInfo.StockStatus = status);
                _stockInfoRepository.UpdateData(stockInfos);
                return true;
            }
@@ -807,22 +787,12 @@
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="locationCodes"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public bool UpdateLocationStatus(List<string> locationCodes, int status)
        {
            try
            {
                List<Dt_LocationInfo> locationInfos = _locationInfoRepository.QueryData(x => locationCodes.Contains(x.LocationCode));
                locationInfos.ForEach(x =>
                {
                    x.LocationStatus = status;
                });
                locationInfos.ForEach(x => x.LocationStatus = status);
                _locationInfoRepository.UpdateData(locationInfos);
                return true;
            }
@@ -832,21 +802,14 @@
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="outStockLockInfos"></param>
        /// <returns></returns>
        public bool UpdateOutStockLockInfo(List<Dt_OutStockLockInfo> outStockLockInfos)
        {
            try
            {
                List<Dt_OutStockLockInfo> updateData = outStockLockInfos.Where(x => x.Id > 0).ToList();
                _outboundLockInfoRepository.UpdateData(updateData);
                List<Dt_OutStockLockInfo> addData = outStockLockInfos.Where(x => x.Id <= 0).ToList();
                _outboundLockInfoRepository.AddData(addData);
                return true;
            }
            catch
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs
@@ -523,24 +523,25 @@
                    if (relevantDetails.Any())
                    {
                        var firstDetail = relevantDetails.First();
                        var useableQuantity = relevantDetails.Sum(d => d.StockQuantity - d.OutboundQuantity);
                        foreach (var item in relevantDetails)
                        {
                        result.Add(new StockSelectViewDTO
                        {
                            LocationCode = stock.LocationCode,
                            MaterielCode = firstDetail.MaterielCode,
                            MaterielName = firstDetail.MaterielName,
                            BatchNo = orderDetail.BatchNo,
                            SupplyCode = orderDetail.SupplyCode,
                            Barcode = firstDetail.Barcode,
                                MaterielCode = item.MaterielCode,
                                MaterielName = item.MaterielName,
                                BatchNo = item.BatchNo,
                                SupplyCode = item.SupplyCode,
                                Barcode = item.Barcode,
                            PalletCode = stock.PalletCode,
                            UseableQuantity = useableQuantity,
                                UseableQuantity = item.StockQuantity,
                            StockCreateDate = stock.CreateDate,
                            StockId = stock.Id,
                                StockId = item.Id,
                            OrderDetailId = orderDetail.Id // å…³è”到具体的出库单明细
                        });
                    }
                    }
                }
            }
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Allocate/AllocateOrderController.cs
@@ -92,6 +92,10 @@
                            ValidDate = barcodeDto.validDate,
                            BoxSN = barcodeDto.BoxSN
                        };
                        if (businessType == BusinessTypeEnum.智仓调外部仓库 || businessType == BusinessTypeEnum.智仓调智仓)
                        {
                            orderDetail.WarehouseCode = model.fromWarehouse;
                        }
                        allocateOrder.Details.Add(orderDetail);
                    }
                }
@@ -108,6 +112,10 @@
                        BarcodeQty=detailDto.Qty,
                        MaterielName = materialName
                    };
                    if (businessType == BusinessTypeEnum.智仓调外部仓库 || businessType == BusinessTypeEnum.智仓调智仓)
                    {
                        orderDetail.WarehouseCode = model.fromWarehouse;
                    }
                    allocateOrder.Details.Add(orderDetail);
                }
                allocateOrder.Details.AddRange(allocateOrder.Details);
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/InboundController.cs
@@ -41,6 +41,17 @@
        }
        /// <summary>
        /// ç›˜ç‚¹ç»„盘
        /// </summary>
        /// <param name="palletDto"></param>
        /// <returns></returns>
        [HttpPost, Route("StockTakeGroupPallet"), AllowAnonymous]
        public async Task<WebResponseContent> StockTakeGroupPallet([FromBody] GroupPalletDto palletDto)
        {
            return await Service.StockTakeGroupPallet(palletDto);
        }
        /// <summary>
        /// å…¥åº“分批回传MES
        /// </summary>
        /// <param name="palletDto"></param>
ÏîÄ¿´úÂë/WMSÎÞ²Ö´¢°æ/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Inbound/TakeStockOrderController.cs
@@ -76,9 +76,18 @@
        /// æ‚发杂收单处理
        /// </summary>
        [HttpPost, HttpGet, Route("DocumentReconciliation"), AllowAnonymous]
        public WebResponseContent DocumentReconciliation(int orderId,int id)
        public WebResponseContent DocumentReconciliation(string barcode)
        {
            return Service.DocumentReconciliation(orderId, id);
            return Service.DocumentReconciliation(barcode);
        }
        /// <summary>
        /// ç›˜ç‚¹ç»„盘
        /// </summary>
        [HttpPost, HttpGet, Route("StockTakeGroupPallet"), AllowAnonymous]
        public WebResponseContent StockTakeGroupPallet(string barcode, string boxNo)
        {
            return Service.StockTakeGroupPallet(barcode,boxNo);
        }
    }
}