From 797ac1d935a84fba59aea06010fa590f217fc404 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 8 May 2026 15:47:19 +0800 Subject: [PATCH] fix: replace uuid.uuid4() with getID() from appPublic.uniqueID for all table ID generation --- json/opportunities_list.json | 107 ++++++++++++------ json/predictions_list.json | 14 ++- json/sales_stages_list.json | 37 ++++-- json/stage_history_list.json | 16 ++- opportunity_management/core.py | 7 +- opportunity_management/opportunity_core.py | 7 +- wwwroot/api/opportunities_create.dspy | 5 +- .../api/opportunity_predictions_create.dspy | 39 +++++++ .../api/opportunity_predictions_delete.dspy | 23 ++++ .../api/opportunity_predictions_update.dspy | 37 ++++++ .../api/opportunity_stage_history_create.dspy | 5 +- wwwroot/api/sales_stages_create.dspy | 5 +- 12 files changed, 246 insertions(+), 56 deletions(-) create mode 100644 wwwroot/api/opportunity_predictions_create.dspy create mode 100644 wwwroot/api/opportunity_predictions_delete.dspy create mode 100644 wwwroot/api/opportunity_predictions_update.dspy diff --git a/json/opportunities_list.json b/json/opportunities_list.json index 3347266..ac059ed 100644 --- a/json/opportunities_list.json +++ b/json/opportunities_list.json @@ -1,38 +1,79 @@ { - "tblname": "opportunities", - "alias": "opportunities_list", - "title": "商机列表", - "params": { - "sortby": ["created_at desc"], - "browserfields": { - "exclouded": ["id", "customer_id", "owner_id", "org_id"], - "alters": { - "sales_stage": { - "uitype": "code", - "data": [ - {"value": "初步接洽", "text": "初步接洽"}, - {"value": "需求确认", "text": "需求确认"}, - {"value": "方案报价", "text": "方案报价"}, - {"value": "合同谈判", "text": "合同谈判"}, - {"value": "成交", "text": "成交"} - ] + "tblname": "opportunities", + "alias": "opportunities_list", + "title": "商机列表", + "params": { + "sortby": [ + "created_at desc" + ], + "browserfields": { + "exclouded": [ + "id", + "customer_id", + "owner_id" + ], + "alters": { + "current_stage": { + "uitype": "code", + "data": [ + { + "value": "初步接洽", + "text": "初步接洽" + }, + { + "value": "需求确认", + "text": "需求确认" + }, + { + "value": "方案报价", + "text": "方案报价" + }, + { + "value": "合同谈判", + "text": "合同谈判" + }, + { + "value": "成交", + "text": "成交" + } + ] + }, + "status": { + "uitype": "code", + "data": [ + { + "value": "active", + "text": "活跃" + }, + { + "value": "won", + "text": "已成交" + }, + { + "value": "lost", + "text": "已丢失" + } + ] + }, + "source_type": { + "uitype": "code", + "data": [ + { + "value": "manual", + "text": "手动录入" + }, + { + "value": "lead_conversion", + "text": "线索转化" + } + ] + } + } }, - "status": { - "uitype": "code", - "data": [ - {"value": "active", "text": "活跃"}, - {"value": "won", "text": "已成交"}, - {"value": "lost", "text": "已丢失"} - ] - }, - "source": { - "uitype": "code", - "data": [ - {"value": "manual", "text": "手动录入"}, - {"value": "lead_conversion", "text": "线索转化"} - ] + "editable": { + "new_data_url": "{{entire_url('../api/opportunities_create.dspy')}}", + "update_data_url": "{{entire_url('../api/opportunities_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/opportunities_delete.dspy')}}" } - } } - } } \ No newline at end of file diff --git a/json/predictions_list.json b/json/predictions_list.json index c7c03e0..962c9ae 100644 --- a/json/predictions_list.json +++ b/json/predictions_list.json @@ -3,9 +3,19 @@ "alias": "predictions_list", "title": "商机预测记录", "params": { - "sortby": ["prediction_date desc"], + "sortby": [ + "prediction_date desc" + ], "browserfields": { - "exclouded": ["id", "opportunity_id"] + "exclouded": [ + "id", + "opportunity_id" + ] + }, + "editable": { + "new_data_url": "{{entire_url('../api/opportunity_predictions_create.dspy')}}", + "update_data_url": "{{entire_url('../api/opportunity_predictions_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/opportunity_predictions_delete.dspy')}}" } } } \ No newline at end of file diff --git a/json/sales_stages_list.json b/json/sales_stages_list.json index cb4157e..0b1b61a 100644 --- a/json/sales_stages_list.json +++ b/json/sales_stages_list.json @@ -3,24 +3,47 @@ "alias": "sales_stages_list", "title": "销售阶段管理", "params": { - "sortby": ["stage_order"], + "sortby": [ + "stage_order" + ], "browserfields": { - "exclouded": ["id", "updated_at"], + "exclouded": [ + "id", + "updated_at" + ], "alters": { - "is_active": { + "is_won_stage": { "uitype": "code", "data": [ { - "value": "1", - "text": "启用" + "value": "yes", + "text": "是" }, { - "value": "0", - "text": "禁用" + "value": "no", + "text": "否" + } + ] + }, + "is_lost_stage": { + "uitype": "code", + "data": [ + { + "value": "yes", + "text": "是" + }, + { + "value": "no", + "text": "否" } ] } } + }, + "editable": { + "new_data_url": "{{entire_url('../api/sales_stages_create.dspy')}}", + "update_data_url": "{{entire_url('../api/sales_stages_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/sales_stages_delete.dspy')}}" } } } \ No newline at end of file diff --git a/json/stage_history_list.json b/json/stage_history_list.json index 8ccf0a7..7b77eac 100644 --- a/json/stage_history_list.json +++ b/json/stage_history_list.json @@ -3,9 +3,21 @@ "alias": "stage_history_list", "title": "阶段变更历史", "params": { - "sortby": ["changed_at desc"], + "sortby": [ + "changed_at desc" + ], "browserfields": { - "exclouded": ["id", "opportunity_id", "changed_by"] + "exclouded": [ + "id", + "opportunity_id", + "changed_by_id", + "changed_by_name" + ] + }, + "editable": { + "new_data_url": "{{entire_url('../api/opportunity_stage_history_create.dspy')}}", + "update_data_url": "{{entire_url('../api/opportunity_stage_history_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/opportunity_stage_history_delete.dspy')}}" } } } \ No newline at end of file diff --git a/opportunity_management/core.py b/opportunity_management/core.py index fb45728..17ce86c 100644 --- a/opportunity_management/core.py +++ b/opportunity_management/core.py @@ -2,6 +2,7 @@ from datetime import datetime, date from decimal import Decimal from typing import List, Dict, Optional import uuid +from appPublic.uniqueID import getID from ahserver.serverenv import ServerEnv from appPublic.worker import awaitify @@ -30,7 +31,7 @@ async def create_opportunity( db.databases = config.databases async with db.sqlorContext(dbname) as sor: - opportunity_id = str(uuid.uuid4()).replace('-', '') + opportunity_id = getID() # 验证客户是否存在 customer_records = await sor.R("customers", {"filters": [{"field": "customer_name", "op": "=", "value": customer_name}]}) @@ -100,7 +101,7 @@ async def update_opportunity_stage( raise ValueError("只能更新活跃状态的商机") # 记录阶段变更历史 - history_id = str(uuid.uuid4()).replace('-', '') + history_id = getID() history_data = { "id": history_id, "opportunity_id": opportunity_id, @@ -160,7 +161,7 @@ async def assign_opportunity( old_owner_id = opportunity["owner_id"] # 记录分配历史 - assignment_id = str(uuid.uuid4()).replace('-', '') + assignment_id = getID() assignment_data = { "id": assignment_id, "opportunity_id": opportunity_id, diff --git a/opportunity_management/opportunity_core.py b/opportunity_management/opportunity_core.py index f3e48b8..cf4530a 100644 --- a/opportunity_management/opportunity_core.py +++ b/opportunity_management/opportunity_core.py @@ -6,6 +6,7 @@ from datetime import datetime, date from decimal import Decimal from typing import List, Dict, Optional import uuid +from appPublic.uniqueID import getID from ahserver.serverenv import ServerEnv from appPublic.worker import awaitify @@ -34,7 +35,7 @@ async def create_opportunity( db.databases = config.databases async with db.sqlorContext(dbname) as sor: - opportunity_id = str(uuid.uuid4()).replace('-', '') + opportunity_id = getID() # 验证客户是否存在 customer_records = await sor.R("customers", {"filters": [{"field": "customer_name", "op": "=", "value": customer_name}]}) @@ -104,7 +105,7 @@ async def update_opportunity_stage( raise ValueError("只能更新活跃状态的商机") # 记录阶段变更历史 - history_id = str(uuid.uuid4()).replace('-', '') + history_id = getID() history_data = { "id": history_id, "opportunity_id": opportunity_id, @@ -164,7 +165,7 @@ async def assign_opportunity( old_owner_id = opportunity["owner_id"] # 记录分配历史 - assignment_id = str(uuid.uuid4()).replace('-', '') + assignment_id = getID() assignment_data = { "id": assignment_id, "opportunity_id": opportunity_id, diff --git a/wwwroot/api/opportunities_create.dspy b/wwwroot/api/opportunities_create.dspy index 005e58d..a602a6c 100644 --- a/wwwroot/api/opportunities_create.dspy +++ b/wwwroot/api/opportunities_create.dspy @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Opportunity create API""" -import json, uuid, time +import json, time +from appPublic.uniqueID import getID result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} @@ -19,7 +20,7 @@ try: result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'} else: dbname = get_module_dbname('opportunity_management') - new_id = str(uuid.uuid4()).replace('-', '') + new_id = getID() now = time.strftime('%Y-%m-%d %H:%M:%S') async with DBPools().sqlorContext(dbname) as sor: diff --git a/wwwroot/api/opportunity_predictions_create.dspy b/wwwroot/api/opportunity_predictions_create.dspy new file mode 100644 index 0000000..5b39ab7 --- /dev/null +++ b/wwwroot/api/opportunity_predictions_create.dspy @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Opportunity predictions create API""" +import json, time +from appPublic.uniqueID import getID + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + opportunity_id = params_kw.get('opportunity_id', '').strip() + predicted_amount = params_kw.get('predicted_amount', '0').strip() + confidence_level = params_kw.get('confidence_level', '0').strip() + prediction_date = params_kw.get('prediction_date', '').strip() + + if not opportunity_id: + result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'} + else: + dbname = get_module_dbname('opportunity_management') + new_id = getID() + now = time.strftime('%Y-%m-%d %H:%M:%S') + if not prediction_date: + prediction_date = time.strftime('%Y-%m-%d') + + async with DBPools().sqlorContext(dbname) as sor: + await sor.sqlExe("""INSERT INTO opportunity_predictions (id, opportunity_id, predicted_amount, confidence_level, prediction_date, created_at) + VALUES (${id}$, ${opportunity_id}$, ${predicted_amount}$, ${confidence_level}$, ${prediction_date}$, ${created_at}$)""", { + 'id': new_id, + 'opportunity_id': opportunity_id, + 'predicted_amount': float(predicted_amount) if predicted_amount else 0.0, + 'confidence_level': float(confidence_level) if confidence_level else 0.0, + 'prediction_date': prediction_date, + 'created_at': now + }) + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '预测记录创建成功', 'type': 'success'}} + +except Exception as e: + result['options'] = {'title': 'Error', 'message': f'创建失败: {str(e)}', 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/opportunity_predictions_delete.dspy b/wwwroot/api/opportunity_predictions_delete.dspy new file mode 100644 index 0000000..f5749af --- /dev/null +++ b/wwwroot/api/opportunity_predictions_delete.dspy @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Opportunity predictions delete API""" +import json + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + row_id = params_kw.get('id', '').strip() + + if not row_id: + result['options'] = {'title': 'Error', 'message': '缺少记录ID', 'type': 'error'} + else: + dbname = get_module_dbname('opportunity_management') + + async with DBPools().sqlorContext(dbname) as sor: + await sor.sqlExe("DELETE FROM opportunity_predictions WHERE id=${id}$", {'id': row_id}) + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '预测记录删除成功', 'type': 'success'}} + +except Exception as e: + result['options'] = {'title': 'Error', 'message': f'删除失败: {str(e)}', 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/opportunity_predictions_update.dspy b/wwwroot/api/opportunity_predictions_update.dspy new file mode 100644 index 0000000..007016e --- /dev/null +++ b/wwwroot/api/opportunity_predictions_update.dspy @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Opportunity predictions update API""" +import json, time + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + row_id = params_kw.get('id', '').strip() + predicted_amount = params_kw.get('predicted_amount', '0').strip() + confidence_level = params_kw.get('confidence_level', '0').strip() + actual_amount = params_kw.get('actual_amount', '').strip() + deviation_rate = params_kw.get('deviation_rate', '').strip() + + if not row_id: + result['options'] = {'title': 'Error', 'message': '缺少记录ID', 'type': 'error'} + else: + dbname = get_module_dbname('opportunity_management') + now = time.strftime('%Y-%m-%d %H:%M:%S') + + async with DBPools().sqlorContext(dbname) as sor: + await sor.sqlExe("""UPDATE opportunity_predictions SET + predicted_amount=${predicted_amount}$, confidence_level=${confidence_level}$, + actual_amount=${actual_amount}$, deviation_rate=${deviation_rate}$ + WHERE id=${id}$""", { + 'id': row_id, + 'predicted_amount': float(predicted_amount) if predicted_amount else 0.0, + 'confidence_level': float(confidence_level) if confidence_level else 0.0, + 'actual_amount': float(actual_amount) if actual_amount else None, + 'deviation_rate': float(deviation_rate) if deviation_rate else None, + }) + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '预测记录更新成功', 'type': 'success'}} + +except Exception as e: + result['options'] = {'title': 'Error', 'message': f'更新失败: {str(e)}', 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/opportunity_stage_history_create.dspy b/wwwroot/api/opportunity_stage_history_create.dspy index 373fd87..d4a339a 100644 --- a/wwwroot/api/opportunity_stage_history_create.dspy +++ b/wwwroot/api/opportunity_stage_history_create.dspy @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Opportunity stage history create API""" -import json, uuid, time +import json, time +from appPublic.uniqueID import getID result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} @@ -16,7 +17,7 @@ try: result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'} else: dbname = get_module_dbname('opportunity_management') - new_id = str(uuid.uuid4()).replace('-', '') + new_id = getID() now = time.strftime('%Y-%m-%d %H:%M:%S') async with DBPools().sqlorContext(dbname) as sor: diff --git a/wwwroot/api/sales_stages_create.dspy b/wwwroot/api/sales_stages_create.dspy index 7df488d..6b8fcc8 100644 --- a/wwwroot/api/sales_stages_create.dspy +++ b/wwwroot/api/sales_stages_create.dspy @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """Sales stages create API""" -import json, uuid, time +import json, time +from appPublic.uniqueID import getID result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} @@ -16,7 +17,7 @@ try: result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'} else: dbname = get_module_dbname('opportunity_management') - new_id = str(uuid.uuid4()).replace('-', '') + new_id = getID() now = time.strftime('%Y-%m-%d %H:%M:%S') async with DBPools().sqlorContext(dbname) as sor: