diff --git a/README.md b/README.md index 4715a28..436473f 100644 --- a/README.md +++ b/README.md @@ -35,137 +35,167 @@ orc-order-v2/ │ │ │ └── converter.py # 单位转换与规格处理 │ │ │ │ │ └── utils/ # 工具函数 -│ │ ├── file_utils.py # 文件操作工具 -│ │ ├── log_utils.py # 日志工具 -│ │ └── string_utils.py # 字符串处理工具 +│ │ ├── file_utils.py # 文件处理工具 +│ │ └── log_utils.py # 日志工具 │ │ -│ └── services/ # 业务服务 +│ └── services/ # 服务层 │ ├── ocr_service.py # OCR服务 -│ └── order_service.py # 订单处理服务 +│ └── excel_service.py # Excel处理服务 │ ├── data/ # 数据目录 -│ ├── input/ # 输入文件 -│ ├── output/ # 输出文件 -│ └── temp/ # 临时文件 +│ ├── input/ # 输入图片目录 +│ ├── output/ # 处理结果输出目录 +│ ├── temp/ # 临时文件目录 +│ └── backup/ # 备份目录 │ ├── logs/ # 日志目录 │ -├── templates/ # 模板文件 -│ └── 银豹-采购单模板.xls # 采购单模板 +├── templates/ # 模板目录 +│ └── 银豹-采购单模板.xls # Excel模板文件 │ -├── 启动器.py # 图形界面启动器 -├── run.py # 命令行入口 ├── 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 -# OCR识别 -python run.py ocr [--input 图片路径] [--batch] - -# Excel处理 -python run.py excel [--input Excel文件路径] - -# 订单合并 -python run.py merge [--input 采购单文件路径列表] - -# 完整流程 -python run.py pipeline +pip install -r requirements.txt ``` -## 文件处理流程 +### 配置文件 -1. **OCR识别处理**: - - 读取`data/input`目录下的图片文件 - - 调用百度OCR API进行表格识别 - - 保存识别结果为Excel文件到`data/output`目录 - -2. **Excel处理**: - - 读取OCR识别生成的Excel文件 - - 提取商品信息(条码、名称、规格、单价、数量等) - - 按照采购单模板格式生成标准采购单Excel文件 - - 输出文件命名为"采购单_原文件名.xls" - -3. **采购单合并**: - - 读取所有采购单Excel文件 - - 合并相同商品的数量 - - 生成总采购单 - -## 配置说明 - -系统配置文件`config.ini`包含以下主要配置: +在`config.ini`中配置以下信息: ```ini -[API] -api_key = 您的百度API Key -secret_key = 您的百度Secret Key -timeout = 30 -max_retries = 3 -retry_delay = 2 -api_url = https://aip.baidubce.com/rest/2.0/ocr/v1/table +[OCR] +api_key = 你的百度OCR API Key +secret_key = 你的百度OCR Secret Key [Paths] input_folder = data/input output_folder = data/output -temp_folder = data/temp - -[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 +template_file = templates/银豹-采购单模板.xls ``` +## 使用方法 + +### 图形界面 + +运行`启动器.py`启动图形界面: + +```bash +python 启动器.py +``` + +图形界面包括以下功能: +- **处理单个文件**:选择并处理单个图片文件 +- **批量处理**:处理data/input目录中的所有图片文件 +- **合并处理**:合并多个采购单 +- **清理文件**:清理input和output目录中的文件 +- **查看日志**:实时显示处理日志 + +### 命令行模式 + +```bash +# 处理单个文件 +python run.py --file=image.jpg + +# 批量处理目录中的所有文件 +python run.py --batch + +# 合并采购单 +python run.py --merge +``` + +## 单位处理规则 + +系统支持多种单位的智能处理,自动识别和转换不同的计量单位。单位处理逻辑如下: + +### 标准单位处理 + +| 单位 | 处理规则 | 示例 | +|------|----------|------| +| 件 | 数量×包装数量
单价÷包装数量
单位转换为"瓶" | 1件(规格1*12) → 12瓶
单价108元/件 → 9元/瓶 | +| 箱 | 数量×包装数量
单价÷包装数量
单位转换为"瓶" | 2箱(规格1*24) → 48瓶
单价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密钥 -2. 图片质量会影响OCR识别结果,建议使用清晰的原始图片 -3. 处理大量图片时可能会受到API调用频率限制 -4. 所有处理好的文件会保存在`data/output`目录中 +1. 确保输入文件格式正确,支持jpg、png等图片格式 +2. 处理结果将输出到data/output目录下 +3. 定期清理临时文件和日志文件 +4. 及时更新百度OCR API密钥 +5. 为避免数据丢失,可使用清理功能前的备份选项 ## 错误排查 diff --git a/app/core/excel/__pycache__/processor.cpython-39.pyc b/app/core/excel/__pycache__/processor.cpython-39.pyc index 5f68473..5430a19 100644 Binary files a/app/core/excel/__pycache__/processor.cpython-39.pyc and b/app/core/excel/__pycache__/processor.cpython-39.pyc differ diff --git a/app/core/excel/converter.py b/app/core/excel/converter.py index 0cdc668..a0cfd39 100644 --- a/app/core/excel/converter.py +++ b/app/core/excel/converter.py @@ -1,30 +1,26 @@ """ -单位转换处理模块 -------------- -提供规格和单位的处理和转换功能。 +单位转换模块 +---------- +提供单位转换功能,支持规格推断和单位自动提取。 """ 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.string_utils import ( - clean_string, - extract_number, - extract_unit, - extract_number_and_unit, - parse_specification -) logger = get_logger(__name__) class UnitConverter: """ - 单位转换器:处理商品规格和单位转换 + 单位转换器:处理不同单位之间的转换,支持从商品名称推断规格 """ def __init__(self): - """初始化单位转换器""" + """ + 初始化单位转换器 + """ # 特殊条码配置 self.special_barcodes = { '6925019900087': { @@ -32,182 +28,299 @@ class UnitConverter: 'target_unit': '瓶', # 目标单位 'description': '特殊处理:数量*10,单位转换为瓶' } - # 可以在这里添加更多特殊条码的配置 + # 可以添加更多特殊条码的配置 } - # 有效的单位列表 - self.valid_units = ['件', '箱', '包', '提', '盒', '瓶', '个', '支', '袋', '副', '桶', '罐', 'L', 'l', '升'] - - # 需要特殊处理的单位 - self.special_units = ['件', '箱', '提', '盒'] - - logger.info("单位转换器初始化完成") + # 规格推断的正则表达式模式 + self.spec_patterns = [ + # 1*6、1x12、1X20等格式 + (r'(\d+)[*xX×](\d+)', r'\1*\2'), + # 1*5*12和1x5x12等三级格式 + (r'(\d+)[*xX×](\d+)[*xX×](\d+)', r'\1*\2*\3'), + # "xx入"格式,如"12入"、"24入" + (r'(\d+)入', r'1*\1'), + # "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'), + # "xxg*1"或"xx克*1"格式 + (r'([\d\.]+)(?:g|克)[*xX×]?(\d+)?', r'\1g*\2' if r'\2' else r'\1g*1'), + # "xxmL*1"或"xx毫升*1"格式 + (r'([\d\.]+)(?:mL|毫升)[*xX×]?(\d+)?', r'\1mL*\2' if r'\2' else r'\1mL*1'), + ] - def add_special_barcode(self, barcode: str, multiplier: int, target_unit: str, description: str = "") -> None: + def extract_unit_from_quantity(self, quantity_str: str) -> Tuple[Optional[float], Optional[str]]: """ - 添加特殊条码处理配置 + 从数量字符串中提取单位 Args: - barcode: 条码 - multiplier: 数量乘数 - target_unit: 目标单位 - description: 处理描述 + quantity_str: 数量字符串,如"2箱"、"5件" + + Returns: + (数量, 单位)的元组,如果无法提取则返回(None, None) """ - self.special_barcodes[barcode] = { - 'multiplier': multiplier, - 'target_unit': target_unit, - 'description': description or f'特殊处理:数量*{multiplier},单位转换为{target_unit}' - } - logger.info(f"添加特殊条码配置: {barcode}, {description}") + if not quantity_str or not isinstance(quantity_str, str): + return None, None + + # 匹配数字+单位格式 + match = re.match(r'^([\d\.]+)\s*([^\d\s\.]+)$', quantity_str.strip()) + if match: + try: + num = float(match.group(1)) + unit = match.group(2) + logger.info(f"从数量提取单位: {quantity_str} -> 数量={num}, 单位={unit}") + return num, unit + except ValueError: + pass + + return None, None - def infer_specification_from_name(self, product_name: str) -> Optional[str]: + def extract_specification(self, text: str) -> Optional[str]: """ - 从商品名称推断规格 + 从文本中提取规格信息 Args: - product_name: 商品名称 + text: 文本字符串 + + 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 product_name or not isinstance(product_name, str): + 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 - 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]]: + return None + + def parse_specification(self, spec: str) -> Tuple[int, int, Optional[int]]: """ - 从数量字符串提取单位 + 解析规格字符串,支持1*12和1*5*12等格式 Args: - quantity_str: 数量字符串 + spec: 规格字符串 Returns: - (数量, 单位)元组 + (一级包装, 二级包装, 三级包装)元组,如果是二级包装,第三个值为None """ - if not quantity_str or not isinstance(quantity_str, str): - return None, None + if not spec or not isinstance(spec, str): + return 1, 1, None - try: - # 清理数量字符串 - quantity_str = clean_string(quantity_str) - - # 提取数字和单位 - return extract_number_and_unit(quantity_str) - - except Exception as e: - logger.error(f"从数量字符串提取单位时出错: {quantity_str}, 错误: {e}") - return None, None - - def process_unit_conversion(self, product: Dict[str, Any]) -> Dict[str, Any]: + # 处理三级包装,如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: 商品字典,包含条码、单位、规格、数量和单价等字段 + product: 商品信息字典 Returns: - 处理后的商品字典 + 处理后的商品信息字典 """ - # 复制商品信息,避免修改原始数据 + # 复制原始数据,避免修改原始字典 result = product.copy() - try: - # 获取条码、单位、规格、数量和单价 - barcode = product.get('barcode', '') - unit = product.get('unit', '') - specification = product.get('specification', '') - quantity = product.get('quantity', 0) - price = product.get('price', 0) - - # 如果缺少关键信息,无法进行转换 - if not barcode or quantity == 0: - return result - - # 1. 首先检查是否是特殊条码 - if barcode in self.special_barcodes: - special_config = self.special_barcodes[barcode] - logger.info(f"应用特殊条码配置: {barcode}, {special_config['description']}") - - # 应用乘数和单位转换 - result['quantity'] = quantity * special_config['multiplier'] - result['unit'] = special_config['target_unit'] - - # 如果有单价,进行单价转换 - if price != 0: - result['price'] = price / special_config['multiplier'] - - return result - - # 2. 提取规格包装数量 - package_quantity = None - 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 - - # 标准处理:数量×包装数量,单价÷包装数量 - logger.info(f"标准单位转换: {unit}->瓶, 规格={specification}, 包装数量={package_quantity}") - result['quantity'] = quantity * package_quantity - result['unit'] = '瓶' - - if price != 0: - result['price'] = price / package_quantity - - return result - - # 4. 默认返回原始数据 + barcode = result.get('barcode', '') + unit = result.get('unit', '') + quantity = result.get('quantity', 0) + price = result.get('price', 0) + specification = result.get('specification', '') + + # 跳过无效数据 + if not barcode or not quantity: return result - except Exception as e: - logger.error(f"单位转换处理出错: {e}") - # 发生错误时,返回原始数据 - return result \ No newline at end of file + # 特殊条码处理 + if barcode in self.special_barcodes: + special_config = self.special_barcodes[barcode] + multiplier = special_config.get('multiplier', 1) + target_unit = special_config.get('target_unit', '瓶') + + # 数量乘以倍数 + new_quantity = quantity * multiplier + + # 如果有单价,单价除以倍数 + new_price = price / multiplier if price else 0 + + 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 + + # 没有规格信息,无法进行单位转换 + if not specification: + return result + + # 解析规格信息 + level1, level2, level3 = self.parse_specification(specification) + + # "件"单位处理 + 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 ['箱']: + # 计算包装数量 + 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 + + # 其他单位保持不变 + logger.info(f"其他单位处理: 保持原样 数量: {quantity}, 单价: {price}, 单位: {unit}") + return result \ No newline at end of file diff --git a/app/core/excel/processor.py b/app/core/excel/processor.py index 0f95cc2..0919dad 100644 --- a/app/core/excel/processor.py +++ b/app/core/excel/processor.py @@ -294,33 +294,100 @@ class ExcelProcessor: output_workbook = xlcopy(template_workbook) 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) - # 商品编码(条码) - output_sheet.write(row, 1, product['barcode']) - # 商品名称 - output_sheet.write(row, 2, product['name']) - # 规格 - output_sheet.write(row, 3, product['specification']) - # 单位 - output_sheet.write(row, 4, product['unit']) - # 单价 - output_sheet.write(row, 5, product['price']) - # 采购数量 - output_sheet.write(row, 6, product['quantity']) - # 采购金额(单价 × 数量) - amount = product['price'] * product['quantity'] - output_sheet.write(row, 7, amount) - # 税率 - output_sheet.write(row, 8, 0) - # 赠送量(默认为0) - output_sheet.write(row, 9, 0) + # 获取数量和单价 + quantity = product.get('quantity', 0) + price = product.get('price', 0) + + # 判断是否为赠品(价格为0) + is_gift = price == 0 + + logger.info(f"处理商品: 条码={barcode}, 数量={quantity}, 单价={price}, 是否赠品={is_gift}") + + if barcode not in barcode_groups: + barcode_groups[barcode] = { + 'normal': None, # 正常商品信息 + 'gift_quantity': 0 # 赠品数量 + } + + if is_gift: + # 是赠品,累加赠品数量 + barcode_groups[barcode]['gift_quantity'] += quantity + logger.info(f"发现赠品:条码{barcode}, 数量={quantity}") + else: + # 是正常商品 + 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) diff --git a/data/output/processed_files.json b/data/output/processed_files.json index b12e9e4..3cc8c6b 100644 --- a/data/output/processed_files.json +++ b/data/output/processed_files.json @@ -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" } \ No newline at end of file diff --git a/data/output/~$微信图片_20250227193150(1).xlsx b/data/output/~$微信图片_20250227193150(1).xlsx new file mode 100644 index 0000000..1e0c8b3 Binary files /dev/null and b/data/output/~$微信图片_20250227193150(1).xlsx differ diff --git a/data/output/微信图片_20250227193150(1).xlsx b/data/output/微信图片_20250227193150(1).xlsx index 29e7733..a433276 100644 Binary files a/data/output/微信图片_20250227193150(1).xlsx and b/data/output/微信图片_20250227193150(1).xlsx differ diff --git a/data/output/采购单_微信图片_20250227193150(1).xls b/data/output/采购单_微信图片_20250227193150(1).xls new file mode 100644 index 0000000..6eb3c91 Binary files /dev/null and b/data/output/采购单_微信图片_20250227193150(1).xls differ diff --git a/data/output/采购单_微信图片_20250227193150(1)_20250502171625.xls b/data/output/采购单_微信图片_20250227193150(1)_20250502171625.xls deleted file mode 100644 index f068002..0000000 Binary files a/data/output/采购单_微信图片_20250227193150(1)_20250502171625.xls and /dev/null differ diff --git a/logs/__main__.active b/logs/__main__.active index 6ef6632..ebbed39 100644 --- a/logs/__main__.active +++ b/logs/__main__.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:24 \ No newline at end of file +Active since: 2025-05-02 17:42:15 \ No newline at end of file diff --git a/logs/__main__.log b/logs/__main__.log index 9ccf117..877b654 100644 --- a/logs/__main__.log +++ b/logs/__main__.log @@ -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: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: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 diff --git a/logs/app.core.excel.converter.active b/logs/app.core.excel.converter.active index 6ef6632..ebbed39 100644 --- a/logs/app.core.excel.converter.active +++ b/logs/app.core.excel.converter.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:24 \ No newline at end of file +Active since: 2025-05-02 17:42:15 \ No newline at end of file diff --git a/logs/app.core.excel.converter.log b/logs/app.core.excel.converter.log index 5e93887..9b2abf2 100644 --- a/logs/app.core.excel.converter.log +++ b/logs/app.core.excel.converter.log @@ -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: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 diff --git a/logs/app.core.excel.merger.active b/logs/app.core.excel.merger.active index 6ef6632..ebbed39 100644 --- a/logs/app.core.excel.merger.active +++ b/logs/app.core.excel.merger.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:24 \ No newline at end of file +Active since: 2025-05-02 17:42:15 \ No newline at end of file diff --git a/logs/app.core.excel.merger.log b/logs/app.core.excel.merger.log index ca4a335..03f4801 100644 --- a/logs/app.core.excel.merger.log +++ b/logs/app.core.excel.merger.log @@ -26,3 +26,9 @@ 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,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 diff --git a/logs/app.core.excel.processor.active b/logs/app.core.excel.processor.active index 6ef6632..ebbed39 100644 --- a/logs/app.core.excel.processor.active +++ b/logs/app.core.excel.processor.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:24 \ No newline at end of file +Active since: 2025-05-02 17:42:15 \ No newline at end of file diff --git a/logs/app.core.excel.processor.log b/logs/app.core.excel.processor.log index c97556a..c5e6225 100644 --- a/logs/app.core.excel.processor.log +++ b/logs/app.core.excel.processor.log @@ -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,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: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 diff --git a/logs/app.core.ocr.baidu_ocr.active b/logs/app.core.ocr.baidu_ocr.active index eb74f98..564d157 100644 --- a/logs/app.core.ocr.baidu_ocr.active +++ b/logs/app.core.ocr.baidu_ocr.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:23 \ No newline at end of file +Active since: 2025-05-02 17:42:14 \ No newline at end of file diff --git a/logs/app.core.ocr.table_ocr.active b/logs/app.core.ocr.table_ocr.active index eb74f98..564d157 100644 --- a/logs/app.core.ocr.table_ocr.active +++ b/logs/app.core.ocr.table_ocr.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:23 \ No newline at end of file +Active since: 2025-05-02 17:42:14 \ No newline at end of file diff --git a/logs/app.core.ocr.table_ocr.log b/logs/app.core.ocr.table_ocr.log index 072e77c..48a95ea 100644 --- a/logs/app.core.ocr.table_ocr.log +++ b/logs/app.core.ocr.table_ocr.log @@ -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\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: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 diff --git a/logs/app.core.utils.file_utils.active b/logs/app.core.utils.file_utils.active index eb74f98..564d157 100644 --- a/logs/app.core.utils.file_utils.active +++ b/logs/app.core.utils.file_utils.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:23 \ No newline at end of file +Active since: 2025-05-02 17:42:14 \ No newline at end of file diff --git a/logs/app.services.ocr_service.active b/logs/app.services.ocr_service.active index eb74f98..564d157 100644 --- a/logs/app.services.ocr_service.active +++ b/logs/app.services.ocr_service.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:23 \ No newline at end of file +Active since: 2025-05-02 17:42:14 \ No newline at end of file diff --git a/logs/app.services.ocr_service.log b/logs/app.services.ocr_service.log index 68fa34a..0234752 100644 --- a/logs/app.services.ocr_service.log +++ b/logs/app.services.ocr_service.log @@ -25,3 +25,9 @@ 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,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初始化完成 diff --git a/logs/app.services.order_service.active b/logs/app.services.order_service.active index 6ef6632..ebbed39 100644 --- a/logs/app.services.order_service.active +++ b/logs/app.services.order_service.active @@ -1 +1 @@ -Active since: 2025-05-02 17:16:24 \ No newline at end of file +Active since: 2025-05-02 17:42:15 \ No newline at end of file diff --git a/logs/app.services.order_service.log b/logs/app.services.order_service.log index 8e2cabb..4d6bbfe 100644 --- a/logs/app.services.order_service.log +++ b/logs/app.services.order_service.log @@ -20,3 +20,12 @@ 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开始处理指定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 diff --git a/v2-优化总结.md b/v2-优化总结.md new file mode 100644 index 0000000..0b3d436 --- /dev/null +++ b/v2-优化总结.md @@ -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文档,说明系统功能和使用方法 \ No newline at end of file diff --git a/启动器.py b/启动器.py index 0f3287d..059bb22 100644 --- a/启动器.py +++ b/启动器.py @@ -283,6 +283,132 @@ def organize_project_files(log_widget): add_to_log(log_widget, "没有需要整理的文件\n") 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(): """主函数""" # 确保必要的目录结构存在并转移旧目录内容 @@ -379,6 +505,15 @@ def main(): command=lambda: organize_project_files(log_text) ).pack(pady=5) + # 清理文件按钮 + tk.Button( + buttons_frame, + text="清理文件", + width=20, + height=2, + command=lambda: clean_data_files(log_text) + ).pack(pady=5) + # 打开输入目录 tk.Button( buttons_frame,