Skip to content

fix(portfolio-import): 草稿行全部带 issue 时仍静默创建空组合 #243

Description

@wxrbyte

现象

import:confirm 在草稿的每一行都还带着 issues 时不会报错,而是把空持仓数组落库、返回「导入成功」。

UI 侧同样放行:「确认导入」按钮只在 draft.rows.length === 0 时禁用,不检查行是否仍有 issue。导入一份「每行都缺字段」的 CSV 后按钮仍可点击,用户会拿到一个 0 持仓的组合,且界面上没有任何提示。

根因

apps/electron/src/main/kernelHost.ts:2099-2112

const rows = Array.isArray(draft.rows) ? draft.rows : [];
if (rows.length === 0) {                                   // ← 校验的是过滤前的 rows
  throw createCodeError('IMPORT_EMPTY', 'Nothing to import — the draft has no rows.');
}
const holdings: Holding[] = rows
  .filter((row) => row.symbol && row.issues.length === 0)  // ← 真正落库的是过滤后的 holdings
  .map((row) => ({ ... }));
return this.importRepository.create({ name, holdings });   // ← holdings 为空也照常创建

守卫对象(rows)与落库对象(holdings)不是同一个集合:只要 rows.length > 0,守卫就一定放行,而 holdings 可能已经是空数组。ManualPortfolioRepository.create 原样写入 holdings(packages/shared/src/portfolio-import/repository.ts:56-65),因此空组合会被持久化。

UI 侧对应位置:packages/ui/src/components/portfolio/ImportDraftReview.tsx:173(disabled={confirming || draft.rows.length === 0})。

复现

经 import:confirm 传入 UI 可直接产生的草稿:

{
  "name": "我的组合",
  "draft": {
    "id": "draft_test",
    "source": "csv",
    "importedAt": 1,
    "warnings": [],
    "rows": [
      { "symbol": "AAPL.US", "confidence": 0.4, "issues": ["Quantity is not a number"] },
      { "symbol": "TSLA.US", "confidence": 0.3, "issues": ["Cost price missing"] }
    ]
  }
}
  • 实际:IMPORT_EMPTY 不触发(rows.length === 2),过滤后 holdings = [],create({ name, holdings: [] }) 成功,IPC 返回 { ok: true, data: <0 持仓组合> }。
  • 预期:抛 IMPORT_EMPTY(「无可导入行」),不落库。

修复

  • apps/electron/src/main/kernelHost.ts:守卫改为作用在落库对象上 —— 先算出 importable = rows.filter(row => row.symbol && row.issues.length === 0),importable.length === 0 即抛 IMPORT_EMPTY。这同时覆盖了原来的 rows.length === 0 分支(空 rows 必然导致空 importable)。
  • packages/ui/src/components/portfolio/ImportDraftReview.tsx:确认按钮改为 disabled={confirming || importableCount === 0},使前端在提交前就阻止这种情况,而不是让用户拿到一个后端错误。

验证

  • bun test apps/electron/src/main/kernelHost.test.ts packages/ui/src/components/portfolio/ImportDraftReview.test.tsx
    • 修复前:20 pass / 2 fail(新增的两条用例精确失败:expect(promise).rejects 收到 resolved 的 Promise;按钮 disabled 为 false)
    • 修复后:22 pass / 0 fail
  • bun run typecheck:5 个 workspace 全部 Exited with code 0。

影响范围

仅改动导入确认路径的校验与按钮禁用条件,不触及解析、草稿生成与仓库层,对合法导入(至少一行无 issue)行为完全不变。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

claimedClaimed by a contributor and currently in progress

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions