Update: Refactor to absolute imports, fix QPS limits, and enhance Gitea sync with SQLite support
This commit is contained in:
@@ -11,8 +11,8 @@ import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from ..config.settings import ConfigManager
|
||||
from ..core.utils.log_utils import get_logger
|
||||
from app.config.settings import ConfigManager
|
||||
from app.core.utils.log_utils import get_logger
|
||||
from .ocr_service import OCRService
|
||||
from .order_service import OrderService
|
||||
|
||||
@@ -25,10 +25,11 @@ IMAGE_EXTS = {'.png', '.jpg', '.jpeg', '.bmp', '.webp', '.tif', '.tiff'}
|
||||
class BatchService:
|
||||
"""批量处理服务。"""
|
||||
|
||||
def __init__(self, config: Optional[ConfigManager] = None):
|
||||
def __init__(self, config: Optional[ConfigManager] = None, missing_barcodes_cb: Optional[Callable[[List[str]], None]] = None):
|
||||
self.config = config or ConfigManager()
|
||||
self.missing_barcodes_cb = missing_barcodes_cb
|
||||
self.ocr_service = OCRService(self.config)
|
||||
self.order_service = OrderService(self.config)
|
||||
self.order_service = OrderService(self.config, missing_barcodes_cb=self.missing_barcodes_cb)
|
||||
self._input_folder = self.config.get_path(
|
||||
'Paths', 'input_folder', fallback='data/input', create=True
|
||||
)
|
||||
|
||||
+27
-21
@@ -7,9 +7,9 @@ OCR服务模块
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any, Callable
|
||||
import os
|
||||
|
||||
from ..config.settings import ConfigManager
|
||||
from ..core.utils.log_utils import get_logger
|
||||
from ..core.ocr.table_ocr import OCRProcessor
|
||||
from app.config.settings import ConfigManager
|
||||
from app.core.utils.log_utils import get_logger
|
||||
from app.core.ocr.table_ocr import OCRProcessor
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -62,16 +62,10 @@ class OCRService:
|
||||
if not self._is_valid_image(image_path):
|
||||
logger.error(f"不支持的文件类型: {image_path}")
|
||||
return None
|
||||
|
||||
# 检查是否已处理
|
||||
excel_file = self._get_excel_path(image_path)
|
||||
if os.path.exists(excel_file):
|
||||
logger.info(f"文件已处理过,跳过OCR识别: {image_path}")
|
||||
# 即使 xlsx 已存在,仍补写 meta.json(首次 OCR 后可能未生成)
|
||||
meta_path = str(excel_file).replace('.xlsx', '.meta.json')
|
||||
if not os.path.exists(meta_path):
|
||||
self._write_meta_json(str(excel_file), {}, image_path)
|
||||
return excel_file
|
||||
|
||||
# 不再做 xlsx 已存在就跳过的判断(xlsx 可能被业务层重命名过),
|
||||
# 跳过逻辑交给 OCRProcessor 内部的 record_manager.is_processed(image_path)
|
||||
# —— 由它读 processed_files.json(业务层会同步更新 xlsx 路径)
|
||||
|
||||
# 执行OCR识别
|
||||
result = self.ocr_processor.process_image(image_path)
|
||||
@@ -212,24 +206,36 @@ class OCRService:
|
||||
base = Path(excel_path)
|
||||
meta_path = base.with_suffix('.meta.json')
|
||||
|
||||
# 0) 优先:调百度通用文字识别(/accurate),覆盖全图文字(含手写抬头/日期)
|
||||
# 0) 优先:调百度通用票务识别(/general_ocr),精准捕获表头(含手写抬头/日期)
|
||||
general_text = ''
|
||||
general_lines = []
|
||||
try:
|
||||
# OCRService.ocr_processor = core.ocr.table_ocr.OCRProcessor
|
||||
# 其 .ocr_client = BaiduOCRClient (process_image 时初始化)
|
||||
client = getattr(self.ocr_processor, 'ocr_client', None)
|
||||
if client is None:
|
||||
# 显式触发一次 process_image 准备流程 (不会重复 OCR)
|
||||
try:
|
||||
self.ocr_processor._ensure_ocr_client()
|
||||
except Exception:
|
||||
pass
|
||||
client = getattr(self.ocr_processor, 'ocr_client', None)
|
||||
if client and hasattr(client, 'recognize_general') and image_path and os.path.exists(str(image_path)):
|
||||
|
||||
# 关键:在进行第二次 OCR (票务识别) 前稍微等待,避免触发 QPS 限制
|
||||
import time
|
||||
time.sleep(0.5)
|
||||
|
||||
# 优先使用票务识别 (Ticket OCR)
|
||||
if client and hasattr(client, 'recognize_ticket') and image_path and os.path.exists(str(image_path)):
|
||||
logger.info("使用通用票务识别捕获表头元数据...")
|
||||
words = client.recognize_ticket(str(image_path))
|
||||
if words:
|
||||
general_lines = [(w.get('words') or '').strip() for w in words if (w.get('words') or '').strip()]
|
||||
general_text = '\n'.join(general_lines)
|
||||
logger.info(f"票务识别获取 {len(general_lines)} 行文字")
|
||||
|
||||
# 如果票务识别无结果或不可用,则兜底使用高精度文字识别
|
||||
if not general_lines and client and hasattr(client, 'recognize_general') and image_path and os.path.exists(str(image_path)):
|
||||
logger.info("尝试高精度文字识别作为兜底...")
|
||||
words = client.recognize_general(str(image_path))
|
||||
if words:
|
||||
# 按 location.top 排序(顶部先),方便后续提取供应商/日期
|
||||
def _top(w):
|
||||
loc = w.get('location') or {}
|
||||
try:
|
||||
@@ -239,9 +245,9 @@ class OCRService:
|
||||
words_sorted = sorted(words, key=_top)
|
||||
general_lines = [(w.get('words') or '').strip() for w in words_sorted if (w.get('words') or '').strip()]
|
||||
general_text = '\n'.join(general_lines)
|
||||
logger.info(f"通用识别获取 {len(general_lines)} 行文字")
|
||||
logger.info(f"通用高精度识别获取 {len(general_lines)} 行文字")
|
||||
except Exception as e:
|
||||
logger.warning(f"通用识别失败(不影响主流程): {e}")
|
||||
logger.warning(f"获取元数据识别失败(不影响主流程): {e}")
|
||||
|
||||
# 1) 表格识别 header/body 拼表内文字(已在前面逻辑处理)
|
||||
ocr_text = ''
|
||||
|
||||
+222
-44
@@ -12,13 +12,13 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple, Union, Any, Callable
|
||||
|
||||
from ..config.settings import ConfigManager
|
||||
from ..core.utils.log_utils import get_logger
|
||||
from ..core.excel.processor import ExcelProcessor
|
||||
from ..core.excel.merger import PurchaseOrderMerger
|
||||
from ..core.db.product_db import ProductDatabase
|
||||
from ..core.db.order_metadata_db import OrderMetadataDB
|
||||
from ..core.ocr.metadata_extractor import OrderMetadataExtractor, sanitize_for_filename
|
||||
from app.config.settings import ConfigManager
|
||||
from app.core.utils.log_utils import get_logger
|
||||
from app.core.excel.processor import ExcelProcessor
|
||||
from app.core.excel.merger import PurchaseOrderMerger
|
||||
from app.core.db.product_db import ProductDatabase
|
||||
from app.core.db.order_metadata_db import OrderMetadataDB
|
||||
from app.core.ocr.metadata_extractor import OrderMetadataExtractor, sanitize_for_filename
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -27,15 +27,17 @@ class OrderService:
|
||||
订单服务:协调Excel处理和订单合并流程
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[ConfigManager] = None):
|
||||
def __init__(self, config: Optional[ConfigManager] = None, missing_barcodes_cb: Optional[Callable[[List[str]], None]] = None):
|
||||
"""
|
||||
初始化订单服务
|
||||
|
||||
Args:
|
||||
config: 配置管理器,如果为None则创建新的
|
||||
missing_barcodes_cb: 缺失条码的回调函数
|
||||
"""
|
||||
logger.info("初始化OrderService")
|
||||
self.config = config or ConfigManager()
|
||||
self.missing_barcodes_cb = missing_barcodes_cb
|
||||
|
||||
# 创建共享的商品数据库实例
|
||||
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'
|
||||
@@ -45,7 +47,11 @@ class OrderService:
|
||||
self.product_db = ProductDatabase(db_path, tpl_path)
|
||||
|
||||
# 创建Excel处理器和采购单合并器
|
||||
self.excel_processor = ExcelProcessor(self.config, product_db=self.product_db)
|
||||
self.excel_processor = ExcelProcessor(
|
||||
self.config,
|
||||
product_db=self.product_db,
|
||||
missing_barcodes_cb=self.missing_barcodes_cb
|
||||
)
|
||||
self.order_merger = PurchaseOrderMerger(self.config)
|
||||
|
||||
# 元信息识别器 + 单据元信息库
|
||||
@@ -291,15 +297,16 @@ class OrderService:
|
||||
if meta_path.exists():
|
||||
try:
|
||||
payload = json.loads(meta_path.read_text(encoding='utf-8'))
|
||||
# 优先用通用识别文本(含手写抬头/日期)
|
||||
ocr_text = payload.get('general_text') or payload.get('ocr_text', '') or ''
|
||||
# 区分表格 OCR 文本和通用识别文本
|
||||
ocr_text = payload.get('ocr_text', '') or ''
|
||||
general_text = payload.get('general_text', '') or ''
|
||||
ocr_rows = payload.get('ocr_rows', []) or []
|
||||
source_image = payload.get('image_path', '') or ''
|
||||
except Exception as e:
|
||||
logger.warning(f"读 meta.json 失败: {e}")
|
||||
|
||||
# 兜底:从 xlsx 拼文本(与 OCRService._write_meta_json 的兜底一致)
|
||||
if not ocr_text:
|
||||
if not ocr_text and not general_text:
|
||||
try:
|
||||
import xlrd
|
||||
rb = xlrd.open_workbook(str(ocr_excel_path))
|
||||
@@ -313,7 +320,7 @@ class OrderService:
|
||||
except Exception as e:
|
||||
logger.debug(f"从 xlsx 拼 OCR 文本失败: {e}")
|
||||
|
||||
meta = self.extractor.extract(ocr_text, ocr_rows)
|
||||
meta = self.extractor.extract(ocr_text, ocr_rows, general_text=general_text)
|
||||
self.metadata_db.save(
|
||||
file_hash=file_hash,
|
||||
supplier=meta.supplier,
|
||||
@@ -334,49 +341,77 @@ class OrderService:
|
||||
def _apply_metadata_to_filenames(self, result_path: str,
|
||||
ocr_excel_path: str,
|
||||
meta) -> str:
|
||||
"""应用新文件名规则:
|
||||
- result: 采购单_{YYYYMMDD}_{供应商}_{hash}.xls
|
||||
- 原图: {原stem}_{YYYYMMDD}_{供应商}_{hash}.{ext}
|
||||
"""应用新文件名规则(按供应商+日期):
|
||||
- result (xls):{供应商}_{日期}.xls
|
||||
- output xlsx: {供应商}_{日期}.xlsx
|
||||
- 原图: {供应商}_{日期}.{ext}
|
||||
|
||||
冲突时加 _2 / _3 ...;任一步骤失败不影响整体。
|
||||
|
||||
任一步骤失败不影响 result 文件本身。
|
||||
Returns:
|
||||
新 result 路径(无论重命名是否成功都返回;失败时返回原路径)
|
||||
"""
|
||||
try:
|
||||
file_hash = Path(ocr_excel_path).stem
|
||||
# 1. 提取并清理元数据
|
||||
supplier_clean = sanitize_for_filename(meta.supplier) or '未知供应商'
|
||||
date_part = meta.bill_date or '未知日期'
|
||||
|
||||
# ── 1. result 重命名 ──
|
||||
new_result_name = f"采购单_{date_part}_{supplier_clean}_{file_hash}.xls"
|
||||
result_dir = Path(result_path).parent
|
||||
new_result_path = result_dir / new_result_name
|
||||
try:
|
||||
# 冲突时加 _N
|
||||
if new_result_path.exists() and str(new_result_path) != str(result_path):
|
||||
new_result_path = self._dedup_path(new_result_path)
|
||||
if str(new_result_path) != str(result_path):
|
||||
os.rename(result_path, str(new_result_path))
|
||||
logger.info(f"result 重命名: {result_path} -> {new_result_path}")
|
||||
result_path = str(new_result_path)
|
||||
except Exception as e:
|
||||
logger.warning(f"result 重命名失败: {e}")
|
||||
# 2. 构造新的基础文件名:[供应商]_[日期]
|
||||
# 按照用户要求:按照供应商名称加日期进行修改,且不含“采购单”等冗余字眼
|
||||
base_name = f"{supplier_clean}_{date_part}"
|
||||
|
||||
# ── 2. 原图重命名 ──
|
||||
file_hash = Path(ocr_excel_path).stem # 仅用作 SQLite PK,不入文件名
|
||||
logger.info(f"生成标准化文件名: {base_name}")
|
||||
|
||||
# ── 1. result 重命名 ──
|
||||
new_result_path = self._safe_rename(
|
||||
result_path, f"{base_name}.xls"
|
||||
)
|
||||
|
||||
# ── 2. output xlsx 重命名(保证 output/ 与 result/ 文件名一致)──
|
||||
new_output_path = self._safe_rename(
|
||||
ocr_excel_path, f"{base_name}.xlsx"
|
||||
)
|
||||
# 同步 meta.json
|
||||
old_meta = Path(ocr_excel_path).with_suffix('.meta.json')
|
||||
if old_meta.exists():
|
||||
new_meta = Path(new_output_path).with_suffix('.meta.json')
|
||||
if str(new_meta) != str(old_meta):
|
||||
try:
|
||||
if new_meta.exists(): os.remove(new_meta)
|
||||
os.rename(old_meta, new_meta)
|
||||
except Exception as e:
|
||||
logger.warning(f"meta.json 重命名失败: {e}")
|
||||
|
||||
# ── 2.5 同步 processed_files.json(避免下次 OCR 重新识别)──
|
||||
if str(new_output_path) != str(ocr_excel_path):
|
||||
self._sync_processed_record(Path(ocr_excel_path), Path(new_output_path))
|
||||
# 也要同步 ExcelProcessor 的记录
|
||||
self._update_excel_process_record(Path(ocr_excel_path), Path(new_output_path))
|
||||
|
||||
# ── 3. 原图重命名 ──
|
||||
try:
|
||||
meta_row = self.metadata_db.get(file_hash)
|
||||
src_image = (meta_row or {}).get('source_image', '')
|
||||
# 尝试从 processed_files.json 反查原图路径
|
||||
src_image = self._find_input_image_for_output(str(ocr_excel_path))
|
||||
|
||||
# 如果没找到,再从 DB 拿
|
||||
if not src_image:
|
||||
meta_row = self.metadata_db.get(file_hash)
|
||||
src_image = (meta_row or {}).get('source_image', '')
|
||||
|
||||
if src_image and os.path.exists(src_image):
|
||||
src_p = Path(src_image)
|
||||
stem = src_p.stem
|
||||
ext = src_p.suffix
|
||||
new_image_name = f"{stem}_{date_part}_{supplier_clean}_{file_hash}{ext}"
|
||||
new_image_path = src_p.parent / new_image_name
|
||||
# 不覆盖已重命名的图片
|
||||
if str(new_image_path) != str(src_p) and not new_image_path.exists():
|
||||
os.rename(src_p, new_image_path)
|
||||
logger.info(f"原图重命名: {src_p.name} -> {new_image_path.name}")
|
||||
# 更新 source_image 路径
|
||||
# 严禁包含原文件名,统一格式: 采购单_YYYYMMDD_供应商.ext
|
||||
new_image_name = f"{base_name}{ext}"
|
||||
new_image_path = self._safe_rename(src_image, new_image_name)
|
||||
|
||||
# ── 3.5 同步 processed_files.json 的 Key (原图路径) ──
|
||||
if str(new_image_path) != str(src_p):
|
||||
self._sync_processed_key(src_p, Path(new_image_path))
|
||||
|
||||
# 更新 source_image 路径到 DB
|
||||
if str(new_image_path) != str(src_p):
|
||||
self.metadata_db.save(
|
||||
file_hash=file_hash,
|
||||
supplier=meta.supplier,
|
||||
@@ -385,14 +420,47 @@ class OrderService:
|
||||
raw_supplier_text=meta.raw_supplier_text,
|
||||
source_image=str(new_image_path),
|
||||
)
|
||||
else:
|
||||
logger.warning(f"找不到原图,跳过重命名: {src_image}")
|
||||
except Exception as e:
|
||||
logger.warning(f"原图重命名失败: {e}")
|
||||
|
||||
return result_path
|
||||
return str(new_result_path)
|
||||
except Exception as e:
|
||||
logger.error(f"_apply_metadata_to_filenames 失败: {e}")
|
||||
return result_path
|
||||
|
||||
@staticmethod
|
||||
def _safe_rename(src: str, new_basename: str) -> str:
|
||||
"""把 src 重命名为 src.parent / new_basename。
|
||||
|
||||
- 目标已存在时加 _2 / _3 ... 后缀避免覆盖
|
||||
- 失败返回原路径,不抛异常
|
||||
"""
|
||||
try:
|
||||
src_p = Path(src)
|
||||
if not src_p.exists():
|
||||
return src
|
||||
new_p = src_p.parent / new_basename
|
||||
if str(new_p) == str(src_p):
|
||||
return src
|
||||
if new_p.exists():
|
||||
# 冲突去重
|
||||
stem, suffix = new_p.stem, new_p.suffix
|
||||
n = 2
|
||||
while True:
|
||||
cand = src_p.parent / f"{stem}_{n}{suffix}"
|
||||
if not cand.exists():
|
||||
new_p = cand
|
||||
break
|
||||
n += 1
|
||||
os.rename(src_p, new_p)
|
||||
logger.info(f"重命名: {src_p.name} -> {new_p.name}")
|
||||
return str(new_p)
|
||||
except Exception as e:
|
||||
logger.warning(f"重命名失败 {src} -> {new_basename}: {e}")
|
||||
return src
|
||||
|
||||
@staticmethod
|
||||
def _dedup_path(p: Path) -> Path:
|
||||
"""路径冲突时加 _N 后缀。"""
|
||||
@@ -404,3 +472,113 @@ class OrderService:
|
||||
if not cand.exists():
|
||||
return cand
|
||||
n += 1
|
||||
|
||||
def _find_input_image_for_output(self, output_xlsx: str) -> Optional[str]:
|
||||
"""从 processed_files.json 反查生成该 output_xlsx 的原图路径。"""
|
||||
try:
|
||||
output_dir = self.config.get_path('Paths', 'output_folder', fallback='data/output')
|
||||
record_file = os.path.join(output_dir, 'processed_files.json')
|
||||
|
||||
if not os.path.exists(record_file):
|
||||
logger.debug(f"记录文件不存在: {record_file}")
|
||||
return None
|
||||
|
||||
with open(record_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
target_output = os.path.normpath(output_xlsx)
|
||||
found_img = None
|
||||
|
||||
# 1. 尝试直接匹配和规范化路径匹配
|
||||
for img_path, out_path in data.items():
|
||||
if os.path.normpath(out_path) == target_output:
|
||||
found_img = img_path
|
||||
break
|
||||
|
||||
if found_img:
|
||||
if os.path.exists(found_img):
|
||||
return found_img
|
||||
|
||||
# 2. 模糊匹配:如果记录中的路径不存在,尝试在同目录下找 stem 匹配的文件
|
||||
# (解决因之前重命名导致的路径不一致问题)
|
||||
img_p = Path(found_img)
|
||||
parent = img_p.parent
|
||||
if parent.exists():
|
||||
stem = img_p.stem
|
||||
logger.debug(f"尝试模糊匹配原图: stem={stem[:20]}... in {parent}")
|
||||
# 尝试寻找以原 stem 开头的文件
|
||||
for cand in parent.iterdir():
|
||||
if cand.is_file() and cand.stem.startswith(stem[:20]):
|
||||
logger.info(f"模糊匹配成功: {cand.name}")
|
||||
return str(cand)
|
||||
logger.debug("模糊匹配失败")
|
||||
else:
|
||||
logger.debug(f"在 processed_files.json 中未找到输出文件 {output_xlsx} 对应的原图记录")
|
||||
except Exception as e:
|
||||
logger.debug(f"反查原图路径失败: {e}")
|
||||
return None
|
||||
|
||||
def _sync_processed_record(self, old_output: Path, new_output: Path) -> None:
|
||||
"""重命名 output xlsx 后,把 processed_files.json 里所有 value 为旧路径的项改成新路径。"""
|
||||
self._update_processed_json(old_path=old_output, new_path=new_output, is_key=False)
|
||||
|
||||
def _sync_processed_key(self, old_input: Path, new_input: Path) -> None:
|
||||
"""重命名原图后,把 processed_files.json 里的 key 从旧路径改为新路径。"""
|
||||
self._update_processed_json(old_path=old_input, new_path=new_input, is_key=True)
|
||||
|
||||
def _update_excel_process_record(self, old_xlsx: Path, new_xlsx: Path) -> None:
|
||||
"""重命名 xlsx 后,同步 excel_process_records.json (ExcelProcessor 使用的记录)。"""
|
||||
self._update_processed_json(
|
||||
old_path=old_xlsx,
|
||||
new_path=new_xlsx,
|
||||
is_key=True,
|
||||
filename='excel_process_records.json'
|
||||
)
|
||||
|
||||
def _update_processed_json(self, old_path: Path, new_path: Path, is_key: bool = False, filename: str = 'processed_files.json') -> None:
|
||||
"""更新处理记录 JSON。
|
||||
|
||||
Args:
|
||||
old_path: 旧路径
|
||||
new_path: 新路径
|
||||
is_key: True 更新 key, False 更新 value
|
||||
filename: JSON 文件名
|
||||
"""
|
||||
try:
|
||||
# 确定文件路径
|
||||
output_dir = self.config.get_path('Paths', 'output_folder', fallback='data/output')
|
||||
record_file = os.path.join(output_dir, filename)
|
||||
|
||||
if not os.path.exists(record_file):
|
||||
return
|
||||
|
||||
with open(record_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
old_str = str(old_path)
|
||||
new_str = str(new_path)
|
||||
changed = False
|
||||
|
||||
# 规范化路径以便匹配
|
||||
old_norm = os.path.normpath(old_str)
|
||||
|
||||
if is_key:
|
||||
# 更新 Key
|
||||
for k in list(data.keys()):
|
||||
if k == old_str or os.path.normpath(k) == old_norm:
|
||||
data[new_str] = data.pop(k)
|
||||
changed = True
|
||||
break
|
||||
else:
|
||||
# 更新 Value
|
||||
for k, v in list(data.items()):
|
||||
if v == old_str or os.path.normpath(v) == old_norm:
|
||||
data[k] = new_str
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
with open(record_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
logger.info(f"{filename} 已同步 ({'Key' if is_key else 'Value'}): {old_str} -> {new_str}")
|
||||
except Exception as e:
|
||||
logger.warning(f"更新 {filename} 失败: {e}")
|
||||
|
||||
@@ -8,10 +8,10 @@ import logging
|
||||
from typing import Dict, Any, Optional, List
|
||||
from pathlib import Path
|
||||
|
||||
from ..core.processors.base import BaseProcessor
|
||||
from ..core.processors.tobacco_processor import TobaccoProcessor
|
||||
from ..core.processors.ocr_processor import OCRProcessor
|
||||
from ..core.utils.log_utils import get_logger
|
||||
from app.core.processors.base import BaseProcessor
|
||||
from app.core.processors.tobacco_processor import TobaccoProcessor
|
||||
from app.core.processors.ocr_processor import OCRProcessor
|
||||
from app.core.utils.log_utils import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
@@ -65,7 +65,7 @@ class ProcessorService:
|
||||
|
||||
for supplier_config in supplier_configs:
|
||||
try:
|
||||
from ..core.processors.supplier_processors.generic_supplier_processor import GenericSupplierProcessor
|
||||
from app.core.processors.supplier_processors.generic_supplier_processor import GenericSupplierProcessor
|
||||
processor = GenericSupplierProcessor(self.config, supplier_config)
|
||||
self.processors.append(processor)
|
||||
logger.info(f"加载供应商处理器: {processor.name}")
|
||||
|
||||
@@ -7,7 +7,7 @@ import time
|
||||
import pandas as pd
|
||||
from typing import Optional, Callable
|
||||
|
||||
from ..core.utils.log_utils import get_logger
|
||||
from app.core.utils.log_utils import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ from typing import Optional, Dict, Any, List, Tuple
|
||||
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 ..config.settings import ConfigManager
|
||||
from app.config.settings import ConfigManager
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user