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",
"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')}}"
}
}
}
}
}

View File

@ -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')}}"
}
}
}

View File

@ -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')}}"
}
}
}

View File

@ -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')}}"
}
}
}

View File

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

View File

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

View File

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

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
# -*- 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:

View File

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