zhou zhou
1 天以前 088e1ba6624c7523ee2566110b2c4721a37204a5
#生成波次
11个文件已修改
210 ■■■■ 已修改文件
.claude/settings.local.json 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/.env.development 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/api/wave.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/locales/langs/zh.json 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/out-stock/index.vue 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-design/src/views/orders/out-stock/outStockPage.helpers.js 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/resources/application-dev.yml 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-open-api/src/main/resources/application-prod.yml 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-dev.yml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/resources/application-prod.yml 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.claude/settings.local.json
@@ -11,7 +11,10 @@
      "Read(//d/Program Files/Git/bin/**)",
      "Read(//d/Program Files/Git/usr/bin/**)",
      "Bash(CLAUDE_CODE_GIT_BASH_PATH=\"D:/Program Files/Git/bin/bash.exe\" claude mcp:*)",
      "Bash(CLAUDE_CODE_GIT_BASH_PATH='D:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe' claude mcp:*)"
      "Bash(CLAUDE_CODE_GIT_BASH_PATH='D:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe' claude mcp:*)",
      "Bash(git:*)",
      "Bash(python:*)",
      "Bash(npm:*)"
    ]
  }
}
rsf-design/.env.development
@@ -13,4 +13,4 @@
VITE_DROP_CONSOLE = false
# 是否开启 Vue DevTools / Inspector(开启后会增加本地模块转换耗时)
VITE_ENABLE_VUE_DEVTOOLS = false
VITE_ENABLE_VUE_DEVTOOLS = true
rsf-design/src/api/wave.js
@@ -103,6 +103,13 @@
  })
}
export function fetchCreateOutStockWave(payload = {}) {
  return request.post({
    url: '/outStock/generate/wave',
    data: payload
  })
}
export async function fetchExportWaveReport(payload = {}, options = {}) {
  return fetch(`${import.meta.env.VITE_API_URL}/wave/export`, {
    method: 'POST',
rsf-design/src/locales/langs/zh.json
@@ -1415,6 +1415,7 @@
          "view": "查看详情",
          "items": "明细",
          "print": "打印",
          "createWave": "生成波次",
          "complete": "完成",
          "cancel": "取消",
          "delete": "删除"
@@ -1466,6 +1467,11 @@
          "detailTimeout": "出库单详情加载超时,已停止等待",
          "itemsTimeout": "出库单明细加载超时,已停止等待",
          "detailLoadFailed": "获取出库单详情失败",
          "createWaveTitle": "生成波次",
          "createWaveConfirm": "确定为已选择的 {count} 条出库单生成波次吗?",
          "createWaveSuccess": "波次生成成功",
          "createWaveFailed": "波次生成失败",
          "createWaveSelectionRequired": "请先选择出库单",
          "completeTitle": "完成确认",
          "completeConfirm": "确定完成出库单 {code} 吗?",
          "completeSuccess": "完成成功",
rsf-design/src/views/orders/out-stock/index.vue
@@ -11,20 +11,25 @@
    <ElCard class="art-table-card">
      <ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
        <template #left>
          <ListExportPrint
            :preview-visible="previewVisible"
            @update:previewVisible="handlePreviewVisibleChange"
            :report-title="reportTitle"
            :selected-rows="selectedRows"
            :query-params="reportQueryParams"
            :columns="columns"
            :preview-rows="previewRows"
            :preview-meta="resolvedPreviewMeta"
            :total="pagination.total"
            :disabled="loading"
            @export="handleExport"
            @print="handlePrint"
          />
          <ElSpace wrap>
            <ElButton type="primary" :loading="createWaveLoading" :disabled="loading || selectedRows.length === 0" @click="handleCreateWave">
              {{ t('pages.orders.outStock.actions.createWave') }}
            </ElButton>
            <ListExportPrint
              :preview-visible="previewVisible"
              @update:previewVisible="handlePreviewVisibleChange"
              :report-title="reportTitle"
              :selected-rows="selectedRows"
              :query-params="reportQueryParams"
              :columns="columns"
              :preview-rows="previewRows"
              :preview-meta="resolvedPreviewMeta"
              :total="pagination.total"
              :disabled="loading"
              @export="handleExport"
              @print="handlePrint"
            />
          </ElSpace>
        </template>
      </ArtTableHeader>
@@ -56,7 +61,7 @@
<script setup>
  import { computed, reactive, ref } from 'vue'
  import { ElMessage, ElMessageBox } from 'element-plus'
  import { ElButton, ElMessage, ElMessageBox, ElSpace } from 'element-plus'
  import { useRouter } from 'vue-router'
  import { useI18n } from 'vue-i18n'
  import { useUserStore } from '@/store/modules/user'
@@ -75,14 +80,17 @@
    fetchGetOutStockMany,
    fetchOutStockPage
  } from '@/api/out-stock'
  import { fetchCreateOutStockWave } from '@/api/wave'
  import OutStockDetailDrawer from './modules/out-stock-detail-drawer.vue'
  import {
    OUT_STOCK_REPORT_STYLE,
    buildCreateWavePayload,
    buildOutStockPageQueryParams,
    buildOutStockPrintRows,
    buildOutStockReportMeta,
    buildOutStockSearchParams,
    createOutStockSearchState,
    getCreateWaveValidationMessage,
    getOutStockReportTitle,
    normalizeOutStockRow
  } from './outStockPage.helpers'
@@ -104,6 +112,7 @@
  const detailData = ref({})
  const detailItemRows = ref([])
  const activeOutStockId = ref(null)
  const createWaveLoading = ref(false)
  const detailItemPagination = reactive({
    current: 1,
    size: 20,
@@ -269,6 +278,40 @@
    loadDetailResources()
  }
  async function handleCreateWave() {
    const validationMessage = getCreateWaveValidationMessage(selectedRows.value, t)
    if (validationMessage) {
      ElMessage.warning(validationMessage)
      return
    }
    try {
      await ElMessageBox.confirm(
        t('pages.orders.outStock.messages.createWaveConfirm', {
          count: selectedRows.value.length
        }),
        t('pages.orders.outStock.messages.createWaveTitle'),
        {
          confirmButtonText: t('common.confirm'),
          cancelButtonText: t('common.cancel'),
          type: 'warning'
        }
      )
      createWaveLoading.value = true
      await fetchCreateOutStockWave(buildCreateWavePayload(selectedRows.value))
      ElMessage.success(t('pages.orders.outStock.messages.createWaveSuccess'))
      selectedRows.value = []
      await refreshData()
      router.push('/orders/wave')
    } catch (error) {
      if (error === 'cancel' || error?.message === 'cancel') return
      ElMessage.error(error?.message || t('pages.orders.outStock.messages.createWaveFailed'))
    } finally {
      createWaveLoading.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'), {
rsf-design/src/views/orders/out-stock/outStockPage.helpers.js
@@ -35,6 +35,14 @@
  return String(value ?? '').trim()
}
function normalizeIds(rows = []) {
  return Array.isArray(rows)
    ? rows
        .map((row) => Number(row?.id))
        .filter((id) => Number.isFinite(id))
    : []
}
function normalizeNumber(value, fallback = void 0) {
  if (value === '' || value === null || value === undefined) {
    return fallback
@@ -196,6 +204,20 @@
  }
}
export function buildCreateWavePayload(rows = []) {
  return {
    ids: normalizeIds(rows)
  }
}
export function getCreateWaveValidationMessage(rows = [], t) {
  const ids = normalizeIds(rows)
  if (ids.length === 0) {
    return translate(t, 'pages.orders.outStock.messages.createWaveSelectionRequired')
  }
  return ''
}
export function getOutStockActionList(row = {}, t) {
  const normalizedRow = normalizeOutStockRow(row, t)
  return [
rsf-open-api/src/main/resources/application-dev.yml
@@ -27,23 +27,24 @@
      min-idle: 5
      max-active: 20
      max-wait: 30000
      keep-alive: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned-timeout: 1800
      #pool-prepared-statements: false
      #max-pool-prepared-statement-per-connection-size: 20
      filters: stat, wall
      validation-query: SELECT 'x'
      aop-patterns: com.zy.*.*.service.*
      validation-query: SELECT 1
      aop-patterns: com.vincent.rsf.openApi.*.service.*
      stat-view-servlet:
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: admin
        login-username: ${DRUID_STAT_USER:admin}
        login-password: ${DRUID_STAT_PWD:admin123}
        enabled: true
  servlet:
    multipart:
rsf-open-api/src/main/resources/application-prod.yml
@@ -19,24 +19,25 @@
      min-idle: 5
      max-active: 20
      max-wait: 30000
      keep-alive: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned: false
      remove-abandoned-timeout: 1800
      #pool-prepared-statements: false
      #max-pool-prepared-statement-per-connection-size: 20
      filters: stat, wall
      validation-query: SELECT 'x'
      aop-patterns: com.zy.*.*.service.*
      validation-query: SELECT 1
      aop-patterns: com.vincent.rsf.openApi.*.service.*
      stat-view-servlet:
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: admin
        enabled: true
        login-username: ${DRUID_STAT_USER:admin}
        login-password: ${DRUID_STAT_PWD:admin123}
        enabled: false
  servlet:
    multipart:
      maxFileSize: 100MB
@@ -84,4 +85,4 @@
      #链接
      host: http://www.itsdg.cn
      #端口
      port: 3741
      port: 3741
rsf-server/src/main/java/com/vincent/rsf/server/common/exception/GlobalExceptionHandler.java
@@ -12,6 +12,7 @@
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.AsyncRequestNotUsableException;
import jakarta.servlet.http.HttpServletResponse;
import java.util.regex.Matcher;
@@ -79,10 +80,18 @@
        return R.error(out);
    }
    @ExceptionHandler(AsyncRequestNotUsableException.class)
    public void asyncRequestNotUsableExceptionHandler(AsyncRequestNotUsableException e) {
        logger.warn("Client connection aborted: {}", resolveAbortMessage(e));
    }
    @ResponseBody
    @ExceptionHandler(RuntimeException.class)
    public R runtimeExceptionHandler(RuntimeException e, HttpServletResponse response) {
        if (isClientAbortException(e)) {
            logger.warn("Client connection aborted: {}", resolveAbortMessage(e));
            return null;
        }
        CommonUtil.addCrossHeaders(response);
        Throwable cause = e.getCause();
        if (cause instanceof CoolException) {
@@ -95,10 +104,44 @@
    @ResponseBody
    @ExceptionHandler(Throwable.class)
    public R exceptionHandler(Throwable e, HttpServletResponse response) {
        if (isClientAbortException(e)) {
            logger.warn("Client connection aborted: {}", resolveAbortMessage(e));
            return null;
        }
        logger.error(e.getMessage(), e);
        CommonUtil.addCrossHeaders(response);
        return R.error(Constants.RESULT_ERROR_MSG);
    }
    private boolean isClientAbortException(Throwable throwable) {
        Throwable current = throwable;
        while (current != null) {
            String message = current.getMessage();
            if (message != null) {
                String normalized = message.toLowerCase();
                if (normalized.contains("broken pipe")
                        || normalized.contains("connection reset")
                        || normalized.contains("forcibly closed")
                        || normalized.contains("abort")) {
                    return true;
                }
            }
            current = current.getCause();
        }
        return false;
    }
    private String resolveAbortMessage(Throwable throwable) {
        Throwable current = throwable;
        while (current != null) {
            String message = current.getMessage();
            if (message != null && !message.isBlank()) {
                return message;
            }
            current = current.getCause();
        }
        return "client aborted connection";
    }
}
rsf-server/src/main/resources/application-dev.yml
@@ -25,10 +25,11 @@
      min-idle: 5
      max-active: 20
      max-wait: 30000
      keep-alive: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned-timeout: 1800
@@ -36,12 +37,12 @@
      #max-pool-prepared-statement-per-connection-size: 20
      filters: stat, wall
      validation-query: SELECT 1
      aop-patterns: com.zy.*.*.service.*
      aop-patterns: com.vincent.rsf.server.*.service.*
      stat-view-servlet:
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: admin
        login-username: ${DRUID_STAT_USER:admin}
        login-password: ${DRUID_STAT_PWD:admin123}
        enabled: true
  servlet:
    multipart:
rsf-server/src/main/resources/application-prod.yml
@@ -19,24 +19,25 @@
      min-idle: 5
      max-active: 20
      max-wait: 30000
      keep-alive: true
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      test-while-idle: true
      test-on-borrow: true
      test-on-borrow: false
      test-on-return: false
      remove-abandoned: true
      remove-abandoned: false
      remove-abandoned-timeout: 1800
      #pool-prepared-statements: false
      #max-pool-prepared-statement-per-connection-size: 20
      filters: stat, wall
      validation-query: SELECT 'x'
      aop-patterns: com.zy.*.*.service.*
      validation-query: SELECT 1
      aop-patterns: com.vincent.rsf.server.*.service.*
      stat-view-servlet:
        url-pattern: /druid/*
        reset-enable: true
        login-username: admin
        login-password: admin
        enabled: true
        login-username: ${DRUID_STAT_USER:admin}
        login-password: ${DRUID_STAT_PWD:admin123}
        enabled: false
  servlet:
    multipart:
      maxFileSize: 100MB