gaoluyang
昨天 d39019790b7e15ab515ace9937c5e5d19eb31837
军泰伟业
1.销售退货逻辑完善与联调
已添加4个文件
已修改1个文件
1071 ■■■■■ 文件已修改
src/api/customerService/index.js 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/salesManagement/returnOrder.js 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/components/detailDia.vue 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/components/formDia.vue 552 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/salesManagement/returnOrder/index.vue 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/customerService/index.js
@@ -59,27 +59,17 @@
  })
}
// å”®åŽå¤„理-附件删除
export function afterSalesServiceFileDel(ids) {
export function afterSalesServiceFileDel(id) {
  return request({
    url: '/afterSalesService/file/del',
    url: `/afterSalesService/file/del/${id}`,
    method: 'delete',
    data: ids,
  })
}
// å”®åŽå¤„理-维修记录列表
export function afterSalesServiceRepairListPage(query) {
  return request({
    url: '/afterSalesService/repair/listPage',
    method: 'get',
    params: query,
  })
}
// ä¸´æœŸå”®åŽç®¡ç†-分页查询
export function expiryAfterSalesListPage(query) {
  return request({
    url: '/expiryAfterSales/listPage',
    url: '/afterSalesNearExpiryService/listPage',
    method: 'get',
    params: query,
  })
@@ -88,7 +78,7 @@
// ä¸´æœŸå”®åŽç®¡ç†-新增
export function expiryAfterSalesAdd(query) {
  return request({
    url: '/expiryAfterSales/add',
    url: '/afterSalesNearExpiryService/add',
    method: 'post',
    data: query,
  })
@@ -97,17 +87,46 @@
// ä¸´æœŸå”®åŽç®¡ç†-更新
export function expiryAfterSalesUpdate(query) {
  return request({
    url: '/expiryAfterSales/update',
    url: '/afterSalesNearExpiryService/update',
    method: 'post',
    data: query,
  })
}
// ä¸´æœŸå”®åŽç®¡ç†-删除
export function expiryAfterSalesDelete(query) {
export function expiryAfterSalesDelete(ids) {
  return request({
    url: '/expiryAfterSales/delete',
    url: '/afterSalesNearExpiryService/delete?ids=' + ids,
    method: 'delete',
    data: query,
  })
}
}
// æŸ¥è¯¢æ‰€æœ‰å®¢æˆ·ä¿¡æ¯
// /basic/customer/list
export function getAllCustomerList(query) {
    return request({
        url: '/basic/customer/list',
        method: 'get',
        params: query,
    })
}
// æ ¹æ®å®¢æˆ·æŸ¥è¯¢é”€å”®è®¢å•号
// afterSalesService/listSalesLedger
export function getSalesLedger(query) {
    return request({
        url: '/afterSalesService/listSalesLedger',
        method: 'get',
        params: query,
    })
}
// æ ¹æ®é”€å”®è®¢å•号查询销售订单详情
// afterSalesService/count
export function getSalesLedgerDetail(query) {
    return request({
        url: '/afterSalesService/count',
        method: 'get',
        params: query,
    })
}
src/api/salesManagement/returnOrder.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
import request from "@/utils/request";
// é”€å”®é€€è´§-查询
// /returnManagement/listPage
export function returnManagementList(query) {
  return request({
    url: "/returnManagement/listPage",
    method: "get",
    params: query,
  });
}
// é”€å”®é€€è´§-添加
// /returnManagement/add
export function returnManagementAdd(data) {
  return request({
    url: "/returnManagement/add",
    method: "post",
    data: data,
  });
}
// é”€å”®é€€è´§-修改
// /returnManagement/update
export function returnManagementUpdate(data) {
  return request({
    url: "/returnManagement/update",
    method: "post",
    data: data,
  });
}
// é”€å”®é€€è´§-删除
// /returnManagement/del
export function returnManagementDel(data) {
  return request({
    url: "/returnManagement/del",
    method: "delete",
    data,
  });
}
// é”€å”®é€€è´§-查询
// /returnManagement/getById
export function returnManagementGetById(query) {
  return request({
    url: "/returnManagement/getById",
    method: "get",
    params: query,
  });
}
// é”€å”®é€€è´§-根据出库单查询销售订单以及产品信息
// /returnManagement/getByShippingId
export function returnManagementGetByShippingId(query) {
  return request({
    url: "/returnManagement/getByShippingId",
    method: "get",
    params: query,
  });
}
// é€šè¿‡å®¢æˆ·åç§°æŸ¥è¯¢
// /shippingInfo/getByCustomerName
export function getSalesLedger(query) {
    return request({
        url: '/shippingInfo/getByCustomerName',
        method: 'get',
        params: query,
    })
}
// å¤„理
// /returnManagement/handle
export function returnManagementHandle(data) {
  return request({
    url: "/returnManagement/handle",
    method: "get",
    params: data,
  });
}
src/views/salesManagement/returnOrder/components/detailDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,146 @@
<template>
  <el-dialog v-model="dialogVisible" title="退货单详情" width="90%" @close="closeDia">
    <div v-loading="loading">
      <span class="descriptions">基本信息</span>
      <el-descriptions :column="4" border>
        <el-descriptions-item label="退货单号">{{ detail.returnNo }}</el-descriptions-item>
        <el-descriptions-item label="单据状态">
          <el-tag :type="getStatusType(detail.status)">{{ getStatusText(detail.status) }}</el-tag>
        </el-descriptions-item>
        <el-descriptions-item label="客户名称">{{ detail.customerName }}</el-descriptions-item>
        <el-descriptions-item label="销售单号">{{ detail.salesContractNo }}</el-descriptions-item>
        <el-descriptions-item label="业务员">{{ detail.salesman }}</el-descriptions-item>
        <el-descriptions-item label="关联出库单号">{{ detail.shippingNo }}</el-descriptions-item>
        <el-descriptions-item label="项目名称">{{ detail.projectName }}</el-descriptions-item>
        <el-descriptions-item label="制单人">{{ detail.maker }}</el-descriptions-item>
        <el-descriptions-item label="制单时间">{{ detail.makeTime }}</el-descriptions-item>
        <el-descriptions-item label="退货原因">{{ detail.returnReason }}</el-descriptions-item>
        <el-descriptions-item label="退款总额">{{ detail.refundAmount }}</el-descriptions-item>
      </el-descriptions>
      <div style="padding-top: 20px">
        <span class="descriptions">产品列表</span>
        <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData" />
      </div>
    </div>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="closeDia">关闭</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { returnManagementGetById, returnManagementGetByShippingId } from "@/api/salesManagement/returnOrder.js";
const dialogVisible = ref(false);
const loading = ref(false);
const detail = ref({});
const tableData = ref([]);
const availableProducts = ref([]);
const tableColumn = [
  {align: "center", label: "产品大类", prop: "productCategory"},
  {align: "center", label: "规格型号", prop: "specificationModel"},
  {align: "center", label: "单位", prop: "unit", width: 80},
  {align: "center", label: "总数量", prop: "quantity", width: 120},
  {align: "center", label: "已退货数量", prop: "totalReturnNum", width: 120},
  {align: "center", label: "未退货数量", prop: "unQuantity", width: 120},
  {align: "center", label: "退货数量", prop: "returnQuantity", width: 120},
  {align: "center", label: "退货产品单价", prop: "price", width: 120},
  {align: "center", label: "退货产品金额", prop: "amount", width: 120},
  {align: "center", label: "是否有质量问题", prop: "isQuality", width: 140, formatData: (v) => ({ "1": "是", "2": "否" }[String(v)] ?? v)},
  {align: "center", label: "备注", prop: "remark", width: 150},
];
const getStatusType = (status) => {
  const statusMap = {
    0: "warning",
    1: "success"
  };
  return statusMap[status] || "info";
};
const getStatusText = (status) => {
  const statusMap = {
    0: "待处理",
    1: "已处理"
  };
  return statusMap[status] || "未知";
};
const openDialog = async (row) => {
  if (!row?.id) return;
  dialogVisible.value = true;
  loading.value = true;
  try {
    const res = await returnManagementGetById({ returnManagementId: row.id });
    detail.value = res?.data ?? res ?? {};
    if (detail.value.shippingId) {
      const productRes = await returnManagementGetByShippingId({ shippingId: detail.value.shippingId });
      if (productRes.code === 200) {
        availableProducts.value = productRes.data.productDtoData || [];
      }
    }
    const list =
      detail.value?.returnSaleProducts ||
        detail.value?.returnSaleProductList ||
        detail.value?.returnSaleProductDtoData ||
        [];
    tableData.value = Array.isArray(list) ? list.map(raw => {
      const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id;
      const product = availableProducts.value.find((p) => p.id === productId);
      const normalized = {
        ...raw,
        id: productId,
        returnQuantity: Number(raw?.num ?? raw?.returnQuantity ?? 0),
        price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0),
        amount: Number(raw?.amount ?? 0).toFixed(2),
        isQuality: raw?.isQuality ?? 2,
        remark: raw?.remark ?? "",
      };
      return product ? { ...product, ...normalized } : normalized;
    }) : [];
  } catch (e) {
    console.error("Failed to load detail", e);
  } finally {
    loading.value = false;
  }
};
const closeDia = () => {
  dialogVisible.value = false;
  detail.value = {};
  tableData.value = [];
  availableProducts.value = [];
};
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.descriptions {
  margin-bottom: 20px;
  display: inline-block;
  font-size: 1rem;
  font-weight: 600;
  padding-left: 12px;
  position: relative;
}
.descriptions::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 1rem;
  background-color: #002FA7;
  border-radius: 2px;
}
</style>
src/views/salesManagement/returnOrder/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,552 @@
<template>
  <div>
    <el-dialog v-model="dialogFormVisible" :title="operationType === 'edit' ? '编辑退货单' : '新增退货单'" width="90%" @close="closeDia">
      <div>
        <span class="descriptions">基本信息</span>
        <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
          <el-row :gutter="30">
            <el-col :span="4">
              <el-form-item label="退货单号:" prop="returnNo">
                <el-input
                  :disabled="operationType === 'edit' || form.returnNoCheckbox"
                  v-model="form.returnNo"
                  placeholder="使用系统编号"
                  class="input-with-select"
                >
                  <template v-if="operationType !== 'edit'" #append>
                    <el-checkbox v-model="form.returnNoCheckbox" @change="handleReturnNoCheckboxChange"></el-checkbox>
                  </template>
                </el-input>
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="客户名称:" prop="customerId">
                <el-select v-model="form.customerId" filterable placeholder="请选择客户" @change="customerNameChange">
                  <el-option
                    v-for="item in customerNameOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.id"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="关联出库单号:" prop="shippingId">
                <el-select v-model="form.shippingId" filterable placeholder="请选择出库单号" @change="outboundNoChange">
                  <el-option
                    v-for="item in outboundOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="制单人:" prop="maker">
                <el-select v-model="form.maker" filterable placeholder="请选择制单人">
                  <el-option v-for="u in userOptions" :key="u.value" :label="u.label" :value="u.value" />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="制单时间:" prop="makeTime">
                <el-date-picker v-model="form.makeTime" type="datetime" style="width:100%" value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" />
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="状态:" prop="status">
                <el-select v-model="form.status" placeholder="请选择状态">
                  <el-option label="待处理" :value="0" />
                  <el-option label="已处理" :value="1" />
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="退货原因:" prop="returnReason">
                <el-input v-model="form.returnReason" placeholder="请输入退货原因" />
              </el-form-item>
            </el-col>
            <el-col :span="4">
              <el-form-item label="退款总额:" prop="refundAmount">
                <el-input v-model="form.refundAmount" disabled placeholder="自动计算" />
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
        <hr>
        <div style="padding-top: 20px">
          <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
            <span class="descriptions" style="margin-bottom:0">产品列表</span>
            <el-button type="primary" @click="openProductSelection" :disabled="!form.shippingId">添加产品</el-button>
          </div>
          <PIMTable :isShowPagination="false" rowKey="id" :column="tableColumn" :tableData="tableData">
            <template #returnQuantity="{ row }">
              <el-input
                v-model="row.returnQuantity"
                style="width:100px"
                placeholder="请输入"
                type="number"
                @input="(val) => handleReturnQuantityChange(val, row)"
              />
            </template>
            <template #price="{ row }">
              <el-input
                v-model="row.price"
                style="width:100px"
                placeholder="请输入"
                type="number"
                @input="(val) => handlePriceChange(val, row)"
              />
            </template>
            <template #amount="{ row }">
              <el-input
                v-model="row.amount"
                style="width:100px"
                placeholder="自动计算"
                type="number"
                disabled
              />
            </template>
            <template #isQuality="{ row }">
              <el-select v-model="row.isQuality" placeholder="请选择" style="width:120px">
                <el-option label="是" :value="1" />
                <el-option label="否" :value="2" />
              </el-select>
            </template>
            <template #remark="{ row }">
              <el-input
                v-model="row.remark"
                style="width:130px"
                placeholder="请输入"
              />
            </template>
            <template #action="{ row, index }">
              <el-button type="danger" link @click="deleteRow(index)">删除</el-button>
            </template>
          </PIMTable>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <el-dialog v-model="productSelectionVisible" title="选择产品" width="70%" append-to-body>
      <el-table
        :data="availableProducts"
        style="width: 100%"
        @selection-change="handleSelectionChange"
        ref="productTableRef"
        row-key="id"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" prop="productCategory" label="产品大类" />
        <el-table-column align="center" prop="specificationModel" label="规格型号" />
        <el-table-column align="center" prop="unit" label="单位" />
        <el-table-column align="center" prop="quantity" label="总数量" />
        <el-table-column align="center" prop="unQuantity" label="未退货数量" />
        <el-table-column align="center" label="已退货数量">
          <template #default="{ row }">{{ calcAlreadyReturned(row) }}</template>
        </el-table-column>
      </el-table>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="confirmProductSelection">确认添加</el-button>
          <el-button @click="productSelectionVisible = false">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { reactive, ref, toRefs, getCurrentInstance } from "vue";
import { returnManagementAdd, returnManagementUpdate, returnManagementGetByShippingId, getSalesLedger, returnManagementGetById } from "@/api/salesManagement/returnOrder.js";
import { getAllCustomerList } from "@/api/customerService/index.js";
import useUserStore from "@/store/modules/user.js";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import { listProject } from "@/api/oaSystem/projectManagement.js";
const { proxy } = getCurrentInstance();
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('');
const formRef = ref(null);
const userStore = useUserStore();
const data = reactive({
  form: {
    returnNoCheckbox: true,
    returnNo: "",
    customerId: "",
    shippingId: "",
    projectId: "",
    maker: "",
    makeTime: "",
    status: 0,
    returnReason: "",
    refundAmount: "",
  },
  rules: {
    returnNo: [{
      validator: (rule, value, callback) => {
        if (form.value.returnNoCheckbox) return callback();
        if (!value) return callback(new Error("请输入退货单号"));
        callback();
      }, trigger: "blur"
    }],
    customerId: [{ required: true, message: "请选择客户", trigger: "change" }],
    shippingId: [{ required: true, message: "请选择关联出库单号", trigger: "change" }],
  }
});
const { form, rules } = toRefs(data);
const calcAlreadyReturned = (row) => {
  const total = Number(row?.quantity ?? row?.totalQuantity ?? row?.totalReturnNum ?? 0);
  const un = Number(row?.unQuantity ?? 0);
  if (!Number.isFinite(total) || !Number.isFinite(un)) return 0;
  return Math.max(total - un, 0);
};
const tableColumn = ref([
  {align: "center", label: "产品大类", prop: "productCategory" },
  {align: "center", label: "规格型号", prop: "specificationModel" },
  {align: "center", label: "单位", prop: "unit", width: 80 },
  {align: "center", label: "总数量", prop: "quantity", width: 120 },
  {align: "center", label: "已退货数量", prop: "totalReturnNum", width: 120 },
  {align: "center", label: "未退货数量", prop: "unQuantity", width: 120 },
  {align: "center", label: "退货数量", prop: "returnQuantity", dataType: "slot", slot: "returnQuantity", width: 120 },
  {align: "center", label: "退货产品单价", prop: "price", dataType: "slot", slot: "price", width: 120 },
  {align: "center", label: "退货产品金额", prop: "amount", dataType: "slot", slot: "amount", width: 120 },
  {align: "center", label: "是否有质量问题", prop: "isQuality", dataType: "slot", slot: "isQuality", width: 140 },
  {align: "center", label: "备注", prop: "remark", dataType: "slot", slot: "remark", width: 150 },
  {align: "center", label: "操作" , prop: "action", dataType: "slot", slot: "action", width: 120 },
]);
const tableData = ref([]);
const customerNameOptions = ref([]);
const outboundOptions = ref([]);
const userOptions = ref([]);
const projectOptions = ref([]);
const deleteRow = (index) => {
  tableData.value.splice(index, 1);
};
const normalizeDetailRow = (raw) => {
  const productId = raw?.returnSaleLedgerProductId ?? raw?.saleLedgerProductId ?? raw?.id;
  const returnSaleProductId = raw?.returnSaleProductId ?? raw?.id;
  const num = Number(raw?.num ?? raw?.returnQuantity ?? 0);
  return {
    ...raw,
    id: productId,
    returnSaleProductId,
    returnSaleLedgerProductId: productId,
    productModelId: raw?.productModelId,
    num,
    returnQuantity: Number.isFinite(num) ? num : 0,
    price: Number(raw?.taxInclusiveUnitPrice ?? raw?.price ?? 0),
    amount: Number(raw?.amount ?? 0).toFixed(2),
    isQuality: raw?.isQuality ?? 2,
    remark: raw?.remark ?? "",
  };
};
const setFormForEdit = async (row) => {
  const res = await returnManagementGetById({ returnManagementId: row?.id });
  console.log("res", res);
  const detail = res?.data ?? res ?? {};
  Object.assign(form.value, detail);
  form.value.returnNoCheckbox = true;
  if (form.value.customerId) {
    await customerNameChange(form.value.customerId, false);
  }
  if (form.value.shippingId) {
    await outboundNoChange(form.value.shippingId, false);
  }
  const list =
    detail?.returnSaleProducts ||
    detail?.returnSaleProductList ||
    detail?.returnSaleProductDtoData ||
    [];
  tableData.value = Array.isArray(list)
    ? list.map((raw) => {
        const normalized = normalizeDetailRow(raw);
        const product = availableProducts.value.find((p) => p.id === normalized.id);
        return product ? { ...product, ...normalized } : normalized;
      })
    : [];
  calculateTotalRefund();
};
const openDialog = async (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
  proxy.resetForm("formRef");
  await Promise.all([initCustomers(), initUsers(), initProjects()]);
  if (type === "edit") {
    await setFormForEdit(row);
  } else {
    tableData.value = [];
    Object.assign(form.value, {
      returnNoCheckbox: true,
      returnNo: "",
      customerId: "",
      shippingId: "",
      projectId: "",
      maker: "",
      makeTime: "",
      status: 0,
      returnReason: "",
      refundAmount: "",
    });
    form.value.maker = userStore.nickName || userStore.name || "";
    form.value.makeTime = new Date().toISOString().replace('T', ' ').split('.')[0]; // Default to now
    form.value.status = 0; // Default status
  }
};
const submitForm = () => {
  proxy.$refs["formRef"].validate(valid => {
    if (!valid) return;
    const returnSaleProducts = (tableData.value || []).map(el => ({
      returnSaleLedgerProductId: el.returnSaleLedgerProductId ?? el.id,
      productModelId: el.productModelId,
      unit: el.unit,
      num: Number(el.num ?? el.returnQuantity ?? 0),
      price: Number(el.price ?? 0),
      amount: Number(el.amount ?? 0),
      isQuality: el.isQuality ?? 2,
      remark: el.remark ?? "",
      id: operationType.value === "edit" ? (el.returnSaleProductId ?? "") : ""
    }));
    const payload = { ...form.value, returnSaleProducts };
    delete payload.returnNoCheckbox;
    if (operationType.value === "add" && form.value.returnNoCheckbox) delete payload.returnNo;
    if (operationType.value === "add") {
      returnManagementAdd(payload).then(() => {
        proxy.$modal.msgSuccess("新增成功");
        closeDia();
      });
    } else {
      returnManagementUpdate(payload).then(() => {
        proxy.$modal.msgSuccess("修改成功");
        closeDia();
      });
    }
  });
};
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close');
};
const initCustomers = async () => {
  const res = await getAllCustomerList({});
  if (res?.records) {
    customerNameOptions.value = res.records.map(item => ({
      label: item.customerName,
      value: item.customerName, // Keep value as name if needed for other logic, but request says customerId
      id: item.id,
      code: item.customerCode
    }));
  }
};
const initUsers = async () => {
  const res = await userListNoPageByTenantId();
  if (res?.data) {
    userOptions.value = res.data.map(u => ({ label: u.nickName || u.userName, value: u.nickName || u.userName }));
  }
};
const initProjects = async () => {
  try {
    const res = await listProject({ pageSize: 1000 });
    if (res?.rows) {
      projectOptions.value = res.rows.map(p => ({ label: p.projectName, value: p.id }));
    }
  } catch (e) {
    console.error("Failed to load projects", e);
  }
};
const handleReturnNoCheckboxChange = (checked) => {
  if (checked) form.value.returnNo = "";
  formRef.value?.validateField('returnNo');
};
const customerNameChange = async (val, clearDownstream = true) => {
  // val is customerId now
  if (clearDownstream) {
    form.value.shippingId = "";
    outboundOptions.value = [];
  }
  // Find customer name for getSalesLedger if it requires name
  const customer = customerNameOptions.value.find(c => c.id === val);
  if (!customer) return;
  // Assuming getSalesLedger takes customerName. If it takes ID, adjust accordingly.
  // Previous code used customerName. Let's try passing customerName.
  getSalesLedger({
    customerName: customer.label,
  }).then(res => {
    if(res.code === 200){
      outboundOptions.value = res.data.map(item => ({
        label: item.salesContractNo, // Or whatever the outbound number field is
        value: item.id,
      }))
    }
  })
};
const outboundNoChange = async (val, clearTable = true) => {
  // val is shippingId
  let res = await returnManagementGetByShippingId({ shippingId: val });
  if(res.code === 200){
    // If backend returns project info, set it
    if (res.data.projectId) form.value.projectId = res.data.projectId;
    // Store available products for selection
    availableProducts.value = res.data.productDtoData || [];
    if (clearTable) tableData.value = [];
  }
};
const handleReturnQuantityChange = (val, row) => {
  if (val === "" || val === null) return;
  const max = row.unQuantity === undefined || row.unQuantity === null ? Infinity : Number(row.unQuantity || 0);
  const current = Number(val);
  if (current > max) {
    proxy.$nextTick(() => {
      row.returnQuantity = max;
      row.num = max;
    });
    proxy.$modal.msgWarning(`退货数量不能超过未退货数量(${max})`);
  } else if (current < 0) {
    proxy.$nextTick(() => {
      row.returnQuantity = 0;
      row.num = 0;
    });
  } else {
    row.num = current;
  }
  calculateRowAmount(row);
  calculateTotalRefund();
};
const handlePriceChange = (val, row) => {
  if (val === "" || val === null) {
    row.price = 0;
  }
  calculateRowAmount(row);
  calculateTotalRefund();
};
const calculateRowAmount = (row) => {
  const quantity = Number(row.returnQuantity || 0);
  const price = Number(row.price || 0);
  row.amount = (quantity * price).toFixed(2);
};
const calculateTotalRefund = () => {
  const total = tableData.value.reduce((sum, row) => {
    return sum + Number(row.amount || 0);
  }, 0);
  form.value.refundAmount = total.toFixed(2);
};
const availableProducts = ref([]);
const productSelectionVisible = ref(false);
const selectedProducts = ref([]);
const openProductSelection = () => {
  productSelectionVisible.value = true;
  // Pre-select items already in tableData
  proxy.$nextTick(() => {
    if (proxy.$refs.productTableRef) {
      proxy.$refs.productTableRef.clearSelection();
      availableProducts.value.forEach(row => {
        if (tableData.value.some(item => item.id === row.id)) {
          proxy.$refs.productTableRef.toggleRowSelection(row, true);
        }
      });
    }
  });
};
const handleSelectionChange = (val) => {
  selectedProducts.value = val;
};
// Removed checkSelectable to allow toggling existing items
const confirmProductSelection = () => {
  const newTableData = [];
  selectedProducts.value.forEach(product => {
    const existing = tableData.value.find(item => item.id === product.id);
    if (existing) {
      newTableData.push(existing);
    } else {
      newTableData.push({
        ...product,
        returnSaleLedgerProductId: product.id,
        productModelId: product.productModelId,
        returnQuantity: 0,
        num: 0,
        price: Number(product.taxInclusiveUnitPrice ?? 0),
        amount: "0.00",
        isQuality: 2,
        remark: "",
        productName: product.productName,
        specificationModel: product.specificationModel,
        unit: product.unit,
        quantity: product.quantity,
        totalReturnNum: product.totalReturnNum,
        unQuantity: product.unQuantity
      });
    }
  });
  tableData.value = newTableData;
  productSelectionVisible.value = false;
};
defineExpose({ openDialog });
</script>
<style scoped lang="scss">
.descriptions {
  margin-bottom: 20px;
  display: inline-block;
  font-size: 1rem;
  font-weight: 600;
  padding-left: 12px;
  position: relative;
}
.descriptions::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 4px;
  height: 1rem;
  background-color: #002FA7;
  border-radius: 2px;
}
</style>
src/views/salesManagement/returnOrder/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,234 @@
<template>
  <div class="app-container">
    <div class="search-wrapper">
      <el-form :model="searchForm" class="demo-form-inline">
        <el-row :gutter="20">
          <el-col :span="4">
            <el-form-item label="退货单号">
              <el-input v-model="searchForm.returnNo" placeholder="请输入退货单号" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="客户名称">
              <el-input v-model="searchForm.customerName" placeholder="客户名称" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="销售单号">
              <el-input v-model="searchForm.salesContractNo" placeholder="销售单号" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item label="关联出库单号">
              <el-input v-model="searchForm.shippingNo" placeholder="关联出库单号" clearable />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-form-item>
              <el-button type="primary" @click="handleQuery">搜索</el-button>
              <el-button @click="handleReset">重置</el-button>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <div class="table_list">
      <div class="table_header" style="display: flex;justify-content: flex-end;margin-bottom: 10px;">
        <el-button type="primary" @click="openForm('add')">新建销售退货</el-button>
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
        rowKey="id"
        :column="tableColumn"
        :tableData="tableData"
        :page="page"
        :isSelection="true"
        @selection-change="handleSelectionChange"
        :tableLoading="tableLoading"
        @pagination="pagination"
      >
        <template #status="{ row }">
          <el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
        </template>
      </PIMTable>
    </div>
    <form-dia ref="formDia" @close="handleQuery" />
    <detail-dia ref="detailDia" />
  </div>
</template>
<script setup>
import { reactive, ref, toRefs, computed, getCurrentInstance, nextTick, onMounted } from "vue";
import { ElMessageBox } from "element-plus";
import FormDia from "./components/formDia.vue";
import DetailDia from "./components/detailDia.vue";
import { returnManagementList, returnManagementDel, returnManagementHandle } from "@/api/salesManagement/returnOrder.js";
const { proxy } = getCurrentInstance();
const formDia = ref();
const detailDia = ref();
const openForm = (type, row) => {
  nextTick(() => formDia.value?.openDialog(type, row));
};
const openDetail = (row) => {
  nextTick(() => detailDia.value?.openDialog(row));
};
const handleRowDelete = (row) => {
  if (!row?.id) return;
  ElMessageBox.confirm("该退货单将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    returnManagementDel([row.id]).then(() => {
      proxy.$modal.msgSuccess("删除成功");
      getList();
    });
  });
};
const handleRowHandle = (row) => {
  if (!row?.id) return;
  ElMessageBox.confirm("是否处理该退货单?处理后将无法修改", "处理提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    returnManagementHandle({ returnManagementId: String(row.id) }).then(() => {
      proxy.$modal.msgSuccess("处理成功");
      getList();
    });
  });
}
const data = reactive({
  searchForm: {
    returnNo: "",
    status: "",
    customerName: "",
    salesContractNo: "",
    salesman: "",
    shippingNo: "",
    projectName: "",
    salesLedgerId: "",
    makeTime: ""
  }
});
const { searchForm } = toRefs(data);
const documentStatusOptions = ref([
  { label: "待处理", value: 0 },
  { label: "已处理", value: 1 }
]);
const defaultColumns = [
  { label: "退货单号", prop: "returnNo", width: 160 },
  { label: "单据状态", prop: "status", width: 90, dataType: "slot", slot: "status" },
  { label: "制单时间", prop: "makeTime", width: 170 },
  { label: "客户名称", prop: "customerName", width: 220 },
  { label: "销售单号", prop: "salesContractNo", width: 160 },
  { label: "业务员", prop: "salesman", width: 120 },
  { label: "关联出库单号", prop: "shippingNo", width: 170 },
  { label: "项目名称", prop: "projectName", width: 180 },
  { label: "制单人", prop: "maker", width: 120 },
  {
    label: "操作",
    prop: "operation",
    dataType: "action",
    align: "center",
    fixed: "right",
    width: 240,
    operation: [
      { name: "编辑", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => openForm("edit", row) },
      { name: "退款处理", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => handleRowHandle(row) },
      { name: "详情", type: "text", clickFun: (row) => openDetail(row) },
      { name: "删除", disabled: (row) => row.status !== 0, type: "text", clickFun: (row) => handleRowDelete(row) },
    ],
  },
];
const tableColumn = defaultColumns;
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({ current: 1, size: 10, total: 0 });
const selectedRows = ref([]);
const tableHeight = computed(() => "calc(100% - 80px)");
const handleReset = () => {
  Object.keys(searchForm.value).forEach(k => searchForm.value[k] = "");
  handleQuery();
};
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const handleQuery = () => {
  page.current = 1;
  getList();
};
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
  getList();
};
const getList = () => {
  tableLoading.value = true;
  returnManagementList({ ...searchForm.value, ...page }).then(res => {
    tableLoading.value = false;
    tableData.value = res?.data?.records || [];
    page.total = res?.data?.total || 0;
  }).finally(() => tableLoading.value = false);
};
const handleOut = () => {
  ElMessageBox.alert("导出功能待接入接口", "提示");
};
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length === 0) {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ids = selectedRows.value.map(i => i.id);
  console.log(ids);
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  }).then(() => {
    returnManagementDel( ids ).then(() => {
      proxy.$modal.msgSuccess("删除成功");
      getList();
    });
  });
};
const getStatusType = (status) => {
  const statusMap = {
    0: "warning",
    1: "success"
  };
  return statusMap[status] || "info";
};
const getStatusText = (status) => {
  const statusMap = {
    0: "待处理",
    1: "已处理"
  };
  return statusMap[status] || "未知";
};
onMounted(() => {
  getList();
});
</script>
<style scoped lang="scss">
.search-wrapper {
  background: white;
  padding: 1rem 1rem 0 1rem;
  border: 8px;
  border-radius: 16px;
}
</style>