已修改27个文件
已添加1个文件
834 ■■■■■ 文件已修改
WTXC.sh 265 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/erp/purchase/request/index.ts 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/data.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/business/detail/data.ts 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/crm/invoice/data.ts 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/in/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/in/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/components/select-dialog.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/components/select.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/data.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/detail/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/order/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/data.ts 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/detail/index.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/request/modules/item-form.vue 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/return/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/purchase/return/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/components/select-dialog.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/components/select.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/data.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/detail/index.vue 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/order/modules/print.vue 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/out/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/out/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/return/data.ts 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/erp/sale/return/modules/item-form.vue 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/mes/qc/ipqc/data.ts 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
WTXC.sh
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,265 @@
#!/usr/bin/env bash
# ============================================================
# å®å¤ä¸‡é€šæ–°æ â€”— å‰ç«¯éƒ¨ç½²è„šæœ¬ï¼ˆmacOS / Linux ç‰ˆï¼‰
# å¯¹åº”仓库里的 WTXC.bat,只适用于本分支(万通新材)。
#
#   ./WTXC.sh                 # æž„建 + ä¸Šä¼  + è§£åŽ‹ï¼ˆç­‰ä»·äºŽ WTXC.bat)
#   ./WTXC.sh --dry-run       # åªæž„建和本地检查,不连服务器、不上传
#   ./WTXC.sh --skip-build    # ä¸æž„建,直接上传当前目录已有的 dist.zip
#
# æµç¨‹ä¸Ž WTXC.bat å®Œå…¨ä¸€è‡´ï¼špnpm build(带 VITE_COMPANY=WTXC)→ ç”Ÿæˆ dist.zip
#   â†’ scp ä¸Šä¼ åˆ° <远端目录>/.dist.zip.uploading â†’ æ ¡éªŒå¤§å° â†’ æ”¹å
#   â†’ unzip -t æ ¡éªŒ â†’ unzip -o è§£åŽ‹ â†’ åˆ é™¤è¿œç«¯ zip
#
# éƒ¨ç½²ç›®æ ‡ï¼šroot@42.63.71.140:/home/vue/dist
#
# çŽ¯å¢ƒå˜é‡è¦†ç›–ï¼šDEPLOY_PASSWORD、DEPLOY_HOST、DEPLOY_DIR、DEPLOY_PORT
#   ï¼ˆæ”¹å¯†ç æˆ–临时指到别的目录时用,避免把密码写进命令行历史)
#
# è¯´æ˜Žï¼šWTXC.bat èµ°çš„æ˜¯ bin\sshpass.exe(Windows ä¸“用)。macOS ä¸Šæ²¡æœ‰ sshpass
#   æ—¶æœ¬è„šæœ¬è‡ªåŠ¨æ”¹ç”¨ç³»ç»Ÿè‡ªå¸¦çš„ expect é€å¯†ç ï¼Œå› æ­¤æ— éœ€é¢å¤–安装;若已装
#   sshpass(brew install hudochenkov/sshpass/sshpass)则优先使用它。
# ============================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# ------------------------------------------------------------------
# 1. å‚数与目标配置(对应 WTXC.bat é¡¶éƒ¨çš„ Configuration æ®µï¼‰
# ------------------------------------------------------------------
SKIP_BUILD=0
DRY_RUN=0
for arg in "$@"; do
  case "$arg" in
    --skip-build) SKIP_BUILD=1 ;;
    --dry-run) DRY_RUN=1 ;;
    -h | --help)
      # æ‰“印文件头部的注释说明(第一行 shebang ä¹‹åŽåˆ° set ä¹‹å‰ï¼‰
      awk 'NR == 1 { next } /^set / { exit } { print }' "${BASH_SOURCE[0]}"
      exit 0
      ;;
    *)
      echo "[ERROR] æœªçŸ¥å‚数:${arg}(可用:--skip-build / --dry-run / -h)" >&2
      exit 1
      ;;
  esac
done
COMPANY="WTXC"
EXPECT_BRANCH="dev_宁夏_万通新材料"
REMOTE_USER="root"
REMOTE_HOST="42.63.71.140"
PORT="22"
PASSWORD="Nxwtxc123."
REMOTE_DIR="/home/vue/dist"
ZIP_FILE="dist.zip"
# å…è®¸ç”¨çŽ¯å¢ƒå˜é‡è¦†ç›–
REMOTE_HOST="${DEPLOY_HOST:-$REMOTE_HOST}"
REMOTE_DIR="${DEPLOY_DIR:-$REMOTE_DIR}"
PASSWORD="${DEPLOY_PASSWORD:-$PASSWORD}"
PORT="${DEPLOY_PORT:-$PORT}"
SSH_OPTS=(-o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o NumberOfPasswordPrompts=1)
# ------------------------------------------------------------------
# 2. å‰ç½®æ£€æŸ¥ï¼šåªèƒ½åœ¨æœ¬å®¢æˆ·åˆ†æ”¯ä¸Šæž„建部署
#    åŒ…里写死了万通的名称/Logo/接口地址,用别的分支构建会把别客户的前端传上来。
# ------------------------------------------------------------------
CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')"
if [ -n "$CURRENT_BRANCH" ] && [ "$CURRENT_BRANCH" != "$EXPECT_BRANCH" ]; then
  if [ "${ALLOW_BRANCH_MISMATCH:-0}" != "1" ]; then
    echo "[ERROR] å½“前分支是 ${CURRENT_BRANCH},不是万通分支 ${EXPECT_BRANCH}。" >&2
    echo "        æœ¬è„šæœ¬ä¼šæŠŠæž„å»ºç»“æžœéƒ¨ç½²åˆ°ä¸‡é€šç”Ÿäº§æœåŠ¡å™¨ï¼Œè¯·å…ˆåˆ‡å›žä¸‡é€šåˆ†æ”¯ï¼š" >&2
    echo "        git checkout ${EXPECT_BRANCH}" >&2
    echo "        ç¡®å®žè¦åœ¨è¯¥åˆ†æ”¯ä¸Šéƒ¨ç½²æ—¶ï¼Œå¯åŠ  ALLOW_BRANCH_MISMATCH=1 è·³è¿‡æ­¤æ ¡éªŒã€‚" >&2
    exit 1
  fi
  echo "[WARN] åˆ†æ”¯ä¸æ˜¯ ${EXPECT_BRANCH}(当前 ${CURRENT_BRANCH}),按 ALLOW_BRANCH_MISMATCH=1 ç»§ç»­ã€‚" >&2
fi
echo
echo "============================================================"
echo "         Vue Dist Auto Deploy (万通新材 / macOS)"
echo "============================================================"
echo
echo "Company    : $COMPANY"
echo "Server     : $REMOTE_USER@$REMOTE_HOST:$PORT"
echo "Remote Dir : $REMOTE_DIR"
echo "Package    : $ZIP_FILE"
if [ -n "$(git status --porcelain 2>/dev/null || true)" ]; then
  echo "Git        : âš  å·¥ä½œåŒºæœ‰æœªæäº¤æ”¹åŠ¨ï¼ˆéƒ¨ç½²çš„æ˜¯å·¥ä½œåŒºå†…å®¹ï¼Œä¸æ˜¯æäº¤å†…å®¹ï¼‰"
else
  echo "Git        : ${CURRENT_BRANCH} @ $(git rev-parse --short HEAD 2>/dev/null || echo '-')"
fi
echo
# ------------------------------------------------------------------
# 3. ä¾èµ–检查(对应 WTXC.bat çš„ [1/7]~[3/7])
# ------------------------------------------------------------------
echo "[1/4] æ£€æŸ¥ pnpm / node ..."
command -v pnpm >/dev/null 2>&1 || {
  echo "[ERROR] æ‰¾ä¸åˆ° pnpm,请先 npm install -g pnpm" >&2
  exit 1
}
command -v node >/dev/null 2>&1 || {
  echo "[ERROR] æ‰¾ä¸åˆ° node" >&2
  exit 1
}
echo "      pnpm $(pnpm --version) / node $(node --version)"
if [ ! -d node_modules ]; then
  echo "[INFO] node_modules ä¸å­˜åœ¨ï¼Œæ‰§è¡Œ pnpm install ..."
  pnpm install
else
  echo "      node_modules å·²å­˜åœ¨"
fi
# ------------------------------------------------------------------
# 4. æž„建(对应 WTXC.bat çš„ [4/7])
#    æ³¨æ„ï¼šarchiver æ’件是在 closeBundle é‡Œå¼‚步写 dist.zip,
#    æ‰€ä»¥æž„建完要等它落地;同时先删掉旧 zip,避免把旧包当新包传上去。
#    é»˜è®¤æ¯æ¬¡é‡æ–°æž„建(WTXC.bat ä¹Ÿæ˜¯å¦‚此),只有显式 --skip-build æ‰å¤ç”¨æ—§åŒ…。
# ------------------------------------------------------------------
if [ "$SKIP_BUILD" -eq 0 ]; then
  echo
  echo "[2/4] æž„建中(VITE_COMPANY=${COMPANY},pnpm run build)..."
  rm -f "$ZIP_FILE"
  VITE_COMPANY="$COMPANY" pnpm run build
  for _ in $(seq 1 30); do
    [ -f "$ZIP_FILE" ] && break
    sleep 1
  done
else
  echo
  echo "[2/4] è·³è¿‡æž„建(--skip-build),复用已有的 ${ZIP_FILE}"
  echo "[WARN] è¯·ç¡®è®¤è¯¥åŒ…就是用 VITE_COMPANY=${COMPANY} æž„建的:" >&2
  echo "       åŒ…里已写死客户的名称/Logo/接口地址,用错包会把别客户的前端传到本服务器。" >&2
fi
if [ ! -f "$ZIP_FILE" ]; then
  echo "[ERROR] æž„建结束但没有生成 $ZIP_FILE" >&2
  exit 1
fi
ZIP_BYTES="$(wc -c < "$ZIP_FILE" | tr -d ' ')"
echo "[OK] æ‰“包完成:${ZIP_FILE}(${ZIP_BYTES} å­—节)"
if [ "$DRY_RUN" -eq 1 ]; then
  echo "[INFO] --dry-run:本地检查通过,未连服务器、未上传。"
  exit 0
fi
# ------------------------------------------------------------------
# 5. è¿œç«¯æ“ä½œï¼ˆå¯¹åº” WTXC.bat çš„ [6/7][7/7])
# ------------------------------------------------------------------
EXPECT_HELPER="$(mktemp -t wtxc-sshpass)"
cleanup() { rm -f "$EXPECT_HELPER"; }
trap cleanup EXIT
cat > "$EXPECT_HELPER" <<'EXPECT'
#!/usr/bin/expect -f
# ç”¨æ³•: sshpass-helper <password> <command...>
set timeout 3600
set pw  [lindex $argv 0]
set cmd [lrange $argv 1 end]
eval spawn -noecho $cmd
expect {
  -re "(?i)are you sure you want to continue connecting" { send "yes\r"; exp_continue }
  -re "(?i)password:"                                   { send "$pw\r"; exp_continue }
  -re "(?i)permission denied"                           { exit 5 }
  # è¶…时必须显式退出,否则 expect ä¼šæŒ‚在后面的 wait ä¸Šä¸è¿”回
  timeout                                               { puts stderr "SSH è¶…æ—¶"; exit 9 }
  eof
}
catch wait result
exit [lindex $result 3]
EXPECT
chmod +x "$EXPECT_HELPER"
HAS_SSHPASS=0
command -v sshpass >/dev/null 2>&1 && HAS_SSHPASS=1
if [ "$HAS_SSHPASS" -eq 0 ] && ! command -v expect >/dev/null 2>&1; then
  echo "[ERROR] æ—¢æ²¡æœ‰ sshpass ä¹Ÿæ²¡æœ‰ expect,无法在脚本里自动送密码。" >&2
  echo "        å¯æ‰§è¡Œï¼šbrew install hudochenkov/sshpass/sshpass,或手工 scp ä¸Šä¼ ã€‚" >&2
  exit 1
fi
# åœ¨è¿œç«¯æ‰§è¡Œå‘½ä»¤ï¼Œè¿”回其退出码;stdout ç”±è°ƒç”¨æ–¹æ•获(失败时输出打到 stderr)
remote_run() {
  local out rc=0
  if [ "$HAS_SSHPASS" -eq 1 ]; then
    out="$(SSHPASS="$PASSWORD" sshpass -e ssh -p "$PORT" "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" "$1" 2>&1)" || rc=$?
  else
    out="$("$EXPECT_HELPER" "$PASSWORD" ssh -p "$PORT" "${SSH_OPTS[@]}" "$REMOTE_USER@$REMOTE_HOST" "$1" 2>&1)" || rc=$?
  fi
  if [ "$rc" -ne 0 ]; then
    echo "[ERROR] è¿œç«¯å‘½ä»¤æ‰§è¡Œå¤±è´¥ï¼ˆexit $rc):$1" >&2
    [ -n "$out" ] && printf '%s\n' "$out" >&2
  fi
  printf '%s' "$out"
  return "$rc"
}
# ä¸Šä¼ ä¸€ä¸ªæ–‡ä»¶ï¼šremote_copy <本地文件> <远端绝对路径>(保留进度条)
remote_copy() {
  if [ "$HAS_SSHPASS" -eq 1 ]; then
    SSHPASS="$PASSWORD" sshpass -e scp -P "$PORT" "${SSH_OPTS[@]}" "$1" "$REMOTE_USER@$REMOTE_HOST:$2"
  else
    "$EXPECT_HELPER" "$PASSWORD" scp -P "$PORT" "${SSH_OPTS[@]}" "$1" "$REMOTE_USER@$REMOTE_HOST:$2"
  fi
}
echo
echo "[3/4] è¿žæŽ¥æœåС噍 ..."
remote_run "echo SSH_OK" >/dev/null
echo "[OK] SSH è¿žæŽ¥æˆåŠŸ"
remote_run "mkdir -p '$REMOTE_DIR'" >/dev/null
echo "[OK] è¿œç«¯ç›®å½•就绪"
REMOTE_TMP="$REMOTE_DIR/.dist.zip.uploading"
echo
echo "[4/4] ä¸Šä¼ å¹¶è§£åŽ‹ ..."
remote_run "rm -f '$REMOTE_TMP'" >/dev/null
remote_copy "$ZIP_FILE" "$REMOTE_TMP"
echo "[OK] ä¸Šä¼ å®Œæˆ"
# è¿œç«¯å‘½ä»¤çš„输出里会混有 expect å›žæ˜¾çš„密码提示,命令本身的 stdout æ€»åœ¨æœ€åŽï¼Œ
# å–最后一个字段即为所需数值(stat çš„ stderr ä¹Ÿä¼šèµ°åˆ°è¿™é‡Œï¼Œæ•…再做一次空值判断)。
REMOTE_BYTES="$(remote_run "stat -c %s '$REMOTE_TMP'" | tr -d '\r' | awk 'END { print $NF }')"
REMOTE_BYTES="$(printf '%s' "$REMOTE_BYTES" | tr -cd '0-9')"
if [ -z "$REMOTE_BYTES" ] || [ "$REMOTE_BYTES" != "$ZIP_BYTES" ]; then
  echo "[ERROR] è¿œç«¯æ–‡ä»¶å¤§å°($REMOTE_BYTES)与本地($ZIP_BYTES)不一致,已中止;远端临时文件保留在 $REMOTE_TMP" >&2
  exit 1
fi
echo "[OK] å¤§å°æ ¡éªŒä¸€è‡´ï¼ˆ$REMOTE_BYTES å­—节)"
remote_run "mv -f '$REMOTE_TMP' '$REMOTE_DIR/$ZIP_FILE'" >/dev/null
echo "[OK] åŒ…已就位"
remote_run "command -v unzip" >/dev/null || {
  echo "[ERROR] è¿œç«¯æ²¡æœ‰ unzip:CentOS ç”¨ yum install -y unzip / Ubuntu ç”¨ apt install -y unzip" >&2
  exit 1
}
remote_run "cd '$REMOTE_DIR' && unzip -tq '$ZIP_FILE'" >/dev/null
echo "[OK] è¿œç«¯ zip æ ¡éªŒé€šè¿‡"
remote_run "cd '$REMOTE_DIR' && unzip -oq '$ZIP_FILE'" >/dev/null
echo "[OK] è§£åŽ‹å®Œæˆ"
remote_run "rm -f '$REMOTE_DIR/$ZIP_FILE'" >/dev/null || echo "[WARN] è¿œç«¯ zip åˆ é™¤å¤±è´¥ï¼ˆä¸å½±å“ä½¿ç”¨ï¼‰"
echo
echo "============================================================"
echo "                 DEPLOY SUCCESS"
echo "============================================================"
echo "Company    : $COMPANY"
echo "Server     : $REMOTE_HOST:$PORT"
echo "Remote Dir : $REMOTE_DIR"
echo "Package    : ${ZIP_FILE}(${ZIP_BYTES} å­—节)"
echo "提示:前端是静态资源,浏览器 Ctrl/Cmd+Shift+R å¼ºåˆ·åŽå†éªŒè¯ã€‚"
echo
src/api/erp/purchase/request/index.ts
@@ -38,11 +38,15 @@
    productUnitId?: number; // äº§å“å•位编号
    productUnitName?: string; // äº§å“å•位名称
    productBarCode?: string; // äº§å“æ¡ç 
    productPrice?: number; // äº§å“å•价(参考价)
    productPrice?: number; // å«ç¨Žå•价(参考价)
    count: number; // æ•°é‡
    taxPercent?: number; // ç¨Žçއ
    totalPrice?: number; // ä¸å«ç¨Žé‡‘额
    taxPrice?: number; // ç¨Žé¢
    demandTime?: string; // éœ€æ±‚日期
    remark?: string; // å¤‡æ³¨
    qcCheckFlag?: boolean; // æ˜¯å¦è´¨æ£€
    inCount?: number; // å…¥åº“数量
  }
}
src/views/crm/business/data.ts
@@ -82,14 +82,13 @@
        api: getBusinessStatusTypeSimpleList,
        labelField: 'name',
        valueField: 'id',
        placeholder: '请选择商机状态组',
        placeholder: '请选择商机状态组(选填)',
        allowClear: true,
      },
      dependencies: {
        triggerFields: ['id'],
        disabled: (values) => values.id,
      },
      rules: 'required',
    },
    {
      fieldName: 'dealTime',
src/views/crm/business/detail/data.ts
@@ -99,19 +99,29 @@
      dependencies: {
        triggerFields: [''],
        async componentProps() {
          const statusList = await getBusinessStatusSimpleList(
            formData.value?.statusTypeId ?? 0,
          );
          const statusTypeId = formData.value?.statusTypeId;
          // å•†æœºçŠ¶æ€ç»„ä¸ºé€‰å¡«ï¼šæœªè®¾ç½®çŠ¶æ€ç»„æ—¶æ²¡æœ‰å¯é€‰é˜¶æ®µï¼Œç¦ç”¨å¹¶è¯´æ˜ŽåŽŸå› 
          if (!statusTypeId) {
            return {
              disabled: true,
              options: [],
              placeholder: '该商机未设置「商机状态组」,无可选商机阶段',
            };
          }
          const statusList = await getBusinessStatusSimpleList(statusTypeId);
          const options = statusList.map((item) => ({
            label: `${item.name}(赢单率:${item.percent}%)`,
            value: item.id,
          }));
          return {
            disabled: false,
            options,
            placeholder: '请选择商机阶段',
          };
        },
        // æœªè®¾ç½®å•†æœºçŠ¶æ€ç»„æ—¶ä¸åšå¿…å¡«æ ¡éªŒï¼ˆæ­¤æ—¶ä¹Ÿæ²¡æœ‰é˜¶æ®µå¯ä¾›é€‰æ‹©ï¼‰
        rules: () => (formData.value?.statusTypeId ? 'required' : null),
      },
      rules: 'required',
    },
  ];
}
src/views/crm/invoice/data.ts
@@ -59,9 +59,9 @@
      fieldName: 'customerId',
      label: '客户名称',
      component: markRaw(CrmCustomerSelect),
      rules: 'required',
      componentProps: (_values, form) => ({
        placeholder: '请选择客户',
        placeholder: '请选择客户(可不选)',
        allowClear: true,
        onChange: () => {
          form.setFieldValue('contractId', undefined);
          form.setFieldValue('price', undefined);
@@ -76,7 +76,6 @@
      fieldName: 'contractId',
      label: '合同名称',
      component: 'Select',
      rules: 'required',
      dependencies: {
        triggerFields: ['customerId'],
        disabled: (values) => !values.customerId || !!values.id,
@@ -84,7 +83,8 @@
          if (!values.customerId) {
            return {
              options: [],
              placeholder: '请先选择客户',
              placeholder: '请先选择客户(可不选)',
              allowClear: true,
            };
          }
          const contracts = await getContractSimpleList(values.customerId);
@@ -100,7 +100,8 @@
                value: item.id,
              };
            }),
            placeholder: '请选择合同',
            placeholder: '请选择合同(可不选)',
            allowClear: true,
            onChange: () => {
              form.setFieldValue('price', undefined);
            },
src/views/erp/purchase/in/data.ts
@@ -134,11 +134,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -258,17 +258,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -278,16 +271,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -595,7 +595,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/in/modules/item-form.vue
@@ -150,6 +150,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  // TODO èŠ‹è‰¿
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -159,13 +160,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpPurchaseInApi.PurchaseInItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseInApi.PurchaseInItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseInApi.PurchaseInItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -269,11 +285,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/purchase/order/components/select-dialog.vue
@@ -93,7 +93,7 @@
    { field: 'supplierName', title: '供应商', minWidth: 120 },
    { field: 'orderTime', title: '订单时间', width: 120, formatter: 'formatDate' },
    { field: 'totalCount', title: '合计数量', width: 100 },
    { field: 'totalPrice', title: '合计金额', width: 100 },
    { field: 'totalPrice', title: '含税合计', width: 100 },
  ];
}
src/views/erp/purchase/order/components/select.vue
@@ -116,7 +116,7 @@
          <div>订单号:{{ selectedItem.no || '-' }}</div>
          <div>供应商:{{ selectedItem.supplierName || '-' }}</div>
          <div>订单时间:{{ selectedItem.orderTime || '-' }}</div>
          <div>合计金额:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
          <div>含税合计:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
        </div>
      </template>
      <Input
src/views/erp/purchase/order/data.ts
@@ -86,7 +86,7 @@
            return z
              .number()
              .min(0, '订金不能小于0')
              .max(values.totalPrice, '订金不能大于优惠后金额');
              .max(values.totalPrice, '订金不能大于优惠后含税金额');
          }
          return z.number().min(0, '订金不能小于0');
        },
@@ -150,11 +150,11 @@
    },
    {
      fieldName: 'totalPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -215,17 +215,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      title: '金额',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPercent',
@@ -235,15 +228,22 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      title: '税额合计',
      title: '含税合计(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
@@ -409,7 +409,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/order/detail/index.vue
@@ -60,7 +60,7 @@
      render: (val) => erpCountInputFormatter(val) || '-',
    },
    {
      label: '合计金额',
      label: '含税合计金额',
      field: 'totalPrice',
      render: (val) => erpPriceInputFormatter(val) || '-',
    },
src/views/erp/purchase/order/modules/item-form.vue
@@ -164,6 +164,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -173,13 +174,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseOrderApi.PurchaseOrderItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -294,11 +310,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/purchase/request/data.ts
@@ -172,17 +172,10 @@
    },
    {
      field: 'productPrice',
      title: '单价(参考价)',
      title: '含税单价(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalPrice',
      title: '预估金额',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPercent',
@@ -190,6 +183,26 @@
      minWidth: 100,
      fixed: 'right',
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPrice',
      title: '税额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      title: '含税合计(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'totalTaxInclusivePrice' },
    },
    {
      field: 'demandTime',
@@ -344,7 +357,7 @@
    },
    {
      field: 'totalPrice',
      title: '预估金额',
      title: '含税金额',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/request/detail/index.vue
@@ -46,13 +46,7 @@
    },
    {
      field: 'productPrice',
      title: '单价(参考价)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
    {
      field: 'totalPrice',
      title: '预估金额',
      title: '含税单价(元)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
@@ -61,6 +55,25 @@
      title: '税率(%)',
      minWidth: 90,
      formatter: ({ cellValue }) => (cellValue ? `${cellValue}%` : '-'),
    },
    {
      field: 'totalPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
    {
      field: 'taxPrice',
      title: '税额(元)',
      minWidth: 120,
      formatter: ({ cellValue }) => erpPriceInputFormatter(cellValue) || '-',
    },
    {
      field: 'totalTaxInclusivePrice',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: ({ row }) =>
        erpPriceInputFormatter((row.totalPrice || 0) + (row.taxPrice || 0)) || '-',
    },
    { field: 'demandTime', title: '需求日期', minWidth: 110 },
    {
@@ -123,7 +136,7 @@
      render: (val) => erpCountInputFormatter(val) || '-',
    },
    {
      label: '合计金额',
      label: '含税合计金额',
      field: 'totalPrice',
      render: (val) => erpPriceInputFormatter(val) || '-',
    },
src/views/erp/purchase/request/modules/item-form.vue
@@ -30,19 +30,22 @@
const tableData = ref<ErpPurchaseRequestApi.PurchaseRequestItem[]>([]);
/** èŽ·å–è¡¨æ ¼åˆè®¡æ•°æ® */
/** èŽ·å–è¡¨æ ¼åˆè®¡æ•°æ®ï¼ˆè¡Œä¸Š totalPrice ä¸ºä¸å«ç¨Žé‡‘额,含税合计需加上税额) */
const summaries = computed(() => {
  return {
    count: tableData.value.reduce((sum, item) => sum + (item.count || 0), 0),
    totalPrice: tableData.value.reduce(
      (sum, item) => {
        const price = erpPriceMultiply(item.productPrice || 0, item.count || 0) ?? 0;
        return sum + price;
      },
      (sum, item) => sum + (item.totalPrice || 0),
      0,
    ),
    taxPrice: tableData.value.reduce((sum, item) => sum + (item.taxPrice || 0), 0),
  };
});
/** å«ç¨Žåˆè®¡ = ä¸å«ç¨Žé‡‘额 + ç¨Žé¢ */
const totalTaxInclusivePrice = computed(
  () => summaries.value.totalPrice + summaries.value.taxPrice,
);
/** è¡¨æ ¼é…ç½® */
const [Grid, gridApi] = useVbenVxeGrid({
@@ -126,25 +129,39 @@
  handleRowChange(row);
}
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
  if (row.productPrice && row.count) {
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = taxInclusivePrice - row.taxPrice;
  }
}
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
  } else {
    tableData.value[index] = row;
  }
  // è®¡ç®—预估金额
  if (row.productPrice && row.count) {
    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
  }
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseRequestApi.PurchaseRequestItem) {
  if (row.productPrice && row.count) {
    row.totalPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -218,6 +235,9 @@
      />
      <span v-else>{{ row.taxPercent ? `${row.taxPercent}%` : '-' }}</span>
    </template>
    <template #totalTaxInclusivePrice="{ row }">
      <span>{{ erpPriceInputFormatter((row.totalPrice || 0) + (row.taxPrice || 0)) }}</span>
    </template>
    <template #demandTime="{ row }">
      <DatePicker
        v-if="!disabled"
@@ -264,7 +284,9 @@
          <span class="font-medium text-foreground">合计:</span>
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>预估金额:{{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
            <span>不含税金额:{{ erpPriceInputFormatter(summaries.totalPrice) }}</span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>含税合计:{{ erpPriceInputFormatter(totalTaxInclusivePrice) }}</span>
          </div>
        </div>
      </div>
src/views/erp/purchase/return/data.ts
@@ -133,11 +133,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -258,17 +258,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -278,16 +271,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -582,7 +582,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/purchase/return/modules/item-form.vue
@@ -153,6 +153,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  // TODO èŠ‹è‰¿
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -162,13 +163,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpPurchaseReturnApi.PurchaseReturnItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -272,11 +288,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/sale/order/components/select-dialog.vue
@@ -95,7 +95,7 @@
    { field: 'customerName', title: '客户', minWidth: 120 },
    { field: 'orderTime', title: '订单时间', width: 120, formatter: 'formatDate' },
    { field: 'totalCount', title: '合计数量', width: 100 },
    { field: 'totalPrice', title: '合计金额', width: 100 },
    { field: 'totalPrice', title: '含税合计', width: 100 },
  ];
}
src/views/erp/sale/order/components/select.vue
@@ -119,7 +119,7 @@
          <div>订单号:{{ selectedItem.no || '-' }}</div>
          <div>客户:{{ selectedItem.customerName || '-' }}</div>
          <div>订单时间:{{ selectedItem.orderTime || '-' }}</div>
          <div>合计金额:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
          <div>含税合计:{{ selectedItem.totalPrice || '-' }} å…ƒ</div>
        </div>
      </template>
      <Input
src/views/erp/sale/order/data.ts
@@ -161,11 +161,11 @@
    },
    {
      fieldName: 'totalPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -202,7 +202,7 @@
            return z
              .number()
              .min(0, '订金不能小于0')
              .max(values.totalPrice, '收取订金不能大于优惠后金额');
              .max(values.totalPrice, '收取订金不能大于优惠后含税金额');
          }
          return z.number().min(0, '订金不能小于0').optional();
        },
@@ -275,17 +275,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      minWidth: 120,
      fixed: 'right',
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      title: '金额',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPercent',
@@ -295,15 +288,22 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      title: '不含税金额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      title: '税额合计',
      title: '含税合计(元)',
      minWidth: 120,
      fixed: 'right',
      formatter: 'formatAmount2',
@@ -479,7 +479,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/sale/order/detail/index.vue
@@ -75,7 +75,7 @@
      render: (val) => erpCountInputFormatter(val) || '-',
    },
    {
      label: '合计金额',
      label: '含税合计金额',
      field: 'totalPrice',
      render: (val) => erpPriceInputFormatter(val) || '-',
    },
src/views/erp/sale/order/modules/item-form.vue
@@ -172,6 +172,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -181,13 +182,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpSaleOrderApi.SaleOrderItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpSaleOrderApi.SaleOrderItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpSaleOrderApi.SaleOrderItem) {
  if (!row.id) {
    recalcRow(row);
  }
  // é»˜è®¤éœ€è¦ç”Ÿäº§
  if (row.needProduction === undefined) {
@@ -311,11 +327,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/sale/order/modules/print.vue
@@ -99,21 +99,21 @@
}
/**
 * æ˜Žç»†è¡Œï¼ˆåŽç«¯ä¸è¿”回明细行的 totalProductPrice/totalPrice,需按单价×数量计算:
 * é‡‘额 = å•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = é‡‘额 Ã— ç¨ŽçŽ‡ï¼›å«ç¨Žé‡‘é¢ = é‡‘额 + ç¨Žé¢ï¼‰
 * æ˜Žç»†è¡Œï¼ˆä¼˜å…ˆç”¨åŽç«¯è¿”回值;缺省时按「单价为含税单价」计算:
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢ï¼‰
 */
const printRows = computed(() =>
  (printData.value.items || []).map((item) => {
    const totalProductPrice =
      item.totalProductPrice ?? multiplyPrice(item.productPrice, item.count) ?? 0;
    const taxInclusivePrice = multiplyPrice(item.productPrice, item.count);
    const taxPrice =
      item.taxPrice ?? multiplyPrice(totalProductPrice, (item.taxPercent ?? 0) / 100) ?? 0;
    const totalPrice = item.totalPrice ?? totalProductPrice + taxPrice;
      item.taxPrice ?? multiplyPrice(taxInclusivePrice, (item.taxPercent ?? 0) / 100) ?? 0;
    const totalPrice = item.totalPrice ?? taxInclusivePrice ?? 0;
    const totalProductPrice = item.totalProductPrice ?? totalPrice - taxPrice;
    return { ...item, totalProductPrice, taxPrice, totalPrice };
  }),
);
/** æ˜Žç»†åˆè®¡ï¼šæ•°é‡/金额优先取订单头(后端权威值),税额与含税金额按明细累加(订单头 totalPrice ä¸ºä¼˜æƒ åŽé‡‘额,另在金额信息区展示) */
/** æ˜Žç»†åˆè®¡ï¼šæ•°é‡/金额优先取订单头(后端权威值),税额与含税金额按明细累加(订单头 totalPrice ä¸ºä¼˜æƒ åŽå«ç¨Žé‡‘额,另在金额信息区展示) */
const summaries = computed(() => {
  const rows = printRows.value;
  const count = rows.reduce((sum, item) => sum + (item.count || 0), 0);
@@ -262,17 +262,17 @@
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              äº§å“å•ä»·(元)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              é‡‘额(元)
              å«ç¨Žå•ä»·(元)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              ç¨Žçއ(%)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
            >
              ä¸å«ç¨Žé‡‘额(元)
            </th>
            <th
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right font-bold"
@@ -312,10 +312,10 @@
              {{ formatPrice(item.productPrice) }}
            </td>
            <td class="border border-solid border-[#dcdfe6] p-2 text-right">
              {{ formatPrice(item.totalProductPrice) }}
              {{ formatPrice(item.taxPercent) }}
            </td>
            <td class="border border-solid border-[#dcdfe6] p-2 text-right">
              {{ formatPrice(item.taxPercent) }}
              {{ formatPrice(item.totalProductPrice) }}
            </td>
            <td class="border border-solid border-[#dcdfe6] p-2 text-right">
              {{ formatPrice(item.taxPrice) }}
@@ -348,13 +348,13 @@
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2"
            ></td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2"
            ></td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right"
            >
              {{ formatPrice(summaries.totalProductPrice) }}
            </td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2"
            ></td>
            <td
              class="border border-solid border-[#dcdfe6] bg-[#f5f7fa] p-2 text-right"
            >
@@ -386,7 +386,7 @@
      >
        <div>优惠率(%):{{ formatPrice(printData.discountPercent) }}</div>
        <div>付款优惠(元):{{ formatPrice(printData.discountPrice) }}</div>
        <div>优惠后金额(元):{{ formatPrice(printData.totalPrice) }}</div>
        <div>优惠后含税金额(元):{{ formatPrice(printData.totalPrice) }}</div>
        <div>收取订金(元):{{ formatPrice(printData.depositPrice) }}</div>
      </div>
src/views/erp/sale/out/data.ts
@@ -151,11 +151,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -279,17 +279,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -299,16 +292,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -603,7 +603,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/sale/out/modules/item-form.vue
@@ -150,6 +150,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -159,13 +160,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpSaleOutApi.SaleOutItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpSaleOutApi.SaleOutItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpSaleOutApi.SaleOutItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -269,11 +285,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/erp/sale/return/data.ts
@@ -146,11 +146,11 @@
    },
    {
      fieldName: 'discountedPrice',
      label: '优惠后金额',
      label: '优惠后含税金额',
      component: 'InputNumber',
      componentProps: {
        class: '!w-full',
        placeholder: '优惠后金额',
        placeholder: '优惠后含税金额',
        precision: 2,
        formatter: erpPriceInputFormatter,
        disabled: true,
@@ -272,17 +272,10 @@
    },
    {
      field: 'productPrice',
      title: '产品单价',
      title: '含税单价(元)',
      fixed: 'right',
      minWidth: 120,
      slots: { default: 'productPrice' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '产品金额',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
@@ -292,16 +285,23 @@
      slots: { default: 'taxPercent' },
    },
    {
      field: 'totalProductPrice',
      fixed: 'right',
      title: '不含税金额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      fixed: 'right',
      field: 'taxPrice',
      title: '税额',
      title: '税额(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
    {
      field: 'totalPrice',
      fixed: 'right',
      title: '合计金额',
      title: '含税合计(元)',
      minWidth: 120,
      formatter: 'formatAmount2',
    },
@@ -590,7 +590,7 @@
    },
    {
      field: 'totalProductPrice',
      title: '金额合计',
      title: '不含税金额合计',
      formatter: 'formatAmount2',
      minWidth: 120,
    },
src/views/erp/sale/return/modules/item-form.vue
@@ -207,6 +207,7 @@
/** å¤„理行数据变更 */
function handleRowChange(row: any) {
  recalcRow(row);
  const index = tableData.value.findIndex((item) => item.seq === row.seq);
  if (index === -1) {
    tableData.value.push(row);
@@ -216,13 +217,28 @@
  emit('update:items', [...tableData.value]);
}
/** åˆå§‹åŒ–行数据 */
function initRow(row: ErpSaleReturnApi.SaleReturnItem) {
/**
 * é‡ç®—行金额(单价为**含税**单价)
 * å«ç¨Žé‡‘额 = å«ç¨Žå•ä»· Ã— æ•°é‡ï¼›ç¨Žé¢ = å«ç¨Žé‡‘额 Ã— ç¨ŽçŽ‡ï¼›ä¸å«ç¨Žé‡‘é¢ = å«ç¨Žé‡‘额 - ç¨Žé¢
 */
function recalcRow(row: ErpSaleReturnApi.SaleReturnItem) {
  if (row.productPrice && row.count) {
    row.totalProductPrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    const taxInclusivePrice = erpPriceMultiply(row.productPrice, row.count) ?? 0;
    row.taxPrice =
      erpPriceMultiply(row.totalProductPrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalPrice = row.totalProductPrice + row.taxPrice;
      erpPriceMultiply(taxInclusivePrice, (row.taxPercent || 0) / 100) ?? 0;
    row.totalProductPrice = taxInclusivePrice - row.taxPrice;
    row.totalPrice = taxInclusivePrice;
  }
}
/**
 * åˆå§‹åŒ–行数据
 * å·²æœ‰å•据的明细行(带 id)沿用后端返回的金额,避免历史单据被新口径覆盖;
 * åªæœ‰æœªä¿å­˜çš„æ–°å¢žè¡Œæ‰æŒ‰æ–°å£å¾„重算。
 */
function initRow(row: ErpSaleReturnApi.SaleReturnItem) {
  if (!row.id) {
    recalcRow(row);
  }
}
@@ -347,11 +363,11 @@
          <div class="flex space-x-4">
            <span>数量:{{ erpCountInputFormatter(summaries.count) }}</span>
            <span>
              é‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
              ä¸å«ç¨Žé‡‘额:{{ erpPriceInputFormatter(summaries.totalProductPrice) }}
            </span>
            <span>税额:{{ erpPriceInputFormatter(summaries.taxPrice) }}</span>
            <span>
              ç¨Žé¢åˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
              å«ç¨Žåˆè®¡ï¼š{{ erpPriceInputFormatter(summaries.totalPrice) }}
            </span>
          </div>
        </div>
src/views/mes/qc/ipqc/data.ts
@@ -156,17 +156,18 @@
      label: '生产订单',
      component: markRaw(ProWorkOrderSelect),
      componentProps: {
        placeholder: '请选择生产订单',
        allowClear: true,
        placeholder: '请选择生产订单(选填)',
        status: MesProWorkOrderStatusEnum.CONFIRMED,
        // è®¢å•变更时清空生产任务
        onChange: () => formApi?.setFieldValue('taskId', undefined),
      },
      rules: 'selectRequired',
      dependencies: {
        triggerFields: ['sourceDocId'],
        componentProps: (values) => ({
          allowClear: true,
          disabled: !!values.sourceDocId,
          placeholder: '请选择生产订单',
          placeholder: '请选择生产订单(选填)',
          status: MesProWorkOrderStatusEnum.CONFIRMED,
          onChange: () => formApi?.setFieldValue('taskId', undefined),
        }),
@@ -177,14 +178,15 @@
      label: '工位',
      component: markRaw(MdWorkstationSelect),
      componentProps: {
        placeholder: '请选择工位',
        allowClear: true,
        placeholder: '请选择工位(选填)',
      },
      rules: 'selectRequired',
      dependencies: {
        triggerFields: ['sourceDocId'],
        componentProps: (values) => ({
          allowClear: true,
          disabled: !!values.sourceDocId,
          placeholder: '请选择工位',
          placeholder: '请选择工位(选填)',
        }),
      },
    },
@@ -226,17 +228,14 @@
          autoSelect: 'first',
          labelField: 'templateName',
          valueField: 'templateId',
          disabled: formType !== 'create' || !values.workOrderId,
          params: values.workOrderId
            ? {
                qcType: 2,
                workOrderId: values.workOrderId,
                taskId: values.taskId,
              }
            : {},
          placeholder: values.workOrderId
            ? '请选择质检方案'
            : '请先选择生产订单',
          disabled: formType !== 'create',
          // æœªé€‰ç”Ÿäº§è®¢å•/生产任务时,后端按「过程检验」种类返回全部启用方案(不再要求先选生产订单)
          params: {
            qcType: 2,
            ...(values.workOrderId ? { workOrderId: values.workOrderId } : {}),
            ...(values.taskId ? { taskId: values.taskId } : {}),
          },
          placeholder: '请选择质检方案',
        }),
      },
    },