fix: replace uuid.uuid4() with getID() from appPublic.uniqueID for all table ID generation

This commit is contained in:
yumoqing 2026-05-08 15:47:19 +08:00
parent 2e8630c5ab
commit 797ac1d935
12 changed files with 246 additions and 56 deletions

View File

@ -1,38 +1,79 @@
{ {
"tblname": "opportunities", "tblname": "opportunities",
"alias": "opportunities_list", "alias": "opportunities_list",
"title": "商机列表", "title": "商机列表",
"params": { "params": {
"sortby": ["created_at desc"], "sortby": [
"browserfields": { "created_at desc"
"exclouded": ["id", "customer_id", "owner_id", "org_id"], ],
"alters": { "browserfields": {
"sales_stage": { "exclouded": [
"uitype": "code", "id",
"data": [ "customer_id",
{"value": "初步接洽", "text": "初步接洽"}, "owner_id"
{"value": "需求确认", "text": "需求确认"}, ],
{"value": "方案报价", "text": "方案报价"}, "alters": {
{"value": "合同谈判", "text": "合同谈判"}, "current_stage": {
{"value": "成交", "text": "成交"} "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": { "editable": {
"uitype": "code", "new_data_url": "{{entire_url('../api/opportunities_create.dspy')}}",
"data": [ "update_data_url": "{{entire_url('../api/opportunities_update.dspy')}}",
{"value": "active", "text": "活跃"}, "delete_data_url": "{{entire_url('../api/opportunities_delete.dspy')}}"
{"value": "won", "text": "已成交"},
{"value": "lost", "text": "已丢失"}
]
},
"source": {
"uitype": "code",
"data": [
{"value": "manual", "text": "手动录入"},
{"value": "lead_conversion", "text": "线索转化"}
]
} }
}
} }
}
} }

View File

@ -3,9 +3,19 @@
"alias": "predictions_list", "alias": "predictions_list",
"title": "商机预测记录", "title": "商机预测记录",
"params": { "params": {
"sortby": ["prediction_date desc"], "sortby": [
"prediction_date desc"
],
"browserfields": { "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')}}"
} }
} }
} }

View File

@ -3,24 +3,47 @@
"alias": "sales_stages_list", "alias": "sales_stages_list",
"title": "销售阶段管理", "title": "销售阶段管理",
"params": { "params": {
"sortby": ["stage_order"], "sortby": [
"stage_order"
],
"browserfields": { "browserfields": {
"exclouded": ["id", "updated_at"], "exclouded": [
"id",
"updated_at"
],
"alters": { "alters": {
"is_active": { "is_won_stage": {
"uitype": "code", "uitype": "code",
"data": [ "data": [
{ {
"value": "1", "value": "yes",
"text": "启用" "text": ""
}, },
{ {
"value": "0", "value": "no",
"text": "禁用" "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')}}"
} }
} }
} }

View File

@ -3,9 +3,21 @@
"alias": "stage_history_list", "alias": "stage_history_list",
"title": "阶段变更历史", "title": "阶段变更历史",
"params": { "params": {
"sortby": ["changed_at desc"], "sortby": [
"changed_at desc"
],
"browserfields": { "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')}}"
} }
} }
} }

View File

@ -2,6 +2,7 @@ from datetime import datetime, date
from decimal import Decimal from decimal import Decimal
from typing import List, Dict, Optional from typing import List, Dict, Optional
import uuid import uuid
from appPublic.uniqueID import getID
from ahserver.serverenv import ServerEnv from ahserver.serverenv import ServerEnv
from appPublic.worker import awaitify from appPublic.worker import awaitify
@ -30,7 +31,7 @@ async def create_opportunity(
db.databases = config.databases db.databases = config.databases
async with db.sqlorContext(dbname) as sor: 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}]}) 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("只能更新活跃状态的商机") raise ValueError("只能更新活跃状态的商机")
# 记录阶段变更历史 # 记录阶段变更历史
history_id = str(uuid.uuid4()).replace('-', '') history_id = getID()
history_data = { history_data = {
"id": history_id, "id": history_id,
"opportunity_id": opportunity_id, "opportunity_id": opportunity_id,
@ -160,7 +161,7 @@ async def assign_opportunity(
old_owner_id = opportunity["owner_id"] old_owner_id = opportunity["owner_id"]
# 记录分配历史 # 记录分配历史
assignment_id = str(uuid.uuid4()).replace('-', '') assignment_id = getID()
assignment_data = { assignment_data = {
"id": assignment_id, "id": assignment_id,
"opportunity_id": opportunity_id, "opportunity_id": opportunity_id,

View File

@ -6,6 +6,7 @@ from datetime import datetime, date
from decimal import Decimal from decimal import Decimal
from typing import List, Dict, Optional from typing import List, Dict, Optional
import uuid import uuid
from appPublic.uniqueID import getID
from ahserver.serverenv import ServerEnv from ahserver.serverenv import ServerEnv
from appPublic.worker import awaitify from appPublic.worker import awaitify
@ -34,7 +35,7 @@ async def create_opportunity(
db.databases = config.databases db.databases = config.databases
async with db.sqlorContext(dbname) as sor: 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}]}) 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("只能更新活跃状态的商机") raise ValueError("只能更新活跃状态的商机")
# 记录阶段变更历史 # 记录阶段变更历史
history_id = str(uuid.uuid4()).replace('-', '') history_id = getID()
history_data = { history_data = {
"id": history_id, "id": history_id,
"opportunity_id": opportunity_id, "opportunity_id": opportunity_id,
@ -164,7 +165,7 @@ async def assign_opportunity(
old_owner_id = opportunity["owner_id"] old_owner_id = opportunity["owner_id"]
# 记录分配历史 # 记录分配历史
assignment_id = str(uuid.uuid4()).replace('-', '') assignment_id = getID()
assignment_data = { assignment_data = {
"id": assignment_id, "id": assignment_id,
"opportunity_id": opportunity_id, "opportunity_id": opportunity_id,

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Opportunity create API""" """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'}} result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
@ -19,7 +20,7 @@ try:
result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'} result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'}
else: else:
dbname = get_module_dbname('opportunity_management') dbname = get_module_dbname('opportunity_management')
new_id = str(uuid.uuid4()).replace('-', '') new_id = getID()
now = time.strftime('%Y-%m-%d %H:%M:%S') now = time.strftime('%Y-%m-%d %H:%M:%S')
async with DBPools().sqlorContext(dbname) as sor: async with DBPools().sqlorContext(dbname) as sor:

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Opportunity stage history create API""" """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'}} result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
@ -16,7 +17,7 @@ try:
result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'} result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'}
else: else:
dbname = get_module_dbname('opportunity_management') dbname = get_module_dbname('opportunity_management')
new_id = str(uuid.uuid4()).replace('-', '') new_id = getID()
now = time.strftime('%Y-%m-%d %H:%M:%S') now = time.strftime('%Y-%m-%d %H:%M:%S')
async with DBPools().sqlorContext(dbname) as sor: async with DBPools().sqlorContext(dbname) as sor:

View File

@ -1,7 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Sales stages create API""" """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'}} result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
@ -16,7 +17,7 @@ try:
result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'} result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'}
else: else:
dbname = get_module_dbname('opportunity_management') dbname = get_module_dbname('opportunity_management')
new_id = str(uuid.uuid4()).replace('-', '') new_id = getID()
now = time.strftime('%Y-%m-%d %H:%M:%S') now = time.strftime('%Y-%m-%d %H:%M:%S')
async with DBPools().sqlorContext(dbname) as sor: async with DBPools().sqlorContext(dbname) as sor: