fix: CRUD JSON compliance - delete non-CRUD files, add editable to missing files
- DELETED 3 non-CRUD format files: funnel_analysis.json, revenue_prediction.json, stage_change.json (these used custom name/title/type/components format instead of tblname/params CRUD spec) - ADDED editable paragraph to sales_stages.json (was missing new/update/delete data URLs) - ADDED editable paragraph to opportunity_stage_history.json (was missing new/update/delete data URLs) - CREATED 6 CRUD API files: sales_stages_create/update/delete.dspy, opportunity_stage_history_create/update/delete.dspy
This commit is contained in:
parent
635f71ea52
commit
fd0c9f4aeb
@ -1,82 +0,0 @@
|
||||
{
|
||||
"name": "funnel_analysis",
|
||||
"title": "销售漏斗分析",
|
||||
"type": "page",
|
||||
"components": [
|
||||
{
|
||||
"type": "container",
|
||||
"name": "filters",
|
||||
"title": "筛选条件",
|
||||
"components": [
|
||||
{
|
||||
"type": "select",
|
||||
"name": "region",
|
||||
"label": "区域",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "owner_id",
|
||||
"label": "销售负责人",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"type": "date_range",
|
||||
"name": "date_range",
|
||||
"label": "时间范围"
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"name": "search",
|
||||
"label": "查询",
|
||||
"action": "refresh_funnel_data"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "chart",
|
||||
"name": "funnel_chart",
|
||||
"title": "销售漏斗图",
|
||||
"chart_type": "funnel",
|
||||
"data_source": "funnel_analysis_api",
|
||||
"config": {
|
||||
"value_field": "total_amount",
|
||||
"category_field": "sales_stage",
|
||||
"show_percentage": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "chart",
|
||||
"name": "amount_pie",
|
||||
"title": "各阶段金额占比",
|
||||
"chart_type": "pie",
|
||||
"data_source": "funnel_analysis_api",
|
||||
"config": {
|
||||
"value_field": "total_amount",
|
||||
"category_field": "sales_stage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"name": "summary_stats",
|
||||
"title": "汇总统计",
|
||||
"components": [
|
||||
{
|
||||
"type": "statistic",
|
||||
"name": "total_opportunities",
|
||||
"label": "商机总数"
|
||||
},
|
||||
{
|
||||
"type": "statistic",
|
||||
"name": "total_amount",
|
||||
"label": "预估总金额"
|
||||
},
|
||||
{
|
||||
"type": "statistic",
|
||||
"name": "predicted_revenue",
|
||||
"label": "预测成交金额"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -2,13 +2,30 @@
|
||||
"tblname": "opportunity_stage_history",
|
||||
"title": "阶段变更历史",
|
||||
"params": {
|
||||
"sortby": ["changed_at desc"],
|
||||
"sortby": [
|
||||
"changed_at desc"
|
||||
],
|
||||
"logined_userid": "changed_by_id",
|
||||
"confidential_fields": [],
|
||||
"browserfields": {
|
||||
"exclouded": ["id", "opportunity_id", "changed_by_id", "changed_at"],
|
||||
"exclouded": [
|
||||
"id",
|
||||
"opportunity_id",
|
||||
"changed_by_id",
|
||||
"changed_at"
|
||||
],
|
||||
"alters": {}
|
||||
},
|
||||
"editexclouded": ["id", "opportunity_id", "changed_by_id", "changed_at"]
|
||||
"editexclouded": [
|
||||
"id",
|
||||
"opportunity_id",
|
||||
"changed_by_id",
|
||||
"changed_at"
|
||||
],
|
||||
"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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
{
|
||||
"name": "revenue_prediction",
|
||||
"title": "收入预测",
|
||||
"type": "page",
|
||||
"components": [
|
||||
{
|
||||
"type": "container",
|
||||
"name": "filters",
|
||||
"title": "预测条件",
|
||||
"components": [
|
||||
{
|
||||
"type": "select",
|
||||
"name": "owner_id",
|
||||
"label": "销售负责人",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "region",
|
||||
"label": "区域",
|
||||
"options": []
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "period",
|
||||
"label": "历史数据周期",
|
||||
"options": [
|
||||
{"value": "last_3_months", "text": "最近3个月"},
|
||||
{"value": "last_6_months", "text": "最近6个月"},
|
||||
{"value": "last_year", "text": "最近1年"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"name": "predict",
|
||||
"label": "生成预测",
|
||||
"action": "run_prediction"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "card",
|
||||
"name": "prediction_results",
|
||||
"title": "预测结果",
|
||||
"components": [
|
||||
{
|
||||
"type": "statistic",
|
||||
"name": "final_prediction",
|
||||
"label": "最终预测收入",
|
||||
"format": "currency"
|
||||
},
|
||||
{
|
||||
"type": "statistic",
|
||||
"name": "stage_based_prediction",
|
||||
"label": "阶段概率预测",
|
||||
"format": "currency"
|
||||
},
|
||||
{
|
||||
"type": "statistic",
|
||||
"name": "historical_based_prediction",
|
||||
"label": "历史转化率预测",
|
||||
"format": "currency"
|
||||
},
|
||||
{
|
||||
"type": "progress",
|
||||
"name": "confidence_level",
|
||||
"label": "预测置信度",
|
||||
"value_field": "confidence_level",
|
||||
"config": {
|
||||
"high": 90,
|
||||
"medium": 70,
|
||||
"low": 50
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "chart",
|
||||
"name": "historical_conversion",
|
||||
"title": "历史转化率趋势",
|
||||
"chart_type": "line",
|
||||
"data_source": "conversion_rate_analysis_api",
|
||||
"config": {
|
||||
"x_field": "month",
|
||||
"y_field": "conversion_rate",
|
||||
"show_markers": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "table",
|
||||
"name": "opportunity_details",
|
||||
"title": "商机明细",
|
||||
"data_source": "active_opportunities_api",
|
||||
"columns": [
|
||||
{"field": "customer_name", "title": "客户名称"},
|
||||
{"field": "estimated_amount", "title": "预估金额", "format": "currency"},
|
||||
{"field": "sales_stage", "title": "销售阶段"},
|
||||
{"field": "probability", "title": "成交概率", "format": "percentage"},
|
||||
{"field": "expected_close_date", "title": "预计成交时间"}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -2,27 +2,54 @@
|
||||
"tblname": "sales_stages",
|
||||
"title": "销售阶段配置",
|
||||
"params": {
|
||||
"sortby": ["stage_order asc"],
|
||||
"sortby": [
|
||||
"stage_order asc"
|
||||
],
|
||||
"confidential_fields": [],
|
||||
"browserfields": {
|
||||
"exclouded": ["id", "created_at", "updated_at"],
|
||||
"exclouded": [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
"alters": {
|
||||
"is_won_stage": {
|
||||
"uitype": "code",
|
||||
"data": [
|
||||
{"value": "yes", "text": "是"},
|
||||
{"value": "no", "text": "否"}
|
||||
{
|
||||
"value": "yes",
|
||||
"text": "是"
|
||||
},
|
||||
{
|
||||
"value": "no",
|
||||
"text": "否"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_lost_stage": {
|
||||
"uitype": "code",
|
||||
"data": [
|
||||
{"value": "yes", "text": "是"},
|
||||
{"value": "no", "text": "否"}
|
||||
{
|
||||
"value": "yes",
|
||||
"text": "是"
|
||||
},
|
||||
{
|
||||
"value": "no",
|
||||
"text": "否"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"editexclouded": ["id", "created_at", "updated_at"]
|
||||
"editexclouded": [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
"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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
{
|
||||
"name": "stage_change",
|
||||
"title": "商机阶段变更",
|
||||
"type": "form",
|
||||
"data_source": "opportunity_by_id_api",
|
||||
"components": [
|
||||
{
|
||||
"type": "display",
|
||||
"name": "customer_name",
|
||||
"label": "客户名称"
|
||||
},
|
||||
{
|
||||
"type": "display",
|
||||
"name": "current_stage",
|
||||
"label": "当前阶段"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"name": "new_stage",
|
||||
"label": "新阶段",
|
||||
"required": true,
|
||||
"options": [
|
||||
{"value": "初步接洽", "text": "初步接洽"},
|
||||
{"value": "需求确认", "text": "需求确认"},
|
||||
{"value": "方案报价", "text": "方案报价"},
|
||||
{"value": "合同谈判", "text": "合同谈判"},
|
||||
{"value": "成交", "text": "成交"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "textarea",
|
||||
"name": "change_reason",
|
||||
"label": "变更原因",
|
||||
"required": true,
|
||||
"placeholder": "请输入阶段变更的原因..."
|
||||
},
|
||||
{
|
||||
"type": "button",
|
||||
"name": "submit",
|
||||
"label": "确认变更",
|
||||
"action": "update_opportunity_stage"
|
||||
}
|
||||
]
|
||||
}
|
||||
38
wwwroot/api/opportunity_stage_history_create.dspy
Normal file
38
wwwroot/api/opportunity_stage_history_create.dspy
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Opportunity stage history create API"""
|
||||
import json, uuid, time
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||
|
||||
try:
|
||||
opportunity_id = params_kw.get('opportunity_id', '').strip()
|
||||
old_stage = params_kw.get('old_stage', '').strip()
|
||||
new_stage = params_kw.get('new_stage', '').strip()
|
||||
changed_by_id = params_kw.get('changed_by_id', '').strip()
|
||||
change_reason = params_kw.get('change_reason', '').strip()
|
||||
|
||||
if not opportunity_id or not new_stage:
|
||||
result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'}
|
||||
else:
|
||||
dbname = get_module_dbname('opportunity_management')
|
||||
new_id = str(uuid.uuid4()).replace('-', '')
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
await sor.sqlExe("""INSERT INTO opportunity_stage_history (id, opportunity_id, old_stage, new_stage, changed_by_id, change_reason, changed_at)
|
||||
VALUES (${id}$, ${opportunity_id}$, ${old_stage}$, ${new_stage}$, ${changed_by_id}$, ${change_reason}$, ${changed_at}$)""", {
|
||||
'id': new_id,
|
||||
'opportunity_id': opportunity_id,
|
||||
'old_stage': old_stage,
|
||||
'new_stage': new_stage,
|
||||
'changed_by_id': changed_by_id,
|
||||
'change_reason': change_reason,
|
||||
'changed_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)
|
||||
23
wwwroot/api/opportunity_stage_history_delete.dspy
Normal file
23
wwwroot/api/opportunity_stage_history_delete.dspy
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Opportunity stage history 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_stage_history 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)
|
||||
36
wwwroot/api/opportunity_stage_history_update.dspy
Normal file
36
wwwroot/api/opportunity_stage_history_update.dspy
Normal file
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Opportunity stage history update API"""
|
||||
import json, time
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||
|
||||
try:
|
||||
row_id = params_kw.get('id', '').strip()
|
||||
old_stage = params_kw.get('old_stage', '').strip()
|
||||
new_stage = params_kw.get('new_stage', '').strip()
|
||||
change_reason = params_kw.get('change_reason', '').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_stage_history SET
|
||||
old_stage=${old_stage}$, new_stage=${new_stage}$,
|
||||
change_reason=${change_reason}$, changed_at=${changed_at}$
|
||||
WHERE id=${id}$""", {
|
||||
'id': row_id,
|
||||
'old_stage': old_stage,
|
||||
'new_stage': new_stage,
|
||||
'change_reason': change_reason,
|
||||
'changed_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)
|
||||
38
wwwroot/api/sales_stages_create.dspy
Normal file
38
wwwroot/api/sales_stages_create.dspy
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Sales stages create API"""
|
||||
import json, uuid, time
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||
|
||||
try:
|
||||
stage_name = params_kw.get('stage_name', '').strip()
|
||||
stage_order = params_kw.get('stage_order', '0').strip()
|
||||
conversion_rate = params_kw.get('conversion_rate', '0').strip()
|
||||
is_won_stage = params_kw.get('is_won_stage', 'no').strip()
|
||||
is_lost_stage = params_kw.get('is_lost_stage', 'no').strip()
|
||||
|
||||
if not stage_name or not stage_order:
|
||||
result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'}
|
||||
else:
|
||||
dbname = get_module_dbname('opportunity_management')
|
||||
new_id = str(uuid.uuid4()).replace('-', '')
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
await sor.sqlExe("""INSERT INTO sales_stages (id, stage_name, stage_order, conversion_rate, is_won_stage, is_lost_stage, created_at, updated_at)
|
||||
VALUES (${id}$, ${stage_name}$, ${stage_order}$, ${conversion_rate}$, ${is_won_stage}$, ${is_lost_stage}$, ${created_at}$, ${updated_at}$)""", {
|
||||
'id': new_id,
|
||||
'stage_name': stage_name,
|
||||
'stage_order': int(stage_order),
|
||||
'conversion_rate': float(conversion_rate) if conversion_rate else 0.0,
|
||||
'is_won_stage': is_won_stage,
|
||||
'is_lost_stage': is_lost_stage,
|
||||
'created_at': now, 'updated_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)
|
||||
23
wwwroot/api/sales_stages_delete.dspy
Normal file
23
wwwroot/api/sales_stages_delete.dspy
Normal file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Sales stages 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 sales_stages 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)
|
||||
41
wwwroot/api/sales_stages_update.dspy
Normal file
41
wwwroot/api/sales_stages_update.dspy
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Sales stages update API"""
|
||||
import json, time
|
||||
|
||||
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||
|
||||
try:
|
||||
row_id = params_kw.get('id', '').strip()
|
||||
stage_name = params_kw.get('stage_name', '').strip()
|
||||
stage_order = params_kw.get('stage_order', '0').strip()
|
||||
conversion_rate = params_kw.get('conversion_rate', '0').strip()
|
||||
is_won_stage = params_kw.get('is_won_stage', 'no').strip()
|
||||
is_lost_stage = params_kw.get('is_lost_stage', 'no').strip()
|
||||
|
||||
if not row_id or not stage_name:
|
||||
result['options'] = {'title': 'Error', 'message': '缺少必要参数', '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 sales_stages SET
|
||||
stage_name=${stage_name}$, stage_order=${stage_order}$,
|
||||
conversion_rate=${conversion_rate}$, is_won_stage=${is_won_stage}$,
|
||||
is_lost_stage=${is_lost_stage}$, updated_at=${updated_at}$
|
||||
WHERE id=${id}$""", {
|
||||
'id': row_id,
|
||||
'stage_name': stage_name,
|
||||
'stage_order': int(stage_order),
|
||||
'conversion_rate': float(conversion_rate) if conversion_rate else 0.0,
|
||||
'is_won_stage': is_won_stage,
|
||||
'is_lost_stage': is_lost_stage,
|
||||
'updated_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)
|
||||
Loading…
x
Reference in New Issue
Block a user