zhou zhou
2026-04-20 59ddc5797782b81b128ffdb2271b7c5ed1654527
#一键测试按钮
9个文件已修改
412 ■■■■ 已修改文件
rsf-design/src/api/out-stock.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/components/biz/list-export-print/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/locales/langs/en.json 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/locales/langs/zh.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/asn-order/index.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/out-stock/index.vue 189 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockItemController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/api/out-stock.js
@@ -63,6 +63,10 @@
  return request.get({ url: `/outStock/cancel/${id}` })
}
export function fetchSaveOutStockWithItems(payload = {}) {
  return request.post({ url: '/outStock/items/save', params: payload })
}
export async function fetchExportOutStockReport(payload = {}, options = {}) {
  return fetch(`${import.meta.env.VITE_API_URL}/outStock/export`, {
    method: 'POST',
rsf-design/src/components/biz/list-export-print/index.vue
@@ -70,7 +70,6 @@
    default: false
  })
  const canExport = computed(() => !props.exportAuth || hasAuth(props.exportAuth))
  const canPrint = computed(() => !props.printAuth || hasAuth(props.printAuth))
  const printLimit = computed(() => {
    const limit = Number(props.maxResults)
    return Number.isFinite(limit) && limit > 0 ? limit : 1000
rsf-design/src/locales/langs/en.json
@@ -1199,6 +1199,7 @@
        "entity": "ASN",
        "buttons": {
          "create": "New ASN",
          "createTest": "Create Test ASN",
          "import": "Import",
          "downloadTemplate": "Download Template",
          "inspection": "Batch Inspection",
@@ -1349,6 +1350,11 @@
          "importFailed": "ASN import failed",
          "templateDownloadSuccess": "Template downloaded successfully",
          "templateDownloadFailed": "Failed to download template",
          "testCreateSuccess": "Test ASN created successfully",
          "testCreateFailed": "Failed to create test ASN",
          "testCreateOptionsRequired": "Please load order type and business type first",
          "testCreateMaterialRequired": "No material is available for creating a test ASN",
          "testCreateMemo": "Test ASN created with one click",
          "typeOptionsTimeout": "Order type options timed out and waiting has stopped",
          "wkTypeOptionsTimeout": "Business type options timed out and waiting has stopped",
          "fieldOptionsTimeout": "Extended field loading timed out and waiting has stopped"
@@ -1497,6 +1503,8 @@
          "view": "View Detail",
          "items": "Items",
          "print": "Print",
          "createTest": "Create Test Out Stock",
          "createWave": "Create Wave",
          "complete": "Complete",
          "cancel": "Cancel",
          "delete": "Delete"
@@ -1548,6 +1556,16 @@
          "detailTimeout": "Out stock detail timed out and waiting has stopped",
          "itemsTimeout": "Out stock items timed out and waiting has stopped",
          "detailLoadFailed": "Failed to load out stock detail",
          "createWaveTitle": "Create Wave",
          "createWaveConfirm": "Are you sure you want to create waves for the selected {count} out stock orders?",
          "createWaveSuccess": "Wave created successfully",
          "createWaveFailed": "Failed to create wave",
          "createWaveSelectionRequired": "Please select out stock orders first",
          "testCreateSuccess": "Test out stock order created successfully",
          "testCreateFailed": "Failed to create test out stock order",
          "testCreateOptionsRequired": "Please maintain outbound business type first",
          "testCreateMaterialRequired": "No material is available for creating a test order",
          "testCreateMemo": "Test out stock order created with one click",
          "completeTitle": "Complete Confirmation",
          "completeConfirm": "Are you sure you want to complete out stock order {code}?",
          "completeSuccess": "Completed successfully",
rsf-design/src/locales/langs/zh.json
@@ -1201,6 +1201,7 @@
        "entity": "入库通知单",
        "buttons": {
          "create": "新建入库通知单",
          "createTest": "创建测试单据",
          "import": "导入",
          "downloadTemplate": "下载模板",
          "inspection": "批量报检",
@@ -1351,6 +1352,11 @@
          "importFailed": "入库通知单导入失败",
          "templateDownloadSuccess": "模板下载成功",
          "templateDownloadFailed": "模板下载失败",
          "testCreateSuccess": "测试入库通知单创建成功",
          "testCreateFailed": "测试入库通知单创建失败",
          "testCreateOptionsRequired": "请先加载单据类型和业务类型",
          "testCreateMaterialRequired": "未找到可用于创建测试单据的物料",
          "testCreateMemo": "一键创建的测试入库通知单",
          "typeOptionsTimeout": "单据类型选项加载超时,已停止等待",
          "wkTypeOptionsTimeout": "业务类型选项加载超时,已停止等待",
          "fieldOptionsTimeout": "扩展字段加载超时,已停止等待"
@@ -1499,6 +1505,7 @@
          "view": "查看详情",
          "items": "明细",
          "print": "打印",
          "createTest": "创建测试单据",
          "createWave": "生成波次",
          "complete": "完成",
          "cancel": "取消",
@@ -1556,6 +1563,11 @@
          "createWaveSuccess": "波次生成成功",
          "createWaveFailed": "波次生成失败",
          "createWaveSelectionRequired": "请先选择出库单",
          "testCreateSuccess": "测试出库通知单创建成功",
          "testCreateFailed": "测试出库通知单创建失败",
          "testCreateOptionsRequired": "请先维护出库业务类型",
          "testCreateMaterialRequired": "未找到可用于创建测试单据的物料",
          "testCreateMemo": "一键创建的测试出库通知单",
          "completeTitle": "完成确认",
          "completeConfirm": "确定完成出库单 {code} 吗?",
          "completeSuccess": "完成成功",
rsf-design/src/views/orders/asn-order/asnOrderPage.helpers.js
@@ -19,6 +19,20 @@
  return value ? String(value) : ''
}
function normalizeDateTimeForApi(value) {
  const normalizedValue = normalizeDateValue(value)
  if (!normalizedValue) {
    return ''
  }
  if (/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}$/.test(normalizedValue)) {
    return new Date(normalizedValue.replace(/\s+/, 'T')).toISOString()
  }
  if (/^\d{4}-\d{2}-\d{2}$/.test(normalizedValue)) {
    return new Date(`${normalizedValue}T00:00:00`).toISOString()
  }
  return normalizedValue
}
function normalizeNumber(value) {
  if (value === '' || value === null || value === undefined) {
    return 0
@@ -333,7 +347,7 @@
    wkType: normalizeText(formData.wkType),
    poCode: normalizeText(formData.poCode),
    logisNo: normalizeText(formData.logisNo),
    arrTime: normalizeDateValue(formData.arrTime),
    arrTime: normalizeDateTimeForApi(formData.arrTime),
    memo: normalizeText(formData.memo)
  }
rsf-design/src/views/orders/asn-order/index.vue
@@ -15,6 +15,9 @@
            <ElButton v-auth="'add'" type="primary" @click="showDialog('add')">
              {{ t('pages.orders.asnOrder.buttons.create') }}
            </ElButton>
            <ElButton v-auth="'add'" :loading="testCreating" @click="handleCreateTestOrder">
              {{ t('pages.orders.asnOrder.buttons.createTest') }}
            </ElButton>
            <ElButton v-auth="'add'" @click="poDialogVisible = true">
              {{ t('pages.orders.asnOrder.buttons.createByPo') }}
            </ElButton>
@@ -106,6 +109,7 @@
  import { useTable } from '@/hooks/core/useTable'
  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
  import { fetchDictDataPage } from '@/api/system-manage'
  import { fetchMatnrPage } from '@/api/wh-mat'
  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
@@ -179,6 +183,7 @@
  const fieldDefinitions = ref([])
  const importing = ref(false)
  const templateDownloading = ref(false)
  const testCreating = ref(false)
  const detailPagination = reactive({
    current: 1,
@@ -623,6 +628,75 @@
    }
  }
  async function handleCreateTestOrder() {
    const type = typeOptions.value[0]?.value
    const wkType = wkTypeOptions.value[0]?.value
    if (
      type === undefined ||
      type === null ||
      type === '' ||
      wkType === undefined ||
      wkType === null ||
      wkType === ''
    ) {
      ElMessage.warning(t('pages.orders.asnOrder.messages.testCreateOptionsRequired'))
      return
    }
    testCreating.value = true
    try {
      const materialResponse = await fetchMatnrPage({ current: 1, pageSize: 3, status: 1 })
      let materials = defaultResponseAdapter(materialResponse).records
      if (!materials.length) {
        const fallbackMaterialResponse = await fetchMatnrPage({ current: 1, pageSize: 3 })
        materials = defaultResponseAdapter(fallbackMaterialResponse).records
      }
      if (!materials.length) {
        ElMessage.warning(t('pages.orders.asnOrder.messages.testCreateMaterialRequired'))
        return
      }
      const timestamp = Date.now()
      const batchPrefix = new Date().toISOString().slice(5, 10).replace('-', '')
      const requestPayload = buildAsnOrderSavePayload({
        formData: {
          type,
          wkType,
          poCode: `TEST-PO-${timestamp}`,
          logisNo: `TEST-LN-${timestamp}`,
          arrTime: new Date().toISOString(),
          memo: t('pages.orders.asnOrder.messages.testCreateMemo')
        },
        itemRows: Array.from({ length: 3 }, (_, index) => {
          const material = materials[index] || materials[0]
          return {
            matnrId: material.id,
            matnrCode: material.code || material.matnrCode || '',
            maktx: material.name || material.maktx || '',
            stockUnit: material.stockUnit || material.unit || '',
            purUnit: material.purUnit || material.stockUnit || material.unit || '',
            platItemId: `TEST-ITEM-${timestamp}-${index + 1}`,
            splrBatch: `${batchPrefix}${String(index + 1).padStart(4, '0')}`,
            anfme: 100,
            qty: 0
          }
        }),
        fieldDefinitions: fieldDefinitions.value
      })
      await fetchSaveAsnOrderWithItems(requestPayload)
      ElMessage.success(t('pages.orders.asnOrder.messages.testCreateSuccess'))
      await refreshData()
    } catch (error) {
      ElMessage.error(error?.message || t('pages.orders.asnOrder.messages.testCreateFailed'))
    } finally {
      testCreating.value = false
    }
  }
  async function handleInspectSelected() {
    if (!selectedRows.value.length) {
      ElMessage.warning(t('pages.orders.asnOrder.messages.inspectionSelectRequired'))
rsf-design/src/views/orders/out-stock/index.vue
@@ -12,7 +12,15 @@
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <ElSpace wrap>
            <ElButton type="primary" :loading="createWaveLoading" :disabled="loading || selectedRows.length === 0" @click="handleCreateWave">
            <ElButton :loading="testCreating" @click="handleCreateTestOrder">
              {{ t('pages.orders.outStock.actions.createTest') }}
            </ElButton>
            <ElButton
              type="primary"
              :loading="createWaveLoading"
              :disabled="loading || selectedRows.length === 0"
              @click="handleCreateWave"
            >
              {{ t('pages.orders.outStock.actions.createWave') }}
            </ElButton>
            <ListExportPrint
@@ -67,6 +75,8 @@
  import { useUserStore } from '@/store/modules/user'
  import { useTable } from '@/hooks/core/useTable'
  import { usePrintExportPage } from '@/views/system/common/usePrintExportPage'
  import { fetchDictDataPage } from '@/api/system-manage'
  import { fetchMatnrPage } from '@/api/wh-mat'
  import ListExportPrint from '@/components/biz/list-export-print/index.vue'
  import { defaultResponseAdapter } from '@/utils/table/tableUtils'
  import { guardRequestWithMessage } from '@/utils/sys/requestGuard'
@@ -78,7 +88,8 @@
    fetchExportOutStockReport,
    fetchGetOutStockDetail,
    fetchGetOutStockMany,
    fetchOutStockPage
    fetchOutStockPage,
    fetchSaveOutStockWithItems
  } from '@/api/out-stock'
  import { fetchCreateOutStockWave } from '@/api/wave'
  import OutStockDetailDrawer from './modules/out-stock-detail-drawer.vue'
@@ -113,6 +124,7 @@
  const detailItemRows = ref([])
  const activeOutStockId = ref(null)
  const createWaveLoading = ref(false)
  const testCreating = ref(false)
  const detailItemPagination = reactive({
    current: 1,
    size: 20,
@@ -128,7 +140,10 @@
      label: t('pages.orders.outStock.search.condition'),
      key: 'condition',
      type: 'input',
      props: { clearable: true, placeholder: t('pages.orders.outStock.search.conditionPlaceholder') }
      props: {
        clearable: true,
        placeholder: t('pages.orders.outStock.search.conditionPlaceholder')
      }
    },
    {
      label: t('pages.orders.outStock.search.code'),
@@ -197,13 +212,19 @@
      label: t('pages.orders.outStock.search.customerName'),
      key: 'customerName',
      type: 'input',
      props: { clearable: true, placeholder: t('pages.orders.outStock.search.customerNamePlaceholder') }
      props: {
        clearable: true,
        placeholder: t('pages.orders.outStock.search.customerNamePlaceholder')
      }
    },
    {
      label: t('pages.orders.outStock.search.saleOrgName'),
      key: 'saleOrgName',
      type: 'input',
      props: { clearable: true, placeholder: t('pages.orders.outStock.search.saleOrgNamePlaceholder') }
      props: {
        clearable: true,
        placeholder: t('pages.orders.outStock.search.saleOrgNamePlaceholder')
      }
    },
    {
      label: t('pages.orders.outStock.search.memo'),
@@ -222,7 +243,11 @@
    detailItemsLoading.value = true
    try {
      const [detailResponse, itemResponse] = await Promise.all([
        guardRequestWithMessage(fetchGetOutStockDetail(activeOutStockId.value), {}, { timeoutMessage: t('pages.orders.outStock.messages.detailTimeout') }),
        guardRequestWithMessage(
          fetchGetOutStockDetail(activeOutStockId.value),
          {},
          { timeoutMessage: t('pages.orders.outStock.messages.detailTimeout') }
        ),
        guardRequestWithMessage(
          fetchOutStockItemPage({
            orderId: activeOutStockId.value,
@@ -312,13 +337,90 @@
    }
  }
  async function loadFirstDictValue(dictTypeCode) {
    const response = await fetchDictDataPage({
      current: 1,
      pageSize: 1,
      dictTypeCode,
      group: '2',
      status: 1
    })
    const record = defaultResponseAdapter(response).records[0]
    return record?.value ?? record?.dictValue ?? record?.id ?? ''
  }
  async function handleCreateTestOrder() {
    testCreating.value = true
    try {
      const [wkType, materialResponse] = await Promise.all([
        loadFirstDictValue('sys_business_type'),
        fetchMatnrPage({ current: 1, pageSize: 3, status: 1 })
      ])
      let materials = defaultResponseAdapter(materialResponse).records
      if (!wkType) {
        ElMessage.warning(t('pages.orders.outStock.messages.testCreateOptionsRequired'))
        return
      }
      if (!materials.length) {
        const fallbackMaterialResponse = await fetchMatnrPage({ current: 1, pageSize: 3 })
        materials = defaultResponseAdapter(fallbackMaterialResponse).records
      }
      if (!materials.length) {
        ElMessage.warning(t('pages.orders.outStock.messages.testCreateMaterialRequired'))
        return
      }
      const timestamp = Date.now()
      const batchPrefix = new Date().toISOString().slice(5, 10).replace('-', '')
      const requestPayload = {
        orders: {
          type: 'out',
          wkType,
          poCode: `TEST-OUT-PO-${timestamp}`,
          logisNo: `TEST-OUT-LN-${timestamp}`,
          arrTime: new Date().toISOString(),
          memo: t('pages.orders.outStock.messages.testCreateMemo')
        },
        items: Array.from({ length: 3 }, (_, index) => {
          const material = materials[index] || materials[0]
          return {
            matnrId: material.id,
            matnrCode: material.code || material.matnrCode || '',
            maktx: material.name || material.maktx || '',
            stockUnit: material.stockUnit || material.unit || '',
            purUnit: material.purUnit || material.stockUnit || material.unit || '',
            poDetlCode: `TEST-OUT-ITEM-${timestamp}-${index + 1}`,
            splrBatch: `${batchPrefix}${String(index + 1).padStart(4, '0')}`,
            anfme: 100,
            qty: 0
          }
        })
      }
      await fetchSaveOutStockWithItems(requestPayload)
      ElMessage.success(t('pages.orders.outStock.messages.testCreateSuccess'))
      await refreshData()
    } catch (error) {
      ElMessage.error(error?.message || t('pages.orders.outStock.messages.testCreateFailed'))
    } finally {
      testCreating.value = false
    }
  }
  async function handleComplete(row) {
    try {
      await ElMessageBox.confirm(t('pages.orders.outStock.messages.completeConfirm', { code: row.code || '' }), t('pages.orders.outStock.messages.completeTitle'), {
        confirmButtonText: t('common.confirm'),
        cancelButtonText: t('common.cancel'),
        type: 'warning'
      })
      await ElMessageBox.confirm(
        t('pages.orders.outStock.messages.completeConfirm', { code: row.code || '' }),
        t('pages.orders.outStock.messages.completeTitle'),
        {
          confirmButtonText: t('common.confirm'),
          cancelButtonText: t('common.cancel'),
          type: 'warning'
        }
      )
      await fetchCompleteOutStock(row.id)
      ElMessage.success(t('pages.orders.outStock.messages.completeSuccess'))
      await refreshData()
@@ -333,11 +435,15 @@
  async function handleCancel(row) {
    try {
      await ElMessageBox.confirm(t('pages.orders.outStock.messages.cancelConfirm', { code: row.code || '' }), t('pages.orders.outStock.messages.cancelTitle'), {
        confirmButtonText: t('common.confirm'),
        cancelButtonText: t('common.cancel'),
        type: 'warning'
      })
      await ElMessageBox.confirm(
        t('pages.orders.outStock.messages.cancelConfirm', { code: row.code || '' }),
        t('pages.orders.outStock.messages.cancelTitle'),
        {
          confirmButtonText: t('common.confirm'),
          cancelButtonText: t('common.cancel'),
          type: 'warning'
        }
      )
      await fetchCancelOutStock(row.id)
      ElMessage.success(t('pages.orders.outStock.messages.cancelSuccess'))
      await refreshData()
@@ -442,35 +548,42 @@
    ).records
  }
  const { previewVisible, previewRows, previewMeta: rawPreviewMeta, handlePreviewVisibleChange, handleExport, handlePrint } =
    usePrintExportPage({
      downloadFileName: 'out-stock.xlsx',
      requestExport: (payload) =>
        fetchExportOutStockReport(payload, {
          headers: {
            Authorization: userStore.accessToken || ''
          }
        }),
      resolvePrintRecords,
      buildPreviewRows: (records) => buildOutStockPrintRows(records, t),
      buildPreviewMeta: (rows) => {
        const now = new Date()
        return {
          reportTitle: reportTitle.value,
          reportDate: now.toLocaleDateString('zh-CN'),
          printedAt: now.toLocaleString('zh-CN', { hour12: false }),
          operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
          count: rows.length,
          reportStyle: { ...OUT_STOCK_REPORT_STYLE }
  const {
    previewVisible,
    previewRows,
    previewMeta: rawPreviewMeta,
    handlePreviewVisibleChange,
    handleExport,
    handlePrint
  } = usePrintExportPage({
    downloadFileName: 'out-stock.xlsx',
    requestExport: (payload) =>
      fetchExportOutStockReport(payload, {
        headers: {
          Authorization: userStore.accessToken || ''
        }
      }),
    resolvePrintRecords,
    buildPreviewRows: (records) => buildOutStockPrintRows(records, t),
    buildPreviewMeta: (rows) => {
      const now = new Date()
      return {
        reportTitle: reportTitle.value,
        reportDate: now.toLocaleDateString('zh-CN'),
        printedAt: now.toLocaleString('zh-CN', { hour12: false }),
        operator: userStore.getUserInfo?.name || userStore.getUserInfo?.username || '',
        count: rows.length,
        reportStyle: { ...OUT_STOCK_REPORT_STYLE }
      }
    })
    }
  })
  const resolvedPreviewMeta = computed(() =>
    buildOutStockReportMeta({
      previewMeta: rawPreviewMeta.value,
      count: previewRows.value.length,
      orientation: rawPreviewMeta.value?.reportStyle?.orientation || OUT_STOCK_REPORT_STYLE.orientation,
      orientation:
        rawPreviewMeta.value?.reportStyle?.orientation || OUT_STOCK_REPORT_STYLE.orientation,
      t
    })
  )
rsf-design/src/views/orders/out-stock/modules/out-stock-detail-drawer.vue
@@ -9,43 +9,63 @@
    <ElScrollbar class="h-[calc(100vh-180px)] pr-1">
      <ElSkeleton :loading="loading" animated :rows="8">
        <div class="space-y-4">
          <ElDescriptions :title="t('pages.orders.outStock.detail.baseInfo')" :column="2" border>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.code')">{{ detail.code || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.poCode')">{{ detail.poCode || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.type')">{{ detail.typeLabel || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.wkType')">{{ detail.wkTypeLabel || '--' }}</ElDescriptionsItem>
          <ElDescriptions :title="t('pages.orders.outStock.detail.baseInfo')" :column="4" border>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.code')">{{
              detail.code || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.poCode')">{{
              detail.poCode || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.wkType')">{{
              detail.wkTypeLabel || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.type')">{{
              detail.typeLabel || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.exceStatus')">
              <ElTag :type="detail.exceStatusTagType || 'info'">{{ detail.exceStatusText || '--' }}</ElTag>
              {{ detail.exceStatusText || '--' }}
            </ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.rleStatus')">
              <ElTag :type="detail.rleStatusTagType || 'info'">{{ detail.rleStatusText || '--' }}</ElTag>
              {{ detail.rleStatusText || '--' }}
            </ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.logisNo')">{{ detail.logisNo || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.businessTime')">{{ detail.businessTimeText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.saleOrgName')">{{ detail.saleOrgName || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.saleUserName')">{{ detail.saleUserName || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.customerId')">{{ detail.customerId || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.customerName')">{{ detail.customerName || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.stockOrgName')">{{ detail.stockOrgName || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.anfme')">{{ detail.anfme ?? '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.workQty')">{{ detail.workQty ?? '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.qty')">{{ detail.qty ?? '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.memo')" :span="2">{{ detail.memo || '--' }}</ElDescriptionsItem>
          </ElDescriptions>
          <ElDescriptions :title="t('pages.orders.outStock.detail.auditInfo')" :column="2" border>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.createBy')">{{ detail.createByText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.createTime')">{{ detail.createTimeText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.updateBy')">{{ detail.updateByText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.updateTime')">{{ detail.updateTimeText || '--' }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.saleOrgName')">{{
              detail.saleOrgName || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.saleUserName')">{{
              detail.saleUserName || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.customerName')">{{
              detail.customerName || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.anfme')">{{
              detail.anfme ?? 0
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.qty')">{{
              detail.qty ?? 0
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.updateTime')">{{
              detail.updateTimeText || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.createTime')">{{
              detail.createTimeText || '--'
            }}</ElDescriptionsItem>
            <ElDescriptionsItem :label="t('pages.orders.outStock.detail.memo')" :span="4">{{
              detail.memo || '--'
            }}</ElDescriptionsItem>
          </ElDescriptions>
          <div class="space-y-3">
            <div class="flex items-center justify-between">
              <div class="text-sm font-medium text-[var(--art-gray-900)]">{{ t('pages.orders.outStock.detail.items') }}</div>
              <div class="text-sm font-medium text-[var(--art-gray-900)]">
                {{ t('pages.orders.outStock.detail.items') }}
              </div>
              <div class="flex items-center gap-3">
                <ElTag effect="plain">{{ t('pages.orders.outStock.detail.count', { count: itemRows.length }) }}</ElTag>
                <ElButton :loading="itemsLoading" @click="$emit('refresh')">{{ t('common.actions.refresh') }}</ElButton>
                <ElTag effect="plain">{{
                  t('pages.orders.outStock.detail.count', { count: itemRows.length })
                }}</ElTag>
                <ElButton :loading="itemsLoading" @click="$emit('refresh')">{{
                  t('common.actions.refresh')
                }}</ElButton>
              </div>
            </div>
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockItemController.java
@@ -43,7 +43,7 @@
    @Autowired
    private CompanysService companysService;
    @PreAuthorize("hasAuthority('manager:outStockItem:list')")
    @PreAuthorize("hasAuthority('manager:outStock:list')")
    @ApiOperation("分页获取列表")
    @PostMapping("/outStockItem/page")
    public R page(@RequestBody Map<String, Object> map) {
@@ -53,25 +53,25 @@
        return R.ok().add(buildPageRowsUtils.rowsMap(outStockItemService.listByAsnId(pageParam, queryWrapper)));
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:list')")
    @PreAuthorize("hasAuthority('manager:outStock:list')")
    @PostMapping("/outStockItem/list")
    public R list(@RequestBody Map<String, Object> map) {
        return R.ok().add(buildPageRowsUtils.rowsMap(outStockItemService.list()));
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:list')")
    @PreAuthorize("hasAuthority('manager:outStock:list')")
    @PostMapping({"/outStockItem/many/{ids}", "/outStockItems/many/{ids}"})
    public R many(@PathVariable Long[] ids) {
        return R.ok().add(buildPageRowsUtils.rowsMap(outStockItemService.listByIds(Arrays.asList(ids))));
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:list')")
    @PreAuthorize("hasAuthority('manager:outStock:list')")
    @GetMapping("/outStockItem/{id}")
    public R get(@PathVariable("id") Long id) {
        return R.ok().add(buildPageRowsUtils.rowsMap(outStockItemService.getById(id)));
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:save')")
    @PreAuthorize("hasAuthority('manager:outStock:save')")
    @OperationLog("Create 出库单明细")
    @PostMapping("/outStockItem/save")
    public R save(@RequestBody Map<String, Object> params) {
@@ -88,7 +88,7 @@
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:update')")
    @PreAuthorize("hasAuthority('manager:outStock:update')")
    @OperationLog("Update 出库单明细")
    @PostMapping("/outStockItem/update")
    public R update(@RequestBody WkOrderItem wkOrderItem) {
@@ -109,7 +109,7 @@
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:remove')")
    @PreAuthorize("hasAuthority('manager:outStock:remove')")
    @OperationLog("Delete 出库单明细")
    @PostMapping("/outStockItem/remove/{ids}")
    public R remove(@PathVariable Long[] ids) {
@@ -119,7 +119,7 @@
        return R.ok("Delete Success").add(buildPageRowsUtils.rowsMap(ids));
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:list')")
    @PreAuthorize("hasAuthority('manager:outStock:list')")
    @PostMapping("/outStockItem/query")
    public R query(@RequestParam(required = false) String condition) {
        List<KeyValVo> vos = new ArrayList<>();
@@ -134,7 +134,7 @@
    }
    @PreAuthorize("hasAuthority('manager:outStockItem:list')")
    @PreAuthorize("hasAuthority('manager:outStock:list')")
    @PostMapping("/outStockItem/export")
    @ApiOperation("导出出库单明细")
    public void export(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
@@ -162,7 +162,7 @@
     */
    @PostMapping("/outStockItem/import")
    @ApiOperation("ASN导入接口")
    @PreAuthorize("hasAuthority('manager:outStockItem:update')")
    @PreAuthorize("hasAuthority('manager:outStock:update')")
    public R importExcel(@RequestParam(value = "file") MultipartFile file) throws Exception {
        if (Objects.isNull(file)) {
            R.error("文件不能为空!!");
@@ -180,7 +180,7 @@
     */
    @PostMapping("/outStockItem/template/download")
    @ApiOperation("下载收货单模板")
    @PreAuthorize("hasAuthority('manager:outStockItem:update')")
    @PreAuthorize("hasAuthority('manager:outStock:update')")
    public void downloadTemplate(@RequestBody Map<String, Object> map, HttpServletResponse response) throws Exception {
        OutStockTemplate template = ExcelUtil.mockData(OutStockTemplate.class);
        List<OutStockTemplate> list = Arrays.asList(template);