Compare commits

..

41 Commits

Author SHA1 Message Date
houhuan c06e3e55f9 docs: 更新指南和API以反映智能文件识别功能
更新 OPENCLAW_GUIDE.md 文档,强调新的全自动智能模式,简化用户操作说明。
同时修改 headless_api.py 的默认处理逻辑,使其能自动识别输入文件类型(图片或Excel)并路由到相应处理流程,提升用户体验。
2026-03-30 15:40:19 +08:00
houhuan 32d41244e5 feat: 实现智能订单识别与自动预处理路由
- 新增智能识别功能,自动检测蓉城易购、烟草公司、杨碧月订单特征
- 修改订单服务流程,在Excel处理前自动执行专用预处理
- 更新无界面API,支持智能识别模式,简化OpenClaw集成
- 完善供应商专用预处理逻辑,修复数量计算和单位换算问题
- 添加变更日志和最终更新报告文档,记录v2.1版本变更
2026-03-30 15:36:27 +08:00
houhuan ba8520a351 fix: update special supplier identification keywords for tobacco and rongcheng 2026-03-30 13:42:39 +08:00
houhuan 26835e265a refactor: unify special suppliers processing into a single intelligent flow 2026-03-30 13:38:05 +08:00
houhuan 708402c7fb feat(订单处理): 添加杨碧月订单预处理功能
在特殊供应商服务中添加 process_yang_biyue 方法,用于处理经手人为"杨碧月"的订单。该方法能够自动识别相关列并进行数据清洗,生成标准格式的预处理文件。

同时优化订单服务的处理流程,在 process_excel 方法中集成特殊供应商预处理检查,通过 _check_special_preprocess 方法识别杨碧月订单并执行列映射转换,确保数据能够被后续标准流程正确处理。
2026-03-30 13:34:30 +08:00
houhuan b7bce93995 refactor: 重构文件读取和日志处理以提升性能和稳定性
- 新增 smart_read_excel 工具函数,统一 Excel 读取逻辑并自动选择引擎
- 重构 ConfigManager.get_path 方法,使用 pathlib 提升路径处理可靠性
- 将 GUI 日志处理改为异步队列模式,避免 UI 阻塞
- 优化 ExcelProcessor 的表头识别逻辑,避免重复读取文件
- 更新配置文件中的版本号
2026-03-30 11:17:25 +08:00
houhuan bfccdd3a37 feat(excel): 过滤非采购行并改进单位处理
- 在ExcelProcessor中增加备注列检查,过滤包含"换货"、"退货"等关键字的非采购行
- 改进单位处理器的匹配逻辑,支持"件、"、"箱装"等变体格式
- 修复config.ini文件末尾缺少换行符的问题
2026-03-30 10:24:18 +08:00
houhuan 3e2f46d26d docs(openclaw): 更新对接指南并重构蓉城易购处理逻辑
- 将蓉城易购处理逻辑从启动器迁移至专用服务类,提升代码模块化
- 更新 OpenClaw 指南,详细说明新增的多种处理模式(Excel、特殊供应商、条码映射)
- 统一 headless_api 入口,通过参数化支持不同处理流程
2026-03-30 10:15:01 +08:00
houhuan 83405a9b8e feat: update barcode mappings and improve build script robustness 2026-03-25 20:49:24 +08:00
houhuan 76f7adddd5 docs: add OpenClaw integration guide 2026-03-25 20:35:09 +08:00
houhuan cd1adc5647 feat: simplify UI and cleanup code (removed support types, mapping wizard, supplier manager, and validation matching) 2026-03-25 19:49:47 +08:00
houhuan fb12e63c4c feat(供应商管理): 新增规则引擎与词典配置支持
refactor(处理器): 重构通用供应商处理器以支持规则引擎
docs: 更新README与文档说明供应商管理功能
build: 更新打包脚本注入版本信息
test: 添加规则引擎单元测试
2025-12-12 13:46:00 +08:00
houhuan 73d17836d7 新版本 2025-11-15 18:46:03 +08:00
houhuan 9f97ac3f21 新系统 2025-08-17 15:52:49 +08:00
houhuan 3414df5317 在更新一版,更方便了 2025-08-17 15:50:21 +08:00
houhuan 556f8d8020 修复条码验证问题:在验证阶段处理过长条码,移除末尾多余的0,确保条码不会超过标准长度 2025-05-30 12:38:25 +08:00
houhuan 53e907411d 修复条码处理问题:修改format_barcode函数,移除末尾多余的0,确保条码不会超过标准长度 2025-05-30 12:14:53 +08:00
houhuan c9afe413f5 修复条码处理和数量计算问题:修复条码格式化函数,确保在数量为空时能正确计算 2025-05-30 12:08:06 +08:00
houhuan 5cf3eeed0f 添加数量为空时通过金额和单价计算数量的功能,增强规格解析能力 2025-05-30 11:54:08 +08:00
houhuan ae8d479acd bug修复 2025-05-30 10:25:46 +08:00
houhuan b3c175836a v1.1.0: 版本更新 - 增强规格解析能力、修复条码映射功能、改进特殊条码处理 2025-05-30 10:24:30 +08:00
houhuan c0fceea9dc zuihou 2025-05-13 10:02:29 +08:00
houhuan 772902c919 完整了,基本最后一次提交 2025-05-10 17:41:11 +08:00
houhuan c3a0e29b19 优化 2025-05-10 14:28:50 +08:00
houhuan 9b2007a995 小更新,但是是比较完善的版本,加油 2025-05-10 13:05:02 +08:00
houhuan 4a8169ff63 ## v1.5.3 (2024-03-21)
- 优化了完整流程处理逻辑:
  - 修改了OCR处理逻辑,当遇到已处理的图片时自动跳过并继续执行
  - 改进了错误处理,避免因图片已处理而中断流程
  - 优化了日志提示信息,提供更清晰的处理状态反馈
- 改进了OCRService的process_image方法:
  - 添加了文件存在性检查
  - 添加了文件类型验证
  - 添加了已处理文件检查
  - 优化了错误处理和日志记录
2025-05-10 12:58:28 +08:00
houhuan 201aac35e6 新增快捷键,新增日志统计 2025-05-10 12:32:10 +08:00
houhuan f5eda6cbd8 新增牛奶箱-瓶的映射 2025-05-10 12:13:04 +08:00
houhuan 5c0b709528 新增条码映射编辑功能图形化界面 2025-05-10 11:39:11 +08:00
houhuan 7b7d491663 更新之后,我也不知道有没有问题 2025-05-08 21:16:58 +08:00
houhuan 390eeb67af 新增逻辑条码映射,把件的商品拆分成单个 2025-05-07 22:30:41 +08:00
houhuan 4c8def4b04 更新readme 2025-05-07 19:29:02 +08:00
houhuan 2f088c87ca 更新修复规格逻辑 2025-05-07 19:16:33 +08:00
houhuan b9739b5267 修复一些问题 2025-05-05 19:38:51 +08:00
houhuan 0b40caaf91 最新提交,提交钱看看有没有优化的地方 2025-05-02 22:46:04 +08:00
houhuan 693c17283b 更新了README文件,添加了版本信息和更新日志 2025-05-02 19:58:27 +08:00
houhuan 71ca90ba6e v1.0正式版 2025-05-02 19:05:42 +08:00
houhuan 14eeb7b39a 日志同步到控制台显示,处理逻辑增强 2025-05-02 18:52:39 +08:00
houhuan b3cecda175 excel 2025-05-02 18:17:24 +08:00
houhuan 131fff6a7d ai说excel部分没问题了,暂且信一次,提交文件 2025-05-02 17:55:29 +08:00
houhuan 0035cd1893 增强版v2-初始化仓库,验证好了ocr部分,先备份一次 2025-05-02 17:25:47 +08:00
159 changed files with 65219 additions and 19004 deletions
-20
View File
@@ -1,20 +0,0 @@
.venv/
__pycache__/
*.pyc
.git/
.gitignore
.claude/
.playwright-mcp/
.trae/
node_modules/
web/frontend/node_modules/
data/
docs/
tests/
*.md
!README.md
release/
dist/
build/
*.spec
*.exe
-3
View File
@@ -1,3 +0,0 @@
# 百度 OCR API 配置
BAIDU_API_KEY=your_api_key_here
BAIDU_SECRET_KEY=your_secret_key_here
+3 -22
View File
@@ -1,6 +1,3 @@
# Environment
.env
# Python # Python
__pycache__/ __pycache__/
*.pyc *.pyc
@@ -18,25 +15,9 @@ release/
logs/ logs/
data/temp/ data/temp/
# Runtime data (all runtime outputs, caches, databases) # Runtime outputs
data/ data/output/
data/result/
# Claude Code / IDE
.claude/
.playwright-mcp/
.trae/
# Old project
wework_xiaoai_bot/
# Node.js
node_modules/
# Frontend build output
web/backend/static/
# Screenshots (from testing)
*.png
# OS/IDE # OS/IDE
.DS_Store .DS_Store
@@ -0,0 +1,43 @@
## 问题与目标
- 弹窗尺寸偏小,不便操作
- 弹窗及文件选择后没有在最上层,易被其他窗口遮挡
- 字段英文名不直观,需要显示中文对应(银豹模板列)
## 改进方案
### 1. 弹窗尺寸与置顶行为
- 将列映射向导窗口尺寸调整为 `780x660`,保持自适应(子控件 `fill=tk.BOTH, expand=True`
- 打开向导时:`dlg.lift()``dlg.attributes('-topmost', True)``after_idle` 取消置顶但保持焦点;`dlg.transient(root)``dlg.grab_set()` 防止被遮挡
- 选择文件后的回调中再次 `dlg.lift()` 和短暂置顶,确保返回后窗口在最上层
- 同步为模板管理窗口应用同样策略
### 2. 字段标签中文提示
- 列映射向导的标准字段改为按 `ColumnMapper.STANDARD_COLUMNS` 动态生成(若可用),并为每个字段追加中文说明,例如:
- `barcode(条码)`
- `name(商品名称)`
- `specification(规格)`
- `quantity(数量)`
- `unit(单位)`
- `unit_price(采购单价)`
- `total_price(金额/小计)`
- `category(类别)`
- `brand(品牌)`
- `supplier(供应商)`
-`ColumnMapper` 不可用,则用内置 `friendly_labels` 字典生成上述标签
### 3. 布局优化与可用性
- 保持“文件路径 + 浏览 + 预览前30行 + 加载列”四项同一行,按钮设 `padx=6`,保证易点
- 映射区使用 `ttk.Combobox(state='readonly')`,宽度适配,并确保行高足够(留白)
- 预览区保留顶部表格(前30行),支持点击行自动填充表头行号;加载列时按指定行读取
### 4. 代码改动位置
- `启动器.py`
- `open_column_mapping_wizard_alt`:调整几何尺寸与置顶;在“浏览”回调与预览/加载流程中补充 `lift/topmost`;扩展标准字段与中文标签
- `open_template_manager`:统一尺寸与置顶行为
- 仅UI与行为改动,不影响处理逻辑;保存仍写入 `suppliers_config.json``header_row``column_mapping`
### 5. 验证
- 打开列映射向导:窗口足够大,居中且在最上层;选择文件后窗口仍在最上层
- 字段标签显示为英+中:如 `name(商品名称)`
- 预览前30行与加载列同一行;点击预览行自动写入表头行号;保存后提示成功并写入配置
请确认以上方案,确认后我将立即实施并验证。
@@ -0,0 +1,41 @@
## 目标
- 在系统设置中提供“供应商管理”GUI,支持新增/编辑/删除供应商,无需手改代码或JSON。
- 一站式配置:基本信息、表头与列映射、规则与词典、模板管理,保存后即时生效。
## 界面设计
- 入口:右侧“系统设置”新增按钮“供应商管理”。
- 布局:
- 左栏:供应商列表(名称),支持搜索/新建/复制/删除。
- 右侧Tab
1) 基本信息:`name``description``filename_patterns``content_indicators``header_row`
2) 列映射与表头:嵌入现有“列映射向导”核心(预览前30行、表头选行、加载列、智能映射、导入/导出)
3) 规则与词典:词典编辑(忽略词、单位同义词、包装倍数、名称正则、默认单位/包装);规则预设与规则预览(原始→规范化)
4) 模板管理:`output_templates` 列表、当前选择与批量校验
- 操作按钮:保存(写入`suppliers_config.json`)、重载处理器、导入/导出供应商配置(单个或全部)。
## 数据流与验证
- 加载/保存:统一读写`config/suppliers_config.json`;保存后调用`ProcessorService.reload_processors()`
- 校验:复用`ProcessorService._validate_suppliers_config`;保存前进行schema校验,错误弹窗聚合列表。
- 预设:提供“基础拆分与推断”规则预设;可导入/导出自定义规则。
## 增强逻辑(自动化建议)
- 新建供应商时,可选择样例Excel:
- 自动检测表头与初始列映射;基于列名关键词给出映射建议。
- 词典预填:常见单位同义词、默认单位“瓶”、常见包装倍数(件/箱/提/盒)。
## 实施步骤
1) 创建`open_supplier_manager`弹窗(系统设置入口),左列表+右侧Tab结构。
2) 基本信息Tab:表单与校验;保存更新到JSON。
3) 列映射与表头Tab:复用现有向导组件(预览/加载/智能映射/导入/导出)。
4) 规则与词典Tab:编辑词典与规则预览,保存写入`dictionary``rules`
5) 模板管理Tab:维护`output_templates``current_template_index`,批量校验与报告显示。
6) 保存与重载:统一写入后调用处理器重载并日志提示。
## 验证
- GUI走查:新增/复制/删除供应商配置;规则预览正确;模板校验能识别缺失列。
- 处理生效:保存后立刻按新规则规范化并填充模板输出。
## 后续扩展
- 多供应商批量校验与报告导出;词典共享/继承;规则预设库扩展。
如果确认,我将开始实现该GUI及其数据流,并将现有向导整合到供应商管理中以形成一站式配置体验。
@@ -0,0 +1,70 @@
## 痛点复盘
- 不同供应商存在强差异:列名不统一、单位混杂在数量、规格隐藏在名称、供应商缺失等
- 现有向导只解决列映射层面,规则与词典编辑、执行顺序与生效范围不清晰
- 缺少可视化“从原始→规范化→模板填充”的贯通验证;流程易跑不通
## 总体方案
- 建立“供应商规则系统”:可配置、可视化、可预览,贯穿 映射→清洗→规则→模板 填充全链路
- 提供“规则库 + 词典 + 执行管道”三层抽象,支持每家供应商自定义规则组合与执行顺序
- 完善 GUI:供应商管理中的四大Tab一站式配置,规则编辑器内置预设与预览,落地即验证
## 数据模型
- `suppliers_config.json` 每个供应商对象结构:
- `name``description`
- `filename_patterns``content_indicators`
- `header_row`
- `column_mapping`(源列→标准列)
- `rules`: 有序规则数组(见下)
- `dictionary`: 解析词典(`ignore_words``unit_synonyms``pack_multipliers``name_patterns``default_unit``default_package_quantity`
- `output_templates`: 模板列表;`current_template_index`: 当前模板索引
## 规则库(首批)
- `split_quantity_unit(source)`: 拆分数量中的单位(箱/件/提/盒/瓶),无单位用默认单位
- `extract_spec_from_name(source)`: 从名称抽取规格/包装(容量×数量/简单双乘),应用忽略词与名称正则
- `normalize_unit(target,map)`: 单位归一(同义词→统一单位),件/箱/提/盒按包装倍数转换数量为“瓶”
- `compute_quantity_from_total()`: 数量缺失时用金额/单价回推数量
- `fill_missing(fills)`: 缺失填充,例如单位默认“瓶”
- `mark_gift()`: 金额/单价为0或名称含“赠品/O/o/空”标记赠品
- 后续扩展:`classify_category(name)`, `extract_brand(name)`, `strip_noise(name)`
## 执行管道
- 处理器执行顺序:映射→清洗→规则(有序)→模板填充
- 每条规则可访问 `dictionary`,执行结果在 DataFrame 上可追踪(供预览)
- 提供“预览栈”:展示原始→每步规则输出(多步Diff),定位问题
## GUI优化
- 供应商管理:
- 左侧供应商列表(搜索/新建/复制/删除/导入/导出)
- 右侧四大Tab
- 基本信息:必填校验与保存
- 列映射与表头:现有向导增强(滚动条、表头选行、智能映射、导入/导出)
- 规则与词典:
- 规则编辑器(顺序可调整:上/下移动、插入/删除规则)
- 词典编辑(忽略词、单位同义词、包装倍数、名称正则、默认值)
- 规则预设(常用组合)与“应用规则预览”(展示原始→规范化两列;可切换查看逐步Diff)
- 模板管理:模板列表/当前选择与批量校验,显示缺失/多余列报告
- 性能与体验:所有弹窗置顶回焦;滚动与水平滚动;列宽拖拽与一键导出预览为CSV
## 使用路径(推荐)
1. 新建供应商→选样例Excel→自动表头与初始映射建议
2. 规则预设:选择“基础拆分与推断”→应用规则预览→查看原始/规范化对比
3. 细化词典:补充忽略词、单位同义词、包装倍数、名称正则→再次预览
4. 保存并重载→跑一份真实文件→最近文件中打开结果核验→如有差异回到规则编辑器微调
## 验证与可视化
- 单元测试:表头识别/数量拆分/名称规格解析/单位归一/数量回推/赠品标记
- 烟雾测试:5–10类典型供货商样本端到端验证(含极端情况:无单位、名称含噪声、数量混合单位)
- 日志:每步规则执行计数与示例行输出(前/后5行),便于定位问题
## 交付物
- 规则引擎模块与规范接口;处理器接入
- 供应商管理GUI(规则编辑器、词典编辑器、预设与预览)
- 扩展配置示例与测试数据;打包脚本校验资源
## 里程碑
- Day 1:规则库与引擎扩展、处理器接入、预览栈接口
- Day 2:GUI规则编辑器(顺序调整/增删)、词典编辑器、规则预设与预览
- Day 3:模板批量校验、单元与烟雾测试、日志强化
- Day 4:回归修正与打包交付
确认后我将开始实现上述内容,确保不同供应商可独立配置精细规则并“所见即所得”验证,流程稳定可跑通。
@@ -0,0 +1,52 @@
## 目标
- 为具体供应商定制更细的解析规则(词典、包装倍数、忽略词、同义单位),并在列映射向导提供可视化编辑入口与预览。
## 配置扩展
- 扩展 `suppliers_config.json` 每个供应商对象新增:
- `dictionary`: 解析词典
- `ignore_words`: ["白膜","彩膜","赠品"](在名称解析时剔除)
- `unit_synonyms`: {"箱":"件","提":"件","盒":"件","瓶":"瓶"}
- `pack_multipliers`: {"件": 24, "箱": 24, "提": 12, "盒": 10}(缺规格时用于单位归一)
- `name_patterns`: [正则表达式](从名称抽取规格/容量×数量,如 `([\d\.]+)(ml|l|升|毫升)[*×xX](\d+)`
- `default_unit`: "瓶"
- `default_package_quantity`: 1
- `rules`: 规则数组(与现有一致),规则在执行时可访问 `dictionary`
- `output_templates`: 模板列表;`current_template_index`: 当前选择索引
## 规则引擎增强
-`app/core/handlers/rule_engine.py`
- `apply_rules(df, rules, dictionary=None)` 接口增加 `dictionary` 参数
- `extract_spec_from_name`:先剔除 `ignore_words`,匹配 `name_patterns`,无匹配时按 `pack_multipliers` 推断包装数量
- `normalize_unit`:使用 `unit_synonyms` 统一单位;如单位为“件/箱/提/盒”且有 `package_quantity``pack_multipliers`,数量×包装并单位归一为“瓶”
- `split_quantity_unit`:解析数量中的单位,同义词归一;无单位时用 `default_unit`
- 其余规则(回推数量、填充、赠品标记)保持不变
## 供应商处理器接入
- `GenericSupplierProcessor`
-`supplier_config['dictionary']` 取词典并传入 `apply_rules`,保证每家供应商按自身词典执行
- 若未配置词典,使用默认空词典
## 向导UI扩展(右侧系统设置→列映射向导)
- 增加“供应商规则”区域:
- 可编辑列表:
- 忽略词(多行输入或表格)
- 单位同义词(键值对表格:原单位→统一单位)
- 包装倍数(单位→包装数量)
- 名称正则(多行,每行一个表达式)
- 默认单位、默认包装数量(输入框)
- 操作:新增/删除、导入(JSON)/导出(JSON)、保存
- 规则预览:
- 选择预设(基础拆分与推断或自定义),点击“应用规则预览”,显示“原始/规范化”两列树表对比
- 保存行为:将 `dictionary``rules` 写入对应供应商的 `suppliers_config.json` 并重载处理器
## 验证与测试
- 单元测试:
- 名称解析(容量×数量、简单乘法、忽略词影响、同义词归一)
- 数量拆分与单位归一(“4瓶/1箱/3件/2提/2盒”)
- 包装倍数应用与数量回推
- 烟雾测试:构建 5–10 类供货商样本,验证端到端转换与模板填充可用
## 交付
- 完成词典与规则编辑入口、配置扩展与引擎接入,提交验证报告与示例配置;保留导入/导出便于你迭代调整。
确认后我将按此方案实现:扩展配置→增强规则引擎→处理器接入→向导UI与预览→测试。
@@ -0,0 +1,30 @@
## 原因
* 按钮调用了 `safe_open_validation_panel`,其内部检查 `open_validation_panel` 是否存在;当前代码中未定义该函数,导致始终提示“程序未加载,请重启”。
## 修复方案
*`启动器.py` 中新增顶层函数 `open_validation_panel(log_widget)`,与其它 `open_*` 工具函数并列,确保加载顺序稳定。
* 保留按钮绑定到 `safe_open_validation_panel`,其将直接调用新定义的 `open_validation_panel`
## 实施
* 添加 `open_validation_panel`
* 入口参数:`log_widget`
* 实现与之前描述一致:供应商选择、原始文件/期望结果选择、运行验证生成差异、生成建议并“应用建议”写回配置。
* 放置位置:`show_supported_processors``safe_open_validation_panel` 相邻区域,确保可见与可用。
## 验证
* 启动程序→系统设置→点击“验证匹配”,应正常打开面板无提示。
* 选择“农夫山泉”与提供的文件后运行验证,查看差异与建议。
## 预期
* 面板不再提示重启;功能可用。
@@ -0,0 +1,57 @@
## 目标
- 用你提供的原始文件与期望结果跑通“验证闭环”,自动对比差异并生成修正建议。
- 将“规则设置”改造成通俗易懂的“向导 + 快速模板 + 自动识别”,让普通用户也能完成操作。
## 验证闭环(立即可用)
- 新增“验证匹配”面板:
- 选择原始Excel`data/output/微信图片_20251115212128_148_108.xlsx`
- 选择期望结果:`data/result/采购单_微信图片_20251115212128_148_108.xls`
- 一键运行当前供应商流程 → 自动生成临时结果 → 与期望结果进行单元格级对比
- 输出差异报告:
- 列差异(列缺失/多余/名称不一致)
- 行差异(按条码或名称对齐,数量/单位/单价/金额差异)
- 规则差异归因(例如:数量未拆分、单位未归一)
- 按“应用建议”自动调整当前供应商的规则/词典(可撤销)
## 简化操作设计
- 两种模式:
- 简单模式(默认):
- 步骤:选择文件 → 选择供应商 → 选择快速模板 → 预览 → 生成
- 字段中文说明(name(商品名称)、quantity(数量)等)与自动建议
- 高级模式:
- 可编辑规则顺序与参数、词典、正则;具备步骤预览与Diff
- 快速模板:
- “无数量/单位列”模板:自动配置拆分数量单位→名称提取规格→单位归一→缺省填充→标记赠品
- “纯金额/单价反推数量”模板:直接回推数量
- “条码驱动匹配”模板:条码为主键对齐
## 自动识别与建议
- 列名识别:从表头关键词自动映射常用字段
- 单位与数量:从“订单数量”拆分,单位同义词与包装倍数自动套用
- 名称提取规格:词典与正则库(容量×数量、双乘、“550水24白膜”)自动解析;失败行计数提示
- 自动修正建议:
- 若数量为空:建议添加“split_quantity_unit(source=订单数量)”
- 若单位为“箱/件/提/盒”:建议添加“normalize_unit + pack_multipliers”
- 若金额与单价存在且数量为空:建议添加“compute_quantity_from_total”
## 差异对比与一键修复
- 差异可视化:原始→规范化→模板填充三栏对比;支持仅显示变化列、导出CSV
- 一键修复:将建议以“卡片”列表展示,点击应用后立即刷新预览;支持撤销上一步
## 交互流程
1) 进入“验证匹配” → 选择原始与期望 → 运行 → 查看差异
2) 点击“应用建议”直观修复规则/词典 → 差异减少直至匹配
3) 切回“简单模式”一键生成采购单
## 技术实现要点
- 供应商配置:继续使用 `suppliers_config.json`,但在GUI中隐藏JSON细节,改为中文表单与下拉
- 规则执行:保留引擎,新增“建议生成器”根据差异报告生成规则/词典变更
- 对比对齐:优先按条码对齐;无条码时按名称近似匹配(去噪后模糊比对)
## 里程碑
- Day 1:验证面板与差异对比;建议生成器(数量/单位/规格/金额)
- Day 2:简单/高级模式切换;快速模板与自动识别
- Day 3:一键修复与撤销;导出差异报告
- Day 4:回归测试与打包;提供“农夫山泉”预设并用你的样例验证匹配
确认后我将实现“验证匹配”面板并接入建议修复,先用你提供的农夫山泉样例跑通闭环,再推广到其它供应商。
@@ -0,0 +1,54 @@
## 目标
- 按照优化实施计划,从第3周任务7/8开始全面落地,并同时推进第4周任务9/10,确保可视化、配置驱动和交付质量提升。
## 任务7:列映射向导完善
- 字段扩展与中文提示:标准字段统一以英+中展示,支持更多列(条码、名称、规格、数量、单位、单价、金额/小计、分类、品牌、供应商)。
- 自动建议增强:结合样本数据统计(字符串比例、特殊关键词、数值特征)给出更可靠的映射建议。
- 表头定位增强:支持预览选择,并保存 `header_row`;向导内清晰提示当前使用的表头行。
- 配置管理:支持导入/导出映射方案(JSON),可一键应用到多供应商。
- 热重载与快捷入口:保存后自动重载供应商处理器;在系统设置区和快捷键中提供入口。
## 任务8:模板管理与校验完善
- 模板组管理:支持为每个供应商选择/保存多个模板(默认、备用),UI下拉选择当前模板。
- 差异检测:读取模板首行表头与系统标准列比较,列表显示缺失/多余字段与修复建议。
- 批量校验:可一次性校验选定供应商的所有模板组并生成报告。
- 示例生成:按标准列生成示例模板,便于对齐格式。
## 任务9:单元测试与烟雾测试
- 单元测试(pytest):
- `_find_header_row` 不同格式表头识别(第一行/中间行/合并行/空白行混杂)。
- `GenericSupplierProcessor` 列映射、清洗规则(remove_rows/fill_na/convert_type)与公式计算。
- 模板填充的关键路径(必要列检测、数量/赠送量/单价写入)。
- 烟雾测试:准备小样本(图片→OCR→Excel→采购单),验证端到端可用,记录日志与输出。
## 任务10:打包与版本信息
- 版本信息:主窗口标题与“关于”对话框显示版本号、构建时间、更新日志入口。
- 打包校验:构建后检查模板与配置文件存在性;打包时拷贝资源并生成校验报告。
- 更新脚本:完善 `build_exe.py` 支持版本号注入与资源校验失败时中止。
## 实现要点
- GUI:系统设置页整合入口;对话框使用 780×660/540 尺寸,`topmost`/`lift`/`focus_force` 保证焦点。
- 配置:`suppliers_config.json` 增加 `header_row``column_mapping``output_templates`(数组)与当前选择索引。
- 处理器:优先使用保存的表头行;模板管理选择的模板在处理时应用。
- 日志与提示:保存/校验/重载操作统一写入日志面板并气泡提示。
## 验证
- 交互走查:向导流程顺畅、字段标签明确、置顶行为正常。
- 单元测试通过率达标(≥90%覆盖关键模块);烟雾测试输出正确文件且无异常。
- 打包后 EXE 启动显示版本信息;资源齐备,最近文件可双击打开。
## 变更范围
- `启动器.py`(设置页UI、向导与模板管理弹窗)
- `app/core/processors/supplier_processors/generic_supplier_processor.py`(读取与映射逻辑)
- `app/core/excel/processor.py`(表头行识别与模板填充校验)
- `config/suppliers_config.json`(结构扩展)
- `tests/`(新增pytest用例与烟雾测试脚本)
- `build_exe.py`(版本注入与校验)
## 时间安排
- Day 1:任务7 UI/配置完成,处理器联动与验证
- Day 2:任务8 模板管理增强与批量校验
- Day 3:任务9 单元测试与烟雾测试
- Day 4:任务10 打包与版本信息,回归测试与文档补充
确认后我将按以上计划开始实施并逐项提交验证结果。
@@ -0,0 +1,52 @@
## 概览
- 目标:让改动更可见并提升识别/处理准确性与可操作性
- 范围:GUI入口与提示、表头识别与手动指定、供应商配置联动、交互一致性
## 可见入口改造
- 在“Excel处理→特殊处理”区新增两个明显按钮:
- “列映射向导”:加载 Excel 源列 → 映射到标准列 → 保存至 `config/suppliers_config.json`
- “模板管理”:选择模板、校验表头列是否包含必需项 → 保存路径至供应商配置
- 在“快捷操作”区补充一个“显示处理器类型”入口,便于确认支持的文件类型
- 在保存成功后增加气泡提示与日志条目(log 面板出现“列映射已保存”“模板路径已保存”)
## 手动表头行支持
- 在“列映射向导”弹窗增加“表头行号(从1开始)”输入框:
- 加载列时按照指定行重读 Excel 并展示源列
- 保存时将 `header_row`(零基索引)写入 `suppliers_config.json`
- 通用供应商处理器读取时优先使用 `supplier_config['header_row']`;未设置时执行自动表头检测(关键词+非空比例+字符串比例)
## 自动表头检测增强
- Excel 单文件处理与通用供应商处理器:
- 扫描范围扩大到前 30 行
- 扩充关键词:加入金额类(“金额/小计/总计/合计/合计金额”)
- 综合评分:关键词命中+非空比例+字符串比例,阈值达到即判为表头;否则选第一个有效行
## 交互与一致性
- “单个识别”按钮直接弹出图片选择(只允许 `jpg/jpeg/png/bmp`),取消即终止
- “单个处理”按钮弹出 Excel 选择(只允许 `xlsx/xls`),取消即终止,不再默认处理最新 Excel
- 处理完成后不再自动打开 `result/output` 目录,改为通过“最近文件”双击打开
- 最近文件仅记录图片/Excel,自动过滤无效并按修改时间降序展示,空历史时从 `data/output``data/result` 回填合规文件
## 供应商配置联动
- `suppliers_config.json`
- `column_mapping`:保存源列→标准列映射
- `header_row`:保存手动指定的表头行(零基)
- `output_template`:保存模板路径(相对路径)
- 处理器加载时校验并日志提示不合法项,保留已通过的配置
## 验证与可视化
- 操作日志:保存映射/模板时写入成功日志
- 处理器信息:通过“显示处理器类型”对话框列出支持的扩展名和描述
- 手动表头验证:在向导中“加载列”后,源列下拉应出现正确列名;处理后“最近文件”出现输出采购单
## 预计改动位置
- GUI按钮与向导弹窗:`启动器.py`(特殊处理区与工具函数)
- 自动表头检测与手动表头使用:
- Excel处理:`app/core/excel/processor.py``_find_header_row``process_specific_file`
- 通用供应商处理:`app/core/processors/supplier_processors/generic_supplier_processor.py``_read_supplier_data``_find_header_row`
- 供应商配置:`config/suppliers_config.json`
## 交付
- 提交代码改动后,更新打包 EXE,便携包包含上述 UI 与功能;提供操作说明与可见验证点。
请确认以上计划,确认后我将按此逐项实现并验证。
@@ -0,0 +1,50 @@
## 你现在就能这样做
- 目标:在没有“规格/数量/单位”列、只有“订单数量”列(如“4瓶/1箱/5箱/3件”)时,拆分出数量与单位,并按名称提取规格,最终规范化为银豹模板字段。
### A. 最小映射(只为模板填充)
- 在“供应商管理 → 列映射与表头”页,只需映射这些常用列:
- name ← 商品名称
- barcode ← 条码(如有)
- unit_price ← 单价(如有)
- total_price ← 金额/小计(如有)
- 不需要映射“数量/单位/规格”,后续用规则直接从原始列生成。
### B. 规则与词典设置(关键)
- 在“供应商管理 → 规则与词典”页:
1) 规则列表按顺序添加:
- split_quantity_unit,参数:source=订单数量
- extract_spec_from_name,参数:source=商品名称
- normalize_unit,参数:target=unitmap={"箱":"件","提":"件","盒":"件"}
- fill_missing,参数:fills={"unit":"瓶"}
- mark_gift(可选)
- compute_quantity_from_total(可选,当只有金额/单价时)
2) 词典设置:
- unit_synonyms{"箱":"件","提":"件","盒":"件","瓶":"瓶"}
- pack_multipliers{"件":24,"箱":24,"提":12,"盒":10}(根据你的供应商习惯调整)
- default_unit:瓶
- default_package_quantity1
- ignore_words:如(白膜、彩膜、赠品)
- name_patterns(每行一个正则):
- (\d+(?:\.\d+)?)(ml|l|升|毫升)[*×xX](\d+)
- (\d+)[*×xX](\d+)瓶
- 需要时加入供应商特有格式,如“550水24白膜”可匹配“(\d{2,3}).*?(\d{1,3})”并将第二组当作包装数
### C. 预览与验证
- 在“规则与词典”页选择Excel,点击“生成步骤预览”:
- 看“原始 → 规范化”两侧表,对比是否出现 quantity(数值)、unit(单位)、specification、package_quantity
- 在“预览步骤”中逐步查看每一条规则的结果;必要时导出CSV核对
- 保存后会自动重载处理器;用真实文件跑一次,结果会出现在“最近文件”,双击打开核验。
### D. 解释关键点
- 映射是为了模板填充字段的命名统一;对于“订单数量”这种来源列,规则会直接读取 source=订单数量,不要求你把它映射成标准列。
- 规则执行后,会产生标准字段:quantity、unit、specification、package_quantity,可被模板填充。
- normalize_unit会用词典的pack_multipliers或name解析出的package_quantity,自动把“件/箱/提/盒”的数量换算成“瓶”,并将unit统一为“瓶”。
## 我将继续优化的点(让流程更清晰)
1) 规则表单中的“来源列”改为可选下拉(直接从Excel列名取值),无需手填。
2) 步骤预览支持“仅显示变化的列(Diff模式)”与“错误计数”(未匹配的行统计)。
3) 提供“无数量/单位列”的快速模版:自动添加上述规则与词典默认值,一键套用。
4) 规则帮助提示:每条规则说明输入/输出字段与常见示例。
5) 供应商级的“测试运行”:选文件→一键规范化→预览→导出结果,独立于正式处理。
如果你同意,我将按上述方案完善GUI交互(来源列下拉、快速模板、Diff预览、错误计数与一键测试),并把默认规则与词典预设直接提供出来,确保“只有订单数量列”的场景开箱可用。
@@ -0,0 +1,42 @@
## 目标
- 通过规则引擎把不同供货商的OCR表格规范为统一字段,并自动拆分“数量+单位”、从名称推断规格、标记赠品,最终稳定填充银豹模板。
## 配置与规则
- 扩展 `suppliers_config.json`
- `rules`: 规则数组(顺序执行)
- `output_templates`: 模板列表;`current_template_index`: 当前模板索引
- 规则类型:
- `split_quantity_unit`(拆分“4瓶/1箱/3件/2提/2盒”)
- `extract_spec_from_name`(解析“1.8L×8瓶”“550水24白膜”等)
- `normalize_unit`(单位归一“箱/件/提/盒/瓶”→统一“瓶”,按包装转换数量)
- `compute_quantity_from_total`(数量缺失时用金额/单价反推)
- `infer_supplier`(文件名/内容推断供应商)
- `fill_missing`(填默认值)
- `mark_gift`(金额/单价=0或“赠品/O/o/空”)
## 实现
- 新建 `app/core/handlers/rule_engine.py`:输入DataFrame与规则列表,返回规范化DataFrame
-`GenericSupplierProcessor` 中:列映射→清洗→规则引擎→输出
- 列映射向导增强:
- 增加“规则预设”选择与“应用规则预览”按钮,显示原始/规范化后的对比
- 支持导入/导出(映射+规则)JSON
- 模板管理增强:模板组选择、批量校验、示例模板生成
## 解析与正则
- 名称解析:
- 容量×数量:`(\d+(?:\.\d+)?)(ml|l|升|毫升)[*×xX](\d+)`
- 简单数量×数量:`(\d+)[*×xX](\d+)`
- 词典忽略词:如“白膜”等
- 数量拆分:`(?P<num>\d+(?:\.\d+)?)(?P<unit>箱|件|提|盒|瓶)`
## 验证
- 单元测试覆盖规则与表头识别;烟雾测试涵盖“无单位/规格/数量混杂”的样本
- GUI预览确认规则效果;保存后热重载处理器
## 里程碑
- Day 1:规则引擎与处理器接入
- Day 2:向导规则预览与导入/导出;模板组管理
- Day 3:测试与样本库
- Day 4:打包与交付验证
请确认,我将立即开始实现并逐步提交验证结果。
-14
View File
@@ -1,19 +1,5 @@
# Changelog # Changelog
## [v2.2.0] - 2026-03-31
### Added
- **UI Simplification**: Removed dedicated buttons for Rongcheng and Tobacco; all Excel orders now use the intelligent auto-routing.
- **Enhanced Yang Biyue Support**: Fixed column mapping for Yang Biyue orders, ensuring standard fields (Barcode, Quantity, Price) are correctly extracted.
- **Headless API Auto-Detect**: `headless_api.py` now automatically distinguishes between Image (OCR) and Excel (Direct) inputs based on file extension.
### Fixed
- **Yang Biyue Preprocessing**: Resolved issue where data was empty due to incorrect column renaming.
- **Interference Filtering**: Added logic to exclude distractor columns like "Settlement Unit" or "Base Quantity" during preprocessing.
### Removed
- **Redundant Files**: Cleaned up `run.py`, `clean.py`, and unused CLI modules.
- **Legacy UI Elements**: Removed tobacco-specific keyboard shortcuts and help entries.
## [v2.1.0] - 2026-03-30 ## [v2.1.0] - 2026-03-30
### Added ### Added
- **Intelligent Recognition**: Automated fingerprinting for Rongcheng Yigou, Tobacco, and Yang Biyue orders. - **Intelligent Recognition**: Automated fingerprinting for Rongcheng Yigou, Tobacco, and Yang Biyue orders.
-146
View File
@@ -1,146 +0,0 @@
# CLAUDE.md - 益选 OCR 订单处理系统
## 项目概述
益选 OCR 订单处理系统 (orc-order-v2) 是一个面向零售与分销场景的采购单处理工具。
**核心流程**: 图片 OCR → Excel 规范化 → 模板填充 → 合并导出
**目标系统**: 银豹 (PosPal) POS 系统
**技术栈**: Python 3.9+, Tkinter, Pandas, Baidu OCR API, xlrd/xlwt/openpyxl
## 项目结构
```
orc-order-v2/
├── 启动器.py # 入口桩 (~13行, 仅导入 main)
├── headless_api.py # CLI 自动化接口 (OpenClaw 对接)
├── build_exe.py # PyInstaller 打包脚本
├── config.ini # 全局配置 (API密钥、路径)
├── config/
│ ├── config.ini # 配置副本
│ ├── barcode_mappings.json # 条码映射规则
│ └── suppliers_config.json # 供应商配置 (列映射/清洗规则/计算规则)
├── app/
│ ├── config/
│ │ ├── settings.py # ConfigManager 单例
│ │ └── defaults.py # 默认配置
│ ├── core/
│ │ ├── excel/
│ │ │ ├── processor.py # ExcelProcessor - 标准化转换核心
│ │ │ ├── converter.py # UnitConverter - 单位转换与规格推断
│ │ │ ├── merger.py # PurchaseOrderMerger - 采购单合并
│ │ │ ├── validators.py # ProductValidator
│ │ │ └── handlers/ # 条码映射、单位转换处理器
│ │ ├── handlers/
│ │ │ ├── rule_engine.py # 通用规则引擎 (split/extract/normalize/mark)
│ │ │ ├── column_mapper.py # 列映射器
│ │ │ ├── data_cleaner.py # 数据清洗器
│ │ │ └── calculator.py # 计算器
│ │ ├── ocr/
│ │ │ ├── table_ocr.py # OCRProcessor
│ │ │ └── baidu_ocr.py # BaiduOCRClient
│ │ ├── processors/
│ │ │ ├── base.py # BaseProcessor 抽象基类
│ │ │ ├── tobacco_processor.py
│ │ │ ├── ocr_processor.py
│ │ │ └── supplier_processors/
│ │ │ └── generic_supplier_processor.py
│ │ └── utils/
│ │ ├── file_utils.py # 文件操作工具
│ │ ├── log_utils.py # 日志工具
│ │ ├── string_utils.py # 字符串工具
│ │ └── dialog_utils.py # Tkinter 对话框工具
│ ├── services/
│ │ ├── order_service.py # 订单服务 (智能路由分发)
│ │ ├── ocr_service.py # OCR 服务
│ │ ├── processor_service.py # 处理器调度服务
│ │ ├── tobacco_service.py # 烟草公司专用服务
│ │ └── special_suppliers_service.py # 特殊供应商服务 (蓉城/杨碧月)
│ └── ui/ # GUI 模块 (从启动器.py拆分)
│ ├── error_utils.py # L0 错误对话框
│ ├── theme.py # L0 主题管理 (THEMES, create_modern_button)
│ ├── logging_ui.py # L0 日志队列与GUI日志处理器
│ ├── ui_widgets.py # L0 StatusBar, ProgressReporter, center_window
│ ├── user_settings.py # L1 用户设置与最近文件管理
│ ├── result_previews.py # L1 处理结果预览对话框
│ ├── command_runner.py # L1 命令执行器 (subprocess + 日志重定向)
│ ├── file_operations.py # L2 文件选择/清理/目录操作
│ ├── action_handlers.py # L2 业务操作 (OCR/Excel/合并/拖拽)
│ ├── barcode_editor.py # L2 条码映射编辑
│ ├── config_dialog.py # L3 系统设置对话框
│ ├── shortcuts.py # L3 键盘快捷键绑定
│ └── main_window.py # L4 main() 主窗口构建
├── templates/
│ ├── 银豹-采购单模板.xls # 输出模板
│ └── 商品资料.xlsx # 单价校验参考数据
├── data/
│ ├── input/ # 输入文件
│ ├── output/ # OCR 输出
│ ├── result/ # 最终采购单
│ └── user_settings.json # 用户设置
└── docs/
└── SYSTEM_ARCHITECTURE.md # 系统架构文档
```
## 命令与运行
```bash
# GUI 模式
python 启动器.py
# CLI 模式 (OpenClaw 对接)
python headless_api.py [input] [--excel|--tobacco|--rongcheng] [--barcode X --target Y]
# 打包 EXE
python build_exe.py
# 条码映射更新
python headless_api.py --update-mapping --barcode 6920584471055 --target 6920584471017
```
## 供应商智能识别逻辑
系统通过扫描 Excel 前 50 行内容特征自动路由:
| 供应商 | 识别特征 | 预处理逻辑 |
|--------|----------|-----------|
| 烟草公司 | "专卖证号" 或 "510109104938" | B/E/G/H 列映射, 数量*10, 单价/10 |
| 蓉城易购 | "RCDH" | E/N/Q/S 列映射, 多条码分裂均分数量 |
| 杨碧月 | "经手人" + "杨碧月" | 列对齐, 单位转换 (件→瓶) |
| 通用供应商 | suppliers_config.json 配置 | 列映射 + 规则引擎 |
## 配置系统
- **ConfigManager** (`app/config/settings.py`): 单例模式, 基于 configparser 读取 `config.ini`
- **供应商配置** (`config/suppliers_config.json`): JSON 格式, 定义列映射/清洗规则/计算规则
- **条码映射** (`config/barcode_mappings.json`): 运行时可更新的条码转换规则
## 关键约定
### 输出格式
- 银豹采购单模板: 4 列 — 条码(B), 采购量(C), 赠送量(D), 采购单价(E)
- 单价保留 4 位小数, 使用 xlwt.XFStyle
- 采购单文件名: `采购单_{原文件名}.xls`
### 单位转换规则
- "件"/"箱"/"提"/"盒" → 数量*包装数量, 单价/包装数量, 单位→"瓶"
- 赠品: 价格为 0 或金额为 0 的行标记为赠品
- 条码映射优先于单位转换
### 规格推断
- 从商品名称推断: "24入纸箱" → 1*24, "450g*15" → 1*15
- 支持三级规格: 1*5*12
- OCR 修正: "IL" → "1L", "6oo" → "600"
## 已知技术债务
1. ~~**启动器.py 过大**~~ (已拆分为 13 个 `app/ui/` 模块, 入口桩仅 13 行)
2. **代码重复**: 表头识别、列映射、金额解析在多处重复实现
3. **配置不统一**: config.ini + suppliers_config.json + 硬编码路径混用
4. **无测试**: 测试目录为空, 无自动化测试
5. **旧格式依赖**: xlrd/xlwt 仅支持 .xls, 不支持 .xlsx 写入
6. **API 密钥明文**: config.ini 中百度 OCR API 密钥未加密
7. **路径硬编码**: config.ini 中 `template_folder = E:\2025Code\python\orc-order-v2\templates`
8. **日志不统一**: 混用 `get_logger()``logging.getLogger()`
-29
View File
@@ -1,29 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies (tkinter needed by app/core processing)
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-tk \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY web/backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy project files
COPY app/ ./app/
COPY config/ ./config/
COPY config.ini ./
COPY templates/ ./templates/
COPY web/ ./web/
# Create data directories
RUN mkdir -p data/input data/output data/result data/temp
# Expose port
EXPOSE 18889
# Run
CMD ["python", "-m", "uvicorn", "web.backend.main:app", "--host", "0.0.0.0", "--port", "18889"]
-19
View File
@@ -1,19 +0,0 @@
# Build stage
FROM node:20-alpine AS build
WORKDIR /app
COPY web/frontend/package.json web/frontend/package-lock.json* ./
RUN npm install
COPY web/frontend/ .
# Override outDir for Docker build (vite config uses ../backend/static for local dev)
RUN npx vite build --outDir ./dist
# Production stage
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 18888
CMD ["nginx", "-g", "daemon off;"]
+45
View File
@@ -0,0 +1,45 @@
# OCR 订单处理系统 - v2.1 更新报告
## 1. 业务逻辑变更 (Business Logic Updates)
### 1.1 智能识别与自动路由
- **功能描述**:系统现在能自动扫描 Excel 前 50 行的特征码。
- **蓉城易购**:检测到 `RCDH` 关键字时自动启用专用清洗。
- **烟草公司**:检测到 `专卖证号``510109104938` 时自动启用专用清洗。
- **杨碧月**:检测到经手人为 `杨碧月` 时自动执行列对齐预处理。
### 1.2 供应商清洗规则校准
| 供应商 | 条码列 | 数量逻辑 | 单价逻辑 | 金额逻辑 |
| :--- | :--- | :--- | :--- | :--- |
| **蓉城易购** | E列 (Index 4) | N列 (Index 13),不换算 | Q列 (Index 16) | S列 (Index 18) |
| **烟草公司** | B列 (Index 1) | G列 (Index 6) **x 10** | E列 (Index 4) **/ 10** | H列 (Index 7) |
## 2. 系统接口与集成 (API & Integration)
### 2.1 Headless API (headless_api.py)
- **更新内容**`--excel` 参数现在支持全自动识别模式,OpenClaw 无需再手动区分供应商。
- **日志增强**:所有详细日志通过 `stderr` 输出,`stdout` 仅保留最终结果路径,确保自动化脚本精准捕获。
### 2.2 OpenClaw 对接文档
- **文档更新**[OPENCLAW_GUIDE.md](OPENCLAW_GUIDE.md) 已同步至 v2.1,包含最新的列映射说明和调用示例。
## 3. 测试与质量保证 (QA & Testing)
### 3.1 自动化测试
- **回归测试**:已通过 `订单1774849009841.xlsx` (蓉城) 和 `订单明细20260330133908.xlsx` (烟草) 的实测验证。
- **单价校验**:集成了银豹标准进货价对比功能,差异超过 1.0 元时自动触发警告。
### 3.2 风险清单与回滚方案
- **风险**:若供应商 Excel 格式发生重大列位移(非上述索引),自动处理可能失效。
- **回滚方案**:可通过 `git checkout v2.0` 回退至上一稳定版本。
- **监控**:建议 OpenClaw 监控 `stderr` 中的 `ERROR` 关键字并实时预警。
## 4. 变更日志 (Changelog)
- `[FIX]` 修复了蓉城易购条码分裂导致的数量计算错误(30 变 5)。
- `[FIX]` 修复了烟草订单单价计算未除以 10 的问题。
- `[NEW]` 实现了基于 `header=None` 的全局智能指纹识别。
- `[OPT]` 移除了 UI 界面中冗余的验证匹配按钮,精简流程。
---
*报告生成日期:2026-03-30*
*负责人:Trae Code Assistant*
+61
View File
@@ -0,0 +1,61 @@
# OCR 订单处理系统 - OpenClaw 对接指南 (增强版)
本指南旨在帮助 OpenClaw 实现对采购单处理系统的全功能自动化控制,涵盖图片识别、Excel 清洗、特殊供应商处理及条码映射管理。
## 1. 核心接口说明 (headless_api.py)
`headless_api.py` 是系统的统一命令行入口。它现在支持**智能文件类型识别**,大多数情况下 OpenClaw **无需携带任何参数**
### 1.1 全自动智能模式 (推荐方式)
无论是收到**图片**还是 **Excel**,都可以直接调用。系统会自动判断文件类型,如果是 Excel 则自动识别供应商指纹并处理;如果是图片则先 OCR 后再智能处理。
```bash
# 场景 1: 自动处理 data/input 中最新的文件 (图片或 Excel)
python headless_api.py
# 场景 2: 处理指定的任意文件 (图片或 Excel)
python headless_api.py "data/input/my_file.jpg"
python headless_api.py "data/input/my_file.xlsx"
```
### 1.2 显式特殊指令
仅在需要强制指定特定流程时使用。
```bash
# 强制指定为 Excel 处理模式 (即便文件没有正确后缀)
python headless_api.py --excel
# 强制映射条码
python headless_api.py --update-mapping --barcode "123" --target "456"
```
## 2. 字段与逻辑变更 (v2.1)
### 2.1 蓉城易购 (Rongcheng)
- **条码位置**:固定映射到 **E列** (Index 4)。
- **数量逻辑**:固定映射到 **N列** (Index 13)。直接提取数值,**不进行任何单位或规格换算**。
- **单价与金额**:Q列 (单价) 和 S列 (金额)。
- **条码分裂**:若条码包含 `/`, `,`, ``, `、` 等分隔符,系统将自动均分数量。
### 2.2 烟草公司 (Tobacco)
- **条码位置**:固定映射到 **B列** (Index 1)。
- **数量换算**:映射 **G列** (订单量),最终数量 = 订单量 **x 10**
- **单价换算**:映射 **E列** (批发价),最终单价 = 批发价 **/ 10**。
- **金额逻辑**:H列 (金额)。不看规格,直接按数量*单价逻辑填充。
## 3. OpenClaw 集成策略
### 3.1 任务分发逻辑
OpenClaw 应根据用户输入判断调用的参数:
- **收到图片** -> 调用默认模式。
- **收到 Excel** -> 调用 `--excel` 模式(推荐,支持全自动识别)。
- **用户明确要求“强制蓉城”** -> 调用 `--rongcheng`
- **用户明确要求“强制烟草”** -> 调用 `--tobacco`
- **用户纠正条码错误** -> 调用 `--update-mapping`
### 3.2 错误码与返回值
- **0**:处理成功,输出结果文件绝对路径。
- **1**:处理失败,详细错误信息在 `stderr`
- **WARNING** stderr 中包含 `Price validation found...` 表示价格差异过大。
---
*版本:2.1 | 更新日期:2026-03-30*
+51 -229
View File
@@ -1,243 +1,65 @@
# 益选 OCR 订单处理系统 # 益选 OCR 订单处理系统
面向零售与分销场景的采购单处理工具,支持图片 OCR → Excel 规范化 → 模板填充 → 合并导出全流程,输出适配银豹 (PosPal) POS 系统。 ## 概览
- 面向零售与分销场景的采购单处理工具,支持图片 OCR → Excel 规范化 → 模板填充 → 合并导出全流程
- 通过供应商管理与规则引擎,适配不同供应商的格式差异(数量含单位、名称包含规格、缺失列补齐)
- 提供验证匹配面板与单价校验机制,确保输出与既定模板一致且价格合理
## 核心功能 ## 核心功能
- **全自动智能识别**:系统现在能自动识别 Excel 内容特征(如:蓉城易购 RCDH、烟草公司专卖证号、杨碧月经手人),并自动路由至专用预处理流程,无需手动干预。
- **图片/Excel处理**:拖拽或选择文件,生成标准银豹采购单。
- **无界面自动化接口 (headless_api.py)**:专为 OpenClaw 等平台设计,支持 `--excel` 模式下的全自动识别。
- **单价预警机制**:自动比对 `templates/商品资料.xlsx`,若价差超过 1.0 元则触发警告。
- **智能供应商识别**:自动扫描 Excel 前 50 行内容特征,路由到对应的预处理逻辑(蓉城易购、烟草公司、杨碧月等) ## 供应商专用逻辑 (v2.1)
- **图片 OCR**:调用百度 OCR 表格识别 API,将采购单图片转为结构化 Excel ### 蓉城易购 (Rongcheng)
- **规则引擎**:支持列映射、数据清洗、单位转换、规格推断、赠品标记等自动化规则 - **精准映射**:条码(E列)、数量(N列)、单价(Q列)、金额(S列)。
- **条码映射**可配置的条码转换规则,支持运行时编辑和云端同步 - **条码分裂**支持多条码行(如 `条码1/条码2`)自动均分数量。
- **单价校验**:自动比对 `商品资料.xlsx`,价差超过 1.0 元触发预警 - **纯净数据**:直接提取原始数量,不进行多余的单位换算。
- **云端同步**:通过 Gitea REST API 在多台设备间同步配置文件(条码映射、供应商配置、商品资料、采购模板)
- **拖拽一键处理**:拖入图片或 Excel 自动走完 OCR → 规范化 → 合并全流程
- **CLI 接口**`headless_api.py` 支持无界面自动化调用
## 快速开始 ### 烟草公司 (Tobacco)
- **换算逻辑**:条码(B列)、数量(G列 x 10)、单价(E列 / 10)、总额(H列)。
- **智能跳行**:自动识别并跳过合计行及非数据行。
### 桌面端 (GUI / CLI) ### 杨碧月订单
- **自动对齐**:识别到经手人“杨碧月”后,自动将非标 Excel 转换为标准列格式。
```bash ## 关键适配(蓉城易购)
# 安装依赖 - 新模板(如“订单1765440157955.xlsx”):
pip install -r requirements.txt - 使用第三行作为表头(`header=2`
- 关键词选列并重命名到期望字段:商品条码(小条码)、订购数量(小单位)、单价(小单位)、优惠后金额(小单位)、单位
- 单位优先匹配“单位(订购单位)”列,清洗为去空白并将“件”替换为“份”
- 多条码行(逗号/顿号/斜杠/空格分隔)拆分为多行,数量均分并重算金额,单位保持订购单位
- 新模板映射:将“优惠后金额(小单位)”作为单价,“出库小计(元)”作为金额来源
# GUI 模式 ## 使用说明
python 启动器.py 1. 运行程序(EXE或源码运行)
2. 在主界面:
- 拖拽或选择图片/Excel进行处理
- 系统设置 → 供应商管理:配置供应商、列映射与规则;使用规则预览查看规范化效果
- 系统设置 → 验证匹配:选择原始与期望文件,差异对比;应用建议后重载配置
3. 处理成功后,采购单保存到 `data/output``data/result`,最近文件列表可双击打开查看
# CLI 模式 ## 构建与打包
python headless_api.py data/input/xxx.xlsx - 依赖:Python 3.9+,虚拟环境建议
python headless_api.py data/input/xxx.jpg --barcode 6920584471055 --target 6920584471017 - 安装打包工具:`pip install pyinstaller`
- 运行打包脚本:`python build_exe.py`
- 生成 EXE`dist/OCR订单处理系统.exe`
- 生成便携包:`release/OCR订单处理系统.exe`(包含 `templates/银豹-采购单模板.xls``templates/商品资料.xlsx`
# 打包 EXE ## Git 提交建议
python build_exe.py - 建议忽略构建目录与运行输出(见 `.gitignore`
``` - 保留模板与配置:`templates/银豹-采购单模板.xls``templates/商品资料.xlsx``config/config.ini``config/barcode_mappings.json`
### Web 端 ## 常见问题
- 表头识别失败:在供应商管理的“列映射与表头”页预览表头行并选择正确行号
- 数量含单位:启用 `split_quantity_unit``normalize_unit` 规则并配置单位同义词与包装倍数
- 名称中规格:配置 `ignore_words``name_patterns`,使用步骤预览确认解析效果
- 单价校验未提示:确认 `templates/商品资料.xlsx` 存在且列名包含“进货价”与条码列(`商品条码/条码/barcode`
```bash ## 变更记录(近期)
# 后端依赖 - 新增验证匹配面板与建议修复
cd web/backend && pip install -r requirements.txt - 规则预设与步骤预览(原始→逐步→规范化)
- 单价校验机制(价差>1元提示)
- 蓉城易购新模板适配(第三行表头、单位(订购单位)、多条码拆分、金额映射)
# 前端依赖
cd web/frontend && npm install
# 启动后端 (端口 8000)
cd web && python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 --reload
# 启动前端开发服务器 (端口 5173)
cd web/frontend && npm run dev
# 构建前端到后端静态目录
cd web/frontend && npm run build
# 构建后直接访问 http://localhost:8000 即可
# 生产部署 (仅后端,前端已内嵌)
cd web && python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000
```
**默认账号:** `admin` / `admin123`(首次登录后建议修改密码)
### Docker 部署 (生产环境)
**环境要求:** Docker + Docker Compose
```bash
# 1. 克隆代码
git clone https://gitea.94kan.cn/houhuan/orc-order-v2.git
cd orc-order-v2
# 2. 配置环境变量(百度 OCR API 密钥、Gitea Token 等)
cp .env.example .env # 若无 .env.example,手动创建 .env
# 编辑 .env 填入:
# BAIDU_OCR_API_KEY=xxx
# BAIDU_OCR_SECRET_KEY=xxx
# GITEA_TOKEN=xxx
# 3. 构建并启动
docker-compose up -d --build
# 4. 访问
# 前端: http://服务器IP:18888
# 后端 API: http://服务器IP:18889
# 默认账号: admin / admin123(首次登录后建议修改密码)
# 常用命令
docker-compose logs -f # 查看日志
docker-compose restart # 重启服务
docker-compose down # 停止服务
docker-compose up -d --build # 重新构建并启动
```
**端口说明:**
- `18888` — 前端 (Nginx),对外访问入口
- `18889` — 后端 API (FastAPI),前端自动代理,无需直接访问
**数据持久化:** `data/` 目录挂载到宿主机,SQLite 数据库、上传文件、处理结果不会因容器重建而丢失。
## 项目结构
```
├── 启动器.py # GUI 入口
├── headless_api.py # CLI 自动化接口
├── config.ini # 全局配置(API密钥、路径、Gitea)
├── config/
│ ├── config.ini # 配置副本
│ ├── barcode_mappings.json # 条码映射规则
│ └── suppliers_config.json # 供应商配置(列映射/规则引擎)
├── app/
│ ├── config/ # 配置管理(ConfigManager 单例)
│ ├── core/
│ │ ├── excel/ # Excel 处理(标准化、转换、合并、校验)
│ │ ├── handlers/ # 规则引擎、列映射、数据清洗、计算器
│ │ ├── ocr/ # 百度 OCR 客户端
│ │ ├── processors/ # 处理器(通用/烟草/OCR)
│ │ └── utils/ # 工具(日志、文件、字符串、云端同步、对话框)
│ ├── services/ # 业务服务(订单、OCR、处理器调度)
│ └── ui/ # GUI 模块(主题、日志、快捷键、主窗口)
├── web/ # Web 端
│ ├── backend/
│ │ ├── main.py # FastAPI 入口
│ │ ├── auth/ # JWT 认证(登录、Token、权限)
│ │ ├── routers/ # API 路由(文件、处理、记忆、条码、同步、任务、日志)
│ │ ├── services/ # 后端服务(任务管理、数据库、文件同步)
│ │ └── middleware/ # HTTP 日志中间件
│ └── frontend/
│ ├── src/
│ │ ├── views/ # 页面(Dashboard、Layout、文件管理、任务、日志等)
│ │ ├── stores/ # Pinia 状态管理(auth、processing
│ │ ├── composables/ # 共享逻辑(useDebounce、useFileUtils、useFilePreview
│ │ ├── api.ts # Axios 封装
│ │ └── router/ # Vue Router 路由
│ ├── package.json
│ └── vite.config.ts
├── templates/
│ ├── 银豹-采购单模板.xls # 输出模板(条码/采购量/赠送量/单价)
│ └── 商品资料.xlsx # 单价校验参考数据
├── data/
│ ├── input/ # 输入文件
│ ├── output/ # OCR 输出
│ ├── result/ # 最终采购单
│ └── web_data.db # Web 端数据库(SQLite
└── tests/ # 单元测试(191 个)
```
## Web 端功能
基于 Vue 3 + Element Plus + FastAPI 的浏览器端管理界面,与桌面端共享同一个 `data/` 目录。
### 处理中心 (Dashboard)
- **一键全流程**:上传图片或 Excel 后,一键完成 OCR → 标准化 → 合并全流程
- **批量 OCR / 批量处理**:可单独执行 OCR 识别或 Excel 标准化步骤
- **实时进度**:WebSocket 推送任务进度、日志、状态变更
- **多任务监控**:同时查看多个运行中任务的进度和日志
- **任务重试**:失败任务可查看错误详情并一键重试
### 文件管理
- **图片处理**:管理 `data/input/` 中的图片文件,支持上传、预览、批量 OCR、批量生成采购单
- **表格处理**:管理 `data/output/` 中的 Excel 文件,支持上传、预览、批量标准化处理
- **采购单管理**:管理 `data/result/` 中的采购单,支持预览、下载、合并、批量删除
- **实时同步**:页面加载时自动同步磁盘文件到数据库,新文件立即可见
- **清除处理缓存**:删除已处理的输出文件,允许重新处理
### 任务与日志
- **任务历史**:查看所有处理任务的状态、进度、日志,支持按状态和类型筛选
- **HTTP 日志**:记录所有 API 请求,支持按方法和状态码筛选
### 记忆库
- **产品记忆**:自动从 OCR 和处理结果中学习产品信息
- **置信度系统**:根据出现次数自动评估记忆可靠度
- **搜索与管理**:支持搜索、编辑、删除记忆条目
### 条码映射
- **映射规则管理**:添加、编辑、删除条码转换规则
- **批量操作**:支持批量导入和删除映射
### 云端同步
- **Gitea 同步**:通过 Gitea REST API 在多台设备间同步配置文件
- **一键推拉**:选择文件推送或拉取,无需 git 客户端
### 系统配置
- **配置编辑**:在浏览器中编辑系统配置(API 密钥、路径、参数)
- **修改密码**:支持修改 Web 端登录密码
### UI/UX
- **响应式布局**:适配桌面和移动端,小屏幕自动切换为抽屉式导航
- **全局错误处理**:未捕获的 Vue 错误自动显示用户提示
- **表单验证**:修改密码等操作有完整的输入验证
## 供应商智能路由
| 供应商 | 识别特征 | 处理逻辑 |
|--------|----------|----------|
| 烟草公司 | "专卖证号" 或 "510109104938" | B/E/G/H 列映射,数量×10,单价÷10 |
| 蓉城易购 | "RCDH" | E/N/Q/S 列映射,多条码分裂均分数量 |
| 杨碧月 | "经手人" + "杨碧月" | 列对齐,单位转换(件→瓶) |
| 通用供应商 | `suppliers_config.json` 配置 | 列映射 + 规则引擎 |
## 云端同步
通过 Gitea REST API 在多台设备间同步配置,无需 git 客户端。
**支持同步的文件:**
- 条码映射 (`barcode_mappings.json`)
- 供应商配置 (`suppliers_config.json`)
- 商品资料 (`templates/商品资料.xlsx`)
- 采购单模板 (`templates/银豹-采购单模板.xls`)
**配置方式:**
1. 系统设置 → 填入 Gitea 地址、仓库信息、Access Token
2. 主窗口 → "云端同步" 按钮 → 选择文件推拉
**Gitea 仓库:** `https://gitea.94kan.cn/houhuan/yixuan-sync-data`
## 配置说明
| 配置项 | 文件 | 说明 |
|--------|------|------|
| API 密钥 | `.env``config.ini` | 百度 OCR API,优先从环境变量读取 |
| Gitea Token | `.env``config.ini` | 云端同步 Token,优先从环境变量读取 |
| 供应商规则 | `config/suppliers_config.json` | 列映射、清洗规则、计算规则 |
| 条码映射 | `config/barcode_mappings.json` | 条码转换规则,运行时可更新 |
## 构建打包
```bash
pip install pyinstaller
python build_exe.py
# 输出: dist/OCR订单处理系统.exe
# 便携包: release/OCR订单处理系统.exe(含模板和商品资料)
```
## 测试
```bash
python -m pytest tests/ -v
```
+5
View File
@@ -0,0 +1,5 @@
"""
OCR订单处理系统 - 命令行接口
-------------------------
提供命令行工具,便于用户使用系统功能。
"""
+138
View File
@@ -0,0 +1,138 @@
"""
Excel处理命令行工具
---------------
提供Excel处理相关的命令行接口。
"""
import os
import sys
import argparse
from typing import List, Optional
from ..config.settings import ConfigManager
from ..core.utils.log_utils import get_logger, close_logger
from ..services.order_service import OrderService
logger = get_logger(__name__)
def create_parser() -> argparse.ArgumentParser:
"""
创建命令行参数解析器
Returns:
参数解析器
"""
parser = argparse.ArgumentParser(description='Excel处理工具')
# 通用选项
parser.add_argument('--config', type=str, help='配置文件路径')
# 子命令
subparsers = parser.add_subparsers(dest='command', help='子命令')
# 处理Excel命令
process_parser = subparsers.add_parser('process', help='处理Excel文件')
process_parser.add_argument('--input', type=str, help='输入Excel文件路径,如果不指定则处理最新的文件')
# 查看命令
list_parser = subparsers.add_parser('list', help='获取最新的Excel文件')
return parser
def process_excel(order_service: OrderService, input_file: Optional[str] = None) -> bool:
"""
处理Excel文件
Args:
order_service: 订单服务
input_file: 输入文件路径,如果为None则处理最新的文件
Returns:
处理是否成功
"""
if input_file:
if not os.path.exists(input_file):
logger.error(f"输入文件不存在: {input_file}")
return False
result = order_service.process_excel(input_file)
else:
latest_file = order_service.get_latest_excel()
if not latest_file:
logger.warning("未找到可处理的Excel文件")
return False
logger.info(f"处理最新的Excel文件: {latest_file}")
result = order_service.process_excel(latest_file)
if result:
logger.info(f"处理成功,输出文件: {result}")
return True
else:
logger.error("处理失败")
return False
def list_latest_excel(order_service: OrderService) -> bool:
"""
获取最新的Excel文件
Args:
order_service: 订单服务
Returns:
是否找到Excel文件
"""
latest_file = order_service.get_latest_excel()
if latest_file:
logger.info(f"最新的Excel文件: {latest_file}")
return True
else:
logger.info("未找到Excel文件")
return False
def main(args: Optional[List[str]] = None) -> int:
"""
Excel处理命令行主函数
Args:
args: 命令行参数,如果为None则使用sys.argv
Returns:
退出状态码
"""
parser = create_parser()
parsed_args = parser.parse_args(args)
if parsed_args.command is None:
parser.print_help()
return 1
try:
# 创建配置管理器
config = ConfigManager(parsed_args.config) if parsed_args.config else ConfigManager()
# 创建订单服务
order_service = OrderService(config)
# 根据命令执行不同功能
if parsed_args.command == 'process':
success = process_excel(order_service, parsed_args.input)
elif parsed_args.command == 'list':
success = list_latest_excel(order_service)
else:
parser.print_help()
return 1
return 0 if success else 1
except Exception as e:
logger.error(f"执行过程中发生错误: {e}")
return 1
finally:
# 关闭日志
close_logger(__name__)
if __name__ == '__main__':
sys.exit(main())
+147
View File
@@ -0,0 +1,147 @@
"""
订单合并命令行工具
--------------
提供订单合并相关的命令行接口。
"""
import os
import sys
import argparse
from typing import List, Optional
from ..config.settings import ConfigManager
from ..core.utils.log_utils import get_logger, close_logger
from ..services.order_service import OrderService
logger = get_logger(__name__)
def create_parser() -> argparse.ArgumentParser:
"""
创建命令行参数解析器
Returns:
参数解析器
"""
parser = argparse.ArgumentParser(description='订单合并工具')
# 通用选项
parser.add_argument('--config', type=str, help='配置文件路径')
# 子命令
subparsers = parser.add_subparsers(dest='command', help='子命令')
# 合并命令
merge_parser = subparsers.add_parser('merge', help='合并采购单')
merge_parser.add_argument('--input', type=str, help='输入采购单文件路径列表,以逗号分隔,如果不指定则合并所有采购单')
# 列出采购单命令
list_parser = subparsers.add_parser('list', help='列出采购单文件')
return parser
def merge_orders(order_service: OrderService, input_files: Optional[str] = None) -> bool:
"""
合并采购单
Args:
order_service: 订单服务
input_files: 输入文件路径列表,以逗号分隔,如果为None则合并所有采购单
Returns:
合并是否成功
"""
if input_files:
# 分割输入文件列表
file_paths = [path.strip() for path in input_files.split(',')]
# 检查文件是否存在
for path in file_paths:
if not os.path.exists(path):
logger.error(f"输入文件不存在: {path}")
return False
result = order_service.merge_orders(file_paths)
else:
# 获取所有采购单文件
file_paths = order_service.get_purchase_orders()
if not file_paths:
logger.warning("未找到采购单文件")
return False
logger.info(f"合并 {len(file_paths)} 个采购单文件")
result = order_service.merge_orders()
if result:
logger.info(f"合并成功,输出文件: {result}")
return True
else:
logger.error("合并失败")
return False
def list_purchase_orders(order_service: OrderService) -> bool:
"""
列出采购单文件
Args:
order_service: 订单服务
Returns:
是否有采购单文件
"""
files = order_service.get_purchase_orders()
if not files:
logger.info("未找到采购单文件")
return False
logger.info(f"采购单文件 ({len(files)}):")
for file in files:
logger.info(f" {file}")
return True
def main(args: Optional[List[str]] = None) -> int:
"""
订单合并命令行主函数
Args:
args: 命令行参数,如果为None则使用sys.argv
Returns:
退出状态码
"""
parser = create_parser()
parsed_args = parser.parse_args(args)
if parsed_args.command is None:
parser.print_help()
return 1
try:
# 创建配置管理器
config = ConfigManager(parsed_args.config) if parsed_args.config else ConfigManager()
# 创建订单服务
order_service = OrderService(config)
# 根据命令执行不同功能
if parsed_args.command == 'merge':
success = merge_orders(order_service, parsed_args.input)
elif parsed_args.command == 'list':
success = list_purchase_orders(order_service)
else:
parser.print_help()
return 1
return 0 if success else 1
except Exception as e:
logger.error(f"执行过程中发生错误: {e}")
return 1
finally:
# 关闭日志
close_logger(__name__)
if __name__ == '__main__':
sys.exit(main())
+164
View File
@@ -0,0 +1,164 @@
"""
OCR命令行工具
----------
提供OCR识别相关的命令行接口。
"""
import os
import sys
import argparse
from typing import List, Optional
from ..config.settings import ConfigManager
from ..core.utils.log_utils import get_logger, close_logger
from ..services.ocr_service import OCRService
logger = get_logger(__name__)
def create_parser() -> argparse.ArgumentParser:
"""
创建命令行参数解析器
Returns:
参数解析器
"""
parser = argparse.ArgumentParser(description='OCR识别工具')
# 通用选项
parser.add_argument('--config', type=str, help='配置文件路径')
# 子命令
subparsers = parser.add_subparsers(dest='command', help='子命令')
# 单文件处理命令
process_parser = subparsers.add_parser('process', help='处理单个文件')
process_parser.add_argument('--input', type=str, required=True, help='输入图片文件路径')
# 批量处理命令
batch_parser = subparsers.add_parser('batch', help='批量处理文件')
batch_parser.add_argument('--batch-size', type=int, help='批处理大小')
batch_parser.add_argument('--max-workers', type=int, help='最大线程数')
# 查看未处理文件命令
list_parser = subparsers.add_parser('list', help='列出未处理的文件')
return parser
def process_file(ocr_service: OCRService, input_file: str) -> bool:
"""
处理单个文件
Args:
ocr_service: OCR服务
input_file: 输入文件路径
Returns:
处理是否成功
"""
if not os.path.exists(input_file):
logger.error(f"输入文件不存在: {input_file}")
return False
if not ocr_service.validate_image(input_file):
logger.error(f"输入文件无效: {input_file}")
return False
result = ocr_service.process_image(input_file)
if result:
logger.info(f"处理成功,输出文件: {result}")
return True
else:
logger.error("处理失败")
return False
def process_batch(ocr_service: OCRService, batch_size: Optional[int] = None, max_workers: Optional[int] = None) -> bool:
"""
批量处理文件
Args:
ocr_service: OCR服务
batch_size: 批处理大小
max_workers: 最大线程数
Returns:
处理是否成功
"""
total, success = ocr_service.process_images_batch(batch_size, max_workers)
if total == 0:
logger.warning("没有找到需要处理的文件")
return False
logger.info(f"批量处理完成,总计: {total},成功: {success}")
return success > 0
def list_unprocessed(ocr_service: OCRService) -> bool:
"""
列出未处理的文件
Args:
ocr_service: OCR服务
Returns:
是否有未处理的文件
"""
files = ocr_service.get_unprocessed_images()
if not files:
logger.info("没有未处理的文件")
return False
logger.info(f"未处理的文件 ({len(files)}):")
for file in files:
logger.info(f" {file}")
return True
def main(args: Optional[List[str]] = None) -> int:
"""
OCR命令行主函数
Args:
args: 命令行参数,如果为None则使用sys.argv
Returns:
退出状态码
"""
parser = create_parser()
parsed_args = parser.parse_args(args)
if parsed_args.command is None:
parser.print_help()
return 1
try:
# 创建配置管理器
config = ConfigManager(parsed_args.config) if parsed_args.config else ConfigManager()
# 创建OCR服务
ocr_service = OCRService(config)
# 根据命令执行不同功能
if parsed_args.command == 'process':
success = process_file(ocr_service, parsed_args.input)
elif parsed_args.command == 'batch':
success = process_batch(ocr_service, parsed_args.batch_size, parsed_args.max_workers)
elif parsed_args.command == 'list':
success = list_unprocessed(ocr_service)
else:
parser.print_help()
return 1
return 0 if success else 1
except Exception as e:
logger.error(f"执行过程中发生错误: {e}")
return 1
finally:
# 关闭日志
close_logger(__name__)
if __name__ == '__main__':
sys.exit(main())
+3 -16
View File
@@ -12,20 +12,14 @@ DEFAULT_CONFIG = {
'timeout': '30', 'timeout': '30',
'max_retries': '3', 'max_retries': '3',
'retry_delay': '2', 'retry_delay': '2',
'api_url': 'https://aip.baidubce.com/rest/2.0/ocr/v1/table', 'api_url': 'https://aip.baidubce.com/rest/2.0/ocr/v1/table'
'token_url': 'https://aip.baidubce.com/oauth/2.0/token',
'form_ocr_url': 'https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/get_request_result'
}, },
'Paths': { 'Paths': {
'input_folder': 'data/input', 'input_folder': 'data/input',
'output_folder': 'data/output', 'output_folder': 'data/output',
'result_folder': 'data/result',
'temp_folder': 'data/temp', 'temp_folder': 'data/temp',
'template_folder': 'templates', 'template_folder': 'templates',
'template_file': 'templates/银豹-采购单模板.xls', 'processed_record': 'data/processed_files.json'
'processed_record': 'data/processed_files.json',
'data_dir': 'data',
'product_db': 'data/product_cache.db'
}, },
'Performance': { 'Performance': {
'max_workers': '4', 'max_workers': '4',
@@ -38,13 +32,6 @@ DEFAULT_CONFIG = {
'max_file_size_mb': '4' 'max_file_size_mb': '4'
}, },
'Templates': { 'Templates': {
'purchase_order': '银豹-采购单模板.xls', 'purchase_order': '银豹-采购单模板.xls'
'item_data': '商品资料.xlsx'
},
'Gitea': {
'base_url': 'https://gitea.94kan.cn',
'owner': 'houhuan',
'repo': 'yixuan-sync-data',
'token': ''
} }
} }
+8 -49
View File
@@ -6,16 +6,12 @@
import os import os
import configparser import configparser
import logging
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from dotenv import load_dotenv
from ..core.utils.log_utils import get_logger
from .defaults import DEFAULT_CONFIG from .defaults import DEFAULT_CONFIG
# 加载 .env 文件 logger = logging.getLogger(__name__)
load_dotenv()
logger = get_logger(__name__)
class ConfigManager: class ConfigManager:
""" """
@@ -33,23 +29,13 @@ class ConfigManager:
def _init(self, config_file): def _init(self, config_file):
"""初始化配置管理器""" """初始化配置管理器"""
# 计算应用根目录(不依赖 os.getcwd() self.config_file = config_file or 'config.ini'
import sys
if getattr(sys, 'frozen', False):
# PyInstaller 打包后,根目录是 exe 所在目录
self.app_root = os.path.dirname(sys.executable)
else:
# 源码运行,根目录是 app/config/ 的上两级
self.app_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
self.config_file = config_file or os.path.join(self.app_root, 'config.ini')
self.config = configparser.ConfigParser() self.config = configparser.ConfigParser()
self.load_config() self.load_config()
def load_config(self) -> None: def load_config(self) -> None:
""" """
加载配置文件,如果不存在则创建默认配置 加载配置文件,如果不存在则创建默认配置
API 密钥优先从环境变量 (.env) 读取
""" """
if not os.path.exists(self.config_file): if not os.path.exists(self.config_file):
self.create_default_config() self.create_default_config()
@@ -67,9 +53,6 @@ class ConfigManager:
if not self.config.has_option(section, option): if not self.config.has_option(section, option):
self.config.set(section, option, value) self.config.set(section, option, value)
# API 密钥优先从环境变量读取
self._override_from_env()
# 保存更新后的配置 # 保存更新后的配置
self.save_config() self.save_config()
logger.info(f"已加载并更新配置文件: {self.config_file}") logger.info(f"已加载并更新配置文件: {self.config_file}")
@@ -78,18 +61,6 @@ class ConfigManager:
logger.info("使用默认配置") logger.info("使用默认配置")
self.create_default_config(save=False) self.create_default_config(save=False)
def _override_from_env(self) -> None:
"""从环境变量覆盖敏感配置"""
env_mapping = {
('API', 'api_key'): 'BAIDU_API_KEY',
('API', 'secret_key'): 'BAIDU_SECRET_KEY',
('Gitea', 'token'): 'GITEA_TOKEN',
}
for (section, option), env_key in env_mapping.items():
env_val = os.getenv(env_key, '').strip()
if env_val:
self.config.set(section, option, env_val)
def create_default_config(self, save: bool = True) -> None: def create_default_config(self, save: bool = True) -> None:
"""创建默认配置""" """创建默认配置"""
for section, options in DEFAULT_CONFIG.items(): for section, options in DEFAULT_CONFIG.items():
@@ -104,25 +75,13 @@ class ConfigManager:
logger.info(f"已创建默认配置文件: {self.config_file}") logger.info(f"已创建默认配置文件: {self.config_file}")
def save_config(self) -> None: def save_config(self) -> None:
"""保存配置到文件(API 密钥不写入文件,Gitea token 需要持久化)""" """保存配置到文件"""
# 保存前临时清空 API 密钥,避免写入文件(这些从 .env 读取)
saved_keys = {}
for option in ('api_key', 'secret_key'):
try:
saved_keys[option] = self.config.get('API', option, fallback='')
except Exception:
saved_keys[option] = ''
self.config.set('API', option, '')
try: try:
with open(self.config_file, 'w', encoding='utf-8') as f: with open(self.config_file, 'w', encoding='utf-8') as f:
self.config.write(f) self.config.write(f)
logger.info(f"配置已保存到: {self.config_file}") logger.info(f"配置已保存到: {self.config_file}")
finally: except Exception as e:
# 恢复内存中的值(即使写入失败也恢复) logger.error(f"保存配置文件时出错: {e}")
for option, val in saved_keys.items():
if val:
self.config.set('API', option, val)
def get(self, section: str, option: str, fallback: Any = None) -> Any: def get(self, section: str, option: str, fallback: Any = None) -> Any:
"""获取配置值""" """获取配置值"""
@@ -163,8 +122,8 @@ class ConfigManager:
path = Path(path_str) path = Path(path_str)
if not path.is_absolute(): if not path.is_absolute():
# 相对路径,转为绝对路径(相对于应用根目录) # 相对路径,转为绝对路径(相对于项目根目录)
path = Path(self.app_root) / path path = Path(os.getcwd()) / path
if create: if create:
try: try:
View File
-609
View File
@@ -1,609 +0,0 @@
"""
商品资料 SQLite 数据库 + 商品记忆库
记忆库功能:
- 处理每步后自动学习商品数据(置信度+一致性加速)
- OCR 字段缺失时用记忆库补全 (conf > 50 直接采用)
- 价格异常检测:偏差 > 2倍触发补全,偏差 > 50% 记录预警
- 批量预加载 → 内存操作 → 批量写回,保障性能
"""
import os
import json
import sqlite3
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Callable
import pandas as pd
from ..utils.log_utils import get_logger
from ..utils.file_utils import smart_read_excel
from ...core.handlers.column_mapper import ColumnMapper
logger = get_logger(__name__)
class ProductDatabase:
"""商品资料 SQLite 数据库 + 商品记忆库"""
SCHEMA = """
CREATE TABLE IF NOT EXISTS products (
barcode TEXT PRIMARY KEY,
name TEXT DEFAULT '',
price REAL DEFAULT 0.0,
unit TEXT DEFAULT '',
updated_at TEXT,
specification TEXT DEFAULT '',
source TEXT DEFAULT 'template',
confidence INTEGER DEFAULT 0,
usage_count INTEGER DEFAULT 0,
last_seen TEXT,
avg_price REAL DEFAULT 0.0,
min_price REAL DEFAULT 0.0,
max_price REAL DEFAULT 0.0,
price_count INTEGER DEFAULT 0
);
"""
_NEW_COLUMNS = {
'specification': "TEXT DEFAULT ''",
'source': "TEXT DEFAULT 'template'",
'confidence': 'INTEGER DEFAULT 0',
'usage_count': 'INTEGER DEFAULT 0',
'last_seen': 'TEXT',
'avg_price': 'REAL DEFAULT 0.0',
'min_price': 'REAL DEFAULT 0.0',
'max_price': 'REAL DEFAULT 0.0',
'price_count': 'INTEGER DEFAULT 0',
}
def __init__(self, db_path: str, excel_source: str):
self.db_path = db_path
self.excel_source = excel_source
self._ensure_db()
def _connect(self) -> sqlite3.Connection:
return sqlite3.connect(self.db_path)
def _ensure_db(self):
if os.path.exists(self.db_path):
self._migrate_schema()
return
if not os.path.exists(self.excel_source):
logger.warning(f"商品资料 Excel 不存在: {self.excel_source}")
self._create_empty_db()
return
logger.info(f"首次运行,从 Excel 导入商品资料: {self.excel_source}")
os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
self._create_empty_db()
count = self.import_from_excel(self.excel_source)
logger.info(f"商品资料导入完成: {count} 条记录")
def _create_empty_db(self):
conn = self._connect()
try:
conn.executescript(self.SCHEMA)
conn.commit()
finally:
conn.close()
def _migrate_schema(self):
conn = self._connect()
try:
cursor = conn.execute("PRAGMA table_info(products)")
existing_cols = {row[1] for row in cursor.fetchall()}
for col_name, col_type in self._NEW_COLUMNS.items():
if col_name not in existing_cols:
conn.execute(f"ALTER TABLE products ADD COLUMN {col_name} {col_type}")
logger.info(f"数据库迁移: 添加列 {col_name}")
conn.commit()
finally:
conn.close()
# ══════════════════════════════════════════════════════════════
# 导入
# ══════════════════════════════════════════════════════════════
def import_from_excel(self, excel_path: str) -> int:
df = smart_read_excel(excel_path)
if df is None or df.empty:
return 0
barcode_col = ColumnMapper.find_column(list(df.columns), 'barcode')
if not barcode_col:
return 0
price_col = ColumnMapper.find_column(list(df.columns), 'unit_price')
if not price_col:
for col in df.columns:
if '进货价' in str(col).strip():
price_col = col
break
name_col = ColumnMapper.find_column(list(df.columns), 'name')
unit_col = ColumnMapper.find_column(list(df.columns), 'unit')
spec_col = ColumnMapper.find_column(list(df.columns), 'specification')
now = datetime.now().isoformat()
rows = []
for _, row in df.iterrows():
barcode = str(row.get(barcode_col, '')).strip()
if not barcode or barcode == 'nan':
continue
price = 0.0
if price_col:
try:
p = row.get(price_col)
if p is not None and str(p).strip() not in ('', 'nan', 'None'):
price = float(p)
except (ValueError, TypeError):
pass
name = str(row.get(name_col, '')).strip() if name_col else ''
if name == 'nan': name = ''
unit = str(row.get(unit_col, '')).strip() if unit_col else ''
if unit == 'nan': unit = ''
spec = str(row.get(spec_col, '')).strip() if spec_col else ''
if spec == 'nan': spec = ''
# template 源置信度 50
rows.append((barcode, name, price, unit, now, spec, 'template', 50, 0, now,
price, price, price, 1 if price > 0 else 0))
if not rows:
return 0
conn = self._connect()
try:
conn.executemany(
"INSERT OR REPLACE INTO products "
"(barcode, name, price, unit, updated_at, specification, source, confidence, "
"usage_count, last_seen, avg_price, min_price, max_price, price_count) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
rows)
conn.commit()
finally:
conn.close()
return len(rows)
def reimport(self) -> int:
conn = self._connect()
try:
conn.execute("DELETE FROM products")
conn.commit()
finally:
conn.close()
return self.import_from_excel(self.excel_source)
# ══════════════════════════════════════════════════════════════
# 查询
# ══════════════════════════════════════════════════════════════
def get_price(self, barcode: str) -> Optional[float]:
conn = self._connect()
try:
row = conn.execute("SELECT avg_price FROM products WHERE barcode=?",
(str(barcode).strip(),)).fetchone()
return row[0] if row and row[0] else None
finally:
conn.close()
def get_prices(self, barcodes: List[str]) -> Dict[str, float]:
if not barcodes:
return {}
conn = self._connect()
try:
placeholders = ','.join('?' * len(barcodes))
rows = conn.execute(
f"SELECT barcode, avg_price FROM products WHERE barcode IN ({placeholders})",
[str(b).strip() for b in barcodes]).fetchall()
return {r[0]: r[1] for r in rows if r[1]}
finally:
conn.close()
def count(self) -> int:
conn = self._connect()
try:
return conn.execute("SELECT COUNT(*) FROM products").fetchone()[0]
finally:
conn.close()
def get_memory(self, barcode: str) -> Optional[Dict]:
conn = self._connect()
conn.row_factory = sqlite3.Row
try:
row = conn.execute("SELECT * FROM products WHERE barcode=?",
(str(barcode).strip(),)).fetchone()
return dict(row) if row else None
finally:
conn.close()
def get_memories(self, barcodes: List[str]) -> Dict[str, Dict]:
if not barcodes:
return {}
conn = self._connect()
conn.row_factory = sqlite3.Row
try:
placeholders = ','.join('?' * len(barcodes))
rows = conn.execute(
f"SELECT * FROM products WHERE barcode IN ({placeholders})",
[str(b).strip() for b in barcodes]).fetchall()
return {r['barcode']: dict(r) for r in rows}
finally:
conn.close()
def get_all_memories(self) -> List[Dict]:
conn = self._connect()
conn.row_factory = sqlite3.Row
try:
return [dict(row) for row in
conn.execute("SELECT * FROM products ORDER BY usage_count DESC, barcode").fetchall()]
finally:
conn.close()
# ══════════════════════════════════════════════════════════════
# 批量预加载 — 性能核心
# ══════════════════════════════════════════════════════════════
def load_batch(self, barcodes: List[str]) -> Dict[str, Dict]:
"""批量预加载条码记忆到 dict — 单次 SQL,后续纯内存操作"""
if not barcodes:
return {}
conn = self._connect()
conn.row_factory = sqlite3.Row
try:
placeholders = ','.join('?' * len(barcodes))
rows = conn.execute(
f"SELECT * FROM products WHERE barcode IN ({placeholders})",
[str(b).strip() for b in barcodes]).fetchall()
return {r['barcode']: dict(r) for r in rows}
finally:
conn.close()
# ══════════════════════════════════════════════════════════════
# 学习逻辑 — 一致性加速 + 价格区间
# ══════════════════════════════════════════════════════════════
def learn_from_product(self, product: Dict, source: str = 'ocr',
memory: Dict[str, Dict] = None,
add_log: Callable = None) -> Optional[str]:
"""
从处理结果中学习,返回日志字符串。
memory: 可选的预加载批量内存,传入则零 DB 查询。
"""
barcode = str(product.get('barcode', '')).strip()
if not barcode:
return None
name = str(product.get('name', ''))
spec = str(product.get('specification', ''))
unit = str(product.get('unit', ''))
price = float(product.get('price', 0))
now = datetime.now().isoformat()
# 查现有记录(优先从内存查)
if memory is not None and barcode in memory:
row = memory[barcode]
old_name = row.get('name', '')
old_spec = row.get('specification', '')
old_unit = row.get('unit', '')
old_conf = row.get('confidence', 0)
old_count = row.get('usage_count', 0)
old_avg = row.get('avg_price', 0) or 0
old_min = row.get('min_price') or price
old_max = row.get('max_price') or price
pc = row.get('price_count', 0) or 0
exists = True
else:
conn = self._connect()
try:
cursor = conn.execute(
"SELECT name, specification, unit, confidence, usage_count, "
"avg_price, min_price, max_price, price_count FROM products WHERE barcode=?",
(barcode,)).fetchone()
finally:
conn.close()
if cursor is None:
exists = False
else:
old_name, old_spec, old_unit, old_conf, old_count, old_avg, old_min, old_max, pc = cursor
old_avg = old_avg or 0
pc = pc or 0
old_min = old_min if old_min is not None else price
old_max = old_max if old_max is not None else price
exists = True
new_count = old_count + 1 if exists else 1
# ── 置信度 ──
if source == 'user_confirmed':
new_conf = 90
elif source == 'template':
new_conf = 50
elif exists and old_conf < 50:
# 一致性加速
spec_match = bool(spec and old_spec and spec == old_spec)
unit_match = bool(unit and old_unit and unit == old_unit)
if spec_match and unit_match:
boost = 10
elif unit_match:
boost = 5
else:
boost = 3
new_conf = min(50, old_conf + boost)
elif exists:
new_conf = old_conf # > 50 稳定不变
else:
new_conf = 10 # 新 OCR 记录
# ── 价格区间 ──
if price > 0:
new_pc = (pc if exists else 0) + 1
new_avg = ((old_avg * (new_pc - 1)) + price) / new_pc if exists else price
new_min = min(old_min, price) if exists else price
new_max = max(old_max, price) if exists else price
else:
new_avg = old_avg if exists else 0
new_min = old_min if exists else 0
new_max = old_max if exists else 0
new_pc = pc if exists else 0
# ── 写入 ──
conn = self._connect()
try:
if not exists:
conn.execute(
"INSERT INTO products (barcode, name, specification, unit, price, "
"source, confidence, usage_count, last_seen, updated_at, "
"avg_price, min_price, max_price, price_count) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(barcode, name, spec, unit, price, source, new_conf, 1, now, now,
new_avg, new_min, new_max, new_pc))
log = f"记忆库新增: {barcode} {name} 源={source} 可信度={new_conf}"
else:
# 高可信度源全字段覆盖;低可信度仅填空
if source in ('template', 'user_confirmed') or new_conf > 50:
conn.execute(
"UPDATE products SET name=?, specification=?, unit=?, price=?, "
"source=?, confidence=?, usage_count=?, last_seen=?, updated_at=?, "
"avg_price=?, min_price=?, max_price=?, price_count=? WHERE barcode=?",
(name or old_name, spec or old_spec, unit or old_unit, price,
source, new_conf, new_count, now, now,
new_avg, new_min, new_max, new_pc, barcode))
else:
conn.execute(
"UPDATE products SET "
"name=CASE WHEN name='' THEN ? ELSE name END, "
"specification=CASE WHEN specification='' THEN ? ELSE specification END, "
"unit=CASE WHEN unit='' THEN ? ELSE unit END, "
"source=?, confidence=?, usage_count=?, last_seen=?, updated_at=?, "
"avg_price=?, min_price=?, max_price=?, price_count=? WHERE barcode=?",
(name, spec, unit, source, new_conf, new_count, now, now,
new_avg, new_min, new_max, new_pc, barcode))
log = f"记忆库更新: {barcode} 可信度{old_conf if exists else 0}{new_conf}"
if price > 0:
log += f" 均价{new_avg:.4f}({new_pc}次)"
conn.commit()
# 更新内存 dict(如果传入了)
if memory is not None and barcode in memory:
memory[barcode].update({
'confidence': new_conf, 'usage_count': new_count,
'avg_price': new_avg, 'min_price': new_min,
'max_price': new_max, 'price_count': new_pc,
'name': name or old_name,
'specification': spec or old_spec,
'unit': unit or old_unit,
})
if add_log:
add_log(log)
return log
finally:
conn.close()
def learn_from_products(self, products: List[Dict], source: str = 'ocr',
add_log: Callable = None) -> int:
"""批量学习 — 先批量预加载,再逐条处理,返回更新条数"""
barcodes = [str(p.get('barcode', '')) for p in products if p.get('barcode')]
memory = self.load_batch(barcodes)
count = 0
for p in products:
try:
result = self.learn_from_product(p, source, memory=memory, add_log=add_log)
if result:
count += 1
except Exception as e:
logger.warning(f"学习商品记忆失败: {e}")
return count
# ══════════════════════════════════════════════════════════════
# 记忆辅助 — OCR 补全
# ══════════════════════════════════════════════════════════════
def _price_anomaly(self, product: Dict, mem: Dict) -> bool:
"""价格异常:> 2倍偏差"""
price = float(product.get('price', 0))
avg = mem.get('avg_price', 0)
if not price or not avg:
return False
return price > avg * 2 or price < avg * 0.5
def fill_from_memory(self, barcode: str, ocr_result: Dict,
memory: Dict[str, Dict] = None) -> Tuple[Dict, str]:
"""用记忆库补全 OCR 缺失字段。返回 (补全后的dict, 日志字符串)"""
if memory:
mem = memory.get(barcode)
else:
mem = self.get_memory(barcode)
if not mem or mem.get('confidence', 0) < 10:
return ocr_result, ""
logs = []
result = dict(ocr_result)
conf = mem.get('confidence', 0)
has_spec = result.get('specification')
has_unit = result.get('unit')
price = float(result.get('price', 0))
if conf > 50 and not has_spec and mem.get('specification'):
result['specification'] = mem['specification']
logs.append(f"规格补全(可信{conf}): {barcode}{mem['specification']}")
elif not has_spec and mem.get('specification') and self._price_anomaly(result, mem):
result['specification'] = mem['specification']
logs.append(f"价格异常→规格补全: {barcode} 本次{price:.2f} vs 均价{mem['avg_price']:.2f}{mem['specification']}")
if conf > 50 and not has_unit and mem.get('unit'):
result['unit'] = mem['unit']
logs.append(f"单位补全(可信{conf}): {barcode}{mem['unit']}")
elif not has_unit and mem.get('unit') and self._price_anomaly(result, mem):
result['unit'] = mem['unit']
logs.append(f"价格异常→单位补全: {barcode}{mem['unit']}")
return result, "; ".join(logs)
def price_warning(self, barcode: str, price: float,
memory: Dict[str, Dict] = None) -> Optional[str]:
"""价格预警。> 50% 偏差告警"""
if memory:
mem = memory.get(barcode)
else:
mem = self.get_memory(barcode)
if not mem or not mem.get('avg_price'):
return None
avg = mem['avg_price']
min_p = mem.get('min_price', avg)
max_p = mem.get('max_price', avg)
pc = mem.get('price_count', 0)
if price > avg * 1.5 or price < avg * 0.5:
return (f"单价预警: {barcode} 本次{price:.4f}元 vs "
f"历史均价{avg:.4f} (范围{min_p:.4f}~{max_p:.4f}, {pc}次)")
return None
# ══════════════════════════════════════════════════════════════
# 手动编辑
# ══════════════════════════════════════════════════════════════
def update_memory(self, barcode: str, fields: Dict) -> bool:
barcode = str(barcode).strip()
if not barcode:
return False
allowed = {'name', 'specification', 'unit', 'price', 'confidence'}
updates = {k: v for k, v in fields.items() if k in allowed}
if not updates:
return False
now = datetime.now().isoformat()
set_clause = ', '.join(f"{k}=?" for k in updates)
values = list(updates.values())
extra_sql = ", source='user_confirmed'"
if 'confidence' not in updates:
extra_sql += ", confidence=90"
conn = self._connect()
try:
conn.execute(
f"UPDATE products SET {set_clause}{extra_sql}, updated_at=? WHERE barcode=?",
values + [now, barcode])
conn.commit()
return conn.total_changes > 0
finally:
conn.close()
def delete_memory(self, barcode: str) -> bool:
conn = self._connect()
try:
conn.execute("DELETE FROM products WHERE barcode=?", (str(barcode).strip(),))
conn.commit()
return conn.total_changes > 0
finally:
conn.close()
# ══════════════════════════════════════════════════════════════
# 云端同步
# ══════════════════════════════════════════════════════════════
def export_for_sync(self) -> Dict:
conn = self._connect()
try:
cursor = conn.execute(
"SELECT barcode, name, specification, unit, price, source, "
"confidence, usage_count, last_seen, avg_price, min_price, max_price, price_count "
"FROM products")
result = {}
for row in cursor.fetchall():
result[row[0]] = {
'name': row[1], 'specification': row[2], 'unit': row[3],
'price': row[4], 'source': row[5], 'confidence': row[6],
'usage_count': row[7], 'last_seen': row[8],
'avg_price': row[9], 'min_price': row[10],
'max_price': row[11], 'price_count': row[12],
}
return result
finally:
conn.close()
def import_from_sync(self, data: Dict) -> int:
now = datetime.now().isoformat()
count = 0
conn = self._connect()
try:
for barcode, info in data.items():
barcode = str(barcode).strip()
if not barcode:
continue
name = str(info.get('name', ''))
spec = str(info.get('specification', ''))
unit = str(info.get('unit', ''))
price = float(info.get('price', 0))
remote_source = str(info.get('source', 'ocr'))
remote_conf = int(info.get('confidence', 50))
remote_count = int(info.get('usage_count', 1))
remote_seen = str(info.get('last_seen', now))
remote_avg = float(info.get('avg_price', price))
remote_min = float(info.get('min_price', price))
remote_max = float(info.get('max_price', price))
remote_pc = int(info.get('price_count', 1))
row = conn.execute("SELECT confidence FROM products WHERE barcode=?",
(barcode,)).fetchone()
if row is None:
conn.execute(
"INSERT INTO products (barcode, name, specification, unit, price, "
"source, confidence, usage_count, last_seen, updated_at, "
"avg_price, min_price, max_price, price_count) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
(barcode, name, spec, unit, price, remote_source, remote_conf,
remote_count, remote_seen, now,
remote_avg, remote_min, remote_max, remote_pc))
count += 1
else:
local_conf = row[0]
if remote_conf > local_conf:
conn.execute(
"UPDATE products SET name=?, specification=?, unit=?, price=?, "
"source=?, confidence=?, usage_count=?, last_seen=?, updated_at=?, "
"avg_price=?, min_price=?, max_price=?, price_count=? WHERE barcode=?",
(name, spec, unit, price, remote_source, remote_conf,
remote_count, remote_seen, now,
remote_avg, remote_min, remote_max, remote_pc, barcode))
count += 1
elif remote_conf == local_conf:
conn.execute(
"UPDATE products SET "
"name=CASE WHEN name='' THEN ? ELSE name END, "
"specification=CASE WHEN specification='' THEN ? ELSE specification END, "
"unit=CASE WHEN unit='' THEN ? ELSE unit END, "
"usage_count=MAX(usage_count, ?), updated_at=? WHERE barcode=?",
(name, spec, unit, remote_count, now, barcode))
count += 1
conn.commit()
finally:
conn.close()
return count
def _export_memory_json(self, json_path=None):
"""导出记忆库为 JSON(兼容旧代码调用)"""
import os as _os
if json_path is None:
json_path = _os.path.join(_os.path.dirname(self.db_path), 'product_memory.json')
data = self.export_for_sync()
_os.makedirs(_os.path.dirname(json_path), exist_ok=True)
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return json_path
+67 -22
View File
@@ -16,7 +16,6 @@ from datetime import datetime
from ...config.settings import ConfigManager from ...config.settings import ConfigManager
from ..utils.log_utils import get_logger from ..utils.log_utils import get_logger
from ..handlers.column_mapper import ColumnMapper
from ..utils.file_utils import ( from ..utils.file_utils import (
ensure_dir, ensure_dir,
get_file_extension, get_file_extension,
@@ -49,7 +48,7 @@ class PurchaseOrderMerger:
# 修复ConfigParser对象没有get_path方法的问题 # 修复ConfigParser对象没有get_path方法的问题
try: try:
# 获取输出目录 # 获取输出目录
self.output_dir = config.get_path('Paths', 'output_folder', fallback='data/output', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/output') self.output_dir = config.get('Paths', 'output_folder', fallback='data/output')
# 确保目录存在 # 确保目录存在
os.makedirs(self.output_dir, exist_ok=True) os.makedirs(self.output_dir, exist_ok=True)
@@ -96,8 +95,8 @@ class PurchaseOrderMerger:
Returns: Returns:
采购单文件路径列表 采购单文件路径列表
""" """
# 采购单文件保存在result目录 # 采购单文件保存在data/result目录
result_dir = self.config.get_path('Paths', 'result_folder', fallback='data/result', create=True) if hasattr(self.config, 'get_path') else os.path.abspath('data/result') result_dir = "data/result"
logger.info(f"搜索目录 {result_dir} 中的采购单Excel文件") logger.info(f"搜索目录 {result_dir} 中的采购单Excel文件")
# 确保目录存在 # 确保目录存在
@@ -141,12 +140,19 @@ class PurchaseOrderMerger:
logger.debug(f"Excel文件的列名: {df.columns.tolist()}") logger.debug(f"Excel文件的列名: {df.columns.tolist()}")
# 处理特殊情况:检查是否需要读取指定行作为标题行 # 处理特殊情况:检查是否需要读取指定行作为标题行
header_row_idx = ColumnMapper.detect_header_row(df, max_rows=5, min_matches=3) for header_row_idx in range(5): # 检查前5行
if header_row_idx >= 0: if len(df) <= header_row_idx:
continue
potential_header = df.iloc[header_row_idx].astype(str)
header_keywords = ['条码', '条形码', '商品条码', '商品名称', '规格', '单价', '数量', '金额', '单位', '必填']
matches = sum(1 for keyword in header_keywords if any(keyword in str(val) for val in potential_header.values))
if matches >= 3: # 如果至少匹配3个关键词,认为是表头
logger.info(f"检测到表头在第 {header_row_idx+1}") logger.info(f"检测到表头在第 {header_row_idx+1}")
# 使用此行作为列名,数据从下一行开始 # 使用此行作为列名,数据从下一行开始
header_row = df.iloc[header_row_idx].astype(str) header_row = potential_header
data_rows = df.iloc[header_row_idx+1:].reset_index(drop=True) data_rows = df.iloc[header_row_idx+1:].reset_index(drop=True)
# 为每一列分配名称(避免重复的列名) # 为每一列分配名称(避免重复的列名)
@@ -162,24 +168,63 @@ class PurchaseOrderMerger:
data_rows.columns = new_columns data_rows.columns = new_columns
df = data_rows df = data_rows
logger.debug(f"重新构建的数据帧列名: {df.columns.tolist()}") logger.debug(f"重新构建的数据帧列名: {df.columns.tolist()}")
break
# 使用 ColumnMapper 统一查找列名(保留中文键名以兼容下游代码) # 定义可能的列名映射
column_mapping = {
'条码': ['条码', '条形码', '商品条码', 'barcode', '商品条形码', '条形码', '商品条码', '商品编码', '商品编号', '条形码', '条码(必填)'],
'采购量': ['数量', '采购数量', '购买数量', '采购数量', '订单数量', '采购数量', '采购量(必填)', '采购量', '数量(必填)'],
'采购单价': ['单价', '价格', '采购单价', '销售价', '采购单价(必填)', '单价(必填)', '价格(必填)'],
'赠送量': ['赠送量', '赠品数量', '赠送数量', '赠品']
}
# 显示所有列名,用于调试
all_columns = df.columns.tolist() all_columns = df.columns.tolist()
logger.info(f"列名: {all_columns}") logger.info(f"列名: {all_columns}")
standard_to_chinese = { # 映射实际的列名
'barcode': '条码',
'quantity': '采购量',
'unit_price': '采购单价',
'gift_quantity': '赠送量',
}
mapped_columns = {} mapped_columns = {}
for std_name, chinese_name in standard_to_chinese.items(): for target_col, possible_names in column_mapping.items():
matched = ColumnMapper.find_column(all_columns, std_name) for col in all_columns:
if matched: # 清理列名以进行匹配
mapped_columns[chinese_name] = matched col_str = str(col).strip()
logger.info(f"列名映射: {matched} -> {chinese_name}")
# 直接匹配整个列名
if col_str in possible_names:
mapped_columns[target_col] = col
logger.info(f"直接匹配列名: {col_str} -> {target_col}")
break
# 移除列名中的空白字符进行比较
clean_col = re.sub(r'\s+', '', col_str)
for name in possible_names:
clean_name = re.sub(r'\s+', '', name)
# 完全匹配
if clean_col == clean_name:
mapped_columns[target_col] = col
logger.info(f"清理后匹配列名: {col_str} -> {target_col}")
break
# 部分匹配(列名包含关键词)
elif clean_name in clean_col:
mapped_columns[target_col] = col
logger.info(f"部分匹配列名: {col_str} -> {target_col}")
break
if target_col in mapped_columns:
break
# 如果没有找到匹配,尝试模糊匹配
if target_col not in mapped_columns:
for col in all_columns:
col_str = str(col).strip().lower()
for name in possible_names:
name_lower = name.lower()
if name_lower in col_str:
mapped_columns[target_col] = col
logger.info(f"模糊匹配列名: {col} -> {target_col}")
break
if target_col in mapped_columns:
break
# 如果找到了必要的列,重命名列 # 如果找到了必要的列,重命名列
if mapped_columns: if mapped_columns:
@@ -354,9 +399,9 @@ class PurchaseOrderMerger:
# 采购单价(必填)- E列(4) # 采购单价(必填)- E列(4)
output_sheet.write(r, price_col, float(row['采购单价']), price_style) output_sheet.write(r, price_col, float(row['采购单价']), price_style)
# 生成输出文件名,保存到result目录 # 生成输出文件名,保存到data/result目录
timestamp = datetime.now().strftime("%Y%m%d%H%M%S") timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
result_dir = self.config.get_path('Paths', 'result_folder', fallback='data/result', create=True) if hasattr(self.config, 'get_path') else os.path.abspath('data/result') result_dir = "data/result"
os.makedirs(result_dir, exist_ok=True) os.makedirs(result_dir, exist_ok=True)
output_file = os.path.join(result_dir, f"合并采购单_{timestamp}.xls") output_file = os.path.join(result_dir, f"合并采购单_{timestamp}.xls")
+200 -116
View File
@@ -25,12 +25,11 @@ from ..utils.file_utils import (
) )
from ..utils.string_utils import ( from ..utils.string_utils import (
clean_string, clean_string,
clean_barcode,
extract_number, extract_number,
format_barcode, format_barcode
parse_monetary_string
) )
from .converter import UnitConverter from .converter import UnitConverter
from ..handlers.column_mapper import ColumnMapper
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -40,20 +39,19 @@ class ExcelProcessor:
提取条码、单价和数量,并按照采购单模板的格式填充 提取条码、单价和数量,并按照采购单模板的格式填充
""" """
def __init__(self, config, product_db=None): def __init__(self, config):
""" """
初始化Excel处理器 初始化Excel处理器
Args: Args:
config: 配置信息 config: 配置信息
product_db: 商品数据库实例(可选,由外部传入以共享)
""" """
self.config = config self.config = config
# 修复ConfigParser对象没有get_path方法的问题 # 修复ConfigParser对象没有get_path方法的问题
try: try:
# 获取输入和输出目录 # 获取输入和输出目录
self.output_dir = config.get_path('Paths', 'output_folder', fallback='data/output', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/output') self.output_dir = config.get('Paths', 'output_folder', fallback='data/output')
self.temp_dir = config.get('Paths', 'temp_folder', fallback='data/temp') self.temp_dir = config.get('Paths', 'temp_folder', fallback='data/temp')
# 获取模板文件路径 # 获取模板文件路径
@@ -75,18 +73,6 @@ class ExcelProcessor:
# 加载单位转换器和配置 # 加载单位转换器和配置
self.unit_converter = UnitConverter() self.unit_converter = UnitConverter()
# 商品记忆库
if product_db is not None:
self.product_db = product_db
else:
from ..db.product_db import ProductDatabase
db_path = config.get_path('Paths', 'product_db', fallback='data/product_cache.db') if hasattr(config, 'get_path') else 'data/product_cache.db'
tpl_folder = config.get('Paths', 'template_folder', fallback='templates')
item_data = config.get('Templates', 'item_data', fallback='商品资料.xlsx')
tpl_path = os.path.join(tpl_folder, item_data)
self.product_db = ProductDatabase(db_path, tpl_path)
logger.info(f"初始化ExcelProcessor完成,模板文件: {self.template_path}") logger.info(f"初始化ExcelProcessor完成,模板文件: {self.template_path}")
except Exception as e: except Exception as e:
logger.error(f"初始化ExcelProcessor失败: {e}") logger.error(f"初始化ExcelProcessor失败: {e}")
@@ -135,6 +121,48 @@ class ExcelProcessor:
logger.info(f"找到最新的Excel文件: {latest_file}") logger.info(f"找到最新的Excel文件: {latest_file}")
return latest_file return latest_file
def validate_barcode(self, barcode: Any) -> bool:
"""
验证条码是否有效
新增功能:如果条码是"仓库",则返回False以避免误认为有效条码
Args:
barcode: 条码值
Returns:
条码是否有效
"""
# 处理"仓库"特殊情况
if isinstance(barcode, str) and barcode.strip() in ["仓库", "仓库全名"]:
logger.warning(f"条码为仓库标识: {barcode}")
return False
# 清理条码格式
barcode_clean = clean_barcode(barcode)
# 对特定的错误条码进行修正(开头改6开头)
if len(barcode_clean) > 8 and barcode_clean.startswith('5') and not barcode_clean.startswith('53'):
barcode_clean = '6' + barcode_clean[1:]
logger.info(f"修正条码前缀 5->6: {barcode} -> {barcode_clean}")
# 验证条码长度
if len(barcode_clean) < 8 or len(barcode_clean) > 13:
logger.warning(f"条码长度异常: {barcode_clean}, 长度={len(barcode_clean)}")
return False
# 验证条码是否全为数字
if not barcode_clean.isdigit():
logger.warning(f"条码包含非数字字符: {barcode_clean}")
return False
# 对于序号9的特殊情况,允许其条码格式
if barcode_clean == "5321545613":
logger.info(f"特殊条码验证通过: {barcode_clean}")
return True
logger.debug(f"条码验证通过: {barcode_clean}")
return True
def extract_barcode(self, df: pd.DataFrame) -> List[str]: def extract_barcode(self, df: pd.DataFrame) -> List[str]:
""" """
从数据帧中提取条码列名 从数据帧中提取条码列名
@@ -145,7 +173,12 @@ class ExcelProcessor:
Returns: Returns:
可能的条码列名列表 可能的条码列名列表
""" """
possible_barcode_columns = ColumnMapper.STANDARD_COLUMNS['barcode'] possible_barcode_columns = [
'条码', '条形码', '商品条码', '商品条形码',
'商品编码', '商品编号', '条形码', '条码(必填)',
'barcode', 'Barcode', '编码', '条形码', '电脑条码',
'条码ID', '产品条码', 'BarCode'
]
found_columns = [] found_columns = []
@@ -269,11 +302,25 @@ class ExcelProcessor:
if amt is None: if amt is None:
is_amt_gift = True is_amt_gift = True
elif isinstance(amt, str): elif isinstance(amt, str):
parsed = parse_monetary_string(amt) s = amt.strip()
is_amt_gift = (parsed is None or parsed == 0.0) if s == '' or s.lower() == 'o' or s == '0' or s == '':
is_amt_gift = True
else: else:
parsed = parse_monetary_string(amt) amt_clean = re.sub(r'[^\d\.,]', '', s)
is_amt_gift = (parsed is not None and parsed == 0.0) if ',' in amt_clean and '.' not in amt_clean:
amt_clean = amt_clean.replace(',', '.')
elif ',' in amt_clean and '.' in amt_clean:
amt_clean = amt_clean.replace(',', '')
if amt_clean:
try:
is_amt_gift = float(amt_clean) == 0.0
except ValueError:
pass
else:
try:
is_amt_gift = float(amt) == 0.0
except (ValueError, TypeError):
pass
if is_amt_gift: if is_amt_gift:
product['is_gift'] = True product['is_gift'] = True
except Exception: except Exception:
@@ -376,17 +423,27 @@ class ExcelProcessor:
# 如果数量为0但单价和金额都存在,计算数量 = 金额/单价 # 如果数量为0但单价和金额都存在,计算数量 = 金额/单价
if (product['quantity'] == 0 or product['quantity'] is None) and product['price'] > 0 and product['amount']: if (product['quantity'] == 0 or product['quantity'] is None) and product['price'] > 0 and product['amount']:
try: try:
amount = parse_monetary_string(product['amount']) # 确保金额是数字
if amount is not None and amount > 0: if isinstance(product['amount'], str):
# 移除货币符号和非数字字符,保留数字、小数点和逗号
amount_str = re.sub(r'[^\d\.,]', '', product['amount'].strip())
# 替换逗号为小数点(如果逗号作为小数分隔符)
if ',' in amount_str and '.' not in amount_str:
amount_str = amount_str.replace(',', '.')
# 处理既有逗号又有小数点的情况(通常逗号是千位分隔符)
elif ',' in amount_str and '.' in amount_str:
amount_str = amount_str.replace(',', '')
amount = float(amount_str)
else:
amount = float(product['amount'])
# 计算数量
if amount > 0:
quantity = amount / product['price'] quantity = amount / product['price']
logger.info(f"数量为空或为0,通过金额({amount})和单价({product['price']})计算得出数量: {quantity}") logger.info(f"数量为空或为0,通过金额({amount})和单价({product['price']})计算得出数量: {quantity}")
product['quantity'] = quantity product['quantity'] = quantity
except Exception as e: except Exception as e:
logger.warning(f"通过金额和单价计算数量失败: {e}") logger.warning(f"通过金额和单价计算数量失败: {e}")
# 应用记忆库补全
product = self._apply_memory(product)
products.append(product) products.append(product)
except Exception as e: except Exception as e:
logger.error(f"提取第{idx+1}行商品信息时出错: {e}", exc_info=True) logger.error(f"提取第{idx+1}行商品信息时出错: {e}", exc_info=True)
@@ -395,59 +452,6 @@ class ExcelProcessor:
logger.info(f"提取到 {len(products)} 个商品信息") logger.info(f"提取到 {len(products)} 个商品信息")
return products return products
def _apply_memory(self, product: Dict) -> Dict:
"""查记忆库,补全 OCR 缺失/错误的字段"""
barcode = product.get('barcode', '')
if not barcode:
return product
try:
memory = self.product_db.get_memory(barcode)
except Exception:
return product
if memory is None or memory.get('confidence', 0) < 80:
return product
# 补全规格
ocr_spec = product.get('specification', '')
mem_spec = memory.get('specification', '') or ''
if mem_spec and (not ocr_spec or self._is_spec_suspicious(ocr_spec)):
product['specification'] = mem_spec
logger.info(f"记忆修正规格: {barcode} '{ocr_spec}' -> '{mem_spec}'")
# 补全名称
ocr_name = product.get('name', '')
mem_name = memory.get('name', '') or ''
if mem_name and not ocr_name:
product['name'] = mem_name
logger.info(f"记忆修正名称: {barcode} -> '{mem_name}'")
# 补全单位
ocr_unit = product.get('unit', '')
mem_unit = memory.get('unit', '') or ''
if mem_unit and not ocr_unit:
product['unit'] = mem_unit
logger.info(f"记忆修正单位: {barcode} -> '{mem_unit}'")
# 不改数量和单价(每单不同)
return product
def _is_spec_suspicious(self, spec: str) -> bool:
"""检测规格是否像 OCR 垃圾"""
if not spec:
return True
# IL*12I 和 1 混淆)
if re.search(r'^[Ii][Ll*]', spec):
return True
# 4.51*4L 被识别为 1
if re.search(r'\d+\.\d+1\*\d+', spec):
return True
# 包含非常规字符(排除常见规格字符)
if re.search(r'[^\d.*xX\-LlKkGgMm升毫瓶桶盒箱件提\s]', spec):
return True
return False
def fill_template(self, products: List[Dict], output_file_path: str) -> bool: def fill_template(self, products: List[Dict], output_file_path: str) -> bool:
""" """
填充采购单模板 填充采购单模板
@@ -490,8 +494,21 @@ class ExcelProcessor:
# 如果数量为0但单价和金额都存在,计算数量 = 金额/单价 # 如果数量为0但单价和金额都存在,计算数量 = 金额/单价
if (quantity == 0 or quantity is None) and price > 0 and amount: if (quantity == 0 or quantity is None) and price > 0 and amount:
try: try:
amount = parse_monetary_string(amount) # 确保金额是数字
if amount is not None and amount > 0: if isinstance(amount, str):
# 移除货币符号和非数字字符,保留数字、小数点和逗号
amount_str = re.sub(r'[^\d\.,]', '', amount.strip())
# 替换逗号为小数点(如果逗号作为小数分隔符)
if ',' in amount_str and '.' not in amount_str:
amount_str = amount_str.replace(',', '.')
# 处理既有逗号又有小数点的情况(通常逗号是千位分隔符)
elif ',' in amount_str and '.' in amount_str:
amount_str = amount_str.replace(',', '')
amount = float(amount_str)
else:
amount = float(amount)
# 计算数量
if amount > 0:
quantity = amount / price quantity = amount / price
logger.info(f"数量为空或为0,通过金额({amount})和单价({price})计算得出数量: {quantity}") logger.info(f"数量为空或为0,通过金额({amount})和单价({price})计算得出数量: {quantity}")
product['quantity'] = quantity product['quantity'] = quantity
@@ -588,16 +605,72 @@ class ExcelProcessor:
return False return False
def _find_header_row(self, df: pd.DataFrame) -> Optional[int]: def _find_header_row(self, df: pd.DataFrame) -> Optional[int]:
"""自动识别表头行,委托给 ColumnMapper.detect_header_row""" """
result = ColumnMapper.detect_header_row(df, max_rows=30) 自动识别表头行
if result >= 0:
logger.info(f"找到表头行: 第{result+1}") 通过多种规则识别表头:
return result 1. 检查行是否包含典型的表头关键词(条码、商品名称、数量等)
# 回退:找第一个非空行 2. 检查是否是第一个非空行
3. 检查行是否有较多的字符串类型单元格(表头通常是字符串)
Args:
df: 数据帧
Returns:
表头行索引,如果未找到则返回None
"""
header_keywords = [
'条码', '条形码', '商品条码', '商品名称', '名称', '数量', '单位', '单价',
'规格', '商品编码', '采购数量', '采购单位', '商品', '品名',
'金额', '小计', '总计', '合计', '合计金额'
]
# 存储每行的匹配分数
row_scores = []
max_rows_to_check = min(30, len(df))
for row in range(max_rows_to_check):
row_data = df.iloc[row]
score = 0
# 检查1: 关键词匹配
for cell in row_data:
if isinstance(cell, str):
cell_clean = str(cell).strip().lower()
for keyword in header_keywords:
if keyword.lower() in cell_clean:
score += 5 # 每匹配一个关键词加5分
# 检查2: 非空单元格比例
non_empty_cells = row_data.count()
if non_empty_cells / len(row_data) > 0.5: # 如果超过一半的单元格有内容
score += 2
# 检查3: 字符串类型单元格比例
string_cells = sum(1 for cell in row_data if isinstance(cell, str))
if string_cells / len(row_data) > 0.5: # 如果超过一半的单元格是字符串
score += 3
row_scores.append((row, score))
# 日志记录每行的评分情况
logger.debug(f"{row+1}行评分: {score},内容: {row_data.values}")
# 按评分排序
row_scores.sort(key=lambda x: x[1], reverse=True)
# 如果最高分达到一定阈值,认为是表头
if row_scores and row_scores[0][1] >= 5:
best_row = row_scores[0][0]
logger.info(f"找到可能的表头行: 第{best_row+1}行,评分: {row_scores[0][1]}")
return best_row
# 如果没有找到明确的表头,尝试找第一个非空行
for row in range(len(df)): for row in range(len(df)):
if df.iloc[row].notna().sum() > 3: if df.iloc[row].notna().sum() > 3: # 至少有3个非空单元格
logger.info(f"未找到明确表头,使用第一个有效行: 第{row+1}") logger.info(f"未找到明确表头,使用第一个有效行: 第{row+1}")
return row return row
logger.warning("无法识别表头行") logger.warning("无法识别表头行")
return None return None
@@ -660,22 +733,14 @@ class ExcelProcessor:
logger.warning("未提取到有效商品信息") logger.warning("未提取到有效商品信息")
return None return None
# 生成输出文件名,保存到result目录 # 生成输出文件名,保存到data/result目录
file_name = os.path.splitext(os.path.basename(file_path))[0] file_name = os.path.splitext(os.path.basename(file_path))[0]
result_dir = self.config.get_path('Paths', 'result_folder', fallback='data/result', create=True) if hasattr(self.config, 'get_path') else os.path.abspath('data/result') result_dir = "data/result"
os.makedirs(result_dir, exist_ok=True) os.makedirs(result_dir, exist_ok=True)
output_file = os.path.join(result_dir, f"采购单_{file_name}.xls") output_file = os.path.join(result_dir, f"采购单_{file_name}.xls")
# 填充模板并保存 # 填充模板并保存
if self.fill_template(products, output_file): if self.fill_template(products, output_file):
# 从处理结果中学习商品记忆
try:
self.product_db.learn_from_products(products, source='ocr')
self.product_db._export_memory_json()
logger.info(f"已从处理结果学习 {len(products)} 条商品记忆")
except Exception as e:
logger.warning(f"学习商品记忆失败: {e}")
# 记录已处理文件 # 记录已处理文件
self.processed_files[file_path] = output_file self.processed_files[file_path] = output_file
self._save_processed_files() self._save_processed_files()
@@ -730,25 +795,44 @@ class ExcelProcessor:
logger.error("未找到条码列,无法处理") logger.error("未找到条码列,无法处理")
return {} return {}
# 使用 ColumnMapper 统一查找列名 # 定义列名映射
mapped_columns = {'barcode': barcode_cols[0]} column_mapping = {
'name': ['商品名称', '名称', '品名', '商品', '商品名', '商品或服务名称', '品项名', '产品名称', '品项', '名 称'],
'specification': ['规格', '规格型号', '型号', '商品规格', '产品规格', '包装规格','规 格'],
'quantity': ['数量', '采购数量', '购买数量', '采购数量', '订单数量', '数量(必填)', '入库数', '入库数量','数 量'],
'unit': ['单位', '采购单位', '计量单位', '单位(必填)', '单位名称', '计价单位','单 位'],
'price': ['单价', '价格', '采购单价', '销售价', '进货价', '单价(必填)', '采购价', '参考价', '入库单价','单 价'],
'amount': ['金额', '小计', '总价', '合计金额', '小计金额', '金额(元)', '金额合计', '合计', '总额']
}
# 映射列名到标准名称
mapped_columns = {'barcode': barcode_cols[0]} # 使用第一个找到的条码列
# 记录列名映射详情
logger.info(f"使用条码列: {mapped_columns['barcode']}") logger.info(f"使用条码列: {mapped_columns['barcode']}")
# 内部键名 -> 标准列名映射 (processor.py 使用 price/amount 作为内部键名) for target, possible_names in column_mapping.items():
field_map = [ for col in df.columns:
('name', 'name'), col_str = str(col).strip()
('specification', 'specification'), for name in possible_names:
('quantity', 'quantity'), if col_str == name:
('unit', 'unit'), mapped_columns[target] = col
('price', 'unit_price'), logger.info(f"找到{target}列: {col}")
('amount', 'total_price'), break
] if target in mapped_columns:
break
for internal_key, standard_name in field_map: # 如果没有找到精确匹配,尝试部分匹配
matched = ColumnMapper.find_column(list(df.columns), standard_name) if target not in mapped_columns:
if matched: for col in df.columns:
mapped_columns[internal_key] = matched col_str = str(col).strip().lower()
logger.info(f"找到{internal_key}列: {matched}") for name in possible_names:
if name.lower() in col_str:
mapped_columns[target] = col
logger.info(f"找到{target}列(部分匹配): {col}")
break
if target in mapped_columns:
break
return mapped_columns return mapped_columns
+50 -8
View File
@@ -9,7 +9,6 @@ import logging
from typing import Dict, Any, Optional, List, Tuple, Union from typing import Dict, Any, Optional, List, Tuple, Union
from ..utils.log_utils import get_logger from ..utils.log_utils import get_logger
from ..utils.string_utils import parse_monetary_string
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -157,8 +156,23 @@ class ProductValidator:
if price_str in ["赠品", "gift", "赠送", "0", ""]: if price_str in ["赠品", "gift", "赠送", "0", ""]:
return True, 0.0, True, None return True, 0.0, True, None
price_value = parse_monetary_string(price_str) # 去除空白和非数字字符(保留小数点和逗号)
if price_value is None: price_clean = re.sub(r'[^\d\.,]', '', price_str)
# 处理小数点和逗号
if ',' in price_clean and '.' not in price_clean:
# 如果只有逗号没有小数点,将逗号视为小数点
price_clean = price_clean.replace(',', '.')
elif ',' in price_clean and '.' in price_clean:
# 如果既有逗号又有小数点,移除逗号(认为逗号是千位分隔符)
price_clean = price_clean.replace(',', '')
if not price_clean:
return False, 0.0, True, "单价不包含数字,视为赠品"
try:
price_value = float(price_clean)
except ValueError:
return False, 0.0, True, f"无法将单价 '{price}' 转换为数字,视为赠品" return False, 0.0, True, f"无法将单价 '{price}' 转换为数字,视为赠品"
else: else:
# 尝试直接转换 # 尝试直接转换
@@ -215,9 +229,28 @@ class ProductValidator:
amount = product.get('amount', None) amount = product.get('amount', None)
try: try:
is_amount_gift = False is_amount_gift = False
parsed_amount = parse_monetary_string(amount) if amount is None:
if parsed_amount is None or parsed_amount == 0.0:
is_amount_gift = True is_amount_gift = True
elif isinstance(amount, str):
s = amount.strip()
if s == '' or s.lower() == 'o' or s == '0':
is_amount_gift = True
else:
amt_clean = re.sub(r'[^\d\.,]', '', s)
if ',' in amt_clean and '.' not in amt_clean:
amt_clean = amt_clean.replace(',', '.')
elif ',' in amt_clean and '.' in amt_clean:
amt_clean = amt_clean.replace(',', '')
if amt_clean:
try:
is_amount_gift = float(amt_clean) == 0.0
except ValueError:
pass
else:
try:
is_amount_gift = float(amount) == 0.0
except (ValueError, TypeError):
pass
if is_amount_gift: if is_amount_gift:
validated_product['is_gift'] = True validated_product['is_gift'] = True
except Exception: except Exception:
@@ -236,9 +269,18 @@ class ProductValidator:
if fixed_price > 0 and amount is not None: if fixed_price > 0 and amount is not None:
try: try:
# 确保金额是数字 # 确保金额是数字
amount = parse_monetary_string(amount) if isinstance(amount, str):
if amount is None: # 移除货币符号和非数字字符,保留数字、小数点和逗号
raise ValueError("无法解析金额") amount_str = re.sub(r'[^\d\.,]', '', amount.strip())
# 替换逗号为小数点(如果逗号作为小数分隔符)
if ',' in amount_str and '.' not in amount_str:
amount_str = amount_str.replace(',', '.')
# 处理既有逗号又有小数点的情况(通常逗号是千位分隔符)
elif ',' in amount_str and '.' in amount_str:
amount_str = amount_str.replace(',', '')
amount = float(amount_str)
else:
amount = float(amount)
# 计算数量 = 金额 / 单价 # 计算数量 = 金额 / 单价
if amount > 0: if amount > 0:
+9 -115
View File
@@ -4,7 +4,6 @@
提供列名映射和转换功能,支持不同供应商的列名标准化 提供列名映射和转换功能,支持不同供应商的列名标准化
""" """
import re
import pandas as pd import pandas as pd
from typing import Dict, Any, Optional, List, Union from typing import Dict, Any, Optional, List, Union
from ...core.utils.log_utils import get_logger from ...core.utils.log_utils import get_logger
@@ -18,47 +17,18 @@ class ColumnMapper:
提供列名标准化功能,将不同供应商的列名映射到标准列名 提供列名标准化功能,将不同供应商的列名映射到标准列名
""" """
# 标准列名定义(所有列名别名的唯一来源) # 标准列名定义
STANDARD_COLUMNS = { STANDARD_COLUMNS = {
'barcode': [ 'barcode': ['条码', '条形码', '商品条码', '产品条码', '条码(必填)', 'barcode', 'code'],
'条码', '条形码', '商品条码', '商品条形码', '产品条码', '商品编码', 'name': ['商品名称', '产品名称', '名称', '商品', '产品', 'name', 'product_name'],
'商品编', '条码(必填)', '电脑条码', '条码ID', 'specification': ['规格', '规格型', '型号', 'specification', 'spec', 'model'],
'barcode', 'Barcode', 'BarCode', 'code', '编码', 'quantity': ['数量', '采购量', '订货数量', '订单量', '需求量', 'quantity', 'qty', '采购量(必填)'],
], 'unit': ['单位', '计量单位', 'unit', 'units'],
'name': [ 'unit_price': ['单价', '价格', '采购单价', '进货价', 'unit_price', 'price', '采购单价(必填)'],
'商品名称', '产品名称', '名称', '商品', '产品', '商品名', '品名', 'total_price': ['总价', '金额', '小计', 'total_price', 'total', 'amount'],
'品项名', '商品或服务名称', '品项', '名 称',
'name', 'product_name',
],
'specification': [
'规格', '规格型号', '型号', '商品规格', '产品规格', '包装规格', '规 格',
'specification', 'spec', 'model',
],
'quantity': [
'数量', '采购量', '订货数量', '订单量', '需求量', '采购数量', '购买数量',
'订单数量', '数量(必填)', '采购量(必填)', '入库数', '入库数量', '数 量',
'quantity', 'qty',
],
'unit': [
'单位', '计量单位', '采购单位', '单位(必填)', '单位名称', '计价单位', '单 位',
'unit', 'units',
],
'unit_price': [
'单价', '价格', '采购单价', '进货价', '销售价', '采购价', '参考价',
'入库单价', '单价(必填)', '采购单价(必填)', '价格(必填)', '单 价',
'unit_price', 'price',
],
'total_price': [
'总价', '金额', '小计', '合计金额', '小计金额', '金额(元)',
'金额合计', '合计', '总额',
'total_price', 'total', 'amount',
],
'gift_quantity': [
'赠送量', '赠品数量', '赠送数量', '赠品',
],
'category': ['类别', '分类', '商品类别', 'category', 'type'], 'category': ['类别', '分类', '商品类别', 'category', 'type'],
'brand': ['品牌', '商标', 'brand'], 'brand': ['品牌', '商标', 'brand'],
'supplier': ['供应商', '供货商', 'supplier', 'vendor'], 'supplier': ['供应商', '供货商', 'supplier', 'vendor']
} }
def __init__(self, mapping_config: Optional[Dict[str, Any]] = None): def __init__(self, mapping_config: Optional[Dict[str, Any]] = None):
@@ -304,79 +274,3 @@ class ColumnMapper:
result['warnings'].append(f"'{col}' 不是数值类型") result['warnings'].append(f"'{col}' 不是数值类型")
return result return result
@classmethod
def find_column(cls, columns: List[str], standard_name: str) -> Optional[str]:
"""在列名列表中查找匹配标准列名的列
匹配策略: 精确匹配 → 忽略空白匹配 → 子串匹配
Args:
columns: 实际列名列表
standard_name: 标准列名 (STANDARD_COLUMNS 的键)
Returns:
匹配到的实际列名,未找到返回 None
"""
candidates = cls.STANDARD_COLUMNS.get(standard_name, [])
if not candidates:
return None
columns_str = [str(c) for c in columns]
# 精确匹配
for col in columns_str:
col_clean = col.strip()
for candidate in candidates:
if col_clean == candidate:
return col
# 忽略空白匹配
for col in columns_str:
col_clean = re.sub(r'\s+', '', col.strip())
for candidate in candidates:
if col_clean == re.sub(r'\s+', '', candidate):
return col
# 子串匹配 (候选名包含在列名中)
for col in columns_str:
col_lower = col.strip().lower()
for candidate in candidates:
if candidate.lower() in col_lower:
return col
return None
@staticmethod
def detect_header_row(df: pd.DataFrame, max_rows: int = 10, min_matches: int = 3) -> int:
"""检测表头所在行
扫描前 max_rows 行,返回包含最多关键词匹配的行索引。
Args:
df: 数据框
max_rows: 最大扫描行数
min_matches: 最少关键词匹配数
Returns:
表头行索引,未找到返回 -1
"""
header_keywords = [
'条码', '条形码', '商品条码', '商品名称', '名称', '规格',
'单价', '数量', '金额', '单位', '必填', '编码',
]
best_row = -1
best_matches = 0
for row_idx in range(min(max_rows, len(df))):
row_values = df.iloc[row_idx].astype(str)
matches = sum(
1 for kw in header_keywords
if any(kw in str(val) for val in row_values.values)
)
if matches >= min_matches and matches > best_matches:
best_matches = matches
best_row = row_idx
return best_row
+2 -2
View File
@@ -19,7 +19,7 @@ def _split_quantity_unit(df: pd.DataFrame, source: str, dictionary: Optional[Dic
try: try:
nums.append(float(v)) nums.append(float(v))
units.append(unit_synonyms.get(default_unit, default_unit)) units.append(unit_synonyms.get(default_unit, default_unit))
except Exception: except:
nums.append(0.0) nums.append(0.0)
units.append(unit_synonyms.get(default_unit, default_unit)) units.append(unit_synonyms.get(default_unit, default_unit))
df["quantity"] = nums df["quantity"] = nums
@@ -44,7 +44,7 @@ def _extract_spec_from_name(df: pd.DataFrame, source: str, dictionary: Optional[
if m and len(m.groups()) >= 2: if m and len(m.groups()) >= 2:
try: try:
qty = int(m.group(len(m.groups()))) qty = int(m.group(len(m.groups())))
except Exception: except:
qty = None qty = None
specs.append(s) specs.append(s)
packs.append(qty) packs.append(qty)
+9 -14
View File
@@ -4,25 +4,24 @@
提供百度OCR API的访问和调用功能。 提供百度OCR API的访问和调用功能。
""" """
import os
import time import time
import base64 import base64
import requests import requests
from typing import Dict, Optional, Union import logging
from typing import Dict, Optional, Any, Union
from ...config.settings import ConfigManager
from ..utils.log_utils import get_logger from ..utils.log_utils import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
# Token 过期相关常量
_DEFAULT_TOKEN_LIFETIME = 30 * 24 * 3600 # 30天(秒)
_TOKEN_EARLY_EXPIRY = 3600 # 提前1小时刷新(秒)
class TokenManager: class TokenManager:
""" """
令牌管理类,负责获取和刷新百度API访问令牌 令牌管理类,负责获取和刷新百度API访问令牌
""" """
def __init__(self, api_key: str, secret_key: str, max_retries: int = 3, retry_delay: int = 2, token_url: str = None): def __init__(self, api_key: str, secret_key: str, max_retries: int = 3, retry_delay: int = 2):
""" """
初始化令牌管理器 初始化令牌管理器
@@ -31,13 +30,11 @@ class TokenManager:
secret_key: 百度Secret Key secret_key: 百度Secret Key
max_retries: 最大重试次数 max_retries: 最大重试次数
retry_delay: 重试延迟(秒) retry_delay: 重试延迟(秒)
token_url: 令牌获取地址
""" """
self.api_key = api_key self.api_key = api_key
self.secret_key = secret_key self.secret_key = secret_key
self.max_retries = max_retries self.max_retries = max_retries
self.retry_delay = retry_delay self.retry_delay = retry_delay
self.token_url = token_url or 'https://aip.baidubce.com/oauth/2.0/token'
self.access_token = None self.access_token = None
self.token_expiry = 0 self.token_expiry = 0
@@ -72,7 +69,7 @@ class TokenManager:
Returns: Returns:
新的访问令牌,如果获取失败则返回None 新的访问令牌,如果获取失败则返回None
""" """
url = self.token_url url = "https://aip.baidubce.com/oauth/2.0/token"
params = { params = {
"grant_type": "client_credentials", "grant_type": "client_credentials",
"client_id": self.api_key, "client_id": self.api_key,
@@ -87,7 +84,7 @@ class TokenManager:
if "access_token" in result: if "access_token" in result:
self.access_token = result["access_token"] self.access_token = result["access_token"]
# 设置令牌过期时间(默认30天,提前1小时过期以确保安全) # 设置令牌过期时间(默认30天,提前1小时过期以确保安全)
self.token_expiry = time.time() + result.get("expires_in", _DEFAULT_TOKEN_LIFETIME) - _TOKEN_EARLY_EXPIRY self.token_expiry = time.time() + result.get("expires_in", 2592000) - 3600
logger.info("成功获取访问令牌") logger.info("成功获取访问令牌")
return self.access_token return self.access_token
@@ -147,8 +144,7 @@ class BaiduOCRClient:
self.api_key, self.api_key,
self.secret_key, self.secret_key,
self.max_retries, self.max_retries,
self.retry_delay, self.retry_delay
token_url=config.get('API', 'token_url', fallback='https://aip.baidubce.com/oauth/2.0/token')
) )
# 验证API配置 # 验证API配置
@@ -303,8 +299,7 @@ class BaiduOCRClient:
logger.error(f"无法从结果中提取有效的request_id: {request_id_or_result}") logger.error(f"无法从结果中提取有效的request_id: {request_id_or_result}")
return None return None
base_url = self.config.get('API', 'form_ocr_url', fallback='https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/get_request_result') url = f"https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/get_request_result?access_token={access_token}"
url = f"{base_url}?access_token={access_token}"
payload = { payload = {
'request_id': request_id, 'request_id': request_id,
+14 -10
View File
@@ -5,11 +5,15 @@
""" """
import os import os
import sys
import time import time
import json
import base64 import base64
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List, Optional, Tuple, Callable from typing import Dict, List, Optional, Tuple, Union, Any, Callable
from ...config.settings import ConfigManager
from ..utils.log_utils import get_logger from ..utils.log_utils import get_logger
from ..utils.file_utils import ( from ..utils.file_utils import (
ensure_dir, ensure_dir,
@@ -114,9 +118,9 @@ class OCRProcessor:
# 修复ConfigParser对象没有get_path方法的问题 # 修复ConfigParser对象没有get_path方法的问题
try: try:
# 获取输入和输出目录 # 获取输入和输出目录
self.input_folder = config.get_path('Paths', 'input_folder', fallback='data/input', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/input') self.input_folder = config.get('Paths', 'input_folder', fallback='data/input')
self.output_folder = config.get_path('Paths', 'output_folder', fallback='data/output', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/output') self.output_folder = config.get('Paths', 'output_folder', fallback='data/output')
self.temp_folder = config.get_path('Paths', 'temp_folder', fallback='data/temp', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/temp') self.temp_folder = config.get('Paths', 'temp_folder', fallback='data/temp')
# 确保目录存在 # 确保目录存在
os.makedirs(self.input_folder, exist_ok=True) os.makedirs(self.input_folder, exist_ok=True)
@@ -173,7 +177,7 @@ class OCRProcessor:
skip_existing = True skip_existing = True
try: try:
skip_existing = self.config.getboolean('Performance', 'skip_existing', fallback=True) skip_existing = self.config.getboolean('Performance', 'skip_existing', fallback=True)
except Exception: except:
pass pass
if skip_existing: if skip_existing:
@@ -210,7 +214,7 @@ class OCRProcessor:
max_size_mb = 4.0 max_size_mb = 4.0
try: try:
max_size_mb = float(self.config.get('File', 'max_file_size_mb', fallback='4.0')) max_size_mb = float(self.config.get('File', 'max_file_size_mb', fallback='4.0'))
except Exception: except:
pass pass
if not is_file_size_valid(image_path, max_size_mb): if not is_file_size_valid(image_path, max_size_mb):
@@ -237,7 +241,7 @@ class OCRProcessor:
skip_existing = True skip_existing = True
try: try:
skip_existing = self.config.getboolean('Performance', 'skip_existing', fallback=True) skip_existing = self.config.getboolean('Performance', 'skip_existing', fallback=True)
except Exception: except:
pass pass
# 如果需要跳过已处理的文件 # 如果需要跳过已处理的文件
@@ -253,7 +257,7 @@ class OCRProcessor:
excel_extension = '.xlsx' excel_extension = '.xlsx'
try: try:
excel_extension = self.config.get('File', 'excel_extension', fallback='.xlsx') excel_extension = self.config.get('File', 'excel_extension', fallback='.xlsx')
except Exception: except:
pass pass
# 生成输出文件路径 # 生成输出文件路径
@@ -343,13 +347,13 @@ class OCRProcessor:
if batch_size is None: if batch_size is None:
try: try:
batch_size = self.config.getint('Performance', 'batch_size', fallback=5) batch_size = self.config.getint('Performance', 'batch_size', fallback=5)
except Exception: except:
batch_size = 5 batch_size = 5
if max_workers is None: if max_workers is None:
try: try:
max_workers = self.config.getint('Performance', 'max_workers', fallback=4) max_workers = self.config.getint('Performance', 'max_workers', fallback=4)
except Exception: except:
max_workers = 4 max_workers = 4
# 获取未处理的图片 # 获取未处理的图片
+1 -29
View File
@@ -8,11 +8,8 @@ from abc import ABC, abstractmethod
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List
from pathlib import Path from pathlib import Path
import logging import logging
import pandas as pd
from ...core.utils.log_utils import get_logger logger = logging.getLogger(__name__)
logger = get_logger(__name__)
class BaseProcessor(ABC): class BaseProcessor(ABC):
@@ -119,31 +116,6 @@ class BaseProcessor(ABC):
""" """
return f"{input_file.stem}{suffix}{input_file.suffix}" return f"{input_file.stem}{suffix}{input_file.suffix}"
def _read_excel_safely(self, file_path: Path, **kwargs) -> pd.DataFrame:
"""根据扩展名选择合适的读取引擎
Args:
file_path: 文件路径
**kwargs: 传递给 pd.read_excel 的参数
Returns:
DataFrame
Raises:
Exception: 读取失败时抛出
"""
suffix = file_path.suffix.lower()
if suffix == '.xlsx':
return pd.read_excel(file_path, engine='openpyxl', **kwargs)
elif suffix == '.xls':
try:
return pd.read_excel(file_path, engine='xlrd', **kwargs)
except Exception as e:
self.logger.warning(f"读取xls失败,可能缺少xlrd: {e}")
raise
else:
return pd.read_excel(file_path, **kwargs)
def log_processing_start(self, input_file: Path): def log_processing_start(self, input_file: Path):
"""记录处理开始日志""" """记录处理开始日志"""
self.logger.info(f"开始处理文件: {input_file}") self.logger.info(f"开始处理文件: {input_file}")
@@ -12,9 +12,6 @@ from pathlib import Path
from ..base import BaseProcessor from ..base import BaseProcessor
from ...utils.log_utils import get_logger from ...utils.log_utils import get_logger
from ...handlers.rule_engine import apply_rules from ...handlers.rule_engine import apply_rules
from ...handlers.column_mapper import ColumnMapper
from ...handlers.data_cleaner import DataCleaner
from ...handlers.calculator import DataCalculator
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -240,9 +237,57 @@ class GenericSupplierProcessor(BaseProcessor):
self.logger.error(f"读取数据失败: {e}") self.logger.error(f"读取数据失败: {e}")
return None return None
def _read_excel_safely(self, file_path: Path, **kwargs) -> pd.DataFrame:
"""根据扩展名选择合适的读取引擎并带有回退"""
suffix = file_path.suffix.lower()
try:
if suffix == '.xlsx':
return pd.read_excel(file_path, engine='openpyxl', **kwargs)
elif suffix == '.xls':
try:
return pd.read_excel(file_path, engine='xlrd', **kwargs)
except Exception as e:
self.logger.warning(f"读取xls失败,可能缺少xlrd: {e}")
raise
else:
return pd.read_excel(file_path, **kwargs)
except Exception as e:
self.logger.error(f"读取Excel失败: {file_path} - {e}")
raise
def _find_header_row(self, df: pd.DataFrame) -> Optional[int]: def _find_header_row(self, df: pd.DataFrame) -> Optional[int]:
result = ColumnMapper.detect_header_row(df, max_rows=30) try:
return result if result >= 0 else None header_keywords = [
'条码','条形码','商品编码','商品名称','名称','数量','单位','单价','规格',
'金额','小计','总计','合计','合计金额'
]
scores = []
rows_to_check = min(30, len(df))
for r in range(rows_to_check):
row = df.iloc[r]
score = 0
for cell in row:
if isinstance(cell, str):
s = cell.strip().lower()
for kw in header_keywords:
if kw.lower() in s:
score += 5
non_empty = row.count()
if non_empty / max(1, len(row)) > 0.5:
score += 2
str_count = sum(1 for c in row if isinstance(c, str))
if str_count / max(1, len(row)) > 0.5:
score += 3
scores.append((r, score))
scores.sort(key=lambda x: x[1], reverse=True)
if scores and scores[0][1] >= 5:
return scores[0][0]
for r in range(len(df)):
if df.iloc[r].notna().sum() > 3:
return r
return None
except Exception:
return None
def _apply_column_mapping(self, df: pd.DataFrame) -> Optional[pd.DataFrame]: def _apply_column_mapping(self, df: pd.DataFrame) -> Optional[pd.DataFrame]:
"""应用列映射 """应用列映射
@@ -280,33 +325,131 @@ class GenericSupplierProcessor(BaseProcessor):
return None return None
def _apply_data_cleaning(self, df: pd.DataFrame) -> Optional[pd.DataFrame]: def _apply_data_cleaning(self, df: pd.DataFrame) -> Optional[pd.DataFrame]:
"""应用数据清洗规则,委托给 DataCleaner""" """应用数据清洗规则
Args:
df: 映射后的数据
Returns:
清洗后的数据或None
"""
if not self.cleaning_rules: if not self.cleaning_rules:
self.logger.info("没有数据清洗规则") self.logger.info("没有数据清洗规则")
return df return df
try: try:
cleaner = DataCleaner() df_cleaned = df.copy()
for rule in self.cleaning_rules: for rule in self.cleaning_rules:
cleaner.add_rule(rule.get('type'), **{k: v for k, v in rule.items() if k != 'type'}) rule_type = rule.get('type')
result = cleaner.clean(df)
self.logger.info(f"数据清洗完成,数据形状: {result.shape}") if rule_type == 'remove_rows':
return result # 删除行
condition = rule.get('condition')
if condition:
before_count = len(df_cleaned)
df_cleaned = df_cleaned.query(condition)
after_count = len(df_cleaned)
self.logger.info(f"删除行规则: {condition}, 删除数量: {before_count - after_count}")
elif rule_type == 'fill_na':
# 填充空值,兼容单列和多列
columns = rule.get('columns') or [rule.get('column')] if rule.get('column') else []
value = rule.get('value', 0)
for col in columns:
if col and col in df_cleaned.columns:
na_count = df_cleaned[col].isna().sum()
df_cleaned[col] = df_cleaned[col].fillna(value)
self.logger.info(f"填充空值: {col} -> {value}, 填充数量: {na_count}")
elif rule_type == 'convert_type':
# 类型转换,兼容单列和多列
target_type = rule.get('target_type', 'float')
columns = rule.get('columns') or [rule.get('column')] if rule.get('column') else []
for col in columns:
if col and col in df_cleaned.columns:
try:
if target_type == 'float':
df_cleaned[col] = pd.to_numeric(df_cleaned[col], errors='coerce')
elif target_type == 'int':
df_cleaned[col] = pd.to_numeric(df_cleaned[col], errors='coerce').astype('Int64')
self.logger.info(f"类型转换: {col} -> {target_type}")
except Exception as e:
self.logger.warning(f"类型转换失败: {col} -> {target_type}: {e}")
else:
self.logger.warning(f"未知的清洗规则类型: {rule_type}")
self.logger.info(f"数据清洗完成,数据形状: {df_cleaned.shape}")
return df_cleaned
except Exception as e: except Exception as e:
self.logger.error(f"数据清洗失败: {e}") self.logger.error(f"数据清洗失败: {e}")
return None return None
def _apply_calculations(self, df: pd.DataFrame) -> Optional[pd.DataFrame]: def _apply_calculations(self, df: pd.DataFrame) -> Optional[pd.DataFrame]:
"""应用计算处理,委托给 DataCalculator""" """应用计算处理
Args:
df: 清洗后的数据
Returns:
计算后的数据或None
"""
if not self.calculations: if not self.calculations:
self.logger.info("没有计算规则") self.logger.info("没有计算规则")
return df return df
try: try:
calculator = DataCalculator() df_calculated = df.copy()
for calc in self.calculations:
calculator.add_rule(calc.get('type'), **{k: v for k, v in calc.items() if k != 'type'}) for calculation in self.calculations:
result = calculator.calculate(df) calc_type = calculation.get('type')
self.logger.info(f"计算处理完成,数据形状: {result.shape}")
return result if calc_type == 'multiply':
# 乘法计算
source_column = calculation.get('source_column')
target_column = calculation.get('target_column')
factor = calculation.get('factor', 1)
if source_column and target_column:
if source_column in df_calculated.columns:
df_calculated[target_column] = df_calculated[source_column] * factor
self.logger.info(f"乘法计算: {source_column} * {factor} -> {target_column}")
else:
self.logger.warning(f"源列不存在: {source_column}")
elif calc_type == 'divide':
# 除法计算
source_column = calculation.get('source_column')
target_column = calculation.get('target_column')
divisor = calculation.get('divisor', 1)
if source_column and target_column and divisor != 0:
if source_column in df_calculated.columns:
df_calculated[target_column] = df_calculated[source_column] / divisor
self.logger.info(f"除法计算: {source_column} / {divisor} -> {target_column}")
else:
self.logger.warning(f"源列不存在: {source_column}")
elif calc_type == 'formula':
# 公式计算
formula = calculation.get('formula')
target_column = calculation.get('target_column')
if formula and target_column:
try:
df_calculated[target_column] = df_calculated.eval(formula)
self.logger.info(f"公式计算: {formula} -> {target_column}")
except Exception as e:
self.logger.error(f"公式计算失败: {formula}: {e}")
else:
self.logger.warning(f"未知的计算类型: {calc_type}")
self.logger.info(f"计算处理完成,数据形状: {df_calculated.shape}")
return df_calculated
except Exception as e: except Exception as e:
self.logger.error(f"计算处理失败: {e}") self.logger.error(f"计算处理失败: {e}")
return None return None
+20 -5
View File
@@ -16,7 +16,6 @@ from pathlib import Path
from .base import BaseProcessor from .base import BaseProcessor
from ...core.utils.log_utils import get_logger from ...core.utils.log_utils import get_logger
from ...core.utils.string_utils import parse_monetary_string
from ...core.utils.dialog_utils import show_custom_dialog from ...core.utils.dialog_utils import show_custom_dialog
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -39,7 +38,7 @@ class TobaccoProcessor(BaseProcessor):
self.template_file = config.get('Paths', 'template_file', fallback='templates/银豹-采购单模板.xls') self.template_file = config.get('Paths', 'template_file', fallback='templates/银豹-采购单模板.xls')
# 输出目录配置 # 输出目录配置
self.result_dir = Path(config.get_path('Paths', 'result_folder', fallback='data/result', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/result')) self.result_dir = Path("data/result")
self.result_dir.mkdir(exist_ok=True) self.result_dir.mkdir(exist_ok=True)
# 默认输出文件名 # 默认输出文件名
@@ -207,6 +206,19 @@ class TobaccoProcessor(BaseProcessor):
self.logger.error(f"读取订单数据失败: {e}") self.logger.error(f"读取订单数据失败: {e}")
return None return None
def _read_excel_safely(self, file_path: Path, **kwargs) -> pd.DataFrame:
suffix = file_path.suffix.lower()
if suffix == '.xlsx':
return pd.read_excel(file_path, engine='openpyxl', **kwargs)
elif suffix == '.xls':
try:
return pd.read_excel(file_path, engine='xlrd', **kwargs)
except Exception as e:
self.logger.error(f"读取xls失败,可能缺少xlrd: {e}")
raise
else:
return pd.read_excel(file_path, **kwargs)
def _generate_pospal_order(self, order_data: pd.DataFrame, order_time: str, output_file: Path) -> bool: def _generate_pospal_order(self, order_data: pd.DataFrame, order_time: str, output_file: Path) -> bool:
"""生成银豹采购单 """生成银豹采购单
@@ -284,9 +296,12 @@ class TobaccoProcessor(BaseProcessor):
} }
# 格式化金额显示 # 格式化金额显示
parsed = parse_monetary_string(total_amount) try:
total_amount = parsed if parsed is not None else 0.0 if isinstance(total_amount, str):
total_amount = float(total_amount.replace(',', ''))
amount_display = f"¥{total_amount:.2f}" amount_display = f"¥{total_amount:.2f}"
except (ValueError, TypeError):
amount_display = f"¥{total_amount}"
# 显示自定义对话框 # 显示自定义对话框
show_custom_dialog( show_custom_dialog(
@@ -316,7 +331,7 @@ class TobaccoProcessor(BaseProcessor):
today_start = datetime.datetime.combine(today, datetime.time.min).timestamp() today_start = datetime.datetime.combine(today, datetime.time.min).timestamp()
# 查找订单明细文件 # 查找订单明细文件
result_dir = Path(self.config.get_path('Paths', 'output_folder', fallback='data/output') if hasattr(self.config, 'get_path') else os.path.abspath('data/output')) result_dir = Path("data/output")
if not result_dir.exists(): if not result_dir.exists():
return None return None
-247
View File
@@ -1,247 +0,0 @@
"""云端同步模块 — 基于 Gitea REST API 的文件同步"""
import base64
import json
from typing import Optional, Tuple
import requests
from .log_utils import get_logger
logger = get_logger(__name__)
class GiteaSync:
"""通过 Gitea REST API 读写仓库文件"""
def __init__(self, base_url: str, owner: str, repo: str, token: str, timeout: int = 15):
self.base_url = base_url.rstrip("/")
self.owner = owner
self.repo = repo
self.token = token
self.timeout = timeout
@property
def _headers(self) -> dict:
return {"Authorization": f"token {self.token}"}
def _api_url(self, path: str) -> str:
return f"{self.base_url}/api/v1/repos/{self.owner}/{self.repo}/contents/{path}"
def pull_file(self, remote_path: str) -> Optional[Tuple[bytes, str]]:
"""从仓库下载文件
Returns:
(content_bytes, sha) 或 None(文件不存在或失败)
"""
try:
resp = requests.get(
self._api_url(remote_path),
headers=self._headers,
timeout=self.timeout,
)
if resp.status_code == 404:
logger.info(f"云端文件不存在: {remote_path}")
return None
if resp.status_code != 200:
logger.warning(f"拉取文件失败: {resp.status_code} {resp.text[:200]}")
return None
data = resp.json()
sha = data.get("sha", "")
content_b64 = data.get("content", "")
# Gitea 返回的 base64 可能含换行
content_bytes = base64.b64decode(content_b64.replace("\n", ""))
logger.info(f"拉取文件成功: {remote_path} ({len(content_bytes)} bytes)")
return content_bytes, sha
except requests.RequestException as e:
logger.error(f"拉取文件网络错误: {e}")
return None
def push_file(
self,
remote_path: str,
content: bytes,
message: str,
sha: Optional[str] = None,
) -> Optional[str]:
"""上传或更新文件到仓库
Args:
remote_path: 仓库中的文件路径
content: 文件内容(bytes
message: commit message
sha: 文件当前 sha(更新时必传,新建时省略)
Returns:
新的 sha,失败返回 None
"""
payload = {
"message": message,
"content": base64.b64encode(content).decode("ascii"),
}
if sha:
payload["sha"] = sha
try:
resp = requests.put(
self._api_url(remote_path),
headers={**self._headers, "Content-Type": "application/json"},
json=payload,
timeout=self.timeout,
)
if resp.status_code not in (200, 201):
logger.warning(f"推送文件失败: {resp.status_code} {resp.text[:200]}")
return None
new_sha = resp.json().get("content", {}).get("sha", "")
logger.info(f"推送文件成功: {remote_path} (sha={new_sha[:12]})")
return new_sha
except requests.RequestException as e:
logger.error(f"推送文件网络错误: {e}")
return None
def file_exists(self, remote_path: str) -> Optional[str]:
"""检查文件是否存在
Returns:
文件 sha(存在)或 None(不存在)
"""
try:
resp = requests.head(
self._api_url(remote_path),
headers=self._headers,
timeout=self.timeout,
)
if resp.status_code == 200:
# HEAD 不返回 body,需要 GET 获取 sha
result = self.pull_file(remote_path)
return result[1] if result else None
return None
except requests.RequestException:
return None
def pull_json(self, remote_path: str) -> Optional[Tuple[dict, str]]:
"""拉取并解析 JSON 文件
Returns:
(parsed_dict, sha) 或 None
"""
result = self.pull_file(remote_path)
if result is None:
return None
content_bytes, sha = result
try:
data = json.loads(content_bytes)
return data, sha
except json.JSONDecodeError as e:
logger.error(f"解析 JSON 失败: {e}")
return None
def push_json(self, remote_path: str, data: dict, message: str, sha: Optional[str] = None) -> Optional[str]:
"""将 dict 序列化为 JSON 并推送
Returns:
新的 sha,失败返回 None
"""
content = json.dumps(data, ensure_ascii=False, indent=2).encode("utf-8")
return self.push_file(remote_path, content, message, sha)
def push_binary(self, remote_path: str, local_path: str, message: str) -> Optional[str]:
"""读取本地二进制文件并推送到云端
Returns:
新的 sha,失败返回 None
"""
try:
with open(local_path, "rb") as f:
content = f.read()
except OSError as e:
logger.error(f"读取本地文件失败: {local_path}{e}")
return None
existing_sha = self.file_exists(remote_path)
return self.push_file(remote_path, content, message, sha=existing_sha)
def push(self) -> str:
"""推送本地数据到云端:product_cache.json + barcode_mappings.json"""
import os
from pathlib import Path
project_root = Path(__file__).resolve().parent.parent.parent.parent
results = []
# 1. Product cache
from app.core.db.product_db import ProductDatabase
excel_source = str(project_root / "templates" / "商品资料.xlsx")
db_path = str(project_root / "data" / "product_cache.db")
product_db = ProductDatabase(db_path, excel_source)
product_data = product_db.export_for_sync()
sha = self.push_json("product_cache.json", product_data, "sync: update product cache")
results.append(f"product_cache: {'ok' if sha else 'skip'}")
# 2. Barcode mappings
barcode_path = project_root / "config" / "barcode_mappings.json"
if barcode_path.exists():
with open(barcode_path, "r", encoding="utf-8") as f:
barcode_data = json.loads(f.read())
sha = self.push_json("barcode_mappings.json", barcode_data, "sync: update barcode mappings")
results.append(f"barcode_mappings: {'ok' if sha else 'skip'}")
return "; ".join(results) if results else "无数据需要同步"
def pull(self) -> str:
"""从云端拉取数据并写入本地文件"""
import os
from pathlib import Path
project_root = Path(__file__).resolve().parent.parent.parent.parent
results = []
# 1. Product cache
result = self.pull_json("product_cache.json")
if result is not None:
data, sha = result
from app.core.db.product_db import ProductDatabase
excel_source = str(project_root / "templates" / "商品资料.xlsx")
db_path = str(project_root / "data" / "product_cache.db")
os.makedirs(os.path.dirname(db_path), exist_ok=True)
product_db = ProductDatabase(db_path, excel_source)
count = product_db.import_from_sync(data)
results.append(f"product_cache: 导入 {count}")
else:
results.append("product_cache: 云端无数据")
# 2. Barcode mappings
barcode_result = self.pull_json("barcode_mappings.json")
if barcode_result is not None:
barcode_data, sha = barcode_result
barcode_path = project_root / "config" / "barcode_mappings.json"
barcode_path.parent.mkdir(parents=True, exist_ok=True)
with open(barcode_path, "w", encoding="utf-8") as f:
json.dump(barcode_data, f, ensure_ascii=False, indent=2)
results.append(f"barcode_mappings: 已更新")
else:
results.append("barcode_mappings: 云端无数据")
return "; ".join(results) if results else "无数据需要同步"
@classmethod
def from_config(cls, config) -> Optional["GiteaSync"]:
"""从 ConfigManager 创建实例
Returns:
GiteaSync 实例,配置不完整时返回 None
"""
base_url = config.get("Gitea", "base_url", fallback="").strip()
owner = config.get("Gitea", "owner", fallback="").strip()
repo = config.get("Gitea", "repo", fallback="").strip()
token = config.get("Gitea", "token", fallback="").strip()
if not all([base_url, owner, repo, token]):
logger.debug("Gitea 配置不完整,跳过云端同步")
return None
return cls(base_url=base_url, owner=owner, repo=repo, token=token)
+5 -496
View File
@@ -8,14 +8,10 @@
""" """
import os import os
import json
import tkinter as tk import tkinter as tk
from tkinter import messagebox, ttk, simpledialog from tkinter import messagebox, ttk, simpledialog
from datetime import datetime from datetime import datetime
from .cloud_sync import GiteaSync
from app.config.settings import ConfigManager
def create_custom_dialog(title="提示", message="", result_file=None, time_info=None, def create_custom_dialog(title="提示", message="", result_file=None, time_info=None,
count_info=None, amount_info=None, additional_info=None): count_info=None, amount_info=None, additional_info=None):
""" """
@@ -82,12 +78,11 @@ def create_custom_dialog(title="提示", message="", result_file=None, time_info
file_size = os.path.getsize(result_file) file_size = os.path.getsize(result_file)
file_time = datetime.fromtimestamp(os.path.getmtime(result_file)) file_time = datetime.fromtimestamp(os.path.getmtime(result_file))
from .file_utils import format_file_size size_text = f"{file_size / 1024:.1f} KB" if file_size < 1024*1024 else f"{file_size / (1024*1024):.1f} MB"
size_text = format_file_size(file_size)
tk.Label(file_frame, text=f"文件大小: {size_text}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2) tk.Label(file_frame, text=f"文件大小: {size_text}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
tk.Label(file_frame, text=f"创建时间: {file_time.strftime('%Y-%m-%d %H:%M:%S')}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2) tk.Label(file_frame, text=f"创建时间: {file_time.strftime('%Y-%m-%d %H:%M:%S')}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
except Exception: except:
tk.Label(file_frame, text="无法获取文件信息", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2) tk.Label(file_frame, text="无法获取文件信息", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
# 添加按钮 # 添加按钮
@@ -107,7 +102,7 @@ def create_custom_dialog(title="提示", message="", result_file=None, time_info
button_frame = tk.Frame(dialog) button_frame = tk.Frame(dialog)
button_frame.pack(pady=10) button_frame.pack(pady=10)
tk.Button(button_frame, text="打开输出目录", command=lambda: os.startfile(ConfigManager().get_path('Paths', 'output_folder', fallback='data/output', create=True))).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="打开输出目录", command=lambda: os.startfile(os.path.abspath("data/output"))).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="关闭", command=dialog.destroy).pack(side=tk.LEFT, padx=5) tk.Button(button_frame, text="关闭", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
# 确保窗口显示在最前 # 确保窗口显示在最前
@@ -488,135 +483,6 @@ def create_barcode_mapping_dialog(parent=None, on_save=None, current_mappings=No
cancel_btn = tk.Button(bottom_frame, text="取消", command=cancel) cancel_btn = tk.Button(bottom_frame, text="取消", command=cancel)
cancel_btn.pack(side=tk.RIGHT, padx=5) cancel_btn.pack(side=tk.RIGHT, padx=5)
# ---- 云端同步按钮 ----
def _build_current_mappings():
"""从弹窗当前数据构建 mappings dict(与 save_mappings 逻辑相同)"""
mappings = {}
for source, target in mapping_list:
mappings[source] = {
'map_to': target,
'description': f'条码映射:{source} -> {target}'
}
for barcode, multiplier, unit, price, spec, desc in special_list:
if barcode not in mappings:
mappings[barcode] = {}
if multiplier:
try:
if isinstance(multiplier, str):
mappings[barcode]['multiplier'] = float(multiplier) if '.' in multiplier else int(multiplier)
else:
mappings[barcode]['multiplier'] = multiplier
except ValueError:
mappings[barcode]['multiplier'] = multiplier
if unit:
mappings[barcode]['target_unit'] = unit
if price:
try:
mappings[barcode]['fixed_price'] = float(price)
except ValueError:
mappings[barcode]['fixed_price'] = price
if spec:
mappings[barcode]['specification'] = spec
if desc and "映射到:" in desc:
parts = desc.split("映射到:")
base_desc = parts[0].strip()
target_barcode = parts[1].strip()
if base_desc:
mappings[barcode]['description'] = base_desc
mappings[barcode]['map_to'] = target_barcode
elif desc:
mappings[barcode]['description'] = desc
return mappings
def _get_sync():
"""获取 GiteaSync 实例,配置不完整时提示用户"""
sync = GiteaSync.from_config(ConfigManager())
if sync is None:
messagebox.showwarning("云端同步", "请先在「系统设置」中配置 Gitea 云端同步参数(token")
return sync
def _refresh_trees(new_mappings):
"""用新数据刷新两个 Treeview"""
# 清空
for item in mapping_tree.get_children():
mapping_tree.delete(item)
mapping_list.clear()
for item in special_tree.get_children():
special_tree.delete(item)
special_list.clear()
# 重新填充
if new_mappings:
for barcode, data in new_mappings.items():
if 'map_to' in data and 'multiplier' not in data:
mapping_list.append((barcode, data['map_to']))
mapping_tree.insert('', 'end', values=(barcode, data['map_to']))
else:
mult = data.get('multiplier', '')
unit = data.get('target_unit', '')
price = data.get('fixed_price', '')
spec = data.get('specification', '')
desc = data.get('description', '')
if 'map_to' in data:
desc = f"{desc} 映射到: {data['map_to']}" if desc else f"映射到: {data['map_to']}"
special_list.append((barcode, mult, unit, price, spec, desc))
tags = ("mapped",) if 'map_to' in data else ()
special_tree.insert('', 'end', values=(barcode, mult, unit, price, spec, desc), tags=tags)
if any('map_to' in d for d in new_mappings.values()):
special_tree.tag_configure("mapped", foreground="blue")
def push_to_cloud():
sync = _get_sync()
if not sync:
return
mappings = _build_current_mappings()
if not mappings:
messagebox.showwarning("同步到云端", "当前没有映射数据可同步")
return
# 先获取当前 sha(如果文件已存在)
sha = None
existing = sync.pull_file("barcode_mappings.json")
if existing:
sha = existing[1]
new_sha = sync.push_json(
"barcode_mappings.json",
mappings,
f"同步条码映射 ({len(mappings)} 条)",
sha=sha,
)
if new_sha:
messagebox.showinfo("同步成功", f"已推送 {len(mappings)} 条映射到云端")
else:
messagebox.showerror("同步失败", "推送到云端失败,请检查网络和 Gitea 配置")
def pull_from_cloud():
sync = _get_sync()
if not sync:
return
result = sync.pull_json("barcode_mappings.json")
if result is None:
messagebox.showwarning("拉取失败", "云端没有找到条码映射文件,或网络错误")
return
data, sha = result
if not isinstance(data, dict) or len(data) == 0:
messagebox.showwarning("拉取失败", "云端数据格式异常")
return
# 同时保存到本地
from app.core.excel.converter import UnitConverter
uc = UnitConverter()
uc.update_barcode_mappings(data)
# 刷新弹窗
_refresh_trees(data)
messagebox.showinfo("拉取成功", f"已从云端拉取 {len(data)} 条映射,本地已同步更新")
sync_frame = tk.Frame(bottom_frame)
sync_frame.pack(side=tk.LEFT, padx=5)
push_btn = tk.Button(sync_frame, text="同步到云端", command=push_to_cloud, fg="white", bg="#4a90d9")
push_btn.pack(side=tk.LEFT, padx=3)
pull_btn = tk.Button(sync_frame, text="从云端拉取", command=pull_from_cloud, fg="white", bg="#5cb85c")
pull_btn.pack(side=tk.LEFT, padx=3)
# 导入当前映射数据 # 导入当前映射数据
if current_mappings: if current_mappings:
for barcode, data in current_mappings.items(): for barcode, data in current_mappings.items():
@@ -800,362 +666,5 @@ def show_config_dialog(parent, config_manager, on_save=None):
dialog.transient(parent) dialog.transient(parent)
dialog.grab_set() dialog.grab_set()
# 等待窗口关闭
# ────────────────────────────────────────────────────────────── parent.wait_window(dialog)
# 云端同步管理对话框
# ──────────────────────────────────────────────────────────────
SYNC_FILES = [
{
"name": "条码映射",
"remote": "barcode_mappings.json",
"local": "config/barcode_mappings.json",
"type": "json",
},
{
"name": "供应商配置",
"remote": "suppliers_config.json",
"local": "config/suppliers_config.json",
"type": "json",
},
{
"name": "商品资料",
"remote": "templates/商品资料.xlsx",
"local": "templates/商品资料.xlsx",
"type": "binary",
},
{
"name": "采购单模板",
"remote": "templates/银豹-采购单模板.xls",
"local": "templates/银豹-采购单模板.xls",
"type": "binary",
},
{
"name": "商品记忆库",
"remote": "product_memory.json",
"local": "data/product_memory.json",
"type": "json",
},
]
def _format_size(path: str) -> str:
try:
size = os.path.getsize(path)
if size < 1024 * 1024:
return f"{size / 1024:.1f} KB"
return f"{size / (1024 * 1024):.1f} MB"
except OSError:
return ""
def show_cloud_sync_dialog(parent=None):
"""统一云端同步管理对话框"""
sync = GiteaSync.from_config(ConfigManager())
if sync is None:
messagebox.showwarning(
"配置不完整",
"请先在「系统设置」中配置 Gitea 地址和 Access Token",
)
return
dlg = tk.Toplevel(parent)
dlg.title("云端同步管理")
dlg.geometry("620x440")
dlg.resizable(False, False)
# 居中
dlg.update_idletasks()
x = (dlg.winfo_screenwidth() - 620) // 2
y = (dlg.winfo_screenheight() - 440) // 2
dlg.geometry(f"620x440+{x}+{y}")
# ── Treeview ──
columns = ("name", "local_status", "cloud_status")
tree = ttk.Treeview(dlg, columns=columns, show="headings", height=6)
tree.heading("name", text="文件")
tree.heading("local_status", text="本地状态")
tree.heading("cloud_status", text="云端状态")
tree.column("name", width=140)
tree.column("local_status", width=220)
tree.column("cloud_status", width=220)
tree.pack(fill=tk.BOTH, expand=True, padx=16, pady=(16, 8))
# tag 颜色
tree.tag_configure("synced", foreground="#2e7d32")
tree.tag_configure("cloud_only", foreground="#e65100")
tree.tag_configure("local_only", foreground="#1565c0")
tree.tag_configure("missing", foreground="#999999")
# 用 iid = remote_path 标识每行
cloud_sha_cache: dict = {} # remote_path -> sha
def _load_local_status():
"""仅加载本地状态,不发网络请求"""
for item in tree.get_children():
tree.delete(item)
for entry in SYNC_FILES:
local = entry["local"]
if os.path.exists(local):
if entry["type"] == "json":
try:
with open(local, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict):
local_text = f"{len(data)}"
elif isinstance(data, list):
local_text = f"{len(data)} 条记录"
else:
local_text = "已存在"
except Exception:
local_text = "已存在(解析异常)"
else:
local_text = _format_size(local)
tag = "local_only"
else:
local_text = "不存在"
tag = "missing"
tree.insert(
"", tk.END,
iid=entry["remote"],
values=(entry["name"], local_text, "点「刷新状态」检查"),
tags=(tag,),
)
def refresh_status():
"""刷新每行的本地/云端状态"""
cloud_sha_cache.clear()
for item in tree.get_children():
tree.delete(item)
for entry in SYNC_FILES:
remote = entry["remote"]
local = entry["local"]
# 本地状态
if os.path.exists(local):
if entry["type"] == "json":
try:
with open(local, "r", encoding="utf-8") as f:
data = json.load(f)
if isinstance(data, dict):
local_text = f"{len(data)}"
elif isinstance(data, list):
local_text = f"{len(data)} 条记录"
else:
local_text = "已存在"
except Exception:
local_text = "已存在(解析异常)"
else:
local_text = _format_size(local)
else:
local_text = "不存在"
# 云端状态 — 网络请求,可能慢
sha = sync.file_exists(remote)
if sha:
cloud_sha_cache[remote] = sha
cloud_text = "已存在"
else:
cloud_text = "未上传"
# tag
local_ok = os.path.exists(local)
cloud_ok = sha is not None
if local_ok and cloud_ok:
tag = "synced"
elif cloud_ok and not local_ok:
tag = "cloud_only"
elif local_ok and not cloud_ok:
tag = "local_only"
else:
tag = "missing"
tree.insert(
"", tk.END,
iid=remote,
values=(entry["name"], local_text, cloud_text),
tags=(tag,),
)
# ── 操作函数 ──
def _get_selected_entries():
"""获取选中的文件条目列表"""
selected = tree.selection()
if not selected:
messagebox.showinfo("提示", "请先选中要操作的文件")
return []
return [e for e in SYNC_FILES if e["remote"] in selected]
def push_selected():
entries = _get_selected_entries()
if not entries:
return
ok, fail = 0, 0
for entry in entries:
local, remote = entry["local"], entry["remote"]
if not os.path.exists(local):
messagebox.showwarning("跳过", f"本地文件不存在: {local}")
fail += 1
continue
if entry["type"] == "json":
try:
with open(local, "r", encoding="utf-8") as f:
data = json.load(f)
sha = cloud_sha_cache.get(remote)
result = sync.push_json(remote, data, f"同步 {entry['name']}", sha=sha)
except Exception as e:
messagebox.showerror("推送失败", f"{entry['name']}: {e}")
fail += 1
continue
else:
result = sync.push_binary(remote, local, f"同步 {entry['name']}")
if result:
ok += 1
else:
fail += 1
if ok:
messagebox.showinfo("推送完成", f"成功 {ok}" + (f",失败 {fail}" if fail else ""))
refresh_status()
def pull_selected():
entries = _get_selected_entries()
if not entries:
return
ok, fail = 0, 0
for entry in entries:
remote, local = entry["remote"], entry["local"]
if entry["type"] == "json":
result = sync.pull_json(remote)
if result is None:
messagebox.showwarning("拉取失败", f"云端文件不存在: {entry['name']}")
fail += 1
continue
content, sha = result
# 写入本地
os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
with open(local, "w", encoding="utf-8") as f:
json.dump(content, f, ensure_ascii=False, indent=2)
# 特殊后处理
_post_pull(entry, content)
else:
result = sync.pull_file(remote)
if result is None:
messagebox.showwarning("拉取失败", f"云端文件不存在: {entry['name']}")
fail += 1
continue
content, sha = result
os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
with open(local, "wb") as f:
f.write(content)
ok += 1
if ok:
messagebox.showinfo("拉取完成", f"成功 {ok}" + (f",失败 {fail}" if fail else ""))
refresh_status()
def _post_pull(entry, data):
"""拉取 JSON 文件后的特殊处理"""
if entry["remote"] == "barcode_mappings.json":
try:
from app.core.excel.converter import UnitConverter
UnitConverter().update_barcode_mappings(data)
except Exception:
pass
elif entry["remote"] == "suppliers_config.json":
try:
from app.services.processor_service import ProcessorService
ProcessorService(ConfigManager()).reload_processors()
except Exception:
pass
elif entry["remote"] == "product_memory.json":
try:
from app.core.db.product_db import ProductDatabase
cfg = ConfigManager()
db_path = cfg.get_path('Paths', 'product_db', fallback='data/product_cache.db') if hasattr(cfg, 'get_path') else 'data/product_cache.db'
tpl_folder = cfg.get('Paths', 'template_folder', fallback='templates')
item_data = cfg.get('Templates', 'item_data', fallback='商品资料.xlsx')
tpl_path = os.path.join(tpl_folder, item_data)
db = ProductDatabase(db_path, tpl_path)
count = db.import_from_sync(data)
logger.info(f"从云端导入商品记忆: {count}")
except Exception:
pass
def push_all():
ok, fail = 0, 0
for entry in SYNC_FILES:
local, remote = entry["local"], entry["remote"]
if not os.path.exists(local):
fail += 1
continue
if entry["type"] == "json":
try:
with open(local, "r", encoding="utf-8") as f:
data = json.load(f)
sha = cloud_sha_cache.get(remote)
result = sync.push_json(remote, data, f"批量同步 {entry['name']}", sha=sha)
except Exception:
fail += 1
continue
else:
result = sync.push_binary(remote, local, f"批量同步 {entry['name']}")
if result:
ok += 1
else:
fail += 1
messagebox.showinfo("批量推送完成", f"成功 {ok} 个,失败 {fail}")
refresh_status()
def pull_all():
ok, fail = 0, 0
for entry in SYNC_FILES:
remote, local = entry["remote"], entry["local"]
if entry["type"] == "json":
result = sync.pull_json(remote)
if result is None:
fail += 1
continue
content, sha = result
os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
with open(local, "w", encoding="utf-8") as f:
json.dump(content, f, ensure_ascii=False, indent=2)
_post_pull(entry, content)
else:
result = sync.pull_file(remote)
if result is None:
fail += 1
continue
content, sha = result
os.makedirs(os.path.dirname(local) or ".", exist_ok=True)
with open(local, "wb") as f:
f.write(content)
ok += 1
messagebox.showinfo("批量拉取完成", f"成功 {ok} 个,失败 {fail}")
refresh_status()
# ── 按钮区域 ──
btn_frame = ttk.Frame(dlg)
btn_frame.pack(fill=tk.X, padx=16, pady=(4, 16))
# 左侧:批量操作
ttk.Button(btn_frame, text="全部推送到云端", command=push_all).pack(side=tk.LEFT, padx=4)
ttk.Button(btn_frame, text="全部从云端拉取", command=pull_all).pack(side=tk.LEFT, padx=4)
# 右侧:选中操作 + 刷新 + 关闭
ttk.Button(btn_frame, text="关闭", command=dlg.destroy).pack(side=tk.RIGHT, padx=4)
ttk.Button(btn_frame, text="刷新状态", command=refresh_status).pack(side=tk.RIGHT, padx=4)
tk.Button(btn_frame, text="推送到云端", command=push_selected, fg="white", bg="#4a90d9").pack(side=tk.RIGHT, padx=4)
tk.Button(btn_frame, text="从云端拉取", command=pull_selected, fg="white", bg="#5cb85c").pack(side=tk.RIGHT, padx=4)
# 仅显示本地状态,云端状态需手动点"刷新状态"
_load_local_status()
dlg.transient(parent)
dlg.grab_set()
-7
View File
@@ -277,10 +277,3 @@ def is_file_size_valid(file_path: str, max_size_mb: float) -> bool:
size_bytes = get_file_size(file_path) size_bytes = get_file_size(file_path)
max_size_bytes = max_size_mb * 1024 * 1024 max_size_bytes = max_size_mb * 1024 * 1024
return size_bytes <= max_size_bytes return size_bytes <= max_size_bytes
def format_file_size(size_bytes: int) -> str:
"""将字节数格式化为可读的文件大小字符串(KB/MB)"""
if size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.1f} KB"
return f"{size_bytes / (1024 * 1024):.1f} MB"
+1 -46
View File
@@ -5,7 +5,7 @@
""" """
import re import re
from typing import Dict, List, Optional, Tuple, Any from typing import Dict, List, Optional, Tuple, Any, Match, Pattern
def clean_string(text: str) -> str: def clean_string(text: str) -> str:
""" """
@@ -192,51 +192,6 @@ def is_scientific_notation(value: str) -> bool:
""" """
return bool(re.match(r'^-?\d+(\.\d+)?[eE][+-]?\d+$', str(value))) return bool(re.match(r'^-?\d+(\.\d+)?[eE][+-]?\d+$', str(value)))
def parse_monetary_string(value: Any) -> Optional[float]:
"""
解析金额/数量字符串为浮点数
处理: 货币符号(¥/$)逗号作小数点逗号作千位分隔符中文""后缀等
Args:
value: 金额值字符串数字或其他类型
Returns:
解析后的浮点数无法解析则返回 None
"""
if value is None:
return None
if isinstance(value, (int, float)):
return float(value)
if not isinstance(value, str):
return None
s = value.strip()
if not s or s.lower() in ('o', 'none', 'null', '-', '--'):
return None
# 移除非数字字符,保留数字、小数点、逗号和负号
cleaned = re.sub(r'[^\d\.\-,]', '', s)
if not cleaned or cleaned in ('-', '.', '-.', ','):
return None
# 逗号处理策略:
# 多个逗号 -> 千位分隔符,全部移除 (如 "1,234,567" = 1234567)
# 一个逗号 + 无小数点 -> 逗号当小数点 (如 "1,5" = 1.5)
# 一个逗号 + 有小数点 -> 千位分隔符,移除 (如 "1,234.56" = 1234.56)
comma_count = cleaned.count(',')
if comma_count > 1:
cleaned = cleaned.replace(',', '')
elif comma_count == 1 and '.' not in cleaned:
cleaned = cleaned.replace(',', '.')
elif comma_count == 1 and '.' in cleaned:
cleaned = cleaned.replace(',', '')
try:
return float(cleaned)
except (ValueError, TypeError):
return None
def format_barcode(barcode: Any) -> str: def format_barcode(barcode: Any) -> str:
""" """
格式化条码处理科学计数法 格式化条码处理科学计数法
+1 -1
View File
@@ -154,7 +154,7 @@ class OCRService:
# 获取文件名(不含扩展名) # 获取文件名(不含扩展名)
base_name = os.path.splitext(os.path.basename(image_path))[0] base_name = os.path.splitext(os.path.basename(image_path))[0]
# 生成Excel文件路径 # 生成Excel文件路径
output_dir = self.config.get_path('Paths', 'output_folder', fallback='data/output', create=True) if hasattr(self.config, 'get_path') else os.path.abspath('data/output') output_dir = self.config.get('Paths', 'output_folder', fallback='data/output')
excel_path = os.path.join(output_dir, f"{base_name}.xlsx") excel_path = os.path.join(output_dir, f"{base_name}.xlsx")
return excel_path return excel_path
+46 -50
View File
@@ -11,7 +11,6 @@ from ..config.settings import ConfigManager
from ..core.utils.log_utils import get_logger from ..core.utils.log_utils import get_logger
from ..core.excel.processor import ExcelProcessor from ..core.excel.processor import ExcelProcessor
from ..core.excel.merger import PurchaseOrderMerger from ..core.excel.merger import PurchaseOrderMerger
from ..core.db.product_db import ProductDatabase
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -30,15 +29,8 @@ class OrderService:
logger.info("初始化OrderService") logger.info("初始化OrderService")
self.config = config or ConfigManager() self.config = config or ConfigManager()
# 创建共享的商品数据库实例
db_path = self.config.get_path('Paths', 'product_db', fallback='data/product_cache.db') if hasattr(self.config, 'get_path') else 'data/product_cache.db'
tpl_folder = self.config.get('Paths', 'template_folder', fallback='templates')
item_data = self.config.get('Templates', 'item_data', fallback='商品资料.xlsx')
tpl_path = os.path.join(tpl_folder, item_data)
self.product_db = ProductDatabase(db_path, tpl_path)
# 创建Excel处理器和采购单合并器 # 创建Excel处理器和采购单合并器
self.excel_processor = ExcelProcessor(self.config, product_db=self.product_db) self.excel_processor = ExcelProcessor(self.config)
self.order_merger = PurchaseOrderMerger(self.config) self.order_merger = PurchaseOrderMerger(self.config)
logger.info("OrderService初始化完成") logger.info("OrderService初始化完成")
@@ -121,20 +113,10 @@ class OrderService:
return special_svc.preprocess_rongcheng_yigou(file_path) return special_svc.preprocess_rongcheng_yigou(file_path)
# 3. 识别:杨碧月 (Yang Biyue) # 3. 识别:杨碧月 (Yang Biyue)
# 特征:经手人列包含“杨碧月”
handler_col = None
for col in df_head.columns:
# 在前50行中搜索“经手人”关键字
if df_head[col].astype(str).str.contains('经手人').any():
handler_col = col
break
if handler_col is not None:
# 检查该列是否有“杨碧月”
if df_head[handler_col].astype(str).str.contains('杨碧月').any():
logger.info("识别到杨碧月订单,执行专用预处理...")
from .special_suppliers_service import SpecialSuppliersService from .special_suppliers_service import SpecialSuppliersService
special_svc = SpecialSuppliersService(self.config) special_svc = SpecialSuppliersService(self.config)
# 我们直接复用 SpecialSuppliersService 里的逻辑,但要确保它只返回路径
# 修改 SpecialSuppliersService.process_yang_biyue 使其支持仅返回预处理路径
return special_svc.process_yang_biyue_only(file_path) return special_svc.process_yang_biyue_only(file_path)
except Exception as e: except Exception as e:
@@ -202,41 +184,55 @@ class OrderService:
""" """
try: try:
import pandas as pd import pandas as pd
import os
from app.core.utils.file_utils import smart_read_excel from app.core.utils.file_utils import smart_read_excel
from app.core.handlers.column_mapper import ColumnMapper as CM
# 使用共享的商品数据库实例 item_path = os.path.join('templates', '商品资料.xlsx')
product_db = self.product_db if not os.path.exists(item_path):
logger.warning(f"未找到商品资料文件: {item_path}")
# 读取待校验的采购单
df_res = smart_read_excel(result_path)
res_barcode_col = CM.find_column(list(df_res.columns), 'barcode')
res_price_col = CM.find_column(list(df_res.columns), 'unit_price')
if not res_barcode_col or not res_price_col:
logger.warning("未能在采购单中找到条码或单价列")
return [] return []
# 批量查询进货价 df_item = smart_read_excel(item_path)
barcodes = df_res[res_barcode_col].astype(str).str.strip().tolist() df_res = smart_read_excel(result_path)
item_prices = product_db.get_prices(barcodes)
def _find_col(df, candidates, contains=None):
cols = list(df.columns)
for c in candidates:
if c in cols:
return c
if contains:
for c in cols:
if contains in str(c):
return c
return None
item_barcode_col = _find_col(df_item, ['商品条码','商品条码(小条码)','条码','barcode'], contains='条码')
item_price_col = _find_col(df_item, ['进货价','进货价(必填)'], contains='进货价')
res_barcode_col = _find_col(df_res, ['条码','barcode'], contains='条码')
res_price_col = _find_col(df_res, ['采购单价','unit_price','单价'], contains='单价')
if not all([item_barcode_col, item_price_col, res_barcode_col, res_price_col]):
logger.warning("未能在文件和商品资料中找到完整的校验列(条码、单价)")
return []
item_map = df_item[[item_barcode_col, item_price_col]].dropna()
item_map[item_price_col] = pd.to_numeric(item_map[item_price_col], errors='coerce')
item_map = item_map.dropna()
imap = dict(zip(item_map[item_barcode_col].astype(str).str.strip(), item_map[item_price_col]))
df_res['_bc_'] = df_res[res_barcode_col].astype(str).str.strip()
df_res['_res_price_'] = pd.to_numeric(df_res[res_price_col], errors='coerce')
df_res['_item_price_'] = df_res['_bc_'].map(imap)
df_check = df_res.dropna(subset=['_res_price_','_item_price_'])
df_check['_diff_'] = (df_check['_res_price_'] - df_check['_item_price_']).abs()
bad = df_check[df_check['_diff_'] > 1.0]
results = [] results = []
for _, row in df_res.iterrows(): if not bad.empty:
bc = str(row[res_barcode_col]).strip() for i in range(len(bad)):
if bc not in item_prices: r = bad.iloc[i]
continue results.append(f"条码 {r['_bc_']}: 采购单价={r['_res_price_']} vs 进货价={r['_item_price_']} 差异={r['_diff_']:.2f}")
try:
res_price = float(row[res_price_col])
except (ValueError, TypeError):
continue
item_price = item_prices[bc]
diff = abs(res_price - item_price)
if diff > 1.0:
results.append(f"条码 {bc}: 采购单价={res_price} vs 进货价={item_price} 差异={diff:.2f}")
return results return results
+19 -50
View File
@@ -5,11 +5,10 @@ import os
import re import re
import time import time
import pandas as pd import pandas as pd
import logging
from typing import Optional, Callable from typing import Optional, Callable
from ..core.utils.log_utils import get_logger logger = logging.getLogger(__name__)
logger = get_logger(__name__)
class SpecialSuppliersService: class SpecialSuppliersService:
""" """
@@ -38,41 +37,24 @@ class SpecialSuppliersService:
if handler_col is None or not df[handler_col].astype(str).str.contains('杨碧月').any(): if handler_col is None or not df[handler_col].astype(str).str.contains('杨碧月').any():
return None return None
# 识别到杨碧月订单,执行专用清洗
logger.info("识别到杨碧月订单,正在执行专用清洗...") logger.info("识别到杨碧月订单,正在执行专用清洗...")
# 定义列映射关系 (映射到 ExcelProcessor 期望的中文列名) # 定义列映射关系
# 使用精确匹配优先,防止“结算单位”匹配到“单位”
column_map = { column_map = {
'商品条码': '商品条码', '商品条码': 'barcode',
'商品名称': '商品名称', '商品名称': 'name',
'商品规格': '规格', '商品规格': 'specification',
'单位': '单位', '单位': 'unit',
'数量': '数量', '数量': 'quantity',
'含税单价': '单价', '含税单价': 'unit_price',
'含税金额': '金额' '含税金额': 'total_price'
} }
# 提取并重命名列
found_cols = {} found_cols = {}
# 1. 第一遍:尝试精确匹配
for target_zh, std_name in column_map.items(): for target_zh, std_name in column_map.items():
for col in df.columns: for col in df.columns:
if str(col).strip() == target_zh: if target_zh in str(col):
found_cols[col] = std_name
break
# 2. 第二遍:对未匹配成功的列尝试模糊匹配(但要排除特定干扰词)
for target_zh, std_name in column_map.items():
if std_name in found_cols.values():
continue
for col in df.columns:
col_str = str(col)
if target_zh in col_str:
# 排除干扰列
if target_zh == '单位' and '结算单位' in col_str:
continue
if target_zh == '数量' and '基本单位数量' in col_str:
continue
found_cols[col] = std_name found_cols[col] = std_name
break break
@@ -84,15 +66,10 @@ class SpecialSuppliersService:
df_clean = df_clean.rename(columns=found_cols) df_clean = df_clean.rename(columns=found_cols)
# 过滤掉空的条码行 # 过滤掉空的条码行
df_clean = df_clean.dropna(subset=['商品条码']) df_clean = df_clean.dropna(subset=['barcode'])
# 保存预处理文件到输出目录(而非源文件目录) # 保存预处理文件
if self.config_manager and hasattr(self.config_manager, 'get_path'): out_dir = os.path.dirname(src_path)
out_dir = self.config_manager.get_path('Paths', 'output_folder', fallback='data/output', create=True)
else:
from app.config.settings import ConfigManager
out_dir = ConfigManager().get_path('Paths', 'output_folder', fallback='data/output', create=True)
os.makedirs(out_dir, exist_ok=True)
base = os.path.basename(src_path) base = os.path.basename(src_path)
final_path = os.path.join(out_dir, f"预处理之后_{base}") final_path = os.path.join(out_dir, f"预处理之后_{base}")
df_clean.to_excel(final_path, index=False) df_clean.to_excel(final_path, index=False)
@@ -200,20 +177,14 @@ class SpecialSuppliersService:
try: try:
up = float(new_row['单价'] or 0) up = float(new_row['单价'] or 0)
new_row['金额'] = up * current_qty new_row['金额'] = up * current_qty
except Exception: except: pass
pass
rows.append(new_row) rows.append(new_row)
continue continue
rows.append(row) rows.append(row)
df2 = pd.DataFrame(rows) df2 = pd.DataFrame(rows)
# 保存预处理文件到输出目录(而非源文件目录) # 保存预处理文件
if self.config_manager and hasattr(self.config_manager, 'get_path'): out_dir = os.path.dirname(src_path)
out_dir = self.config_manager.get_path('Paths', 'output_folder', fallback='data/output', create=True)
else:
from app.config.settings import ConfigManager
out_dir = ConfigManager().get_path('Paths', 'output_folder', fallback='data/output', create=True)
os.makedirs(out_dir, exist_ok=True)
base = os.path.basename(src_path) base = os.path.basename(src_path)
final_path = os.path.join(out_dir, f"预处理之后_{base}") final_path = os.path.join(out_dir, f"预处理之后_{base}")
df2.to_excel(final_path, index=False) df2.to_excel(final_path, index=False)
@@ -231,7 +202,5 @@ class SpecialSuppliersService:
""" """
cleaned_path = self.preprocess_rongcheng_yigou(src_path, progress_cb) cleaned_path = self.preprocess_rongcheng_yigou(src_path, progress_cb)
if cleaned_path: if cleaned_path:
from app.services.order_service import OrderService return self.order_service.process_excel(cleaned_path, progress_cb=lambda p: progress_cb(60 + int(p*0.4), "生成采购单中...") if progress_cb else None)
order_service = OrderService(self.config_manager)
return order_service.process_excel(cleaned_path, progress_cb=lambda p: progress_cb(60 + int(p*0.4), "生成采购单中...") if progress_cb else None)
return None return None
+10 -8
View File
@@ -18,7 +18,6 @@ from xlutils.copy import copy
from openpyxl import load_workbook from openpyxl import load_workbook
from typing import Optional, Dict, Any, List, Tuple from typing import Optional, Dict, Any, List, Tuple
from app.core.utils.log_utils import get_logger from app.core.utils.log_utils import get_logger
from app.core.utils.string_utils import parse_monetary_string
from app.core.utils.dialog_utils import show_custom_dialog # 导入自定义弹窗工具 from app.core.utils.dialog_utils import show_custom_dialog # 导入自定义弹窗工具
from ..config.settings import ConfigManager from ..config.settings import ConfigManager
@@ -36,10 +35,10 @@ class TobaccoService:
""" """
self.config = config self.config = config
# 修复配置获取方式,使用fallback机制 # 修复配置获取方式,使用fallback机制
self.output_dir = config.get_path('Paths', 'output_folder', fallback='data/output', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/output') self.output_dir = config.get('Paths', 'output_folder', fallback='data/output')
self.template_file = config.get('Paths', 'template_file', fallback='templates/银豹-采购单模板.xls') self.template_file = config.get('Paths', 'template_file', fallback='templates/银豹-采购单模板.xls')
# 将烟草订单保存到result目录 # 将烟草订单保存到result目录
result_dir = config.get_path('Paths', 'result_folder', fallback='data/result', create=True) if hasattr(config, 'get_path') else os.path.abspath('data/result') result_dir = "data/result"
os.makedirs(result_dir, exist_ok=True) os.makedirs(result_dir, exist_ok=True)
self.output_file = os.path.join(result_dir, '银豹采购单_烟草公司.xls') self.output_file = os.path.join(result_dir, '银豹采购单_烟草公司.xls')
@@ -132,9 +131,8 @@ class TobaccoService:
final_cols = ['商品条码', '商品名称', '数量', '单价', '金额'] final_cols = ['商品条码', '商品名称', '数量', '单价', '金额']
df_final = df[final_cols].copy() df_final = df[final_cols].copy()
# 保存预处理文件到输出目录(而非源文件目录) # 保存预处理文件
out_dir = self.output_dir out_dir = os.path.dirname(file_path)
os.makedirs(out_dir, exist_ok=True)
base = os.path.basename(file_path) base = os.path.basename(file_path)
final_path = os.path.join(out_dir, f"预处理之后_{base}") final_path = os.path.join(out_dir, f"预处理之后_{base}")
df_final.to_excel(final_path, index=False) df_final.to_excel(final_path, index=False)
@@ -318,9 +316,13 @@ class TobaccoService:
} }
# 确保 total_amount 是数字类型 # 确保 total_amount 是数字类型
parsed = parse_monetary_string(total_amount) try:
total_amount = parsed if parsed is not None else 0.0 if isinstance(total_amount, str):
total_amount = float(total_amount.replace(',', ''))
amount_display = f"¥{total_amount:.2f}" amount_display = f"¥{total_amount:.2f}"
except (ValueError, TypeError):
# 如果转换失败,直接使用原始值
amount_display = f"¥{total_amount}"
# 显示自定义对话框 # 显示自定义对话框
show_custom_dialog( show_custom_dialog(
-2
View File
@@ -1,2 +0,0 @@
# -*- coding: utf-8 -*-
"""益选-OCR订单处理系统 UI 模块"""
-565
View File
@@ -1,565 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""业务操作处理模块"""
import os
import time
import datetime
import json
import logging
import tkinter as tk
from tkinter import messagebox
from threading import Thread
from app.config.settings import ConfigManager
from app.services.ocr_service import OCRService
from app.services.order_service import OrderService
from app.core.utils.log_utils import get_logger
from .logging_ui import add_to_log, init_gui_logger, dispose_gui_logger, GUILogHandler
from .ui_widgets import ProgressReporter
from .error_utils import show_error_dialog, get_error_suggestion
logger = get_logger(__name__)
from .result_previews import show_ocr_result_preview, show_excel_result_preview, show_merge_result_preview
from .user_settings import add_recent_file
from .command_runner import get_running_task, set_running_task
from .file_operations import select_file, select_excel_file, validate_unit_price_against_item_data
def _ask_and_merge_purchase_orders(order_service, log_widget, add_to_recent=False):
"""弹窗询问是否合并采购单,返回合并结果路径或 None。
用于 run_pipeline_directly batch_process_orders_with_status 的共享逻辑
"""
try:
purchase_orders = order_service.get_purchase_orders()
if len(purchase_orders) == 0:
add_to_log(log_widget, "没有找到采购单文件,跳过合并步骤\n", "info")
elif len(purchase_orders) == 1:
add_to_log(log_widget, f"只有1个采购单文件,无需合并: {os.path.basename(purchase_orders[0])}\n", "info")
else:
add_to_log(log_widget, f"找到{len(purchase_orders)}个采购单文件\n", "info")
file_list = "\n".join([f"{os.path.basename(f)}" for f in purchase_orders])
merge_choice = messagebox.askyesnocancel(
"采购单合并选择",
f"发现{len(purchase_orders)}个采购单文件:\n\n{file_list}\n\n是否需要合并这些采购单?\n\n• 选择'':合并所有采购单\n• 选择'':保持文件分离\n• 选择'取消':跳过此步骤",
icon='question'
)
if merge_choice is True:
add_to_log(log_widget, "开始合并采购单...\n", "info")
merge_result = order_service.merge_all_purchase_orders()
if merge_result:
add_to_log(log_widget, "采购单合并完成\n", "success")
if add_to_recent:
try:
add_recent_file(merge_result)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
return merge_result
else:
add_to_log(log_widget, "合并失败\n", "warning")
elif merge_choice is False:
add_to_log(log_widget, "用户选择不合并采购单,保持文件分离\n", "info")
else:
add_to_log(log_widget, "用户取消合并操作\n", "info")
except Exception as e:
add_to_log(log_widget, f"合并过程出现问题: {str(e)}\n", "warning")
return None
def process_single_image_with_status(log_widget, status_bar):
status_bar.set_status("选择图片中...")
file_path = select_file(log_widget, [("图片文件", "*.jpg *.jpeg *.png *.bmp"), ("所有文件", "*.*")], "选择图片")
if not file_path:
status_bar.set_status("操作已取消")
add_to_log(log_widget, "未选择文件,操作已取消\n", "warning")
return
def run_in_thread():
try:
status_bar.set_running(True)
status_bar.set_status("开始处理图片...")
gui_handler = GUILogHandler(log_widget)
gui_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
gui_handler.setFormatter(formatter)
root_logger = logging.getLogger()
for handler in root_logger.handlers[:]:
if isinstance(handler, logging.StreamHandler):
root_logger.removeHandler(handler)
root_logger.addHandler(gui_handler)
root_logger.setLevel(logging.INFO)
ocr_service = OCRService()
add_to_log(log_widget, f"开始处理图片: {file_path}\n", "info")
try:
add_recent_file(file_path)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
excel_path = ocr_service.process_image(file_path)
if excel_path:
add_to_log(log_widget, "图片OCR处理完成\n", "success")
preview_output = f"采购单已保存到: {excel_path}\n"
show_excel_result_preview(preview_output)
try:
add_recent_file(excel_path)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
else:
add_to_log(log_widget, "图片OCR处理失败\n", "error")
except Exception as e:
add_to_log(log_widget, f"处理单个图片时出错: {str(e)}\n", "error")
sugg = get_error_suggestion(str(e))
if sugg:
show_error_dialog("OCR处理错误", str(e), sugg)
finally:
try:
root_logger = logging.getLogger()
for handler in root_logger.handlers[:]:
if isinstance(handler, GUILogHandler):
root_logger.removeHandler(handler)
handler.close()
except Exception as e:
logger.debug(f"清理日志处理器失败: {e}")
status_bar.set_running(False)
status_bar.set_status("就绪")
thread = Thread(target=run_in_thread)
thread.daemon = True
thread.start()
def run_pipeline_directly(log_widget, status_bar):
"""直接运行完整处理流程"""
if get_running_task() is not None:
messagebox.showinfo("任务进行中", "请等待当前任务完成后再执行新的操作。")
return
def run_in_thread():
set_running_task("pipeline")
if status_bar:
status_bar.set_running(True)
status_bar.set_status("开始完整处理流程...")
start_time = datetime.datetime.now()
start_perf = time.perf_counter()
log_widget.configure(state=tk.NORMAL)
log_widget.delete(1.0, tk.END)
log_widget.insert(tk.END, "执行命令: 完整处理流程\n", "command")
log_widget.insert(tk.END, f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n", "time")
log_widget.insert(tk.END, "=" * 50 + "\n\n", "separator")
log_widget.configure(state=tk.DISABLED)
try:
config = ConfigManager()
gui_handler = init_gui_logger(log_widget)
ocr_service = OCRService(config)
order_service = OrderService(config)
reporter = ProgressReporter(status_bar)
reporter.running()
reporter.set("开始OCR批量处理...", 10)
total, success = ocr_service.batch_process(progress_cb=lambda p: reporter.set("OCR处理中...", p))
if total == 0:
add_to_log(log_widget, "没有找到需要处理的图片\n", "warning")
if status_bar:
status_bar.set_status("未找到图片文件")
return
elif success == 0:
add_to_log(log_widget, "OCR处理没有成功处理任何新文件\n", "warning")
else:
add_to_log(log_widget, f"OCR处理完成,共处理 {success}/{total} 个文件\n", "success")
try:
processed_map = {}
config = ConfigManager()
pjson = config.get('Paths', 'processed_record', fallback='data/processed_files.json')
if os.path.exists(pjson):
with open(pjson, 'r', encoding='utf-8') as f:
processed_map = json.load(f)
outputs = list(processed_map.values())
for p in outputs[-10:]:
if p:
add_recent_file(os.path.abspath(p))
except Exception as e:
logger.debug(f"加载已处理文件记录失败: {e}")
reporter.set("开始Excel处理...", 92)
add_to_log(log_widget, "开始Excel处理...\n", "info")
result = order_service.process_excel()
if not result:
add_to_log(log_widget, "Excel处理失败\n", "error")
else:
add_to_log(log_widget, "Excel处理完成\n", "success")
try:
add_recent_file(result)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
try:
validate_unit_price_against_item_data(result, log_widget)
except Exception as e:
logger.debug(f"单价校验失败: {e}")
reporter.set("检查是否需要合并采购单...", 80)
_ask_and_merge_purchase_orders(order_service, log_widget, add_to_recent=True)
end_time = datetime.datetime.now()
duration_sec = max(0.0, time.perf_counter() - start_perf)
add_to_log(log_widget, f"\n{'=' * 50}\n", "separator")
add_to_log(log_widget, "完整处理流程执行完毕!\n", "success")
add_to_log(log_widget, f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}\n", "time")
add_to_log(log_widget, f"耗时: {duration_sec:.2f}\n", "time")
reporter.set("处理完成", 100)
except Exception as e:
add_to_log(log_widget, f"执行过程中发生错误: {str(e)}\n", "error")
import traceback
add_to_log(log_widget, f"详细错误信息: {traceback.format_exc()}\n", "error")
finally:
dispose_gui_logger()
reporter.done()
set_running_task(None)
if status_bar:
status_bar.set_running(False)
status_bar.set_status("就绪")
thread = Thread(target=run_in_thread)
thread.daemon = True
thread.start()
def batch_ocr_with_status(log_widget, status_bar):
"""OCR批量识别"""
def run_in_thread():
try:
reporter = ProgressReporter(status_bar)
reporter.running()
reporter.set("正在进行OCR批量识别...", 10)
add_to_log(log_widget, "开始OCR批量识别\n", "info")
init_gui_logger(log_widget)
ocr_service = OCRService()
result = ocr_service.batch_process()
if result:
add_to_log(log_widget, "OCR批量识别完成\n", "success")
show_ocr_result_preview("OCR批量识别成功完成")
reporter.set("批量识别完成", 100)
try:
processed_map = {}
config = ConfigManager()
pjson = config.get('Paths', 'processed_record', fallback='data/processed_files.json')
if os.path.exists(pjson):
with open(pjson, 'r', encoding='utf-8') as f:
processed_map = json.load(f)
outputs = list(processed_map.values())
for p in outputs[-10:]:
if p:
add_recent_file(p)
inputs = list(processed_map.keys())
for p in inputs[-10:]:
if p:
add_recent_file(p)
except Exception as e:
logger.debug(f"加载已处理文件记录失败: {e}")
else:
add_to_log(log_widget, "OCR批量识别失败\n", "error")
except Exception as e:
add_to_log(log_widget, f"OCR批量识别出错: {str(e)}\n", "error")
sugg = get_error_suggestion(str(e))
if sugg:
show_error_dialog("OCR处理错误", str(e), sugg)
finally:
dispose_gui_logger()
reporter.done()
thread = Thread(target=run_in_thread)
thread.daemon = True
thread.start()
def batch_process_orders_with_status(log_widget, status_bar):
"""批量处理订单(仅Excel处理,包含合并确认)"""
def run_in_thread():
try:
reporter = ProgressReporter(status_bar)
reporter.running()
reporter.set("正在批量处理订单...", 10)
add_to_log(log_widget, "开始批量处理订单\n", "info")
init_gui_logger(log_widget)
order_service = OrderService()
add_to_log(log_widget, "开始Excel处理...\n", "info")
try:
latest_input = order_service.get_latest_excel()
if latest_input:
add_recent_file(latest_input)
except Exception as e:
logger.debug(f"获取最新Excel失败: {e}")
result = order_service.process_excel(progress_cb=lambda p: reporter.set("Excel处理中...", p))
if result:
add_to_log(log_widget, "Excel处理完成\n", "success")
try:
validate_unit_price_against_item_data(result, log_widget)
except Exception as e:
logger.debug(f"单价校验失败: {e}")
reporter.set("检查是否需要合并采购单...", 70)
add_to_log(log_widget, "检查是否需要合并采购单...\n", "info")
_ask_and_merge_purchase_orders(order_service, log_widget)
add_to_log(log_widget, "批量处理订单完成\n", "success")
reporter.set("批量处理订单完成", 100)
show_excel_result_preview(f"采购单已保存到: {result}\n")
try:
add_recent_file(result)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
else:
add_to_log(log_widget, "批量处理订单失败\n", "error")
except Exception as e:
add_to_log(log_widget, f"批量处理订单时出错: {str(e)}\n", "error")
sugg = get_error_suggestion(str(e))
if sugg:
show_error_dialog("Excel处理错误", str(e), sugg)
finally:
dispose_gui_logger()
reporter.done()
thread = Thread(target=run_in_thread)
thread.daemon = True
thread.start()
def merge_orders_with_status(log_widget, status_bar):
"""合并采购单"""
def run_in_thread():
try:
reporter = ProgressReporter(status_bar)
reporter.running()
reporter.set("正在合并采购单...", 10)
add_to_log(log_widget, "开始合并采购单\n", "info")
init_gui_logger(log_widget)
order_service = OrderService()
result = order_service.merge_all_purchase_orders(progress_cb=lambda p: reporter.set("合并处理中...", p))
if result:
add_to_log(log_widget, "采购单合并完成\n", "success")
show_merge_result_preview(f"已保存到: {result}\n")
try:
add_recent_file(result)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
try:
validate_unit_price_against_item_data(result, log_widget)
except Exception as e:
logger.debug(f"单价校验失败: {e}")
else:
add_to_log(log_widget, "采购单合并失败\n", "error")
except Exception as e:
add_to_log(log_widget, f"采购单合并出错: {str(e)}\n", "error")
sugg = get_error_suggestion(str(e))
if sugg:
show_error_dialog("合并错误", str(e), sugg)
finally:
dispose_gui_logger()
reporter.done()
thread = Thread(target=run_in_thread)
thread.daemon = True
thread.start()
def process_excel_file_with_status(log_widget, status_bar):
"""处理Excel文件"""
def run_in_thread():
try:
status_bar.set_running(True)
status_bar.set_status("选择Excel文件中...")
file_path = select_excel_file(log_widget)
if file_path:
status_bar.set_status("开始处理Excel文件...")
add_to_log(log_widget, f"开始处理Excel文件: {file_path}\n", "info")
else:
status_bar.set_status("操作已取消")
add_to_log(log_widget, "未选择文件,操作已取消\n", "warning")
return
init_gui_logger(log_widget)
order_service = OrderService()
if file_path:
try:
add_recent_file(file_path)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
result = order_service.process_excel(file_path, progress_cb=lambda p: status_bar.set_status("Excel处理中...", p))
else:
try:
latest_input = order_service.get_latest_excel()
if latest_input:
add_recent_file(latest_input)
except Exception as e:
logger.debug(f"获取最新Excel失败: {e}")
result = order_service.process_excel(progress_cb=lambda p: status_bar.set_status("Excel处理中...", p))
if result:
add_to_log(log_widget, "Excel文件处理完成\n", "success")
show_excel_result_preview(f"采购单已保存到: {result}\n")
try:
add_recent_file(result)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
try:
validate_unit_price_against_item_data(result, log_widget)
except Exception as e:
logger.debug(f"单价校验失败: {e}")
else:
add_to_log(log_widget, "Excel文件处理失败\n", "error")
except Exception as e:
add_to_log(log_widget, f"Excel文件处理出错: {str(e)}\n", "error")
msg = str(e)
suggestion = None
if 'openpyxl' in msg or 'engine' in msg:
suggestion = "安装依赖:pip install openpyxl"
elif 'xlrd' in msg:
suggestion = "安装依赖:pip install xlrd"
if suggestion:
show_error_dialog("Excel处理错误", msg, suggestion)
finally:
dispose_gui_logger()
status_bar.set_running(False)
status_bar.set_status("就绪")
thread = Thread(target=run_in_thread)
thread.daemon = True
thread.start()
def process_dropped_file(log_widget, status_bar, file_path):
try:
ext = os.path.splitext(file_path)[1].lower()
if ext in ['.jpg', '.jpeg', '.png', '.bmp']:
def _run_img():
try:
reporter = ProgressReporter(status_bar)
reporter.running()
init_gui_logger(log_widget)
add_to_log(log_widget, f"开始一键处理图片: {file_path}\n", "info")
try:
add_recent_file(file_path)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
# 步骤1: OCR识别
reporter.set("OCR识别中...", 10)
ocr_service = OCRService()
excel_path = ocr_service.process_image(file_path)
if not excel_path:
add_to_log(log_widget, "图片OCR处理失败\n", "error")
return
add_to_log(log_widget, f"OCR识别完成: {excel_path}\n", "success")
# 步骤2: Excel处理
reporter.set("Excel处理中...", 40)
order_service = OrderService()
result = order_service.process_excel(excel_path, progress_cb=lambda p: reporter.set("Excel处理中...", p))
if not result:
add_to_log(log_widget, "Excel处理失败\n", "error")
return
add_to_log(log_widget, f"Excel处理完成: {result}\n", "success")
try:
add_recent_file(result)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
try:
validate_unit_price_against_item_data(result, log_widget)
except Exception as e:
logger.debug(f"单价校验失败: {e}")
# 步骤3: 合并采购单
reporter.set("检查合并采购单...", 80)
_ask_and_merge_purchase_orders(order_service, log_widget, add_to_recent=True)
reporter.set("处理完成", 100)
add_to_log(log_widget, "一键处理完成!\n", "success")
finally:
dispose_gui_logger()
reporter.done()
t = Thread(target=_run_img)
t.daemon = True
t.start()
elif ext in ['.xlsx', '.xls']:
def _run_xls():
try:
reporter = ProgressReporter(status_bar)
reporter.running()
init_gui_logger(log_widget)
order_service = OrderService()
add_to_log(log_widget, f"开始一键处理Excel文件: {file_path}\n", "info")
try:
add_recent_file(file_path)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
# 步骤1: Excel处理
reporter.set("Excel处理中...", 20)
result = order_service.process_excel(file_path, progress_cb=lambda p: reporter.set("Excel处理中...", p))
if not result:
add_to_log(log_widget, "Excel文件处理失败\n", "error")
return
add_to_log(log_widget, f"Excel处理完成: {result}\n", "success")
try:
add_recent_file(result)
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
try:
validate_unit_price_against_item_data(result, log_widget)
except Exception as e:
logger.debug(f"单价校验失败: {e}")
# 步骤2: 合并采购单
reporter.set("检查合并采购单...", 80)
_ask_and_merge_purchase_orders(order_service, log_widget, add_to_recent=True)
reporter.set("处理完成", 100)
add_to_log(log_widget, "一键处理完成!\n", "success")
finally:
dispose_gui_logger()
reporter.done()
t = Thread(target=_run_xls)
t.daemon = True
t.start()
else:
add_to_log(log_widget, f"不支持的文件类型: {file_path}\n", "warning")
except Exception as e:
add_to_log(log_widget, f"处理拖拽文件失败: {str(e)}\n", "error")
-33
View File
@@ -1,33 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""条码映射编辑模块"""
from tkinter import messagebox
from app.core.excel.converter import UnitConverter
from app.core.utils.dialog_utils import show_barcode_mapping_dialog
from .logging_ui import add_to_log
def edit_barcode_mappings(log_widget):
"""编辑条码映射配置"""
try:
add_to_log(log_widget, "正在加载条码映射配置...\n", "info")
unit_converter = UnitConverter()
current_mappings = unit_converter.special_barcodes
def save_mappings(new_mappings):
success = unit_converter.update_barcode_mappings(new_mappings)
if success:
add_to_log(log_widget, f"成功保存条码映射配置,共{len(new_mappings)}\n", "success")
else:
add_to_log(log_widget, "保存条码映射配置失败\n", "error")
show_barcode_mapping_dialog(None, save_mappings, current_mappings)
except Exception as e:
add_to_log(log_widget, f"编辑条码映射时出错: {str(e)}\n", "error")
messagebox.showerror("错误", f"编辑条码映射时出错: {str(e)}")
-160
View File
@@ -1,160 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""命令执行器模块"""
import os
import sys
import time
import subprocess
import datetime
import re
import tkinter as tk
from tkinter import messagebox
from threading import Thread
from .logging_ui import LogRedirector
from .result_previews import show_result_preview
# 任务状态跟踪
_RUNNING_TASK = None
def get_running_task():
return _RUNNING_TASK
def set_running_task(val):
global _RUNNING_TASK
_RUNNING_TASK = val
def run_command_with_logging(command, log_widget, status_bar=None, on_complete=None):
"""运行命令并将输出重定向到日志窗口"""
if _RUNNING_TASK is not None:
messagebox.showinfo("任务进行中", "请等待当前任务完成后再执行新的操作。")
return
def run_in_thread():
global _RUNNING_TASK
_RUNNING_TASK = command
if status_bar:
status_bar.set_running(True)
start_time = datetime.datetime.now()
start_perf = time.perf_counter()
log_widget.configure(state=tk.NORMAL)
log_widget.delete(1.0, tk.END)
log_widget.insert(tk.END, f"执行命令: {' '.join(command)}\n", "command")
log_widget.insert(tk.END, f"开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}\n", "time")
log_widget.insert(tk.END, "=" * 50 + "\n\n", "separator")
log_widget.configure(state=tk.DISABLED)
old_stdout = sys.stdout
old_stderr = sys.stderr
log_redirector = LogRedirector(log_widget)
env = os.environ.copy()
try:
from app.config.settings import ConfigManager
cfg = ConfigManager()
env["OCR_OUTPUT_DIR"] = cfg.get_path('Paths', 'output_folder', fallback='data/output', create=True)
env["OCR_INPUT_DIR"] = cfg.get_path('Paths', 'input_folder', fallback='data/input', create=True)
env["OCR_TEMP_DIR"] = cfg.get_path('Paths', 'temp_folder', fallback='data/temp', create=True)
except Exception:
# 回退:使用 exe/脚本所在目录
app_root = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
env["OCR_OUTPUT_DIR"] = os.path.join(app_root, "data", "output")
env["OCR_INPUT_DIR"] = os.path.join(app_root, "data", "input")
env["OCR_TEMP_DIR"] = os.path.join(app_root, "data", "temp")
env["OCR_LOG_LEVEL"] = "DEBUG"
try:
sys.stdout = log_redirector
sys.stderr = log_redirector
print("日志重定向已启动,现在同时输出到终端和GUI")
process = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
universal_newlines=True,
env=env
)
output_data = []
for line in process.stdout:
output_data.append(line)
print(line.rstrip())
if status_bar:
progress = extract_progress_from_log(line)
if progress is not None:
log_widget.after(0, lambda p=progress: status_bar.set_status(f"处理中: {p}%完成", p))
process.wait()
end_time = datetime.datetime.now()
duration_sec = max(0.0, time.perf_counter() - start_perf)
print(f"\n{'=' * 50}")
print(f"执行完毕!返回码: {process.returncode}")
print(f"结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"耗时: {duration_sec:.2f}")
output_text = ''.join(output_data)
is_pipeline = "pipeline" in command
no_merge_files = "未找到采购单文件" in output_text
single_file = "只有1个采购单文件" in output_text
if is_pipeline and (no_merge_files or single_file):
print("完整流程中没有需要合并的文件,但其他步骤执行成功,视为成功完成")
if status_bar:
log_widget.after(0, lambda: status_bar.set_status("处理完成", 100))
log_widget.after(0, lambda: show_result_preview(command, output_text))
else:
if on_complete:
log_widget.after(0, lambda: on_complete(process.returncode, output_text))
elif process.returncode == 0:
if status_bar:
log_widget.after(0, lambda: status_bar.set_status("处理完成", 100))
log_widget.after(0, lambda: show_result_preview(command, output_text))
else:
if status_bar:
log_widget.after(0, lambda: status_bar.set_status(f"处理失败 (返回码: {process.returncode})", 0))
log_widget.after(0, lambda: messagebox.showerror("操作失败", f"处理失败,返回码:{process.returncode}"))
except Exception as e:
print(f"\n执行出错: {str(e)}")
if status_bar:
log_widget.after(0, lambda: status_bar.set_status(f"执行出错: {str(e)}", 0))
log_widget.after(0, lambda: messagebox.showerror("执行错误", f"执行命令时出错: {str(e)}"))
finally:
sys.stdout = old_stdout
sys.stderr = old_stderr
_RUNNING_TASK = None
if status_bar:
log_widget.after(0, lambda: status_bar.set_running(False))
Thread(target=run_in_thread).start()
def extract_progress_from_log(log_line):
"""从日志行中提取进度信息"""
batch_match = re.search(r'处理批次 (\d+)/(\d+)', log_line)
if batch_match:
current = int(batch_match.group(1))
total = int(batch_match.group(2))
return int(current / total * 100)
percent_match = re.search(r'(\d+)%', log_line)
if percent_match:
return int(percent_match.group(1))
return None
-205
View File
@@ -1,205 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""系统设置对话框模块"""
import os
import tkinter as tk
from tkinter import messagebox, filedialog, ttk
from app.config.settings import ConfigManager
from .user_settings import load_user_settings, save_user_settings
from .ui_widgets import center_window
from app.core.utils.dialog_utils import show_cloud_sync_dialog
def show_config_dialog(root, cfg: ConfigManager):
settings = load_user_settings()
dlg = tk.Toplevel(root)
dlg.title("系统设置")
dlg.geometry("700x460")
center_window(dlg)
content = ttk.Frame(dlg)
content.pack(fill=tk.BOTH, expand=True, padx=12, pady=12)
content.columnconfigure(0, weight=1)
# ── 辅助函数 ──
def _add_pair(parent, row, col, label_text, widget, label_width=None):
"""在 parent 的 (row, col*2) 放 label, (row, col*2+1) 放 widget"""
lbl = ttk.Label(parent, text=label_text)
if label_width:
lbl.configure(width=label_width)
lbl.grid(row=row, column=col * 2, sticky='w', padx=(6, 2), pady=3)
widget.grid(row=row, column=col * 2 + 1, sticky='ew', padx=(2, 6), pady=3)
def _make_dir_widget(parent, var, label):
f = ttk.Frame(parent)
e = ttk.Entry(f, textvariable=var)
e.pack(side=tk.LEFT, fill=tk.X, expand=True)
def _select_dir():
d = filedialog.askdirectory(title=f"选择{label}")
if d:
try:
var.set(os.path.relpath(d, os.getcwd()))
except Exception:
var.set(d)
ttk.Button(f, text="选择", command=_select_dir).pack(side=tk.LEFT, padx=4)
return f
# ── 当前值 ──
log_level_val = tk.StringVar(value=settings.get('log_level', 'INFO'))
max_workers_val = tk.StringVar(value=str(settings.get('concurrency_max_workers', cfg.getint('Performance', 'max_workers', 4))))
batch_size_val = tk.StringVar(value=str(settings.get('concurrency_batch_size', cfg.getint('Performance', 'batch_size', 5))))
template_path_val = tk.StringVar(value=settings.get('template_path', os.path.join(cfg.get('Paths', 'template_folder', 'templates'), cfg.get('Templates', 'purchase_order', '银豹-采购单模板.xls'))))
input_dir_val = tk.StringVar(value=settings.get('input_folder', cfg.get('Paths', 'input_folder', 'data/input')))
output_dir_val = tk.StringVar(value=settings.get('output_folder', cfg.get('Paths', 'output_folder', 'data/output')))
result_dir_val = tk.StringVar(value=settings.get('result_folder', 'data/result'))
api_key_val = tk.StringVar(value=settings.get('api_key', cfg.get('API', 'api_key', '')))
secret_key_val = tk.StringVar(value=settings.get('secret_key', cfg.get('API', 'secret_key', '')))
timeout_val = tk.StringVar(value=str(settings.get('timeout', cfg.getint('API', 'timeout', 30))))
max_retries_val = tk.StringVar(value=str(settings.get('max_retries', cfg.getint('API', 'max_retries', 3))))
retry_delay_val = tk.StringVar(value=str(settings.get('retry_delay', cfg.getint('API', 'retry_delay', 2))))
api_url_val = tk.StringVar(value=settings.get('api_url', cfg.get('API', 'api_url', '')))
gitea_url_val = tk.StringVar(value=cfg.get('Gitea', 'base_url', fallback='https://gitea.94kan.cn'))
gitea_owner_val = tk.StringVar(value=cfg.get('Gitea', 'owner', fallback='houhuan'))
gitea_repo_val = tk.StringVar(value=cfg.get('Gitea', 'repo', fallback='yixuan-sync-data'))
gitea_token_val = tk.StringVar(value=cfg.get('Gitea', 'token', fallback=''))
# ═══════════════════════════════════════════════════
# 区块 1: 基本设置
# ═══════════════════════════════════════════════════
f1 = ttk.LabelFrame(content, text=" 基本设置 ", padding=(8, 4))
f1.pack(fill=tk.X, pady=(0, 6))
for c in range(4):
f1.columnconfigure(c, weight=1 if c % 2 == 1 else 0)
lvl = ttk.Combobox(f1, textvariable=log_level_val, values=['DEBUG', 'INFO', 'WARNING', 'ERROR'], state='readonly', width=12)
_add_pair(f1, 0, 0, "日志级别", lvl)
_add_pair(f1, 0, 1, "最大并发", ttk.Entry(f1, textvariable=max_workers_val, width=6))
_add_pair(f1, 1, 0, "批次大小", ttk.Entry(f1, textvariable=batch_size_val, width=6))
# 模板路径(带选择按钮,占右列)
tpl_frame = ttk.Frame(f1)
tpl_entry = ttk.Entry(tpl_frame, textvariable=template_path_val)
tpl_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
def _select_template():
p = filedialog.askopenfilename(title="选择模板文件", filetypes=[("Excel模板", "*.xls *.xlsx"), ("所有文件", "*.*")])
if p:
try:
template_path_val.set(os.path.relpath(p, os.getcwd()))
except Exception:
template_path_val.set(p)
ttk.Button(tpl_frame, text="选择", command=_select_template).pack(side=tk.LEFT, padx=4)
_add_pair(f1, 1, 1, "采购模板", tpl_frame)
_add_pair(f1, 2, 0, "输入目录", _make_dir_widget(f1, input_dir_val, "输入目录"))
_add_pair(f1, 2, 1, "输出目录", _make_dir_widget(f1, output_dir_val, "输出目录"))
_add_pair(f1, 3, 0, "结果目录", _make_dir_widget(f1, result_dir_val, "结果目录"))
# ═══════════════════════════════════════════════════
# 区块 2: API 设置
# ═══════════════════════════════════════════════════
f2 = ttk.LabelFrame(content, text=" API 设置 ", padding=(8, 4))
f2.pack(fill=tk.X, pady=(0, 6))
for c in range(4):
f2.columnconfigure(c, weight=1 if c % 2 == 1 else 0)
_add_pair(f2, 0, 0, "API Key", ttk.Entry(f2, textvariable=api_key_val))
secret_entry = ttk.Entry(f2, textvariable=secret_key_val, show='*')
_add_pair(f2, 0, 1, "Secret Key", secret_entry)
_add_pair(f2, 1, 0, "Timeout", ttk.Entry(f2, textvariable=timeout_val, width=6))
_add_pair(f2, 1, 1, "Max Retries", ttk.Entry(f2, textvariable=max_retries_val, width=6))
_add_pair(f2, 2, 0, "Retry Delay", ttk.Entry(f2, textvariable=retry_delay_val, width=6))
_add_pair(f2, 2, 1, "API URL", ttk.Entry(f2, textvariable=api_url_val))
# ═══════════════════════════════════════════════════
# 区块 3: 云端同步 (Gitea)
# ═══════════════════════════════════════════════════
f3 = ttk.LabelFrame(content, text=" 云端同步 (Gitea) ", padding=(8, 4))
f3.pack(fill=tk.X, pady=(0, 8))
for c in range(4):
f3.columnconfigure(c, weight=1 if c % 2 == 1 else 0)
_add_pair(f3, 0, 0, "Gitea 地址", ttk.Entry(f3, textvariable=gitea_url_val))
_add_pair(f3, 0, 1, "仓库所有者", ttk.Entry(f3, textvariable=gitea_owner_val))
_add_pair(f3, 1, 0, "仓库名称", ttk.Entry(f3, textvariable=gitea_repo_val))
_add_pair(f3, 1, 1, "Access Token", ttk.Entry(f3, textvariable=gitea_token_val, show='*'))
# ═══════════════════════════════════════════════════
# 按钮区
# ═══════════════════════════════════════════════════
btns = ttk.Frame(content)
btns.pack(fill=tk.X, pady=(4, 0))
def save_settings():
try:
s = load_user_settings()
s['log_level'] = log_level_val.get()
s['concurrency_max_workers'] = int(max_workers_val.get() or '4')
s['concurrency_batch_size'] = int(batch_size_val.get() or '5')
tp = template_path_val.get()
inp = input_dir_val.get()
outp = output_dir_val.get()
resp = result_dir_val.get()
try:
if tp:
tp = os.path.relpath(tp, os.getcwd()) if os.path.isabs(tp) else tp
if inp:
inp = os.path.relpath(inp, os.getcwd()) if os.path.isabs(inp) else inp
if outp:
outp = os.path.relpath(outp, os.getcwd()) if os.path.isabs(outp) else outp
if resp:
resp = os.path.relpath(resp, os.getcwd()) if os.path.isabs(resp) else resp
except Exception:
pass
s['template_path'] = tp
s['input_folder'] = inp
s['output_folder'] = outp
s['result_folder'] = resp
save_user_settings(s)
try:
from app.core.utils.log_utils import set_log_level
set_log_level(s['log_level'])
except Exception:
pass
try:
tpl_path = s['template_path']
tpl_dir = os.path.dirname(tpl_path)
tpl_name = os.path.basename(tpl_path)
cfg.update('Paths', 'template_folder', tpl_dir)
cfg.update('Templates', 'purchase_order', tpl_name)
try:
cfg.update('Paths', 'template_file', os.path.join(tpl_dir, tpl_name))
except Exception:
pass
cfg.update('Paths', 'input_folder', s['input_folder'])
cfg.update('Paths', 'output_folder', s['output_folder'])
cfg.update('Performance', 'max_workers', s['concurrency_max_workers'])
cfg.update('Performance', 'batch_size', s['concurrency_batch_size'])
cfg.update('API', 'api_key', api_key_val.get())
cfg.update('API', 'secret_key', secret_key_val.get())
cfg.update('API', 'timeout', timeout_val.get())
cfg.update('API', 'max_retries', max_retries_val.get())
cfg.update('API', 'retry_delay', retry_delay_val.get())
cfg.update('API', 'api_url', api_url_val.get())
cfg.update('Gitea', 'base_url', gitea_url_val.get())
cfg.update('Gitea', 'owner', gitea_owner_val.get())
cfg.update('Gitea', 'repo', gitea_repo_val.get())
cfg.update('Gitea', 'token', gitea_token_val.get())
cfg.save_config()
except Exception:
pass
messagebox.showinfo("设置已保存", "系统设置已更新并保存")
dlg.destroy()
except Exception as e:
messagebox.showerror("保存失败", str(e))
ttk.Button(btns, text="云端同步", command=lambda: show_cloud_sync_dialog(dlg)).pack(side=tk.LEFT)
ttk.Button(btns, text="取消", command=dlg.destroy).pack(side=tk.RIGHT)
ttk.Button(btns, text="保存", command=save_settings).pack(side=tk.RIGHT, padx=6)
-41
View File
@@ -1,41 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""错误处理工具模块"""
from tkinter import messagebox
from typing import Optional
from app.core.utils.log_utils import get_logger
logger = get_logger(__name__)
def show_error_dialog(title: str, message: str, suggestion: Optional[str] = None):
try:
full_msg = message
if suggestion:
full_msg = f"{message}\n\n建议操作:\n- {suggestion}"
messagebox.showerror(title, full_msg)
except Exception as e:
logger.debug(f"显示错误对话框失败: {e}")
def get_error_suggestion(message: str) -> Optional[str]:
msg = (message or "").lower()
if 'openpyxl' in msg or ('engine' in msg and 'xlsx' in msg):
return '安装依赖:pip install openpyxl'
if 'xlrd' in msg or ('engine' in msg and 'xls' in msg):
return '安装依赖:pip install xlrd'
if 'timeout' in msg or 'timed out' in msg:
return '检查网络,增大API超时时间或稍后重试'
if 'invalid access_token' in msg or 'access token' in msg:
return '刷新百度OCR令牌或检查api_key/secret_key'
if '429' in msg or 'too many requests' in msg:
return '降低识别频率或稍后重试'
if '模板文件不存在' in msg or ('no such file' in msg and '模板' in msg):
return '在系统设置中选择正确的模板文件路径'
if '没有找到采购单' in msg or '未在' in msg and '找到采购单' in msg:
return '确认result目录内存在采购单文件'
if 'permission denied' in msg:
return '以管理员权限运行或更改目录写入权限'
return None
-205
View File
@@ -1,205 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""文件与目录操作模块"""
import os
import json
import tkinter as tk
from tkinter import messagebox, filedialog, scrolledtext
from .logging_ui import add_to_log
from .ui_widgets import center_window
from app.config.settings import ConfigManager
def select_file(log_widget, file_types=None, title="选择文件"):
"""通用文件选择对话框"""
if file_types is None:
file_types = [("所有文件", "*.*")]
file_path = filedialog.askopenfilename(title=title, filetypes=file_types)
if file_path:
add_to_log(log_widget, f"已选择文件: {file_path}\n", "info")
return file_path
def select_excel_file(log_widget):
"""选择Excel文件"""
return select_file(
log_widget,
[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")],
"选择Excel文件"
)
def ensure_directories():
"""确保必要的目录结构存在"""
config = ConfigManager()
directories = [
config.get_path('Paths', 'input_folder', fallback='data/input', create=True),
config.get_path('Paths', 'output_folder', fallback='data/output', create=True),
config.get_path('Paths', 'result_folder', fallback='data/result', create=True),
config.get_path('Paths', 'temp_folder', fallback='data/temp', create=True),
os.path.join(config.app_root, 'logs')
]
for directory in directories:
if not os.path.exists(directory):
os.makedirs(directory, exist_ok=True)
print(f"创建目录: {directory}")
def clean_cache(log_widget):
"""清除处理缓存"""
from .command_runner import set_running_task
try:
config = ConfigManager()
processed_record = config.get_path('Paths', 'processed_record', fallback='data/processed_files.json')
output_folder = config.get_path('Paths', 'output_folder', fallback='data/output')
cache_files = [
processed_record,
os.path.join(output_folder, "processed_files.json"),
os.path.join(output_folder, "merged_files.json")
]
for cache_file in cache_files:
if os.path.exists(cache_file):
os.remove(cache_file)
add_to_log(log_widget, f"已清除缓存文件: {cache_file}\n", "success")
temp_dir = config.get_path('Paths', 'temp_folder', fallback='data/temp')
if os.path.exists(temp_dir):
for file in os.listdir(temp_dir):
file_path = os.path.join(temp_dir, file)
try:
if os.path.isfile(file_path):
os.remove(file_path)
add_to_log(log_widget, f"已清除临时文件: {file_path}\n", "info")
except Exception as e:
add_to_log(log_widget, f"清除文件时出错: {file_path}, 错误: {str(e)}\n", "error")
log_dir = os.path.join(config.app_root, 'logs')
if os.path.exists(log_dir):
for file in os.listdir(log_dir):
if file.endswith(".active"):
file_path = os.path.join(log_dir, file)
try:
os.remove(file_path)
add_to_log(log_widget, f"已清除活动日志标记: {file_path}\n", "info")
except Exception as e:
add_to_log(log_widget, f"清除文件时出错: {file_path}, 错误: {str(e)}\n", "error")
set_running_task(None)
add_to_log(log_widget, "缓存清除完成,系统将重新处理所有文件\n", "success")
messagebox.showinfo("缓存清除", "缓存已清除,系统将重新处理所有文件。")
except Exception as e:
add_to_log(log_widget, f"清除缓存时出错: {str(e)}\n", "error")
messagebox.showerror("错误", f"清除缓存时出错: {str(e)}")
def open_result_directory():
try:
config = ConfigManager()
result_dir = config.get_path('Paths', 'result_folder', fallback='data/result', create=True)
os.startfile(result_dir)
except Exception as e:
messagebox.showerror("错误", f"无法打开结果目录: {str(e)}")
def _open_directory_from_settings(config_key, default_path, label):
"""通用的从配置读取路径并打开目录"""
try:
config = ConfigManager()
path = config.get_path('Paths', config_key, fallback=default_path, create=True)
os.startfile(path)
except Exception as e:
messagebox.showerror("错误", f"无法打开{label}: {str(e)}")
def open_input_directory_from_settings():
_open_directory_from_settings('input_folder', 'data/input', '输入目录')
def open_output_directory_from_settings():
_open_directory_from_settings('output_folder', 'data/output', '输出目录')
def open_result_directory_from_settings():
_open_directory_from_settings('result_folder', 'data/result', '结果目录')
def clean_data_files(log_widget):
"""清理数据文件(仅清理input和output目录)"""
try:
if not messagebox.askyesno("确认清理", "确定要清理input和output目录的文件吗?这将删除所有输入和输出数据。"):
add_to_log(log_widget, "操作已取消\n", "info")
return
config = ConfigManager()
files_cleaned = 0
input_dir = config.get_path('Paths', 'input_folder', fallback='data/input')
if os.path.exists(input_dir):
for file in os.listdir(input_dir):
file_path = os.path.join(input_dir, file)
if os.path.isfile(file_path):
os.remove(file_path)
files_cleaned += 1
add_to_log(log_widget, "已清理input目录\n", "info")
output_dir = config.get_path('Paths', 'output_folder', fallback='data/output')
if os.path.exists(output_dir):
for file in os.listdir(output_dir):
file_path = os.path.join(output_dir, file)
if os.path.isfile(file_path):
os.remove(file_path)
files_cleaned += 1
add_to_log(log_widget, "已清理output目录\n", "info")
add_to_log(log_widget, f"清理完成,共清理 {files_cleaned} 个文件\n", "success")
messagebox.showinfo("清理完成", f"已成功清理 {files_cleaned} 个文件")
except Exception as e:
add_to_log(log_widget, f"清理数据文件时出错: {str(e)}\n", "error")
messagebox.showerror("错误", f"清理数据文件时出错: {str(e)}")
def clean_result_files(log_widget):
try:
if not messagebox.askyesno("确认清理", "确定要清理result目录的文件吗?这将删除所有已生成的采购单文件。"):
add_to_log(log_widget, "操作已取消\n", "info")
return
config = ConfigManager()
count = 0
result_dir = config.get_path('Paths', 'result_folder', fallback='data/result')
if os.path.exists(result_dir):
for file in os.listdir(result_dir):
file_path = os.path.join(result_dir, file)
if os.path.isfile(file_path):
os.remove(file_path)
count += 1
add_to_log(log_widget, f"已清理result目录,共 {count} 个文件\n", "success")
messagebox.showinfo("清理完成", f"已清理result目录 {count} 个文件")
except Exception as e:
add_to_log(log_widget, f"清理result目录时出错: {str(e)}\n", "error")
messagebox.showerror("错误", f"清理result目录时出错: {str(e)}")
def validate_unit_price_against_item_data(result_path: str, log_widget=None):
try:
from app.services.order_service import OrderService
service = OrderService()
bad_results = service.validate_unit_price(result_path)
if bad_results:
display_count = min(len(bad_results), 10)
msg = f"存在{len(bad_results)}条单价与商品资料进货价差异超过1元:\n" + "\n".join(bad_results[:display_count])
if len(bad_results) > 10:
msg += f"\n...(其余 {len(bad_results) - 10} 条已省略)"
messagebox.showwarning("单价校验提示", msg)
if log_widget is not None:
add_to_log(log_widget, f"单价校验发现{len(bad_results)}条差异>1元\n", "warning")
else:
if log_widget is not None:
add_to_log(log_widget, "单价校验通过(差异<=1元)\n", "success")
except Exception as e:
if log_widget is not None:
add_to_log(log_widget, f"单价校验出错: {str(e)}\n", "error")
-126
View File
@@ -1,126 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""GUI日志处理模块"""
import logging
import queue
import sys
import tkinter as tk
# 全局日志队列,用于异步更新UI
LOG_QUEUE = queue.Queue()
class LogRedirector:
"""日志重定向器,用于捕获命令输出并显示到界面"""
def __init__(self, text_widget):
self.text_widget = text_widget
self.buffer = ""
self.terminal = sys.__stdout__
def write(self, string):
self.buffer += string
self.terminal.write(string)
self.text_widget.after(0, self.update_text_widget)
def update_text_widget(self):
self.text_widget.configure(state=tk.NORMAL)
if self.buffer.strip():
if any(marker in self.buffer.lower() for marker in ["错误", "error", "失败", "异常", "exception"]):
self.text_widget.insert(tk.END, self.buffer, "error")
elif any(marker in self.buffer.lower() for marker in ["警告", "warning"]):
self.text_widget.insert(tk.END, self.buffer, "warning")
elif any(marker in self.buffer.lower() for marker in ["成功", "success", "完成", "成功处理"]):
self.text_widget.insert(tk.END, self.buffer, "success")
elif any(marker in self.buffer.lower() for marker in ["info", "信息", "开始", "处理中"]):
self.text_widget.insert(tk.END, self.buffer, "info")
else:
self.text_widget.insert(tk.END, self.buffer, "normal")
else:
self.text_widget.insert(tk.END, self.buffer)
self.text_widget.see(tk.END)
self.text_widget.configure(state=tk.DISABLED)
self.buffer = ""
def flush(self):
self.terminal.flush()
class GUILogHandler(logging.Handler):
"""自定义日志处理器,将日志放入队列,由GUI主线程定时消费"""
def __init__(self, text_widget):
super().__init__()
self.text_widget = text_widget
def emit(self, record):
try:
msg = self.format(record)
if record.levelno >= logging.ERROR:
tag = "error"
elif record.levelno >= logging.WARNING:
tag = "warning"
elif record.levelno >= logging.INFO:
tag = "info"
else:
tag = "normal"
LOG_QUEUE.put((msg + "\n", tag))
except Exception:
self.handleError(record)
def poll_log_queue(text_widget):
"""定期从队列中读取日志并更新UI"""
try:
updated = False
while not LOG_QUEUE.empty():
msg, tag = LOG_QUEUE.get_nowait()
text_widget.configure(state=tk.NORMAL)
text_widget.insert(tk.END, msg, tag)
updated = True
if updated:
text_widget.see(tk.END)
text_widget.configure(state=tk.DISABLED)
except Exception:
pass
finally:
text_widget.after(100, lambda: poll_log_queue(text_widget))
def init_gui_logger(text_widget, level=logging.INFO):
handler = GUILogHandler(text_widget)
handler.setLevel(level)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
root_logger = logging.getLogger()
for h in root_logger.handlers[:]:
if isinstance(h, logging.StreamHandler):
root_logger.removeHandler(h)
if not any(isinstance(h, GUILogHandler) for h in root_logger.handlers):
root_logger.addHandler(handler)
root_logger.setLevel(level)
return handler
def dispose_gui_logger():
root_logger = logging.getLogger()
for handler in root_logger.handlers[:]:
if isinstance(handler, GUILogHandler):
root_logger.removeHandler(handler)
try:
handler.close()
except Exception:
pass
def add_to_log(log_widget, text, tag="normal"):
"""向日志队列添加文本,由 poll_log_queue 消费并更新 UI"""
if log_widget is None:
print(f"[{tag}] {text}", end="")
return
LOG_QUEUE.put((text, tag))
-501
View File
@@ -1,501 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""主窗口模块"""
import os
import sys
import subprocess
import tkinter as tk
from tkinter import messagebox, filedialog, scrolledtext
from app.config.settings import ConfigManager
from app.core.utils.log_utils import set_log_level
from .theme import THEMES, get_theme_mode, set_theme_mode, create_modern_button, create_card_frame
from .logging_ui import add_to_log, poll_log_queue
from .ui_widgets import StatusBar
from .user_settings import (
load_user_settings, save_user_settings, refresh_recent_list_widget,
_extract_path_from_recent_item, clear_recent_files, RECENT_LIST_WIDGET,
)
from .file_operations import (
ensure_directories, open_result_directory, clean_cache,
clean_data_files, clean_result_files,
)
from .action_handlers import (
process_single_image_with_status, run_pipeline_directly,
batch_ocr_with_status, batch_process_orders_with_status,
merge_orders_with_status, process_excel_file_with_status,
process_dropped_file,
)
from .memory_editor import show_memory_editor
from .config_dialog import show_config_dialog
from .barcode_editor import edit_barcode_mappings
from .shortcuts import bind_keyboard_shortcuts
from app.core.utils.dialog_utils import show_cloud_sync_dialog
def _init_window():
"""初始化窗口、主题和设置,返回 (root, theme, settings, dnd_supported)"""
ensure_directories()
dnd_supported = False
try:
from tkinterdnd2 import TkinterDnD, DND_FILES
root = TkinterDnD.Tk()
dnd_supported = True
except Exception:
root = tk.Tk()
settings = load_user_settings()
theme_mode = settings.get('theme_mode', get_theme_mode())
set_theme_mode(theme_mode)
try:
cfg_for_title = ConfigManager()
ver = cfg_for_title.get('App', 'version', fallback='dev')
root.title(f"益选-OCR订单处理系统 v{ver} by 欢欢欢")
except Exception:
root.title("益选-OCR订单处理系统 by 欢欢欢")
root.geometry("900x600")
settings['window_size'] = "900x600"
theme = THEMES[get_theme_mode()]
root.configure(bg=theme["bg"])
try:
log_level = settings.get('log_level')
if log_level:
set_log_level(log_level)
concurrency = settings.get('concurrency_max_workers')
if concurrency:
cfg = ConfigManager()
cfg.update('Performance', 'max_workers', str(concurrency))
cfg.save_config()
except Exception:
pass
try:
root.iconbitmap(default="")
except Exception:
pass
return root, theme, settings, dnd_supported
def _create_left_panel(content_frame, theme, log_text, status_bar):
"""创建左侧面板:完整流程、OCR处理、Excel处理、最近文件"""
left_panel = create_card_frame(content_frame)
left_panel.pack(side=tk.LEFT, fill=tk.BOTH, expand=False, padx=(0, 5), pady=5)
left_panel.configure(width=160)
panel_content = tk.Frame(left_panel, bg=theme["card_bg"])
panel_content.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10))
# 完整流程区
pipeline_section = tk.LabelFrame(
panel_content, text="完整流程", bg=theme["card_bg"], fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
)
pipeline_section.pack(fill=tk.X, pady=(0, 8))
pipeline_frame = tk.Frame(pipeline_section, bg=theme["card_bg"])
pipeline_frame.pack(fill=tk.X, padx=8, pady=6)
create_modern_button(pipeline_frame, "一键处理", lambda: run_pipeline_directly(log_text, status_bar), "primary", px_width=150, px_height=32).pack(anchor='w', pady=3)
# OCR处理区
core_section = tk.LabelFrame(
panel_content, text="OCR处理", bg=theme["card_bg"], fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
)
core_section.pack(fill=tk.X, pady=(0, 8))
core_buttons_frame = tk.Frame(core_section, bg=theme["card_bg"])
core_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
core_row1 = tk.Frame(core_buttons_frame, bg=theme["card_bg"])
core_row1.pack(fill=tk.X, pady=3)
create_modern_button(core_row1, "批量识别", lambda: batch_ocr_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(0, 3))
create_modern_button(core_row1, "单个识别", lambda: process_single_image_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
# Excel处理区
ocr_section = tk.LabelFrame(
panel_content, text="Excel处理", bg=theme["card_bg"], fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
)
ocr_section.pack(fill=tk.X, pady=(0, 8))
ocr_buttons_frame = tk.Frame(ocr_section, bg=theme["card_bg"])
ocr_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
ocr_row1 = tk.Frame(ocr_buttons_frame, bg=theme["card_bg"])
ocr_row1.pack(fill=tk.X, pady=3)
create_modern_button(ocr_row1, "批量处理", lambda: batch_process_orders_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(0, 3))
create_modern_button(ocr_row1, "单个处理", lambda: process_excel_file_with_status(log_text, status_bar), "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
# 最近文件区
_create_recent_files_section(panel_content, theme, log_text)
def _create_recent_files_section(parent, theme, log_text):
"""创建最近文件列表区域"""
recent_section = tk.LabelFrame(
parent, text="最近文件", bg=theme["card_bg"], fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
)
recent_section.pack(fill=tk.BOTH, pady=(0, 12))
recent_frame = tk.Frame(recent_section, bg=theme["card_bg"])
recent_frame.pack(fill=tk.BOTH, padx=8, pady=6)
recent_top = tk.Frame(recent_frame, bg=theme["card_bg"])
recent_top.pack(fill=tk.X)
def _resize_recent_top(e):
try:
h = max(int(e.height * 0.85), 180)
recent_top.configure(height=h)
except Exception:
pass
try:
recent_top.pack_propagate(False)
except Exception:
pass
recent_frame.bind('<Configure>', _resize_recent_top)
recent_rect = tk.Frame(recent_top, bg=theme["card_bg"], highlightbackground=theme["border"], highlightthickness=1)
recent_rect.pack(fill=tk.BOTH, expand=True)
recent_list = tk.Listbox(recent_rect, height=20)
recent_scrollbar = tk.Scrollbar(recent_rect)
recent_list.configure(yscrollcommand=recent_scrollbar.set)
recent_scrollbar.configure(command=recent_list.yview)
recent_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
recent_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
import app.ui.user_settings as _us_mod
_us_mod.RECENT_LIST_WIDGET = recent_list
def _open_selected_event(evt=None):
try:
idxs = recent_list.curselection()
if not idxs:
return
p = _extract_path_from_recent_item(recent_list.get(idxs[0]))
if os.path.exists(p):
os.startfile(p)
else:
messagebox.showwarning("文件不存在", p)
except Exception as e:
messagebox.showerror("打开失败", str(e))
recent_list.bind('<Double-Button-1>', _open_selected_event)
refresh_recent_list_widget()
rf_btns = tk.Frame(recent_frame, bg=theme["card_bg"])
rf_btns.pack(fill=tk.X, pady=6)
def clear_list():
clear_recent_files()
recent_list.delete(0, tk.END)
create_modern_button(rf_btns, "清空列表", clear_list, "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
def purge_invalid():
try:
kept = []
for i in range(recent_list.size()):
item = recent_list.get(i)
p = _extract_path_from_recent_item(item)
if os.path.exists(p):
kept.append(p)
try:
kept_sorted = sorted(kept, key=lambda p: os.path.getmtime(p), reverse=True)
except Exception:
kept_sorted = kept
s = load_user_settings()
s['recent_files'] = kept_sorted
save_user_settings(s)
recent_list.delete(0, tk.END)
for i, p in enumerate(s['recent_files'][:recent_list.size() or len(s['recent_files'])], start=1):
recent_list.insert(tk.END, f"{i}. {p}")
refresh_recent_list_widget()
add_to_log(log_text, "已清理无效的最近文件条目\n", "success")
except Exception as e:
messagebox.showerror("清理失败", str(e))
create_modern_button(rf_btns, "清理无效", purge_invalid, "primary", px_width=72, px_height=32).pack(side=tk.LEFT, padx=(3, 0))
def _create_right_panel(content_frame, theme, log_text, root):
"""创建右侧面板:快捷操作、系统设置"""
right_panel = create_card_frame(content_frame)
right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=False, padx=(5, 0), pady=5)
right_panel.configure(width=380)
right_panel_content = tk.Frame(right_panel, bg=theme["card_bg"])
right_panel_content.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10))
# 工具功能区
tools_section = tk.LabelFrame(
right_panel_content, text="快捷操作", bg=theme["card_bg"], fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
)
tools_section.pack(fill=tk.X, pady=(0, 8))
tools_buttons_frame = tk.Frame(tools_section, bg=theme["card_bg"])
tools_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
tk.Frame(tools_buttons_frame, bg=theme["card_bg"]).pack(fill=tk.X, pady=3)
create_modern_button(tools_buttons_frame, "打开结果目录", lambda: open_result_directory(), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(tools_buttons_frame, "打开输出目录", lambda: os.startfile(ConfigManager().get_path('Paths', 'output_folder', fallback='data/output', create=True)), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(tools_buttons_frame, "打开输入目录", lambda: os.startfile(ConfigManager().get_path('Paths', 'input_folder', fallback='data/input', create=True)), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(tools_buttons_frame, "合并订单", lambda: merge_orders_with_status(log_text, StatusBar(root)), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(tools_buttons_frame, "清除缓存", lambda: clean_cache(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(tools_buttons_frame, "清理input/out文件", lambda: clean_data_files(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(tools_buttons_frame, "清理result文件", lambda: clean_result_files(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
# 系统设置区
settings_section = tk.LabelFrame(
right_panel_content, text="系统设置", bg=theme["card_bg"], fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
)
settings_section.pack(fill=tk.X, pady=(0, 8))
settings_buttons_frame = tk.Frame(settings_section, bg=theme["card_bg"])
settings_buttons_frame.pack(fill=tk.X, padx=8, pady=6)
create_modern_button(settings_buttons_frame, "系统设置", lambda: show_config_dialog(root, ConfigManager()), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(settings_buttons_frame, "条码映射", lambda: edit_barcode_mappings(log_text), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(settings_buttons_frame, "云端同步", lambda: show_cloud_sync_dialog(root), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
create_modern_button(settings_buttons_frame, "商品记忆库", lambda: show_memory_editor(root), "primary", px_width=132, px_height=32).pack(anchor='w', pady=3)
def _setup_drag_area(mid_container, theme, dnd_supported, log_text, status_bar):
"""创建拖拽/点击选择文件区域"""
drag_panel = create_card_frame(mid_container)
drag_panel.pack(side=tk.TOP, fill=tk.X, padx=(5, 5), pady=(0, 5))
drag_panel_content = tk.Frame(drag_panel, bg=theme["card_bg"])
drag_panel_content.pack(fill=tk.X, padx=10, pady=6)
dnd_section = tk.LabelFrame(
drag_panel_content, bg=theme["card_bg"], fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold"), relief="flat", borderwidth=0
)
dnd_section.pack(fill=tk.X, pady=(0, 0))
dnd_frame = tk.Frame(dnd_section, bg=theme["card_bg"], highlightthickness=1, highlightbackground=theme["border"])
dnd_frame.configure(height=60)
dnd_frame.pack(fill=tk.X, padx=8, pady=6)
try:
dnd_frame.pack_propagate(False)
except Exception:
pass
def _set_highlight(active: bool):
try:
dnd_frame.configure(highlightbackground=theme["info"] if active else theme["border"])
except Exception:
pass
dnd_frame.bind('<Enter>', lambda e: _set_highlight(True))
dnd_frame.bind('<Leave>', lambda e: _set_highlight(False))
msg_row = tk.Frame(dnd_frame, bg=theme["card_bg"])
msg_row.pack(fill=tk.X)
if dnd_supported:
tk.Label(
msg_row, text="拖拽已启用:拖拽或点击此区域选择文件",
bg=theme["card_bg"], fg="#999999", justify="center"
).pack(fill=tk.X)
else:
tk.Label(
msg_row, text="点击此区域选择文件;可安装拖拽支持",
bg=theme["card_bg"], fg="#999999", justify="center"
).pack(fill=tk.X)
if not dnd_supported:
btn_row = tk.Frame(dnd_frame, bg=theme["card_bg"])
btn_row.pack(fill=tk.X)
is_frozen = getattr(sys, 'frozen', False)
def copy_install():
try:
mid_container.winfo_toplevel().clipboard_clear()
mid_container.winfo_toplevel().clipboard_append("pip install tkinterdnd2")
messagebox.showinfo("已复制", "已复制安装命令:pip install tkinterdnd2")
except Exception as e:
messagebox.showwarning("复制失败", str(e))
if is_frozen:
tk.Label(
btn_row, text="EXE版不支持运行时安装,请用源码版安装后重新打包",
bg=theme["card_bg"], fg="#999999", font=("Microsoft YaHei UI", 8)
).pack(side=tk.RIGHT, padx=4)
else:
def install_and_restart():
try:
add_to_log(log_text, "开始安装拖拽支持库 tkinterdnd2...\n", "info")
cmd = [sys.executable, "-m", "pip", "install", "tkinterdnd2"]
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
add_to_log(log_text, result.stdout + "\n", "info")
add_to_log(log_text, "安装成功,准备重启程序以启用拖拽...\n", "success")
if messagebox.askyesno("安装完成", "已安装拖拽支持,是否立即重启应用?"):
os.execl(sys.executable, sys.executable, *sys.argv)
except subprocess.CalledProcessError as e:
add_to_log(log_text, f"安装失败: {e.stderr}\n", "error")
messagebox.showerror("安装失败", f"安装输出:\n{e.stderr}")
except Exception as e:
add_to_log(log_text, f"安装失败: {str(e)}\n", "error")
messagebox.showerror("安装失败", str(e))
create_modern_button(btn_row, "一键安装拖拽", install_and_restart, "primary", px_width=132, px_height=28).pack(side=tk.RIGHT, padx=(3, 0))
create_modern_button(btn_row, "复制安装命令", copy_install, "primary", px_width=132, px_height=28).pack(side=tk.RIGHT)
# 点击拖拽框选择文件
def _click_select(evt=None):
try:
files = filedialog.askopenfilenames(
title="选择图片或Excel文件",
filetypes=[
("支持文件", "*.xlsx *.xls *.jpg *.jpeg *.png *.bmp"),
("Excel", "*.xlsx *.xls"),
("图片", "*.jpg *.jpeg *.png *.bmp"),
("所有文件", "*.*"),
]
)
if not files:
return
for p in files:
process_dropped_file(log_text, status_bar, p)
except Exception as e:
messagebox.showerror("选择失败", str(e))
dnd_frame.bind('<Button-1>', _click_select)
msg_row.bind('<Button-1>', _click_select)
if dnd_supported:
def _on_drop(event):
try:
data = event.data
paths = []
buf = ""
in_brace = False
for ch in data:
if ch == '{':
in_brace = True
buf = ""
elif ch == '}':
in_brace = False
paths.append(buf)
buf = ""
elif ch == ' ' and not in_brace:
if buf:
paths.append(buf)
buf = ""
else:
buf += ch
if buf:
paths.append(buf)
for p in paths:
process_dropped_file(log_text, status_bar, p)
except Exception as e:
add_to_log(log_text, f"拖拽处理失败: {str(e)}\n", "error")
try:
from tkinterdnd2 import DND_FILES
dnd_frame.drop_target_register(DND_FILES)
dnd_frame.dnd_bind('<<Drop>>', _on_drop)
except Exception:
pass
def _create_log_panel(mid_container, theme):
"""创建中间日志面板,返回 log_text widget"""
log_panel = create_card_frame(mid_container, "处理日志")
log_panel.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=(5, 5), pady=5)
log_text = scrolledtext.ScrolledText(
log_panel, wrap=tk.WORD, width=68, height=26,
bg=theme["log_bg"], fg=theme["log_fg"],
font=("Consolas", 9), state=tk.DISABLED,
relief="flat", borderwidth=0
)
log_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10))
log_text.tag_configure("command", foreground=theme["info"], font=("Consolas", 9, "bold"))
log_text.tag_configure("time", foreground=theme["secondary_bg"], font=("Consolas", 8))
log_text.tag_configure("separator", foreground=theme["border"])
log_text.tag_configure("success", foreground=theme["success"], font=("Consolas", 9, "bold"))
log_text.tag_configure("error", foreground=theme["error"], font=("Consolas", 9, "bold"))
log_text.tag_configure("warning", foreground=theme["warning"], font=("Consolas", 9, "bold"))
log_text.tag_configure("info", foreground=theme["info"], font=("Consolas", 9))
poll_log_queue(log_text)
try:
_ver = ConfigManager().get('App', 'version', fallback='')
_ver_str = f" v{_ver}" if _ver else ""
except Exception:
_ver_str = ""
add_to_log(log_text, f"欢迎使用 益选-OCR订单处理系统{_ver_str}\n", "success")
add_to_log(log_text, "系统已就绪,请选择相应功能进行操作。\n\n", "info")
add_to_log(log_text, "功能说明:\n", "command")
add_to_log(log_text, "• 完整处理流程:一键完成OCR识别和Excel处理\n", "info")
add_to_log(log_text, "• 批量处理订单:批量处理多个订单文件\n", "info")
add_to_log(log_text, "• 处理烟草订单:专门处理烟草类订单\n", "info")
add_to_log(log_text, "• 合并订单:将多个订单合并为一个文件\n\n", "info")
cfg = ConfigManager()
add_to_log(log_text, f"请将需要处理的图片文件放入 {cfg.get_path('Paths', 'input_folder', fallback='data/input')} 目录中。\n", "warning")
add_to_log(log_text, f"OCR识别结果保存在 {cfg.get_path('Paths', 'output_folder', fallback='data/output')} 目录,处理完成的订单保存在 {cfg.get_path('Paths', 'result_folder', fallback='data/result')} 目录中。\n\n", "warning")
add_to_log(log_text, "=" * 50 + "\n\n", "separator")
return log_text
def main():
"""主函数"""
try:
root, theme, settings, dnd_supported = _init_window()
# 主容器
main_container = tk.Frame(root, bg=theme["bg"])
main_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
content_frame = tk.Frame(main_container, bg=theme["bg"])
content_frame.pack(fill=tk.BOTH, expand=True)
# 中间容器(拖拽区 + 日志区)
mid_container = tk.Frame(content_frame, bg=theme["bg"])
mid_container.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 5), pady=5)
log_text = _create_log_panel(mid_container, theme)
# 状态栏
status_bar = StatusBar(root)
status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 左侧面板
_create_left_panel(content_frame, theme, log_text, status_bar)
# 右侧面板
_create_right_panel(content_frame, theme, log_text, root)
# 拖拽区域
_setup_drag_area(mid_container, theme, dnd_supported, log_text, status_bar)
# 快捷键 + 关闭事件
def on_close():
try:
w = root.winfo_width()
h = root.winfo_height()
settings['window_size'] = f"{w}x{h}"
settings['theme_mode'] = get_theme_mode()
save_user_settings(settings)
except Exception:
pass
root.destroy()
root.protocol("WM_DELETE_WINDOW", on_close)
bind_keyboard_shortcuts(root, log_text, status_bar)
root.mainloop()
except Exception as e:
import traceback
error_msg = f"程序启动失败: {str(e)}\n详细错误信息:\n{traceback.format_exc()}"
print(error_msg)
try:
import tkinter.messagebox as mb
mb.showerror("启动错误", f"程序启动失败:\n{str(e)}")
except Exception:
pass
-198
View File
@@ -1,198 +0,0 @@
"""商品记忆库查看/编辑对话框"""
import os
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
from app.config.settings import ConfigManager
from app.core.db.product_db import ProductDatabase
from .ui_widgets import center_window
def _get_product_db():
cfg = ConfigManager()
db_path = cfg.get_path('Paths', 'product_db', fallback='data/product_cache.db') if hasattr(cfg, 'get_path') else 'data/product_cache.db'
tpl_folder = cfg.get('Paths', 'template_folder', fallback='templates')
item_data = cfg.get('Templates', 'item_data', fallback='商品资料.xlsx')
tpl_path = os.path.join(tpl_folder, item_data)
return ProductDatabase(db_path, tpl_path)
def show_memory_editor(root):
"""显示商品记忆库编辑器"""
db = _get_product_db()
dlg = tk.Toplevel(root)
dlg.title("商品记忆库")
dlg.geometry("950x520")
center_window(dlg)
# ── 顶部搜索栏 ──
top = ttk.Frame(dlg)
top.pack(fill=tk.X, padx=8, pady=(8, 4))
ttk.Label(top, text="搜索:").pack(side=tk.LEFT)
search_var = tk.StringVar()
search_entry = ttk.Entry(top, textvariable=search_var, width=30)
search_entry.pack(side=tk.LEFT, padx=4)
# ── 统计标签 ──
stats_label = ttk.Label(top, text="")
stats_label.pack(side=tk.RIGHT)
# ── Treeview ──
columns = ("barcode", "name", "specification", "unit", "price", "source", "confidence", "usage_count", "last_seen")
tree = ttk.Treeview(dlg, columns=columns, show="headings", height=18)
headers = {
"barcode": ("条码", 120),
"name": ("名称", 180),
"specification": ("规格", 80),
"unit": ("单位", 50),
"price": ("单价", 70),
"source": ("来源", 80),
"confidence": ("置信度", 60),
"usage_count": ("使用次数", 70),
"last_seen": ("最后使用", 140),
}
for col, (text, width) in headers.items():
tree.heading(col, text=text)
tree.column(col, width=width, anchor="center")
# 置信度颜色标签
tree.tag_configure("high", foreground="#28a745") # >= 80 绿
tree.tag_configure("medium", foreground="#ffc107") # 50-79 黄
tree.tag_configure("low", foreground="#dc3545") # < 50 红
scrollbar = ttk.Scrollbar(dlg, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=scrollbar.set)
tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(8, 0), pady=4)
scrollbar.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 8), pady=4)
# ── 数据加载 ──
all_records = []
def load_data(filter_text=""):
nonlocal all_records
all_records = db.get_all_memories()
tree.delete(*tree.get_children())
filtered = all_records
if filter_text:
ft = filter_text.lower()
filtered = [r for r in all_records
if ft in str(r.get('barcode', '')).lower()
or ft in str(r.get('name', '')).lower()]
for r in filtered:
conf = r.get('confidence', 0) or 0
tag = "high" if conf >= 80 else ("medium" if conf >= 50 else "low")
last_seen = r.get('last_seen', '') or ''
if last_seen and len(last_seen) > 16:
last_seen = last_seen[:16]
source_display = {
'template': '模板',
'ocr': 'OCR',
'user_confirmed': '手动',
}.get(r.get('source', ''), r.get('source', ''))
tree.insert("", tk.END, values=(
r.get('barcode', ''),
r.get('name', ''),
r.get('specification', ''),
r.get('unit', ''),
f"{r.get('price', 0):.2f}" if r.get('price') else '',
source_display,
conf,
r.get('usage_count', 0) or 0,
last_seen,
), tags=(tag,))
stats_label.config(text=f"{len(filtered)} / {len(all_records)}")
def on_search(*_):
load_data(search_var.get())
search_var.trace_add("write", on_search)
# ── 按钮区 ──
btn_frame = ttk.Frame(dlg)
btn_frame.pack(fill=tk.X, padx=8, pady=(0, 8))
def edit_selected():
sel = tree.selection()
if not sel:
messagebox.showwarning("提示", "请先选择一条记录")
return
item = tree.item(sel[0])
vals = item['values']
barcode = vals[0]
# 弹出编辑对话框
edit_dlg = tk.Toplevel(dlg)
edit_dlg.title(f"编辑: {barcode}")
edit_dlg.geometry("380x260")
center_window(edit_dlg)
fields = [
("名称", "name", vals[1]),
("规格", "specification", vals[2]),
("单位", "unit", vals[3]),
("单价", "price", vals[4]),
]
entries = {}
for i, (label, key, val) in enumerate(fields):
ttk.Label(edit_dlg, text=label).grid(row=i, column=0, sticky='w', padx=8, pady=4)
var = tk.StringVar(value=str(val) if val else '')
ttk.Entry(edit_dlg, textvariable=var, width=30).grid(row=i, column=1, padx=8, pady=4)
entries[key] = var
def save_edit():
updates = {}
for key, var in entries.items():
v = var.get().strip()
if key == 'price':
try:
updates[key] = float(v) if v else 0
except ValueError:
updates[key] = 0
else:
updates[key] = v
db.update_memory(barcode, updates)
edit_dlg.destroy()
load_data(search_var.get())
ttk.Button(edit_dlg, text="保存", command=save_edit).grid(row=len(fields), column=0, columnspan=2, pady=12)
def delete_selected():
sel = tree.selection()
if not sel:
messagebox.showwarning("提示", "请先选择一条记录")
return
item = tree.item(sel[0])
barcode = item['values'][0]
if messagebox.askyesno("确认删除", f"确定要删除条码 {barcode} 的记忆记录吗?"):
db.delete_memory(barcode)
load_data(search_var.get())
def reimport_template():
if messagebox.askyesno("确认", "重新从商品资料导入将重置所有模板商品的置信度为100,确定继续吗?"):
count = db.reimport()
messagebox.showinfo("完成", f"已重新导入 {count} 条记录")
load_data(search_var.get())
ttk.Button(btn_frame, text="编辑", command=edit_selected).pack(side=tk.LEFT, padx=4)
ttk.Button(btn_frame, text="删除", command=delete_selected).pack(side=tk.LEFT, padx=4)
ttk.Button(btn_frame, text="重新导入模板", command=reimport_template).pack(side=tk.LEFT, padx=4)
ttk.Button(btn_frame, text="刷新", command=lambda: load_data(search_var.get())).pack(side=tk.LEFT, padx=4)
ttk.Button(btn_frame, text="关闭", command=dlg.destroy).pack(side=tk.RIGHT, padx=4)
# 双击编辑
tree.bind("<Double-1>", lambda e: edit_selected())
# 初始加载
load_data()
-377
View File
@@ -1,377 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""处理结果预览对话框模块"""
import os
import re
import datetime
import tkinter as tk
from tkinter import messagebox, scrolledtext
from .theme import THEMES, get_theme_mode, apply_theme
from .ui_widgets import center_window
from app.core.utils.file_utils import format_file_size
from app.config.settings import ConfigManager
TOBACCO_PREVIEW_WINDOW = None
def _get_output_dir():
"""获取输出目录的绝对路径"""
return ConfigManager().get_path('Paths', 'output_folder', fallback='data/output', create=True)
def show_result_preview(command, output):
"""显示处理结果预览"""
if "ocr" in command:
show_ocr_result_preview(output)
elif "excel" in command:
show_excel_result_preview(output)
elif "merge" in command:
show_merge_result_preview(output)
elif "pipeline" in command:
show_pipeline_result_preview(output)
else:
messagebox.showinfo("处理完成", f"操作已成功完成!\n请在{_get_output_dir()}目录查看结果。")
def show_ocr_result_preview(output):
"""显示OCR处理结果预览"""
files_match = re.search(r'找到 (\d+) 个图片文件,其中 (\d+) 个未处理', output)
processed_match = re.search(r'所有图片处理完成, 总计: (\d+), 成功: (\d+)', output)
if processed_match:
total = int(processed_match.group(1))
success = int(processed_match.group(2))
preview = tk.Toplevel()
preview.title("OCR处理结果")
preview.geometry("400x300")
preview.resizable(False, False)
center_window(preview)
tk.Label(preview, text="OCR处理完成", font=("Arial", 16, "bold")).pack(pady=10)
result_frame = tk.Frame(preview)
result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
tk.Label(result_frame, text=f"总共处理: {total} 个文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"成功处理: {success} 个文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"失败数量: {total - success} 个文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
if success == total:
result_text = "全部处理成功!"
result_color = "#28a745"
elif success > total * 0.8:
result_text = "大部分处理成功。"
result_color = "#ffc107"
else:
result_text = "处理失败较多,请检查日志。"
result_color = "#dc3545"
tk.Label(result_frame, text=result_text, font=("Arial", 12, "bold"), fg=result_color).pack(pady=10)
button_frame = tk.Frame(preview)
button_frame.pack(pady=10)
tk.Button(button_frame, text="查看输出文件", command=lambda: os.startfile(_get_output_dir())).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=10)
else:
messagebox.showinfo("OCR处理完成", f"OCR处理已完成,请在{_get_output_dir()}目录查看结果。")
def show_excel_result_preview(output):
"""显示Excel处理结果预览"""
extract_match = re.search(r'提取到 (\d+) 个商品信息', output)
file_match = re.search(r'采购单已保存到: (.+?)(?:\n|$)', output)
if extract_match and file_match:
products_count = int(extract_match.group(1))
output_file = file_match.group(1)
preview = tk.Toplevel()
preview.title("Excel处理结果")
preview.geometry("450x320")
preview.resizable(False, False)
center_window(preview)
tk.Label(preview, text="Excel处理完成", font=("Arial", 16, "bold")).pack(pady=10)
result_frame = tk.Frame(preview)
result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
tk.Label(result_frame, text=f"提取商品数量: {products_count}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"输出文件: {os.path.basename(output_file)}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text="采购单已成功生成!", font=("Arial", 12, "bold"), fg="#28a745").pack(pady=10)
file_frame = tk.Frame(result_frame, relief=tk.GROOVE, borderwidth=1)
file_frame.pack(fill=tk.X, padx=15, pady=5)
tk.Label(file_frame, text="文件信息", font=("Arial", 10, "bold")).pack(anchor=tk.W, padx=10, pady=5)
try:
file_size = os.path.getsize(output_file)
file_time = datetime.datetime.fromtimestamp(os.path.getmtime(output_file))
size_text = format_file_size(file_size)
tk.Label(file_frame, text=f"文件大小: {size_text}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
tk.Label(file_frame, text=f"创建时间: {file_time.strftime('%Y-%m-%d %H:%M:%S')}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
except Exception:
tk.Label(file_frame, text="无法获取文件信息", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
button_frame = tk.Frame(preview)
button_frame.pack(pady=10)
tk.Button(button_frame, text="打开文件", command=lambda: os.startfile(output_file)).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="打开所在文件夹", command=lambda: os.startfile(os.path.dirname(output_file))).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=5)
else:
messagebox.showinfo("Excel处理完成", f"Excel处理已完成,请在{_get_output_dir()}目录查看结果。")
def show_merge_result_preview(output):
"""显示合并结果预览"""
merged_match = re.search(r'合并了 (\d+) 个采购单', output)
product_match = re.search(r'共处理 (\d+) 个商品', output)
output_match = re.search(r'已保存到: (.+?)(?:\n|$)', output)
if merged_match and output_match:
merged_count = int(merged_match.group(1))
product_count = int(product_match.group(1)) if product_match else 0
output_file = output_match.group(1)
preview = tk.Toplevel()
preview.title("采购单合并结果")
preview.geometry("450x300")
preview.resizable(False, False)
apply_theme(preview)
tk.Label(preview, text="采购单合并完成", font=("Arial", 16, "bold")).pack(pady=10)
result_frame = tk.Frame(preview)
result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
tk.Label(result_frame, text=f"合并采购单数量: {merged_count}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"处理商品数量: {product_count}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"输出文件: {os.path.basename(output_file)}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
theme = THEMES[get_theme_mode()]
tk.Label(result_frame, text="采购单已成功合并!", font=("Arial", 12, "bold"), fg=theme["success"]).pack(pady=10)
button_frame = tk.Frame(preview)
button_frame.pack(pady=10)
tk.Button(button_frame, text="打开文件", command=lambda: os.startfile(output_file)).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="打开所在文件夹", command=lambda: os.startfile(os.path.dirname(output_file))).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=10)
else:
messagebox.showinfo("采购单合并完成", f"采购单合并已完成,请在{_get_output_dir()}目录查看结果。")
def show_pipeline_result_preview(output):
"""显示完整流程结果预览"""
ocr_match = re.search(r'所有图片处理完成, 总计: (\d+), 成功: (\d+)', output)
excel_match = re.search(r'提取到 (\d+) 个商品信息', output)
output_file_match = re.search(r'采购单已保存到: (.+?)(?:\n|$)', output)
preview = tk.Toplevel()
preview.title("完整流程处理结果")
preview.geometry("500x400")
preview.resizable(False, False)
center_window(preview)
tk.Label(preview, text="完整处理流程已完成", font=("Arial", 16, "bold")).pack(pady=10)
no_files_match = re.search(r'未找到可合并的文件', output)
if no_files_match:
tk.Label(preview, text="未找到可合并的文件,但其他步骤已成功执行", font=("Arial", 12)).pack(pady=0)
result_frame = tk.Frame(preview)
result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
result_text = scrolledtext.ScrolledText(result_frame, wrap=tk.WORD, height=15, width=60)
result_text.pack(fill=tk.BOTH, expand=True, padx=15, pady=5)
result_text.configure(state=tk.NORMAL)
result_text.insert(tk.END, "===== 流程执行结果 =====\n\n", "title")
result_text.insert(tk.END, "步骤1: OCR识别\n", "step")
if ocr_match:
total = int(ocr_match.group(1))
success = int(ocr_match.group(2))
result_text.insert(tk.END, f" 处理图片: {total}\n", "info")
result_text.insert(tk.END, f" 成功识别: {success}\n", "info")
if success == total:
result_text.insert(tk.END, " 结果: 全部识别成功\n", "success")
else:
result_text.insert(tk.END, f" 结果: 部分识别成功 ({success}/{total})\n", "warning")
else:
result_text.insert(tk.END, " 结果: 无OCR处理或处理信息不完整\n", "warning")
result_text.insert(tk.END, "\n步骤2: Excel处理\n", "step")
if excel_match:
products = int(excel_match.group(1))
result_text.insert(tk.END, f" 提取商品: {products}\n", "info")
result_text.insert(tk.END, " 结果: 成功生成采购单\n", "success")
if output_file_match:
output_file = output_file_match.group(1)
result_text.insert(tk.END, f" 输出文件: {os.path.basename(output_file)}\n", "info")
else:
result_text.insert(tk.END, " 结果: 无Excel处理或处理信息不完整\n", "warning")
result_text.insert(tk.END, "\n===== 整体评估 =====\n", "title")
has_errors = "错误" in output or "失败" in output
no_files_match2 = re.search(r'未找到采购单文件', output)
single_file_match = re.search(r'只有1个采购单文件', output)
if no_files_match2:
result_text.insert(tk.END, "没有找到可合并的文件,但处理流程已成功完成。\n", "warning")
result_text.insert(tk.END, "可以选择打开Excel文件或查看输出文件夹。\n", "info")
elif single_file_match:
result_text.insert(tk.END, "只有一个采购单文件,无需合并,处理流程已成功完成。\n", "warning")
result_text.insert(tk.END, "可以选择打开生成的Excel文件。\n", "info")
elif ocr_match and excel_match and not has_errors:
result_text.insert(tk.END, "流程完整执行成功!\n", "success")
elif ocr_match or excel_match:
result_text.insert(tk.END, "流程部分执行成功,请检查日志获取详情。\n", "warning")
else:
result_text.insert(tk.END, "流程执行可能存在问题,请查看详细日志。\n", "error")
result_text.tag_configure("title", font=("Arial", 12, "bold"))
result_text.tag_configure("step", font=("Arial", 11, "bold"))
result_text.tag_configure("info", font=("Arial", 10))
result_text.tag_configure("success", font=("Arial", 10, "bold"), foreground="#28a745")
result_text.tag_configure("warning", font=("Arial", 10, "bold"), foreground="#ffc107")
result_text.tag_configure("error", font=("Arial", 10, "bold"), foreground="#dc3545")
result_text.configure(state=tk.DISABLED)
button_frame = tk.Frame(preview)
button_frame.pack(pady=10)
if output_file_match:
output_file = output_file_match.group(1)
tk.Button(button_frame, text="打开Excel文件", command=lambda: os.startfile(output_file)).pack(side=tk.LEFT, padx=10)
else:
if excel_match or no_files_match or single_file_match:
output_dir = _get_output_dir()
excel_files = [f for f in os.listdir(output_dir) if f.startswith('采购单_') and (f.endswith('.xls') or f.endswith('.xlsx'))]
if excel_files:
excel_files.sort(key=lambda x: os.path.getmtime(os.path.join(output_dir, x)), reverse=True)
latest_file = os.path.join(output_dir, excel_files[0])
tk.Button(button_frame, text="打开最新Excel文件",
command=lambda: os.startfile(latest_file)).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="查看输出文件夹", command=lambda: os.startfile(_get_output_dir())).pack(side=tk.LEFT, padx=10)
tk.Button(button_frame, text="关闭", command=preview.destroy).pack(side=tk.LEFT, padx=10)
def show_tobacco_result_preview(returncode, output):
"""显示烟草订单处理结果预览"""
global TOBACCO_PREVIEW_WINDOW
if returncode != 0:
return
try:
try:
if TOBACCO_PREVIEW_WINDOW and TOBACCO_PREVIEW_WINDOW.winfo_exists():
TOBACCO_PREVIEW_WINDOW.lift()
return
except Exception:
TOBACCO_PREVIEW_WINDOW = None
result_file = None
order_time = "(未知)"
total_amount = "(未知)"
items_count = 0
abs_path_match = re.search(r'烟草订单处理完成,绝对路径: (.+)(?:\n|$)', output)
if abs_path_match:
result_file = abs_path_match.group(1).strip()
for line in output.split('\n'):
if "烟草公司订单处理成功" in line and "订单时间" in line:
time_match = re.search(r'订单时间: ([^,]+)', line)
amount_match = re.search(r'总金额: ([^,]+)', line)
items_match = re.search(r'处理条目: (\d+)', line)
if time_match:
order_time = time_match.group(1).strip()
if amount_match:
total_amount = amount_match.group(1).strip()
if items_match:
items_count = int(items_match.group(1).strip())
if not result_file or not os.path.exists(result_file):
default_path = os.path.join(_get_output_dir(), "银豹采购单_烟草公司.xls")
if os.path.exists(default_path):
result_file = default_path
preview = tk.Toplevel()
preview.title("烟草订单处理结果")
preview.geometry("450x320")
preview.resizable(False, False)
TOBACCO_PREVIEW_WINDOW = preview
def _close_preview():
global TOBACCO_PREVIEW_WINDOW
TOBACCO_PREVIEW_WINDOW = None
try:
preview.destroy()
except Exception:
pass
preview.protocol("WM_DELETE_WINDOW", _close_preview)
center_window(preview)
tk.Label(preview, text="烟草订单处理完成", font=("Arial", 16, "bold")).pack(pady=10)
result_frame = tk.Frame(preview)
result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
tk.Label(result_frame, text=f"订单时间: {order_time}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"订单总金额: {total_amount}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"处理商品数量: {items_count}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
if result_file and os.path.exists(result_file):
tk.Label(result_frame, text=f"输出文件: {os.path.basename(result_file)}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text="银豹采购单已成功生成!", font=("Arial", 12, "bold"), fg="#28a745").pack(pady=10)
file_frame = tk.Frame(result_frame, relief=tk.GROOVE, borderwidth=1)
file_frame.pack(fill=tk.X, padx=15, pady=5)
tk.Label(file_frame, text="文件信息", font=("Arial", 10, "bold")).pack(anchor=tk.W, padx=10, pady=5)
try:
file_size = os.path.getsize(result_file)
file_time = datetime.datetime.fromtimestamp(os.path.getmtime(result_file))
size_text = format_file_size(file_size)
tk.Label(file_frame, text=f"文件大小: {size_text}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
tk.Label(file_frame, text=f"创建时间: {file_time.strftime('%Y-%m-%d %H:%M:%S')}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
except Exception:
tk.Label(file_frame, text="无法获取文件信息", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
button_frame = tk.Frame(preview)
button_frame.pack(pady=10)
tk.Button(button_frame, text="打开文件", command=lambda: os.startfile(result_file)).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="打开所在文件夹", command=lambda: os.startfile(os.path.dirname(result_file))).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="关闭", command=_close_preview).pack(side=tk.LEFT, padx=5)
else:
tk.Label(result_frame, text="未找到输出文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text=f"请检查{_get_output_dir()}目录", font=("Arial", 12, "bold"), fg="#dc3545").pack(pady=10)
button_frame = tk.Frame(preview)
button_frame.pack(pady=10)
tk.Button(button_frame, text="打开输出目录", command=lambda: os.startfile(_get_output_dir())).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="关闭", command=_close_preview).pack(side=tk.LEFT, padx=5)
preview.lift()
preview.attributes('-topmost', True)
preview.after_idle(lambda: preview.attributes('-topmost', False))
except Exception as e:
messagebox.showerror(
"处理异常",
f"显示预览时发生错误: {e}\n请检查日志了解详细信息。"
)
-60
View File
@@ -1,60 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""键盘快捷键模块"""
import tkinter as tk
from tkinter import messagebox
from .ui_widgets import center_window
from .action_handlers import (
process_single_image_with_status,
process_excel_file_with_status,
batch_ocr_with_status,
run_pipeline_directly,
merge_orders_with_status,
)
from .file_operations import clean_cache
def bind_keyboard_shortcuts(root, log_widget, status_bar):
"""绑定键盘快捷键"""
root.bind('<Control-o>', lambda e: process_single_image_with_status(log_widget, status_bar))
root.bind('<Control-e>', lambda e: process_excel_file_with_status(log_widget, status_bar))
root.bind('<Control-b>', lambda e: batch_ocr_with_status(log_widget, status_bar))
root.bind('<Control-p>', lambda e: run_pipeline_directly(log_widget, status_bar))
root.bind('<Control-m>', lambda e: merge_orders_with_status(log_widget, status_bar))
root.bind('<F5>', lambda e: clean_cache(log_widget))
root.bind('<Escape>', lambda e: root.quit() if messagebox.askyesno("确认退出", "确定要退出程序吗?") else None)
root.bind('<F1>', lambda e: show_shortcuts_help())
def show_shortcuts_help():
"""显示快捷键帮助对话框"""
help_dialog = tk.Toplevel()
help_dialog.title("快捷键帮助")
help_dialog.geometry("400x450")
center_window(help_dialog)
tk.Label(help_dialog, text="键盘快捷键", font=("Arial", 16, "bold")).pack(pady=10)
help_text = tk.Text(help_dialog, wrap=tk.WORD, width=50, height=20)
help_text.pack(padx=20, pady=10, fill=tk.BOTH, expand=True)
shortcuts = """
Ctrl+O: 处理单个图片
Ctrl+E: 处理Excel文件
Ctrl+B: OCR批量识别
Ctrl+P: 完整处理流程
Ctrl+M: 合并采购单
F5: 清除处理缓存
Esc: 退出程序
"""
help_text.insert(tk.END, shortcuts)
help_text.configure(state=tk.DISABLED)
tk.Button(help_dialog, text="确定", command=help_dialog.destroy).pack(pady=10)
help_dialog.lift()
help_dialog.attributes('-topmost', True)
help_dialog.after_idle(lambda: help_dialog.attributes('-topmost', False))
-193
View File
@@ -1,193 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""主题管理模块"""
import tkinter as tk
from tkinter import scrolledtext, ttk
# 私有主题模式变量
_theme_mode = "light"
# 浅色和深色主题颜色
THEMES = {
"light": {
"bg": "#f8f9fa",
"fg": "#212529",
"button_bg": "#ffffff",
"button_fg": "#495057",
"button_hover": "#e9ecef",
"primary_bg": "#007bff",
"primary_fg": "#ffffff",
"secondary_bg": "#6c757d",
"secondary_fg": "#ffffff",
"log_bg": "#ffffff",
"log_fg": "#212529",
"highlight_bg": "#007bff",
"highlight_fg": "#ffffff",
"border": "#dee2e6",
"success": "#28a745",
"error": "#dc3545",
"warning": "#ffc107",
"info": "#17a2b8",
"card_bg": "#ffffff",
"shadow": "#00000010"
},
"dark": {
"bg": "#1a1a1a",
"fg": "#e9ecef",
"button_bg": "#343a40",
"button_fg": "#e9ecef",
"button_hover": "#495057",
"primary_bg": "#0d6efd",
"primary_fg": "#ffffff",
"secondary_bg": "#6c757d",
"secondary_fg": "#ffffff",
"log_bg": "#212529",
"log_fg": "#e9ecef",
"highlight_bg": "#0d6efd",
"highlight_fg": "#ffffff",
"border": "#495057",
"success": "#198754",
"error": "#dc3545",
"warning": "#ffc107",
"info": "#0dcaf0",
"card_bg": "#2d3748",
"shadow": "#00000030"
}
}
def get_theme_mode() -> str:
return _theme_mode
def set_theme_mode(mode: str):
global _theme_mode
_theme_mode = mode
def create_modern_button(parent, text, command, style="primary", width=None, height=None, px_width=None, px_height=None):
"""创建现代化样式的按钮"""
theme = THEMES[_theme_mode]
if style == "primary":
bg_color = "white"
fg_color = theme["primary_bg"]
hover_color = "#f0f8ff"
border_color = theme["primary_bg"]
elif style == "secondary":
bg_color = theme["secondary_bg"]
fg_color = theme["secondary_fg"]
hover_color = theme["button_hover"]
border_color = theme["secondary_bg"]
else:
bg_color = "white"
fg_color = theme["primary_bg"]
hover_color = "#f0f8ff"
border_color = theme["primary_bg"]
button_frame = tk.Frame(parent, bg=border_color, highlightthickness=0)
button_frame.configure(relief="flat", bd=0)
if px_width or px_height:
try:
w = px_width if px_width else button_frame.winfo_reqwidth()
h = px_height if px_height else 32
button_frame.configure(width=w, height=h)
button_frame.pack_propagate(False)
except Exception:
pass
button = tk.Button(
button_frame,
text=text,
command=command,
bg=bg_color,
fg=fg_color,
font=("Microsoft YaHei UI", 8),
relief="flat",
bd=0,
padx=14,
pady=4,
anchor="center",
cursor="hand2",
activebackground=hover_color,
activeforeground=fg_color
)
if width:
button.configure(width=width)
else:
button.configure(width=12)
if height is not None:
button.configure(height=height)
else:
button.configure(height=1)
if height:
button.configure(height=height)
# 悬停效果
def on_enter(e):
button.configure(bg=hover_color)
def on_leave(e):
button.configure(bg=bg_color)
button.bind("<Enter>", on_enter)
button.bind("<Leave>", on_leave)
button_frame.bind("<Enter>", on_enter)
button_frame.bind("<Leave>", on_leave)
button.pack(fill=tk.BOTH, expand=True, padx=1, pady=1)
return button_frame
def create_card_frame(parent, title=None):
"""创建卡片样式的框架"""
theme = THEMES[_theme_mode]
card = tk.Frame(
parent,
bg=theme["card_bg"],
relief="flat",
borderwidth=1,
highlightbackground=theme["border"],
highlightthickness=1
)
if title:
title_label = tk.Label(
card,
text=title,
bg=theme["card_bg"],
fg=theme["fg"],
font=("Microsoft YaHei UI", 10, "bold")
)
title_label.pack(pady=(6, 3))
return card
def apply_theme(widget, theme_mode=None):
"""应用主题到小部件"""
if theme_mode is None:
theme_mode = _theme_mode
theme = THEMES[theme_mode]
try:
widget.configure(bg=theme["bg"], fg=theme["fg"])
except Exception:
pass
for child in widget.winfo_children():
if isinstance(child, tk.Button) and not isinstance(child, ttk.Button):
child.configure(bg=theme["button_bg"], fg=theme["button_fg"])
elif isinstance(child, scrolledtext.ScrolledText):
child.configure(bg=theme["log_bg"], fg=theme["log_fg"])
else:
try:
child.configure(bg=theme["bg"], fg=theme["fg"])
except Exception:
pass
apply_theme(child, theme_mode)
-121
View File
@@ -1,121 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""UI控件模块 - StatusBar、ProgressReporter、可折叠框架等"""
import tkinter as tk
from tkinter import ttk
from .theme import THEMES, get_theme_mode
class StatusBar(tk.Frame):
"""状态栏,显示当前系统状态和进度"""
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.configure(height=25, relief=tk.SUNKEN, borderwidth=1)
self.status_label = tk.Label(self, text="就绪", anchor=tk.W, padx=5)
self.status_label.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.progress = ttk.Progressbar(self, orient=tk.HORIZONTAL, length=200, mode='determinate')
self.progress.pack(side=tk.RIGHT, padx=5, pady=2)
self.progress.pack_forget()
def set_status(self, text, progress=None):
"""设置状态栏文本和进度"""
self.status_label.config(text=text)
if progress is not None and 0 <= progress <= 100:
self.progress.pack(side=tk.RIGHT, padx=5, pady=2)
self.progress.config(value=progress)
else:
self.progress.pack_forget()
def set_running(self, is_running=True):
"""设置运行状态"""
theme = THEMES[get_theme_mode()]
if is_running:
self.status_label.config(text="处理中...", foreground=theme["info"])
self.progress.pack(side=tk.RIGHT, padx=5, pady=2)
self.progress.config(mode='indeterminate')
self.progress.start()
else:
self.status_label.config(text="就绪", foreground=theme["fg"])
self.progress.stop()
self.progress.pack_forget()
class ProgressReporter:
def __init__(self, status_bar: StatusBar):
self.status_bar = status_bar
def set(self, text: str, percent: int = None):
try:
if percent is not None:
self.status_bar.set_status(text, percent)
else:
self.status_bar.set_status(text)
except Exception:
pass
def running(self):
try:
self.status_bar.set_running(True)
except Exception:
pass
def done(self):
try:
self.status_bar.set_running(False)
self.status_bar.set_status("就绪")
except Exception:
pass
def create_collapsible_frame(parent, title, initial_state=True):
"""创建可折叠的面板"""
frame = tk.Frame(parent)
frame.pack(fill=tk.X, pady=5)
title_frame = tk.Frame(frame)
title_frame.pack(fill=tk.X)
state_var = tk.BooleanVar(value=initial_state)
indicator = "" if initial_state else ""
state_label = tk.Label(title_frame, text=indicator, font=("Arial", 10, "bold"))
state_label.pack(side=tk.LEFT, padx=5)
title_label = tk.Label(title_frame, text=title, font=("Arial", 11, "bold"))
title_label.pack(side=tk.LEFT, padx=5)
content_frame = tk.Frame(frame)
if initial_state:
content_frame.pack(fill=tk.X, padx=20, pady=5)
def toggle_collapse(event=None):
current_state = state_var.get()
new_state = not current_state
state_var.set(new_state)
state_label.config(text="" if new_state else "")
if new_state:
content_frame.pack(fill=tk.X, padx=20, pady=5)
else:
content_frame.pack_forget()
title_frame.bind("<Button-1>", toggle_collapse)
state_label.bind("<Button-1>", toggle_collapse)
title_label.bind("<Button-1>", toggle_collapse)
return content_frame, state_var
def center_window(window):
"""使窗口居中显示"""
window.update_idletasks()
width = window.winfo_width()
height = window.winfo_height()
x = (window.winfo_screenwidth() // 2) - (width // 2)
y = (window.winfo_screenheight() // 2) - (height // 2)
window.geometry('{}x{}+{}+{}'.format(width, height, x, y))
-130
View File
@@ -1,130 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""用户设置与最近文件管理模块"""
import os
import json
import re
import tkinter as tk
from typing import Dict, List, Any
from app.core.utils.log_utils import get_logger
from app.config.settings import ConfigManager
logger = get_logger(__name__)
RECENT_LIST_WIDGET = None
def load_user_settings():
try:
path = os.path.abspath(os.path.join('data', 'user_settings.json'))
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.debug(f"加载用户设置失败: {e}")
return {}
def save_user_settings(settings: Dict[str, Any]):
try:
os.makedirs('data', exist_ok=True)
path = os.path.abspath(os.path.join('data', 'user_settings.json'))
with open(path, 'w', encoding='utf-8') as f:
json.dump(settings, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.debug(f"保存用户设置失败: {e}")
def get_recent_files() -> List[str]:
s = load_user_settings()
items = s.get('recent_files', [])
if not isinstance(items, list):
return []
def _allowed(p: str) -> bool:
try:
if not isinstance(p, str) or not os.path.isfile(p):
return False
ext = os.path.splitext(p)[1].lower()
return ext in {'.xlsx', '.xls', '.jpg', '.jpeg', '.png', '.bmp'}
except Exception:
return False
kept = [p for p in items if _allowed(p)]
if not kept:
candidates = []
cfg = ConfigManager()
for d in [cfg.get_path('Paths', 'output_folder', fallback='data/output'), cfg.get_path('Paths', 'result_folder', fallback='data/result')]:
try:
if os.path.exists(d):
for name in os.listdir(d):
p = os.path.join(d, name)
if _allowed(p):
candidates.append(p)
except Exception:
pass
if candidates:
kept = candidates
try:
kept_sorted = sorted(kept, key=lambda p: os.path.getmtime(p), reverse=True)
except Exception:
kept_sorted = kept
if kept_sorted != items or len(kept_sorted) != len(items):
s['recent_files'] = kept_sorted[:20]
save_user_settings(s)
return kept_sorted[:10]
def refresh_recent_list_widget():
try:
global RECENT_LIST_WIDGET
if RECENT_LIST_WIDGET is None:
return
RECENT_LIST_WIDGET.delete(0, tk.END)
for i, p in enumerate(get_recent_files(), start=1):
RECENT_LIST_WIDGET.insert(tk.END, f"{i}. {p}")
except Exception as e:
logger.debug(f"刷新最近文件列表失败: {e}")
def _extract_path_from_recent_item(s: str) -> str:
try:
m = re.match(r'^(\d+)\.\s+(.*)$', s)
p = m.group(2) if m else s
return p.strip().strip('"')
except Exception:
return s.strip().strip('"')
def add_recent_file(path: str) -> None:
try:
if not path:
return
try:
if not os.path.isfile(path):
return
ext = os.path.splitext(path)[1].lower()
if ext not in {'.xlsx', '.xls', '.jpg', '.jpeg', '.png', '.bmp'}:
return
except Exception:
return
s = load_user_settings()
items = s.get('recent_files', [])
items = [p for p in items if p != path]
items.insert(0, path)
s['recent_files'] = items[:20]
save_user_settings(s)
refresh_recent_list_widget()
except Exception as e:
logger.debug(f"添加最近文件失败: {e}")
def clear_recent_files():
try:
s = load_user_settings()
s['recent_files'] = []
save_user_settings(s)
except Exception as e:
logger.debug(f"清空最近文件失败: {e}")
-22
View File
@@ -57,8 +57,6 @@ hidden_imports = [
'xlwt', 'xlwt',
'xlutils', 'xlutils',
'requests', 'requests',
'dotenv',
'tkinterdnd2',
'configparser', 'configparser',
'threading', 'threading',
'datetime', 'datetime',
@@ -70,28 +68,8 @@ hidden_imports = [
'app.services.ocr_service', 'app.services.ocr_service',
'app.services.order_service', 'app.services.order_service',
'app.services.tobacco_service', 'app.services.tobacco_service',
'app.services.processor_service',
'app.core.utils.dialog_utils', 'app.core.utils.dialog_utils',
'app.core.utils.file_utils',
'app.core.utils.log_utils',
'app.core.utils.string_utils',
'app.core.handlers.column_mapper',
'app.core.excel.converter', 'app.core.excel.converter',
'app.core.db.product_db',
'app.ui.error_utils',
'app.ui.theme',
'app.ui.logging_ui',
'app.ui.ui_widgets',
'app.ui.user_settings',
'app.ui.result_previews',
'app.ui.command_runner',
'app.ui.file_operations',
'app.ui.action_handlers',
'app.ui.barcode_editor',
'app.ui.config_dialog',
'app.ui.shortcuts',
'app.ui.main_window',
'app.ui.memory_editor',
] ]
a = Analysis( a = Analysis(
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
清理脚本 - 用于删除无关的文件和日志
"""
import os
import shutil
import glob
def clean_logs():
"""清理日志文件"""
print("清理日志文件...")
# 删除.active文件
active_files = glob.glob("logs/*.active")
for file in active_files:
try:
os.remove(file)
print(f"已删除: {file}")
except Exception as e:
print(f"删除文件时出错 {file}: {e}")
# 保留最新的日志,删除旧的备份
log_files = glob.glob("logs/*.log.*")
for file in log_files:
try:
os.remove(file)
print(f"已删除: {file}")
except Exception as e:
print(f"删除文件时出错 {file}: {e}")
def clean_temp_files():
"""清理临时文件"""
print("清理临时文件...")
# 清空临时目录
temp_dir = "data/temp"
if os.path.exists(temp_dir):
for file in os.listdir(temp_dir):
file_path = os.path.join(temp_dir, file)
try:
if os.path.isfile(file_path):
os.remove(file_path)
print(f"已删除: {file_path}")
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
print(f"已删除目录: {file_path}")
except Exception as e:
print(f"删除文件时出错 {file_path}: {e}")
# 删除备份文件
backup_files = glob.glob("data/*.bak") + glob.glob("config/*.bak")
for file in backup_files:
try:
os.remove(file)
print(f"已删除: {file}")
except Exception as e:
print(f"删除文件时出错 {file}: {e}")
def clean_pycache():
"""清理Python缓存文件"""
print("清理Python缓存文件...")
# 查找并删除所有__pycache__目录
for root, dirs, files in os.walk("."):
for dir in dirs:
if dir == "__pycache__":
cache_dir = os.path.join(root, dir)
try:
shutil.rmtree(cache_dir)
print(f"已删除目录: {cache_dir}")
except Exception as e:
print(f"删除目录时出错 {cache_dir}: {e}")
def main():
"""主函数"""
print("开始清理无关文件...")
clean_logs()
clean_temp_files()
clean_pycache()
print("清理完成!")
if __name__ == "__main__":
main()
+5 -22
View File
@@ -1,23 +1,17 @@
[API] [API]
api_key = api_key = O0Fgk3o69RWJ86eAX8BTHRaB
secret_key = secret_key = VyZD5lzcIMgsup1uuD6Cw0pfzS20IGPZ
timeout = 30 timeout = 30
max_retries = 3 max_retries = 3
retry_delay = 2 retry_delay = 2
api_url = https://aip.baidubce.com/rest/2.0/ocr/v1/table api_url = https://aip.baidubce.com/rest/2.0/ocr/v1/table
token_url = https://aip.baidubce.com/oauth/2.0/token
form_ocr_url = https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/get_request_result
[Paths] [Paths]
input_folder = data/input input_folder = data/input
output_folder = data/output output_folder = data/output
temp_folder = data/temp temp_folder = data/temp
template_folder = templates template_folder = E:\2025Code\python\orc-order-v2\templates
template_file = templates/银豹-采购单模板.xls
processed_record = data/processed_files.json processed_record = data/processed_files.json
data_dir = data
product_db = data/product_cache.db
result_folder = data/result
[Performance] [Performance]
max_workers = 4 max_workers = 4
@@ -27,22 +21,11 @@ skip_existing = true
[File] [File]
allowed_extensions = .jpg,.jpeg,.png,.bmp allowed_extensions = .jpg,.jpeg,.png,.bmp
excel_extension = .xlsx excel_extension = .xlsx
max_file_size_mb = 5 max_file_size_mb = 4
[Templates] [Templates]
purchase_order = 银豹-采购单模板.xls purchase_order = 银豹-采购单模板.xls
item_data = 商品资料.xlsx
[App] [App]
version = 2026.05.05.0239 version = 2026.03.30.1036
[Gitea]
base_url = https://gitea.94kan.cn
owner = houhuan
repo = yixuan-sync-data
token =
[WebAuth]
username = admin
password_hash = $2b$12$nllT8o1QIMfWKuTlpQI3G./E2NS.gqf0EHZyNkJ8gMpVa9grTXRoC
-16
View File
@@ -231,10 +231,6 @@
"map_to": "6977826050028", "map_to": "6977826050028",
"description": "条码映射:6972556000022 -> 6977826050028" "description": "条码映射:6972556000022 -> 6977826050028"
}, },
"6949352266280": {
"map_to": "6949352266273",
"description": "条码映射:6949352266280 -> 6949352266273"
},
"6925019900087": { "6925019900087": {
"multiplier": 10, "multiplier": 10,
"target_unit": "瓶", "target_unit": "瓶",
@@ -257,17 +253,5 @@
"target_unit": "个", "target_unit": "个",
"specification": "1*14", "specification": "1*14",
"description": "友臣肉松,1盒14个" "description": "友臣肉松,1盒14个"
},
"6921734933485": {
"multiplier": 12,
"target_unit": "支",
"specification": "1*12",
"description": "得力铅笔"
},
"6901826888244": {
"multiplier": 30,
"target_unit": "对",
"specification": "1*30",
"description": "南孚电池"
} }
} }
+2 -15
View File
@@ -1,23 +1,17 @@
[API] [API]
api_key = api_key = O0Fgk3o69RWJ86eAX8BTHRaB
secret_key = secret_key = VyZD5lzcIMgsup1uuD6Cw0pfzS20IGPZ
timeout = 30 timeout = 30
max_retries = 3 max_retries = 3
retry_delay = 2 retry_delay = 2
api_url = https://aip.baidubce.com/rest/2.0/ocr/v1/table api_url = https://aip.baidubce.com/rest/2.0/ocr/v1/table
token_url = https://aip.baidubce.com/oauth/2.0/token
form_ocr_url = https://aip.baidubce.com/rest/2.0/solution/v1/form_ocr/get_request_result
[Paths] [Paths]
input_folder = data/input input_folder = data/input
output_folder = data/output output_folder = data/output
result_folder = data/result
temp_folder = data/temp temp_folder = data/temp
template_folder = templates template_folder = templates
template_file = templates/银豹-采购单模板.xls
processed_record = data/processed_files.json processed_record = data/processed_files.json
data_dir = data
product_db = data/product_cache.db
[Performance] [Performance]
max_workers = 4 max_workers = 4
@@ -31,11 +25,4 @@ max_file_size_mb = 4
[Templates] [Templates]
purchase_order = 银豹-采购单模板.xls purchase_order = 银豹-采购单模板.xls
item_data = 商品资料.xlsx
[Gitea]
base_url = https://gitea.94kan.cn
owner = houhuan
repo = yixuan-sync-data
token = 50b61e43a141d606ae2529cd1755bc666d800e08
+14
View File
@@ -0,0 +1,14 @@
{
"window_size": "900x600",
"theme_mode": "light",
"recent_files": [
"data/result\\采购单_预处理之后_订单明细20260330133908.xls",
"data/output\\预处理之后_订单明细20260330133908.xlsx",
"data/result\\采购单_预处理之后_订单1774849009841.xls",
"data/output\\预处理之后_订单1774849009841.xlsx",
"E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx",
"data/output\\订单1774849009841.xlsx",
"E:/2025Code/python/orc-order-v2/data/output/订单明细20260330133908.xlsx",
"data/output\\订单明细20260330133908.xlsx"
]
}
+30
View File
@@ -0,0 +1,30 @@
# 更新日志
## v1.1.0 (2025-05-30)
### 新特性
- 添加对特殊条码6958620703716的处理,支持同时设置规格和条码映射
- 增强不规范规格格式的解析能力(如"IL*12"、"6oo*12"等)
- 支持带重量单位的规格解析(如"5kg*6"
- 添加数量为空时通过金额和单价自动计算数量的功能
### 修复
- 修复条码映射功能在特殊处理后不生效的问题
- 修复OrderService中缺少merge_all_purchase_orders方法导致合并采购单报错的问题
- 修复了条码映射对话框无法同时添加特殊处理和映射的问题
### 改进
- 改进了BarcodeMapper类,使其支持同时进行特殊处理和条码映射
- 改进了规格解析逻辑,增加了对各种单位和格式的支持
- 添加条码映射对话框中可视化标记映射关系
- 更新了条码映射配置文件,增加了更多特殊条码处理
- 改进商品验证器,在数量为空但单价和金额存在时,自动计算数量
## v1.0.0 (2025-05-01)
### 初始版本
- 基础OCR识别功能
- Excel处理功能
- 采购单合并功能
- 烟草订单处理功能
- 图形用户界面
+309
View File
@@ -0,0 +1,309 @@
# 益选-OCR订单处理系统 - 项目概述文档
## 项目介绍和目标
益选-OCR订单处理系统是一个基于Python开发的智能化采购单处理解决方案。该项目旨在通过OCR(光学字符识别)技术自动识别采购单图片中的商品信息,并将其转换为结构化的Excel数据,实现采购流程的数字化和自动化。
### 项目愿景
- **自动化处理**:减少人工录入工作,提高数据处理效率
- **智能化识别**:准确识别商品条码、名称、规格、数量、单价等关键信息
- **标准化输出**:生成统一格式的采购单,便于后续处理和管理
- **批量处理能力**:支持大批量图片的并发处理
### 核心价值
- 将传统的手工录入模式转变为自动化处理
- 提高采购单处理准确率,减少人为错误
- 大幅缩短采购单处理时间,提升工作效率
- 提供友好的图形界面,降低使用门槛
## 系统功能特性
### 核心功能模块
#### 1. OCR识别模块
- **图片识别**:支持JPG、JPEG、PNG、BMP等多种图片格式
- **表格识别**:专门优化表格结构识别,准确提取行列信息
- **文字识别**:高精度中文数字混合识别
- **智能纠错**:自动纠正识别过程中的常见错误
#### 2. Excel处理模块
- **数据提取**:从OCR结果中提取商品信息
- **格式转换**:将识别结果转换为标准采购单格式
- **数据清洗**:自动清理和标准化不规范数据
- **模板应用**:支持自定义采购单模板
#### 3. 采购单合并模块
- **多文件合并**:支持合并多个采购单文件
- **商品汇总**:自动汇总相同商品,计算总数量
- **格式统一**:确保合并后的采购单格式一致性
- **冲突处理**:智能处理合并过程中的数据冲突
#### 4. 条码映射模块
- **条码转换**:支持将特定条码映射为其他条码
- **系统适配**:适应不同供应商系统的条码要求
- **映射配置**:提供灵活的条码映射规则配置
#### 5. 规格处理模块
- **单位识别**:智能识别商品计量单位
- **单位转换**:自动进行单位换算(如箱转个)
- **规格解析**:解析复杂的商品规格描述
#### 6. 烟草订单处理模块
- **专用优化**:针对烟草行业订单的特殊格式优化
- **规则适配**:适配烟草公司的特殊要求和格式
### 辅助功能
#### 图形用户界面
- **直观操作**:提供简洁明了的操作界面
- **进度显示**:实时显示处理进度和状态
- **结果预览**:处理完成后显示结果摘要
- **日志查看**:提供详细的处理日志查看功能
#### 配置管理
- **灵活配置**:支持多种处理参数的配置
- **配置文件**:使用INI格式配置文件,易于修改
- **运行时调整**:支持运行时的参数调整
#### 性能优化
- **批量处理**:支持大批量图片的并发处理
- **断点续传**:支持处理中断后的继续处理
- **内存优化**:优化内存使用,支持大文件处理
## 技术栈和架构
### 核心技术栈
#### 后端技术
- **Python 3.8+**:主要开发语言
- **百度OCR API**:提供高精度的OCR识别服务
- **Pandas**:数据处理和分析
- **OpenPyXL**Excel文件读写
- **ConfigParser**:配置文件管理
#### 前端技术
- **Tkinter**Python标准GUI库
- **Threading**:多线程处理,避免界面卡顿
- **ttk**:现代风格的界面组件
#### 工具库
- **Requests**HTTP请求处理
- **Logging**:日志记录和管理
- **JSON**:数据序列化和配置存储
- **Pathlib**:文件路径处理
- **Datetime**:日期时间处理
### 系统架构
```
┌─────────────────────────────────────────────────────────────┐
│ 用户界面层 (UI Layer) │
├─────────────────────────────────────────────────────────────┤
│ 业务逻辑层 (Service Layer) │
│ ┌─────────────┬──────────────┬─────────────┬──────────┐ │
│ │ OCR服务 │ Excel服务 │ 合并服务 │ 配置管理 │ │
│ │ (OCRService)│ (OrderService)│ (MergeService)│ (Config) │ │
│ └─────────────┴──────────────┴─────────────┴──────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 核心处理层 (Core Layer) │
│ ┌─────────────┬──────────────┬─────────────┬──────────┐ │
│ │ OCR处理器 │ Excel处理器 │ 数据清洗 │ 工具函数 │ │
│ │ (OCRProc) │ (ExcelProc) │ (Cleaner) │ (Utils) │ │
│ └─────────────┴──────────────┴─────────────┴──────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 数据访问层 (Data Access Layer) │
│ ┌─────────────┬──────────────┬─────────────┬──────────┐ │
│ │ 文件系统 │ 配置文件 │ 日志文件 │ 模板文件 │ │
│ │ (FileSystem)│ (Config) │ (Logs) │ (Templates)│ │
│ └─────────────┴──────────────┴─────────────┴──────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 模块划分
#### 应用层 (app/)
- **cli/**:命令行接口模块
- **config/**:配置管理模块
- **core/**:核心业务逻辑模块
- **services/**:服务层模块
#### 核心层 (app/core/)
- **ocr/**OCR识别相关功能
- **excel/**Excel处理相关功能
- **utils/**:工具函数和辅助功能
#### 数据层
- **data/**:数据文件存储目录
- **templates/**:模板文件存储目录
- **logs/**:日志文件存储目录
## 安装配置说明
### 系统要求
#### 软件要求
- **操作系统**Windows 7/8/10/11LinuxmacOS
- **Python版本**Python 3.8 或更高版本
- **依赖库**:详见requirements.txt文件
#### 硬件要求
- **CPU**:双核处理器或以上
- **内存**4GB RAM或以上
- **存储**:至少1GB可用磁盘空间
- **网络**:需要网络连接(用于OCR API调用)
### 安装步骤
#### 1. 环境准备
```bash
# 确保已安装Python 3.8+
python --version
# 安装项目依赖
pip install -r requirements.txt
```
#### 2. 配置百度OCR API
1. 注册百度智能云账号
2. 创建OCR应用,获取API Key和Secret Key
3. 在config.ini文件中配置API密钥:
```ini
[API]
api_key = 你的API密钥
secret_key = 你的密钥
```
#### 3. 配置文件设置
编辑config.ini文件,设置相关路径和参数:
```ini
[Paths]
input_folder = data/input
output_folder = data/output
temp_folder = data/temp
[Performance]
max_workers = 4
batch_size = 5
skip_existing = true
```
### 运行配置
#### 启动方式
```bash
# 方式1:直接运行启动器
python 启动器.py
# 方式2:运行主程序
python run.py
```
#### 可执行文件
项目提供编译好的可执行文件:
- **OCR订单处理系统.exe**:主程序可执行文件
- **release/**目录:完整的发布版本
## 使用指南
### 基本操作流程
#### 1. 准备图片文件
- 将需要识别的采购单图片放入`data/input`目录
- 支持的图片格式:JPG、JPEG、PNG、BMP
- 建议图片清晰度在300DPI以上
#### 2. 启动系统
- 运行`启动器.py``OCR订单处理系统.exe`
- 系统会自动加载配置并初始化
#### 3. 选择功能
主界面提供以下功能选项:
- **OCR识别**:识别图片中的商品信息
- **Excel处理**:将OCR结果处理成标准采购单
- **采购单合并**:合并多个采购单文件
- **完整流程**:执行OCR识别+Excel处理+合并的完整流程
#### 4. 执行处理
- 点击相应功能按钮
- 系统会显示处理进度和状态
- 处理完成后显示结果预览
#### 5. 查看结果
- 处理结果保存在`data/output`目录
- 可以查看生成的Excel采购单文件
- 通过日志查看详细处理过程
### 高级功能使用
#### 批量处理配置
在config.ini中设置批量处理参数:
```ini
[Performance]
batch_size = 5 # 每批处理的文件数量
max_workers = 4 # 最大并发线程数
skip_existing = true # 跳过已处理的文件
```
#### 条码映射配置
编辑`config/barcode_mappings.json`文件,设置条码映射规则:
```json
{
"原条码1": "目标条码1",
"原条码2": "目标条码2"
}
```
#### 模板配置
`templates`目录中放置自定义的采购单模板文件,系统会自动识别并使用。
### 故障排除
#### 常见问题
1. **OCR识别失败**:检查图片清晰度和格式
2. **网络连接失败**:检查API密钥和网络设置
3. **文件权限错误**:确保有读写权限
4. **内存不足**:减少批量处理的大小
#### 日志查看
查看`logs`目录下的日志文件,获取详细的错误信息和处理过程。
### 最佳实践
#### 图片质量
- 使用高分辨率扫描或拍照
- 确保图片清晰,无反光和阴影
- 保持采购单平整,避免折叠
#### 处理效率
- 合理设置批量处理大小
- 使用多线程处理提高效率
- 定期清理临时文件
#### 数据管理
- 定期备份重要的采购单数据
- 建立规范的文件命名规则
- 及时清理已处理的文件
## 项目维护
### 版本管理
- 使用Git进行版本控制
- 定期更新CHANGELOG.md文件
- 维护版本发布记录
### 依赖管理
- 定期更新依赖库版本
- 测试新版本的兼容性
- 维护requirements.txt文件
### 文档维护
- 保持文档的及时更新
- 根据用户反馈完善文档
- 提供多语言支持
## 版权信息
© 2025 益选-OCR订单处理系统
作者:欢欢欢
本项目采用模块化设计,遵循最佳实践,致力于为用户提供高效、准确的采购单自动化处理解决方案。
+43
View File
@@ -0,0 +1,43 @@
# 益选-OCR订单处理系统
一个集OCR识别、Excel处理和订单合并功能于一体的采购单处理系统。
## 主要功能
- **OCR识别**:识别图片中的商品信息,包括条码、名称、数量、单价等
- **Excel处理**:将OCR识别结果处理成规范的Excel采购单
- **采购单合并**:合并多个采购单,汇总相同商品
- **条码映射**:支持将特定条码映射为其他条码,适应不同系统要求
- **规格处理**:智能解析商品规格,实现单位自动转换
- **烟草订单处理**:专门处理烟草公司订单
## 技术特点
- 基于Python开发,使用Tkinter构建图形界面
- 采用模块化设计,易于扩展和维护
- 自动处理各种不规范数据格式
- 配置文件支持,可自定义各种处理参数
- 日志记录,便于问题排查
## 使用方法
1. 运行`启动器.py`打开主界面
2. 根据需要选择相应功能按钮
3. 按照提示操作,完成数据处理
## 系统要求
- Python 3.8+
- 所需第三方库:详见`requirements.txt`
## 最近更新
请查看[更新日志](CHANGELOG.md)了解最新版本变更。
## 贡献者
- 欢欢欢
## 版权
© 2025 益选-OCR订单处理系统
+634
View File
@@ -0,0 +1,634 @@
# 益选-OCR订单处理系统 - 技术架构文档
## 系统架构设计
### 整体架构概述
益选-OCR订单处理系统采用分层架构设计,遵循单一职责原则和开闭原则,确保系统的可维护性、可扩展性和可测试性。系统架构分为四个主要层次:用户界面层、业务逻辑层、核心处理层和数据访问层。
### 架构设计原则
#### 1. 分层解耦
- 各层之间通过明确定义的接口进行通信
- 上层依赖下层,下层不依赖上层
- 层与层之间保持松耦合关系
#### 2. 模块化设计
- 每个模块具有明确的职责边界
- 模块内部高内聚,模块之间低耦合
- 支持模块的独立开发和测试
#### 3. 配置驱动
- 系统行为通过配置文件控制
- 支持运行时参数调整
- 提供灵活的配置管理机制
#### 4. 错误处理
- 统一的异常处理机制
- 详细的日志记录和错误追踪
- 优雅的错误恢复机制
## 模块划分和职责
### 用户界面层 (UI Layer)
#### 启动器模块 (启动器.py)
**职责**
- 提供图形用户界面
- 协调各个功能模块的调用
- 显示处理进度和结果
- 管理用户交互流程
**主要组件**
- `OCR订单处理系统`类:主应用程序类
- `StatusBar`类:状态栏组件
- `LogRedirector`类:日志重定向器
- 各种对话框和预览窗口
#### 命令行接口 (app/cli/)
**职责**
- 提供命令行操作方式
- 支持脚本化操作
- 实现批量处理功能
**子模块**
- `ocr_cli.py`OCR识别命令行接口
- `excel_cli.py`Excel处理命令行接口
- `merge_cli.py`:合并功能命令行接口
### 业务逻辑层 (Service Layer)
#### OCR服务 (app/services/ocr_service.py)
**职责**
- 协调OCR识别流程
- 管理OCR处理器的生命周期
- 提供OCR相关的业务逻辑
- 处理OCR结果的验证和转换
**核心方法**
- `process_image()`:处理单个图片
- `process_images_batch()`:批量处理图片
- `get_unprocessed_images()`:获取待处理图片列表
- `validate_image()`:验证图片有效性
#### 订单服务 (app/services/order_service.py)
**职责**
- 处理Excel订单文件
- 提取和标准化商品信息
- 应用条码映射规则
- 执行规格单位转换
**核心功能**
- Excel文件读取和解析
- 商品信息提取和清洗
- 条码映射和转换
- 规格单位智能识别
#### 烟草服务 (app/services/tobacco_service.py)
**职责**
- 专门处理烟草行业订单
- 适配烟草公司的特殊格式
- 处理烟草订单的特定规则
#### 合并服务
**职责**
- 合并多个采购单文件
- 汇总相同商品信息
- 处理合并冲突和重复项
### 核心处理层 (Core Layer)
#### OCR处理核心 (app/core/ocr/)
##### 表格OCR处理器 (table_ocr.py)
**职责**
- 协调OCR识别流程
- 管理处理记录
- 控制批量处理逻辑
- 处理文件I/O操作
**核心组件**
- `OCRProcessor`类:主要的OCR处理器
- `ProcessedRecordManager`类:处理记录管理器
**处理流程**
1. 图片验证和预处理
2. 调用百度OCR API进行识别
3. 解析OCR返回结果
4. 生成Excel文件
5. 更新处理记录
##### 百度OCR客户端 (baidu_ocr.py)
**职责**
- 封装百度OCR API调用
- 处理API认证和授权
- 管理API请求和响应
- 实现重试和错误处理机制
**核心功能**
- API密钥管理
- 请求签名生成
- 表格识别API调用
- 结果获取和解析
#### Excel处理核心 (app/core/excel/)
##### Excel处理器
**职责**
- 读取和解析Excel文件
- 提取商品信息
- 数据清洗和标准化
- 生成标准采购单格式
**处理逻辑**
1. 读取Excel文件
2. 识别商品数据区域
3. 提取商品属性(条码、名称、规格、数量、单价)
4. 应用数据清洗规则
5. 生成标准化输出
##### 单位转换器 (converter.py)
**职责**
- 智能识别商品规格单位
- 执行单位换算
- 处理复杂的规格描述
**支持的单位**
- 数量单位:个、只、条、包、箱、件等
- 重量单位:克、千克、斤、公斤等
- 体积单位:毫升、升、立方米等
#### 工具模块 (app/core/utils/)
##### 文件工具 (file_utils.py)
**职责**
- 文件系统操作封装
- 路径处理和验证
- 文件类型检查
- 批量文件操作
##### 日志工具 (log_utils.py)
**职责**
- 日志配置和管理
- 日志级别控制
- 日志文件轮转
- 错误追踪和记录
##### 对话框工具 (dialog_utils.py)
**职责**
- 自定义对话框实现
- 用户交互界面组件
- 配置界面管理
### 数据访问层 (Data Access Layer)
#### 配置管理 (app/config/)
##### 配置管理器 (settings.py)
**职责**
- 配置文件加载和解析
- 配置项访问和修改
- 配置验证和默认值处理
- 配置持久化
##### 默认配置 (defaults.py)
**职责**
- 定义系统默认配置
- 提供配置模板
- 确保配置完整性
#### 数据存储
##### 文件系统接口
- **输入目录**`data/input/` - 存放待处理的图片文件
- **输出目录**`data/output/` - 存放处理结果和生成的Excel文件
- **临时目录**`data/temp/` - 存放临时文件
- **模板目录**`templates/` - 存放Excel模板文件
- **配置目录**`config/` - 存放配置文件和映射规则
##### 处理记录管理
- **JSON记录文件**`data/output/processed_files.json`
- **记录内容**:已处理文件的映射关系
- **更新机制**:处理完成后自动更新记录
## 核心算法和流程
### OCR识别算法流程
```mermaid
graph TD
A[开始] --> B[图片验证]
B --> C{图片有效?}
C -->|是| D[检查是否已处理]
C -->|否| E[返回错误]
D --> F{已处理?}
F -->|是| G[返回现有结果]
F -->|否| H[调用百度OCR API]
H --> I[解析OCR结果]
I --> J[生成Excel文件]
J --> K[更新处理记录]
K --> L[返回成功]
E --> M[结束]
G --> M
L --> M
```
#### 图片验证算法
```python
def validate_image(image_path: str) -> bool:
# 1. 文件存在性检查
if not os.path.exists(image_path):
return False
# 2. 文件扩展名验证
ext = get_file_extension(image_path)
if ext not in ALLOWED_EXTENSIONS:
return False
# 3. 文件大小检查
if not is_file_size_valid(image_path, MAX_SIZE_MB):
return False
# 4. 图片格式验证(可选)
try:
with Image.open(image_path) as img:
img.verify()
except:
return False
return True
```
#### OCR结果解析算法
```python
def parse_ocr_result(ocr_response: dict) -> dict:
result = {
'tables': [],
'text': '',
'excel_data': None
}
# 1. 提取表格数据
if 'tables_result' in ocr_response:
for table in ocr_response['tables_result']:
table_data = extract_table_data(table)
result['tables'].append(table_data)
# 2. 提取文本内容
if 'words_result' in ocr_response:
result['text'] = extract_text_content(ocr_response['words_result'])
# 3. 提取Excel数据
excel_base64 = find_excel_data(ocr_response)
if excel_base64:
result['excel_data'] = base64.b64decode(excel_base64)
return result
```
### Excel处理算法流程
#### 商品信息提取算法
```python
def extract_product_info(excel_data: pd.DataFrame) -> List[Dict]:
products = []
# 1. 识别表头行
header_row = identify_header_row(excel_data)
# 2. 确定列映射
column_mapping = map_columns(excel_data.iloc[header_row])
# 3. 提取商品数据
for row_idx in range(header_row + 1, len(excel_data)):
row_data = excel_data.iloc[row_idx]
product = {
'barcode': extract_barcode(row_data, column_mapping),
'name': extract_product_name(row_data, column_mapping),
'specification': extract_specification(row_data, column_mapping),
'quantity': extract_quantity(row_data, column_mapping),
'unit_price': extract_unit_price(row_data, column_mapping),
'total_price': extract_total_price(row_data, column_mapping)
}
# 4. 数据验证和清洗
if validate_product(product):
cleaned_product = clean_product_data(product)
products.append(cleaned_product)
return products
```
#### 规格单位识别算法
```python
def parse_specification(spec_text: str) -> Dict:
result = {
'original': spec_text,
'quantity': 1,
'unit': '个',
'parsed': False
}
# 1. 预定义单位模式
unit_patterns = {
r'(\d+)\s*个': ('个', 1),
r'(\d+)\s*只': ('只', 1),
r'(\d+)\s*条': ('条', 1),
r'(\d+)\s*包': ('包', 1),
r'(\d+)\s*箱': ('箱', 1),
r'(\d+)\s*件': ('件', 1),
r'(\d+)\s*克': ('克', 1),
r'(\d+)\s*千克': ('千克', 1),
r'(\d+)\s*斤': ('斤', 1),
r'(\d+)\s*公斤': ('公斤', 1)
}
# 2. 模式匹配
for pattern, (unit, multiplier) in unit_patterns.items():
match = re.search(pattern, spec_text, re.IGNORECASE)
if match:
result['quantity'] = int(match.group(1))
result['unit'] = unit
result['parsed'] = True
break
# 3. 复杂规格处理
if not result['parsed']:
result = parse_complex_specification(spec_text)
return result
```
### 采购单合并算法
#### 商品汇总算法
```python
def merge_products(products_list: List[List[Dict]]) -> List[Dict]:
merged_products = {}
# 1. 收集所有商品
for products in products_list:
for product in products:
key = generate_product_key(product)
if key in merged_products:
# 2. 合并相同商品
merged_products[key]['quantity'] += product['quantity']
merged_products[key]['total_price'] += product['total_price']
merged_products[key]['source_files'].append(product.get('source_file', ''))
else:
# 3. 添加新商品
merged_products[key] = product.copy()
merged_products[key]['source_files'] = [product.get('source_file', '')]
# 4. 转换回列表格式
result = list(merged_products.values())
# 5. 排序(按条码或名称)
result.sort(key=lambda x: x.get('barcode', x.get('name', '')))
return result
```
## 数据流设计
### 主要数据流
#### 1. OCR识别数据流
```
输入图片 → 图片验证 → OCR API调用 → 结果解析 → Excel生成 → 输出文件
```
#### 2. Excel处理数据流
```
Excel文件 → 数据读取 → 商品提取 → 数据清洗 → 格式转换 → 标准采购单
```
#### 3. 合并处理数据流
```
多个采购单 → 商品提取 → 去重汇总 → 冲突处理 → 合并结果 → 输出文件
```
### 数据结构设计
#### 商品数据结构
```python
{
'barcode': str, # 商品条码
'name': str, # 商品名称
'specification': str, # 商品规格
'quantity': int, # 数量
'unit': str, # 单位
'unit_price': float, # 单价
'total_price': float, # 总价
'source_file': str, # 来源文件
'category': str, # 商品类别
'brand': str # 品牌
}
```
#### 处理记录数据结构
```python
{
'image_file': str, # 输入图片路径
'output_file': str, # 输出文件路径
'processing_time': str, # 处理时间
'status': str, # 处理状态
'error_message': str # 错误信息(如果有)
}
```
#### OCR结果数据结构
```python
{
'tables': List[Dict], # 表格数据
'text': str, # 文本内容
'excel_data': bytes, # Excel文件数据
'confidence': float, # 识别置信度
'processing_time': float # 处理耗时
}
```
## 关键技术实现
### 并发处理机制
#### 多线程批量处理
```python
class BatchProcessor:
def __init__(self, max_workers: int = 4):
self.max_workers = max_workers
self.executor = ThreadPoolExecutor(max_workers=max_workers)
def process_batch(self, items: List[Any], processor_func) -> List[Any]:
# 使用线程池并发处理
futures = [self.executor.submit(processor_func, item) for item in items]
# 收集处理结果
results = []
for future in as_completed(futures):
try:
result = future.result()
results.append(result)
except Exception as e:
logger.error(f"处理失败: {e}")
results.append(None)
return results
```
### 错误处理和重试机制
#### API调用重试机制
```python
def call_with_retry(func, max_retries=3, retry_delay=2):
for attempt in range(max_retries):
try:
result = func()
return result
except Exception as e:
logger.warning(f"第{attempt + 1}次尝试失败: {e}")
if attempt < max_retries - 1:
time.sleep(retry_delay)
else:
logger.error(f"所有重试尝试都失败")
raise
```
### 内存优化策略
#### 大文件处理
```python
def process_large_file(file_path: str, chunk_size: int = 1000):
# 使用生成器避免一次性加载大文件
def read_in_chunks():
with pd.read_excel(file_path, chunksize=chunk_size) as reader:
for chunk in reader:
yield chunk
# 逐块处理
for chunk in read_in_chunks():
process_chunk(chunk)
# 及时清理内存
del chunk
gc.collect()
```
### 配置管理实现
#### 动态配置加载
```python
class ConfigManager:
def __init__(self, config_file: str):
self.config_file = config_file
self.config = configparser.ConfigParser()
self.load_config()
def load_config(self):
if os.path.exists(self.config_file):
self.config.read(self.config_file, encoding='utf-8')
else:
self.create_default_config()
def get(self, section: str, option: str, fallback: Any = None) -> Any:
return self.config.get(section, option, fallback=fallback)
def getint(self, section: str, option: str, fallback: int = 0) -> int:
return self.config.getint(section, option, fallback=fallback)
def getboolean(self, section: str, option: str, fallback: bool = False) -> bool:
return self.config.getboolean(section, option, fallback=fallback)
```
### 日志系统设计
#### 结构化日志记录
```python
import logging
from logging.handlers import RotatingFileHandler
def setup_logging(log_file: str = 'logs/app.log'):
# 创建logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
# 创建文件处理器(带轮转)
file_handler = RotatingFileHandler(
log_file, maxBytes=10*1024*1024, backupCount=5
)
file_handler.setLevel(logging.DEBUG)
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 创建格式化器
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 添加格式化器到处理器
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# 添加处理器到logger
logger.addHandler(file_handler)
logger.addHandler(console_handler)
return logger
```
### 性能监控和优化
#### 处理时间统计
```python
import time
from functools import wraps
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
processing_time = end_time - start_time
logger.info(f"{func.__name__} 执行耗时: {processing_time:.2f}秒")
return result
return wrapper
```
### 安全性考虑
#### API密钥管理
```python
import os
from cryptography.fernet import Fernet
class SecureConfig:
def __init__(self, encryption_key: str = None):
self.cipher = Fernet(encryption_key or self._get_or_create_key())
def _get_or_create_key(self) -> str:
key_file = 'config/.key'
if os.path.exists(key_file):
with open(key_file, 'rb') as f:
return f.read()
else:
key = Fernet.generate_key()
with open(key_file, 'wb') as f:
f.write(key)
return key
def encrypt(self, data: str) -> str:
return self.cipher.encrypt(data.encode()).decode()
def decrypt(self, encrypted_data: str) -> str:
return self.cipher.decrypt(encrypted_data.encode()).decode()
```
这个技术架构文档详细描述了益选-OCR订单处理系统的技术实现细节,包括系统架构设计、模块职责划分、核心算法流程、数据流设计以及关键技术实现。系统设计遵循软件工程最佳实践,确保系统的可靠性、可维护性和可扩展性。
+476
View File
@@ -0,0 +1,476 @@
# 益选-OCR订单处理系统 - 用户操作手册
## 界面功能介绍
### 主界面布局
#### 启动界面
系统启动时会显示一个简洁的启动界面,包含:
- **系统标题**:益选-OCR订单处理系统
- **版本信息**:显示当前系统版本
- **加载进度**:显示系统初始化进度
#### 主操作界面
主界面采用现代化设计风格,包含以下主要区域:
##### 顶部工具栏
- **系统标题**:显示在界面顶部中央
- **主题切换**:支持浅色/深色主题切换
- **帮助按钮**:访问用户手册和系统信息
##### 功能按钮区域
- **OCR识别按钮**:启动图片识别功能
- **Excel处理按钮**:处理OCR识别结果
- **采购单合并按钮**:合并多个采购单
- **完整流程按钮**:执行完整的处理流程
- **配置管理按钮**:打开系统配置界面
##### 状态栏
- **状态显示**:显示当前系统状态(就绪/处理中/完成)
- **进度条**:显示处理进度百分比
- **时间信息**:显示处理开始时间和耗时
##### 日志显示区域
- **实时日志**:显示处理过程的详细日志
- **颜色标识**:不同级别的日志使用不同颜色
- **滚动支持**:支持日志内容的滚动查看
- **清空功能**:可以清空当前日志内容
### 功能界面详解
#### OCR识别界面
当点击OCR识别按钮时,系统会:
1. 自动扫描`data/input`目录中的图片文件
2. 显示找到的图片文件数量
3. 开始批量OCR识别处理
4. 实时显示处理进度和结果
#### Excel处理界面
处理OCR结果时,系统会:
1. 读取OCR生成的Excel文件
2. 提取商品信息(条码、名称、规格、数量、单价)
3. 应用数据清洗和标准化规则
4. 生成标准格式的采购单
#### 采购单合并界面
合并功能界面提供:
1. 选择要合并的采购单文件
2. 显示合并进度和状态
3. 展示合并结果摘要
4. 提供合并后文件的快速访问
#### 配置管理界面
配置界面包含:
1. **基本设置**:输入输出目录配置
2. **API配置**:百度OCR API密钥设置
3. **性能设置**:批量处理参数配置
4. **高级设置**:其他高级参数配置
## 详细操作步骤
### 首次使用设置
#### 1. 系统安装和配置
**步骤1.1**:确保系统环境满足要求
- 操作系统:Windows 7/8/10/11LinuxmacOS
- Python版本:3.8或更高版本(如使用源码)
**步骤1.2**:获取百度OCR API密钥
1. 访问百度智能云官网(https://cloud.baidu.com/
2. 注册并登录账号
3. 进入"文字识别"服务
4. 创建应用,获取API Key和Secret Key
5. 记录这两个密钥,后续配置需要使用
**步骤1.3**:配置系统参数
1. 打开`config.ini`文件
2. 在`[API]`部分填入获取的密钥:
```ini
[API]
api_key = 你的API密钥
secret_key = 你的Secret密钥
timeout = 30
max_retries = 3
retry_delay = 2
```
3. 配置输入输出路径:
```ini
[Paths]
input_folder = data/input
output_folder = data/output
temp_folder = data/temp
```
#### 2. 准备图片文件
**步骤2.1**:创建图片文件夹
在系统目录下确保存在以下文件夹:
- `data/input/` - 存放待处理的采购单图片
- `data/output/` - 存放处理结果
**步骤2.2**:图片质量要求
- **分辨率**:建议300DPI或更高
- **格式**:支持JPG、JPEG、PNG、BMP格式
- **大小**:单张图片不超过4MB
- **清晰度**:文字清晰,无模糊、反光
**步骤2.3**:图片命名规范
- 使用有意义的文件名,如"采购单_20250101.jpg"
- 避免使用特殊字符和空格
- 建议使用日期和序号进行命名
### 基本操作流程
#### 单张图片处理流程
**步骤1**:启动系统
1. 双击`OCR订单处理系统.exe`或在命令行运行`python 启动器.py`
2. 等待系统初始化完成(状态栏显示"就绪")
**步骤2**:放置图片文件
1. 将采购单图片文件复制到`data/input/`目录
2. 确保图片格式正确,质量良好
**步骤3**:执行OCR识别
1. 点击"OCR识别"按钮
2. 系统会自动扫描输入目录
3. 在日志区域查看处理进度
4. 等待识别完成
**步骤4**:处理Excel结果
1. 点击"Excel处理"按钮
2. 系统会读取OCR生成的Excel文件
3. 提取商品信息并标准化
4. 生成标准采购单格式
**步骤5**:查看处理结果
1. 处理完成后,点击"打开输出目录"
2. 查看生成的Excel采购单文件
3. 核对商品信息是否正确
#### 批量处理流程
**步骤1**:准备多张图片
1. 将多个采购单图片放入`data/input/`目录
2. 确保所有图片都符合质量要求
**步骤2**:执行完整流程
1. 点击"完整流程"按钮
2. 系统会依次执行:
- OCR识别所有图片
- 处理所有Excel文件
- 合并相同商品
3. 查看批量处理结果
**步骤3**:监控处理进度
1. 观察状态栏的进度条
2. 查看日志区域的详细信息
3. 如有错误,查看错误信息并处理
### 高级功能使用
#### 条码映射配置
**步骤1**:打开条码映射文件
1. 编辑`config/barcode_mappings.json`文件
2. 按照JSON格式添加映射规则
**步骤2**:配置映射规则
```json
{
"原条码1": "目标条码1",
"原条码2": "目标条码2",
"6901234567890": "新条码123456"
}
```
**步骤3**:应用映射规则
1. 系统在处理时会自动应用映射
2. 原条码会被替换为目标条码
3. 适用于不同系统的条码适配
#### 自定义模板使用
**步骤1**:准备模板文件
1. 在`templates/`目录放置Excel模板
2. 模板应包含标准的采购单格式
**步骤2**:配置模板
1. 在`config.ini`中配置模板:
```ini
[Templates]
purchase_order = 银豹-采购单模板.xls
```
**步骤3**:使用模板
1. 系统生成采购单时会使用指定模板
2. 确保模板格式与系统要求匹配
#### 性能调优
**步骤1**:调整批量处理参数
`config.ini`中配置:
```ini
[Performance]
max_workers = 4 # 最大工作线程数
batch_size = 5 # 每批处理文件数
skip_existing = true # 跳过已处理文件
```
**步骤2**:优化处理策略
- 根据电脑性能调整线程数
- 大批量处理时适当增加批大小
- 启用跳过已处理文件以提高效率
**步骤3**:监控资源使用
- 观察CPU和内存使用情况
- 根据系统资源调整参数
- 避免设置过高的并发数
## 常见问题解答
### Q1: 系统无法启动怎么办?
**A1**:
1. 检查Python环境是否正确安装(源码版本)
2. 确认所有依赖库已安装:`pip install -r requirements.txt`
3. 检查是否有足够的系统权限
4. 查看错误日志获取详细信息
### Q2: OCR识别失败如何处理?
**A2**:
1. 检查图片质量是否满足要求
2. 确认图片格式是否支持
3. 验证百度OCR API密钥是否正确
4. 检查网络连接是否正常
5. 尝试降低图片分辨率或压缩图片大小
### Q3: 识别结果不准确怎么办?
**A3**:
1. 提高图片扫描分辨率
2. 确保图片光线充足,无反光
3. 检查采购单格式是否规范
4. 手动校正重要的商品信息
5. 考虑使用更高质量的扫描设备
### Q4: 批量处理时系统卡顿?
**A4**:
1. 降低批量处理的并发线程数
2. 减小每批处理的文件数量
3. 关闭其他占用资源的程序
4. 增加系统内存或使用更高配置的电脑
### Q5: 生成的Excel文件打不开?
**A5**:
1. 确认已安装Excel或兼容软件
2. 检查文件是否完整生成
3. 验证文件路径是否正确
4. 尝试使用不同版本的Excel打开
5. 检查是否有足够的磁盘空间
### Q6: 条码映射不生效?
**A6**:
1. 检查JSON文件格式是否正确
2. 确认条码映射文件路径正确
3. 验证原条码和目标条码格式
4. 重启系统使配置生效
5. 检查日志中是否有映射相关的错误信息
### Q7: 处理速度很慢怎么办?
**A7**:
1. 优化网络连接,使用稳定的网络
2. 调整批量处理参数
3. 使用本地缓存减少API调用
4. 考虑使用更高性能的硬件
5. 分批处理大量文件,避免一次性处理过多
### Q8: 系统显示"未找到可合并的文件"?
**A8**:
1. 确认输出目录中有Excel文件
2. 检查文件格式是否符合要求
3. 验证文件是否包含有效的商品数据
4. 确保文件没有被其他程序锁定
## 故障排除指南
### 系统启动问题
#### 症状:双击程序无反应
**可能原因**
1. 系统缺少运行库
2. 防病毒软件阻止运行
3. 程序文件损坏
**解决方案**
1. 以管理员身份运行程序
2. 临时关闭防病毒软件
3. 重新下载或编译程序
4. 检查系统事件查看器中的错误日志
#### 症状:显示缺少DLL文件
**可能原因**
1. 系统缺少Visual C++运行库
2. .NET Framework版本过低
**解决方案**
1. 安装Visual C++ 2015-2022运行库
2. 更新.NET Framework到最新版本
3. 安装所有Windows更新
### OCR识别问题
#### 症状:所有图片都识别失败
**排查步骤**
1. **检查API密钥**
- 确认config.ini中的API密钥正确
- 验证密钥是否过期或被禁用
- 检查百度智能云账户余额
2. **检查网络连接**
- 测试能否访问百度智能云服务
- 检查防火墙设置
- 验证代理设置(如使用代理)
3. **检查图片文件**
- 确认图片格式正确
- 验证图片文件未损坏
- 检查文件大小是否超限
#### 症状:部分图片识别失败
**可能原因**
1. 图片质量问题
2. 图片格式不支持
3. 文件大小超过限制
**解决方案**
1. 重新扫描或拍摄图片
2. 转换图片格式为支持的格式
3. 压缩或调整图片大小
4. 手动处理失败的图片
### Excel处理问题
#### 症状:Excel文件生成失败
**排查方法**
1. **检查磁盘空间**:确保有足够的可用空间
2. **验证文件权限**:确认有写入权限
3. **检查Excel格式**:确认模板文件格式正确
4. **查看错误日志**:获取详细的错误信息
#### 症状:商品信息提取错误
**常见原因**
1. Excel格式不规范
2. 表头识别错误
3. 数据格式不统一
**解决方法**
1. 标准化Excel格式
2. 手动指定表头行
3. 使用数据清洗功能
4. 调整提取规则
### 合并功能问题
#### 症状:合并后商品信息丢失
**可能原因**
1. 商品关键信息缺失
2. 合并规则设置不当
3. 文件格式不兼容
**解决方案**
1. 确保所有商品都有条码或名称
2. 调整合并规则配置
3. 统一文件格式和结构
### 性能问题
#### 症状:系统响应缓慢
**优化建议**
1. **减少并发数**:降低max_workers值
2. **减小批大小**:减少batch_size值
3. **清理临时文件**:定期清理temp目录
4. **增加内存**:关闭其他占用内存的程序
#### 症状:处理过程中崩溃
**排查步骤**
1. 检查系统内存使用情况
2. 查看Windows事件日志
3. 分析错误日志文件
4. 逐步减少处理量测试
## 最佳实践建议
### 日常使用建议
#### 1. 文件管理最佳实践
- **分类存储**:按日期或供应商分类存储图片文件
- **规范命名**:使用统一的文件命名规则
- **定期清理**:定期清理已处理的文件和临时文件
- **备份重要数据**:定期备份重要的采购单数据
#### 2. 图片质量优化
- **扫描设置**:使用300DPI或更高分辨率扫描
- **光线控制**:确保充足均匀的光线
- **避免反光**:使用防反光材料或调整角度
- **保持平整**:确保采购单平整无折叠
#### 3. 处理效率提升
- **批量操作**:尽量使用批量处理功能
- **合理分批**:将大量文件分成小批次处理
- **预处理检查**:处理前检查图片质量
- **参数调优**:根据硬件配置调整处理参数
### 系统维护建议
#### 1. 定期维护任务
- **日志清理**:定期清理旧的日志文件
- **临时文件清理**:清理temp目录中的临时文件
- **配置备份**:定期备份配置文件
- **更新检查**:关注系统更新和补丁
#### 2. 性能优化
- **硬件升级**:根据需要升级内存和CPU
- **存储优化**:使用SSD提高文件读写速度
- **网络优化**:确保稳定的网络连接
- **系统优化**:关闭不必要的服务和程序
#### 3. 安全建议
- **API密钥保护**:妥善保管API密钥,不要泄露
- **文件权限**:设置适当的文件访问权限
- **数据加密**:对敏感数据进行加密存储
- **定期备份**:建立定期备份机制
### 业务流程优化
#### 1. 采购流程整合
- **标准化格式**:统一采购单格式和标准
- **自动化集成**:与其他业务系统集成
- **数据验证**:建立数据质量检查机制
- **异常处理**:制定异常情况处理流程
#### 2. 质量控制
- **准确性检查**:定期抽查处理结果的准确性
- **性能监控**:监控系统处理性能指标
- **错误分析**:分析常见错误类型和原因
- **持续改进**:根据使用情况优化流程
#### 3. 团队协作
- **权限管理**:根据角色设置不同的操作权限
- **操作规范**:制定标准化的操作流程
- **培训体系**:建立用户培训和技能提升机制
- **经验分享**:定期分享使用经验和技巧
### 故障预防
#### 1. 预防措施
- **定期测试**:定期测试系统各项功能
- **监控告警**:建立系统监控和告警机制
- **容量规划**:根据业务量规划系统容量
- **应急预案**:制定系统故障应急预案
#### 2. 问题响应
- **快速定位**:建立问题快速定位和诊断机制
- **分级处理**:根据问题严重程度分级处理
- **升级机制**:建立问题升级和汇报机制
- **恢复流程**:制定系统恢复和重启流程
通过遵循这些最佳实践建议,用户可以更高效、更稳定地使用益选-OCR订单处理系统,获得更好的使用体验和业务价值。
+79
View File
@@ -0,0 +1,79 @@
# OCR订单处理系统优化实施建议与周计划
## 目标
- 提升稳定性与可维护性:统一日志、进度回调、错误提示与配置持久化
- 优化用户体验:一致的弹窗与目录打开、清晰进度与结果摘要
- 控制成本与性能:合理并发、避免重复处理、日志滚动归档
## 范围
- 应用层(GUI、设置、交互)
- 服务层(OCR、Excel、合并)
- 工具层(日志、配置、校验)
## 已完成优化
- 日志滚动归档:单文件5MB,保留3个备份(RotatingFileHandler
- 统一GUI日志挂载:`init_gui_logger`/`dispose_gui_logger`
- 统一进度显示:`ProgressReporter` 接入主要流程
- 结果目录一致化:Excel/合并/完整流程优先打开 `data/result`
- Excel引擎错误提示:统一弹窗附带安装建议(openpyxl/xlrd
- 用户设置持久化:窗口尺寸与主题记忆(`data/user_settings.json`
## 待实施优化(按周迭代)
### 第1周:基础能力统一与设置面板扩展
- 任务1:系统设置面板扩展
- 新增设置项:日志级别、并发参数(`max_workers`/`batch_size`)、模板路径、输入/输出/结果目录
- 写入 `user_settings.json` 并同步到 `config.ini`(通过 `ConfigManager.update/save_config`
- 验收:设置变更后重启仍生效;操作流程读取最新参数
- 任务2:进度回调全链路接入(服务层)
- 为合并流程(`PurchaseOrderMerger`)添加 `progress_cb`97%→100%),UI显示阶段进度
- 完整流程串联 OCR→Excel→合并 三段进度
- 验收:状态栏进度从0→100%,阶段文案一致、无跳变
- 任务3:错误提示模板统一
- 抽象错误弹窗生成:标题/描述/建议操作(缺依赖、模板列缺失、文件格式异常)
- 在服务层捕获常见错误并附带建议(不用泄露敏感信息)
- 验收:触发典型错误时弹窗一致、日志有详细堆栈
### 第2周:兼容性与可维护性增强
- 任务4:供应商配置校验(schema)
- 对 `suppliers_config.json` 加载前校验:字段完整、列映射存在、清洗/计算规则可执行
- 失败时聚合错误列表并弹窗提示
- 验收:错误配置阻止加载且提示清晰;正确配置即时生效
- 任务5:拖拽与最近文件
- 日志面板上方增加拖拽区域(图片/Excel),拖入即处理
- 最近处理文件列表(持久化、可清空)
- 验收:拖拽成功处理;最近列表记忆与操作正常
- 任务6:线程池复用与批次自适应
- OCR批量识别的线程池复用;根据 CPU 核心数与文件量自适应 `max_workers``batch_size`
- 验收:大批量处理性能平稳,无过载或明显阻塞
### 第3周:数据处理与模板管理
- 任务7:列映射向导
- 弹窗展示识别到的列与标准列,支持手动修正并保存到供应商配置
- 验收:手动修正列映射后处理成功;配置热重载生效
- 任务8:多模板管理与校验
- 支持按供应商选择模板;校验模板表头与系统标准列,给出差异提示
- 验收:不同模板无错填;差异提示友好
### 第4周:测试与交付质量
- 任务9:单元测试与烟雾测试
- 核心逻辑单元测试(规格解析、数量反推、列映射、模板填充)
- 端到端小样本烟雾测试(OCR→Excel→采购单)
- 验收:测试通过率达标;烟雾测试稳定
- 任务10:打包与版本信息
- 打包资源校验(模板与配置存在性);显示版本号与更新日志入口
- 验收:打包可用;启动显示版本信息;更新日志可查看
## 验收标准与度量
- 功能验收:设置变更生效、进度准确、错误提示一致
- 稳定性:长时间运行日志不会膨胀;批量处理无明显卡顿
- 可维护性:配置驱动、模块职责清晰、代码重复显著减少
## 风险与回滚
- 配置同步风险:提供回滚策略(保留最近一次有效 `config.ini``user_settings.json` 备份)
- 依赖安装风险:提供一键安装与离线安装说明
- 模板差异风险:在校验失败时阻断流程并提示修复路径
## 实施说明
- 每周开始前将与您确认要启动的任务;每个任务完成后提交变更摘要与验证结果
- 如发现新需求或变化,计划将滚动更新,并在执行前与您确认
+161
View File
@@ -0,0 +1,161 @@
# OCR订单处理系统 - 更新日志
## v1.5 (2025-05-09)
### 功能改进
- 烟草订单处理结果展示:改进烟草订单处理完成后的结果展示界面
- 美化结果展示界面,显示订单时间、总金额和处理条目数
- 添加文件信息展示,包括文件大小和创建时间
- 提供打开文件、打开所在文件夹等便捷操作按钮
- 统一与Excel处理结果展示风格,提升用户体验
- 增强结果文件路径解析能力,确保正确找到并显示结果文件
- 条码映射编辑功能:
- 添加图形化条码映射编辑工具,方便管理条码映射和特殊处理规则
- 支持添加、修改和删除条码映射关系
- 支持配置特殊处理规则,如乘数、目标单位、固定单价等
- 自动保存到配置文件,便于后续使用
### 问题修复
- 修复烟草订单处理时出现双重弹窗问题
- 修复烟草订单处理完成后结果展示弹窗无法正常显示的问题
- 修复ConfigParser兼容性问题,支持标准ConfigParser对象
- 修复百度OCR客户端中getint方法调用不兼容问题
- 修复OCRService中缺少batch_process方法的问题,确保OCR功能正常工作
- 改进日志管理,确保所有日志正确关闭
- 优化UI界面,统一按钮样式
- 修复启动器中处理烟草订单按钮的显示样式
- 修复run.py中close_logger调用缺少参数的问题
### 代码改进
- 改进TobaccoService类对配置的处理方式,使用标准get方法
- 添加fallback机制以增强配置健壮性
- 优化启动器中结果预览逻辑,避免重复弹窗
- 统一UI组件风格,提升用户体验
- 增强错误处理,提供更清晰的错误信息
## v1.4 (2025-05-09)
### 新功能
- 烟草订单处理:新增烟草公司特定格式订单明细文件处理功能
- 支持自动处理标准烟草订单明细格式
- 根据烟草公司"盒码"作为条码生成银豹采购单
- 自动将"订单量"转换为"采购量"并计算采购单价
- 处理结果以银豹采购单格式保存,方便直接导入
### 功能优化
- 配置兼容性:优化配置处理逻辑,兼容标准ConfigParser对象
- 启动器优化:启动器界面增加"处理烟草订单"功能按钮
- 代码结构优化:将烟草订单处理功能模块化,集成到整体服务架构
## v1.3 (2025-07-20)
### 功能优化
- 采购单赠品处理逻辑优化:修改了银豹采购单中赠品的处理方式
- 之前:赠品数量单独填写在"赠送量"列,与正常采购量分开处理
- 现在:将赠品数量合并到采购量中,赠送量列留空
- 有正常商品且有赠品的情况:采购量 = 正常商品数量 + 赠品数量,单价 = 原单价 × 正常商品数量 ÷ 总数量
- 只有赠品的情况:采购量填写赠品数量,单价为0
- 更新说明:经用户反馈,赠品处理逻辑已还原为原始方式,正常商品数量和赠品数量分开填写
## v1.2 (2025-07-15)
### 功能优化
- 规格提取优化:改进了从商品名称中提取规格的逻辑,优先识别"容量*数量"格式
- 例如从"美汁源果粒橙1.8L*8瓶"能准确提取"1.8L*8"而非错误的"1.8L*1"
- 规格解析增强:优化`parse_specification`方法,能正确解析"1.8L*8"格式规格,确保准确提取包装数量
- 单位推断增强:在`extract_product_info`方法中增加新逻辑,当单位为空且有条码、规格、数量、单价时,根据规格格式(如容量*数量格式或简单数量*数量格式)自动推断单位为"件"
- 件单位处理优化:确保当设置单位为"件"时,正确触发UnitConverter单位处理逻辑,将数量乘以包装数量,单价除以包装数量,单位转为"瓶"
- 整体改进:提高了系统处理复杂格式商品名称和规格的能力,使单位转换更加准确可靠
- 规格提取逻辑修正:修复了在Excel中已有规格信息时仍会从商品名称推断规格的问题,现在系统会优先使用Excel中的数据,只有在规格为空时才尝试从商品名称推断
## v1.1 (2025-05-07)
### 功能更新
- 单位自动推断:当单位为空但有商品编码、规格、数量、单价等信息,且规格符合容量*数量格式时,自动将单位设置为"件"并按照件的处理规则进行转换
- 规格解析优化:改进对容量*数量格式规格的解析,如"1.8L*8"能正确识别包装数量为8
- 规格提取增强:从商品名称中提取"容量*数量"格式的规格时,能正确识别如"美汁源果粒橙1.8L*8瓶"中的"1.8L*8"部分
- 条码映射功能:增加特定条码的自动映射功能,支持将特定条码自动转换为指定的目标条码
- 6920584471055 → 6920584471017
- 6925861571159 → 69021824
- 6923644268923 → 6923644268480
- 条码映射后会继续按照件/箱等单位的标准处理规则进行数量和单价的转换
## v1.0 (2025-05-02)
### 主要功能
- 图像OCR识别:支持对采购单图片进行OCR识别并生成Excel文件
- Excel数据处理:智能处理Excel文件,提取和转换商品信息
- 采购单生成:按照模板格式生成标准采购单Excel文件
- 采购单合并:支持多个采购单合并为一个总单
- 图形界面:提供简洁直观的操作界面
- 命令行支持:支持命令行调用,方便自动化处理
### 技术改进
- 模块化架构:重构代码为配置、核心功能、服务和CLI等模块
- 单位智能处理:完善的单位转换规则,支持多种计量单位
- 规格智能推断:从商品名称自动推断规格信息
- 日志管理:完善的日志记录系统,支持终端和GUI同步显示
- 表头智能识别:自动识别Excel中的表头位置,兼容多种格式
- 改进用户体验:界面优化,批量处理支持,实时状态反馈
## v1.5.1 (2024-03-21)
- 修复了配置管理相关的问题:
- 修复了`config.ini`文件被意外重置的问题
- 优化了配置加载逻辑,确保保留现有配置值
- 添加了配置缺失项自动补充功能
- 新增系统设置功能:
- 添加了图形化配置设置界面
- 支持API设置、路径设置、性能设置和文件设置
- 所有设置更改实时保存
- 移除了统计报告功能,替换为更实用的系统设置功能
- 优化了用户界面和交互体验
## v1.5.0 (2024-03-20)
- 添加了统计与报告功能
- 添加了键盘快捷键支持
- 优化了用户界面
- 删除了不必要的文件
- 更新了README.md
- 创建了更新日志文档
## v1.4.0 (2024-03-19)
- 添加了自定义弹窗演示
- 优化了错误处理
- 改进了日志记录
## v1.3.0 (2024-03-18)
- 添加了条码映射功能
- 优化了文件处理逻辑
- 改进了用户界面
## v1.2.0 (2024-03-17)
- 添加了批量处理功能
- 优化了性能
- 改进了错误处理
## v1.1.0 (2024-03-16)
- 添加了Excel处理功能
- 优化了OCR识别
- 改进了用户界面
## v1.0.0 (2024-03-15)
- 初始版本发布
- 基本OCR功能
- 基本用户界面
## v1.5.2 (2024-03-21)
- 修复了方法名称不匹配的问题:
- 将`process_latest_excel`方法调用改为`process_excel`
- 确保Excel处理功能正常工作
- 优化了错误处理和日志记录
## v1.5.3 (2024-03-21)
- 优化了完整流程处理逻辑:
- 修改了OCR处理逻辑,当遇到已处理的图片时自动跳过并继续执行
- 改进了错误处理,避免因图片已处理而中断流程
- 优化了日志提示信息,提供更清晰的处理状态反馈
- 改进了OCRService的process_image方法:
- 添加了文件存在性检查
- 添加了文件类型验证
- 添加了已处理文件检查
- 优化了错误处理和日志记录
-27
View File
@@ -1,27 +0,0 @@
services:
backend:
build:
context: .
dockerfile: Dockerfile.backend
container_name: yixuan-backend
ports:
- "18889:18889"
volumes:
- ./data:/app/data
- ./config.ini:/app/config.ini:ro
- ./config:/app/config:ro
- ./templates:/app/templates:ro
env_file:
- .env
restart: unless-stopped
frontend:
build:
context: .
dockerfile: Dockerfile.frontend
container_name: yixuan-frontend
ports:
- "18888:18888"
depends_on:
- backend
restart: unless-stopped
+23 -52
View File
@@ -10,6 +10,7 @@ OCR订单处理系统 - 无界面自动化接口
import os import os
import sys import sys
import logging
import time import time
import argparse import argparse
import json import json
@@ -24,9 +25,15 @@ from app.services.ocr_service import OCRService
from app.services.order_service import OrderService from app.services.order_service import OrderService
from app.services.tobacco_service import TobaccoService from app.services.tobacco_service import TobaccoService
from app.services.special_suppliers_service import SpecialSuppliersService from app.services.special_suppliers_service import SpecialSuppliersService
from app.core.utils.log_utils import get_logger, set_log_level from app.core.utils.log_utils import set_log_level
logger = get_logger("HeadlessAPI") # 配置日志输出到 stderr
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
stream=sys.stderr
)
logger = logging.getLogger("HeadlessAPI")
def get_latest_file(directory: str, extensions: List[str]) -> Optional[str]: def get_latest_file(directory: str, extensions: List[str]) -> Optional[str]:
"""获取目录中最新的指定后缀文件""" """获取目录中最新的指定后缀文件"""
@@ -45,8 +52,8 @@ def get_latest_file(directory: str, extensions: List[str]) -> Optional[str]:
latest_file = max(files, key=lambda p: p.stat().st_mtime) latest_file = max(files, key=lambda p: p.stat().st_mtime)
return str(latest_file) return str(latest_file)
def update_barcode_mapping(barcode: str, target_barcode: str = None, multiplier: float = None, unit: str = None, price: float = None, spec: str = None): def update_barcode_mapping(barcode: str, target_barcode: str):
"""更新条码映射或特殊处理配置""" """更新条码映射"""
try: try:
config_path = os.path.join("config", "barcode_mappings.json") config_path = os.path.join("config", "barcode_mappings.json")
mappings = {} mappings = {}
@@ -54,38 +61,15 @@ def update_barcode_mapping(barcode: str, target_barcode: str = None, multiplier:
with open(config_path, 'r', encoding='utf-8') as f: with open(config_path, 'r', encoding='utf-8') as f:
mappings = json.load(f) mappings = json.load(f)
# 获取或创建该条码的配置 mappings[barcode] = target_barcode
config = mappings.get(barcode, {})
if target_barcode:
config["map_to"] = target_barcode
config["description"] = config.get("description", "") + f" 条码映射 -> {target_barcode}"
if multiplier is not None:
config["multiplier"] = multiplier
config["description"] = config.get("description", "") + f" 数量倍数*{multiplier}"
if unit:
config["target_unit"] = unit
if price is not None:
config["fixed_price"] = price
if spec:
config["specification"] = spec
if not config.get("description"):
config["description"] = f"特殊条码配置: {barcode}"
mappings[barcode] = config
with open(config_path, 'w', encoding='utf-8') as f: with open(config_path, 'w', encoding='utf-8') as f:
json.dump(mappings, f, ensure_ascii=False, indent=2) json.dump(mappings, f, ensure_ascii=False, indent=2)
logger.info(f"成功更新条码配置: {barcode} -> {config}") logger.info(f"成功更新条码映射: {barcode} -> {target_barcode}")
return True return True
except Exception as e: except Exception as e:
logger.error(f"更新条码配置失败: {e}") logger.error(f"更新条码映射失败: {e}")
return False return False
def run_pipeline(args): def run_pipeline(args):
@@ -96,28 +80,19 @@ def run_pipeline(args):
start_time = time.perf_counter() start_time = time.perf_counter()
final_excel = None final_excel = None
input_folder = config_manager.get('Paths', 'input_folder', fallback='data/input')
output_folder = config_manager.get('Paths', 'output_folder', fallback='data/output')
# 1. 处理条码映射更新 # 1. 处理条码映射更新
if args.update_mapping: if args.update_mapping:
if not args.barcode: if not args.barcode or not args.target:
print("ERROR: --barcode is required for --update-mapping", file=sys.stderr) print("ERROR: --barcode and --target are required for --update-mapping", file=sys.stderr)
return None return None
if update_barcode_mapping(args.barcode, args.target):
# 至少需要一个更新项 print(f"SUCCESS: Mapping updated {args.barcode} -> {args.target}")
if not any([args.target, args.multiplier, args.unit, args.price, args.spec]):
print("ERROR: At least one update option (--target, --multiplier, --unit, --price, --spec) is required", file=sys.stderr)
return None
if update_barcode_mapping(args.barcode, args.target, args.multiplier, args.unit, args.price, args.spec):
print(f"SUCCESS: Barcode configuration updated for {args.barcode}")
return "MAPPING_UPDATED" return "MAPPING_UPDATED"
return None return None
# 2. 烟草公司处理 (显式指定) # 2. 烟草公司处理 (显式指定)
if args.tobacco: if args.tobacco:
input_path = args.input or get_latest_file(output_folder, [".xlsx", ".xls"]) input_path = args.input or get_latest_file("data/output", [".xlsx", ".xls"])
if not input_path: if not input_path:
print("ERROR: No tobacco order file found.", file=sys.stderr) print("ERROR: No tobacco order file found.", file=sys.stderr)
return None return None
@@ -128,7 +103,7 @@ def run_pipeline(args):
# 3. 蓉城易购处理 (显式指定) # 3. 蓉城易购处理 (显式指定)
elif args.rongcheng: elif args.rongcheng:
input_path = args.input or get_latest_file(output_folder, [".xlsx", ".xls"]) input_path = args.input or get_latest_file("data/output", [".xlsx", ".xls"])
if not input_path: if not input_path:
print("ERROR: No Rongcheng Yigou order file found.", file=sys.stderr) print("ERROR: No Rongcheng Yigou order file found.", file=sys.stderr)
return None return None
@@ -138,7 +113,7 @@ def run_pipeline(args):
# 4. 普通 Excel 处理 (支持自动识别烟草/蓉城/杨碧月) # 4. 普通 Excel 处理 (支持自动识别烟草/蓉城/杨碧月)
elif args.excel: elif args.excel:
input_path = args.input or get_latest_file(input_folder, [".xlsx", ".xls"]) input_path = args.input or get_latest_file("data/input", [".xlsx", ".xls"])
if not input_path: if not input_path:
print("ERROR: No Excel file found in input.", file=sys.stderr) print("ERROR: No Excel file found in input.", file=sys.stderr)
return None return None
@@ -148,9 +123,9 @@ def run_pipeline(args):
# 5. 智能处理 (默认逻辑:自动判断图片还是 Excel) # 5. 智能处理 (默认逻辑:自动判断图片还是 Excel)
else: else:
input_path = args.input or get_latest_file(input_folder, [".jpg", ".jpeg", ".png", ".bmp", ".xlsx", ".xls"]) input_path = args.input or get_latest_file("data/input", [".jpg", ".jpeg", ".png", ".bmp", ".xlsx", ".xls"])
if not input_path: if not input_path:
print(f"ERROR: No input file found in {input_folder}.", file=sys.stderr) print("ERROR: No input file found in data/input.", file=sys.stderr)
return None return None
ext = os.path.splitext(input_path)[1].lower() ext = os.path.splitext(input_path)[1].lower()
@@ -202,10 +177,6 @@ if __name__ == "__main__":
parser.add_argument('--barcode', help='待映射的原始条码 (用于 --update-mapping)') parser.add_argument('--barcode', help='待映射的原始条码 (用于 --update-mapping)')
parser.add_argument('--target', help='目标条码 (用于 --update-mapping)') parser.add_argument('--target', help='目标条码 (用于 --update-mapping)')
parser.add_argument('--multiplier', type=float, help='数量倍数 (例如箱转瓶填写30)')
parser.add_argument('--unit', help='目标单位 (例如"")')
parser.add_argument('--price', type=float, help='固定单价')
parser.add_argument('--spec', help='固定规格 (例如"1*30")')
args = parser.parse_args() args = parser.parse_args()
result = run_pipeline(args) result = run_pipeline(args)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
2025-08-16 00:52:17,210 - app.core.excel.handlers.barcode_mapper - INFO - 条码映射: 6937003706322 -> 6937003703833
2025-11-15 16:34:22,181 - app.core.excel.handlers.barcode_mapper - INFO - 条码映射: 6923450653012 -> 69021343
File diff suppressed because it is too large Load Diff
+343
View File
@@ -0,0 +1,343 @@
2025-08-16 00:52:16,853 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-08-16 00:52:16,861 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 20:52:59,975 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,975 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 20:52:59,980 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,980 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 20:52:59,985 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,985 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 20:52:59,999 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,999 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 20:53:00,004 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:53:00,004 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 21:55:05,648 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 21:55:05,656 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 21:55:14,957 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 21:55:14,960 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 21:56:00,538 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 21:56:00,562 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 22:00:56,344 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 22:00:56,344 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 22:00:56,357 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 22:00:56,357 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 22:00:56,364 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 22:00:56,365 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 23:22:38,475 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 23:22:38,475 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 23:53:32,028 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 23:53:32,028 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-14 23:56:57,447 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 23:56:57,447 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-15 00:18:49,537 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 00:18:49,537 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-15 00:44:36,719 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 00:44:36,720 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-15 01:58:02,054 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 01:58:02,054 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-15 09:48:24,108 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 09:48:24,126 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-15 10:06:56,596 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:06:56,620 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-15 10:10:31,639 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:10:31,653 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: templates\银豹-采购单模板.xls
2025-11-15 10:39:24,612 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:39:24,612 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 10:39:48,326 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:39:48,337 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 10:39:52,727 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 10:39:52,728 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 10:49:13,775 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:49:13,775 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 10:51:44,473 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:51:44,475 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 10:51:49,960 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:51:49,960 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 10:54:02,883 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:54:02,886 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 10:54:06,684 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 10:54:06,705 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 10:58:26,607 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:58:26,621 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 10:58:36,067 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:58:36,082 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 14:17:23,600 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 14:17:23,608 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 14:17:23,670 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 14:17:23,682 - app.core.excel.merger - WARNING - 未在 data/result 目录下找到采购单Excel文件
2025-11-15 14:17:23,718 - app.core.excel.merger - WARNING - 没有找到可合并的采购单文件
2025-11-15 14:22:16,926 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 14:22:16,935 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:11:57,981 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:11:57,984 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:11:59,649 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 15:11:59,650 - app.core.excel.merger - INFO - 找到 2 个采购单Excel文件
2025-11-15 15:12:52,674 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:12:52,692 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:12:54,195 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 15:12:54,206 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 15:21:05,667 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:21:05,673 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:22:01,186 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:22:01,202 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:25:41,372 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:25:41,379 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:25:42,598 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 15:25:42,601 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 15:34:06,486 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:34:06,497 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:34:08,030 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 15:34:08,041 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 15:37:42,584 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:37:42,592 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:39:11,314 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:39:11,314 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:43:33,538 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:43:33,548 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:43:37,053 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 15:43:37,054 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 15:54:35,546 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:54:35,559 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 15:54:38,702 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 15:54:38,713 - app.core.excel.merger - INFO - 找到 2 个采购单Excel文件
2025-11-15 16:19:42,388 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:19:42,388 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:34:22,131 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:34:22,132 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:46:22,429 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:46:22,440 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:46:28,876 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:46:28,896 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:46:37,767 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:46:37,782 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:46:39,219 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 16:46:39,230 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 16:48:30,476 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:48:30,488 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:48:42,047 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:48:42,063 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:48:43,633 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 16:48:43,644 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 16:52:35,959 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:52:35,969 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:52:37,531 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 16:52:37,541 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 16:57:42,442 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:57:42,451 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:59:07,007 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:59:07,016 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:59:14,595 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:59:14,604 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 16:59:16,171 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 16:59:16,180 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 17:01:30,240 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:01:30,245 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:01:31,385 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 17:01:31,395 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 17:04:33,243 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:04:33,255 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:04:45,041 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:04:45,048 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:04:46,493 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 17:04:46,505 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 17:09:39,846 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:09:39,851 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:12:37,471 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:12:37,476 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:12:39,165 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 17:12:39,173 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-15 17:28:46,834 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:28:46,847 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:28:49,875 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 17:28:49,876 - app.core.excel.merger - INFO - 找到 2 个采购单Excel文件
2025-11-15 17:59:22,818 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:59:22,823 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:59:33,491 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:59:33,506 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 17:59:39,835 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:59:39,849 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 18:00:04,153 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 18:00:04,165 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-15 18:00:07,581 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-15 18:00:07,582 - app.core.excel.merger - INFO - 找到 3 个采购单Excel文件
2025-11-15 18:01:51,698 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 18:01:51,710 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 10:48:45,534 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 10:48:45,552 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 10:56:22,516 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 10:56:22,516 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 11:23:59,373 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 11:23:59,373 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 11:26:06,795 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 11:26:06,795 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 12:51:06,910 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 12:51:06,910 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 13:03:10,562 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:03:10,563 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 13:03:10,583 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:03:10,584 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 13:18:18,247 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:18:18,248 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 13:18:18,270 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:18:18,271 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 13:51:09,017 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:51:09,018 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 14:25:50,027 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 14:25:50,031 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 14:25:55,596 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 14:25:55,612 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 14:25:56,992 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-16 14:25:57,001 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-16 14:39:42,980 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 14:39:42,980 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 14:39:42,995 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 14:39:42,996 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 14:59:35,437 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 14:59:35,437 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 15:03:21,893 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:03:21,893 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 15:08:33,545 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:08:33,546 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 15:11:11,240 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:11:11,254 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 15:11:12,200 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-16 15:11:12,212 - app.core.excel.merger - INFO - 找到 2 个采购单Excel文件
2025-11-16 15:13:47,399 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:13:47,401 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 15:13:50,815 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-16 15:13:50,816 - app.core.excel.merger - INFO - 找到 3 个采购单Excel文件
2025-11-16 15:15:36,200 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:15:36,203 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-16 15:15:39,032 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-16 15:15:39,033 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-20 18:36:13,285 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:36:13,295 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-20 18:36:14,159 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-20 18:36:14,170 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-20 18:36:42,097 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:36:42,104 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-20 18:36:43,288 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-20 18:36:43,297 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-20 18:42:25,918 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:42:25,932 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-20 18:42:26,808 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-20 18:42:26,819 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-20 18:44:11,119 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:44:11,128 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-20 18:46:17,345 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-20 18:46:17,346 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-20 18:47:03,916 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:47:03,919 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-20 18:50:11,046 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-20 18:50:11,047 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-11-20 18:56:30,337 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:56:30,348 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-11-20 18:56:34,058 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-11-20 18:56:34,061 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-12-01 22:21:09,101 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-01 22:21:09,105 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-01 22:21:20,388 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
2025-12-01 22:21:20,390 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
2025-12-12 11:00:14,089 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 11:00:14,095 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 11:13:15,287 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 11:13:15,288 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 11:22:25,990 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 11:22:25,990 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 11:32:26,694 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 11:32:26,694 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 11:34:40,238 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 11:34:40,238 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 11:38:53,256 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 11:38:53,260 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 11:49:38,203 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 11:49:38,203 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 12:30:07,698 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 12:30:07,700 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2025-12-12 12:32:20,579 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-12 12:32:20,579 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 10:20:20,144 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 10:20:20,145 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 10:21:25,355 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 10:21:25,356 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 10:21:52,469 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 10:21:52,470 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:28:54,451 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:28:54,451 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:29:24,575 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:29:24,576 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:29:24,580 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:29:24,580 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:29:58,483 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:29:58,484 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:29:58,488 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:29:58,488 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:30:29,317 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:30:29,317 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:30:29,321 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:30:29,322 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:31:51,946 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:31:51,947 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:31:51,951 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:31:51,952 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:46:00,210 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:46:00,211 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:46:00,229 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:46:00,230 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:46:02,785 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:46:02,785 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:47:05,233 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:47:05,233 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:47:05,255 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:47:05,256 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:47:06,654 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:47:06,654 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:48:28,519 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:48:28,519 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:48:28,525 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:48:28,525 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:48:30,802 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:48:30,802 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:51:44,156 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:51:44,156 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:51:44,170 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:51:44,171 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 13:51:46,072 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 13:51:46,072 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:00:52,374 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:00:52,374 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:00:52,392 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:00:52,392 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:01:20,295 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:01:20,295 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:01:20,307 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:01:20,308 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:01:24,376 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:01:24,376 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:06:58,242 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:06:58,243 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:07:00,055 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:07:00,055 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:14:46,832 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:14:46,832 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:14:47,891 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:14:47,891 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:23:18,310 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:23:18,310 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:23:20,043 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:23:20,043 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:24:02,882 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:24:02,883 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:24:04,318 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:24:04,318 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:28:39,039 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:28:39,040 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:28:39,737 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:28:39,738 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:41:11,949 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:41:11,949 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:41:33,388 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:41:33,388 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:41:47,796 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:41:47,796 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
2026-03-30 14:41:49,673 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2026-03-30 14:41:49,673 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
2025-11-14 21:55:30,948 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-14 23:53:34,369 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 00:20:01,697 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 09:53:49,682 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 10:06:38,107 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 10:39:49,255 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 10:54:03,264 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 14:16:12,391 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 14:41:42,507 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 15:12:40,831 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 15:25:05,333 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 15:33:48,514 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 15:43:34,042 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 16:39:06,073 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 17:28:47,324 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-15 18:00:04,623 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-16 12:50:38,921 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-16 15:11:07,710 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-16 15:13:47,756 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-11-16 15:15:36,568 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
2025-12-01 22:21:09,536 - app.core.ocr.baidu_ocr - INFO - 成功获取访问令牌
+400
View File
@@ -0,0 +1,400 @@
2025-11-14 20:52:59,972 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 20:52:59,973 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,973 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 20:52:59,973 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 20:52:59,974 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 20:52:59,976 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 20:52:59,978 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,978 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 20:52:59,978 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 20:52:59,978 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 20:52:59,982 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 20:52:59,982 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,982 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 20:52:59,984 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 20:52:59,984 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 20:52:59,996 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 20:52:59,996 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:52:59,996 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 20:52:59,997 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 20:52:59,997 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 20:53:00,001 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 20:53:00,001 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 20:53:00,001 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 20:53:00,001 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 20:53:00,002 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 21:55:14,932 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 21:55:14,933 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 21:55:14,935 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 21:55:14,936 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 21:55:14,937 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 21:55:15,025 - app.core.ocr.table_ocr - INFO - 找到 0 个图片文件,其中 0 个未处理
2025-11-14 21:55:15,031 - app.core.ocr.table_ocr - WARNING - 没有需要处理的图片
2025-11-14 21:55:30,512 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 21:55:30,522 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 21:55:30,535 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 21:55:30,547 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 21:55:30,563 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 21:55:30,612 - app.core.ocr.table_ocr - INFO - 开始处理图片: E:/2025Code/python/orc-order-v2/data/input/微信图片_20250909184135_44_108.jpg
2025-11-14 21:55:32,256 - app.core.ocr.table_ocr - INFO - 图片处理成功: E:/2025Code/python/orc-order-v2/data/input/微信图片_20250909184135_44_108.jpg, 输出文件: data/output\微信图片_20250909184135_44_108.xlsx
2025-11-14 22:00:56,334 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 22:00:56,335 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 22:00:56,336 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 22:00:56,336 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 22:00:56,338 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 22:00:56,354 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 22:00:56,354 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 22:00:56,354 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 22:00:56,355 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 22:00:56,355 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 22:00:56,361 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 22:00:56,361 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 22:00:56,361 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 22:00:56,361 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 22:00:56,362 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 23:22:38,471 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 23:22:38,471 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 23:22:38,472 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 23:22:38,472 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 23:22:38,472 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 23:53:32,026 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 23:53:32,026 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 23:53:32,026 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 23:53:32,026 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 23:53:32,026 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-14 23:53:34,048 - app.core.ocr.table_ocr - INFO - 开始处理图片: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:53:35,690 - app.core.ocr.table_ocr - INFO - 图片处理成功: data\input\微信图片_20250909184135_44_108.jpg, 输出文件: data/output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:54:00,127 - app.core.ocr.table_ocr - INFO - 开始处理图片: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:01,441 - app.core.ocr.table_ocr - INFO - 图片处理成功: data\input\微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:50,647 - app.core.ocr.table_ocr - INFO - 开始处理图片: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:54:51,981 - app.core.ocr.table_ocr - INFO - 图片处理成功: data\input\微信图片_20251019141843_92_108.jpg, 输出文件: data/output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:56:57,443 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-14 23:56:57,443 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-14 23:56:57,443 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-14 23:56:57,443 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-14 23:56:57,443 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 00:18:49,534 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 00:18:49,534 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 00:18:49,534 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 00:18:49,534 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 00:18:49,535 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 00:20:01,395 - app.core.ocr.table_ocr - INFO - 开始处理图片: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:03,253 - app.core.ocr.table_ocr - INFO - 图片处理成功: data\input\微信图片_20251019141843_92_108.jpg, 输出文件: data/output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:44:36,717 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 00:44:36,717 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 00:44:36,717 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 00:44:36,717 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 00:44:36,718 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 01:58:02,050 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 01:58:02,051 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 01:58:02,051 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 01:58:02,051 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 01:58:02,051 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 09:48:24,086 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 09:48:24,087 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 09:48:24,087 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 09:48:24,087 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 09:48:24,088 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 09:49:24,553 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 09:49:24,553 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 09:49:24,557 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 09:49:24,560 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 09:49:24,563 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 09:53:49,094 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 09:53:49,107 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 09:53:49,118 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 09:53:49,132 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 09:53:49,147 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 09:53:49,215 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-15 09:53:49,231 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 09:53:49,248 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251027125604_98_108.jpg
2025-11-15 09:53:51,046 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 09:53:51,047 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-15 10:06:37,789 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 10:06:37,795 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:06:37,803 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 10:06:37,811 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 10:06:37,818 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 10:06:37,843 - app.core.ocr.table_ocr - INFO - 开始处理图片: E:/2025Code/python/orc-order-v2/data/input/微信图片_20251027125604_98_108.jpg
2025-11-15 10:06:39,473 - app.core.ocr.table_ocr - INFO - 图片处理成功: E:/2025Code/python/orc-order-v2/data/input/微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 10:39:24,607 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 10:39:24,607 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:39:24,608 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 10:39:24,608 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 10:39:24,609 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 10:39:48,299 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 10:39:48,301 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:39:48,303 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 10:39:48,305 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 10:39:48,308 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 10:39:48,365 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-15 10:39:48,368 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 10:39:48,392 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251027125604_98_108.jpg
2025-11-15 10:39:50,666 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 10:39:50,667 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-15 10:49:13,768 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 10:49:13,769 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:49:13,769 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 10:49:13,769 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 10:49:13,770 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 10:51:44,469 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 10:51:44,469 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:51:44,469 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 10:51:44,470 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 10:51:44,470 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 10:51:49,953 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 10:51:49,954 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:51:49,954 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 10:51:49,955 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 10:51:49,955 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 10:54:02,833 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 10:54:02,834 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 10:54:02,837 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 10:54:02,841 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 10:54:02,846 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 10:54:02,940 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-15 10:54:02,943 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 10:54:02,973 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251027125604_98_108.jpg
2025-11-15 10:54:04,733 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 10:54:04,738 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-15 14:16:11,980 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 14:16:11,982 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 14:16:11,985 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 14:16:11,997 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 14:16:11,999 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 14:16:12,028 - app.core.ocr.table_ocr - INFO - 开始处理图片: E:/2025Code/python/orc-order-v2/data/input/微信图片_20251113183218_594_278.jpg
2025-11-15 14:16:14,042 - app.core.ocr.table_ocr - INFO - 图片处理成功: E:/2025Code/python/orc-order-v2/data/input/微信图片_20251113183218_594_278.jpg, 输出文件: data/output\微信图片_20251113183218_594_278.xlsx
2025-11-15 14:41:42,172 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 14:41:42,173 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 14:41:42,175 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 14:41:42,176 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 14:41:42,179 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 14:41:42,198 - app.core.ocr.table_ocr - INFO - 开始处理图片: F:/下载/微信图片_20251027125604_98_108.jpg
2025-11-15 14:41:44,478 - app.core.ocr.table_ocr - INFO - 图片处理成功: F:/下载/微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:11:57,934 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 15:11:57,937 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:11:57,939 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 15:11:57,945 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 15:11:57,946 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 15:11:58,040 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-15 15:11:58,053 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 15:11:58,081 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251113183218_594_278.jpg
2025-11-15 15:11:58,091 - app.core.ocr.table_ocr - INFO - 已存在对应的Excel文件,跳过处理: 微信图片_20251113183218_594_278.jpg -> 微信图片_20251113183218_594_278.xlsx
2025-11-15 15:11:58,132 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-15 15:12:40,318 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 15:12:40,333 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:12:40,345 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 15:12:40,358 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 15:12:40,370 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 15:12:40,403 - app.core.ocr.table_ocr - INFO - 开始处理图片: F:/下载/微信图片_20251027125604_98_108.jpg
2025-11-15 15:12:42,172 - app.core.ocr.table_ocr - INFO - 图片处理成功: F:/下载/微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:25:05,003 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 15:25:05,004 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:25:05,006 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 15:25:05,009 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 15:25:05,019 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 15:25:05,052 - app.core.ocr.table_ocr - INFO - 开始处理图片: F:/下载/微信图片_20251027125604_98_108.jpg
2025-11-15 15:25:06,786 - app.core.ocr.table_ocr - INFO - 图片处理成功: F:/下载/微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:33:47,219 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 15:33:47,225 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:33:47,228 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 15:33:47,229 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 15:33:47,234 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 15:33:47,269 - app.core.ocr.table_ocr - INFO - 开始处理图片: F:/下载/微信图片_20251027125604_98_108.jpg
2025-11-15 15:33:50,403 - app.core.ocr.table_ocr - INFO - 图片处理成功: F:/下载/微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:39:11,309 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 15:39:11,310 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:39:11,310 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 15:39:11,310 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 15:39:11,311 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 15:43:33,473 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 15:43:33,474 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 15:43:33,478 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 15:43:33,481 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 15:43:33,485 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 15:43:33,617 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-15 15:43:33,637 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 15:43:33,670 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251113183218_594_278.jpg
2025-11-15 15:43:35,235 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251113183218_594_278.jpg, 输出文件: data/output\微信图片_20251113183218_594_278.xlsx
2025-11-15 15:43:35,236 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-15 16:39:05,575 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 16:39:05,581 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:39:05,587 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 16:39:05,594 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 16:39:05,597 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 16:39:05,641 - app.core.ocr.table_ocr - INFO - 开始处理图片: F:/下载/微信图片_20251027125604_98_108.jpg
2025-11-15 16:39:07,420 - app.core.ocr.table_ocr - INFO - 图片处理成功: F:/下载/微信图片_20251027125604_98_108.jpg, 输出文件: data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 16:46:22,388 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 16:46:22,388 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 16:46:22,390 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 16:46:22,390 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 16:46:22,392 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 16:46:22,501 - app.core.ocr.table_ocr - INFO - 找到 0 个图片文件,其中 0 个未处理
2025-11-15 16:46:22,522 - app.core.ocr.table_ocr - WARNING - 没有需要处理的图片
2025-11-15 17:28:46,777 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 17:28:46,778 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:28:46,781 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 17:28:46,784 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 17:28:46,789 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 17:28:46,913 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-15 17:28:46,930 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 17:28:46,975 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251115145536_614_278.jpg
2025-11-15 17:28:48,564 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251115145536_614_278.jpg, 输出文件: data/output\微信图片_20251115145536_614_278.xlsx
2025-11-15 17:28:48,566 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-15 17:29:22,057 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 17:29:22,070 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:29:22,076 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 17:29:22,084 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 17:29:22,098 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 17:29:22,147 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-15 17:29:22,159 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 17:29:22,180 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251115145536_614_278.jpg
2025-11-15 17:29:22,198 - app.core.ocr.table_ocr - INFO - 已存在对应的Excel文件,跳过处理: 微信图片_20251115145536_614_278.jpg -> 微信图片_20251115145536_614_278.xlsx
2025-11-15 17:29:22,217 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-15 17:59:39,798 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 17:59:39,799 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 17:59:39,800 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 17:59:39,800 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 17:59:39,801 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 17:59:39,913 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 0 个未处理
2025-11-15 17:59:39,930 - app.core.ocr.table_ocr - WARNING - 没有需要处理的图片
2025-11-15 18:00:04,112 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-15 18:00:04,114 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-15 18:00:04,116 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-15 18:00:04,117 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-15 18:00:04,119 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-15 18:00:04,224 - app.core.ocr.table_ocr - INFO - 找到 2 个图片文件,其中 1 个未处理
2025-11-15 18:00:04,247 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-15 18:00:04,278 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251114131924_600_278.jpg
2025-11-15 18:00:05,870 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251114131924_600_278.jpg, 输出文件: data/output\微信图片_20251114131924_600_278.xlsx
2025-11-15 18:00:05,872 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-16 11:23:59,369 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 11:23:59,369 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 11:23:59,369 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 11:23:59,369 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 11:23:59,369 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 11:26:06,790 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 11:26:06,791 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 11:26:06,791 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 11:26:06,791 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 11:26:06,792 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 12:50:38,502 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 12:50:38,503 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 12:50:38,504 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 12:50:38,506 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 12:50:38,508 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 12:50:38,541 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-16 12:50:38,550 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-16 12:50:38,560 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251115212128_148_108.jpg
2025-11-16 12:50:40,143 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251115212128_148_108.jpg, 输出文件: data/output\微信图片_20251115212128_148_108.xlsx
2025-11-16 12:50:40,149 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-16 12:51:06,904 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 12:51:06,906 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 12:51:06,906 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 12:51:06,906 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 12:51:06,907 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 13:03:10,557 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 13:03:10,557 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:03:10,557 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 13:03:10,558 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 13:03:10,558 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 13:03:10,578 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 13:03:10,578 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:03:10,579 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 13:03:10,579 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 13:03:10,580 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 13:18:18,243 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 13:18:18,243 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:18:18,244 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 13:18:18,244 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 13:18:18,244 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 13:18:18,265 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 13:18:18,265 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:18:18,266 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 13:18:18,266 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 13:18:18,267 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 13:51:09,012 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 13:51:09,013 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 13:51:09,013 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 13:51:09,013 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 13:51:09,014 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 14:39:42,975 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 14:39:42,976 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 14:39:42,976 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 14:39:42,976 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 14:39:42,977 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 14:39:42,990 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 14:39:42,991 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 14:39:42,991 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 14:39:42,991 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 14:39:42,992 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 15:11:05,478 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 15:11:05,485 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:11:05,487 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 15:11:05,489 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 15:11:05,498 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 15:11:05,536 - app.core.ocr.table_ocr - INFO - 找到 2 个图片文件,其中 1 个未处理
2025-11-16 15:11:05,549 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-16 15:11:05,571 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251116151001_152_108.jpg
2025-11-16 15:11:08,994 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251116151001_152_108.jpg, 输出文件: data/output\微信图片_20251116151001_152_108.xlsx
2025-11-16 15:11:08,995 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-16 15:13:47,370 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 15:13:47,372 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:13:47,375 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 15:13:47,381 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 15:13:47,383 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 15:13:47,457 - app.core.ocr.table_ocr - INFO - 找到 3 个图片文件,其中 1 个未处理
2025-11-16 15:13:47,470 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-16 15:13:47,510 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251116151334_153_108.jpg
2025-11-16 15:13:49,392 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251116151334_153_108.jpg, 输出文件: data/output\微信图片_20251116151334_153_108.xlsx
2025-11-16 15:13:49,397 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-16 15:15:36,173 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-16 15:15:36,173 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-16 15:15:36,174 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-16 15:15:36,175 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-16 15:15:36,177 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-16 15:15:36,243 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-16 15:15:36,257 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-16 15:15:36,299 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251116151514_154_108.jpg
2025-11-16 15:15:37,808 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251116151514_154_108.jpg, 输出文件: data/output\微信图片_20251116151514_154_108.xlsx
2025-11-16 15:15:37,809 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-20 18:44:11,080 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-20 18:44:11,083 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:44:11,087 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-20 18:44:11,089 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-20 18:44:11,094 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-20 18:44:11,194 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-20 18:44:11,216 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-20 18:44:11,261 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251116151514_154_108.jpg
2025-11-20 18:44:11,261 - app.core.ocr.table_ocr - INFO - 已存在对应的Excel文件,跳过处理: 微信图片_20251116151514_154_108.jpg -> 微信图片_20251116151514_154_108.xlsx
2025-11-20 18:44:11,295 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-20 18:47:03,864 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-20 18:47:03,867 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:47:03,867 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-20 18:47:03,869 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-20 18:47:03,871 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-20 18:47:03,990 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-20 18:47:04,011 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-20 18:47:04,048 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251116151514_154_108.jpg
2025-11-20 18:47:04,058 - app.core.ocr.table_ocr - INFO - 已存在对应的Excel文件,跳过处理: 微信图片_20251116151514_154_108.jpg -> 微信图片_20251116151514_154_108.xlsx
2025-11-20 18:47:04,080 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-11-20 18:56:30,283 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-11-20 18:56:30,284 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-11-20 18:56:30,287 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-11-20 18:56:30,289 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-11-20 18:56:30,291 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-11-20 18:56:30,417 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-11-20 18:56:30,439 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-11-20 18:56:30,483 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251116151514_154_108.jpg
2025-11-20 18:56:30,483 - app.core.ocr.table_ocr - INFO - 已存在对应的Excel文件,跳过处理: 微信图片_20251116151514_154_108.jpg -> 微信图片_20251116151514_154_108.xlsx
2025-11-20 18:56:30,495 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
2025-12-01 22:21:09,028 - app.core.ocr.table_ocr - INFO - 使用输入目录: E:\2025Code\python\orc-order-v2\data\input
2025-12-01 22:21:09,043 - app.core.ocr.table_ocr - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
2025-12-01 22:21:09,046 - app.core.ocr.table_ocr - INFO - 使用临时目录: E:\2025Code\python\orc-order-v2\data\temp
2025-12-01 22:21:09,048 - app.core.ocr.table_ocr - INFO - 允许的文件类型: ['.jpg', '.jpeg', '.png', '.bmp']
2025-12-01 22:21:09,052 - app.core.ocr.table_ocr - INFO - 初始化OCRProcessor完成:输入目录=data/input, 输出目录=data/output
2025-12-01 22:21:09,173 - app.core.ocr.table_ocr - INFO - 找到 1 个图片文件,其中 1 个未处理
2025-12-01 22:21:09,198 - app.core.ocr.table_ocr - INFO - 处理批次 1/1: 1 个文件
2025-12-01 22:21:09,246 - app.core.ocr.table_ocr - INFO - 开始处理图片: data/input\微信图片_20251201221738_176_108.jpg
2025-12-01 22:21:10,536 - app.core.ocr.table_ocr - INFO - 图片处理成功: data/input\微信图片_20251201221738_176_108.jpg, 输出文件: data/output\微信图片_20251201221738_176_108.xlsx
2025-12-01 22:21:10,538 - app.core.ocr.table_ocr - INFO - 所有图片处理完成, 总计: 1, 成功: 1
+4
View File
@@ -0,0 +1,4 @@
2025-11-14 21:55:05,688 - app.core.utils.file_utils - WARNING - 未在目录 data/output 中找到符合条件的文件
2025-11-16 10:48:45,595 - app.core.utils.file_utils - WARNING - 未在目录 data/output 中找到符合条件的文件
2025-11-16 10:48:45,656 - app.core.utils.file_utils - WARNING - 未在目录 data/output 中找到符合条件的文件
2025-11-16 10:56:22,516 - app.core.utils.file_utils - WARNING - 未在目录 data/output 中找到符合条件的文件
+589
View File
@@ -0,0 +1,589 @@
2025-11-14 20:52:59,971 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 20:52:59,974 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 20:52:59,976 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 20:52:59,978 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 20:52:59,982 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 20:52:59,984 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 20:52:59,995 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 20:52:59,997 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 20:53:00,000 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 20:53:00,002 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 21:55:14,931 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 21:55:14,939 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 21:55:14,988 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-14 21:55:15,019 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-14 21:55:30,504 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 21:55:30,576 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 21:55:32,258 - app.services.ocr_service - INFO - 处理完成: E:/2025Code/python/orc-order-v2/data/input/微信图片_20250909184135_44_108.jpg -> data/output\微信图片_20250909184135_44_108.xlsx
2025-11-14 22:00:56,331 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 22:00:56,339 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 22:00:56,354 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 22:00:56,355 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 22:00:56,360 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 22:00:56,362 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 23:22:38,470 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 23:22:38,472 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 23:53:32,025 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 23:53:32,027 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 23:53:35,691 - app.services.ocr_service - INFO - 处理完成: data\input\微信图片_20250909184135_44_108.jpg -> data/output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:37,742 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:53:39,788 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:53:41,832 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:53:43,881 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:53:45,934 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:53:48,001 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:53:50,039 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20250909184135_44_108.jpg
2025-11-14 23:54:01,442 - app.services.ocr_service - INFO - 处理完成: data\input\微信图片_20251027125604_98_108.jpg -> data/output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:03,487 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:05,528 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:07,576 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:09,616 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:13,773 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:15,821 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:17,867 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:19,919 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:21,962 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:24,008 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:26,046 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:28,097 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:30,151 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:32,204 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:34,250 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:36,296 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:38,351 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:40,404 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:42,458 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:44,502 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:46,556 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:48,600 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:51,982 - app.services.ocr_service - INFO - 处理完成: data\input\微信图片_20251019141843_92_108.jpg -> data/output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:54:52,021 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:54,069 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:54:54,107 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:56,148 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:54:56,189 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:54:58,238 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:54:58,275 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:00,326 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:00,364 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:02,419 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:02,462 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:04,521 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:04,558 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:06,605 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:06,642 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:08,703 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:08,740 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:10,786 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:10,827 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:12,879 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:12,916 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:14,960 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:14,996 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:17,036 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:17,073 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:19,125 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:19,162 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:21,204 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:21,242 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:23,294 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:23,335 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:25,382 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:25,421 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:27,467 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:27,505 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:29,547 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:29,582 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:31,622 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:31,669 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:33,737 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:33,774 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:35,817 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:35,853 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:37,920 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:37,961 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:40,013 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:40,051 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:42,108 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:42,146 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:44,193 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:44,229 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:46,275 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:46,315 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:48,365 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:48,404 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:50,443 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:50,482 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:52,535 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:52,573 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:54,614 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:54,653 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:56,710 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:56,748 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:55:58,800 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:55:58,838 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:56:00,891 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:56:00,933 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:56:02,979 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:56:03,018 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:56:05,062 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:56:05,105 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:56:07,162 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:56:07,210 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:56:57,442 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-14 23:56:57,445 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-14 23:56:59,616 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:01,537 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:05,227 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:06,470 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:09,653 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:11,576 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:15,136 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:16,887 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:20,234 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:21,430 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:24,780 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:26,265 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:29,872 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:31,572 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:35,123 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:36,918 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:40,537 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:42,112 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:45,416 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:46,720 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:49,990 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:51,501 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:54,894 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:57:56,395 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:57:59,834 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:01,639 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:05,077 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:06,547 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:09,915 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:11,351 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:14,705 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:16,392 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:19,888 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:21,532 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:24,978 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:26,500 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:29,831 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:31,416 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:34,852 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:36,575 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:39,921 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:41,452 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:44,946 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:46,443 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:50,034 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:51,597 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:58:55,053 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:58:56,555 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:00,070 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:01,844 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:05,320 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:06,825 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:10,473 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:12,139 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:15,603 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:17,192 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:20,612 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:22,114 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:25,589 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:27,211 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:30,767 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:32,326 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:36,011 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:37,686 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:41,201 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:42,778 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:46,304 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:47,761 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:51,191 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:52,841 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-14 23:59:56,216 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-14 23:59:57,611 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:01,219 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:02,758 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:06,052 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:07,434 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:10,840 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:12,561 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:16,097 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:17,522 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:20,868 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:22,464 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:26,088 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:27,515 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:31,017 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:32,515 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:36,053 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:37,624 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:41,060 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:42,493 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:45,839 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:47,275 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:50,609 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:52,109 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:00:55,459 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:00:57,222 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:00,688 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:02,298 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:05,719 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:07,317 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:10,858 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:12,551 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:16,041 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:17,743 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:21,145 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:22,630 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:26,026 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:27,349 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:30,913 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:32,597 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:35,986 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:37,481 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:40,840 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:42,630 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:46,085 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:47,711 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:51,307 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:53,097 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:01:56,452 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:01:57,944 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:01,308 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:02,944 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:06,570 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:08,356 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:12,004 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:13,757 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:17,556 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:19,476 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:22,928 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:24,545 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:27,890 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:29,443 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:32,790 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:34,247 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:37,750 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:39,242 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:42,620 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:44,196 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:47,546 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:49,236 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:52,656 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:54,352 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:02:57,747 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:02:59,241 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:02,582 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:03,966 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:07,396 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:09,150 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:12,454 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:14,078 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:17,563 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:18,931 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:22,254 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:23,958 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:27,433 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:28,875 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:32,492 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:33,730 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:37,280 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:38,745 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:42,378 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:44,064 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:47,518 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:49,323 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:52,839 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:54,315 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:03:57,762 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:03:59,377 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:02,912 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:04,333 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:07,634 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:09,217 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:12,781 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:14,391 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:17,952 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:19,426 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:22,773 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:24,437 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:27,786 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:29,491 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:33,208 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:34,834 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:38,270 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:39,870 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:43,227 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:44,671 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:48,011 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:49,544 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:52,898 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:54,514 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:04:58,133 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:04:59,637 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:03,203 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:04,439 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:07,802 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:09,420 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:13,002 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:14,449 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:17,865 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:19,351 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:22,585 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:24,115 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:27,622 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:29,332 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:32,669 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:34,242 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:37,801 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:39,407 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:42,969 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:44,554 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:48,144 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:49,714 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:53,196 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:54,908 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:05:58,328 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:05:59,736 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:18:49,533 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 00:18:49,535 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 00:18:51,772 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:18:53,982 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:18:57,742 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:18:59,499 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:19:02,811 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:19:04,852 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:19:08,780 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:19:10,559 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:19:14,369 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:19:16,366 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251027125604_98_108.jpg
2025-11-15 00:20:03,254 - app.services.ocr_service - INFO - 处理完成: data\input\微信图片_20251019141843_92_108.jpg -> data/output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:06,966 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:10,786 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:14,177 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:17,835 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:21,070 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:24,531 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:28,407 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:31,774 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:35,631 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:39,543 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:20:43,297 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:44:36,716 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 00:44:36,718 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 00:44:39,002 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:44:43,224 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 00:44:47,343 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:02,050 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 01:58:02,051 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 01:58:04,310 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:08,536 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:12,817 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:17,274 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:21,129 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:39,915 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:43,786 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:47,743 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:52,055 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:58:56,257 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:59:38,855 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:59:42,879 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:59:46,931 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:59:51,017 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 01:59:55,157 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:00:39,353 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:00:43,387 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:00:47,550 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:00:52,036 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:00:56,180 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:01:40,371 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:01:44,582 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:01:48,847 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:01:53,093 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:01:57,105 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:02:41,519 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:02:45,567 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:02:49,844 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:02:53,881 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:02:57,870 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:03:42,078 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:03:46,476 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:03:50,820 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:03:54,976 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:03:59,499 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:04:44,020 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:04:48,141 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:04:52,372 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:04:56,700 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:05:00,874 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:05:45,362 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:05:49,450 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:05:53,666 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:05:57,703 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:06:01,736 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:06:46,004 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:06:50,334 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:06:54,620 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:06:59,012 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:07:03,137 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:07:47,221 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:07:51,451 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:07:55,498 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:07:59,518 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:08:03,656 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:08:47,835 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:08:52,031 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:08:56,234 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:09:00,502 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:09:04,593 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:09:48,844 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:09:52,817 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:09:56,979 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:10:01,030 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:10:05,248 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:10:49,756 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:10:53,930 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:10:57,881 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:11:01,994 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:11:05,986 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:11:50,114 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:11:54,250 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:11:58,403 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:12:02,467 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:12:06,514 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:12:50,982 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:12:55,311 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:12:59,508 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:13:03,771 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 02:13:08,205 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: data\input\微信图片_20251019141843_92_108.jpg
2025-11-15 09:48:24,085 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 09:48:24,088 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 09:49:24,549 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 09:49:24,565 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 09:49:24,572 - app.services.ocr_service - INFO - 文件已处理过,跳过OCR识别: E:/2025Code/python/orc-order-v2/data/input/微信图片_20251019141843_92_108.jpg
2025-11-15 09:53:49,066 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 09:53:49,162 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 09:53:49,180 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 09:53:49,195 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 10:06:37,768 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 10:06:37,827 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 10:06:39,474 - app.services.ocr_service - INFO - 处理完成: E:/2025Code/python/orc-order-v2/data/input/微信图片_20251027125604_98_108.jpg -> data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 10:39:24,605 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 10:39:24,609 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 10:39:48,296 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 10:39:48,310 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 10:39:48,360 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 10:39:48,361 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 10:49:13,767 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 10:49:13,771 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 10:51:44,468 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 10:51:44,471 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 10:51:49,953 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 10:51:49,956 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 10:54:02,829 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 10:54:02,847 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 10:54:02,925 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 10:54:02,926 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 14:16:11,975 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 14:16:12,005 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 14:16:14,043 - app.services.ocr_service - INFO - 处理完成: E:/2025Code/python/orc-order-v2/data/input/微信图片_20251113183218_594_278.jpg -> data/output\微信图片_20251113183218_594_278.xlsx
2025-11-15 14:41:42,157 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 14:41:42,185 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 14:41:44,498 - app.services.ocr_service - INFO - 处理完成: F:/下载/微信图片_20251027125604_98_108.jpg -> data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:11:57,934 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 15:11:57,950 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 15:11:58,027 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 15:11:58,027 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 15:12:40,317 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 15:12:40,382 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 15:12:42,173 - app.services.ocr_service - INFO - 处理完成: F:/下载/微信图片_20251027125604_98_108.jpg -> data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:25:04,977 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 15:25:05,031 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 15:25:06,788 - app.services.ocr_service - INFO - 处理完成: F:/下载/微信图片_20251027125604_98_108.jpg -> data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:33:47,199 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 15:33:47,245 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 15:33:50,404 - app.services.ocr_service - INFO - 处理完成: F:/下载/微信图片_20251027125604_98_108.jpg -> data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 15:39:11,308 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 15:39:11,311 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 15:43:33,472 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 15:43:33,492 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 15:43:33,597 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 15:43:33,597 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 16:39:05,552 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 16:39:05,605 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 16:39:07,421 - app.services.ocr_service - INFO - 处理完成: F:/下载/微信图片_20251027125604_98_108.jpg -> data/output\微信图片_20251027125604_98_108.xlsx
2025-11-15 16:46:22,384 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 16:46:22,392 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 16:46:22,491 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 16:46:22,491 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 17:28:46,775 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 17:28:46,794 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 17:28:46,901 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 17:28:46,901 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 17:29:22,045 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 17:29:22,113 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 17:29:22,124 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 17:29:22,138 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 17:59:39,797 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 17:59:39,802 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 17:59:39,891 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 17:59:39,892 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-15 18:00:04,109 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-15 18:00:04,120 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-15 18:00:04,212 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-15 18:00:04,213 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-16 11:23:59,366 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 11:23:59,369 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 11:26:06,789 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 11:26:06,792 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 12:50:38,497 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 12:50:38,510 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 12:50:38,512 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-16 12:50:38,524 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-16 12:51:06,904 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 12:51:06,907 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 13:03:10,556 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 13:03:10,559 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 13:03:10,577 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 13:03:10,580 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 13:18:18,242 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 13:18:18,246 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 13:18:18,264 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 13:18:18,267 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 13:51:09,011 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 13:51:09,014 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 14:39:42,974 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 14:39:42,977 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 14:39:42,990 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 14:39:42,993 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 15:11:05,474 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 15:11:05,507 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 15:11:05,514 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-16 15:11:05,525 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-16 15:13:47,369 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 15:13:47,385 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 15:13:47,444 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-16 15:13:47,445 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-16 15:15:36,170 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-16 15:15:36,181 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-16 15:15:36,232 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-16 15:15:36,232 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-20 18:44:11,053 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-20 18:44:11,098 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-20 18:44:11,171 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-20 18:44:11,173 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-20 18:47:03,863 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-20 18:47:03,876 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-20 18:47:03,967 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-20 18:47:03,967 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-11-20 18:56:30,280 - app.services.ocr_service - INFO - 初始化OCRService
2025-11-20 18:56:30,296 - app.services.ocr_service - INFO - OCRService初始化完成
2025-11-20 18:56:30,390 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-11-20 18:56:30,399 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
2025-12-01 22:21:09,016 - app.services.ocr_service - INFO - 初始化OCRService
2025-12-01 22:21:09,058 - app.services.ocr_service - INFO - OCRService初始化完成
2025-12-01 22:21:09,151 - app.services.ocr_service - INFO - OCRService.batch_process被调用,转发到process_images_batch
2025-12-01 22:21:09,161 - app.services.ocr_service - INFO - OCRService开始批量处理图片, batch_size=None, max_workers=None
+817
View File
@@ -0,0 +1,817 @@
2025-08-16 00:52:16,815 - app.services.order_service - INFO - 初始化OrderService
2025-08-16 00:52:16,863 - app.services.order_service - INFO - OrderService初始化完成
2025-08-16 00:52:16,867 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-14 20:52:59,974 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 20:52:59,976 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 20:52:59,979 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 20:52:59,980 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 20:52:59,984 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 20:52:59,985 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 20:52:59,997 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 20:52:59,999 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 20:53:00,002 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 20:53:00,005 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 21:55:05,594 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 21:55:05,663 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 21:55:05,672 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-14 21:55:14,941 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 21:55:14,963 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 21:56:00,461 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 21:56:00,575 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 21:56:00,599 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20250909184135_44_108.xlsx
2025-11-14 22:00:56,339 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 22:00:56,344 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 22:00:56,355 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 22:00:56,357 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 22:00:56,362 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 22:00:56,365 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 23:22:38,472 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 23:22:38,475 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 23:53:32,027 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 23:53:32,028 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 23:53:35,691 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:37,742 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:39,788 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:41,832 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:43,882 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:45,934 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:48,001 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:53:50,039 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20250909184135_44_108.xlsx
2025-11-14 23:54:01,442 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:03,487 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:05,529 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:07,577 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:09,616 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:13,773 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:15,821 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:17,867 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:19,919 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:21,962 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:24,008 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:26,047 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:28,098 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:30,151 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:32,204 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:34,250 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:36,296 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:38,351 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:40,404 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:42,458 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:44,502 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:46,556 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:48,600 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:51,983 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:54:52,021 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:54,069 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:54:54,107 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:56,148 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:54:56,189 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:54:58,238 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:54:58,275 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:00,326 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:00,365 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:02,420 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:02,462 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:04,521 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:04,558 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:06,605 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:06,643 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:08,703 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:08,740 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:10,788 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:10,828 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:12,879 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:12,916 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:14,960 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:14,996 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:17,036 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:17,073 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:19,125 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:19,162 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:21,205 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:21,243 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:23,295 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:23,335 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:25,382 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:25,421 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:27,467 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:27,505 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:29,547 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:29,582 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:31,622 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:31,669 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:33,737 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:33,774 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:35,817 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:35,853 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:37,921 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:37,961 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:40,014 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:40,051 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:42,108 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:42,147 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:44,193 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:44,229 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:46,275 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:46,315 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:48,365 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:48,404 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:50,443 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:50,482 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:52,535 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:52,573 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:54,614 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:54,653 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:56,710 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:56,748 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:55:58,800 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:55:58,838 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:56:00,891 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:56:00,935 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:56:02,980 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:56:03,018 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:56:05,062 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:56:05,106 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:56:07,162 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:56:07,210 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:56:57,445 - app.services.order_service - INFO - 初始化OrderService
2025-11-14 23:56:57,448 - app.services.order_service - INFO - OrderService初始化完成
2025-11-14 23:56:59,671 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:01,610 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:05,270 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:06,524 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:09,725 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:11,648 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:15,205 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:16,956 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:20,272 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:21,477 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:24,830 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:26,314 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:29,933 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:31,639 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:35,183 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:36,999 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:40,585 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:42,149 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:45,471 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:46,771 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:50,045 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:51,537 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:54,947 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:57:56,455 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:57:59,886 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:01,675 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:05,116 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:06,597 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:09,988 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:11,402 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:14,762 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:16,456 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:19,926 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:21,593 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:25,017 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:26,549 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:29,869 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:31,480 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:34,917 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:36,626 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:39,970 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:41,505 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:45,014 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:46,495 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:50,070 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:51,660 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:58:55,105 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:58:56,605 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:00,139 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:01,892 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:05,383 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:06,901 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:10,551 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:12,206 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:15,639 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:17,241 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:20,663 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:22,160 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:25,634 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:27,248 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:30,819 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:32,365 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:36,079 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:37,734 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:41,258 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:42,824 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:46,370 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:47,834 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:51,254 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:52,905 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-14 23:59:56,269 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-14 23:59:57,672 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:01,256 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:02,807 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:06,104 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:07,485 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:10,893 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:12,610 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:16,149 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:17,562 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:20,946 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:22,538 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:26,131 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:27,583 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:31,069 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:32,567 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:36,102 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:37,675 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:41,096 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:42,559 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:45,876 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:47,314 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:50,657 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:52,159 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:00:55,531 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:00:57,287 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:00,725 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:02,358 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:05,756 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:07,382 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:10,909 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:12,635 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:16,124 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:17,810 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:21,182 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:22,681 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:26,069 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:27,398 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:30,983 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:32,654 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:36,036 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:37,546 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:40,916 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:42,666 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:46,135 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:47,771 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:51,376 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:53,154 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:01:56,488 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:01:58,006 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:01,361 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:03,000 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:06,651 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:08,438 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:12,057 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:13,824 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:17,635 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:19,526 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:23,005 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:24,594 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:27,928 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:29,505 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:32,852 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:34,299 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:37,813 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:39,281 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:42,672 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:44,232 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:47,595 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:49,284 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:52,743 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:54,437 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:02:57,797 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:02:59,304 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:02,645 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:04,003 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:07,447 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:09,204 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:12,509 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:14,146 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:17,611 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:18,970 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:22,320 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:23,995 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:27,472 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:28,938 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:32,546 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:33,779 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:37,358 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:38,810 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:42,430 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:44,137 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:47,587 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:49,403 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:52,901 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:54,388 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:03:57,810 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:03:59,455 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:02,984 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:04,394 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:07,710 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:09,289 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:12,859 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:14,474 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:17,989 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:19,500 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:22,822 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:24,473 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:27,848 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:29,568 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:33,270 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:34,897 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:38,318 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:39,934 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:43,278 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:44,722 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:48,052 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:49,619 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:52,975 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:54,572 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:04:58,173 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:04:59,694 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:03,261 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:04,491 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:07,887 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:09,478 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:13,079 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:14,500 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:17,918 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:19,387 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:22,622 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:24,193 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:27,664 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:29,394 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:32,717 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:34,328 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:37,870 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:39,473 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:43,044 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:44,606 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:48,195 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:49,765 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:53,268 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:54,963 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:05:58,364 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:05:59,802 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:18:49,535 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 00:18:49,538 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 00:18:51,832 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:18:54,057 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:18:57,817 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:18:59,547 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:19:02,881 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:19:04,937 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:19:08,852 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:19:10,644 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:19:14,425 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:19:16,439 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251027125604_98_108.xlsx
2025-11-15 00:20:03,324 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:07,029 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:10,864 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:14,215 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:17,878 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:21,109 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:24,592 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:28,474 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:31,856 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:35,700 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:39,605 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:20:43,355 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:44:36,718 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 00:44:36,720 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 00:44:39,085 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:44:43,294 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 00:44:47,418 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:02,052 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 01:58:02,054 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 01:58:04,404 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:08,613 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:12,905 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:17,338 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:21,192 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:39,977 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:43,852 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:47,831 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:52,175 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:58:56,355 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:59:38,962 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:59:42,963 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:59:47,041 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:59:51,100 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 01:59:55,247 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:00:39,437 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:00:43,455 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:00:47,653 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:00:52,105 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:00:56,245 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:01:40,456 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:01:44,653 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:01:48,950 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:01:53,159 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:01:57,193 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:02:41,611 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:02:45,668 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:02:49,948 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:02:53,952 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:02:57,956 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:03:42,179 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:03:46,610 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:03:50,918 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:03:55,072 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:03:59,598 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:04:44,111 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:04:48,235 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:04:52,463 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:04:56,788 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:05:01,024 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:05:45,464 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:05:49,519 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:05:53,734 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:05:57,788 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:06:01,836 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:06:46,074 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:06:50,451 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:06:54,716 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:06:59,148 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:07:03,221 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:07:47,305 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:07:51,518 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:07:55,599 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:07:59,603 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:08:03,773 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:08:47,930 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:08:52,146 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:08:56,363 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:09:00,604 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:09:04,679 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:09:48,912 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:09:52,927 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:09:57,080 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:10:01,136 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:10:05,339 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:10:49,825 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:10:53,995 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:10:57,962 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:11:02,097 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:11:06,075 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:11:50,181 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:11:54,337 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:11:58,472 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:12:02,549 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:12:06,611 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:12:51,091 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:12:55,402 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:12:59,595 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:13:03,883 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 02:13:08,305 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data\output\微信图片_20251019141843_92_108.xlsx
2025-11-15 09:48:24,088 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 09:48:24,126 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:06:56,495 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:06:56,644 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:06:56,677 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251027125604_98_108.xlsx
2025-11-15 10:10:31,568 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:10:31,669 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:10:31,686 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251027125604_98_108.xlsx
2025-11-15 10:39:24,610 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:39:24,613 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:39:48,312 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:39:48,340 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:39:50,736 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 10:49:13,771 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:49:13,775 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:51:44,471 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:51:44,475 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:51:49,956 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:51:49,960 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:54:02,853 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:54:02,897 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:54:04,837 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 10:58:26,517 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:58:26,634 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:58:26,651 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 10:58:36,001 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 10:58:36,098 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 10:58:36,133 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251027125604_98_108.xlsx
2025-11-15 14:17:23,548 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 14:17:23,617 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 14:17:23,630 - app.services.order_service - INFO - OrderService开始合并所有采购单
2025-11-15 14:17:23,648 - app.services.order_service - INFO - OrderService开始合并所有采购单
2025-11-15 14:22:16,889 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 14:22:16,949 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 14:22:16,977 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: F:/下载/订单1762933924814.xlsx
2025-11-15 15:11:57,955 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:11:57,991 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:11:58,198 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 15:12:52,608 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:12:52,708 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:12:52,745 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 15:21:05,637 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:21:05,678 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:21:05,686 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251027125604_98_108.xlsx
2025-11-15 15:22:01,116 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:22:01,217 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:22:01,232 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251027125604_98_108.xlsx
2025-11-15 15:25:41,328 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:25:41,393 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:25:41,435 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 15:34:06,437 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:34:06,514 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:34:06,543 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 15:37:42,545 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:37:42,599 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:37:42,606 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 15:39:11,311 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:39:11,314 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:43:33,496 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:43:33,561 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:43:35,318 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 15:54:35,478 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 15:54:35,573 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 15:54:35,609 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:19:42,366 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:19:42,388 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:19:42,389 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单明细20251115154455.xlsx
2025-11-15 16:34:22,100 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:34:22,132 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:34:22,132 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1762933924814.xlsx
2025-11-15 16:46:22,393 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:46:22,453 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:46:28,800 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:46:28,913 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:46:29,004 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:46:37,684 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:46:37,798 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:46:37,873 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:48:30,424 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:48:30,499 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:48:30,574 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:48:41,962 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:48:42,077 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:48:42,154 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:52:35,911 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:52:35,973 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:52:36,050 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:57:42,391 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:57:42,459 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:57:42,542 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:59:06,951 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:59:07,027 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:59:07,102 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 16:59:14,530 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 16:59:14,619 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 16:59:14,678 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:01:30,192 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:01:30,257 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:01:30,308 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:04:33,184 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:04:33,258 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:04:33,323 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:04:44,983 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:04:45,063 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:04:45,134 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:09:39,792 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:09:39,862 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:09:39,929 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:12:37,423 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:12:37,485 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:12:37,545 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:28:46,799 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:28:46,861 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:28:48,645 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:59:22,782 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:59:22,828 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:59:22,867 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:59:33,421 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:59:33,523 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 17:59:33,585 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 17:59:39,804 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 17:59:39,862 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 18:00:04,123 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 18:00:04,180 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 18:00:05,990 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-15 18:01:51,643 - app.services.order_service - INFO - 初始化OrderService
2025-11-15 18:01:51,715 - app.services.order_service - INFO - OrderService初始化完成
2025-11-15 18:01:51,763 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-16 10:48:45,445 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 10:48:45,564 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 10:48:45,630 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-16 10:56:22,512 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 10:56:22,516 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 11:23:59,369 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 11:23:59,374 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 11:26:06,793 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 11:26:06,795 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 12:51:06,907 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 12:51:06,910 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 13:03:10,559 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 13:03:10,563 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 13:03:10,580 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 13:03:10,584 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 13:18:18,246 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 13:18:18,248 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 13:18:18,268 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 13:18:18,271 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 13:51:09,015 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 13:51:09,018 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 14:25:50,009 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 14:25:50,033 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 14:25:50,107 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-16 14:25:55,548 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 14:25:55,619 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 14:25:55,699 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-16 14:39:42,977 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 14:39:42,981 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 14:39:42,993 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 14:39:42,996 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 14:59:35,433 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 14:59:35,437 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 14:59:35,437 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251115212128_148_108.xlsx
2025-11-16 15:03:21,889 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 15:03:21,893 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 15:03:21,894 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251115212128_148_108.xlsx
2025-11-16 15:08:33,542 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 15:08:33,546 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 15:08:33,546 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/微信图片_20251115212128_148_108.xlsx
2025-11-16 15:11:11,179 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 15:11:11,268 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 15:11:11,345 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-16 15:13:47,387 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 15:13:47,411 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 15:13:49,534 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-16 15:15:36,186 - app.services.order_service - INFO - 初始化OrderService
2025-11-16 15:15:36,205 - app.services.order_service - INFO - OrderService初始化完成
2025-11-16 15:15:37,909 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-20 18:36:13,245 - app.services.order_service - INFO - 初始化OrderService
2025-11-20 18:36:13,299 - app.services.order_service - INFO - OrderService初始化完成
2025-11-20 18:36:13,371 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-20 18:36:42,069 - app.services.order_service - INFO - 初始化OrderService
2025-11-20 18:36:42,111 - app.services.order_service - INFO - OrderService初始化完成
2025-11-20 18:36:42,174 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-20 18:42:25,860 - app.services.order_service - INFO - 初始化OrderService
2025-11-20 18:42:25,947 - app.services.order_service - INFO - OrderService初始化完成
2025-11-20 18:42:26,024 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-20 18:44:11,101 - app.services.order_service - INFO - 初始化OrderService
2025-11-20 18:44:11,134 - app.services.order_service - INFO - OrderService初始化完成
2025-11-20 18:44:11,376 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-20 18:47:03,878 - app.services.order_service - INFO - 初始化OrderService
2025-11-20 18:47:03,927 - app.services.order_service - INFO - OrderService初始化完成
2025-11-20 18:47:04,166 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-11-20 18:56:30,301 - app.services.order_service - INFO - 初始化OrderService
2025-11-20 18:56:30,361 - app.services.order_service - INFO - OrderService初始化完成
2025-11-20 18:56:30,593 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-12-01 22:21:09,063 - app.services.order_service - INFO - 初始化OrderService
2025-12-01 22:21:09,121 - app.services.order_service - INFO - OrderService初始化完成
2025-12-01 22:21:10,614 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
2025-12-12 11:00:14,048 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 11:00:14,096 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 11:00:14,097 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765440157955.xlsx
2025-12-12 11:13:15,273 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 11:13:15,288 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 11:13:15,289 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765440157955.xlsx
2025-12-12 11:22:25,989 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 11:22:25,990 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 11:22:25,990 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765440157955.xlsx
2025-12-12 11:32:26,688 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 11:32:26,694 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 11:32:26,696 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765440157955.xlsx
2025-12-12 11:34:40,235 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 11:34:40,238 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 11:34:40,238 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765440157955.xlsx
2025-12-12 11:38:53,256 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 11:38:53,260 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 11:38:53,260 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765440157955.xlsx
2025-12-12 11:49:38,202 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 11:49:38,203 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 11:49:38,206 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765440157955.xlsx
2025-12-12 12:30:07,696 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 12:30:07,700 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 12:30:07,700 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765513775092.xlsx
2025-12-12 12:32:20,576 - app.services.order_service - INFO - 初始化OrderService
2025-12-12 12:32:20,579 - app.services.order_service - INFO - OrderService初始化完成
2025-12-12 12:32:20,579 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\蓉城易购-订单1765513867817.xlsx
2026-03-30 10:20:20,106 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 10:20:20,145 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 10:20:20,145 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: e:\2025Code\python\orc-order-v2\data\output\495a630b-87cf-4245-834e-2705a3dcd12f.xlsx
2026-03-30 10:21:25,351 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 10:21:25,356 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 10:21:25,356 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: e:\2025Code\python\orc-order-v2\data\output\495a630b-87cf-4245-834e-2705a3dcd12f.xlsx
2026-03-30 10:21:52,466 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 10:21:52,470 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 10:21:52,470 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: e:\2025Code\python\orc-order-v2\data\output\495a630b-87cf-4245-834e-2705a3dcd12f.xlsx
2026-03-30 13:28:54,447 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:28:54,451 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:28:54,452 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\原始数据.xlsx
2026-03-30 13:28:54,461 - app.services.order_service - ERROR - 检查特殊预处理时出错: 'OrderService' object has no attribute 'config_manager'
2026-03-30 13:29:24,572 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:29:24,576 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:29:24,576 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\原始数据.xlsx
2026-03-30 13:29:24,577 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:29:24,581 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:29:24,854 - app.services.order_service - INFO - 识别到杨碧月订单,执行预处理...
2026-03-30 13:29:24,868 - app.services.order_service - WARNING - 预处理识别失败: name 'os' is not defined
2026-03-30 13:29:58,480 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:29:58,484 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:29:58,484 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\原始数据.xlsx
2026-03-30 13:29:58,485 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:29:58,488 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:29:58,758 - app.services.order_service - INFO - 识别到杨碧月订单,执行预处理...
2026-03-30 13:29:58,893 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:\2025Code\python\orc-order-v2\data\output\预处理之后.xlsx
2026-03-30 13:30:29,313 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:30:29,317 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:30:29,318 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\原始数据.xlsx
2026-03-30 13:30:29,319 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:30:29,322 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:30:29,597 - app.services.order_service - INFO - 识别到杨碧月订单,执行预处理...
2026-03-30 13:30:29,734 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:\2025Code\python\orc-order-v2\data\output\预处理之后.xlsx
2026-03-30 13:31:51,943 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:31:51,947 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:31:51,947 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:\2025Code\python\orc-order-v2\data\output\原始数据.xlsx
2026-03-30 13:31:51,948 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:31:51,952 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:31:52,230 - app.services.order_service - INFO - 识别到杨碧月订单,执行预处理...
2026-03-30 13:31:52,367 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:\2025Code\python\orc-order-v2\data\output\预处理之后.xlsx
2026-03-30 13:46:00,207 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:46:00,211 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:46:00,225 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/原始数据.xlsx
2026-03-30 13:46:00,227 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:46:00,230 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:46:00,255 - app.services.order_service - INFO - 识别到杨碧月订单,执行通用预处理...
2026-03-30 13:46:00,355 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:/2025Code/python/orc-order-v2/data/output\预处理之后.xlsx
2026-03-30 13:46:02,782 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:46:02,785 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:47:05,230 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:47:05,233 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:47:05,252 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1765513867817.xlsx
2026-03-30 13:47:05,253 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:47:05,256 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:47:06,651 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:47:06,655 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:48:28,517 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:48:28,520 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:48:28,522 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
2026-03-30 13:48:28,522 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:48:28,525 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:48:30,799 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:48:30,802 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:51:44,153 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:51:44,156 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:51:44,167 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单明细20260330133908.xlsx
2026-03-30 13:51:44,168 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:51:44,171 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 13:51:46,069 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 13:51:46,072 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:00:52,371 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:00:52,375 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:00:52,389 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
2026-03-30 14:00:52,389 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:00:52,393 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:01:20,292 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:01:20,295 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:01:20,304 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
2026-03-30 14:01:20,305 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:01:20,308 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:01:24,373 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:01:24,376 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:06:58,240 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:06:58,243 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:06:58,248 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
2026-03-30 14:07:00,053 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:07:00,055 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:14:46,829 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:14:46,832 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:14:46,844 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单明细20260330133908.xlsx
2026-03-30 14:14:47,889 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:14:47,891 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:23:18,308 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:23:18,311 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:23:18,320 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
2026-03-30 14:23:20,041 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:23:20,043 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:24:02,880 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:24:02,883 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:24:02,890 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
2026-03-30 14:24:04,316 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:24:04,318 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:28:39,036 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:28:39,040 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:28:39,040 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data/output/订单1774849009841.xlsx
2026-03-30 14:28:39,333 - app.services.order_service - INFO - 识别到蓉城易购订单,执行专用预处理...
2026-03-30 14:28:39,514 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: data/output\预处理之后_订单1774849009841.xlsx
2026-03-30 14:28:39,734 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:28:39,738 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:28:39,738 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data/output/订单明细20260330133908.xlsx
2026-03-30 14:28:39,746 - app.services.order_service - INFO - 识别到烟草公司订单,执行专用预处理...
2026-03-30 14:28:39,828 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: data/output\预处理之后_订单明细20260330133908.xlsx
2026-03-30 14:41:11,945 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:41:11,949 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:41:11,957 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
2026-03-30 14:41:11,994 - app.services.order_service - INFO - 识别到蓉城易购订单,执行专用预处理...
2026-03-30 14:41:12,128 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:/2025Code/python/orc-order-v2/data/output\预处理之后_订单1774849009841.xlsx
2026-03-30 14:41:33,385 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:41:33,388 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:41:47,793 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:41:47,796 - app.services.order_service - INFO - OrderService初始化完成
2026-03-30 14:41:47,810 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单明细20260330133908.xlsx
2026-03-30 14:41:47,819 - app.services.order_service - INFO - 识别到烟草公司订单,执行专用预处理...
2026-03-30 14:41:47,865 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:/2025Code/python/orc-order-v2/data/output\预处理之后_订单明细20260330133908.xlsx
2026-03-30 14:41:49,670 - app.services.order_service - INFO - 初始化OrderService
2026-03-30 14:41:49,674 - app.services.order_service - INFO - OrderService初始化完成
+49
View File
@@ -0,0 +1,49 @@
2025-11-15 15:44:36,108 - app.services.tobacco_service - WARNING - 未找到烟草公司订单明细文件
2025-11-15 15:44:36,116 - app.services.tobacco_service - WARNING - 未找到烟草公司订单明细文件
2025-11-15 15:44:36,125 - app.services.tobacco_service - ERROR - 未找到可处理的烟草订单明细文件
2025-11-15 15:45:17,682 - app.services.tobacco_service - INFO - 找到最新烟草订单明细文件: data/output\订单明细20251115154455.xlsx
2025-11-15 15:45:17,689 - app.services.tobacco_service - INFO - 开始处理烟草公司订单: data/output\订单明细20251115154455.xlsx
2025-11-15 15:45:17,724 - app.services.tobacco_service - INFO - 采购单生成成功: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:45:17,726 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:45:17,738 - app.services.tobacco_service - INFO - 采购单已生成: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:45:17,850 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:46:55,566 - app.services.tobacco_service - INFO - 找到最新烟草订单明细文件: data/output\订单明细20251115154455.xlsx
2025-11-15 15:46:55,572 - app.services.tobacco_service - INFO - 开始处理烟草公司订单: data/output\订单明细20251115154455.xlsx
2025-11-15 15:46:55,599 - app.services.tobacco_service - INFO - 采购单生成成功: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:46:55,600 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:46:55,607 - app.services.tobacco_service - INFO - 采购单已生成: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:46:55,667 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:47:59,480 - app.services.tobacco_service - INFO - 找到最新烟草订单明细文件: data/output\订单明细20251115154455.xlsx
2025-11-15 15:47:59,485 - app.services.tobacco_service - INFO - 开始处理烟草公司订单: data/output\订单明细20251115154455.xlsx
2025-11-15 15:47:59,512 - app.services.tobacco_service - INFO - 采购单生成成功: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:47:59,513 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:47:59,520 - app.services.tobacco_service - INFO - 采购单已生成: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:47:59,582 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:50:32,432 - app.services.tobacco_service - INFO - 找到最新烟草订单明细文件: data/output\订单明细20251115154455.xlsx
2025-11-15 15:50:32,432 - app.services.tobacco_service - INFO - 开始处理烟草公司订单: data/output\订单明细20251115154455.xlsx
2025-11-15 15:50:32,455 - app.services.tobacco_service - INFO - 采购单生成成功: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:50:32,456 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:50:32,463 - app.services.tobacco_service - INFO - 采购单已生成: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:50:32,519 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:51:02,790 - app.services.tobacco_service - INFO - 找到最新烟草订单明细文件: data/output\订单明细20251115154455.xlsx
2025-11-15 15:51:02,791 - app.services.tobacco_service - INFO - 开始处理烟草公司订单: data/output\订单明细20251115154455.xlsx
2025-11-15 15:51:02,814 - app.services.tobacco_service - INFO - 采购单生成成功: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:51:02,815 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:51:02,827 - app.services.tobacco_service - INFO - 采购单已生成: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:51:02,885 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:53:51,294 - app.services.tobacco_service - INFO - 找到最新烟草订单明细文件: data/output\订单明细20251115154455.xlsx
2025-11-15 15:53:51,298 - app.services.tobacco_service - INFO - 开始处理烟草公司订单: data/output\订单明细20251115154455.xlsx
2025-11-15 15:53:51,328 - app.services.tobacco_service - INFO - 采购单生成成功: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:53:51,329 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:53:51,337 - app.services.tobacco_service - INFO - 采购单已生成: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:53:51,391 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:54:07,757 - app.services.tobacco_service - INFO - 找到最新烟草订单明细文件: data/output\订单明细20251115154455.xlsx
2025-11-15 15:54:07,758 - app.services.tobacco_service - INFO - 开始处理烟草公司订单: data/output\订单明细20251115154455.xlsx
2025-11-15 15:54:07,783 - app.services.tobacco_service - INFO - 采购单生成成功: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:54:07,783 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2025-11-15 15:54:07,796 - app.services.tobacco_service - INFO - 采购单已生成: data/result\银豹采购单_烟草公司.xls
2025-11-15 15:54:07,930 - app.services.tobacco_service - INFO - 烟草公司订单处理成功,订单时间: 2025-11-10, 总金额: 12226.22, 处理条目: 34
2026-03-30 14:28:39,783 - app.services.tobacco_service - INFO - 执行烟草订单专用预处理: data/output/订单明细20260330133908.xlsx
2026-03-30 14:28:39,828 - app.services.tobacco_service - INFO - 烟草订单预处理完成: data/output\预处理之后_订单明细20260330133908.xlsx
2026-03-30 14:41:47,819 - app.services.tobacco_service - INFO - 执行烟草订单专用预处理: E:/2025Code/python/orc-order-v2/data/output/订单明细20260330133908.xlsx
2026-03-30 14:41:47,865 - app.services.tobacco_service - INFO - 烟草订单预处理完成: E:/2025Code/python/orc-order-v2/data/output\预处理之后_订单明细20260330133908.xlsx
-43
View File
@@ -1,43 +0,0 @@
server {
listen 18888;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_min_length 1000;
# API proxy to backend
location /api/ {
proxy_pass http://backend:18889;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket proxy to backend
location /ws/ {
proxy_pass http://backend:18889;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
# Vue Router history mode - serve index.html for all routes
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
}
-1
View File
@@ -1,5 +1,4 @@
configparser>=5.0.0 configparser>=5.0.0
python-dotenv>=1.0.0
numpy>=1.19.0 numpy>=1.19.0
openpyxl>=3.0.0 openpyxl>=3.0.0
pandas>=1.3.0 pandas>=1.3.0
+206
View File
@@ -0,0 +1,206 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
OCR订单处理系统 - 主入口
---------------------
提供命令行接口整合OCR识别Excel处理和订单合并功能
"""
import os
import sys
import argparse
from typing import List, Optional
from app.config.settings import ConfigManager
from app.core.utils.log_utils import get_logger, close_all_loggers, set_log_level
from app.services.ocr_service import OCRService
from app.services.order_service import OrderService
from app.services.tobacco_service import TobaccoService
logger = get_logger(__name__)
def parse_args():
"""
解析命令行参数
Returns:
解析后的参数
"""
parser = argparse.ArgumentParser(description='OCR订单处理系统')
# 通用选项
parser.add_argument('--config', type=str, help='配置文件路径')
parser.add_argument('--log-level', type=str, choices=['debug', 'info', 'warning', 'error', 'critical'], help='日志级别')
# 子命令
subparsers = parser.add_subparsers(dest='command', help='子命令')
# OCR识别命令
ocr_parser = subparsers.add_parser('ocr', help='OCR识别')
ocr_parser.add_argument('--input', type=str, help='输入图片路径')
ocr_parser.add_argument('--batch', action='store_true', help='批量处理')
ocr_parser.add_argument('--batch-size', type=int, default=5, help='批处理大小')
ocr_parser.add_argument('--max-workers', type=int, default=4, help='最大线程数')
# Excel处理命令
excel_parser = subparsers.add_parser('excel', help='Excel处理')
excel_parser.add_argument('--input', type=str, help='输入Excel文件路径')
# 合并命令
merge_parser = subparsers.add_parser('merge', help='合并采购单')
merge_parser.add_argument('--input', type=str, help='输入采购单文件路径(逗号分隔)')
# 完整流程命令
pipeline_parser = subparsers.add_parser('pipeline', help='完整处理流程')
pipeline_parser.add_argument('--input', type=str, help='输入图片路径')
pipeline_parser.add_argument('--merge', action='store_true', help='是否合并采购单')
# 烟草订单处理
tobacco_parser = subparsers.add_parser('tobacco', help='处理烟草订单')
tobacco_parser.add_argument('--input', type=str, help='输入订单明细文件路径')
# 解析参数
parsed_args = parser.parse_args()
return parsed_args
def main():
"""
主函数入口
Returns:
退出码
"""
# 解析命令行参数
args = parse_args()
if not args.command:
argparse.ArgumentParser().print_help()
return 1
# 加载配置
config_path = args.config
config_manager = ConfigManager(config_path)
config = config_manager.config
# 设置日志级别
log_level = getattr(args, 'log_level', None)
if log_level:
set_log_level(log_level)
try:
if args.command == 'ocr':
# OCR识别处理
ocr_service = OCRService(config)
if args.batch:
# 批量处理
total, success = ocr_service.batch_process(
batch_size=args.batch_size,
max_workers=args.max_workers
)
return 0 if success > 0 else 1
else:
# 处理单个文件
result = ocr_service.process_image(args.input)
return 0 if result else 1
elif args.command == 'excel':
# Excel处理
order_service = OrderService(config)
if args.input:
# 处理指定文件
result = order_service.process_excel(args.input)
else:
# 处理最新文件
result = order_service.process_excel()
return 0 if result else 1
elif args.command == 'merge':
# 合并采购单
order_service = OrderService(config)
if args.input:
# 合并指定文件
file_list = args.input.split(',')
result = order_service.merge_purchase_orders(file_list)
else:
# 合并所有采购单
result = order_service.merge_all_purchase_orders()
return 0 if result else 1
elif args.command == 'pipeline':
# 完整流程
ocr_service = OCRService(config)
order_service = OrderService(config)
# 1. OCR处理
if args.input:
# 处理单个文件
excel_file = ocr_service.process_image(args.input)
else:
# 批量处理
total, success = ocr_service.batch_process()
if total == 0:
logger.warning("没有找到需要处理的图片")
elif success == 0:
logger.warning("OCR处理没有成功处理任何新文件")
excel_file = None # 批量处理不返回具体文件
# 2. Excel处理
if excel_file:
# 处理指定的Excel文件
result = order_service.process_excel(excel_file)
else:
# 处理最新的Excel文件
result = order_service.process_excel()
if not result:
logger.error("Excel处理失败")
return 1
# 3. 合并采购单(可选)
if args.merge:
result = order_service.merge_all_purchase_orders()
if not result:
logger.warning("合并采购单失败")
# 不影响整体流程,继续执行
return 0
elif args.command == 'tobacco':
# 烟草订单处理
tobacco_service = TobaccoService(config)
if args.input:
# 处理指定文件
logger.info(f"开始处理烟草订单,输入文件: {args.input}")
result = tobacco_service.process_tobacco_order(args.input)
else:
# 处理最新文件
logger.info("开始烟草公司订单处理")
result = tobacco_service.process_tobacco_order()
# 检查结果是否为None
if result is None:
logger.error("烟草订单处理失败")
return 1
else:
logger.info(f"烟草订单处理成功,输出文件: {result}")
# 确保result是绝对路径
if not os.path.isabs(result):
result = os.path.abspath(result)
logger.info(f"烟草订单处理完成,绝对路径: {result}")
return 0
else:
logger.error(f"未知命令: {args.command}")
return 1
except Exception as e:
logger.error(f"执行过程中发生错误: {e}", exc_info=True)
return 1
finally:
# 关闭所有日志记录器
close_all_loggers()
if __name__ == "__main__":
sys.exit(main())
Binary file not shown.
View File
-222
View File
@@ -1,222 +0,0 @@
"""app.core.handlers.calculator 单元测试"""
import pytest
import pandas as pd
import numpy as np
from app.core.handlers.calculator import DataCalculator
@pytest.fixture
def sample_df():
return pd.DataFrame({
'price': [10.0, 20.0, 30.0],
'quantity': [2, 5, 10],
'name': ['A', 'B', 'C'],
})
class TestMultiply:
def test_basic_multiply(self, sample_df):
calc = DataCalculator()
calc.add_rule('multiply', source_column='price', target_column='total', factor=2)
result = calc.calculate(sample_df)
assert list(result['total']) == [20.0, 40.0, 60.0]
def test_multiply_missing_source(self, sample_df):
calc = DataCalculator()
calc.add_rule('multiply', source_column='nonexistent', target_column='total', factor=2)
result = calc.calculate(sample_df)
assert 'total' not in result.columns
def test_multiply_default_factor(self, sample_df):
calc = DataCalculator()
calc.add_rule('multiply', source_column='price', target_column='copy', factor=1)
result = calc.calculate(sample_df)
assert list(result['copy']) == [10.0, 20.0, 30.0]
def test_convenience_method(self, sample_df):
calc = DataCalculator()
calc.multiply('price', 'total', 3)
result = calc.calculate(sample_df)
assert list(result['total']) == [30.0, 60.0, 90.0]
class TestDivide:
def test_basic_divide(self, sample_df):
calc = DataCalculator()
calc.add_rule('divide', source_column='price', target_column='half', divisor=2)
result = calc.calculate(sample_df)
assert list(result['half']) == [5.0, 10.0, 15.0]
def test_divide_by_zero(self, sample_df):
calc = DataCalculator()
calc.add_rule('divide', source_column='price', target_column='half', divisor=0)
result = calc.calculate(sample_df)
assert 'half' not in result.columns
def test_divide_missing_source(self, sample_df):
calc = DataCalculator()
calc.add_rule('divide', source_column='nonexistent', target_column='x', divisor=2)
result = calc.calculate(sample_df)
assert 'x' not in result.columns
class TestAdd:
def test_add_columns(self):
df = pd.DataFrame({'a': [1, 2, 3], 'b': [10, 20, 30]})
calc = DataCalculator()
calc.add_rule('add', columns=['a', 'b'], target_column='sum')
result = calc.calculate(df)
assert list(result['sum']) == [11, 22, 33]
def test_add_constant(self):
df = pd.DataFrame({'a': [1, 2, 3]})
calc = DataCalculator()
calc.add_rule('add', target_column='a', constant=100)
result = calc.calculate(df)
assert list(result['a']) == [101, 102, 103]
def test_add_columns_with_constant(self):
df = pd.DataFrame({'a': [1, 2], 'b': [3, 4]})
calc = DataCalculator()
calc.add_rule('add', columns=['a', 'b'], target_column='total', constant=10)
result = calc.calculate(df)
assert list(result['total']) == [14, 16]
def test_add_string_column(self):
df = pd.DataFrame({'a': [1, 2]})
calc = DataCalculator()
calc.add_rule('add', columns='a', target_column='total')
result = calc.calculate(df)
assert list(result['total']) == [1, 2]
class TestSubtract:
def test_subtract_two_columns(self):
df = pd.DataFrame({'income': [100, 200], 'cost': [30, 80]})
calc = DataCalculator()
calc.add_rule('subtract', minuend='income', subtrahend='cost', target_column='profit')
result = calc.calculate(df)
assert list(result['profit']) == [70, 120]
def test_subtract_constant(self):
df = pd.DataFrame({'price': [100, 200]})
calc = DataCalculator()
calc.add_rule('subtract', minuend='price', target_column='discounted', constant=10)
result = calc.calculate(df)
assert list(result['discounted']) == [90, 190]
def test_subtract_missing_minuend(self):
df = pd.DataFrame({'a': [1, 2]})
calc = DataCalculator()
calc.add_rule('subtract', minuend='nonexistent', target_column='x', constant=1)
result = calc.calculate(df)
assert 'x' not in result.columns
class TestFormula:
def test_basic_formula(self, sample_df):
calc = DataCalculator()
calc.add_rule('formula', formula='price * quantity', target_column='total')
result = calc.calculate(sample_df)
assert list(result['total']) == [20.0, 100.0, 300.0]
def test_invalid_formula(self, sample_df):
calc = DataCalculator()
calc.add_rule('formula', formula='nonexistent + 1', target_column='x')
result = calc.calculate(sample_df)
# formula fails, original df returned
assert 'x' not in result.columns
def test_formula_missing_target(self, sample_df):
calc = DataCalculator()
calc.add_rule('formula', formula='price * 2')
result = calc.calculate(sample_df)
# no target_column, nothing happens
assert list(result['price']) == [10.0, 20.0, 30.0]
class TestRound:
def test_round_specific_columns(self):
df = pd.DataFrame({'a': [1.234, 2.567], 'b': [3.1, 4.9]})
calc = DataCalculator()
calc.add_rule('round', columns=['a'], decimals=1)
result = calc.calculate(df)
assert list(result['a']) == [1.2, 2.6]
assert list(result['b']) == [3.1, 4.9] # unchanged
def test_round_all_numeric(self):
df = pd.DataFrame({'a': [1.234, 2.567], 'b': [3.111, 4.999]})
calc = DataCalculator()
calc.add_rule('round', decimals=0)
result = calc.calculate(df)
assert list(result['a']) == [1.0, 3.0]
assert list(result['b']) == [3.0, 5.0]
def test_round_string_column_skipped(self):
df = pd.DataFrame({'name': ['a', 'b'], 'val': [1.5, 2.5]})
calc = DataCalculator()
calc.add_rule('round', columns=['name', 'val'], decimals=0)
result = calc.calculate(df)
assert list(result['val']) == [2.0, 2.0]
class TestSum:
def test_sum_columns_to_target(self):
df = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6]})
calc = DataCalculator()
calc.add_rule('sum', columns=['a', 'b'], target_column='total')
result = calc.calculate(df)
assert list(result['total']) == [4, 6]
def test_sum_missing_columns(self):
df = pd.DataFrame({'a': [1, 2]})
calc = DataCalculator()
calc.add_rule('sum', columns=['a', 'missing'], target_column='total')
result = calc.calculate(df)
assert list(result['total']) == [1, 2]
class TestChaining:
def test_multiple_rules(self, sample_df):
calc = DataCalculator()
calc.add_rule('multiply', source_column='price', target_column='total', factor=2)
calc.add_rule('add', columns=['total', 'quantity'], target_column='grand')
result = calc.calculate(sample_df)
assert list(result['total']) == [20.0, 40.0, 60.0]
assert list(result['grand']) == [22.0, 45.0, 70.0]
def test_chaining_convenience(self, sample_df):
calc = DataCalculator()
calc.multiply('price', 'total', 2).round_columns('total', 0)
result = calc.calculate(sample_df)
assert list(result['total']) == [20.0, 40.0, 60.0]
class TestEdgeCases:
def test_empty_dataframe(self):
df = pd.DataFrame({'a': pd.Series([], dtype=float)})
calc = DataCalculator()
calc.add_rule('multiply', source_column='a', target_column='b', factor=2)
result = calc.calculate(df)
assert len(result) == 0
def test_no_rules(self, sample_df):
calc = DataCalculator()
result = calc.calculate(sample_df)
assert list(result['price']) == [10.0, 20.0, 30.0]
def test_unknown_rule_type(self, sample_df):
calc = DataCalculator()
calc.add_rule('unknown_op', source_column='price', target_column='x')
result = calc.calculate(sample_df)
# unknown rule is skipped, df unchanged
assert list(result['price']) == [10.0, 20.0, 30.0]
def test_rule_failure_continues(self, sample_df):
calc = DataCalculator()
calc.add_rule('formula', formula='nonexistent + 1', target_column='x')
calc.add_rule('multiply', source_column='price', target_column='y', factor=2)
result = calc.calculate(sample_df)
assert list(result['y']) == [20.0, 40.0, 60.0]
-154
View File
@@ -1,154 +0,0 @@
"""app.core.handlers.column_mapper 单元测试"""
import pytest
import pandas as pd
from app.core.handlers.column_mapper import ColumnMapper
class TestStandardColumns:
"""STANDARD_COLUMNS 完整性测试"""
def test_has_all_standard_fields(self):
expected = {'barcode', 'name', 'specification', 'quantity', 'unit',
'unit_price', 'total_price', 'gift_quantity',
'category', 'brand', 'supplier'}
assert set(ColumnMapper.STANDARD_COLUMNS.keys()) == expected
def test_no_empty_alias_lists(self):
for field, aliases in ColumnMapper.STANDARD_COLUMNS.items():
assert len(aliases) > 0, f"{field} has no aliases"
def test_barcode_includes_key_names(self):
bc = ColumnMapper.STANDARD_COLUMNS['barcode']
assert '条码' in bc
assert '商品条码' in bc
assert 'barcode' in bc
def test_gift_quantity_includes_common_names(self):
gq = ColumnMapper.STANDARD_COLUMNS['gift_quantity']
assert '赠送量' in gq
assert '赠品数量' in gq
class TestFindColumn:
"""ColumnMapper.find_column 列查找测试"""
def test_exact_match(self):
cols = ['商品条码', '商品名称', '数量', '单价']
assert ColumnMapper.find_column(cols, 'barcode') == '商品条码'
def test_exact_match_standard_english(self):
cols = ['barcode', 'name', 'quantity']
assert ColumnMapper.find_column(cols, 'barcode') == 'barcode'
def test_whitespace_match(self):
"""列名含空格时应匹配"""
cols = ['名 称', '数 量']
assert ColumnMapper.find_column(cols, 'name') == '名 称'
assert ColumnMapper.find_column(cols, 'quantity') == '数 量'
def test_partial_match_substring(self):
"""列名包含候选名时应匹配"""
cols = ['商品条码(小条码)', '商品名称']
assert ColumnMapper.find_column(cols, 'barcode') == '商品条码(小条码)'
def test_not_found_returns_none(self):
cols = ['日期', '备注', '编号']
assert ColumnMapper.find_column(cols, 'barcode') is None
def test_unknown_standard_name_returns_none(self):
cols = ['商品条码']
assert ColumnMapper.find_column(cols, 'nonexistent_field') is None
def test_first_match_wins(self):
"""多个列都能匹配时返回第一个"""
cols = ['条码', '商品条码', 'barcode']
assert ColumnMapper.find_column(cols, 'barcode') == '条码'
def test_case_insensitive(self):
cols = ['Barcode', 'Name']
assert ColumnMapper.find_column(cols, 'barcode') == 'Barcode'
def test_all_fields_matchable(self):
"""每个标准字段都能找到至少一个匹配"""
cols = [
'商品条码', '商品名称', '规格', '数量', '单位',
'单价', '金额', '赠送量', '类别', '品牌', '供应商',
]
for std_name in ColumnMapper.STANDARD_COLUMNS:
result = ColumnMapper.find_column(cols, std_name)
assert result is not None, f"Could not find {std_name} in {cols}"
class TestDetectHeaderRow:
"""ColumnMapper.detect_header_row 表头检测测试"""
def test_header_on_first_row(self):
df = pd.DataFrame({
'A': ['条码', '123456', '789012'],
'B': ['数量', '10', '20'],
'C': ['单价', '5.5', '3.0'],
})
assert ColumnMapper.detect_header_row(df, min_matches=2) == 0
def test_header_on_second_row(self):
df = pd.DataFrame({
'A': ['备注', '条码', '123456'],
'B': ['日期', '数量', '10'],
'C': ['时间', '单价', '5.5'],
})
assert ColumnMapper.detect_header_row(df, min_matches=2) == 1
def test_no_header_returns_minus_one(self):
df = pd.DataFrame({
'A': ['aaa', 'bbb', 'ccc'],
'B': ['ddd', 'eee', 'fff'],
})
assert ColumnMapper.detect_header_row(df, min_matches=3) == -1
def test_empty_dataframe(self):
df = pd.DataFrame()
assert ColumnMapper.detect_header_row(df) == -1
def test_max_rows_limits_scan(self):
"""表头在第 10 行但 max_rows=5 时应返回 -1"""
data = {f'col{i}': ['x'] * 15 for i in range(3)}
data['col0'][10] = '条码'
data['col1'][10] = '数量'
data['col2'][10] = '单价'
df = pd.DataFrame(data)
assert ColumnMapper.detect_header_row(df, max_rows=5, min_matches=2) == -1
class TestColumnMapperInstance:
"""ColumnMapper 实例方法测试"""
def test_init_with_no_config(self):
mapper = ColumnMapper()
assert mapper.mapping_config == {}
def test_init_with_custom_config(self):
mapper = ColumnMapper(mapping_config={'barcode': ['我的条码']})
assert '我的条码' in mapper.custom_mappings
def test_map_columns_renames(self):
mapper = ColumnMapper()
df = pd.DataFrame({'商品条码': ['123'], '商品名称': ['测试'], '数量': [10]})
result = mapper.map_columns(df, target_columns=['barcode', 'name', 'quantity'])
assert 'barcode' in result.columns
assert 'name' in result.columns
assert 'quantity' in result.columns
def test_map_columns_fills_missing(self):
mapper = ColumnMapper()
df = pd.DataFrame({'商品条码': ['123']})
result = mapper.map_columns(df, target_columns=['barcode', 'quantity'])
assert 'barcode' in result.columns
assert 'quantity' in result.columns
assert result['quantity'].iloc[0] == 0 # default value
def test_add_custom_mapping(self):
mapper = ColumnMapper()
mapper.add_custom_mapping('barcode', '自定义条码列')
assert '自定义条码列' in mapper.reverse_mapping
assert mapper.reverse_mapping['自定义条码列'] == 'barcode'
-236
View File
@@ -1,236 +0,0 @@
"""app.core.handlers.data_cleaner 单元测试"""
import pytest
import pandas as pd
from app.core.handlers.data_cleaner import DataCleaner
@pytest.fixture
def sample_df():
return pd.DataFrame({
'name': [' Alice ', 'Bob', 'Charlie', 'Dave'],
'age': [25, 30, None, 40],
'score': [80.5, 90.0, 70.0, 85.0],
'city': ['Beijing', 'Shanghai', 'Beijing', 'Guangzhou'],
})
class TestFillNa:
def test_fill_na_with_value(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('fill_na', columns=['age'], value=0)
result = cleaner.clean(sample_df)
assert result['age'].isna().sum() == 0
assert result.loc[2, 'age'] == 0
def test_fill_na_all_columns(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('fill_na', value=-1)
result = cleaner.clean(sample_df)
assert result.isna().sum().sum() == 0
def test_fill_na_string_column(self):
df = pd.DataFrame({'a': ['x', None, 'z']})
cleaner = DataCleaner()
cleaner.add_rule('fill_na', columns=['a'], value='unknown')
result = cleaner.clean(df)
assert result.loc[1, 'a'] == 'unknown'
def test_convenience_method(self, sample_df):
cleaner = DataCleaner()
cleaner.fill_na(columns='age', value=99)
result = cleaner.clean(sample_df)
assert result.loc[2, 'age'] == 99
class TestRemoveDuplicates:
def test_remove_by_subset(self):
df = pd.DataFrame({
'name': ['A', 'B', 'A', 'C'],
'val': [1, 2, 3, 4],
})
cleaner = DataCleaner()
cleaner.add_rule('remove_duplicates', subset=['name'], keep='first')
result = cleaner.clean(df)
assert len(result) == 3
assert list(result['name']) == ['A', 'B', 'C']
def test_remove_all_columns(self):
df = pd.DataFrame({
'a': [1, 1, 2],
'b': [10, 10, 20],
})
cleaner = DataCleaner()
cleaner.add_rule('remove_duplicates')
result = cleaner.clean(df)
assert len(result) == 2
def test_no_duplicates(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('remove_duplicates', subset=['name'])
result = cleaner.clean(sample_df)
assert len(result) == 4
class TestRemoveRows:
def test_remove_by_condition(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('remove_rows', condition='age > 25')
result = cleaner.clean(sample_df)
assert len(result) == 2
def test_remove_by_values(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('remove_rows', columns=['city'], values=['Beijing'])
result = cleaner.clean(sample_df)
assert len(result) == 2
assert 'Beijing' not in result['city'].values
def test_remove_no_match(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('remove_rows', condition='age > 100')
result = cleaner.clean(sample_df)
assert len(result) == 0 # condition filter: no rows match age > 100
def test_convenience_method(self, sample_df):
cleaner = DataCleaner()
cleaner.remove_rows(condition='score < 75')
result = cleaner.clean(sample_df)
assert len(result) == 1 # condition filter: keeps only Charlie (score=70.0)
class TestConvertType:
def test_to_float(self):
df = pd.DataFrame({'val': ['1.5', '2.7', 'abc']})
cleaner = DataCleaner()
cleaner.add_rule('convert_type', columns=['val'], target_type='float')
result = cleaner.clean(df)
assert result['val'].dtype.kind == 'f'
assert result.loc[0, 'val'] == 1.5
assert pd.isna(result.loc[2, 'val'])
def test_to_int(self):
df = pd.DataFrame({'val': ['1', '2', '3']})
cleaner = DataCleaner()
cleaner.add_rule('convert_type', columns=['val'], target_type='int')
result = cleaner.clean(df)
assert result.loc[0, 'val'] == 1
def test_to_string(self):
df = pd.DataFrame({'val': [1, 2, 3]})
cleaner = DataCleaner()
cleaner.add_rule('convert_type', columns=['val'], target_type='string')
result = cleaner.clean(df)
assert result.loc[0, 'val'] == '1'
def test_missing_column_skipped(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('convert_type', columns=['nonexistent'], target_type='float')
result = cleaner.clean(sample_df)
assert len(result) == 4
class TestStripWhitespace:
def test_strip_specific_columns(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('strip_whitespace', columns=['name'])
result = cleaner.clean(sample_df)
assert result.loc[0, 'name'] == 'Alice'
def test_strip_all_text(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('strip_whitespace')
result = cleaner.clean(sample_df)
assert result.loc[0, 'name'] == 'Alice'
def test_strip_non_text_skipped(self):
df = pd.DataFrame({'val': [1, 2, 3]})
cleaner = DataCleaner()
cleaner.add_rule('strip_whitespace', columns=['val'])
result = cleaner.clean(df)
assert list(result['val']) == [1, 2, 3]
class TestNormalizeText:
def test_lowercase(self):
df = pd.DataFrame({'name': ['ALICE', 'BOB']})
cleaner = DataCleaner()
cleaner.add_rule('normalize_text', columns=['name'], lowercase=True)
result = cleaner.clean(df)
assert list(result['name']) == ['alice', 'bob']
def test_uppercase(self):
df = pd.DataFrame({'name': ['alice', 'bob']})
cleaner = DataCleaner()
cleaner.add_rule('normalize_text', columns=['name'], uppercase=True)
result = cleaner.clean(df)
assert list(result['name']) == ['ALICE', 'BOB']
def test_replace_map(self):
df = pd.DataFrame({'city': ['BJ', 'SH']})
cleaner = DataCleaner()
cleaner.add_rule('normalize_text', columns=['city'], replace_map={'BJ': 'Beijing', 'SH': 'Shanghai'})
result = cleaner.clean(df)
assert list(result['city']) == ['Beijing', 'Shanghai']
class TestValidateData:
def test_validate_logs_but_does_not_modify(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('validate_data', columns=['score'], min_value=0, max_value=100)
result = cleaner.clean(sample_df)
assert len(result) == 4
def test_validate_required(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('validate_data', columns=['age'], required=True)
result = cleaner.clean(sample_df)
assert len(result) == 4
class TestChaining:
def test_multiple_rules(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('strip_whitespace', columns=['name'])
cleaner.add_rule('fill_na', columns=['age'], value=0)
cleaner.add_rule('convert_type', columns=['age'], target_type='int')
result = cleaner.clean(sample_df)
assert result.loc[0, 'name'] == 'Alice'
assert result['age'].isna().sum() == 0
assert result.loc[2, 'age'] == 0
def test_convenience_chaining(self, sample_df):
cleaner = DataCleaner()
cleaner.strip_whitespace('name').fill_na('age', value=0)
result = cleaner.clean(sample_df)
assert result.loc[0, 'name'] == 'Alice'
assert result.loc[2, 'age'] == 0
class TestEdgeCases:
def test_empty_dataframe(self):
df = pd.DataFrame({'a': pd.Series([], dtype=float)})
cleaner = DataCleaner()
cleaner.add_rule('fill_na', value=0)
result = cleaner.clean(df)
assert len(result) == 0
def test_no_rules(self, sample_df):
cleaner = DataCleaner()
result = cleaner.clean(sample_df)
assert len(result) == 4
def test_unknown_rule_type(self, sample_df):
cleaner = DataCleaner()
cleaner.add_rule('unknown_op', columns=['name'])
result = cleaner.clean(sample_df)
assert len(result) == 4
def test_rule_failure_continues(self, sample_df):
"""A failing rule should not block subsequent rules."""
cleaner = DataCleaner()
cleaner.add_rule('convert_type', columns=['nonexistent'], target_type='float')
cleaner.add_rule('fill_na', columns=['age'], value=0)
result = cleaner.clean(sample_df)
assert result.loc[2, 'age'] == 0
-187
View File
@@ -1,187 +0,0 @@
"""app.core.db.product_db 单元测试"""
import os
import tempfile
import pytest
import pandas as pd
from app.core.db.product_db import ProductDatabase
@pytest.fixture
def db_dir():
"""临时目录"""
with tempfile.TemporaryDirectory() as d:
yield d
@pytest.fixture
def sample_excel(db_dir):
"""创建测试用 Excel 文件"""
path = os.path.join(db_dir, '商品资料.xlsx')
df = pd.DataFrame({
'商品条码': ['6920584471055', '6901028001133', '6925303800013'],
'商品名称': ['农夫山泉550ml', '蒙牛纯牛奶', '可口可乐330ml'],
'进货价': [1.2, 3.5, 1.8],
'单位': ['', '', ''],
})
df.to_excel(path, index=False)
return path
@pytest.fixture
def db_with_data(db_dir, sample_excel):
"""已导入数据的数据库"""
db_path = os.path.join(db_dir, 'product_cache.db')
db = ProductDatabase(db_path, sample_excel)
return db
class TestProductDatabaseInit:
"""数据库初始化测试"""
def test_auto_import_on_first_run(self, db_dir, sample_excel):
"""首次运行自动从 Excel 导入"""
db_path = os.path.join(db_dir, 'product_cache.db')
assert not os.path.exists(db_path)
db = ProductDatabase(db_path, sample_excel)
assert os.path.exists(db_path)
assert db.count() == 3
def test_no_reimport_on_existing_db(self, db_dir, sample_excel):
"""数据库已存在时不重新导入"""
db_path = os.path.join(db_dir, 'product_cache.db')
db1 = ProductDatabase(db_path, sample_excel)
assert db1.count() == 3
# 删除 Excel 后仍能打开已有数据库
os.remove(sample_excel)
db2 = ProductDatabase(db_path, sample_excel)
assert db2.count() == 3
def test_missing_excel_creates_empty_db(self, db_dir):
"""Excel 不存在时创建空数据库"""
db_path = os.path.join(db_dir, 'product_cache.db')
fake_excel = os.path.join(db_dir, '不存在.xlsx')
db = ProductDatabase(db_path, fake_excel)
assert os.path.exists(db_path)
assert db.count() == 0
def test_missing_dir_created(self, db_dir, sample_excel):
"""数据库目录不存在时自动创建"""
db_path = os.path.join(db_dir, 'subdir', 'product_cache.db')
db = ProductDatabase(db_path, sample_excel)
assert os.path.exists(db_path)
assert db.count() == 3
class TestGetPrice:
"""单条查询测试"""
def test_existing_barcode(self, db_with_data):
price = db_with_data.get_price('6920584471055')
assert price == pytest.approx(1.2)
def test_nonexistent_barcode(self, db_with_data):
price = db_with_data.get_price('0000000000000')
assert price is None
def test_empty_barcode(self, db_with_data):
price = db_with_data.get_price('')
assert price is None
def test_barcode_with_spaces(self, db_with_data):
"""条码前后空格应能匹配"""
price = db_with_data.get_price(' 6920584471055 ')
assert price == pytest.approx(1.2)
class TestGetPrices:
"""批量查询测试"""
def test_multiple_barcodes(self, db_with_data):
result = db_with_data.get_prices(['6920584471055', '6901028001133'])
assert len(result) == 2
assert result['6920584471055'] == pytest.approx(1.2)
assert result['6901028001133'] == pytest.approx(3.5)
def test_partial_match(self, db_with_data):
"""部分条码存在,部分不存在"""
result = db_with_data.get_prices(['6920584471055', '0000000000000'])
assert len(result) == 1
assert '6920584471055' in result
def test_empty_list(self, db_with_data):
result = db_with_data.get_prices([])
assert result == {}
def test_all_nonexistent(self, db_with_data):
result = db_with_data.get_prices(['0000000000000', '1111111111111'])
assert result == {}
class TestReimport:
"""重新导入测试"""
def test_reimport_clears_and_reloads(self, db_dir, sample_excel):
db_path = os.path.join(db_dir, 'product_cache.db')
db = ProductDatabase(db_path, sample_excel)
assert db.count() == 3
# 修改 Excel,添加一行
df = pd.read_excel(sample_excel)
df = pd.concat([df, pd.DataFrame({
'商品条码': ['6954365200123'],
'商品名称': ['测试商品'],
'进货价': [5.0],
'单位': [''],
})])
df.to_excel(sample_excel, index=False)
count = db.reimport()
assert count == 4
assert db.count() == 4
assert db.get_price('6954365200123') == pytest.approx(5.0)
class TestEdgeCases:
"""边界条件测试"""
def test_excel_with_missing_price(self, db_dir):
"""Excel 中价格列为空的行"""
path = os.path.join(db_dir, '商品资料.xlsx')
df = pd.DataFrame({
'商品条码': ['6920584471055', '6901028001133'],
'商品名称': ['商品A', '商品B'],
'进货价': [1.5, None],
})
df.to_excel(path, index=False)
db_path = os.path.join(db_dir, 'product_cache.db')
db = ProductDatabase(db_path, path)
assert db.count() == 2
assert db.get_price('6920584471055') == pytest.approx(1.5)
assert db.get_price('6901028001133') == pytest.approx(0.0)
def test_excel_with_duplicate_barcodes(self, db_dir):
"""重复条码取最后一条 (INSERT OR REPLACE)"""
path = os.path.join(db_dir, '商品资料.xlsx')
df = pd.DataFrame({
'商品条码': ['6920584471055', '6920584471055'],
'商品名称': ['商品A', '商品A-新'],
'进货价': [1.0, 2.0],
})
df.to_excel(path, index=False)
db_path = os.path.join(db_dir, 'product_cache.db')
db = ProductDatabase(db_path, path)
assert db.count() == 1
assert db.get_price('6920584471055') == pytest.approx(2.0)
-223
View File
@@ -1,223 +0,0 @@
"""app.core.handlers.rule_engine 单元测试"""
import pytest
import pandas as pd
from app.core.handlers.rule_engine import (
apply_rules,
_split_quantity_unit,
_extract_spec_from_name,
_normalize_unit,
_compute_quantity_from_total,
_fill_missing,
_mark_gift,
)
@pytest.fixture
def sample_df():
return pd.DataFrame({
'name': ['农夫山泉550ml*24', '蒙牛纯牛奶', '可口可乐330ml*6'],
'quantity_raw': ['2箱', '5', '3提'],
'unit_price': [28.8, 3.5, 10.8],
'total_price': [57.6, 17.5, 32.4],
})
class TestSplitQuantityUnit:
def test_split_with_unit(self):
df = pd.DataFrame({'quantity_raw': ['2箱', '5瓶', '3提']})
result = _split_quantity_unit(df, 'quantity_raw')
assert list(result['quantity']) == [2.0, 5.0, 3.0]
assert list(result['unit']) == ['', '', '']
def test_split_number_only(self):
df = pd.DataFrame({'quantity_raw': ['10', '20']})
result = _split_quantity_unit(df, 'quantity_raw')
assert list(result['quantity']) == [10.0, 20.0]
def test_split_with_synonyms(self):
df = pd.DataFrame({'quantity_raw': ['2件']})
dictionary = {'unit_synonyms': {'': ''}, 'default_unit': ''}
result = _split_quantity_unit(df, 'quantity_raw', dictionary)
assert result.loc[0, 'unit'] == ''
def test_split_missing_column(self):
df = pd.DataFrame({'other': [1, 2]})
result = _split_quantity_unit(df, 'quantity_raw')
assert 'quantity' not in result.columns
def test_split_invalid_value(self):
df = pd.DataFrame({'quantity_raw': ['abc']})
result = _split_quantity_unit(df, 'quantity_raw')
assert result.loc[0, 'quantity'] == 0.0
class TestExtractSpecFromName:
def test_extract_550ml_24(self):
df = pd.DataFrame({'name': ['农夫山泉550ml*24']})
result = _extract_spec_from_name(df, 'name')
assert result.loc[0, 'package_quantity'] == 24
def test_extract_330ml_6(self):
df = pd.DataFrame({'name': ['可口可乐330ml*6']})
result = _extract_spec_from_name(df, 'name')
assert result.loc[0, 'package_quantity'] == 6
def test_extract_1_star_pattern(self):
df = pd.DataFrame({'name': ['啤酒1*12']})
result = _extract_spec_from_name(df, 'name')
assert result.loc[0, 'package_quantity'] == 12
def test_no_spec(self):
df = pd.DataFrame({'name': ['蒙牛纯牛奶']})
result = _extract_spec_from_name(df, 'name')
assert result.loc[0, 'package_quantity'] is None
def test_missing_column(self):
df = pd.DataFrame({'other': ['test']})
result = _extract_spec_from_name(df, 'name')
assert 'package_quantity' not in result.columns
def test_with_ignore_words(self):
df = pd.DataFrame({'name': ['新品 农夫山泉550ml*24']})
dictionary = {'ignore_words': ['新品'], 'name_patterns': []}
result = _extract_spec_from_name(df, 'name', dictionary)
assert result.loc[0, 'package_quantity'] == 24
class TestNormalizeUnit:
def test_map_units(self):
df = pd.DataFrame({'unit': ['', '', '', ''], 'quantity': [1, 2, 3, 4]})
unit_map = {'': '', '': '', '': ''}
result = _normalize_unit(df, 'unit', unit_map)
# _normalize_unit maps via unit_map, then converts 件→瓶 as packed unit
assert list(result['unit']) == ['', '', '', '']
def test_convert_quantity_for_packed_units(self):
df = pd.DataFrame({
'unit': ['', ''],
'quantity': [2, 5],
'package_quantity': [12, None],
})
unit_map = {'': ''}
result = _normalize_unit(df, 'unit', unit_map)
assert result.loc[0, 'quantity'] == 24 # 2 * 12
assert result.loc[1, 'quantity'] == 5 # unchanged
def test_missing_column(self):
df = pd.DataFrame({'other': [1]})
result = _normalize_unit(df, 'unit', {})
assert 'unit' not in result.columns
class TestComputeQuantityFromTotal:
def test_compute_when_qty_zero(self):
df = pd.DataFrame({
'quantity': [0, 5, 0],
'unit_price': [10.0, 20.0, 0.0],
'total_price': [50.0, 100.0, 30.0],
})
result = _compute_quantity_from_total(df)
assert result.loc[0, 'quantity'] == 5.0 # 50 / 10
assert result.loc[1, 'quantity'] == 5 # unchanged
def test_no_compute_when_qty_positive(self):
df = pd.DataFrame({
'quantity': [3, 5],
'unit_price': [10.0, 20.0],
'total_price': [50.0, 100.0],
})
result = _compute_quantity_from_total(df)
assert list(result['quantity']) == [3, 5]
class TestFillMissing:
def test_fill_existing_column(self):
df = pd.DataFrame({'a': [1, None, 3], 'b': [None, 2, None]})
result = _fill_missing(df, {'a': 0, 'b': 99})
assert result.loc[1, 'a'] == 0
assert result.loc[0, 'b'] == 99
def test_fill_new_column(self):
df = pd.DataFrame({'a': [1, 2]})
result = _fill_missing(df, {'new_col': 'default'})
assert list(result['new_col']) == ['default', 'default']
class TestMarkGift:
def test_gift_by_zero_price(self):
df = pd.DataFrame({
'name': ['商品A', '商品B'],
'unit_price': [10.0, 0.0],
'total_price': [20.0, 0.0],
})
result = _mark_gift(df)
assert result.loc[0, 'is_gift'] == False
assert result.loc[1, 'is_gift'] == True
def test_gift_by_name(self):
df = pd.DataFrame({
'name': ['赠品-杯子', '商品A'],
'unit_price': [0.0, 10.0],
'total_price': [0.0, 20.0],
})
result = _mark_gift(df)
assert result.loc[0, 'is_gift'] == True
assert result.loc[1, 'is_gift'] == False
def test_gift_no_price_columns(self):
df = pd.DataFrame({'name': ['赠品', '正常']})
result = _mark_gift(df)
assert result.loc[0, 'is_gift'] == True
assert result.loc[1, 'is_gift'] == False
class TestApplyRules:
def test_multiple_rules(self, sample_df):
rules = [
{'type': 'split_quantity_unit', 'source': 'quantity_raw'},
{'type': 'extract_spec_from_name', 'source': 'name'},
{'type': 'mark_gift'},
{'type': 'fill_missing', 'fills': {'unit': ''}},
]
result = apply_rules(sample_df, rules)
assert 'quantity' in result.columns
assert 'unit' in result.columns
assert 'package_quantity' in result.columns
assert 'is_gift' in result.columns
def test_empty_rules(self, sample_df):
result = apply_rules(sample_df, [])
assert len(result) == len(sample_df)
def test_none_rules(self, sample_df):
result = apply_rules(sample_df, None)
assert len(result) == len(sample_df)
def test_unknown_rule_type(self, sample_df):
rules = [{'type': 'unknown_operation'}]
result = apply_rules(sample_df, rules)
assert len(result) == len(sample_df)
def test_with_dictionary(self):
df = pd.DataFrame({
'name': ['农夫山泉550ml*24'],
'quantity_raw': ['2箱'],
})
dictionary = {
'unit_synonyms': {'': ''},
'default_unit': '',
'ignore_words': [],
'name_patterns': [],
'pack_multipliers': {'': 12},
'default_package_quantity': 1,
}
rules = [
{'type': 'split_quantity_unit', 'source': 'quantity_raw'},
{'type': 'extract_spec_from_name', 'source': 'name'},
{'type': 'normalize_unit', 'target': 'unit', 'map': {'': ''}},
]
result = apply_rules(df, rules, dictionary)
assert 'quantity' in result.columns
assert 'unit' in result.columns
-124
View File
@@ -1,124 +0,0 @@
"""app.core.utils.string_utils 单元测试"""
import pytest
from app.core.utils.string_utils import parse_monetary_string, format_barcode
class TestParseMonetaryString:
"""parse_monetary_string 金额/数量字符串解析测试"""
# --- 基本类型 ---
def test_none_returns_none(self):
assert parse_monetary_string(None) is None
def test_int_passthrough(self):
assert parse_monetary_string(42) == 42.0
def test_float_passthrough(self):
assert parse_monetary_string(3.14) == 3.14
def test_zero_int(self):
assert parse_monetary_string(0) == 0.0
# --- 正常字符串 ---
def test_plain_number(self):
assert parse_monetary_string("123.45") == 123.45
def test_integer_string(self):
assert parse_monetary_string("100") == 100.0
# --- 货币符号 ---
def test_yen_prefix(self):
assert parse_monetary_string("¥1234.56") == 1234.56
def test_dollar_prefix(self):
assert parse_monetary_string("$99.9") == 99.9
def test_yuan_suffix(self):
assert parse_monetary_string("100元") == 100.0
# --- 逗号处理 ---
def test_comma_as_decimal_point(self):
"""逗号当小数点: "1,5" = 1.5"""
assert parse_monetary_string("1,5") == 1.5
def test_comma_as_thousands_sep(self):
"""逗号当千位分隔符: "1,234.56" = 1234.56"""
assert parse_monetary_string("1,234.56") == 1234.56
def test_multiple_commas_thousands(self):
"""多个逗号: "1,234,567" = 1234567"""
assert parse_monetary_string("1,234,567") == 1234567.0
# --- 空值/无效值 ---
def test_empty_string(self):
assert parse_monetary_string("") is None
def test_whitespace_only(self):
assert parse_monetary_string(" ") is None
def test_o_string(self):
"""OCR 常见误识别: 字母 o 当数字 0"""
assert parse_monetary_string("o") is None
def test_none_string(self):
assert parse_monetary_string("none") is None
def test_null_string(self):
assert parse_monetary_string("null") is None
def test_dash(self):
assert parse_monetary_string("-") is None
def test_double_dash(self):
assert parse_monetary_string("--") is None
def test_no_digits(self):
assert parse_monetary_string("赠品") is None
# --- 负数 ---
def test_negative_number(self):
assert parse_monetary_string("-5.5") == -5.5
# --- 非字符串非数字类型 ---
def test_list_returns_none(self):
assert parse_monetary_string([1, 2]) is None
def test_dict_returns_none(self):
assert parse_monetary_string({"a": 1}) is None
class TestFormatBarcode:
"""format_barcode 条码格式化测试"""
def test_none_returns_empty(self):
assert format_barcode(None) == ""
def test_normal_digit_string(self):
assert format_barcode("6920584471055") == "6920584471055"
def test_integer_input(self):
assert format_barcode(6920584471055) == "6920584471055"
def test_float_with_zero_decimal(self):
assert format_barcode(6920584471055.0) == "6920584471055"
def test_scientific_notation(self):
assert format_barcode("6.920584e+12") == "6920584000000"
def test_trailing_zeros_stripped(self):
assert format_barcode("123456.0") == "123456"
def test_long_barcode_with_trailing_zeros(self):
"""14位条码末尾是0时应截断到13位"""
assert format_barcode("69205844710550") == "6920584471055"
def test_long_barcode_without_trailing_zeros(self):
"""14位条码末尾不是0时不截断"""
assert format_barcode("69205844710551") == "69205844710551"
def test_non_digit_chars_removed(self):
assert format_barcode("692-058-4471055") == "6920584471055"
def test_empty_string(self):
assert format_barcode("") == ""
-251
View File
@@ -1,251 +0,0 @@
"""app.core.excel.validators 单元测试"""
import pytest
from app.core.excel.validators import ProductValidator
@pytest.fixture
def validator():
return ProductValidator()
class TestValidateBarcode:
"""条码验证测试"""
def test_valid_barcode_13_digits(self, validator):
ok, val, err = validator.validate_barcode("6920584471055")
assert ok is True
assert val == "6920584471055"
assert err is None
def test_valid_barcode_8_digits(self, validator):
ok, val, err = validator.validate_barcode("12345678")
assert ok is True
assert val == "12345678"
def test_valid_barcode_12_digits(self, validator):
ok, val, err = validator.validate_barcode("692058447105")
assert ok is True
def test_none_returns_invalid(self, validator):
ok, val, err = validator.validate_barcode(None)
assert ok is False
assert err == "条码为空"
def test_warehouse_identifier(self, validator):
ok, val, err = validator.validate_barcode("仓库")
assert ok is False
assert val == "仓库"
assert err == "条码为仓库标识"
def test_warehouse_full_name(self, validator):
ok, val, err = validator.validate_barcode("仓库全名")
assert ok is False
def test_prefix_5_to_6_correction(self, validator):
"""5开头(非53)的长条码应修正为6开头"""
ok, val, err = validator.validate_barcode("5920584471055")
assert ok is True
assert val.startswith("6")
assert val == "6920584471055"
def test_prefix_53_not_corrected(self, validator):
"""53开头的条码不修正"""
ok, val, err = validator.validate_barcode("5321545613000")
assert ok is True
assert val.startswith("53")
def test_14_digit_trailing_zero_truncated(self, validator):
"""14位条码末尾是0时截断到13位"""
ok, val, err = validator.validate_barcode("69205844710550")
assert ok is True
assert len(val) == 13
def test_14_digit_no_trailing_zero_invalid(self, validator):
"""14位条码末尾不是0时报错"""
ok, val, err = validator.validate_barcode("69205844710551")
assert ok is False
assert "长度异常" in err
def test_too_short_invalid(self, validator):
ok, val, err = validator.validate_barcode("1234567")
assert ok is False
assert "长度异常" in err
def test_too_long_invalid(self, validator):
ok, val, err = validator.validate_barcode("1" * 14)
# 14 digits with trailing 0s gets truncated, but "111...1" has no trailing 0
ok2, val2, err2 = validator.validate_barcode("1" * 15)
assert ok2 is False
def test_no_digits_invalid(self, validator):
ok, val, err = validator.validate_barcode("abc")
assert ok is False
assert err == "条码不包含数字"
def test_float_input_cleaned(self, validator):
"""浮点数输入应清理为整数字符串"""
ok, val, err = validator.validate_barcode(6920584471055.0)
assert ok is True
assert val == "6920584471055"
def test_special_barcode_5321545613(self, validator):
"""特殊条码 5321545613 应通过验证"""
ok, val, err = validator.validate_barcode("5321545613")
assert ok is True
assert val == "5321545613"
class TestValidatePrice:
"""单价验证测试"""
def test_valid_price(self, validator):
ok, val, is_gift, err = validator.validate_price(10.5)
assert ok is True
assert val == 10.5
assert is_gift is False
def test_zero_price_is_gift(self, validator):
ok, val, is_gift, err = validator.validate_price(0)
assert ok is True
assert val == 0.0
assert is_gift is True
def test_none_is_gift(self, validator):
ok, val, is_gift, err = validator.validate_price(None)
assert ok is False
assert is_gift is True
def test_gift_string(self, validator):
ok, val, is_gift, err = validator.validate_price("赠品")
assert ok is True
assert is_gift is True
def test_gift_english(self, validator):
ok, val, is_gift, err = validator.validate_price("gift")
assert ok is True
assert is_gift is True
def test_price_string_with_yen(self, validator):
ok, val, is_gift, err = validator.validate_price("¥123.45")
assert ok is True
assert val == 123.45
assert is_gift is False
def test_price_string_with_comma(self, validator):
ok, val, is_gift, err = validator.validate_price("1,234.56")
assert ok is True
assert val == 1234.56
def test_negative_price_invalid(self, validator):
ok, val, is_gift, err = validator.validate_price(-5)
assert ok is False
assert is_gift is True
def test_empty_string_is_gift(self, validator):
ok, val, is_gift, err = validator.validate_price("")
assert ok is True
assert is_gift is True
class TestValidateQuantity:
"""数量验证测试"""
def test_valid_quantity(self, validator):
ok, val, err = validator.validate_quantity(10)
assert ok is True
assert val == 10.0
def test_float_quantity(self, validator):
ok, val, err = validator.validate_quantity(2.5)
assert ok is True
assert val == 2.5
def test_string_quantity(self, validator):
ok, val, err = validator.validate_quantity("15")
assert ok is True
assert val == 15.0
def test_string_with_unit(self, validator):
ok, val, err = validator.validate_quantity("10瓶")
assert ok is True
assert val == 10.0
def test_none_invalid(self, validator):
ok, val, err = validator.validate_quantity(None)
assert ok is False
assert err == "数量为空"
def test_zero_invalid(self, validator):
ok, val, err = validator.validate_quantity(0)
assert ok is False
assert "必须大于0" in err
def test_negative_invalid(self, validator):
ok, val, err = validator.validate_quantity(-3)
assert ok is False
assert "必须大于0" in err
def test_non_numeric_string_invalid(self, validator):
ok, val, err = validator.validate_quantity("abc")
assert ok is False
assert err == "数量不包含数字"
class TestValidateProduct:
"""商品数据整体验证测试"""
def test_valid_product(self, validator):
product = {
'barcode': '6920584471055',
'price': 10.5,
'quantity': 5,
'amount': 52.5,
}
result = validator.validate_product(product)
assert result['barcode'] == '6920584471055'
assert result['price'] == 10.5
assert result['quantity'] == 5.0
assert result.get('is_gift') is None or result.get('is_gift') is False
def test_gift_product(self, validator):
product = {
'barcode': '6920584471055',
'price': '赠品',
'quantity': 5,
}
result = validator.validate_product(product)
assert result['is_gift'] is True
assert result['price'] == 0.0
def test_quantity_from_amount_and_price(self, validator):
"""数量为空时,通过金额/单价计算"""
product = {
'barcode': '6920584471055',
'price': 10.0,
'amount': 50.0,
'quantity': None,
}
result = validator.validate_product(product)
assert result['quantity'] == 5.0 # 50 / 10
def test_invalid_barcode_still_uses_fixed(self, validator):
"""条码验证失败但有修复值时仍使用修复值"""
product = {
'barcode': '5920584471055', # 5开头, 会被修正为6开头
'price': 10.0,
'quantity': 5,
}
result = validator.validate_product(product)
assert result['barcode'] == '6920584471055'
def test_amount_zero_marks_gift(self, validator):
"""金额为0时标记为赠品"""
product = {
'barcode': '6920584471055',
'price': 10.0,
'quantity': 5,
'amount': 0,
}
result = validator.validate_product(product)
assert result.get('is_gift') is True

Some files were not shown because too many files have changed in this diff Show More