First Commit

This commit is contained in:
yangfan
2025-10-17 13:40:44 +08:00
commit c21e3189e3
16 changed files with 4477 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(pip install:*)",
"Bash(python3:*)",
"Bash(source venv/bin/activate)"
],
"deny": [],
"ask": []
}
}

BIN
AccountingEntries.xlsx Normal file

Binary file not shown.

79
CHANGELOG.md Normal file
View File

@@ -0,0 +1,79 @@
# 更新日志
## v1.2 (2025-10-17)
### 新功能
#### 1. 汇率文件支持
- **功能**: 支持从 `exchange_rate.txt` 文件读取汇率
- **优先级**: 文件汇率 > 程序默认汇率
- **使用方法**:
```bash
echo "7.25" > exchange_rate.txt
python3 generate_accounting_entries.py
```
#### 2. 智能汇率验证
- **范围检查**: 汇率必须在 0.1 ~ 100 范围内
- **格式验证**: 自动检测非数字格式
- **错误处理**: 异常情况自动回退到默认汇率 (7.1072)
#### 3. 详细日志输出
程序运行时会显示汇率来源:
- 从文件读取: `从 exchange_rate.txt 读取汇率: 7.25`
- 文件不存在: `汇率文件 exchange_rate.txt 不存在,使用默认汇率: 7.1072`
- 格式错误: `汇率文件 exchange_rate.txt 中的值无法解析,使用默认汇率: 7.1072`
- 值不合理: `汇率文件中的值 150.0 不合理,使用默认汇率: 7.1072`
### 代码修改
**文件**: `generate_accounting_entries.py`
1. **新增导入**:
- `import os` - 用于文件检查
2. **新增函数**:
- `load_exchange_rate()` (line 16-52) - 汇率加载和验证逻辑
3. **修改函数签名**:
- `create_accounting_entries(data, exchange_rate)` - 添加汇率参数
4. **修改 main 函数**:
- 调用 `load_exchange_rate()` 获取汇率
- 传递汇率参数到 `create_accounting_entries()`
### 文档更新
1. **User.md**:
- 更新"汇率配置"章节,添加文件方法说明
- 更新 Q4 常见问题
- 更新文件说明表,添加 `exchange_rate.txt`
- 添加 v1.2 更新记录
2. **CLAUDE.md**:
- 更新"Exchange Rate"配置章节
- 添加详细的错误处理说明和示例
- 更新版本历史
### 测试结果
✅ 正常汇率文件 (7.25): 成功读取
✅ 文件不存在: 使用默认汇率
✅ 非数字格式 (abc): 使用默认汇率
✅ 不合理值 (150): 使用默认汇率
### 向后兼容性
- ✅ 完全向后兼容
- ✅ 不影响现有功能
- ✅ 无 `exchange_rate.txt` 文件时使用程序默认值
### 文件清单
| 文件 | 状态 | 说明 |
|------|------|------|
| `generate_accounting_entries.py` | 已修改 | 添加汇率文件读取功能 |
| `exchange_rate.txt` | 新增 | 汇率配置文件 (可选) |
| `User.md` | 已更新 | 用户文档 |
| `CLAUDE.md` | 已更新 | 开发文档 |
| `CHANGELOG.md` | 新增 | 本文件 |

236
CLAUDE.md Normal file
View File

@@ -0,0 +1,236 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
财务Excel数据处理系统 (Financial Excel Data Processing System) - A Python-based automation system for processing financial Excel data, extracting payment information, and generating standardized accounting entries with data validation and error marking capabilities.
**Language**: Chinese (中文) - All documentation, comments, and output are in Chinese.
## Development Commands
### Setup and Installation
```bash
# Install dependencies (system-wide)
pip install openpyxl --break-system-packages
# OR use virtual environment (recommended)
python3 -m venv venv
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
pip install openpyxl
```
### Run the Processing Pipeline
```bash
# Step 1: Extract data from Excel to JSON
python3 process_excel.py
# Step 2: Generate accounting entries Excel
python3 generate_accounting_entries.py
# Optional: Analyze Excel structure (debugging tool)
python3 analyze_excel.py
```
### Verify Installation
```bash
python3 -c "import openpyxl; print(openpyxl.__version__)"
```
## Architecture and Data Flow
### Processing Pipeline
```
data/data.xlsx (Raw financial data)
[process_excel.py] - Extract payment records
res.json (Intermediate JSON data)
[generate_accounting_entries.py] - Generate accounting entries
AccountingEntries.xlsx (Final accounting entry table)
```
### Key Components
1. **`process_excel.py`** - Excel Data Extraction Engine
- Handles merged and non-merged cells in column F (ReceivedAmount)
- Extracts orders from rows within merged cell ranges
- Validates amounts: `ReceivedAmount + HandlingFee ≈ Sum(Order[].Amount)` (tolerance: 0.01)
- Uses `data_only=True` to read formula results from column O
2. **`generate_accounting_entries.py`** - Accounting Entry Generator
- Creates debit/credit entries following Chinese accounting standards
- Merges cells for same ReceivedAmount groups
- Marks validation failures with pink background (#FAD1D4)
- Applies fixed exchange rate to currency conversions
3. **`analyze_excel.py`** - Structure Analysis Utility
- Debugging tool to inspect merged cells
- Preview data structure
### Data Structures
#### res.json Schema
```json
[
{
"ReceivedAmount": 12125, // Column F - supports merged cells
"HandlingFee": 25, // Column G - null becomes 0
"Order": [
{
"OrderNum": "XLRQD300T25", // Column H
"Amount": 550, // Column I
"AccountName": "24台湾长荣航运" // Column O - formula result
}
],
"checkRes": true // Validation: amount match within 0.01
}
]
```
#### Accounting Entry Rules
**For each ReceivedAmount record:**
1. **Debit Entry (到账金额)** - 1 record per ReceivedAmount
- Account: `1002.02` - 银行存款 - 中行USD
- Currency: 美元 (USD)
- Amount: `ReceivedAmount × EXCHANGE_RATE`
2. **Debit Entry (手续费)** - Only if HandlingFee > 0
- Account: `5603.03` - 财务费用-手续费
- Currency: 人民币 (RMB)
- Amount: `HandlingFee × EXCHANGE_RATE`
3. **Credit Entries (订单明细)** - 1 record per Order
- Account: `1122` - 应收账款
- Currency: 美元 (USD)
- Amount: `Order.Amount × EXCHANGE_RATE`
- **Display Order.Amount in "应收账款" column**
- Skip orders where Amount is null
### Special Processing Logic
#### Merged Cell Handling (process_excel.py:33-69)
- `get_f_column_ranges()`: Identifies all data ranges in column F
- Handles mixed scenarios: merged and non-merged cells
- Non-merged cells are treated as single-row ranges
- Merged cell value read from top-left corner (min_row, min_col)
#### Validation and Error Marking
- **checkRes calculation**: `abs((ReceivedAmount + HandlingFee) - Sum(Order[].Amount)) < 0.01`
- **Error marking**: Pink background (#FAD1D4) applied to all entries where checkRes = false
- Background color applied **before** cell merging to ensure visibility
#### Cell Merging Strategy (generate_accounting_entries.py:178-206)
- Groups entries by `(ReceivedAmount, HandlingFee)` key
- Merges "到账金额" (column A) and "手续费" (column B) for consecutive rows
- Centers content vertically and horizontally
- Re-applies background color after merging
## Configuration
### Exchange Rate
**Priority**: Program reads exchange rate in the following order:
1. **From `exchange_rate.txt` file** (if exists in current directory)
- Create a text file named `exchange_rate.txt` containing only the exchange rate value
- Example: `echo "7.25" > exchange_rate.txt`
- Validation: Rate must be between 0.1 and 100, otherwise falls back to default
2. **From default constant** (if file doesn't exist or contains invalid value)
- Location: `generate_accounting_entries.py:13`
- Default value: `7.1072`
**Error Handling** (generate_accounting_entries.py:16-52):
- File not found → Use default rate
- Invalid format (non-numeric) → Use default rate
- Unreasonable value (<0.1 or >100) → Use default rate
- Any other error → Use default rate
**Examples**:
```bash
# Set custom exchange rate
echo "7.25" > exchange_rate.txt
# Program will display which rate is being used
python3 generate_accounting_entries.py
# Output: "从 exchange_rate.txt 读取汇率: 7.25"
# Remove file to use default
rm exchange_rate.txt
python3 generate_accounting_entries.py
# Output: "汇率文件 exchange_rate.txt 不存在,使用默认汇率: 7.1072"
```
### Column Mapping (data/data.xlsx)
| Field | Column | Notes |
|-------|--------|-------|
| ReceivedAmount | F (6) | Supports merged cells |
| HandlingFee | G (7) | Null → 0 |
| OrderNum | H (8) | Skip if empty |
| Amount | I (9) | Null orders skipped |
| AccountName | O (15) | Formula result (data_only=True) |
### Excel Output Format
**Column widths**: `[12, 10, 18, 12, 25, 25, 8, 15, 25, 25, 10, 10, 12, 15]`
**Headers**:
```
到账金额, 手续费, 订单号, 应收账款, 金蝶名称,
摘要, 借/贷, 科目代码(*), 科目名称(*),
核算项目, 币别, 汇率, 原币金额, 金额
```
**Header style**: Bold, blue background (#CCE5FF), centered
## Important Implementation Notes
1. **Data starts from row 2** (row 1 is header)
2. **Formula handling**: Always use `data_only=True` when loading workbook to read calculated values
3. **Order filtering**: Skip rows where OrderNum is None or empty string
4. **Amount precision**: All calculations rounded to 2 decimal places
5. **UTF-8 encoding**: All files use UTF-8 encoding
6. **Error handling**:
- File not found: Exit with error message
- Invalid sheet: Exit with error message
- Invalid data: Log and skip row
- checkRes=false: Mark but continue processing
7. **Performance**: Handles 300+ rows of Excel data generating 500+ accounting entries in <10 seconds
## Testing Guidance
### Test Scenarios (from task.md:253-272)
1. **Single order, no fee**: ReceivedAmount=695, HandlingFee=0, Order[0].Amount=695
- Expected: 2 entries (debit + credit), checkRes=true
2. **Multiple orders with fee**: ReceivedAmount=12125, HandlingFee=25, Orders=[550, 11600]
- Expected: 4 entries, checkRes=true
3. **Amount mismatch**: ReceivedAmount=17270, HandlingFee=0, Orders=[5676, 11450]
- Expected: checkRes=false, pink background on all entries
4. **Null order amount**: ReceivedAmount=240, HandlingFee=25, Order[0].Amount=null
- Expected: Skip order credit entry, no error
## Version History
- **v1.2** (2025-10-17): Added exchange rate file support (`exchange_rate.txt`), intelligent rate validation, improved error handling
- **v1.1** (2025-01-17): Optimized accounting rules - removed redundant debit entries, simplified single-order logic
- **v1.0** (2025-01-17): Initial release with extraction, generation, validation, and error marking features

360
User.md Normal file
View File

@@ -0,0 +1,360 @@
# 财务Excel数据处理程序 - 使用说明
## 概述
本程序用于处理财务Excel数据,自动提取收款信息并生成会计分录表。
---
## 前置要求
### 运行环境
- **Python版本**: Python 3.x
- **操作系统**: Windows / Linux / macOS
### 依赖库安装
本程序依赖以下Python第三方包:
#### 1. openpyxl
**用途**: Excel文件读写操作
**安装命令**:
```bash
pip install openpyxl --break-system-packages
```
或使用虚拟环境(推荐):
```bash
# 创建虚拟环境
python3 -m venv venv
# 激活虚拟环境
# Linux/Mac:
source venv/bin/activate
# Windows:
venv\Scripts\activate
# 安装依赖
pip install openpyxl
```
**版本要求**: 建议使用最新稳定版本
#### 验证安装
安装完成后,可以通过以下命令验证:
```bash
python3 -c "import openpyxl; print(openpyxl.__version__)"
```
如果输出版本号(例如: 3.1.2),则说明安装成功。
---
## 功能说明
### 1. 数据提取 (`process_excel.py`)
从Excel文件中提取财务数据并输出为JSON格式。
**输入文件**: `data/data.xlsx` (Sheet1)
**输出文件**: `res.json`
**提取字段**:
- `ReceivedAmount`: 实收金额 (F列)
- `HandlingFee`: 手续费 (G列空值记为0)
- `Order`: 订单列表
- `OrderNum`: 订单号 (H列)
- `Amount`: 金额 (I列)
- `AccountName`: 账户名称 (O列)
- `checkRes`: 验证结果 (实收金额+手续费 = 订单金额之和)
**运行命令**:
```bash
python3 process_excel.py
```
**输出示例**:
```json
[
{
"ReceivedAmount": 12125,
"HandlingFee": 25,
"Order": [
{
"OrderNum": "XLRQD300T25",
"Amount": 550,
"AccountName": "24台湾长荣航运"
}
],
"checkRes": true
}
]
```
---
### 2. 会计分录生成 (`generate_accounting_entries.py`)
根据`res.json`生成标准会计分录表。
**输入文件**: `res.json`
**输出文件**: `AccountingEntries.xlsx`
**运行命令**:
```bash
python3 generate_accounting_entries.py
```
---
## 会计分录规则
### 基本规则
每笔到账金额产生以下分录:
1. **到账金额 - 借方** (每笔记录1条)
- 科目代码: `1002.02`
- 科目名称: `银行存款 - 中行USD`
- 币别: 美元
- 原币金额: ReceivedAmount
- 金额: ReceivedAmount × 汇率
2. **手续费 - 借方** (如果手续费>0)
- 科目代码: `5603.03`
- 科目名称: `财务费用-手续费`
- 币别: 人民币
- 金额: HandlingFee × 汇率
3. **订单明细 - 贷方** (每个Order记录1条)
- 科目代码: `1122`
- 科目名称: `应收账款`
- 应收账款: Order.Amount (显示在"应收账款"列)
- 币别: 美元
- 原币金额: Order.Amount
- 金额: Order.Amount × 汇率
### 特殊规则
- **金额验证**: checkRes为false的记录,所有相关分录行标记为粉红色背景(#FAD1D4)
- **单元格合并**: 同一笔到账金额的所有分录,"到账金额"和"手续费"列会合并显示
- **空值处理**: 订单金额为空的订单会被跳过,不生成贷方分录
---
## 汇率配置
程序支持两种方式设置汇率:
### 方法一: 使用汇率文件 (推荐)
在程序目录下创建 `exchange_rate.txt` 文件,文件中只包含汇率数值。
**操作步骤**:
```bash
# 创建汇率文件
echo "7.25" > exchange_rate.txt
# 运行程序,会自动读取该文件中的汇率
python3 generate_accounting_entries.py
```
**输出示例**:
```
从 exchange_rate.txt 读取汇率: 7.25
使用汇率: 7.25
```
**注意事项**:
- 汇率值必须是有效数字
- 汇率范围: 0.1 ~ 100 (超出范围会使用默认汇率)
- 文件格式错误会自动使用默认汇率
### 方法二: 修改程序默认值
编辑 `generate_accounting_entries.py` 文件第13行:
```python
DEFAULT_EXCHANGE_RATE = 7.1072 # 修改此默认值
```
**默认汇率**: 7.1072
**优先级**: 汇率文件 > 程序默认值
---
## 文件说明
| 文件名 | 说明 |
|--------|------|
| `data/data.xlsx` | 原始财务数据Excel文件 |
| `process_excel.py` | Excel数据提取程序 |
| `res.json` | 提取的财务数据(JSON格式) |
| `generate_accounting_entries.py` | 会计分录生成程序 |
| `AccountingEntries.xlsx` | 生成的会计分录表 |
| `exchange_rate.txt` | 汇率配置文件(可选) |
| `analyze_excel.py` | Excel结构分析工具(可选) |
---
## 使用流程
### 标准流程
1. **准备数据**
```bash
# 确保 data/data.xlsx 文件存在
```
2. **提取数据**
```bash
python3 process_excel.py
# 输出: res.json
```
3. **生成会计分录**
```bash
python3 generate_accounting_entries.py
# 输出: AccountingEntries.xlsx
```
4. **检查结果**
- 打开 `AccountingEntries.xlsx`
- 粉红色背景的行表示金额不匹配,需要核对
---
## 数据验证
### checkRes字段说明
- **true**: 实收金额 + 手续费 = 订单金额之和 (误差<0.01)
- **false**: 金额不匹配,需要人工核对
### 识别问题记录
在 `AccountingEntries.xlsx` 中:
- 粉红色背景(#FAD1D4)的行表示该笔记录金额不匹配
- 建议优先核对这些记录
---
## 常见问题
### Q1: 如何处理合并单元格?
程序自动处理F列的合并单元格:
- 合并单元格: 该区域内所有行属于同一笔记录
- 非合并单元格: 每行单独处理
### Q2: 手续费为空怎么办?
程序自动将空值记为0。
### Q3: 订单金额为空怎么办?
金额为空的订单会被跳过,不生成贷方分录。
### Q4: 如何修改汇率?
**推荐方法**: 创建 `exchange_rate.txt` 文件,输入汇率值即可。
```bash
echo "7.25" > exchange_rate.txt
```
**备选方法**: 编辑 `generate_accounting_entries.py` 第13行的 `DEFAULT_EXCHANGE_RATE` 值。
### Q5: Excel列对应关系
| 字段 | Excel列 | 说明 |
|------|---------|------|
| ReceivedAmount | F | 实收金额 |
| HandlingFee | G | 手续费 |
| OrderNum | H | 订单号 |
| Amount | I | 解款金额 |
| AccountName | O | 金蝶名称 |
---
## 输出示例
### res.json 示例
```json
[
{
"ReceivedAmount": 9455,
"HandlingFee": 25,
"Order": [
{
"OrderNum": "XLRQD063M25",
"Amount": 9480,
"AccountName": "20 Transsea"
}
],
"checkRes": true
}
]
```
### AccountingEntries.xlsx 结构
| 到账金额 | 手续费 | 订单号 | 应收账款 | 金蝶名称 | 摘要 | 借/贷 | 科目代码 | 科目名称 | 核算项目 | 币别 | 汇率 | 原币金额 | 金额 |
|---------|--------|--------|---------|---------|------|-------|----------|----------|----------|------|------|----------|------|
| 9455 | 25 | XLRQD063M25 | | 20 Transsea | 美金收款-XLRQD063M25 | 借 | 1002.02 | 银行存款 - 中行USD | 20 Transsea | 美元 | 7.1072 | 9455 | 67,188.54 |
| (合并) | (合并) | XLRQD063M25 | | | 美金收款-XLRQD063M25 | 借 | 5603.03 | 财务费用-手续费 | | 人民币 | | | 177.68 |
| (合并) | (合并) | XLRQD063M25 | 9480 | 20 Transsea | 美金收款-XLRQD063M25 | 贷 | 1122 | 应收账款 | 20 Transsea | 美元 | 7.1072 | 9480 | 67,366.22 |
---
## 注意事项
1. **数据起始行**: 程序从第2行开始读取(第1行为表头)
2. **汇率固定**: 默认使用固定汇率7.1072,不会从Excel读取
3. **金额精度**: 计算结果保留2位小数
4. **合并单元格**: "到账金额"和"手续费"列会自动合并
5. **背景标记**: checkRes=false的记录用粉红色标记
6. **文件编码**: 所有文件使用UTF-8编码
---
## 更新记录
- **v1.2** - 2025-10-17
- 新增汇率文件支持: 可通过 `exchange_rate.txt` 设置汇率
- 智能汇率验证: 自动检测异常汇率并回退到默认值
- 改进错误处理: 汇率文件异常时提供详细提示信息
- **v1.1** - 2025-01-17
- 优化会计分录规则: 移除"到账金额-贷方"记录
- 每个Order记录都生成对应的贷方分录
- Order生成的贷方记录在"应收账款"列显示Amount金额
- 简化单订单处理逻辑
- **v1.0** - 2025-01-17
- Excel数据提取
- 会计分录生成
- 合并单元格支持
- 金额验证功能
- 背景色标记
---
## 联系支持
如遇问题或需要帮助,请检查:
1. Excel文件格式是否正确
2. 列映射是否匹配
3. Python依赖是否已安装
4. 汇率设置是否正确

43
analyze_excel.py Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
"""分析Excel文件结构,查看合并单元格信息"""
from openpyxl import load_workbook
# 加载Excel文件
wb = load_workbook('data/data.xlsx')
ws = wb['Sheet1']
print("=" * 80)
print("合并单元格信息:")
print("=" * 80)
# 获取所有合并单元格
merged_cells = list(ws.merged_cells.ranges)
print(f"总共有 {len(merged_cells)} 个合并单元格区域\n")
# 筛选F列的合并单元格
f_column_merges = []
for merge in merged_cells:
# 检查是否在F列
if merge.min_col == 6 and merge.max_col == 6: # F列是第6列
f_column_merges.append(merge)
print(f"F列合并: {merge} (行{merge.min_row}{merge.max_row})")
print(f"\nF列合并单元格数量: {len(f_column_merges)}")
print("\n" + "=" * 80)
print("前10行数据预览 (F, G, H, I, O列):")
print("=" * 80)
print(f"{'行号':<6} {'F列(实收)':<15} {'G列(手续费)':<15} {'H列(订单号)':<20} {'I列(金额)':<15} {'O列(账户)':<20}")
print("-" * 100)
for row in range(1, min(11, ws.max_row + 1)):
f_val = ws.cell(row, 6).value # F列
g_val = ws.cell(row, 7).value # G列
h_val = ws.cell(row, 8).value # H列
i_val = ws.cell(row, 9).value # I列
o_val = ws.cell(row, 15).value # O列
print(f"{row:<6} {str(f_val)[:15]:<15} {str(g_val)[:15]:<15} {str(h_val)[:20]:<20} {str(i_val)[:15]:<15} {str(o_val)[:20]:<20}")
print(f"\n总行数: {ws.max_row}")

BIN
data/data.xlsx Normal file

Binary file not shown.

BIN
data/~$data.xlsx Normal file

Binary file not shown.

1
exchange_rate.txt Normal file
View File

@@ -0,0 +1 @@
7.1072

View File

@@ -0,0 +1,308 @@
#!/usr/bin/env python3
"""
根据res.json生成会计分录表AccountingEntries.xlsx
"""
import json
import os
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill
from typing import List, Dict, Any
# 默认汇率
DEFAULT_EXCHANGE_RATE = 7.1072
def load_exchange_rate() -> float:
"""
从exchange_rate.txt文件中读取汇率
如果文件不存在或值异常,则使用默认汇率
返回:
汇率值
"""
rate_file = 'exchange_rate.txt'
# 检查文件是否存在
if not os.path.exists(rate_file):
print(f"汇率文件 {rate_file} 不存在,使用默认汇率: {DEFAULT_EXCHANGE_RATE}")
return DEFAULT_EXCHANGE_RATE
try:
# 读取文件内容
with open(rate_file, 'r', encoding='utf-8') as f:
content = f.read().strip()
# 尝试转换为浮点数
rate = float(content)
# 验证汇率是否合理 (假设汇率应该在 0.1 到 100 之间)
if rate <= 0 or rate > 100:
print(f"汇率文件中的值 {rate} 不合理,使用默认汇率: {DEFAULT_EXCHANGE_RATE}")
return DEFAULT_EXCHANGE_RATE
print(f"{rate_file} 读取汇率: {rate}")
return rate
except ValueError:
print(f"汇率文件 {rate_file} 中的值无法解析,使用默认汇率: {DEFAULT_EXCHANGE_RATE}")
return DEFAULT_EXCHANGE_RATE
except Exception as e:
print(f"读取汇率文件时发生错误: {e},使用默认汇率: {DEFAULT_EXCHANGE_RATE}")
return DEFAULT_EXCHANGE_RATE
def create_accounting_entries(data: List[Dict[str, Any]], exchange_rate: float) -> List[Dict[str, Any]]:
"""
根据财务数据生成会计分录
参数:
data: res.json中的财务数据
exchange_rate: 汇率
返回:
会计分录列表
"""
entries = []
for record in data:
received_amount = record["ReceivedAmount"]
handling_fee = record["HandlingFee"]
orders = record["Order"]
check_res = record.get("checkRes", True) # 获取checkRes字段
# 跳过无效记录
if received_amount is None or not orders:
continue
# 1. ReceivedAmount 借方记录
# 科目代码: 1002.02, 科目名称: 银行存款 - 中行USD
for order in orders:
order_num = order["OrderNum"]
account_name = order["AccountName"]
entry_debit = {
"到账金额": received_amount,
"手续费": handling_fee,
"订单号": order_num,
"应收账款": "",
"金蝶名称": account_name,
"摘要": f"美金收款-{order_num}",
"借/贷": "",
"科目代码(*)": "1002.02",
"科目名称(*)": "银行存款 - 中行USD",
"核算项目": account_name,
"币别": "美元",
"汇率": exchange_rate,
"原币金额": received_amount,
"金额": round(received_amount * exchange_rate, 2),
"_check_res": check_res # 添加checkRes标记
}
entries.append(entry_debit)
break # 只记录一次借方
# 2. 手续费借方记录 (如果手续费>0)
# 科目代码: 5603.03, 科目名称: 财务费用-手续费
if handling_fee > 0:
# 获取第一个订单号用于摘要
first_order_num = orders[0]["OrderNum"] if orders else ""
entry_fee = {
"到账金额": received_amount,
"手续费": handling_fee,
"订单号": first_order_num,
"应收账款": "",
"金蝶名称": "",
"摘要": f"美金收款-{first_order_num}",
"借/贷": "",
"科目代码(*)": "5603.03",
"科目名称(*)": "财务费用-手续费",
"核算项目": "",
"币别": "人民币",
"汇率": "",
"原币金额": "",
"金额": round(handling_fee * exchange_rate, 2),
"_check_res": check_res # 添加checkRes标记
}
entries.append(entry_fee)
# 3. Order列表中每一项的贷方记录
# 科目代码: 1122, 科目名称: 应收账款
for order in orders:
order_num = order["OrderNum"]
amount = order["Amount"]
account_name = order["AccountName"]
# 跳过金额为空的订单
if amount is None:
continue
entry_order = {
"到账金额": received_amount,
"手续费": handling_fee,
"订单号": order_num,
"应收账款": amount, # 填入Order的Amount金额
"金蝶名称": account_name,
"摘要": f"美金收款-{order_num}",
"借/贷": "",
"科目代码(*)": "1122",
"科目名称(*)": "应收账款",
"核算项目": account_name,
"币别": "美元",
"汇率": exchange_rate,
"原币金额": amount,
"金额": round(amount * exchange_rate, 2),
"_check_res": check_res # 添加checkRes标记
}
entries.append(entry_order)
return entries
def save_to_excel(entries: List[Dict[str, Any]], output_file: str):
"""
将会计分录保存为Excel文件
参数:
entries: 会计分录列表
output_file: 输出文件路径
"""
from openpyxl.utils import get_column_letter
wb = Workbook()
ws = wb.active
ws.title = "会计分录"
# 定义表头
headers = [
"到账金额", "手续费", "订单号", "应收账款", "金蝶名称",
"摘要", "借/贷", "科目代码(*)", "科目名称(*)",
"核算项目", "币别", "汇率", "原币金额", "金额"
]
# 写入表头
for col_idx, header in enumerate(headers, start=1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.font = Font(bold=True)
cell.fill = PatternFill(start_color="CCE5FF", end_color="CCE5FF", fill_type="solid")
cell.alignment = Alignment(horizontal="center", vertical="center")
# 写入数据
error_fill = PatternFill(start_color="FAD1D4", end_color="FAD1D4", fill_type="solid")
for row_idx, entry in enumerate(entries, start=2):
check_res = entry.get("_check_res", True)
# 写入数据
ws.cell(row=row_idx, column=1, value=entry.get("到账金额", ""))
ws.cell(row=row_idx, column=2, value=entry.get("手续费", ""))
ws.cell(row=row_idx, column=3, value=entry.get("订单号", ""))
ws.cell(row=row_idx, column=4, value=entry.get("应收账款", ""))
ws.cell(row=row_idx, column=5, value=entry.get("金蝶名称", ""))
ws.cell(row=row_idx, column=6, value=entry.get("摘要", ""))
ws.cell(row=row_idx, column=7, value=entry.get("借/贷", ""))
ws.cell(row=row_idx, column=8, value=entry.get("科目代码(*)", ""))
ws.cell(row=row_idx, column=9, value=entry.get("科目名称(*)", ""))
ws.cell(row=row_idx, column=10, value=entry.get("核算项目", ""))
ws.cell(row=row_idx, column=11, value=entry.get("币别", ""))
ws.cell(row=row_idx, column=12, value=entry.get("汇率", ""))
ws.cell(row=row_idx, column=13, value=entry.get("原币金额", ""))
ws.cell(row=row_idx, column=14, value=entry.get("金额", ""))
# 先设置所有背景颜色(在合并单元格之前)
for row_idx, entry in enumerate(entries, start=2):
check_res = entry.get("_check_res", True)
if not check_res:
for col_idx in range(1, 15):
ws.cell(row=row_idx, column=col_idx).fill = error_fill
# 合并同一ReceivedAmount的"到账金额"和"手续费"单元格
merge_groups = {} # {(received_amount, handling_fee): [row_start, row_end]}
for row_idx, entry in enumerate(entries, start=2):
received_amount = entry.get("到账金额", "")
handling_fee = entry.get("手续费", "")
key = (received_amount, handling_fee)
if key not in merge_groups:
merge_groups[key] = [row_idx, row_idx]
else:
# 检查是否连续
if row_idx == merge_groups[key][1] + 1:
merge_groups[key][1] = row_idx
else:
# 不连续,创建新组
merge_groups[f"{key}_{row_idx}"] = [row_idx, row_idx]
# 执行合并
for key, (start_row, end_row) in merge_groups.items():
if start_row < end_row: # 只有多于1行时才合并
# 合并"到账金额"列(A列)
ws.merge_cells(f'A{start_row}:A{end_row}')
ws.cell(start_row, 1).alignment = Alignment(horizontal="center", vertical="center")
# 合并"手续费"列(B列)
ws.merge_cells(f'B{start_row}:B{end_row}')
ws.cell(start_row, 2).alignment = Alignment(horizontal="center", vertical="center")
# 合并后重新应用背景颜色(确保合并单元格也有背景色)
for row_idx, entry in enumerate(entries, start=2):
check_res = entry.get("_check_res", True)
if not check_res:
for col_idx in range(1, 15):
ws.cell(row=row_idx, column=col_idx).fill = error_fill
# 调整列宽
column_widths = [12, 10, 18, 12, 25, 25, 8, 15, 25, 25, 10, 10, 12, 15]
for col_idx, width in enumerate(column_widths, start=1):
ws.column_dimensions[chr(64 + col_idx)].width = width
# 保存文件
wb.save(output_file)
print(f"\n会计分录已保存到: {output_file}")
print(f"总共生成 {len(entries)} 条会计分录")
def main():
"""主函数"""
input_file = 'res.json'
output_file = 'AccountingEntries.xlsx'
print("开始生成会计分录...")
print(f"读取文件: {input_file}")
try:
# 加载汇率
exchange_rate = load_exchange_rate()
# 读取JSON数据
with open(input_file, 'r', encoding='utf-8') as f:
data = json.load(f)
print(f"读取了 {len(data)} 条财务记录")
print(f"使用汇率: {exchange_rate}")
# 生成会计分录
entries = create_accounting_entries(data, exchange_rate)
# 保存到Excel
save_to_excel(entries, output_file)
# 统计信息
debit_count = sum(1 for e in entries if e["借/贷"] == "")
credit_count = sum(1 for e in entries if e["借/贷"] == "")
print(f"\n统计:")
print(f" 借方记录: {debit_count}")
print(f" 贷方记录: {credit_count}")
print("\n处理完成!")
except Exception as e:
print(f"\n错误: {e}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
main()

214
process_excel.py Normal file
View File

@@ -0,0 +1,214 @@
#!/usr/bin/env python3
"""
财务Excel数据处理程序
读取data/data.xlsx中的Sheet1表格,提取财务数据并输出到res.json
"""
import json
from openpyxl import load_workbook
from typing import List, Dict, Any, Optional
def get_cell_value(ws, row: int, col: int) -> Any:
"""获取单元格值,处理公式计算结果"""
cell = ws.cell(row, col)
return cell.value
def get_merged_cell_value(ws, row: int, col: int, merged_ranges) -> Any:
"""
获取合并单元格的值
如果单元格在合并区域内,返回合并区域左上角单元格的值
"""
for merged_range in merged_ranges:
if merged_range.min_row <= row <= merged_range.max_row and \
merged_range.min_col <= col <= merged_range.max_col:
# 返回合并区域左上角的值
return ws.cell(merged_range.min_row, merged_range.min_col).value
# 不在任何合并区域内,直接返回单元格值
return ws.cell(row, col).value
def get_f_column_ranges(ws, start_row: int = 2) -> List[tuple]:
"""
获取F列的所有数据区域(包括合并和非合并单元格)
从第2行开始读取
返回: [(起始行, 结束行), ...]
"""
merged_cells = list(ws.merged_cells.ranges)
# 找到F列的所有合并单元格区域
f_merges = []
for merge in merged_cells:
# F列是第6列
if merge.min_col == 6 and merge.max_col == 6 and merge.min_row >= start_row:
f_merges.append((merge.min_row, merge.max_row))
# 创建合并单元格行的集合,用于快速查找
merged_rows = set()
for start, end in f_merges:
for row in range(start, end + 1):
merged_rows.add(row)
# 处理非合并单元格(从第2行开始到最大行)
all_ranges = []
for row in range(start_row, ws.max_row + 1):
if row not in merged_rows:
# 检查F列是否有值
f_value = ws.cell(row, 6).value
if f_value is not None:
# 非合并单元格,单独一行
all_ranges.append((row, row))
# 添加合并单元格区域
all_ranges.extend(f_merges)
# 按起始行排序
all_ranges.sort(key=lambda x: x[0])
return all_ranges
def extract_orders(ws, start_row: int, end_row: int, merged_ranges) -> List[Dict[str, Any]]:
"""
提取指定行范围内的订单数据
参数:
ws: 工作表对象
start_row: 起始行
end_row: 结束行
merged_ranges: 合并单元格范围列表
返回:
订单列表
"""
orders = []
for row in range(start_row, end_row + 1):
# H列: 订单号, I列: 金额, O列: 账户名
order_num = get_merged_cell_value(ws, row, 8, merged_ranges) # H列
amount = get_merged_cell_value(ws, row, 9, merged_ranges) # I列
account_name = get_merged_cell_value(ws, row, 15, merged_ranges) # O列
# 跳过空订单号的行
if order_num is None or str(order_num).strip() == '':
continue
order = {
"OrderNum": str(order_num).strip() if order_num else None,
"Amount": amount,
"AccountName": str(account_name).strip() if account_name else None
}
orders.append(order)
return orders
def process_excel_data(file_path: str) -> List[Dict[str, Any]]:
"""
处理Excel文件,提取所有财务记录
从第2行开始读取数据
参数:
file_path: Excel文件路径
返回:
记录列表,每条记录包含ReceivedAmount, HandlingFee和Order列表
"""
# 加载Excel文件,data_only=True读取公式计算结果
wb = load_workbook(file_path, data_only=True)
ws = wb['Sheet1']
# 获取所有合并单元格范围
merged_ranges = list(ws.merged_cells.ranges)
# 获取F列的所有数据区域(从第2行开始)
f_ranges = get_f_column_ranges(ws, start_row=2)
print(f"找到 {len(f_ranges)} 个F列数据区域(包含合并和非合并单元格)")
results = []
for start_row, end_row in f_ranges:
# 获取F列(实收金额)和G列(手续费)的值
# 合并单元格的值在左上角,非合并单元格直接获取
received_amount = get_cell_value(ws, start_row, 6) # F列
handling_fee = get_cell_value(ws, start_row, 7) # G列
# 手续费为空时记为0
if handling_fee is None:
handling_fee = 0
# 提取该区域的订单列表
orders = extract_orders(ws, start_row, end_row, merged_ranges)
# 跳过没有订单的记录
if not orders:
continue
# 计算订单金额总和
order_amount_sum = sum(order["Amount"] for order in orders if order["Amount"] is not None)
# 计算实收金额+手续费
received_plus_fee = (received_amount if received_amount is not None else 0) + handling_fee
# 检查是否相等(考虑浮点数精度)
check_res = abs(received_plus_fee - order_amount_sum) < 0.01
record = {
"ReceivedAmount": received_amount,
"HandlingFee": handling_fee,
"Order": orders,
"checkRes": check_res
}
results.append(record)
return results
def save_to_json(data: List[Dict[str, Any]], output_file: str):
"""
将数据保存为JSON文件
参数:
data: 要保存的数据
output_file: 输出文件路径
"""
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print(f"\n数据已保存到: {output_file}")
print(f"总共提取 {len(data)} 条记录")
def main():
"""主函数"""
input_file = 'data/data.xlsx'
output_file = 'res.json'
print("开始处理Excel文件...")
print(f"输入文件: {input_file}")
try:
# 提取数据
data = process_excel_data(input_file)
# 保存到JSON
save_to_json(data, output_file)
# 显示前3条记录作为预览
if data:
print("\n前3条记录预览:")
print(json.dumps(data[:3], ensure_ascii=False, indent=2))
print("\n处理完成!")
except Exception as e:
print(f"\n错误: {e}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
main()

2865
res.json Normal file

File diff suppressed because it is too large Load Diff

355
task.md Normal file
View File

@@ -0,0 +1,355 @@
# 财务Excel数据处理系统 - 需求文档
## 项目概述
本系统用于自动化处理财务Excel数据,提取收款信息并生成标准会计分录表,支持数据验证和异常标记功能。
---
## 功能模块
### 模块一: Excel数据提取 (`process_excel.py`)
#### 1.1 数据源
- **文件路径**: `data/data.xlsx`
- **工作表**: Sheet1
- **数据起始行**: 第2行(第1行为表头)
#### 1.2 数据提取规则
##### 1.2.1 主记录字段
- **ReceivedAmount** (实收金额)
- 来源: F列
- 说明: 支持合并单元格(一行或多行)
- 处理: 非合并单元格按单行处理
- **HandlingFee** (手续费)
- 来源: G列
- 处理: 空值自动记为0
##### 1.2.2 订单明细字段
- **Order** (订单列表)
- 范围: F列合并单元格包含的所有行
- 包含字段:
- **OrderNum** (订单号): H列
- **Amount** (金额): I列
- **AccountName** (账户名称): O列(支持公式,读取计算结果)
##### 1.2.3 数据验证
- **checkRes** (验证结果): Boolean
- 计算规则: `ReceivedAmount + HandlingFee ≈ Sum(Order[].Amount)`
- 容差: 0.01
- true: 金额匹配
- false: 金额不匹配,需要人工核对
#### 1.3 输出格式
**文件**: `res.json`
**结构**:
```json
[
{
"ReceivedAmount": 12125,
"HandlingFee": 25,
"Order": [
{
"OrderNum": "XLRQD300T25",
"Amount": 550,
"AccountName": "24台湾长荣航运"
},
{
"OrderNum": "XLRQD044T25",
"Amount": 11600,
"AccountName": "24台湾长荣航运"
}
],
"checkRes": true
}
]
```
#### 1.4 特殊处理
- 跳过订单号为空的行
- 处理F列合并和非合并单元格混合的情况
- 读取O列公式计算后的值(data_only=True)
---
### 模块二: 会计分录生成 (`generate_accounting_entries.py`)
#### 2.1 输入输出
- **输入**: `res.json`
- **输出**: `AccountingEntries.xlsx`
#### 2.2 会计分录规则
##### 2.2.1 基本分录
每笔 ReceivedAmount 记录生成以下分录:
**1) 到账金额 - 借方记录** (每笔记录1条)
- 科目代码: `1002.02`
- 科目名称: `银行存款 - 中行USD`
- 摘要: `美金收款-{OrderNum}`
- 核算项目: Order[0].AccountName
- 币别: 美元
- 汇率: 7.1072 (可配置)
- 原币金额: ReceivedAmount
- 金额: ReceivedAmount × 汇率
**2) 手续费 - 借方记录** (仅当 HandlingFee > 0)
- 科目代码: `5603.03`
- 科目名称: `财务费用-手续费`
- 摘要: `美金收款-{OrderNum}`
- 核算项目: 空
- 币别: 人民币
- 汇率: 空
- 原币金额: 空
- 金额: HandlingFee × 汇率
**3) 订单明细 - 贷方记录** (每个Order记录1条)
- 科目代码: `1122`
- 科目名称: `应收账款`
- 应收账款: Order.Amount (显示在"应收账款"列)
- 摘要: `美金收款-{OrderNum}`
- 核算项目: Order.AccountName
- 币别: 美元
- 汇率: 7.1072 (可配置)
- 原币金额: Order.Amount
- 金额: Order.Amount × 汇率
##### 2.2.2 特殊规则
**空值处理**
- Order.Amount为null的订单跳过,不生成分录
**金额验证**
- checkRes为false的记录,所有相关分录行标记为粉红色背景(#FAD1D4)
#### 2.3 Excel格式设置
##### 2.3.1 表头
- 字段: 到账金额, 手续费, 订单号, 应收账款, 金蝶名称, 摘要, 借/贷, 科目代码(*), 科目名称(*), 核算项目, 币别, 汇率, 原币金额, 金额
- 样式: 粗体, 蓝色背景(#CCE5FF), 居中对齐
##### 2.3.2 单元格合并
- **到账金额列** (A列): 同一ReceivedAmount的所有分录行合并
- **手续费列** (B列): 同一ReceivedAmount的所有分录行合并
- 对齐方式: 垂直居中,水平居中
##### 2.3.3 异常标记
- 条件: 原始记录的 checkRes = false
- 处理: 该记录产生的所有分录行设置背景色
- 颜色: #FAD1D4 (粉红色)
- 目的: 突出显示金额不匹配的记录,便于人工核对
##### 2.3.4 列宽设置
```
A(到账金额): 12, B(手续费): 10, C(订单号): 18,
D(应收账款): 12, E(金蝶名称): 25, F(摘要): 25,
G(借/贷): 8, H(科目代码): 15, I(科目名称): 25,
J(核算项目): 25, K(币别): 10, L(汇率): 10,
M(原币金额): 12, N(金额): 15
```
---
## 配置参数
### 汇率配置
- **变量名**: `EXCHANGE_RATE`
- **位置**: `generate_accounting_entries.py` 第12行
- **默认值**: 7.1072
- **修改方式**: 直接编辑变量值
```python
# 固定汇率
EXCHANGE_RATE = 7.1072 # 修改此值
```
---
## 数据流程
```
data/data.xlsx (原始数据)
[process_excel.py 提取]
res.json (中间数据)
[generate_accounting_entries.py 生成]
AccountingEntries.xlsx (会计分录表)
```
---
## 技术要求
### 开发语言
- Python 3.x
### 依赖库
- openpyxl: Excel文件读写
### 安装命令
```bash
pip install openpyxl --break-system-packages
```
---
## 数据示例
### 输入示例 (data.xlsx)
| 到账金额 | 手续费 | 订单号 | 解款金额 | 金蝶名称 |
|---------|--------|---------|---------|---------|
| 12125 | 25 | XLRQD300T25 | 550 | 24台湾长荣航运 |
| (合并) | (合并) | XLRQD044T25 | 11600 | 24台湾长荣航运 |
### 中间数据 (res.json)
```json
{
"ReceivedAmount": 12125,
"HandlingFee": 25,
"Order": [
{"OrderNum": "XLRQD300T25", "Amount": 550, "AccountName": "24台湾长荣航运"},
{"OrderNum": "XLRQD044T25", "Amount": 11600, "AccountName": "24台湾长荣航运"}
],
"checkRes": true
}
```
### 输出示例 (AccountingEntries.xlsx)
| 到账金额 | 手续费 | 订单号 | 应收账款 | 金蝶名称 | 借/贷 | 科目代码 | 科目名称 | 币别 | 原币金额 | 金额 |
|---------|--------|--------|---------|---------|-------|----------|----------|------|----------|------|
| 12125 | 25 | XLRQD300T25 | | 24台湾长荣航运 | 借 | 1002.02 | 银行存款 - 中行USD | 美元 | 12125 | 86,174.80 |
| (合并) | (合并) | XLRQD300T25 | | | 借 | 5603.03 | 财务费用-手续费 | 人民币 | | 177.68 |
| (合并) | (合并) | XLRQD300T25 | 550 | 24台湾长荣航运 | 贷 | 1122 | 应收账款 | 美元 | 550 | 3,908.96 |
| (合并) | (合并) | XLRQD044T25 | 11600 | 24台湾长荣航运 | 贷 | 1122 | 应收账款 | 美元 | 11600 | 82,443.52 |
---
## 验证规则
### checkRes计算
```python
received_plus_fee = ReceivedAmount + HandlingFee
order_amount_sum = Sum(Order[].Amount where Amount is not null)
checkRes = abs(received_plus_fee - order_amount_sum) < 0.01
```
### 异常情况
- **checkRes = false**: 粉红色背景标记
- **Order.Amount = null**: 跳过该订单,不生成分录
- **HandlingFee = null**: 自动记为0
---
## 测试用例
### 测试用例1: 正常单订单
- 输入: ReceivedAmount=695, HandlingFee=0, Order[0].Amount=695
- 预期: 2行分录(到账借+订单贷), checkRes=true
- 应收账款列: 贷方记录显示695
### 测试用例2: 正常多订单
- 输入: ReceivedAmount=12125, HandlingFee=25, Order=[550, 11600]
- 预期: 4行分录(到账借+手续费借+2个订单贷), checkRes=true
- 应收账款列: 两条贷方记录分别显示550和11600
### 测试用例3: 金额不匹配
- 输入: ReceivedAmount=17270, HandlingFee=0, Order=[5676, 11450]
- 预期: checkRes=false, 所有分录行粉红色背景(#FAD1D4)
### 测试用例4: 订单金额为空
- 输入: ReceivedAmount=240, HandlingFee=25, Order[0].Amount=null
- 预期: 跳过订单明细分录,不报错,只生成到账借方和手续费借方
---
## 文件清单
| 文件名 | 类型 | 说明 |
|--------|------|------|
| data/data.xlsx | 输入 | 原始财务数据 |
| process_excel.py | 程序 | 数据提取脚本 |
| res.json | 中间 | 提取的JSON数据 |
| generate_accounting_entries.py | 程序 | 会计分录生成脚本 |
| AccountingEntries.xlsx | 输出 | 会计分录表 |
| analyze_excel.py | 工具 | Excel结构分析工具 |
| task.md | 文档 | 需求文档(本文件) |
| User.md | 文档 | 用户使用说明 |
---
## 性能要求
- 支持处理300+行Excel数据
- 生成500+行会计分录
- 处理时间 < 10秒
- 实测: 174条记录生成566条会计分录
---
## 错误处理
### Excel读取错误
- 文件不存在: 提示并退出
- Sheet1不存在: 提示并退出
- 列映射错误: 记录日志,跳过该行
### 数据验证错误
- 空值: 自动处理(HandlingFee=0, 跳过Amount=null)
- 格式错误: 记录日志,继续处理
- checkRes=false: 标记但继续处理
---
## 扩展需求
### 未来可能增加的功能
- [ ] 支持多工作表批量处理
- [ ] 汇率从配置文件读取
- [ ] 生成汇总统计报表
- [ ] 支持导出金蝶格式
- [ ] 添加数据修正功能
---
## 版本信息
- **版本**: v1.1
- **最后更新**: 2025-01-17
- **作者**: Claude Code
- **状态**: 已完成并测试
### 版本历史
**v1.1** (2025-01-17)
- 优化会计分录规则: 移除"到账金额-贷方"记录
- 每个Order记录都生成对应的贷方分录(无例外)
- Order生成的贷方记录在"应收账款"列显示Amount金额
- 简化单订单处理逻辑,移除单订单优化规则
**v1.0** (2025-01-17)
- 初始版本
- Excel数据提取功能
- 会计分录生成功能
- 合并单元格支持
- 金额验证功能
- 异常背景色标记
---
## 备注
1. 所有金额计算保留2位小数
2. 汇率统一使用固定值,不从Excel读取
3. 合并单元格的值读取左上角单元格
4. O列公式使用data_only=True读取计算结果
5. 背景色仅用于标记,不影响数据内容

BIN
temp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

5
venv/pyvenv.cfg Normal file
View File

@@ -0,0 +1,5 @@
home = /usr/bin
include-system-site-packages = false
version = 3.12.3
executable = /usr/bin/python3.12
command = /usr/bin/python3 -m venv /mnt/d/Cursor/TableProcessing/venv