|
@@ -1232,6 +1232,73 @@ def get_photo_record_detail(goods_art_no: str = None):
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+@app.get("/get_action_names_by_goods", description="通过货号获取动作名称列表")
|
|
|
|
|
+def get_action_names_by_goods(goods_art_no: str = None):
|
|
|
|
|
+ """
|
|
|
|
|
+ 通过货号查询PhotoRecord表,按自然排序获取action_id,
|
|
|
|
|
+ 再查询DeviceConfig获取对应的action_name,按顺序返回action_name数组
|
|
|
|
|
+
|
|
|
|
|
+ Args:
|
|
|
|
|
+ goods_art_no: 货号
|
|
|
|
|
+
|
|
|
|
|
+ Returns:
|
|
|
|
|
+ 如果货号不存在: {"code": 1, "msg": "货号不存在", "data": None}
|
|
|
|
|
+ 如果货号存在: {"code": 0, "msg": "", "data": [action_name1, action_name2, ...]}
|
|
|
|
|
+ """
|
|
|
|
|
+ if goods_art_no is None or goods_art_no.strip() == "":
|
|
|
|
|
+ return {"code": 1, "msg": "参数错误", "data": None}
|
|
|
|
|
+
|
|
|
|
|
+ session = SqlQuery()
|
|
|
|
|
+ try:
|
|
|
|
|
+ # 1. 查询该货号的所有记录
|
|
|
|
|
+ photos = CRUD(PhotoRecord)
|
|
|
|
|
+ photo_records = photos.read_all(
|
|
|
|
|
+ session,
|
|
|
|
|
+ conditions={"goods_art_no": goods_art_no, "delete_time": None}
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 2. 如果货号不存在,返回错误
|
|
|
|
|
+ if not photo_records or len(photo_records) == 0:
|
|
|
|
|
+ return {"code": 1, "msg": "货号不存在", "data": None}
|
|
|
|
|
+
|
|
|
|
|
+ # 3. 按自然排序对记录进行排序(基于image_index或其他字段)
|
|
|
|
|
+ # 使用natsort对image_index进行自然排序
|
|
|
|
|
+ from natsort import natsorted, ns
|
|
|
|
|
+
|
|
|
|
|
+ # 将记录转换为可排序的列表,按image_index排序
|
|
|
|
|
+ sorted_records = natsorted(
|
|
|
|
|
+ photo_records,
|
|
|
|
|
+ key=lambda x: str(x.image_index) if x.image_index is not None else "",
|
|
|
|
|
+ alg=ns.PATH | ns.IGNORECASE
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ # 4. 提取排序后的action_id列表
|
|
|
|
|
+ action_ids = [record.action_id for record in sorted_records if record.action_id is not None]
|
|
|
|
|
+
|
|
|
|
|
+ if not action_ids:
|
|
|
|
|
+ return {"code": 0, "msg": "", "data": []}
|
|
|
|
|
+
|
|
|
|
|
+ # 5. 根据action_id查询DeviceConfig获取action_name
|
|
|
|
|
+ device_config_crud = CRUD(DeviceConfig)
|
|
|
|
|
+ action_names = []
|
|
|
|
|
+
|
|
|
|
|
+ for action_id in action_ids:
|
|
|
|
|
+ device_config = device_config_crud.read(
|
|
|
|
|
+ session,
|
|
|
|
|
+ conditions={"id": action_id,'take_picture':True}
|
|
|
|
|
+ )
|
|
|
|
|
+ if device_config and device_config.action_name:
|
|
|
|
|
+ action_names.append(device_config.action_name)
|
|
|
|
|
+
|
|
|
|
|
+ return {"code": 0, "msg": "", "data": action_names}
|
|
|
|
|
+
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.error(f"查询货号 {goods_art_no} 的动作名称失败: {str(e)}")
|
|
|
|
|
+ return {"code": 1, "msg": f"查询失败: {str(e)}", "data": None}
|
|
|
|
|
+ finally:
|
|
|
|
|
+ session.close()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
@app.post("/delect_goods_arts", description="通过货号删除记录")
|
|
@app.post("/delect_goods_arts", description="通过货号删除记录")
|
|
|
def delect_goods_arts(params: PhotoRecordDelete):
|
|
def delect_goods_arts(params: PhotoRecordDelete):
|
|
|
limit_path = "{}/{}".format(settings.OUTPUT_DIR,
|
|
limit_path = "{}/{}".format(settings.OUTPUT_DIR,
|
|
@@ -2077,123 +2144,3 @@ async def import_images_from_dir(params:ImportDirs):
|
|
|
logger.error(f"API 调用异常: {str(e)}")
|
|
logger.error(f"API 调用异常: {str(e)}")
|
|
|
raise UnicornException(f"{str(e)}")
|
|
raise UnicornException(f"{str(e)}")
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-def auto_add_missing_columns():
|
|
|
|
|
- """
|
|
|
|
|
- 自动检测并添加缺失的数据库字段(打包兼容版本)
|
|
|
|
|
- - 使用原生 SQLite,避免 SQLAlchemy 在打包环境中的问题
|
|
|
|
|
- - 简化字段类型处理,避免复杂类型导致的问题
|
|
|
|
|
- - 只添加新字段,不会修改或删除现有字段
|
|
|
|
|
- - 不会丢失任何数据
|
|
|
|
|
- - 可以安全地重复执行(幂等性)
|
|
|
|
|
- """
|
|
|
|
|
- try:
|
|
|
|
|
- import sqlite3
|
|
|
|
|
-
|
|
|
|
|
- # 获取数据库文件路径
|
|
|
|
|
- db_path = sqlite_file_name.replace("sqlite:///", "")
|
|
|
|
|
-
|
|
|
|
|
- # 直接连接 SQLite 数据库
|
|
|
|
|
- conn = sqlite3.connect(db_path)
|
|
|
|
|
- cursor = conn.cursor()
|
|
|
|
|
-
|
|
|
|
|
- # 定义模型和表名的映射
|
|
|
|
|
- models_tables = [
|
|
|
|
|
- (DeviceConfig, "device_config"),
|
|
|
|
|
- (PhotoRecord, "photo_record"),
|
|
|
|
|
- (DeviceConfigTabs, "device_config_tabs"),
|
|
|
|
|
- (SysConfigs, "sys_configs"),
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- for model_class, table_name in models_tables:
|
|
|
|
|
- try:
|
|
|
|
|
- # 检查表是否存在
|
|
|
|
|
- cursor.execute(
|
|
|
|
|
- f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'")
|
|
|
|
|
- if not cursor.fetchone():
|
|
|
|
|
- print(f"⚠️ 表 {table_name} 不存在,跳过")
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- # 获取现有列
|
|
|
|
|
- cursor.execute(f"PRAGMA table_info({table_name})")
|
|
|
|
|
- existing_columns = {row[1] for row in cursor.fetchall()}
|
|
|
|
|
- print(f"📋 表 {table_name} 现有字段: {existing_columns}")
|
|
|
|
|
-
|
|
|
|
|
- # 从模型中获取所有字段
|
|
|
|
|
- model_columns = model_class.__table__.columns
|
|
|
|
|
-
|
|
|
|
|
- for column_name, column_obj in model_columns.items():
|
|
|
|
|
- # 如果列已存在,跳过
|
|
|
|
|
- if column_name in existing_columns:
|
|
|
|
|
- continue
|
|
|
|
|
-
|
|
|
|
|
- try:
|
|
|
|
|
- # 简化类型映射 - 使用 SQLite 原生类型
|
|
|
|
|
- column_type_str = str(column_obj.type).upper()
|
|
|
|
|
-
|
|
|
|
|
- # 映射 SQLAlchemy 类型到 SQLite 类型
|
|
|
|
|
- if 'INTEGER' in column_type_str or 'INT' in column_type_str:
|
|
|
|
|
- sqlite_type = 'INTEGER'
|
|
|
|
|
- elif 'FLOAT' in column_type_str or 'DOUBLE' in column_type_str or 'DECIMAL' in column_type_str or 'REAL' in column_type_str:
|
|
|
|
|
- sqlite_type = 'REAL'
|
|
|
|
|
- elif 'BOOLEAN' in column_type_str or 'BOOL' in column_type_str:
|
|
|
|
|
- sqlite_type = 'INTEGER' # SQLite 用 0/1 表示布尔
|
|
|
|
|
- elif 'DATETIME' in column_type_str or 'TIMESTAMP' in column_type_str:
|
|
|
|
|
- sqlite_type = 'TEXT' # SQLite 用 TEXT 存储时间
|
|
|
|
|
- elif 'VARCHAR' in column_type_str or 'CHAR' in column_type_str or 'STRING' in column_type_str or 'TEXT' in column_type_str:
|
|
|
|
|
- sqlite_type = 'TEXT'
|
|
|
|
|
- else:
|
|
|
|
|
- sqlite_type = 'TEXT' # 默认使用 TEXT
|
|
|
|
|
-
|
|
|
|
|
- # 处理 NULL/NOT NULL
|
|
|
|
|
- nullable_str = "" if column_obj.nullable else "NOT NULL"
|
|
|
|
|
-
|
|
|
|
|
- # 简化默认值处理
|
|
|
|
|
- default_str = ""
|
|
|
|
|
- if column_obj.default is not None:
|
|
|
|
|
- default_value = column_obj.default.arg
|
|
|
|
|
- if callable(default_value):
|
|
|
|
|
- # 跳过函数默认值
|
|
|
|
|
- default_str = ""
|
|
|
|
|
- elif isinstance(default_value, bool):
|
|
|
|
|
- default_str = f"DEFAULT {int(default_value)}"
|
|
|
|
|
- elif isinstance(default_value, (int, float)):
|
|
|
|
|
- default_str = f"DEFAULT {default_value}"
|
|
|
|
|
- elif isinstance(default_value, str):
|
|
|
|
|
- default_str = f"DEFAULT '{default_value}'"
|
|
|
|
|
-
|
|
|
|
|
- # 构建 ALTER TABLE 语句
|
|
|
|
|
- parts = [
|
|
|
|
|
- f"ALTER TABLE {table_name}",
|
|
|
|
|
- f"ADD COLUMN {column_name}",
|
|
|
|
|
- sqlite_type,
|
|
|
|
|
- ]
|
|
|
|
|
- if nullable_str:
|
|
|
|
|
- parts.append(nullable_str)
|
|
|
|
|
- if default_str:
|
|
|
|
|
- parts.append(default_str)
|
|
|
|
|
-
|
|
|
|
|
- sql = " ".join(parts)
|
|
|
|
|
-
|
|
|
|
|
- cursor.execute(sql)
|
|
|
|
|
- print(
|
|
|
|
|
- f"✅ 成功添加字段: {table_name}.{column_name} ({sqlite_type})")
|
|
|
|
|
-
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- print(f"⚠️ 添加字段失败 {table_name}.{column_name}: {e}")
|
|
|
|
|
- # 继续处理下一个字段
|
|
|
|
|
-
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- print(f"⚠️ 处理表 {table_name} 时出错: {e}")
|
|
|
|
|
- import traceback
|
|
|
|
|
- traceback.print_exc()
|
|
|
|
|
-
|
|
|
|
|
- conn.commit()
|
|
|
|
|
- conn.close()
|
|
|
|
|
- print("🎉 数据库迁移检查完成")
|
|
|
|
|
-
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- print(f"⚠️ 数据库迁移整体失败: {e}")
|
|
|
|
|
- import traceback
|
|
|
|
|
- traceback.print_exc()
|
|
|
|
|
- # 不抛出异常,允许程序继续运行
|
|
|