ai说excel部分没问题了,暂且信一次,提交文件

This commit is contained in:
侯欢 2025-05-02 17:55:29 +08:00
parent 0035cd1893
commit 131fff6a7d
27 changed files with 853 additions and 295 deletions

234
README.md
View File

@ -35,137 +35,167 @@ orc-order-v2/
│ │ │ └── converter.py # 单位转换与规格处理 │ │ │ └── converter.py # 单位转换与规格处理
│ │ │ │ │ │
│ │ └── utils/ # 工具函数 │ │ └── utils/ # 工具函数
│ │ ├── file_utils.py # 文件操作工具 │ │ ├── file_utils.py # 文件处理工具
│ │ ├── log_utils.py # 日志工具 │ │ └── log_utils.py # 日志工具
│ │ └── string_utils.py # 字符串处理工具
│ │ │ │
│ └── services/ # 业务服务 │ └── services/ # 服务
│ ├── ocr_service.py # OCR服务 │ ├── ocr_service.py # OCR服务
│ └── order_service.py # 订单处理服务 │ └── excel_service.py # Excel处理服务
├── data/ # 数据目录 ├── data/ # 数据目录
│ ├── input/ # 输入文件 │ ├── input/ # 输入图片目录
│ ├── output/ # 输出文件 │ ├── output/ # 处理结果输出目录
│ └── temp/ # 临时文件 │ ├── temp/ # 临时文件目录
│ └── backup/ # 备份目录
├── logs/ # 日志目录 ├── logs/ # 日志目录
├── templates/ # 模板文件 ├── templates/ # 模板目录
│ └── 银豹-采购单模板.xls # 采购单模板 │ └── 银豹-采购单模板.xls # Excel模板文件
├── 启动器.py # 图形界面启动器
├── run.py # 命令行入口
├── config.ini # 配置文件 ├── config.ini # 配置文件
└── requirements.txt # 依赖包列表 ├── run.py # 命令行入口脚本
├── 启动器.py # 图形界面启动器
└── README.md # 项目说明文档
``` ```
### 主要模块说明 ## 安装与配置
- **配置模块**:统一管理系统配置,支持默认值和配置文件读取 ### 环境要求
- **OCR模块**调用百度OCR API进行表格识别生成Excel文件
- **Excel处理模块**读取OCR生成的Excel文件提取商品信息
- **单位转换模块**:处理商品规格和单位转换
- **订单合并模块**:合并多个采购单为一个总单
- **文件工具模块**:处理文件读写、路径管理等
- **启动器**:提供图形界面操作
## 使用方法 - Python 3.8+
- 百度OCR API账号及密钥
### 环境准备 ### 安装依赖
1. 安装Python 3.6+
2. 安装依赖包:
```
pip install -r requirements.txt
```
3. 配置百度OCR API密钥
- 在`config.ini`中填写您的API密钥和Secret密钥
### 图形界面使用
1. 运行启动器:
```
python 启动器.py
```
2. 使用界面上的功能按钮进行操作:
- **OCR图像识别**:批量处理`data/input`目录下的图片
- **处理单个图片**:选择并处理单个图片
- **处理Excel文件**处理OCR识别后的Excel文件生成采购单
- **合并采购单**:合并所有生成的采购单
- **完整处理流程**:按顺序执行所有处理步骤
- **整理项目文件**:整理文件到规范目录结构
### 命令行使用
系统提供命令行方式调用,便于集成到自动化流程中:
```bash ```bash
# OCR识别 pip install -r requirements.txt
python run.py ocr [--input 图片路径] [--batch]
# Excel处理
python run.py excel [--input Excel文件路径]
# 订单合并
python run.py merge [--input 采购单文件路径列表]
# 完整流程
python run.py pipeline
``` ```
## 文件处理流程 ### 配置文件
1. **OCR识别处理** 在`config.ini`中配置以下信息:
- 读取`data/input`目录下的图片文件
- 调用百度OCR API进行表格识别
- 保存识别结果为Excel文件到`data/output`目录
2. **Excel处理**
- 读取OCR识别生成的Excel文件
- 提取商品信息(条码、名称、规格、单价、数量等)
- 按照采购单模板格式生成标准采购单Excel文件
- 输出文件命名为"采购单_原文件名.xls"
3. **采购单合并**
- 读取所有采购单Excel文件
- 合并相同商品的数量
- 生成总采购单
## 配置说明
系统配置文件`config.ini`包含以下主要配置:
```ini ```ini
[API] [OCR]
api_key = 您的百度API Key api_key = 你的百度OCR API Key
secret_key = 您的百度Secret Key secret_key = 你的百度OCR Secret Key
timeout = 30
max_retries = 3
retry_delay = 2
api_url = https://aip.baidubce.com/rest/2.0/ocr/v1/table
[Paths] [Paths]
input_folder = data/input input_folder = data/input
output_folder = data/output output_folder = data/output
temp_folder = data/temp template_file = templates/银豹-采购单模板.xls
[Performance]
max_workers = 4
batch_size = 5
skip_existing = true
[File]
allowed_extensions = .jpg,.jpeg,.png,.bmp
excel_extension = .xlsx
max_file_size_mb = 4
``` ```
## 使用方法
### 图形界面
运行`启动器.py`启动图形界面:
```bash
python 启动器.py
```
图形界面包括以下功能:
- **处理单个文件**:选择并处理单个图片文件
- **批量处理**处理data/input目录中的所有图片文件
- **合并处理**:合并多个采购单
- **清理文件**清理input和output目录中的文件
- **查看日志**:实时显示处理日志
### 命令行模式
```bash
# 处理单个文件
python run.py --file=image.jpg
# 批量处理目录中的所有文件
python run.py --batch
# 合并采购单
python run.py --merge
```
## 单位处理规则
系统支持多种单位的智能处理,自动识别和转换不同的计量单位。单位处理逻辑如下:
### 标准单位处理
| 单位 | 处理规则 | 示例 |
|------|----------|------|
| 件 | 数量×包装数量<br>单价÷包装数量<br>单位转换为"瓶" | 1件(规格1*12) → 12瓶<br>单价108元/件 → 9元/瓶 |
| 箱 | 数量×包装数量<br>单价÷包装数量<br>单位转换为"瓶" | 2箱(规格1*24) → 48瓶<br>单价120元/箱 → 5元/瓶 |
| 包 | 保持原数量和单位不变 | 3包 → 3包 |
| 其他单位 | 保持原数量和单位不变 | 5瓶 → 5瓶 |
### 提和盒单位特殊处理
系统对"提"和"盒"单位有特殊的处理逻辑:
1. 当规格是三级格式如1*5*12
- 按照件的计算方式处理
- 数量 = 原始数量 × 包装数量
- 单位转换为"瓶"
- 单价 = 原始单价 ÷ 包装数量
示例3提(规格1*5*12) → 36瓶
2. 当规格是二级格式如1*16
- **保持原数量不变**
- **保持原单位不变**
示例3提(规格1*16) → 仍然是3提
### 特殊条码处理
系统支持对特定条码进行特殊处理,这些条码的处理规则会覆盖上述的标准单位处理规则:
1. 特殊条码配置:
```python
special_barcodes = {
'6925019900087': {
'multiplier': 10, # 数量乘以10
'target_unit': '瓶', # 目标单位
'description': '特殊处理:数量*10单位转换为瓶'
}
# 可以添加更多特殊条码的配置
}
```
2. 处理规则:
- 当遇到特殊条码时,无论规格是二级还是三级
- 无论单位是提还是盒还是件
- 都按照特殊条码配置进行处理
- 数量乘以配置的倍数
- 单位转换为配置的目标单位
- 如果有单价,单价除以配置的倍数
## 智能规格推断
当规格信息为空时,系统能从商品名称自动推断规格:
1. 匹配"xx入"格式:
- 如"445水溶C血橙15入纸箱" → 规格推断为 1*15
2. 匹配直接包含规格的格式:
- 如"500-东方树叶-绿茶1*15-纸箱装" → 规格推断为 1*15
3. 匹配容量格式:
- 如"12.9L桶装水" → 规格推断为 12.9L*1
4. 其他商品命名模式:
- 如"900树叶茉莉花茶12入纸箱" → 规格推断为 1*12
- 如"500茶π蜜桃乌龙15纸箱" → 规格推断为 1*15
## 注意事项 ## 注意事项
1. 系统依赖百度OCR API使用前请确保已配置正确的API密钥 1. 确保输入文件格式正确支持jpg、png等图片格式
2. 图片质量会影响OCR识别结果建议使用清晰的原始图片 2. 处理结果将输出到data/output目录下
3. 处理大量图片时可能会受到API调用频率限制 3. 定期清理临时文件和日志文件
4. 所有处理好的文件会保存在`data/output`目录中 4. 及时更新百度OCR API密钥
5. 为避免数据丢失,可使用清理功能前的备份选项
## 错误排查 ## 错误排查

View File

@ -1,30 +1,26 @@
""" """
单位转换处理模块 单位转换模块
------------- ----------
提供规格和单位的处理和转换功能 提供单位转换功能支持规格推断和单位自动提取
""" """
import re import re
from typing import Dict, List, Optional, Tuple, Any import logging
from typing import Dict, Tuple, Optional, Any, List, Union
from ..utils.log_utils import get_logger from ..utils.log_utils import get_logger
from ..utils.string_utils import (
clean_string,
extract_number,
extract_unit,
extract_number_and_unit,
parse_specification
)
logger = get_logger(__name__) logger = get_logger(__name__)
class UnitConverter: class UnitConverter:
""" """
单位转换器处理商品规格和单位转换 单位转换器处理不同单位之间的转换支持从商品名称推断规格
""" """
def __init__(self): def __init__(self):
"""初始化单位转换器""" """
初始化单位转换器
"""
# 特殊条码配置 # 特殊条码配置
self.special_barcodes = { self.special_barcodes = {
'6925019900087': { '6925019900087': {
@ -32,182 +28,299 @@ class UnitConverter:
'target_unit': '', # 目标单位 'target_unit': '', # 目标单位
'description': '特殊处理:数量*10单位转换为瓶' 'description': '特殊处理:数量*10单位转换为瓶'
} }
# 可以在这里添加更多特殊条码的配置 # 可以添加更多特殊条码的配置
} }
# 有效的单位列表 # 规格推断的正则表达式模式
self.valid_units = ['', '', '', '', '', '', '', '', '', '', '', '', 'L', 'l', ''] self.spec_patterns = [
# 1*6、1x12、1X20等格式
# 需要特殊处理的单位 (r'(\d+)[*xX×](\d+)', r'\1*\2'),
self.special_units = ['', '', '', ''] # 1*5*12和1x5x12等三级格式
(r'(\d+)[*xX×](\d+)[*xX×](\d+)', r'\1*\2*\3'),
logger.info("单位转换器初始化完成") # "xx入"格式,如"12入"、"24入"
(r'(\d+)入', r'1*\1'),
def add_special_barcode(self, barcode: str, multiplier: int, target_unit: str, description: str = "") -> None: # "xxL*1"或"xx升*1"格式
""" (r'([\d\.]+)[L升][*xX×]?(\d+)?', r'\1L*\2' if r'\2' else r'\1L*1'),
添加特殊条码处理配置 # "xxkg*1"或"xx公斤*1"格式
(r'([\d\.]+)(?:kg|公斤)[*xX×]?(\d+)?', r'\1kg*\2' if r'\2' else r'\1kg*1'),
Args: # "xxg*1"或"xx克*1"格式
barcode: 条码 (r'([\d\.]+)(?:g|克)[*xX×]?(\d+)?', r'\1g*\2' if r'\2' else r'\1g*1'),
multiplier: 数量乘数 # "xxmL*1"或"xx毫升*1"格式
target_unit: 目标单位 (r'([\d\.]+)(?:mL|毫升)[*xX×]?(\d+)?', r'\1mL*\2' if r'\2' else r'\1mL*1'),
description: 处理描述 ]
"""
self.special_barcodes[barcode] = {
'multiplier': multiplier,
'target_unit': target_unit,
'description': description or f'特殊处理:数量*{multiplier},单位转换为{target_unit}'
}
logger.info(f"添加特殊条码配置: {barcode}, {description}")
def infer_specification_from_name(self, product_name: str) -> Optional[str]:
"""
从商品名称推断规格
Args:
product_name: 商品名称
Returns:
推断的规格如果无法推断则返回None
"""
if not product_name or not isinstance(product_name, str):
return None
try:
# 清理商品名称
name = clean_string(product_name)
# 1. 匹配 XX入纸箱 格式
match = re.search(r'(\d+)入纸箱', name)
if match:
return f"1*{match.group(1)}"
# 2. 匹配 绿茶1*15-纸箱装 格式
match = re.search(r'(\d+)[*×xX](\d+)[-\s]?纸箱', name)
if match:
return f"{match.group(1)}*{match.group(2)}"
# 3. 匹配 12.9L桶装水 格式
match = re.search(r'([\d\.]+)[Ll升](?!.*[*×xX])', name)
if match:
return f"{match.group(1)}L*1"
# 4. 匹配 商品12入纸箱 格式(数字在中间)
match = re.search(r'\D(\d+)入\w*箱', name)
if match:
return f"1*{match.group(1)}"
# 5. 匹配 商品15纸箱 格式(数字在中间)
match = re.search(r'\D(\d+)\w*箱', name)
if match:
return f"1*{match.group(1)}"
# 6. 匹配 商品1*30 格式
match = re.search(r'(\d+)[*×xX](\d+)', name)
if match:
return f"{match.group(1)}*{match.group(2)}"
logger.debug(f"无法从商品名称推断规格: {name}")
return None
except Exception as e:
logger.error(f"从商品名称推断规格时出错: {e}")
return None
def extract_unit_from_quantity(self, quantity_str: str) -> Tuple[Optional[float], Optional[str]]: def extract_unit_from_quantity(self, quantity_str: str) -> Tuple[Optional[float], Optional[str]]:
""" """
从数量字符串提取单位 从数量字符串中提取单位
Args: Args:
quantity_str: 数量字符串 quantity_str: 数量字符串"2箱""5件"
Returns: Returns:
(数量, 单位)元组 (数量, 单位)的元组如果无法提取则返回(None, None)
""" """
if not quantity_str or not isinstance(quantity_str, str): if not quantity_str or not isinstance(quantity_str, str):
return None, None return None, None
# 匹配数字+单位格式
match = re.match(r'^([\d\.]+)\s*([^\d\s\.]+)$', quantity_str.strip())
if match:
try: try:
# 清理数量字符串 num = float(match.group(1))
quantity_str = clean_string(quantity_str) unit = match.group(2)
logger.info(f"从数量提取单位: {quantity_str} -> 数量={num}, 单位={unit}")
return num, unit
except ValueError:
pass
# 提取数字和单位
return extract_number_and_unit(quantity_str)
except Exception as e:
logger.error(f"从数量字符串提取单位时出错: {quantity_str}, 错误: {e}")
return None, None return None, None
def process_unit_conversion(self, product: Dict[str, Any]) -> Dict[str, Any]: def extract_specification(self, text: str) -> Optional[str]:
""" """
处理单位转换根据单位和规格转换数量和单价 从文本中提取规格信息
Args: Args:
product: 商品字典包含条码单位规格数量和单价等字段 text: 文本字符串
Returns: Returns:
处理后的商品字典 提取的规格字符串如果无法提取则返回None
""" """
# 复制商品信息,避免修改原始数据 if not text or not isinstance(text, str):
return None
# 尝试所有模式
for pattern, replacement in self.spec_patterns:
match = re.search(pattern, text)
if match:
# 特殊处理三级格式确保正确显示为1*5*12
if '*' in replacement and replacement.count('*') == 1 and len(match.groups()) >= 2:
result = f"{match.group(1)}*{match.group(2)}"
logger.info(f"提取规格: {text} -> {result}")
return result
# 特殊处理三级规格格式
elif '*' in replacement and replacement.count('*') == 2 and len(match.groups()) >= 3:
result = f"{match.group(1)}*{match.group(2)}*{match.group(3)}"
logger.info(f"提取三级规格: {text} -> {result}")
return result
# 一般情况
else:
result = re.sub(pattern, replacement, text)
logger.info(f"提取规格: {text} -> {result}")
return result
# 没有匹配任何模式
return None
def infer_specification_from_name(self, name: str) -> Optional[str]:
"""
从商品名称中推断规格
Args:
name: 商品名称
Returns:
推断的规格如果无法推断则返回None
"""
if not name or not isinstance(name, str):
return None
# 特殊模式的名称处理
# 如"445水溶C血橙15入纸箱" -> "1*15"
pattern1 = r'.*(\d+)入'
match = re.match(pattern1, name)
if match:
inferred_spec = f"1*{match.group(1)}"
logger.info(f"从名称推断规格(入): {name} -> {inferred_spec}")
return inferred_spec
# 如"500-东方树叶-绿茶1*15-纸箱装" -> "1*15"
pattern2 = r'.*(\d+)[*xX×](\d+).*'
match = re.match(pattern2, name)
if match:
inferred_spec = f"{match.group(1)}*{match.group(2)}"
logger.info(f"从名称推断规格(直接): {name} -> {inferred_spec}")
return inferred_spec
# 如"12.9L桶装水" -> "12.9L*1"
pattern3 = r'.*?([\d\.]+)L.*'
match = re.match(pattern3, name)
if match:
inferred_spec = f"{match.group(1)}L*1"
logger.info(f"从名称推断规格(L): {name} -> {inferred_spec}")
return inferred_spec
# 从名称中提取规格
spec = self.extract_specification(name)
if spec:
return spec
return None
def parse_specification(self, spec: str) -> Tuple[int, int, Optional[int]]:
"""
解析规格字符串支持1*12和1*5*12等格式
Args:
spec: 规格字符串
Returns:
(一级包装, 二级包装, 三级包装)元组如果是二级包装第三个值为None
"""
if not spec or not isinstance(spec, str):
return 1, 1, None
# 处理三级包装如1*5*12
three_level_match = re.match(r'(\d+)[*xX×](\d+)[*xX×](\d+)', spec)
if three_level_match:
try:
level1 = int(three_level_match.group(1))
level2 = int(three_level_match.group(2))
level3 = int(three_level_match.group(3))
logger.info(f"解析三级规格: {spec} -> {level1}*{level2}*{level3}")
return level1, level2, level3
except ValueError:
pass
# 处理二级包装如1*12
two_level_match = re.match(r'(\d+)[*xX×](\d+)', spec)
if two_level_match:
try:
level1 = int(two_level_match.group(1))
level2 = int(two_level_match.group(2))
logger.info(f"解析二级规格: {spec} -> {level1}*{level2}")
return level1, level2, None
except ValueError:
pass
# 特殊处理L/升为单位的规格如12.5L*1
volume_match = re.match(r'([\d\.]+)[L升][*xX×](\d+)', spec)
if volume_match:
try:
volume = float(volume_match.group(1))
quantity = int(volume_match.group(2))
logger.info(f"解析容量规格: {spec} -> {volume}L*{quantity}")
return 1, quantity, None
except ValueError:
pass
# 默认值
logger.warning(f"无法解析规格: {spec}使用默认值1*1")
return 1, 1, None
def process_unit_conversion(self, product: Dict) -> Dict:
"""
处理单位转换按照以下规则
1. 特殊条码: 优先处理特殊条码
2. ""单位: 数量×包装数量, 单价÷包装数量, 单位转为""
3. ""单位: 数量×包装数量, 单价÷包装数量, 单位转为""
4. """"单位: 如果是三级规格, 按件处理; 如果是二级规格, 保持不变
5. 其他单位: 保持不变
Args:
product: 商品信息字典
Returns:
处理后的商品信息字典
"""
# 复制原始数据,避免修改原始字典
result = product.copy() result = product.copy()
try: barcode = result.get('barcode', '')
# 获取条码、单位、规格、数量和单价 unit = result.get('unit', '')
barcode = product.get('barcode', '') quantity = result.get('quantity', 0)
unit = product.get('unit', '') price = result.get('price', 0)
specification = product.get('specification', '') specification = result.get('specification', '')
quantity = product.get('quantity', 0)
price = product.get('price', 0)
# 如果缺少关键信息,无法进行转换 # 跳过无效数据
if not barcode or quantity == 0: if not barcode or not quantity:
return result return result
# 1. 首先检查是否是特殊条码 # 特殊条码处理
if barcode in self.special_barcodes: if barcode in self.special_barcodes:
special_config = self.special_barcodes[barcode] special_config = self.special_barcodes[barcode]
logger.info(f"应用特殊条码配置: {barcode}, {special_config['description']}") multiplier = special_config.get('multiplier', 1)
target_unit = special_config.get('target_unit', '')
# 应用乘数和单位转换 # 数量乘以倍数
result['quantity'] = quantity * special_config['multiplier'] new_quantity = quantity * multiplier
result['unit'] = special_config['target_unit']
# 如果有单价,进行单价转换 # 如果有单价,单价除以倍数
if price != 0: new_price = price / multiplier if price else 0
result['price'] = price / special_config['multiplier']
logger.info(f"特殊条码处理: {barcode}, 数量: {quantity} -> {new_quantity}, 单价: {price} -> {new_price}, 单位: {unit} -> {target_unit}")
result['quantity'] = new_quantity
result['price'] = new_price
result['unit'] = target_unit
return result return result
# 2. 提取规格包装数量 # 没有规格信息,无法进行单位转换
package_quantity = None if not specification:
if specification:
package_quantity = parse_specification(specification)
# 3. 处理单位转换
if unit and unit in self.special_units and package_quantity:
# 判断是否是三级规格1*5*12格式
is_three_level = bool(re.search(r'\d+[\*xX×]\d+[\*xX×]\d+', str(specification)))
# 对于"提"和"盒"单位的特殊处理
if (unit in ['', '']) and not is_three_level:
# 二级规格:保持原数量不变
logger.info(f"二级规格的提/盒单位,保持原状: {unit}, 规格={specification}")
return result return result
# 标准处理:数量×包装数量,单价÷包装数量 # 解析规格信息
logger.info(f"标准单位转换: {unit}->瓶, 规格={specification}, 包装数量={package_quantity}") level1, level2, level3 = self.parse_specification(specification)
result['quantity'] = quantity * package_quantity
# "件"单位处理
if unit in ['']:
# 计算包装数量(二级*三级,如果无三级则仅二级)
packaging_count = level2 * (level3 or 1)
# 数量×包装数量
new_quantity = quantity * packaging_count
# 单价÷包装数量
new_price = price / packaging_count if price else 0
logger.info(f"件单位处理: 数量: {quantity} -> {new_quantity}, 单价: {price} -> {new_price}, 单位: 件 -> 瓶")
result['quantity'] = new_quantity
result['price'] = new_price
result['unit'] = '' result['unit'] = ''
return result
if price != 0: # "箱"单位处理 - 与"件"单位处理相同
result['price'] = price / package_quantity if unit in ['']:
# 计算包装数量
packaging_count = level2 * (level3 or 1)
# 数量×包装数量
new_quantity = quantity * packaging_count
# 单价÷包装数量
new_price = price / packaging_count if price else 0
logger.info(f"箱单位处理: 数量: {quantity} -> {new_quantity}, 单价: {price} -> {new_price}, 单位: 箱 -> 瓶")
result['quantity'] = new_quantity
result['price'] = new_price
result['unit'] = ''
return result
# "提"和"盒"单位处理
if unit in ['', '']:
# 如果是三级规格,按件处理
if level3 is not None:
# 计算包装数量
packaging_count = level2 * level3
# 数量×包装数量
new_quantity = quantity * packaging_count
# 单价÷包装数量
new_price = price / packaging_count if price else 0
logger.info(f"提/盒单位(三级规格)处理: 数量: {quantity} -> {new_quantity}, 单价: {price} -> {new_price}, 单位: {unit} -> 瓶")
result['quantity'] = new_quantity
result['price'] = new_price
result['unit'] = ''
else:
# 如果是二级规格,保持不变
logger.info(f"提/盒单位(二级规格)处理: 保持原样 数量: {quantity}, 单价: {price}, 单位: {unit}")
return result return result
# 4. 默认返回原始数据 # 其他单位保持不变
return result logger.info(f"其他单位处理: 保持原样 数量: {quantity}, 单价: {price}, 单位: {unit}")
except Exception as e:
logger.error(f"单位转换处理出错: {e}")
# 发生错误时,返回原始数据
return result return result

View File

@ -294,33 +294,100 @@ class ExcelProcessor:
output_workbook = xlcopy(template_workbook) output_workbook = xlcopy(template_workbook)
output_sheet = output_workbook.get_sheet(0) output_sheet = output_workbook.get_sheet(0)
# 填充商品信息 # 先对产品按条码分组,区分正常商品和赠品
start_row = 1 # 从第2行开始填充数据索引从0开始 barcode_groups = {}
for i, product in enumerate(products): # 遍历所有产品,按条码分组
row = start_row + i logger.info(f"开始处理{len(products)} 个产品信息")
for product in products:
barcode = product.get('barcode', '')
if not barcode:
logger.warning(f"跳过无条码商品")
continue
# 序号 # 获取数量和单价
output_sheet.write(row, 0, i + 1) quantity = product.get('quantity', 0)
# 商品编码(条码) price = product.get('price', 0)
output_sheet.write(row, 1, product['barcode'])
# 商品名称 # 判断是否为赠品价格为0
output_sheet.write(row, 2, product['name']) is_gift = price == 0
# 规格
output_sheet.write(row, 3, product['specification']) logger.info(f"处理商品: 条码={barcode}, 数量={quantity}, 单价={price}, 是否赠品={is_gift}")
# 单位
output_sheet.write(row, 4, product['unit']) if barcode not in barcode_groups:
# 单价 barcode_groups[barcode] = {
output_sheet.write(row, 5, product['price']) 'normal': None, # 正常商品信息
# 采购数量 'gift_quantity': 0 # 赠品数量
output_sheet.write(row, 6, product['quantity']) }
# 采购金额(单价 × 数量)
amount = product['price'] * product['quantity'] if is_gift:
output_sheet.write(row, 7, amount) # 是赠品,累加赠品数量
# 税率 barcode_groups[barcode]['gift_quantity'] += quantity
output_sheet.write(row, 8, 0) logger.info(f"发现赠品:条码{barcode}, 数量={quantity}")
# 赠送量默认为0 else:
output_sheet.write(row, 9, 0) # 是正常商品
if barcode_groups[barcode]['normal'] is None:
barcode_groups[barcode]['normal'] = {
'product': product,
'quantity': quantity,
'price': price
}
logger.info(f"发现正常商品:条码{barcode}, 数量={quantity}, 单价={price}")
else:
# 如果有多个正常商品记录,累加数量
barcode_groups[barcode]['normal']['quantity'] += quantity
logger.info(f"累加正常商品数量:条码{barcode}, 新增={quantity}, 累计={barcode_groups[barcode]['normal']['quantity']}")
# 如果单价不同,取平均值
if price != barcode_groups[barcode]['normal']['price']:
avg_price = (barcode_groups[barcode]['normal']['price'] + price) / 2
barcode_groups[barcode]['normal']['price'] = avg_price
logger.info(f"调整单价(取平均值):条码{barcode}, 原价={barcode_groups[barcode]['normal']['price']}, 新价={price}, 平均={avg_price}")
# 输出调试信息
logger.info(f"分组后共{len(barcode_groups)} 个不同条码的商品")
for barcode, group in barcode_groups.items():
if group['normal'] is not None:
logger.info(f"条码 {barcode} 处理结果:正常商品数量{group['normal']['quantity']},单价{group['normal']['price']},赠品数量{group['gift_quantity']}")
else:
logger.info(f"条码 {barcode} 处理结果:只有赠品,数量={group['gift_quantity']}")
# 准备填充数据
row_index = 1 # 从第2行开始填充索引从0开始
for barcode, group in barcode_groups.items():
# 1. 列B(1): 条码(必填)
output_sheet.write(row_index, 1, barcode)
if group['normal'] is not None:
# 有正常商品
product = group['normal']['product']
# 2. 列C(2): 采购量(必填) 使用正常商品的采购量
normal_quantity = group['normal']['quantity']
output_sheet.write(row_index, 2, normal_quantity)
# 3. 列D(3): 赠送量 - 添加赠品数量
if group['gift_quantity'] > 0:
output_sheet.write(row_index, 3, group['gift_quantity'])
logger.info(f"条码 {barcode} 填充:采购量={normal_quantity},赠品数量{group['gift_quantity']}")
# 4. 列E(4): 采购单价(必填)
purchase_price = group['normal']['price']
style = xlwt.XFStyle()
style.num_format_str = '0.0000'
output_sheet.write(row_index, 4, round(purchase_price, 4), style)
else:
# 只有赠品,没有正常商品
# 采购量填0赠送量填赠品数量
output_sheet.write(row_index, 2, 0) # 采购量为0
output_sheet.write(row_index, 3, group['gift_quantity']) # 赠送量
output_sheet.write(row_index, 4, 0) # 单价为0
logger.info(f"条码 {barcode} 填充:仅有赠品,采购量=0赠品数量={group['gift_quantity']}")
# 移到下一行
row_index += 1
# 保存文件 # 保存文件
output_workbook.save(output_file_path) output_workbook.save(output_file_path)

View File

@ -1,3 +1,3 @@
{ {
"D:\\My Documents\\python\\orc-order-v2\\data\\output\\微信图片_20250227193150(1).xlsx": "D:\\My Documents\\python\\orc-order-v2\\data\\output\\采购单_微信图片_20250227193150(1)_20250502171625.xls" "D:\\My Documents\\python\\orc-order-v2\\data\\output\\微信图片_20250227193150(1).xlsx": "D:\\My Documents\\python\\orc-order-v2\\data\\output\\采购单_微信图片_20250227193150(1).xls"
} }

Binary file not shown.

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:24 Active since: 2025-05-02 17:42:15

View File

@ -23,3 +23,9 @@
2025-05-02 17:10:09,825 - __main__ - INFO - Excel处理成功输出文件: D:\My Documents\python\orc-order-v2\output\采购单_微信图片_20250227193150(1)_20250502171009.xls 2025-05-02 17:10:09,825 - __main__ - INFO - Excel处理成功输出文件: D:\My Documents\python\orc-order-v2\output\采购单_微信图片_20250227193150(1)_20250502171009.xls
2025-05-02 17:16:24,478 - __main__ - INFO - 处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx 2025-05-02 17:16:24,478 - __main__ - INFO - 处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:16:25,037 - __main__ - INFO - Excel处理成功输出文件: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1)_20250502171625.xls 2025-05-02 17:16:25,037 - __main__ - INFO - Excel处理成功输出文件: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1)_20250502171625.xls
2025-05-02 17:32:36,464 - __main__ - INFO - 处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:32:37,143 - __main__ - INFO - Excel处理成功输出文件: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1).xls
2025-05-02 17:40:07,689 - __main__ - INFO - 处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:40:08,402 - __main__ - INFO - Excel处理成功输出文件: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1).xls
2025-05-02 17:42:15,227 - __main__ - INFO - 处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:42:15,838 - __main__ - INFO - Excel处理成功输出文件: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1).xls

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:24 Active since: 2025-05-02 17:42:15

View File

@ -28,3 +28,28 @@
2025-05-02 17:16:25,025 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15 2025-05-02 17:16:25,025 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:16:25,025 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15 2025-05-02 17:16:25,025 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:16:25,025 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15 2025-05-02 17:16:25,025 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:32:36,462 - app.core.excel.converter - INFO - 单位转换器初始化完成
2025-05-02 17:32:37,130 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:32:37,130 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:32:37,130 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:32:37,130 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:32:37,131 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:32:37,131 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:32:37,131 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:40:07,687 - app.core.excel.converter - INFO - 单位转换器初始化完成
2025-05-02 17:40:08,377 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:40:08,378 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:40:08,378 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:40:08,379 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:40:08,379 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:40:08,380 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:40:08,380 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,226 - app.core.excel.converter - INFO - 单位转换器初始化完成
2025-05-02 17:42:15,792 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,792 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,792 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,793 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,793 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,794 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,794 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15
2025-05-02 17:42:15,794 - app.core.excel.converter - INFO - 标准单位转换: 件->瓶, 规格=1*15, 包装数量=15

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:24 Active since: 2025-05-02 17:42:15

View File

@ -26,3 +26,9 @@
2025-05-02 17:10:09,224 - app.core.excel.merger - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls 2025-05-02 17:10:09,224 - app.core.excel.merger - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls
2025-05-02 17:16:24,477 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger 2025-05-02 17:16:24,477 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger
2025-05-02 17:16:24,478 - app.core.excel.merger - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls 2025-05-02 17:16:24,478 - app.core.excel.merger - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls
2025-05-02 17:32:36,463 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger
2025-05-02 17:32:36,464 - app.core.excel.merger - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls
2025-05-02 17:40:07,688 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger
2025-05-02 17:40:07,688 - app.core.excel.merger - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls
2025-05-02 17:42:15,226 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger
2025-05-02 17:42:15,226 - app.core.excel.merger - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:24 Active since: 2025-05-02 17:42:15

View File

@ -33,3 +33,80 @@
2025-05-02 17:16:25,022 - app.core.excel.processor - INFO - 列名映射结果: {'barcode': '条码', 'specification': '规格', 'quantity': '数量', 'unit': '单位', 'price': '单价'} 2025-05-02 17:16:25,022 - app.core.excel.processor - INFO - 列名映射结果: {'barcode': '条码', 'specification': '规格', 'quantity': '数量', 'unit': '单位', 'price': '单价'}
2025-05-02 17:16:25,025 - app.core.excel.processor - INFO - 提取到 8 个商品信息 2025-05-02 17:16:25,025 - app.core.excel.processor - INFO - 提取到 8 个商品信息
2025-05-02 17:16:25,035 - app.core.excel.processor - INFO - 采购单已保存到: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1)_20250502171625.xls 2025-05-02 17:16:25,035 - app.core.excel.processor - INFO - 采购单已保存到: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1)_20250502171625.xls
2025-05-02 17:32:36,461 - app.core.excel.processor - INFO - 初始化ExcelProcessor
2025-05-02 17:32:36,463 - app.core.excel.processor - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls
2025-05-02 17:32:36,464 - app.core.excel.processor - INFO - 开始处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:32:37,128 - app.core.excel.processor - INFO - 成功读取Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx, 共 11 行
2025-05-02 17:32:37,129 - app.core.excel.processor - INFO - 列名映射结果: {'barcode': '条码', 'specification': '规格', 'quantity': '数量', 'unit': '单位', 'price': '单价'}
2025-05-02 17:32:37,132 - app.core.excel.processor - INFO - 提取到 8 个商品信息
2025-05-02 17:32:37,141 - app.core.excel.processor - INFO - 采购单已保存到: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1).xls
2025-05-02 17:40:07,686 - app.core.excel.processor - INFO - 初始化ExcelProcessor
2025-05-02 17:40:07,688 - app.core.excel.processor - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls
2025-05-02 17:40:07,690 - app.core.excel.processor - INFO - 开始处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:40:08,376 - app.core.excel.processor - INFO - 成功读取Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx, 共 11 行
2025-05-02 17:40:08,376 - app.core.excel.processor - INFO - 列名映射结果: {'barcode': '条码', 'specification': '规格', 'quantity': '数量', 'unit': '单位', 'price': '单价'}
2025-05-02 17:40:08,381 - app.core.excel.processor - INFO - 提取到 8 个商品信息
2025-05-02 17:40:08,392 - app.core.excel.processor - INFO - 开始处理8 个产品信息
2025-05-02 17:40:08,392 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202346, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:40:08,392 - app.core.excel.processor - INFO - 发现正常商品条码6973497202346, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202940, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 发现正常商品条码6973497202940, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 处理商品: 条码=6973497200267, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 发现正常商品条码6973497200267, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 处理商品: 条码=6973497200403, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 发现正常商品条码6973497200403, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 处理商品: 条码=6873497204449, 数量=0, 单价=65.0, 是否赠品=False
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 发现正常商品条码6873497204449, 数量=0, 单价=65.0
2025-05-02 17:40:08,393 - app.core.excel.processor - INFO - 处理商品: 条码=6973497204432, 数量=15.0, 单价=4.333333333333333, 是否赠品=False
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 发现正常商品条码6973497204432, 数量=15.0, 单价=4.333333333333333
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202360, 数量=15.0, 单价=0, 是否赠品=True
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 发现赠品条码6973497202360, 数量=15.0
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202889, 数量=15.0, 单价=0, 是否赠品=True
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 发现赠品条码6973497202889, 数量=15.0
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 分组后共8 个不同条码的商品
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 条码 6973497202346 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 条码 6973497202940 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 条码 6973497200267 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:40:08,394 - app.core.excel.processor - INFO - 条码 6973497200403 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:40:08,395 - app.core.excel.processor - INFO - 条码 6873497204449 处理结果正常商品数量0单价65.0赠品数量0
2025-05-02 17:40:08,395 - app.core.excel.processor - INFO - 条码 6973497204432 处理结果正常商品数量15.0单价4.333333333333333赠品数量0
2025-05-02 17:40:08,395 - app.core.excel.processor - INFO - 条码 6973497202360 处理结果:只有赠品,数量=15.0
2025-05-02 17:40:08,395 - app.core.excel.processor - INFO - 条码 6973497202889 处理结果:只有赠品,数量=15.0
2025-05-02 17:40:08,395 - app.core.excel.processor - INFO - 条码 6973497202360 填充:仅有赠品,采购量=0赠品数量=15.0
2025-05-02 17:40:08,395 - app.core.excel.processor - INFO - 条码 6973497202889 填充:仅有赠品,采购量=0赠品数量=15.0
2025-05-02 17:40:08,399 - app.core.excel.processor - INFO - 采购单已保存到: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1).xls
2025-05-02 17:42:15,225 - app.core.excel.processor - INFO - 初始化ExcelProcessor
2025-05-02 17:42:15,226 - app.core.excel.processor - INFO - 初始化完成,模板文件: templates\银豹-采购单模板.xls
2025-05-02 17:42:15,228 - app.core.excel.processor - INFO - 开始处理Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:42:15,790 - app.core.excel.processor - INFO - 成功读取Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx, 共 11 行
2025-05-02 17:42:15,790 - app.core.excel.processor - INFO - 列名映射结果: {'barcode': '条码', 'specification': '规格', 'quantity': '数量', 'unit': '单位', 'price': '单价'}
2025-05-02 17:42:15,795 - app.core.excel.processor - INFO - 提取到 8 个商品信息
2025-05-02 17:42:15,801 - app.core.excel.processor - INFO - 开始处理8 个产品信息
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202346, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 发现正常商品条码6973497202346, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202940, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 发现正常商品条码6973497202940, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 处理商品: 条码=6973497200267, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 发现正常商品条码6973497200267, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 处理商品: 条码=6973497200403, 数量=15.0, 单价=3.6666666666666665, 是否赠品=False
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 发现正常商品条码6973497200403, 数量=15.0, 单价=3.6666666666666665
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 处理商品: 条码=6873497204449, 数量=15.0, 单价=4.333333333333333, 是否赠品=False
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 发现正常商品条码6873497204449, 数量=15.0, 单价=4.333333333333333
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 处理商品: 条码=6973497204432, 数量=15.0, 单价=4.333333333333333, 是否赠品=False
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 发现正常商品条码6973497204432, 数量=15.0, 单价=4.333333333333333
2025-05-02 17:42:15,802 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202360, 数量=15.0, 单价=0, 是否赠品=True
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 发现赠品条码6973497202360, 数量=15.0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 处理商品: 条码=6973497202889, 数量=15.0, 单价=0, 是否赠品=True
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 发现赠品条码6973497202889, 数量=15.0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 分组后共8 个不同条码的商品
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 条码 6973497202346 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 条码 6973497202940 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 条码 6973497200267 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 条码 6973497200403 处理结果正常商品数量15.0单价3.6666666666666665赠品数量0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 条码 6873497204449 处理结果正常商品数量15.0单价4.333333333333333赠品数量0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 条码 6973497204432 处理结果正常商品数量15.0单价4.333333333333333赠品数量0
2025-05-02 17:42:15,803 - app.core.excel.processor - INFO - 条码 6973497202360 处理结果:只有赠品,数量=15.0
2025-05-02 17:42:15,833 - app.core.excel.processor - INFO - 条码 6973497202889 处理结果:只有赠品,数量=15.0
2025-05-02 17:42:15,833 - app.core.excel.processor - INFO - 条码 6973497202360 填充:仅有赠品,采购量=0赠品数量=15.0
2025-05-02 17:42:15,833 - app.core.excel.processor - INFO - 条码 6973497202889 填充:仅有赠品,采购量=0赠品数量=15.0
2025-05-02 17:42:15,836 - app.core.excel.processor - INFO - 采购单已保存到: D:\My Documents\python\orc-order-v2\data\output\采购单_微信图片_20250227193150(1).xls

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:23 Active since: 2025-05-02 17:42:14

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:23 Active since: 2025-05-02 17:42:14

View File

@ -51,3 +51,15 @@
2025-05-02 17:16:24,475 - app.core.ocr.table_ocr - INFO - 使用输出目录: D:\My Documents\python\orc-order-v2\data\output 2025-05-02 17:16:24,475 - app.core.ocr.table_ocr - INFO - 使用输出目录: D:\My Documents\python\orc-order-v2\data\output
2025-05-02 17:16:24,475 - app.core.ocr.table_ocr - INFO - 使用临时目录: D:\My Documents\python\orc-order-v2\data\temp 2025-05-02 17:16:24,475 - app.core.ocr.table_ocr - INFO - 使用临时目录: D:\My Documents\python\orc-order-v2\data\temp
2025-05-02 17:16:24,475 - app.core.ocr.table_ocr - INFO - OCR处理器初始化完成输入目录: D:\My Documents\python\orc-order-v2\data\input, 输出目录: D:\My Documents\python\orc-order-v2\data\output 2025-05-02 17:16:24,475 - app.core.ocr.table_ocr - INFO - OCR处理器初始化完成输入目录: D:\My Documents\python\orc-order-v2\data\input, 输出目录: D:\My Documents\python\orc-order-v2\data\output
2025-05-02 17:32:36,460 - app.core.ocr.table_ocr - INFO - 使用输入目录: D:\My Documents\python\orc-order-v2\data\input
2025-05-02 17:32:36,460 - app.core.ocr.table_ocr - INFO - 使用输出目录: D:\My Documents\python\orc-order-v2\data\output
2025-05-02 17:32:36,460 - app.core.ocr.table_ocr - INFO - 使用临时目录: D:\My Documents\python\orc-order-v2\data\temp
2025-05-02 17:32:36,461 - app.core.ocr.table_ocr - INFO - OCR处理器初始化完成输入目录: D:\My Documents\python\orc-order-v2\data\input, 输出目录: D:\My Documents\python\orc-order-v2\data\output
2025-05-02 17:40:07,685 - app.core.ocr.table_ocr - INFO - 使用输入目录: D:\My Documents\python\orc-order-v2\data\input
2025-05-02 17:40:07,685 - app.core.ocr.table_ocr - INFO - 使用输出目录: D:\My Documents\python\orc-order-v2\data\output
2025-05-02 17:40:07,685 - app.core.ocr.table_ocr - INFO - 使用临时目录: D:\My Documents\python\orc-order-v2\data\temp
2025-05-02 17:40:07,686 - app.core.ocr.table_ocr - INFO - OCR处理器初始化完成输入目录: D:\My Documents\python\orc-order-v2\data\input, 输出目录: D:\My Documents\python\orc-order-v2\data\output
2025-05-02 17:42:15,224 - app.core.ocr.table_ocr - INFO - 使用输入目录: D:\My Documents\python\orc-order-v2\data\input
2025-05-02 17:42:15,224 - app.core.ocr.table_ocr - INFO - 使用输出目录: D:\My Documents\python\orc-order-v2\data\output
2025-05-02 17:42:15,224 - app.core.ocr.table_ocr - INFO - 使用临时目录: D:\My Documents\python\orc-order-v2\data\temp
2025-05-02 17:42:15,225 - app.core.ocr.table_ocr - INFO - OCR处理器初始化完成输入目录: D:\My Documents\python\orc-order-v2\data\input, 输出目录: D:\My Documents\python\orc-order-v2\data\output

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:23 Active since: 2025-05-02 17:42:14

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:23 Active since: 2025-05-02 17:42:14

View File

@ -25,3 +25,9 @@
2025-05-02 17:10:09,222 - app.services.ocr_service - INFO - OCRService初始化完成 2025-05-02 17:10:09,222 - app.services.ocr_service - INFO - OCRService初始化完成
2025-05-02 17:16:24,474 - app.services.ocr_service - INFO - 初始化OCRService 2025-05-02 17:16:24,474 - app.services.ocr_service - INFO - 初始化OCRService
2025-05-02 17:16:24,476 - app.services.ocr_service - INFO - OCRService初始化完成 2025-05-02 17:16:24,476 - app.services.ocr_service - INFO - OCRService初始化完成
2025-05-02 17:32:36,459 - app.services.ocr_service - INFO - 初始化OCRService
2025-05-02 17:32:36,461 - app.services.ocr_service - INFO - OCRService初始化完成
2025-05-02 17:40:07,684 - app.services.ocr_service - INFO - 初始化OCRService
2025-05-02 17:40:07,686 - app.services.ocr_service - INFO - OCRService初始化完成
2025-05-02 17:42:15,222 - app.services.ocr_service - INFO - 初始化OCRService
2025-05-02 17:42:15,225 - app.services.ocr_service - INFO - OCRService初始化完成

View File

@ -1 +1 @@
Active since: 2025-05-02 17:16:24 Active since: 2025-05-02 17:42:15

View File

@ -20,3 +20,12 @@
2025-05-02 17:16:24,476 - app.services.order_service - INFO - 初始化OrderService 2025-05-02 17:16:24,476 - app.services.order_service - INFO - 初始化OrderService
2025-05-02 17:16:24,478 - app.services.order_service - INFO - OrderService初始化完成 2025-05-02 17:16:24,478 - app.services.order_service - INFO - OrderService初始化完成
2025-05-02 17:16:24,478 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx 2025-05-02 17:16:24,478 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:32:36,461 - app.services.order_service - INFO - 初始化OrderService
2025-05-02 17:32:36,464 - app.services.order_service - INFO - OrderService初始化完成
2025-05-02 17:32:36,464 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:40:07,686 - app.services.order_service - INFO - 初始化OrderService
2025-05-02 17:40:07,689 - app.services.order_service - INFO - OrderService初始化完成
2025-05-02 17:40:07,690 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx
2025-05-02 17:42:15,225 - app.services.order_service - INFO - 初始化OrderService
2025-05-02 17:42:15,226 - app.services.order_service - INFO - OrderService初始化完成
2025-05-02 17:42:15,227 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: D:\My Documents\python\orc-order-v2\data\output\微信图片_20250227193150(1).xlsx

72
v2-优化总结.md Normal file
View File

@ -0,0 +1,72 @@
# OCR订单处理系统 v2 版本优化总结
## 主要优化点
### 1. 项目结构优化
- **模块化重构**将代码按功能分为配置、核心功能、服务和CLI等模块
- **目录结构规范化**创建统一的data目录管理所有输入和输出文件
- **配置集中管理**使用ConfigManager统一管理配置支持默认值和配置文件读取
### 2. OCR功能优化
- **修复百度API调用问题**:解决"无法获取请求ID"的错误
- **改进表格识别**:优化表格结构识别,提高识别准确率
- **增加重试机制**添加API调用失败重试机制提高成功率
### 3. 文件处理优化
- **统一文件路径**规范化文件路径处理使用data/input和data/output目录
- **简化处理流程**直接从data/input读取处理后输出到data/output无需中间转移
- **文件名处理**:优化输出文件命名方式,移除时间戳,采用"采购单_原名称.xls"格式
### 4. 单位转换优化
- **完整的单位处理规则**实现v1版本中所有的单位转换规则包括
- "件"和"箱"单位转换为"瓶"
- "提"和"盒"单位的特殊处理(区分二级和三级规格)
- 特殊条码的处理
- **规格推断**:从商品名称自动推断规格,提高数据完整性
- **单位提取**:从数量字段自动提取单位
### 5. 用户界面优化
- **双栏布局**:从单栏设计改为左右双栏布局,增加实时日志显示区域
- **多线程处理**使用多线程避免UI阻塞提升用户体验
- **状态反馈**:添加更详细的处理状态反馈,清晰显示处理进度
- **文件清理功能**:增加文件清理功能,可选择性清理输入输出文件,支持文件备份
### 6. 采购单处理优化
- **商品合并处理**:对相同条码商品进行合并处理,累计数量
- **赠品处理**:正确区分正常商品和赠品,分别处理
- **条码修正**自动修正特定错误格式的条码如5开头改为6开头
- **模板填充精确定位**:确保按照银豹采购单模板的要求正确填充数据
## 代码质量改进
1. **代码结构清晰**:遵循单一职责原则,每个模块专注于特定功能
2. **错误处理完善**:增加完整的异常处理和错误日志记录
3. **代码注释充分**:添加详细的函数和类注释,便于理解和维护
4. **类型提示**添加Python类型提示提高代码可读性和IDE支持
5. **日志系统改进**:实现分级日志系统,便于调试和问题追踪
## 文件管理改进
1. **统一目录结构**:规范化目录结构,避免多个相似功能的目录
2. **备份机制**:实现文件备份功能,避免意外数据丢失
3. **清理工具**:添加文件清理工具,可选择性地清理输入和输出文件
4. **处理记录**:保存文件处理记录,避免重复处理
## 性能优化
1. **减少文件操作**:优化文件读写次数,减少不必要的文件复制操作
2. **批量处理**:支持批量模式,提高处理效率
3. **资源释放**:及时释放文件句柄和内存资源,避免资源泄漏
## 可维护性改进
1. **配置外部化**将配置参数提取到config.ini文件便于调整
2. **模块间低耦合**:模块之间通过明确的接口交互,降低耦合度
3. **可扩展设计**:系统设计考虑未来扩展,如添加新的特殊条码处理规则
4. **完整文档**提供详细的README文档说明系统功能和使用方法

View File

@ -283,6 +283,132 @@ def organize_project_files(log_widget):
add_to_log(log_widget, "没有需要整理的文件\n") add_to_log(log_widget, "没有需要整理的文件\n")
messagebox.showinfo("整理完成", "没有需要整理的文件。") messagebox.showinfo("整理完成", "没有需要整理的文件。")
def clean_data_files(log_widget):
"""清理data目录中的文件"""
# 确保目录存在
ensure_directories()
add_to_log(log_widget, "开始清理文件...\n")
# 获取需要清理的目录
input_dir = os.path.abspath("data/input")
output_dir = os.path.abspath("data/output")
# 统计文件信息
input_files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
output_files = [f for f in os.listdir(output_dir) if os.path.isfile(os.path.join(output_dir, f))]
# 显示统计信息
add_to_log(log_widget, f"输入目录 ({input_dir}) 共有 {len(input_files)} 个文件\n")
add_to_log(log_widget, f"输出目录 ({output_dir}) 共有 {len(output_files)} 个文件\n")
# 显示确认对话框
if not input_files and not output_files:
messagebox.showinfo("清理文件", "没有需要清理的文件")
return
confirm_message = "确定要清理以下文件吗?\n\n"
confirm_message += f"- 输入目录: {len(input_files)} 个文件\n"
confirm_message += f"- 输出目录: {len(output_files)} 个文件\n"
confirm_message += "\n此操作不可撤销!"
if not messagebox.askyesno("确认清理", confirm_message):
add_to_log(log_widget, "清理操作已取消\n")
return
# 清理输入目录的文件
files_deleted = 0
# 先提示用户选择要清理的目录
options = []
if input_files:
options.append(("输入目录(data/input)", input_dir))
if output_files:
options.append(("输出目录(data/output)", output_dir))
# 创建临时的选择对话框
dialog = tk.Toplevel()
dialog.title("选择要清理的目录")
dialog.geometry("300x200")
dialog.transient(log_widget.winfo_toplevel()) # 设置为主窗口的子窗口
dialog.grab_set() # 模态对话框
tk.Label(dialog, text="请选择要清理的目录:", font=("Arial", 12)).pack(pady=10)
# 选择变量
choices = {}
for name, path in options:
var = tk.BooleanVar(value=True) # 默认选中
choices[path] = var
tk.Checkbutton(dialog, text=name, variable=var, font=("Arial", 10)).pack(anchor=tk.W, padx=20, pady=5)
# 删除前备份选项
backup_var = tk.BooleanVar(value=False)
tk.Checkbutton(dialog, text="删除前备份文件", variable=backup_var, font=("Arial", 10)).pack(anchor=tk.W, padx=20, pady=5)
result = {"confirmed": False, "choices": {}, "backup": False}
def on_confirm():
result["confirmed"] = True
result["choices"] = {path: var.get() for path, var in choices.items()}
result["backup"] = backup_var.get()
dialog.destroy()
def on_cancel():
dialog.destroy()
# 按钮
button_frame = tk.Frame(dialog)
button_frame.pack(pady=10)
tk.Button(button_frame, text="确认", command=on_confirm, width=10).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="取消", command=on_cancel, width=10).pack(side=tk.LEFT, padx=10)
# 等待对话框关闭
dialog.wait_window()
if not result["confirmed"]:
add_to_log(log_widget, "清理操作已取消\n")
return
# 备份文件
if result["backup"]:
backup_dir = os.path.join("data", "backup", datetime.datetime.now().strftime("%Y%m%d%H%M%S"))
os.makedirs(backup_dir, exist_ok=True)
add_to_log(log_widget, f"创建备份目录: {backup_dir}\n")
for dir_path, selected in result["choices"].items():
if selected:
dir_name = os.path.basename(dir_path)
backup_subdir = os.path.join(backup_dir, dir_name)
os.makedirs(backup_subdir, exist_ok=True)
files = [f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f))]
for file in files:
src = os.path.join(dir_path, file)
dst = os.path.join(backup_subdir, file)
try:
shutil.copy2(src, dst)
add_to_log(log_widget, f"已备份: {src} -> {dst}\n")
except Exception as e:
add_to_log(log_widget, f"备份失败: {src}, 错误: {e}\n")
# 删除所选目录的文件
for dir_path, selected in result["choices"].items():
if selected:
files = [f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f))]
for file in files:
file_path = os.path.join(dir_path, file)
try:
os.remove(file_path)
add_to_log(log_widget, f"已删除: {file_path}\n")
files_deleted += 1
except Exception as e:
add_to_log(log_widget, f"删除失败: {file_path}, 错误: {e}\n")
# 显示结果
add_to_log(log_widget, f"清理完成,共删除 {files_deleted} 个文件\n")
messagebox.showinfo("清理完成", f"共删除 {files_deleted} 个文件")
def main(): def main():
"""主函数""" """主函数"""
# 确保必要的目录结构存在并转移旧目录内容 # 确保必要的目录结构存在并转移旧目录内容
@ -379,6 +505,15 @@ def main():
command=lambda: organize_project_files(log_text) command=lambda: organize_project_files(log_text)
).pack(pady=5) ).pack(pady=5)
# 清理文件按钮
tk.Button(
buttons_frame,
text="清理文件",
width=20,
height=2,
command=lambda: clean_data_files(log_text)
).pack(pady=5)
# 打开输入目录 # 打开输入目录
tk.Button( tk.Button(
buttons_frame, buttons_frame,