tq[MESSAGE]:
[TYPE]: [JIRA]:
This commit is contained in:
@@ -8,10 +8,16 @@
|
||||
├── user (用户表)
|
||||
|
||||
基础数据相关
|
||||
├── brokers (券商表)
|
||||
├── broker (券商表)
|
||||
├── stock_info (股票基本信息表)
|
||||
└── stock_daily_price (股票每日收盘价表)
|
||||
|
||||
财务报表相关(见 财务报表模块-产品与数据库设计.md)
|
||||
├── financial_report (报表主表)
|
||||
├── financial_report_line (行项目表)
|
||||
├── financial_report_stats (预计算统计表)
|
||||
└── financial_item_category (自定义分类表,可选)
|
||||
|
||||
账户相关
|
||||
├── positions (持仓表)
|
||||
├── position_changes (持仓变更记录表)
|
||||
@@ -513,6 +519,157 @@ WHERE p.auto_price_update = true
|
||||
|
||||
---
|
||||
|
||||
## 财务报表相关表设计
|
||||
|
||||
> 详细产品与设计说明见:[财务报表模块-产品与数据库设计.md](./财务报表模块-产品与数据库设计.md)
|
||||
|
||||
### financial_report 报表主表
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| stock_code | VARCHAR(20) | NOT NULL | 股票代码,关联 stock_info |
|
||||
| market | VARCHAR(20) | NOT NULL | 市场,关联 stock_info |
|
||||
| report_date | DATE | NOT NULL | 报告期截止日 |
|
||||
| period_type | VARCHAR(20) | NOT NULL | annual / quarterly |
|
||||
| statement_type | VARCHAR(30) | NOT NULL | balance_sheet / income_statement / cash_flow |
|
||||
| source_file_name | VARCHAR(255) | | 来源文件名 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
```sql
|
||||
CREATE TABLE financial_report (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
stock_code VARCHAR(20) NOT NULL,
|
||||
market VARCHAR(20) NOT NULL,
|
||||
report_date DATE NOT NULL,
|
||||
period_type VARCHAR(20) NOT NULL,
|
||||
statement_type VARCHAR(30) NOT NULL,
|
||||
source_file_name VARCHAR(255),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(stock_code, market, report_date, statement_type),
|
||||
CONSTRAINT fk_financial_report_stock FOREIGN KEY (stock_code, market)
|
||||
REFERENCES stock_info(stock_code, market) ON DELETE CASCADE,
|
||||
CONSTRAINT check_period_type CHECK (period_type IN ('annual', 'quarterly')),
|
||||
CONSTRAINT check_statement_type CHECK (statement_type IN ('balance_sheet', 'income_statement', 'cash_flow'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_financial_report_stock_market ON financial_report(stock_code, market);
|
||||
CREATE INDEX idx_financial_report_report_date ON financial_report(report_date);
|
||||
CREATE INDEX idx_financial_report_statement_type ON financial_report(statement_type);
|
||||
|
||||
CREATE TRIGGER update_financial_report_updated_at
|
||||
BEFORE UPDATE ON financial_report
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON TABLE financial_report IS '财务报表主表:公司+报告期+表型';
|
||||
COMMENT ON COLUMN financial_report.statement_type IS 'balance_sheet(资产负债表)/income_statement(利润表)/cash_flow(现金流量表)';
|
||||
```
|
||||
|
||||
### financial_report_line 行项目表
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| report_id | BIGINT | NOT NULL, FK | 所属报表 |
|
||||
| item_name | VARCHAR(200) | NOT NULL | 科目名称(原文) |
|
||||
| item_code | VARCHAR(50) | | 科目编码(可选) |
|
||||
| value | DECIMAL(24, 6) | | 数值 |
|
||||
| unit | VARCHAR(20) | | 单位 |
|
||||
| row_order | INT | DEFAULT 0 | 行顺序 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
```sql
|
||||
CREATE TABLE financial_report_line (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
report_id BIGINT NOT NULL REFERENCES financial_report(id) ON DELETE CASCADE,
|
||||
item_name VARCHAR(200) NOT NULL,
|
||||
item_code VARCHAR(50),
|
||||
value DECIMAL(24, 6),
|
||||
unit VARCHAR(20),
|
||||
row_order INT DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_financial_report_line_report_id ON financial_report_line(report_id);
|
||||
CREATE INDEX idx_financial_report_line_item_name ON financial_report_line(report_id, item_name);
|
||||
|
||||
COMMENT ON TABLE financial_report_line IS '财务报表行项目,存原始科目名与数值,支持多市场科目差异';
|
||||
```
|
||||
|
||||
### financial_report_stats 预计算统计表
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| report_id | BIGINT | NOT NULL, FK | 所属报表 |
|
||||
| stat_key | VARCHAR(80) | NOT NULL | 如 total_assets, net_profit, roe |
|
||||
| stat_value | DECIMAL(24, 6) | | 统计值 |
|
||||
| dimension | VARCHAR(50) | | 可选维度 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
```sql
|
||||
CREATE TABLE financial_report_stats (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
report_id BIGINT NOT NULL REFERENCES financial_report(id) ON DELETE CASCADE,
|
||||
stat_key VARCHAR(80) NOT NULL,
|
||||
stat_value DECIMAL(24, 6),
|
||||
dimension VARCHAR(50),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(report_id, stat_key, dimension)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_financial_report_stats_report_id ON financial_report_stats(report_id);
|
||||
CREATE INDEX idx_financial_report_stats_stat_key ON financial_report_stats(stat_key);
|
||||
|
||||
CREATE TRIGGER update_financial_report_stats_updated_at
|
||||
BEFORE UPDATE ON financial_report_stats
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON TABLE financial_report_stats IS '报表预计算统计项,上传时计算并写入';
|
||||
```
|
||||
|
||||
### financial_item_category 自定义分类表(可选)
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|---------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| user_id | BIGINT | FK user, 可选 | 多用户时按用户隔离 |
|
||||
| market | VARCHAR(20) | NOT NULL | 市场 |
|
||||
| statement_type | VARCHAR(30) | NOT NULL | 表型 |
|
||||
| item_name_pattern | VARCHAR(200) | NOT NULL | 科目名或匹配模式 |
|
||||
| category_slug | VARCHAR(60) | NOT NULL | 分类标签,如 cash_like, operating |
|
||||
| sort_order | INT | DEFAULT 0 | 展示顺序 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
```sql
|
||||
CREATE TABLE financial_item_category (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT REFERENCES user(user_id) ON DELETE CASCADE,
|
||||
market VARCHAR(20) NOT NULL,
|
||||
statement_type VARCHAR(30) NOT NULL,
|
||||
item_name_pattern VARCHAR(200) NOT NULL,
|
||||
category_slug VARCHAR(60) NOT NULL,
|
||||
sort_order INT DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_financial_item_category_user_market ON financial_item_category(user_id, market, statement_type);
|
||||
|
||||
CREATE TRIGGER update_financial_item_category_updated_at
|
||||
BEFORE UPDATE ON financial_item_category
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON TABLE financial_item_category IS '科目名到自定义分类的映射,用于按类现金/投资资产等聚合展示';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 账户相关表设计
|
||||
|
||||
### positions 持仓表
|
||||
|
||||
214
packages/design-document/我编写的文档/财务报表模块-产品与数据库设计.md
Normal file
214
packages/design-document/我编写的文档/财务报表模块-产品与数据库设计.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 财务报表模块 - 产品与数据库设计
|
||||
|
||||
## 一、需求摘要
|
||||
|
||||
- **用途**:个人研究用,上传并落库公司历次财务报表(资产负债表、利润表、现金流量表),支持按年/按季;后续做绘制、分类、对比查看。
|
||||
- **数据来源**:页面上传 Excel,每次一家公司、一种表型、多期数据;表中部分时间/列需要丢弃,部分需要落库。
|
||||
- **扩展需求**:不同市场(如 A 股 / 港股)科目名不同;需支持自定义分类(如类现金资产、投资资产、应收类、经营类等);常用统计希望预存避免每次计算。
|
||||
|
||||
---
|
||||
|
||||
## 二、三个核心问题的建议
|
||||
|
||||
### 1. 数据库如何支持不同市场、不同公司的财务数据(科目不一致)?
|
||||
|
||||
**思路:用「报表 + 行项目」的通用结构,行项目以「原始科目名 + 数值」存储,不强行统一科目编码。**
|
||||
|
||||
- **不采用**:为每个市场建一套固定列(如 A 股一张表、港股一张表)。维护成本高,且新增市场或科目时要改表结构。
|
||||
- **推荐**:
|
||||
- **报表主表**:一条记录 = 某公司、某报告期、某表型(资产负债表/利润表/现金流量表)、某市场。
|
||||
- **行项目表**:每条记录 = 某报表下的一个科目一行,存「科目名称(原文)」「数值」「单位」「顺序」等。
|
||||
- A 股、港股、美股的科目名可以完全不同,都落在同一张行项目表里,用 `report_id` + `item_name` 区分;前端或分析时再通过「分类配置」把不同市场的不同科目名映射到你自己的分类(类现金、投资资产等)。
|
||||
|
||||
这样:
|
||||
|
||||
- 同一套表支持所有市场。
|
||||
- 新市场只需新数据,不必改表结构。
|
||||
- 科目差异体现在「行项目」的 `item_name` 上,分类通过单独的分类配置表完成(见下)。
|
||||
|
||||
---
|
||||
|
||||
### 2. 常用统计数据如何存储(具体存哪些可后续再定)?
|
||||
|
||||
**思路:单独建「报表统计值」表,按统计项键值存储;具体要存哪些可以逐步加。**
|
||||
|
||||
- **不推荐**:每次从行项目实时聚合(如 SUM、比率),在报表很多时会有性能和复杂度问题。
|
||||
- **推荐**:
|
||||
- 新增表 **financial_report_stats**:
|
||||
- 关联到某一份报表(report_id)。
|
||||
- 存「统计项 key」(如 `total_assets`、`net_profit`、`roe`、`operating_cash_flow`、`current_ratio` 等)。
|
||||
- 存「统计值」(数值型);必要时可加「维度」字段(如按子类型、按口径)。
|
||||
- **何时写入**:在上传/覆盖该报表时,由后端解析并计算这些统计项,写入或更新本表。
|
||||
- **具体要存哪些**:可以在代码里先实现一批常用项(总资产、净资产、净利润、营收、经营现金流、ROE、资产负债率、流动比率等),后续要加新指标时只需加新的 `stat_key` 和计算逻辑,无需改表结构。若希望更灵活,可以用一条 JSONB 列存「多个 stat_key → value」,但查询和索引会稍复杂;键值表更清晰、易查、易扩展。
|
||||
|
||||
这样:常用统计只算一次、落库,后续绘图、筛选、对比都直接读预计算结果。
|
||||
|
||||
---
|
||||
|
||||
### 3. Node + NestJS 能否准确处理 Excel 并落库?
|
||||
|
||||
**可以。用成熟库读 Excel,在服务层做校验与映射,再落库。**
|
||||
|
||||
- **选型**:
|
||||
- **xlsx (SheetJS)**:轻量、使用广,适合「读入整表 → 转 JSON/二维数组」再自己解析。
|
||||
- **exceljs**:API 更细(按单元格、按行),适合格式复杂或需要严格按行列解析的场景。
|
||||
建议:若 Excel 格式相对固定(例如首行/首列是时间或科目名,其余为数值),用 **xlsx** 即可;若有多表、多标题行、合并单元格,可用 **exceljs** 按位置解析。
|
||||
|
||||
- **流程建议**:
|
||||
1. 上传接口:接收 Excel 文件 + 元数据(公司、市场、表型、报告期类型 年/季)。
|
||||
2. 解析服务:
|
||||
- 用 xlsx/exceljs 读取 sheet,得到二维数组或按行列访问。
|
||||
- 根据「市场 + 表型」选择或配置解析规则(例如:第几行是期数/时间,第几列是科目名,哪些列对应要落库的期数)。
|
||||
3. 过滤:只保留你需要的报告期列(丢弃不需要的时间段)。
|
||||
4. 校验:
|
||||
- 必填项:公司、市场、表型、至少一期数据。
|
||||
- 数值列尽量做「可解析为数字」的校验;异常行可记录日志或返回错误行号。
|
||||
5. 落库:
|
||||
- 事务内:插入/更新报表主表 → 插入/更新行项目表(按行写入 item_name + value)。
|
||||
- 若同一公司、同一报告期、同一表型已存在,则采用「覆盖」或「跳过」策略(建议明确一种,并在接口文档说明)。
|
||||
6. 预计算:在报表落库后调用统计计算逻辑,写入 **financial_report_stats**。
|
||||
|
||||
- **准确性**:
|
||||
- 依赖 Excel 格式相对稳定。建议:为每种数据源(如 Wind 导出的 A 股、港交所格式等)定一个「解析模板」或配置(行列位置、科目名到内部 item_name 的映射),在代码里分支或配置化;这样同一来源的 Excel 能稳定、准确解析。
|
||||
- 可在解析后做简单一致性检查(如资产负债表左右是否平衡、利润表/现金流量表关键合计是否与行项目一致),不一致时打日志或返回警告,避免静默错误。
|
||||
|
||||
---
|
||||
|
||||
## 三、数据库设计(与现有库衔接)
|
||||
|
||||
### 3.1 与现有表的关系
|
||||
|
||||
- **公司维度**:复用现有 **stock_info**(stock_code + market 唯一),不再新建「公司表」。一份报表归属到某公司 = 关联到 (stock_code, market)。
|
||||
- **用户**:若后续要做「我的自定义分类」按用户隔离,可关联 **user**;若仅自用单用户,可先不关联,分类配置全局即可。
|
||||
|
||||
### 3.2 表结构概览
|
||||
|
||||
```
|
||||
财务报表相关
|
||||
├── financial_report (报表主表:公司+报告期+表型+市场)
|
||||
├── financial_report_line (行项目:科目名+数值,支持多市场科目差异)
|
||||
├── financial_report_stats (预计算统计项:report_id + stat_key + value)
|
||||
└── financial_item_category (可选,自定义分类:科目名/模式 → 你的分类标签)
|
||||
```
|
||||
|
||||
### 3.3 表定义
|
||||
|
||||
#### financial_report 报表主表
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|----------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| stock_code | VARCHAR(20) | NOT NULL | 股票代码,关联 stock_info |
|
||||
| market | VARCHAR(20) | NOT NULL | 市场,关联 stock_info |
|
||||
| report_date | DATE | NOT NULL | 报告期截止日(如年报 2023-12-31,一季报 2023-03-31) |
|
||||
| period_type | VARCHAR(20) | NOT NULL | 报告期类型:annual / quarterly |
|
||||
| statement_type | VARCHAR(30) | NOT NULL | 表型:balance_sheet / income_statement / cash_flow |
|
||||
| source_file_name | VARCHAR(255) | | 来源文件名(便于追溯) |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
| UNIQUE(stock_code, market, report_date, statement_type) | | | 同一公司同一报告期同一表型唯一 |
|
||||
|
||||
- 同一公司、同一报告期、同一表型只保留一份;上传时若已存在可覆盖(更新 updated_at 并删旧行项目、再插新行项目 + 重算 stats)。
|
||||
|
||||
#### financial_report_line 行项目表
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|----------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| report_id | BIGINT | NOT NULL, FK → financial_report.id | 所属报表 |
|
||||
| item_name | VARCHAR(200) | NOT NULL | 科目名称(原文,如「货币资金」「Cash and bank balances」) |
|
||||
| item_code | VARCHAR(50) | | 可选,科目编码(若 Excel 有) |
|
||||
| value | DECIMAL(24, 6) | | 数值 |
|
||||
| unit | VARCHAR(20) | | 单位(元、千元、万元等,可选) |
|
||||
| row_order | INT | DEFAULT 0 | 行顺序(便于还原表序) |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
- 索引:report_id;(report_id, item_name) 若需防重复可加 UNIQUE,取决于你是否允许同名多行(多数情况下一表内科目名不重复)。
|
||||
|
||||
#### financial_report_stats 预计算统计表
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|----------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| report_id | BIGINT | NOT NULL, FK → financial_report.id | 所属报表 |
|
||||
| stat_key | VARCHAR(80) | NOT NULL | 统计项键,如 total_assets, net_profit, roe, current_ratio |
|
||||
| stat_value | DECIMAL(24, 6) | | 统计值 |
|
||||
| dimension | VARCHAR(50) | | 可选维度(如口径、子类型) |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
| UNIQUE(report_id, stat_key, dimension) | | | 同一报表同一 key(及维度)唯一,便于 upsert |
|
||||
|
||||
- 具体 stat_key 可在代码中枚举或配置,逐步增加(如 total_assets, total_liabilities, net_assets, revenue, net_profit, operating_cash_flow, roe, roa, current_ratio, quick_ratio, asset_liability_ratio 等)。
|
||||
|
||||
#### financial_item_category 自定义分类(可选)
|
||||
|
||||
| 字段名 | 数据类型 | 约束 | 说明 |
|
||||
|--------|----------|------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 主键 |
|
||||
| user_id | BIGINT | FK → user.user_id, 可选 | 若多用户则按用户隔离;单用户可 NULL 表示全局 |
|
||||
| market | VARCHAR(20) | NOT NULL | 市场 |
|
||||
| statement_type | VARCHAR(30) | NOT NULL | 表型 |
|
||||
| item_name_pattern | VARCHAR(200) | NOT NULL | 科目名或匹配模式(如精确「货币资金」或简单通配) |
|
||||
| category_slug | VARCHAR(60) | NOT NULL | 你的分类标签,如 cash_like, investment, receivable, operating |
|
||||
| sort_order | INT | DEFAULT 0 | 展示顺序 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
- 查询时:根据报表的 market + statement_type,用 line 的 item_name 去匹配 item_name_pattern,得到 category_slug,再在前端或 API 里按 category 聚合/展示。后续若要支持「多对一」(多个科目同一分类),可多条记录相同 category_slug、不同 item_name_pattern。
|
||||
|
||||
---
|
||||
|
||||
## 四、Excel 解析与落库流程(NestJS 实现要点)
|
||||
|
||||
1. **上传接口**
|
||||
- 使用现有或新建 `StorageController` 的 multipart 接口,接受 `file` + body(stock_code, market, statement_type, period_type, report_dates_to_import[] 等)。
|
||||
- 文件类型校验:`.xlsx` / `.xls`(若需要),大小限制(如 10MB)。
|
||||
|
||||
2. **解析服务(新模块,如 FinancialReportParseService)**
|
||||
- 使用 **xlsx** 或 **exceljs** 读 buffer。
|
||||
- 按 market + statement_type 选择解析规则(可从配置或策略 map 读取):
|
||||
- 表头行、科目列、数据列对应关系;
|
||||
- 哪些列对应「要落库的报告期」(对应 report_date)。
|
||||
- 输出:`{ reportDate, lines: [ { itemName, value, rowOrder } ] }[]`,只包含需要落库的期数。
|
||||
|
||||
3. **报表服务(FinancialReportService)**
|
||||
- 对每个 reportDate:
|
||||
- 若存在则删除旧 line、旧 stats;
|
||||
- 插入 financial_report(或更新 updated_at);
|
||||
- 批量插入 financial_report_line;
|
||||
- 调用 StatsService 计算并写入 financial_report_stats。
|
||||
- 事务包裹,保证要么全部成功要么全部回滚。
|
||||
|
||||
4. **统计计算服务(FinancialReportStatsService)**
|
||||
- 输入:report_id,以及该 report 的 lines(或从 DB 再查一次)。
|
||||
- 根据 statement_type 计算:
|
||||
- 资产负债表:总资产、总负债、净资产、流动比率、速动比率等;
|
||||
- 利润表:营收、净利润、毛利率等;
|
||||
- 现金流量表:经营/投资/筹资现金流等。
|
||||
- 写入 financial_report_stats(upsert by report_id + stat_key + dimension)。
|
||||
|
||||
5. **校验与错误处理**
|
||||
- 解析阶段:无法识别表头、缺少必填列时返回 400 + 明确错误信息。
|
||||
- 数值解析失败:可记录行号并跳过该行或整表拒绝,建议至少日志 + 返回「第 N 行解析失败」。
|
||||
- 若启用资产负债表平衡校验:资产合计 ≈ 负债+权益合计,不一致时记录 warning 或返回提示,不阻塞落库(因有时四舍五入或口径差异)。
|
||||
|
||||
---
|
||||
|
||||
## 五、后续可扩展点
|
||||
|
||||
- **多 sheet**:一个 Excel 内多 sheet 对应多表型或多公司时,可在解析时循环 sheet,每个 sheet 对应一个 (company, statement_type) 的导入。
|
||||
- **科目标准化**:若希望跨市场对比同一「逻辑科目」,可在 line 表增加 optional 的 `standard_item_code`,由解析时或后台任务根据 item_name + market 映射到统一编码。
|
||||
- **版本与审计**:若需保留历史版本,可在 financial_report 加 version 或不改主表、仅保留「每次上传生成新 report_id 并软删旧版」的策略。
|
||||
- **前端**:上传页选择公司(从 stock_info 查)、市场、表型、文件;解析结果可先预览「将落库的期数 + 行数」,确认后再写入;列表页按公司/时间/表型筛选,详情页按自定义分类展示行项目与预计算统计、并做简单图表。
|
||||
|
||||
---
|
||||
|
||||
## 六、总结
|
||||
|
||||
| 问题 | 建议 |
|
||||
|------|------|
|
||||
| 不同市场、不同公司科目不一致 | 用「报表主表 + 行项目表」存原始科目名与数值;分类用单独配置表做「科目名 → 自定义分类」映射。 |
|
||||
| 常用统计不想每次算 | 用 financial_report_stats 表存 (report_id, stat_key, stat_value),在上传/更新报表时计算并写入;具体指标可后续逐步加。 |
|
||||
| Node + NestJS 能否准确读 Excel 并落库 | 可以;用 xlsx 或 exceljs 解析,固定或配置化每种数据源格式,在服务层做校验与过滤后事务落库,并在落库后写 stats。 |
|
||||
|
||||
按上述设计,你可以在不开放的前提下,先实现「上传 → 解析 → 落库 → 预计算」闭环,再在现有库上扩展列表/筛选/分类展示与绘图。
|
||||
Reference in New Issue
Block a user