Compare commits
44 Commits
main
..
10ebe9240b
| Author | SHA1 | Date | |
|---|---|---|---|
| 10ebe9240b | |||
| 96cdb0f62e | |||
| 76859fd774 | |||
| c06e3e55f9 | |||
| 32d41244e5 | |||
| ba8520a351 | |||
| 26835e265a | |||
| 708402c7fb | |||
| b7bce93995 | |||
| bfccdd3a37 | |||
| 3e2f46d26d | |||
| 83405a9b8e | |||
| 76f7adddd5 | |||
| cd1adc5647 | |||
| fb12e63c4c | |||
| 73d17836d7 | |||
| 9f97ac3f21 | |||
| 3414df5317 | |||
| 556f8d8020 | |||
| 53e907411d | |||
| c9afe413f5 | |||
| 5cf3eeed0f | |||
| ae8d479acd | |||
| b3c175836a | |||
| c0fceea9dc | |||
| 772902c919 | |||
| c3a0e29b19 | |||
| 9b2007a995 | |||
| 4a8169ff63 | |||
| 201aac35e6 | |||
| f5eda6cbd8 | |||
| 5c0b709528 | |||
| 7b7d491663 | |||
| 390eeb67af | |||
| 4c8def4b04 | |||
| 2f088c87ca | |||
| b9739b5267 | |||
| 0b40caaf91 | |||
| 693c17283b | |||
| 71ca90ba6e | |||
| 14eeb7b39a | |||
| b3cecda175 | |||
| 131fff6a7d | |||
| 0035cd1893 |
@@ -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
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# 百度 OCR API 配置
|
|
||||||
BAIDU_API_KEY=your_api_key_here
|
|
||||||
BAIDU_SECRET_KEY=your_secret_key_here
|
|
||||||
+3
-22
@@ -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=unit,map={"箱":"件","提":"件","盒":"件"}
|
||||||
|
- fill_missing,参数:fills={"unit":"瓶"}
|
||||||
|
- mark_gift(可选)
|
||||||
|
- compute_quantity_from_total(可选,当只有金额/单价时)
|
||||||
|
2) 词典设置:
|
||||||
|
- unit_synonyms:{"箱":"件","提":"件","盒":"件","瓶":"瓶"}
|
||||||
|
- pack_multipliers:{"件":24,"箱":24,"提":12,"盒":10}(根据你的供应商习惯调整)
|
||||||
|
- default_unit:瓶
|
||||||
|
- default_package_quantity:1
|
||||||
|
- 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:打包与交付验证
|
||||||
|
|
||||||
|
请确认,我将立即开始实现并逐步提交验证结果。
|
||||||
@@ -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()`
|
|
||||||
@@ -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"]
|
|
||||||
@@ -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;"]
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# OCR 订单处理系统 - v2.2 更新报告
|
||||||
|
|
||||||
|
## 1. 业务逻辑与 UI 变更 (v2.2 Updates)
|
||||||
|
|
||||||
|
### 1.1 UI 极简优化
|
||||||
|
- **移除专用按钮**:从主界面彻底移除了“蓉城易购”和“烟草处理”两个特定按钮。
|
||||||
|
- **统一入口**:所有供应商 Excel 订单现在均通过“处理 Excel 文件”或直接拖拽至主界面进行处理。系统会自动识别并路由。
|
||||||
|
- **快捷键更新**:移除了 `Ctrl+T` (烟草处理) 快捷键,简化了键盘操作逻辑。
|
||||||
|
|
||||||
|
### 1.2 杨碧月预处理修复
|
||||||
|
- **列名校准**:修正了预处理输出列名,确保与银豹处理器期望的中文列名(商品条码、数量、单价等)完全一致。
|
||||||
|
- **干扰过滤**:在提取列时,自动排除了 `结算单位`、`基本单位数量` 等名称相似的非业务列,提高了匹配精度。
|
||||||
|
|
||||||
|
### 1.3 Headless API 智能增强
|
||||||
|
- **后缀感知**:`headless_api.py` 能够根据文件后缀自动区分图片与 Excel,不再需要显式指定 `--excel` 或 `--tobacco`。
|
||||||
|
- **零配置接入**:OpenClaw 仅需运行 `python headless_api.py [文件路径]` 即可完成全流程。
|
||||||
|
|
||||||
|
## 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) |
|
||||||
|
|
||||||
|
## 3. 代码与环境清理
|
||||||
|
- **移除无用文件**:清理了 `run.py` (冗余)、`clean.py` (旧脚本) 以及 `doc/` (旧文档)。
|
||||||
|
- **模块重构**:删除了 `app/cli/` 模块,所有命令行逻辑已合并至根目录的 `headless_api.py`。
|
||||||
|
|
||||||
|
---
|
||||||
|
*报告生成日期:2026-03-31*
|
||||||
|
*负责人:Trae Code Assistant*
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# OCR 订单处理系统 - OpenClaw 对接指南 (v2.2)
|
||||||
|
|
||||||
|
## 1. 核心接口说明 (headless_api.py)
|
||||||
|
|
||||||
|
`headless_api.py` 是系统的统一命令行入口。它支持**智能文件类型与供应商识别**,OpenClaw 通常**无需携带任何功能参数**。
|
||||||
|
|
||||||
|
### 1.1 全自动智能模式 (推荐方式)
|
||||||
|
无论是收到**图片**还是 **Excel**,都可以直接调用。系统会自动判断文件类型:如果是 Excel 则自动识别供应商指纹(蓉城、烟草、杨碧月等)并处理;如果是图片则先 OCR 后再智能处理。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 自动处理 data/input 中最新的文件 (图片或 Excel)
|
||||||
|
python headless_api.py
|
||||||
|
|
||||||
|
# 处理指定的任意文件 (图片或 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. 字段与逻辑变更
|
||||||
|
|
||||||
|
### 2.1 蓉城易购 (Rongcheng)
|
||||||
|
- **条码映射**:E列 (Index 4)。
|
||||||
|
- **数量逻辑**:N列 (Index 13)。直接提取,不进行单位换算。
|
||||||
|
- **条码分裂**:支持 `/` `,` `,` `、` 分隔符自动均分。
|
||||||
|
|
||||||
|
### 2.2 烟草公司 (Tobacco)
|
||||||
|
- **条码映射**:B列 (Index 1)。
|
||||||
|
- **数量逻辑**:G列 (订单量) **x 10**。
|
||||||
|
- **单价逻辑**:E列 (批发价) **/ 10**。
|
||||||
|
|
||||||
|
### 2.3 杨碧月 (Yang Biyue)
|
||||||
|
- **自动对齐**:自动识别经手人并对齐“商品条码”、“数量”、“单价”等标准列。
|
||||||
|
|
||||||
|
---
|
||||||
|
*版本:2.2 | 更新日期:2026-03-31*
|
||||||
@@ -1,243 +1,66 @@
|
|||||||
# 益选 OCR 订单处理系统
|
# 益选 OCR 订单处理系统
|
||||||
|
|
||||||
面向零售与分销场景的采购单处理工具,支持图片 OCR → Excel 规范化 → 模板填充 → 合并导出全流程,输出适配银豹 (PosPal) POS 系统。
|
## 概览
|
||||||
|
- 面向零售与分销场景的采购单处理工具,支持图片 OCR → Excel 规范化 → 模板填充 → 合并导出全流程
|
||||||
|
- 通过供应商管理与规则引擎,适配不同供应商的格式差异(数量含单位、名称包含规格、缺失列补齐)
|
||||||
|
- 提供验证匹配面板与单价校验机制,确保输出与既定模板一致且价格合理
|
||||||
|
|
||||||
## 核心功能
|
## 核心功能
|
||||||
|
- **全自动智能识别**:系统现在能自动识别 Excel 内容特征(如:蓉城易购 RCDH、烟草公司专卖证号、杨碧月经手人),并自动路由至专用预处理流程,无需手动干预。
|
||||||
|
- **图片/Excel处理**:支持拖拽或选择文件,一键生成标准银豹采购单。
|
||||||
|
- **极简 UI 体验**:移除了冗余的供应商特定按钮,所有 Excel 统一走智能路由处理。
|
||||||
|
- **无界面自动化接口 (headless_api.py)**:专为 OpenClaw 等平台设计,支持全自动文件类型与供应商识别。
|
||||||
|
- **单价预警机制**:自动比对 `templates/商品资料.xlsx`,若价差超过 1.0 元则触发警告。
|
||||||
|
|
||||||
- **智能供应商识别**:自动扫描 Excel 前 50 行内容特征,路由到对应的预处理逻辑(蓉城易购、烟草公司、杨碧月等)
|
## 供应商专用逻辑 (v2.2)
|
||||||
- **图片 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
|
|
||||||
```
|
|
||||||
|
|||||||
+3
-16
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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*12(I 和 1 混淆)
|
|
||||||
if re.search(r'^[Ii][Ll*]', spec):
|
|
||||||
return True
|
|
||||||
# 4.51*4(L 被识别为 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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|
||||||
# 获取未处理的图片
|
# 获取未处理的图片
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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()
|
|
||||||
@@ -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"
|
|
||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
格式化条码,处理科学计数法
|
格式化条码,处理科学计数法
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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初始化完成")
|
||||||
@@ -202,41 +194,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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
@@ -86,13 +85,8 @@ class SpecialSuppliersService:
|
|||||||
# 过滤掉空的条码行
|
# 过滤掉空的条码行
|
||||||
df_clean = df_clean.dropna(subset=['商品条码'])
|
df_clean = df_clean.dropna(subset=['商品条码'])
|
||||||
|
|
||||||
# 保存预处理文件到输出目录(而非源文件目录)
|
# 保存预处理文件
|
||||||
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 +194,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 +219,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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""益选-OCR订单处理系统 UI 模块"""
|
|
||||||
@@ -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")
|
|
||||||
@@ -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)}")
|
|
||||||
@@ -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
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -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")
|
|
||||||
@@ -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))
|
|
||||||
@@ -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
|
|
||||||
@@ -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()
|
|
||||||
@@ -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请检查日志了解详细信息。"
|
|
||||||
)
|
|
||||||
@@ -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
@@ -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)
|
|
||||||
@@ -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))
|
|
||||||
@@ -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}")
|
|
||||||
@@ -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(
|
||||||
|
|||||||
+5
-22
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"window_size": "900x600",
|
||||||
|
"theme_mode": "light",
|
||||||
|
"recent_files": [
|
||||||
|
"data/result\\采购单_预处理之后_订单1774849009841.xls",
|
||||||
|
"data/output\\预处理之后_订单1774849009841.xlsx",
|
||||||
|
"E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx",
|
||||||
|
"data/output\\订单1774849009841.xlsx"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
# OCR 订单处理系统 - 系统架构文档 (v2.2)
|
||||||
|
|
||||||
|
本文件详述了“OCR 订单处理系统”的技术架构、业务流向、数据模型及部署方案。
|
||||||
|
|
||||||
|
## 1. 系统整体架构图 (System Overall Architecture)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TB
|
||||||
|
subgraph 用户交互层
|
||||||
|
UI[启动器.py / Tkinter GUI]
|
||||||
|
CLI[headless_api.py / CLI]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 核心业务逻辑层
|
||||||
|
OS[OrderService / 订单调度]
|
||||||
|
OCR[OCRService / 图片识别]
|
||||||
|
SSS[SpecialSuppliersService / 特殊供应商处理]
|
||||||
|
TS[TobaccoService / 烟草处理]
|
||||||
|
EP[ExcelProcessor / 标准化转换]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 基础设施与存储
|
||||||
|
CONFIG[ConfigManager / JSON 配置]
|
||||||
|
FS[FileSystem / Excel 数据存储]
|
||||||
|
LOG[QueueLogger / 异步日志队列]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 第三方集成
|
||||||
|
OPENCLAW[OpenClaw 自动化平台]
|
||||||
|
POSPAL[银豹 POS 系统 (导出模板)]
|
||||||
|
end
|
||||||
|
|
||||||
|
UI --> OS
|
||||||
|
CLI --> OS
|
||||||
|
OPENCLAW -- 调用 --> CLI
|
||||||
|
OS --> OCR
|
||||||
|
OS --> SSS
|
||||||
|
OS --> TS
|
||||||
|
OS --> EP
|
||||||
|
EP --> FS
|
||||||
|
EP --> CONFIG
|
||||||
|
SSS --> EP
|
||||||
|
TS --> EP
|
||||||
|
OS -- 验证 --> FS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 图例说明
|
||||||
|
- **用户交互层**:支持桌面 GUI 操作及专为 OpenClaw 设计的无界面 API 接入。
|
||||||
|
- **核心业务层**:各服务高度解耦,通过 `OrderService` 进行智能路由分发。
|
||||||
|
- **存储层**:系统采用“文件即数据库”的设计,利用 Excel 存储模板和商品资料,JSON 存储映射关系。
|
||||||
|
- **第三方集成**:与 OpenClaw 平台通过 CLI 接口对接,最终生成符合银豹 POS 要求的采购单。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 核心业务逻辑流程图 (Core Business Logic)
|
||||||
|
|
||||||
|
以“智能订单识别与预处理”为例:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant User as 用户/OpenClaw
|
||||||
|
participant OS as OrderService
|
||||||
|
participant SSS as SpecialSuppliersService
|
||||||
|
participant TS as TobaccoService
|
||||||
|
participant EP as ExcelProcessor
|
||||||
|
|
||||||
|
User->>OS: 提交 Excel 文件
|
||||||
|
OS->>OS: 扫描前50行内容特征
|
||||||
|
|
||||||
|
alt 包含 "RCDH"
|
||||||
|
OS->>SSS: 路由至蓉城易购预处理
|
||||||
|
SSS->>SSS: 按 E, N, Q, S 列强制清洗
|
||||||
|
SSS-->>OS: 返回清洗后的临时文件
|
||||||
|
else 包含 "专卖证号"
|
||||||
|
OS->>TS: 路由至烟草专用预处理
|
||||||
|
TS->>TS: 数量*10 / 单价/10 / B,E,G,H列映射
|
||||||
|
TS-->>OS: 返回清洗后的临时文件
|
||||||
|
else 包含 "杨碧月"
|
||||||
|
OS->>SSS: 路由至杨碧月列对齐流程
|
||||||
|
SSS-->>OS: 返回标准列临时文件
|
||||||
|
else 通用格式
|
||||||
|
OS->>OS: 直接跳过预处理
|
||||||
|
end
|
||||||
|
|
||||||
|
OS->>EP: 执行标准条码映射与模板填充
|
||||||
|
EP->>EP: 校验单价 (与商品资料比对)
|
||||||
|
EP-->>User: 输出最终银豹采购单 (data/result)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 技术注解
|
||||||
|
- **智能指纹识别**:通过 `header=None` 读取前 50 行,避免了因标题行位置不固定导致的识别失败。
|
||||||
|
- **原子化预处理**:每个供应商逻辑独立,预处理结果均为统一格式的中间文件,确立了系统的可扩展性。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 技术架构分层图 (Layered Architecture)
|
||||||
|
|
||||||
|
| 分层 | 技术栈 / 组件 | 功能描述 |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **表现层 (Presentation)** | Tkinter, headless_api.py | 桌面 GUI 交互与 OpenClaw 命令行接口 |
|
||||||
|
| **业务逻辑层 (Business)** | Python 3.x, Pandas, OCRService | 核心数据清洗、条码分裂、供应商特征识别 |
|
||||||
|
| **数据访问层 (Data)** | Pandas (Excel Engine), Json | 对 Excel 模板、映射表、用户设置的读写 |
|
||||||
|
| **基础设施层 (Infrastructure)** | Queue, Logging, PyInstaller | 异步日志分发、全局错误处理、EXE 打包工具 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据架构设计 (Data Architecture)
|
||||||
|
|
||||||
|
系统未采用传统关系型数据库,而是基于 **JSON + Excel** 的混合存储架构。
|
||||||
|
|
||||||
|
### 4.1 表间关系示意 (JSON Mapping)
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
CONFIG_JSON ||--o{ BARCODE_MAPPING_JSON : "存储映射"
|
||||||
|
BARCODE_MAPPING_JSON {
|
||||||
|
string original_barcode "OCR识别出的原始条码"
|
||||||
|
string target_barcode "系统目标条码"
|
||||||
|
}
|
||||||
|
ITEM_DATA_EXCEL ||--o{ PURCHASE_ORDER_EXCEL : "验证单价"
|
||||||
|
ITEM_DATA_EXCEL {
|
||||||
|
string barcode "条码 (主键)"
|
||||||
|
float cost_price "进货价"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 存储方案
|
||||||
|
- **映射关系**:`barcode_mappings.json`。支持运行时动态更新,通过 `headless_api.py --update-mapping` 修改。
|
||||||
|
- **业务数据**:`templates/商品资料.xlsx`。作为单价校验的权威数据源。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 微服务与模块化设计 (Microservices & Modularity)
|
||||||
|
|
||||||
|
虽然系统目前采用单体架构(Monolithic Architecture)以适配桌面部署环境,但在逻辑上采用了**微服务式的模块化设计**:
|
||||||
|
|
||||||
|
- **服务拆分**:每个供应商逻辑(Rongcheng, Tobacco, YangBiyue)都是独立的类,具备高度自治性。
|
||||||
|
- **解耦机制**:通过统一的 `preprocess` 契约(输入:原始文件,输出:清洗后文件)进行交互,未来可轻松迁移至独立服务。
|
||||||
|
- **进程隔离**:GUI 主进程与业务处理线程通过 `queue.Queue` 进行解耦,确保处理逻辑不阻塞用户界面。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 部署架构图 (Deployment)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
subgraph 生产服务器 (Windows)
|
||||||
|
APP[orc-order-v2.exe]
|
||||||
|
DATA[data/ 目录]
|
||||||
|
LOGS[logs/ 目录]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph 自动化平台
|
||||||
|
OC[OpenClaw]
|
||||||
|
end
|
||||||
|
|
||||||
|
OC -- 命令行调用 --> APP
|
||||||
|
APP -- 读写 --> DATA
|
||||||
|
APP -- 记录 --> LOGS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 部署要点
|
||||||
|
- **便携化**:通过 PyInstaller 将 Python 运行环境与依赖打包,实现单文件/单目录部署。
|
||||||
|
- **路径无关性**:系统内部通过 `os.path.abspath` 动态计算路径,支持安装在任意盘符。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 安全架构图 (Security)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[外部输入] --> B{文件类型校验}
|
||||||
|
B -- 非图片/Excel --> C[拒绝处理]
|
||||||
|
B -- 图片/Excel --> D[清洗逻辑]
|
||||||
|
D --> E{单价偏差校验}
|
||||||
|
E -- 差值 > 1.0 --> F[生成警告日志/弹窗]
|
||||||
|
E -- 正常 --> G[生成采购单]
|
||||||
|
G --> H[日志埋点与审计]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安全策略
|
||||||
|
- **数据隔离**:所有处理后的文件存放在 `data/output` 和 `data/result`,不修改原始输入文件。
|
||||||
|
- **权限控制**:系统运行于用户权限下,利用 Windows 文件系统权限保护配置文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 技术债务与优化建议 (Tech Debt & Optimization)
|
||||||
|
|
||||||
|
### 7.1 当前技术债务
|
||||||
|
1. **并发限制**:目前为单进程串行处理,面对超大规模订单(万行级)可能存在阻塞。
|
||||||
|
2. **持久化局限**:使用 JSON 存储映射关系在条码量达到万级时,查询性能会下降。
|
||||||
|
3. **环境依赖**:OCR 引擎高度依赖 Tesseract/PaddleOCR 等本地二进制库,部署复杂。
|
||||||
|
|
||||||
|
### 7.2 单点故障风险 (SPOF Analysis)
|
||||||
|
1. **本地环境强依赖**:所有 OCR 与 Excel 处理均在单一 Windows 节点,若该节点故障,OpenClaw 对接将完全中断。
|
||||||
|
2. **核心模板丢失**:`templates/` 下的商品资料或采购单模板缺失会导致全流程崩溃。
|
||||||
|
3. **OCR 精度波动**:OCR 结果受图片质量影响,若 OCR 识别条码错误且无映射表,则该行数据将丢失。
|
||||||
|
|
||||||
|
### 7.3 架构优化建议方案
|
||||||
|
- **容灾备份**:建议将 `templates/` 和 `barcode_mappings.json` 定期备份至远程 Git 仓库(如 Gitea)。
|
||||||
|
- **分布式识别**:引入 PaddleOCR 服务端,支持多节点并发 OCR 识别,减少本地算力依赖。
|
||||||
|
- **配置热更新**:支持从远程 URL 加载 `barcode_mappings.json`,实现多机条码库同步。
|
||||||
|
- **数据回退机制**:增加中间文件持久化策略,允许在处理失败时手动干预已清洗的 Excel。
|
||||||
|
|
||||||
|
---
|
||||||
|
*附注:本文档图表均采用 Mermaid 标准编写,可直接在 VS Code (需安装 Mermaid 插件) 或 [Mermaid Live Editor](https://mermaid.live/) 中实时渲染并导出为高清 PNG/SVG 格式。*
|
||||||
|
|
||||||
|
---
|
||||||
|
*文档版本:2.2.0 | 生成日期:2026-03-31*
|
||||||
+23
-52
@@ -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
@@ -0,0 +1,369 @@
|
|||||||
|
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
|
||||||
|
2026-03-31 08:50:15,740 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 08:50:15,754 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 08:50:17,775 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 08:50:17,775 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 08:52:46,014 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 08:52:46,014 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 08:52:47,191 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 08:52:47,192 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:00:01,693 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 09:00:01,693 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:03:11,828 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 09:03:11,829 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:03:13,872 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 09:03:13,872 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:05:33,093 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 09:05:33,094 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:05:33,868 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 09:05:33,868 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:05:36,165 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
|
||||||
|
2026-03-31 09:05:36,165 - app.core.excel.merger - INFO - 找到 1 个采购单Excel文件
|
||||||
|
2026-03-31 09:07:58,235 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 09:07:58,235 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:07:58,352 - app.core.excel.merger - INFO - 使用输出目录: E:\2025Code\python\orc-order-v2\data\output
|
||||||
|
2026-03-31 09:07:58,353 - app.core.excel.merger - INFO - 初始化PurchaseOrderMerger完成,模板文件: E:\2025Code\python\orc-order-v2\templates\银豹-采购单模板.xls
|
||||||
|
2026-03-31 09:08:00,645 - app.core.excel.merger - INFO - 搜索目录 data/result 中的采购单Excel文件
|
||||||
|
2026-03-31 09:08:00,646 - app.core.excel.merger - INFO - 找到 2 个采购单Excel文件
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 - 成功获取访问令牌
|
||||||
@@ -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
|
||||||
@@ -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 中找到符合条件的文件
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,856 @@
|
|||||||
|
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初始化完成
|
||||||
|
2026-03-31 08:50:15,691 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 08:50:15,754 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 08:50:15,757 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/订单1774849009841.xlsx
|
||||||
|
2026-03-31 08:50:15,811 - app.services.order_service - INFO - 识别到蓉城易购订单,执行专用预处理...
|
||||||
|
2026-03-31 08:50:15,872 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:/2025Code/python/orc-order-v2/data/output\预处理之后_订单1774849009841.xlsx
|
||||||
|
2026-03-31 08:50:17,773 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 08:50:17,775 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 08:52:46,011 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 08:52:46,014 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 08:52:46,029 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/原始数据.xlsx
|
||||||
|
2026-03-31 08:52:46,087 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:/2025Code/python/orc-order-v2/data/output\预处理之后_原始数据.xlsx
|
||||||
|
2026-03-31 08:52:47,190 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 08:52:47,192 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 09:00:01,690 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 09:00:01,694 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 09:00:01,694 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: data/output/原始数据.xlsx
|
||||||
|
2026-03-31 09:00:01,984 - app.services.order_service - INFO - 识别到杨碧月订单,执行专用预处理...
|
||||||
|
2026-03-31 09:00:02,134 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: data/output\预处理之后_原始数据.xlsx
|
||||||
|
2026-03-31 09:03:11,825 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 09:03:11,829 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 09:03:11,832 - app.services.order_service - INFO - OrderService开始处理指定Excel文件: E:/2025Code/python/orc-order-v2/data/output/原始数据.xlsx
|
||||||
|
2026-03-31 09:03:11,886 - app.services.order_service - INFO - 识别到杨碧月订单,执行专用预处理...
|
||||||
|
2026-03-31 09:03:11,970 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: E:/2025Code/python/orc-order-v2/data/output\预处理之后_原始数据.xlsx
|
||||||
|
2026-03-31 09:03:13,869 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 09:03:13,873 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 09:05:33,091 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 09:05:33,094 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 09:05:33,098 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
|
||||||
|
2026-03-31 09:05:33,126 - app.services.order_service - INFO - 识别到蓉城易购订单,执行专用预处理...
|
||||||
|
2026-03-31 09:05:33,174 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: data/output\预处理之后_订单1774849009841.xlsx
|
||||||
|
2026-03-31 09:05:33,865 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 09:05:33,869 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 09:07:58,232 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 09:07:58,236 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
|
2026-03-31 09:07:58,241 - app.services.order_service - INFO - OrderService开始处理最新Excel文件
|
||||||
|
2026-03-31 09:07:58,250 - app.services.order_service - INFO - 识别到烟草公司订单,执行专用预处理...
|
||||||
|
2026-03-31 09:07:58,289 - app.services.order_service - INFO - 检测到特殊供应商,已生成预处理文件: data/output\预处理之后_订单明细20260331090709.xlsx
|
||||||
|
2026-03-31 09:07:58,350 - app.services.order_service - INFO - 初始化OrderService
|
||||||
|
2026-03-31 09:07:58,354 - app.services.order_service - INFO - OrderService初始化完成
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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
|
||||||
|
2026-03-31 09:07:58,250 - app.services.tobacco_service - INFO - 执行烟草订单专用预处理: data/output\订单明细20260331090709.xlsx
|
||||||
|
2026-03-31 09:07:58,289 - app.services.tobacco_service - INFO - 烟草订单预处理完成: data/output\预处理之后_订单明细20260331090709.xlsx
|
||||||
-43
@@ -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,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
|
||||||
|
|||||||
Binary file not shown.
@@ -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]
|
|
||||||
@@ -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'
|
|
||||||
@@ -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
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -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("") == ""
|
|
||||||
@@ -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
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
"""FastAPI auth dependencies"""
|
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException, status, Query, Request
|
|
||||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
||||||
|
|
||||||
from .jwt_handler import decode_token
|
|
||||||
|
|
||||||
security = HTTPBearer()
|
|
||||||
|
|
||||||
|
|
||||||
async def get_current_user(
|
|
||||||
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
||||||
) -> dict:
|
|
||||||
try:
|
|
||||||
payload = decode_token(credentials.credentials)
|
|
||||||
username = payload.get("sub")
|
|
||||||
if username is None:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
||||||
return {"username": username}
|
|
||||||
except Exception:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭据")
|
|
||||||
|
|
||||||
|
|
||||||
async def get_current_user_ws(token: str = Query(...)) -> dict:
|
|
||||||
"""WebSocket auth via query parameter"""
|
|
||||||
try:
|
|
||||||
payload = decode_token(token)
|
|
||||||
username = payload.get("sub")
|
|
||||||
if username is None:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
||||||
return {"username": username}
|
|
||||||
except Exception:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭据")
|
|
||||||
|
|
||||||
|
|
||||||
async def get_current_user_flexible(
|
|
||||||
request: Request,
|
|
||||||
credentials: HTTPAuthorizationCredentials = Depends(HTTPBearer(auto_error=False)),
|
|
||||||
token: str = Query(None),
|
|
||||||
) -> dict:
|
|
||||||
"""Auth from header OR query param (for file downloads in browser)."""
|
|
||||||
token_str = None
|
|
||||||
if credentials:
|
|
||||||
token_str = credentials.credentials
|
|
||||||
elif token:
|
|
||||||
token_str = token
|
|
||||||
|
|
||||||
if not token_str:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="未提供认证凭据")
|
|
||||||
|
|
||||||
try:
|
|
||||||
payload = decode_token(token_str)
|
|
||||||
username = payload.get("sub")
|
|
||||||
if username is None:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
|
||||||
return {"username": username}
|
|
||||||
except Exception:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭据")
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
"""JWT token creation and validation"""
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from jose import jwt, JWTError
|
|
||||||
|
|
||||||
from ..config import get_or_generate_secret, JWT_ALGORITHM, JWT_EXPIRE_HOURS
|
|
||||||
|
|
||||||
|
|
||||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
|
||||||
to_encode = data.copy()
|
|
||||||
expire = datetime.now(timezone.utc) + (expires_delta or timedelta(hours=JWT_EXPIRE_HOURS))
|
|
||||||
to_encode.update({"exp": expire})
|
|
||||||
return jwt.encode(to_encode, get_or_generate_secret(), algorithm=JWT_ALGORITHM)
|
|
||||||
|
|
||||||
|
|
||||||
def decode_token(token: str) -> dict:
|
|
||||||
return jwt.decode(token, get_or_generate_secret(), algorithms=[JWT_ALGORITHM])
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
"""Auth API endpoints"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import bcrypt
|
|
||||||
from fastapi import APIRouter, HTTPException, Depends, status
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from .jwt_handler import create_access_token
|
|
||||||
from .dependencies import get_current_user
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
||||||
|
|
||||||
# Default credentials (should be changed on first login)
|
|
||||||
DEFAULT_USERNAME = "admin"
|
|
||||||
DEFAULT_PASSWORD = "admin123"
|
|
||||||
|
|
||||||
|
|
||||||
class LoginRequest(BaseModel):
|
|
||||||
username: str
|
|
||||||
password: str
|
|
||||||
|
|
||||||
|
|
||||||
class LoginResponse(BaseModel):
|
|
||||||
access_token: str
|
|
||||||
token_type: str = "bearer"
|
|
||||||
|
|
||||||
|
|
||||||
def _get_credentials() -> tuple[str, bytes]:
|
|
||||||
"""Get username and password hash from config or defaults"""
|
|
||||||
try:
|
|
||||||
from app.config.settings import ConfigManager
|
|
||||||
cfg = ConfigManager()
|
|
||||||
username = cfg.get('WebAuth', 'username', fallback=DEFAULT_USERNAME)
|
|
||||||
pw_hash = cfg.get('WebAuth', 'password_hash', fallback='')
|
|
||||||
if not pw_hash:
|
|
||||||
# First run: store default password hash
|
|
||||||
pw_hash = bcrypt.hashpw(DEFAULT_PASSWORD.encode(), bcrypt.gensalt()).decode()
|
|
||||||
try:
|
|
||||||
cfg.update('WebAuth', 'username', DEFAULT_USERNAME)
|
|
||||||
cfg.update('WebAuth', 'password_hash', pw_hash)
|
|
||||||
cfg.save_config()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return username, pw_hash.encode()
|
|
||||||
except Exception:
|
|
||||||
return DEFAULT_USERNAME, bcrypt.hashpw(DEFAULT_PASSWORD.encode(), bcrypt.gensalt())
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login", response_model=LoginResponse)
|
|
||||||
async def login(req: LoginRequest):
|
|
||||||
stored_username, stored_hash = _get_credentials()
|
|
||||||
|
|
||||||
if req.username != stored_username:
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误")
|
|
||||||
|
|
||||||
if not bcrypt.checkpw(req.password.encode(), stored_hash):
|
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误")
|
|
||||||
|
|
||||||
token = create_access_token({"sub": req.username})
|
|
||||||
return LoginResponse(access_token=token)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/me")
|
|
||||||
async def me(current_user: dict = Depends(get_current_user)):
|
|
||||||
return current_user
|
|
||||||
|
|
||||||
|
|
||||||
class ChangePasswordRequest(BaseModel):
|
|
||||||
old_password: str
|
|
||||||
new_password: str
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/change-password")
|
|
||||||
async def change_password(req: ChangePasswordRequest, current_user: dict = Depends(get_current_user)):
|
|
||||||
_, stored_hash = _get_credentials()
|
|
||||||
|
|
||||||
if not bcrypt.checkpw(req.old_password.encode(), stored_hash):
|
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="旧密码错误")
|
|
||||||
|
|
||||||
new_hash = bcrypt.hashpw(req.new_password.encode(), bcrypt.gensalt()).decode()
|
|
||||||
try:
|
|
||||||
from app.config.settings import ConfigManager
|
|
||||||
cfg = ConfigManager()
|
|
||||||
cfg.update('WebAuth', 'password_hash', new_hash)
|
|
||||||
cfg.save_config()
|
|
||||||
except Exception as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"保存密码失败: {e}")
|
|
||||||
|
|
||||||
return {"message": "密码修改成功"}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
"""Web-specific configuration"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import secrets
|
|
||||||
|
|
||||||
# JWT
|
|
||||||
JWT_SECRET_KEY = os.getenv("JWT_SECRET_KEY", "")
|
|
||||||
JWT_ALGORITHM = "HS256"
|
|
||||||
JWT_EXPIRE_HOURS = 24
|
|
||||||
|
|
||||||
# File upload
|
|
||||||
MAX_UPLOAD_SIZE = 50 * 1024 * 1024 # 50MB
|
|
||||||
ALLOWED_IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.bmp'}
|
|
||||||
ALLOWED_EXCEL_EXTENSIONS = {'.xlsx', '.xls'}
|
|
||||||
ALLOWED_EXTENSIONS = ALLOWED_IMAGE_EXTENSIONS | ALLOWED_EXCEL_EXTENSIONS
|
|
||||||
|
|
||||||
# CORS
|
|
||||||
CORS_ORIGINS = os.getenv("CORS_ORIGINS", "*").split(",")
|
|
||||||
|
|
||||||
# Auth rate limit
|
|
||||||
LOGIN_RATE_LIMIT = 5 # per minute
|
|
||||||
|
|
||||||
|
|
||||||
def get_or_generate_secret() -> str:
|
|
||||||
"""Get JWT secret from env or auto-generate on first run"""
|
|
||||||
global JWT_SECRET_KEY
|
|
||||||
if not JWT_SECRET_KEY:
|
|
||||||
secret_file = os.path.join(
|
|
||||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
|
|
||||||
'data', '.jwt_secret'
|
|
||||||
)
|
|
||||||
if os.path.exists(secret_file):
|
|
||||||
with open(secret_file, 'r') as f:
|
|
||||||
JWT_SECRET_KEY = f.read().strip()
|
|
||||||
if not JWT_SECRET_KEY:
|
|
||||||
JWT_SECRET_KEY = secrets.token_urlsafe(48)
|
|
||||||
os.makedirs(os.path.dirname(secret_file), exist_ok=True)
|
|
||||||
with open(secret_file, 'w') as f:
|
|
||||||
f.write(JWT_SECRET_KEY)
|
|
||||||
return JWT_SECRET_KEY
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
"""FastAPI application entry point for the web-based OCR order processing system."""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from contextlib import asynccontextmanager
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Ensure app/ is importable
|
|
||||||
_web_dir = Path(__file__).resolve().parent.parent # web/
|
|
||||||
_project_root = _web_dir.parent # project root
|
|
||||||
if str(_project_root) not in sys.path:
|
|
||||||
sys.path.insert(0, str(_project_root))
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
from fastapi.responses import FileResponse
|
|
||||||
|
|
||||||
from .config import get_or_generate_secret # noqa: trigger secret generation
|
|
||||||
from .services.task_manager import TaskManager
|
|
||||||
from .services.db_pool import DBPool
|
|
||||||
from .auth.router import router as auth_router
|
|
||||||
from .routers.files import router as files_router
|
|
||||||
from .routers.processing import router as processing_router
|
|
||||||
from .routers.memory import router as memory_router
|
|
||||||
from .routers.config_api import router as config_router
|
|
||||||
from .routers.barcodes import router as barcodes_router
|
|
||||||
from .routers.sync import router as sync_router
|
|
||||||
from .routers.websocket import router as ws_router
|
|
||||||
from .routers.logs import router as logs_router
|
|
||||||
from .routers.tasks import router as tasks_router
|
|
||||||
from .middleware.logging import LoggingMiddleware
|
|
||||||
|
|
||||||
# Shared singletons
|
|
||||||
task_manager = TaskManager()
|
|
||||||
db_pool = DBPool()
|
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
|
||||||
async def lifespan(app: FastAPI):
|
|
||||||
"""Initialize shared resources on startup."""
|
|
||||||
from app.config.settings import ConfigManager
|
|
||||||
ConfigManager()
|
|
||||||
|
|
||||||
# Initialize DB and cleanup old records
|
|
||||||
from .services.db_schema import init_db, cleanup_old_records, sync_file_relations
|
|
||||||
init_db()
|
|
||||||
cleanup_old_records()
|
|
||||||
|
|
||||||
# Sync file relations from existing files
|
|
||||||
sync_file_relations()
|
|
||||||
|
|
||||||
# Wire up DB pool to task manager
|
|
||||||
task_manager.set_db_pool(db_pool)
|
|
||||||
|
|
||||||
app.state.task_manager = task_manager
|
|
||||||
app.state.db_pool = db_pool
|
|
||||||
yield
|
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI(
|
|
||||||
title="益选 OCR 订单处理系统",
|
|
||||||
version="1.0.0",
|
|
||||||
lifespan=lifespan,
|
|
||||||
)
|
|
||||||
|
|
||||||
# CORS
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:8000"],
|
|
||||||
allow_credentials=True,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# HTTP logging middleware (after CORS, before routes)
|
|
||||||
app.add_middleware(LoggingMiddleware)
|
|
||||||
|
|
||||||
# Make task_manager and db_pool accessible via request.state
|
|
||||||
@app.middleware("http")
|
|
||||||
async def inject_services(request, call_next):
|
|
||||||
request.state.task_manager = task_manager
|
|
||||||
request.state.db_pool = db_pool
|
|
||||||
return await call_next(request)
|
|
||||||
|
|
||||||
|
|
||||||
# Mount routers
|
|
||||||
app.include_router(auth_router)
|
|
||||||
app.include_router(files_router)
|
|
||||||
app.include_router(processing_router)
|
|
||||||
app.include_router(memory_router)
|
|
||||||
app.include_router(config_router)
|
|
||||||
app.include_router(barcodes_router)
|
|
||||||
app.include_router(sync_router)
|
|
||||||
app.include_router(ws_router)
|
|
||||||
app.include_router(logs_router)
|
|
||||||
app.include_router(tasks_router)
|
|
||||||
|
|
||||||
|
|
||||||
# Serve Vue SPA static files
|
|
||||||
_static_dir = Path(__file__).resolve().parent / "static"
|
|
||||||
if _static_dir.is_dir():
|
|
||||||
app.mount("/assets", StaticFiles(directory=str(_static_dir / "assets")), name="assets")
|
|
||||||
|
|
||||||
@app.get("/{full_path:path}")
|
|
||||||
async def serve_spa(full_path: str):
|
|
||||||
"""Catch-all: serve index.html for Vue Router history mode."""
|
|
||||||
file_path = _static_dir / full_path
|
|
||||||
if file_path.is_file():
|
|
||||||
return FileResponse(str(file_path))
|
|
||||||
return FileResponse(str(_static_dir / "index.html"))
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
"""HTTP request logging middleware."""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import time
|
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
|
||||||
from starlette.requests import Request
|
|
||||||
from starlette.responses import Response
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# Paths that should not be logged
|
|
||||||
_SKIP_PREFIXES = ("/assets", "/ws")
|
|
||||||
_SKIP_PATHS = ("/favicon.ico",)
|
|
||||||
|
|
||||||
|
|
||||||
class LoggingMiddleware(BaseHTTPMiddleware):
|
|
||||||
"""Logs every HTTP request to the database via db_schema.insert_http_log.
|
|
||||||
|
|
||||||
- Skips static assets, WebSocket, and favicon paths.
|
|
||||||
- Measures request duration in milliseconds.
|
|
||||||
- Extracts username from request.state.user when available.
|
|
||||||
- Writes logs asynchronously (non-blocking).
|
|
||||||
- Never lets logging failures break a request.
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def dispatch(self, request: Request, call_next) -> Response:
|
|
||||||
path = request.url.path
|
|
||||||
|
|
||||||
# Skip paths that should not be logged
|
|
||||||
if path in _SKIP_PATHS or any(path.startswith(p) for p in _SKIP_PREFIXES):
|
|
||||||
return await call_next(request)
|
|
||||||
|
|
||||||
start = time.perf_counter()
|
|
||||||
status_code = 500 # default if call_next raises
|
|
||||||
try:
|
|
||||||
response = await call_next(request)
|
|
||||||
status_code = response.status_code
|
|
||||||
return response
|
|
||||||
finally:
|
|
||||||
duration_ms = (time.perf_counter() - start) * 1000
|
|
||||||
method = request.method
|
|
||||||
user = getattr(request.state, "user", None)
|
|
||||||
ip = request.client.host if request.client else None
|
|
||||||
|
|
||||||
# Fire-and-forget: never block the response
|
|
||||||
asyncio.create_task(
|
|
||||||
self._write_log(method, path, status_code, duration_ms, user, ip)
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _write_log(
|
|
||||||
method: str,
|
|
||||||
path: str,
|
|
||||||
status_code: int,
|
|
||||||
duration_ms: float,
|
|
||||||
user: str | None,
|
|
||||||
ip: str | None,
|
|
||||||
) -> None:
|
|
||||||
"""Write the log entry in a thread executor to avoid blocking."""
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
await loop.run_in_executor(
|
|
||||||
None,
|
|
||||||
lambda: _db_insert(method, path, status_code, duration_ms, user, ip),
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
# Logging must never break the request
|
|
||||||
logger.debug("Failed to write HTTP log", exc_info=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _db_insert(
|
|
||||||
method: str,
|
|
||||||
path: str,
|
|
||||||
status_code: int,
|
|
||||||
duration_ms: float,
|
|
||||||
user: str | None,
|
|
||||||
ip: str | None,
|
|
||||||
) -> None:
|
|
||||||
"""Synchronous DB insert — called inside run_in_executor."""
|
|
||||||
try:
|
|
||||||
from web.backend.services.db_schema import insert_http_log
|
|
||||||
|
|
||||||
insert_http_log(
|
|
||||||
method=method,
|
|
||||||
path=path,
|
|
||||||
status_code=status_code,
|
|
||||||
duration_ms=duration_ms,
|
|
||||||
user=user,
|
|
||||||
ip=ip,
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
logger.debug("DB insert_http_log failed", exc_info=True)
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# Web backend dependencies
|
|
||||||
fastapi>=0.104.0
|
|
||||||
uvicorn[standard]>=0.24.0
|
|
||||||
python-jose[cryptography]>=3.3.0
|
|
||||||
bcrypt>=4.0.0
|
|
||||||
python-multipart>=0.0.6
|
|
||||||
httpx>=0.25.0
|
|
||||||
werkzeug>=3.0.0
|
|
||||||
|
|
||||||
# Core app dependencies (needed by processing endpoints)
|
|
||||||
pandas>=1.3.0
|
|
||||||
openpyxl>=3.0.0
|
|
||||||
xlrd>=2.0.0,<2.1.0
|
|
||||||
xlwt>=1.3.0
|
|
||||||
xlutils>=2.0.0
|
|
||||||
numpy>=1.19.0
|
|
||||||
requests>=2.25.0
|
|
||||||
python-dotenv>=1.0.0
|
|
||||||
configparser>=5.0.0
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
"""Barcode mapping CRUD endpoints."""
|
|
||||||
|
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, Optional, List
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException, Depends
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from ..auth.dependencies import get_current_user
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/barcodes", tags=["barcodes"])
|
|
||||||
|
|
||||||
_project_root = Path(__file__).resolve().parent.parent.parent.parent
|
|
||||||
_mappings_file = _project_root / "config" / "barcode_mappings.json"
|
|
||||||
|
|
||||||
|
|
||||||
class BarcodeMapping(BaseModel):
|
|
||||||
barcode: str
|
|
||||||
target: Optional[str] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
# Special rule fields
|
|
||||||
multiplier: Optional[int] = None
|
|
||||||
target_unit: Optional[str] = None
|
|
||||||
fixed_price: Optional[float] = None
|
|
||||||
specification: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class BarcodeMappingUpdate(BaseModel):
|
|
||||||
target: Optional[str] = None
|
|
||||||
description: Optional[str] = None
|
|
||||||
multiplier: Optional[int] = None
|
|
||||||
target_unit: Optional[str] = None
|
|
||||||
fixed_price: Optional[float] = None
|
|
||||||
specification: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
def _load_mappings() -> Dict:
|
|
||||||
if not _mappings_file.is_file():
|
|
||||||
return {}
|
|
||||||
try:
|
|
||||||
return json.loads(_mappings_file.read_text(encoding="utf-8"))
|
|
||||||
except Exception:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def _save_mappings(data: Dict):
|
|
||||||
_mappings_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
_mappings_file.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
|
||||||
async def list_barcodes(
|
|
||||||
search: str = "",
|
|
||||||
current_user: dict = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
mappings = _load_mappings()
|
|
||||||
items = []
|
|
||||||
for barcode, info in mappings.items():
|
|
||||||
if isinstance(info, dict):
|
|
||||||
target = info.get("map_to", info.get("target", ""))
|
|
||||||
desc = info.get("description", "")
|
|
||||||
item = {
|
|
||||||
"barcode": barcode,
|
|
||||||
"target": target,
|
|
||||||
"description": desc,
|
|
||||||
"multiplier": info.get("multiplier"),
|
|
||||||
"target_unit": info.get("target_unit"),
|
|
||||||
"fixed_price": info.get("fixed_price"),
|
|
||||||
"specification": info.get("specification"),
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
item = {
|
|
||||||
"barcode": barcode,
|
|
||||||
"target": str(info),
|
|
||||||
"description": "",
|
|
||||||
"multiplier": None,
|
|
||||||
"target_unit": None,
|
|
||||||
"fixed_price": None,
|
|
||||||
"specification": None,
|
|
||||||
}
|
|
||||||
s = search.lower() if search else ""
|
|
||||||
if s and s not in barcode.lower() and s not in item["target"].lower() and s not in (desc or "").lower():
|
|
||||||
continue
|
|
||||||
items.append(item)
|
|
||||||
return {"items": items, "total": len(items)}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{barcode}")
|
|
||||||
async def get_barcode(
|
|
||||||
barcode: str,
|
|
||||||
current_user: dict = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
mappings = _load_mappings()
|
|
||||||
if barcode not in mappings:
|
|
||||||
raise HTTPException(404, f"未找到条码映射 {barcode}")
|
|
||||||
info = mappings[barcode]
|
|
||||||
if isinstance(info, dict):
|
|
||||||
return {"barcode": barcode, "target": info.get("map_to", info.get("target", "")), "description": info.get("description", "")}
|
|
||||||
return {"barcode": barcode, "target": str(info), "description": ""}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("")
|
|
||||||
async def create_barcode(
|
|
||||||
body: BarcodeMapping,
|
|
||||||
current_user: dict = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
mappings = _load_mappings()
|
|
||||||
if body.barcode in mappings:
|
|
||||||
raise HTTPException(409, f"条码 {body.barcode} 已存在")
|
|
||||||
|
|
||||||
entry: dict = {"description": body.description or ""}
|
|
||||||
if body.multiplier:
|
|
||||||
entry["multiplier"] = body.multiplier
|
|
||||||
if body.target_unit:
|
|
||||||
entry["target_unit"] = body.target_unit
|
|
||||||
if body.fixed_price is not None:
|
|
||||||
entry["fixed_price"] = body.fixed_price
|
|
||||||
if body.specification:
|
|
||||||
entry["specification"] = body.specification
|
|
||||||
else:
|
|
||||||
entry["map_to"] = body.target or ""
|
|
||||||
|
|
||||||
mappings[body.barcode] = entry
|
|
||||||
_save_mappings(mappings)
|
|
||||||
return {"message": f"已创建规则 {body.barcode}"}
|
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{barcode}")
|
|
||||||
async def update_barcode(
|
|
||||||
barcode: str,
|
|
||||||
body: BarcodeMappingUpdate,
|
|
||||||
current_user: dict = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
mappings = _load_mappings()
|
|
||||||
if barcode not in mappings:
|
|
||||||
raise HTTPException(404, f"未找到条码规则 {barcode}")
|
|
||||||
|
|
||||||
existing = mappings[barcode]
|
|
||||||
if not isinstance(existing, dict):
|
|
||||||
existing = {"map_to": str(existing), "description": ""}
|
|
||||||
|
|
||||||
# Check if this is a special rule (has multiplier) or being converted to one
|
|
||||||
if body.multiplier is not None:
|
|
||||||
# Convert to special rule: remove map_to, add multiplier fields
|
|
||||||
existing.pop("map_to", None)
|
|
||||||
existing["multiplier"] = body.multiplier
|
|
||||||
if body.target_unit is not None:
|
|
||||||
existing["target_unit"] = body.target_unit
|
|
||||||
if body.fixed_price is not None:
|
|
||||||
existing["fixed_price"] = body.fixed_price
|
|
||||||
if body.specification is not None:
|
|
||||||
existing["specification"] = body.specification
|
|
||||||
elif body.target is not None:
|
|
||||||
# Convert to simple mapping: remove special fields, add map_to
|
|
||||||
for k in ("multiplier", "target_unit", "fixed_price", "specification"):
|
|
||||||
existing.pop(k, None)
|
|
||||||
existing["map_to"] = body.target
|
|
||||||
|
|
||||||
if body.description is not None:
|
|
||||||
existing["description"] = body.description
|
|
||||||
|
|
||||||
mappings[barcode] = existing
|
|
||||||
_save_mappings(mappings)
|
|
||||||
return {"message": f"已更新规则 {barcode}"}
|
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/{barcode}")
|
|
||||||
async def delete_barcode(
|
|
||||||
barcode: str,
|
|
||||||
current_user: dict = Depends(get_current_user),
|
|
||||||
):
|
|
||||||
mappings = _load_mappings()
|
|
||||||
if barcode not in mappings:
|
|
||||||
raise HTTPException(404, f"未找到条码映射 {barcode}")
|
|
||||||
del mappings[barcode]
|
|
||||||
_save_mappings(mappings)
|
|
||||||
return {"message": f"已删除映射 {barcode}"}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user