sync: local modifications to contract_management
- Updated core modules: __init__, ai_config, ai_core, attachment, contract, enhanced_contract - Updated mysql.ddl.sql - Updated UI files: contract_detail, contract_edit, contract_list - Added new files: api/*.dspy, contract_list.dspy
This commit is contained in:
parent
3772f0f6fb
commit
a72637b2a3
@ -1,15 +1,10 @@
|
|||||||
"""
|
"""
|
||||||
合同管理模块 - 增强版
|
合同管理模块
|
||||||
支持从商机创建合同、自动生成里程碑和订单、AI智能审核等功能
|
支持合同全生命周期管理
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .enhanced_contract_core import (
|
from .contract_core import (
|
||||||
create_contract,
|
create_contract,
|
||||||
create_contract_from_opportunity,
|
|
||||||
create_contract_version,
|
|
||||||
create_manual_order,
|
|
||||||
update_contract_milestone_status,
|
|
||||||
ai_contract_analysis,
|
|
||||||
update_contract,
|
update_contract,
|
||||||
delete_contract,
|
delete_contract,
|
||||||
get_contract_by_id,
|
get_contract_by_id,
|
||||||
@ -17,18 +12,13 @@ from .enhanced_contract_core import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 版本信息
|
# 版本信息
|
||||||
__version__ = "2.0.0"
|
__version__ = "1.0.0"
|
||||||
__author__ = "Hermes AI Agent"
|
__author__ = "Hermes AI Agent"
|
||||||
__description__ = "Enhanced Contract Management Module with Opportunity Integration and AI Analysis"
|
__description__ = "Contract Management Module"
|
||||||
|
|
||||||
# 导出所有公共接口
|
# 导出所有公共接口
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'create_contract',
|
'create_contract',
|
||||||
'create_contract_from_opportunity',
|
|
||||||
'create_contract_version',
|
|
||||||
'create_manual_order',
|
|
||||||
'update_contract_milestone_status',
|
|
||||||
'ai_contract_analysis',
|
|
||||||
'update_contract',
|
'update_contract',
|
||||||
'delete_contract',
|
'delete_contract',
|
||||||
'get_contract_by_id',
|
'get_contract_by_id',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from sqlor.dbp import getDBP
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
async def save_ai_config(request_data: dict, org_id: str) -> dict:
|
async def save_ai_config(request_data: dict, org_id: str) -> dict:
|
||||||
"""保存AI配置"""
|
"""保存AI配置"""
|
||||||
@ -8,7 +8,7 @@ async def save_ai_config(request_data: dict, org_id: str) -> dict:
|
|||||||
|
|
||||||
# 检查是否已存在配置
|
# 检查是否已存在配置
|
||||||
check_sql = "SELECT id FROM contract_ai_config WHERE org_id = %(org_id)s"
|
check_sql = "SELECT id FROM contract_ai_config WHERE org_id = %(org_id)s"
|
||||||
check_result = await dbp.doQuery(check_sql, {'org_id': org_id})
|
check_result = await sor.doQuery(check_sql, {'org_id': org_id})
|
||||||
|
|
||||||
if check_result:
|
if check_result:
|
||||||
# 更新现有配置
|
# 更新现有配置
|
||||||
@ -26,7 +26,7 @@ async def save_ai_config(request_data: dict, org_id: str) -> dict:
|
|||||||
request_data['id'] = str(uuid.uuid4()).replace('-', '')
|
request_data['id'] = str(uuid.uuid4()).replace('-', '')
|
||||||
|
|
||||||
request_data['org_id'] = org_id
|
request_data['org_id'] = org_id
|
||||||
await dbp.doTransaction([{'sql': sql, 'params': request_data}])
|
await sor.doTransaction([{'sql': sql, 'params': request_data}])
|
||||||
return {'success': True, 'message': 'AI配置保存成功'}
|
return {'success': True, 'message': 'AI配置保存成功'}
|
||||||
|
|
||||||
# 在 init.py 中添加这个函数的暴露
|
# 在 init.py 中添加这个函数的暴露
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
from appPublic.jsonconfig import getConfig
|
from appPublic.Config import getConfig
|
||||||
|
|
||||||
class AIContractService:
|
class AIContractService:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@ -2,14 +2,14 @@ import os
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
from appPublic.jsonconfig import getConfig
|
from appPublic.Config import getConfig
|
||||||
from appPublic.worker import Worker
|
# Worker removed - not available
|
||||||
from sqlor.dbp import getDBP
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
class ContractAttachmentManager:
|
class ContractAttachmentManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = getConfig()
|
self.config = getConfig()
|
||||||
self.worker = Worker()
|
pass # Worker removed
|
||||||
self.upload_dir = self.config.get('contract_upload_dir', '/var/uploads/contracts')
|
self.upload_dir = self.config.get('contract_upload_dir', '/var/uploads/contracts')
|
||||||
|
|
||||||
async def get_db_connection(self, org_id: str):
|
async def get_db_connection(self, org_id: str):
|
||||||
@ -49,7 +49,7 @@ class ContractAttachmentManager:
|
|||||||
SELECT MAX(version) as max_version FROM contract_attachment
|
SELECT MAX(version) as max_version FROM contract_attachment
|
||||||
WHERE contract_id = %(contract_id)s AND file_name = %(file_name)s AND org_id = %(org_id)s
|
WHERE contract_id = %(contract_id)s AND file_name = %(file_name)s AND org_id = %(org_id)s
|
||||||
"""
|
"""
|
||||||
check_result = await dbp.doQuery(check_sql, {
|
check_result = await sor.doQuery(check_sql, {
|
||||||
'contract_id': contract_id,
|
'contract_id': contract_id,
|
||||||
'file_name': file_name,
|
'file_name': file_name,
|
||||||
'org_id': org_id
|
'org_id': org_id
|
||||||
@ -81,7 +81,7 @@ class ContractAttachmentManager:
|
|||||||
'org_id': org_id
|
'org_id': org_id
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbp.doTransaction([{'sql': sql, 'params': params}])
|
await sor.doTransaction([{'sql': sql, 'params': params}])
|
||||||
return attachment_id
|
return attachment_id
|
||||||
|
|
||||||
async def get_contract_attachments(self, contract_id: str, org_id: str) -> List[Dict]:
|
async def get_contract_attachments(self, contract_id: str, org_id: str) -> List[Dict]:
|
||||||
@ -92,7 +92,7 @@ class ContractAttachmentManager:
|
|||||||
WHERE contract_id = %(contract_id)s AND org_id = %(org_id)s
|
WHERE contract_id = %(contract_id)s AND org_id = %(org_id)s
|
||||||
ORDER BY version DESC, created_at DESC
|
ORDER BY version DESC, created_at DESC
|
||||||
"""
|
"""
|
||||||
result = await dbp.doQuery(sql, {'contract_id': contract_id, 'org_id': org_id})
|
result = await sor.doQuery(sql, {'contract_id': contract_id, 'org_id': org_id})
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 全局实例
|
# 全局实例
|
||||||
|
|||||||
@ -3,14 +3,12 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
from appPublic.jsonconfig import getConfig
|
from appPublic.Config import getConfig
|
||||||
from appPublic.worker import Worker
|
from sqlor.dbpools import DBPools
|
||||||
from sqlor.dbp import getDBP
|
|
||||||
|
|
||||||
class ContractManager:
|
class ContractManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = getConfig()
|
self.config = getConfig()
|
||||||
self.worker = Worker()
|
|
||||||
|
|
||||||
async def get_db_connection(self, org_id: str):
|
async def get_db_connection(self, org_id: str):
|
||||||
"""获取数据库连接"""
|
"""获取数据库连接"""
|
||||||
@ -52,7 +50,7 @@ class ContractManager:
|
|||||||
'org_id': org_id
|
'org_id': org_id
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbp.doTransaction([{'sql': sql, 'params': params}])
|
await sor.doTransaction([{'sql': sql, 'params': params}])
|
||||||
return contract_id
|
return contract_id
|
||||||
|
|
||||||
async def update_contract(self, contract_id: str, contract_data: Dict, org_id: str) -> bool:
|
async def update_contract(self, contract_id: str, contract_data: Dict, org_id: str) -> bool:
|
||||||
@ -72,21 +70,21 @@ class ContractManager:
|
|||||||
update_fields.append("updated_at = NOW()")
|
update_fields.append("updated_at = NOW()")
|
||||||
sql = f"UPDATE contract SET {', '.join(update_fields)} WHERE id = %(id)s AND org_id = %(org_id)s"
|
sql = f"UPDATE contract SET {', '.join(update_fields)} WHERE id = %(id)s AND org_id = %(org_id)s"
|
||||||
|
|
||||||
result = await dbp.doTransaction([{'sql': sql, 'params': params}])
|
result = await sor.doTransaction([{'sql': sql, 'params': params}])
|
||||||
return result.rowcount > 0
|
return result.rowcount > 0
|
||||||
|
|
||||||
async def delete_contract(self, contract_id: str, org_id: str) -> bool:
|
async def delete_contract(self, contract_id: str, org_id: str) -> bool:
|
||||||
"""删除合同(软删除)"""
|
"""删除合同(软删除)"""
|
||||||
dbp = await self.get_db_connection(org_id)
|
dbp = await self.get_db_connection(org_id)
|
||||||
sql = "UPDATE contract SET status = 'deleted', updated_at = NOW() WHERE id = %(id)s AND org_id = %(org_id)s"
|
sql = "UPDATE contract SET status = 'deleted', updated_at = NOW() WHERE id = %(id)s AND org_id = %(org_id)s"
|
||||||
result = await dbp.doTransaction([{'sql': sql, 'params': {'id': contract_id, 'org_id': org_id}}])
|
result = await sor.doTransaction([{'sql': sql, 'params': {'id': contract_id, 'org_id': org_id}}])
|
||||||
return result.rowcount > 0
|
return result.rowcount > 0
|
||||||
|
|
||||||
async def get_contract_by_id(self, contract_id: str, org_id: str) -> Optional[Dict]:
|
async def get_contract_by_id(self, contract_id: str, org_id: str) -> Optional[Dict]:
|
||||||
"""根据ID获取合同"""
|
"""根据ID获取合同"""
|
||||||
dbp = await self.get_db_connection(org_id)
|
dbp = await self.get_db_connection(org_id)
|
||||||
sql = "SELECT * FROM contract WHERE id = %(id)s AND org_id = %(org_id)s AND status != 'deleted'"
|
sql = "SELECT * FROM contract WHERE id = %(id)s AND org_id = %(org_id)s AND status != 'deleted'"
|
||||||
result = await dbp.doQuery(sql, {'id': contract_id, 'org_id': org_id})
|
result = await sor.doQuery(sql, {'id': contract_id, 'org_id': org_id})
|
||||||
return result[0] if result else None
|
return result[0] if result else None
|
||||||
|
|
||||||
async def list_contracts(self, org_id: str, filters: Optional[Dict] = None,
|
async def list_contracts(self, org_id: str, filters: Optional[Dict] = None,
|
||||||
@ -125,7 +123,7 @@ class ContractManager:
|
|||||||
|
|
||||||
# 获取总数
|
# 获取总数
|
||||||
count_sql = f"SELECT COUNT(*) as total FROM contract WHERE {where_sql}"
|
count_sql = f"SELECT COUNT(*) as total FROM contract WHERE {where_sql}"
|
||||||
count_result = await dbp.doQuery(count_sql, params)
|
count_result = await sor.doQuery(count_sql, params)
|
||||||
total = count_result[0]['total'] if count_result else 0
|
total = count_result[0]['total'] if count_result else 0
|
||||||
|
|
||||||
# 获取分页数据
|
# 获取分页数据
|
||||||
@ -139,7 +137,7 @@ class ContractManager:
|
|||||||
params['limit'] = page_size
|
params['limit'] = page_size
|
||||||
params['offset'] = offset
|
params['offset'] = offset
|
||||||
|
|
||||||
data_result = await dbp.doQuery(data_sql, params)
|
data_result = await sor.doQuery(data_sql, params)
|
||||||
return data_result, total
|
return data_result, total
|
||||||
|
|
||||||
# 全局实例
|
# 全局实例
|
||||||
|
|||||||
@ -3,15 +3,14 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime, date, timedelta
|
||||||
from typing import List, Dict, Optional, Tuple
|
from typing import List, Dict, Optional, Tuple
|
||||||
from appPublic.jsonconfig import getConfig
|
from appPublic.Config import getConfig
|
||||||
from appPublic.worker import Worker
|
from appPublic.worker import awaitify
|
||||||
from sqlor.dbp import getDBP
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
|
||||||
class EnhancedContractManager:
|
class EnhancedContractManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.config = getConfig()
|
self.config = getConfig()
|
||||||
self.worker = Worker()
|
|
||||||
|
|
||||||
async def get_db_connection(self, org_id: str):
|
async def get_db_connection(self, org_id: str):
|
||||||
"""获取数据库连接"""
|
"""获取数据库连接"""
|
||||||
@ -22,7 +21,7 @@ class EnhancedContractManager:
|
|||||||
"""从商机创建合同"""
|
"""从商机创建合同"""
|
||||||
# 获取商机信息
|
# 获取商机信息
|
||||||
opp_dbp = await getDBP(org_id)
|
opp_dbp = await getDBP(org_id)
|
||||||
opportunity = await opp_dbp.select_one("opportunities", {"id": opportunity_id})
|
opportunity = await opp_sor.select_one("opportunities", {"id": opportunity_id})
|
||||||
if not opportunity:
|
if not opportunity:
|
||||||
raise ValueError("商机不存在")
|
raise ValueError("商机不存在")
|
||||||
|
|
||||||
@ -90,7 +89,7 @@ class EnhancedContractManager:
|
|||||||
'tax_rate': contract_data.get('tax_rate', '0.1300')
|
'tax_rate': contract_data.get('tax_rate', '0.1300')
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbp.doTransaction([{'sql': sql, 'params': params}])
|
await sor.doTransaction([{'sql': sql, 'params': params}])
|
||||||
|
|
||||||
# 创建初始版本
|
# 创建初始版本
|
||||||
await self.create_contract_version(contract_id, contract_data.get('content', ''), user_id, org_id, 'v1.0', '初始版本')
|
await self.create_contract_version(contract_id, contract_data.get('content', ''), user_id, org_id, 'v1.0', '初始版本')
|
||||||
@ -104,7 +103,7 @@ class EnhancedContractManager:
|
|||||||
dbp = await self.get_db_connection(org_id)
|
dbp = await self.get_db_connection(org_id)
|
||||||
|
|
||||||
# 将之前的当前版本标记为非当前
|
# 将之前的当前版本标记为非当前
|
||||||
await dbp.update(
|
await sor.update(
|
||||||
"contract_versions",
|
"contract_versions",
|
||||||
{"is_current": "0"},
|
{"is_current": "0"},
|
||||||
{"contract_id": contract_id, "is_current": "1"}
|
{"contract_id": contract_id, "is_current": "1"}
|
||||||
@ -123,7 +122,7 @@ class EnhancedContractManager:
|
|||||||
"is_current": "1"
|
"is_current": "1"
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbp.insert("contract_versions", version_data)
|
await sor.insert("contract_versions", version_data)
|
||||||
return version_id
|
return version_id
|
||||||
|
|
||||||
async def generate_milestones_from_payment_terms(self, contract_id: str, payment_terms: str, org_id: str):
|
async def generate_milestones_from_payment_terms(self, contract_id: str, payment_terms: str, org_id: str):
|
||||||
@ -173,7 +172,7 @@ class EnhancedContractManager:
|
|||||||
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
}
|
}
|
||||||
dbp = await self.get_db_connection(org_id)
|
dbp = await self.get_db_connection(org_id)
|
||||||
await dbp.insert("contract_milestones", milestone_data)
|
await sor.insert("contract_milestones", milestone_data)
|
||||||
|
|
||||||
async def generate_orders_from_payment_terms(self, contract_id: str, contract_data: Dict, org_id: str):
|
async def generate_orders_from_payment_terms(self, contract_id: str, contract_data: Dict, org_id: str):
|
||||||
"""根据付款条款自动生成订单"""
|
"""根据付款条款自动生成订单"""
|
||||||
@ -221,7 +220,7 @@ class EnhancedContractManager:
|
|||||||
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
}
|
}
|
||||||
dbp = await self.get_db_connection(org_id)
|
dbp = await self.get_db_connection(org_id)
|
||||||
await dbp.insert("orders", order_data)
|
await sor.insert("orders", order_data)
|
||||||
|
|
||||||
async def create_manual_order(self, contract_id: str, order_data: Dict, user_id: str, org_id: str) -> str:
|
async def create_manual_order(self, contract_id: str, order_data: Dict, user_id: str, org_id: str) -> str:
|
||||||
"""手动创建订单"""
|
"""手动创建订单"""
|
||||||
@ -232,7 +231,7 @@ class EnhancedContractManager:
|
|||||||
|
|
||||||
# 计算已收款金额
|
# 计算已收款金额
|
||||||
dbp = await self.get_db_connection(org_id)
|
dbp = await self.get_db_connection(org_id)
|
||||||
paid_orders = await dbp.query(
|
paid_orders = await sor.query(
|
||||||
"SELECT SUM(amount) as total_paid FROM orders WHERE contract_id = %(contract_id)s AND status != 'cancelled'",
|
"SELECT SUM(amount) as total_paid FROM orders WHERE contract_id = %(contract_id)s AND status != 'cancelled'",
|
||||||
{"contract_id": contract_id}
|
{"contract_id": contract_id}
|
||||||
)
|
)
|
||||||
@ -264,7 +263,7 @@ class EnhancedContractManager:
|
|||||||
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
}
|
}
|
||||||
|
|
||||||
await dbp.insert("orders", final_order_data)
|
await sor.insert("orders", final_order_data)
|
||||||
return order_id
|
return order_id
|
||||||
|
|
||||||
async def update_contract_milestone_status(self, milestone_id: str, status: str, actual_date: str = None, org_id: str = None):
|
async def update_contract_milestone_status(self, milestone_id: str, status: str, actual_date: str = None, org_id: str = None):
|
||||||
@ -277,7 +276,7 @@ class EnhancedContractManager:
|
|||||||
if actual_date:
|
if actual_date:
|
||||||
update_data["actual_date"] = actual_date
|
update_data["actual_date"] = actual_date
|
||||||
|
|
||||||
await dbp.update("contract_milestones", update_data, {"id": milestone_id})
|
await sor.update("contract_milestones", update_data, {"id": milestone_id})
|
||||||
|
|
||||||
# 如果里程碑完成,更新关联的合同履约进度
|
# 如果里程碑完成,更新关联的合同履约进度
|
||||||
if status == "completed":
|
if status == "completed":
|
||||||
@ -286,13 +285,13 @@ class EnhancedContractManager:
|
|||||||
async def update_contract_fulfillment_progress(self, milestone_id: str, org_id: str):
|
async def update_contract_fulfillment_progress(self, milestone_id: str, org_id: str):
|
||||||
"""更新合同履约进度"""
|
"""更新合同履约进度"""
|
||||||
dbp = await self.get_db_connection(org_id)
|
dbp = await self.get_db_connection(org_id)
|
||||||
milestone = await dbp.select_one("contract_milestones", {"id": milestone_id})
|
milestone = await sor.select_one("contract_milestones", {"id": milestone_id})
|
||||||
if not milestone:
|
if not milestone:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 计算履约进度(简化逻辑)
|
# 计算履约进度(简化逻辑)
|
||||||
contract_id = milestone["contract_id"]
|
contract_id = milestone["contract_id"]
|
||||||
all_milestones = await dbp.query(
|
all_milestones = await sor.query(
|
||||||
"SELECT COUNT(*) as total, SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed FROM contract_milestones WHERE contract_id = %(contract_id)s",
|
"SELECT COUNT(*) as total, SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed FROM contract_milestones WHERE contract_id = %(contract_id)s",
|
||||||
{"contract_id": contract_id}
|
{"contract_id": contract_id}
|
||||||
)
|
)
|
||||||
@ -303,12 +302,12 @@ class EnhancedContractManager:
|
|||||||
progress = (completed / total * 100) if total > 0 else 0
|
progress = (completed / total * 100) if total > 0 else 0
|
||||||
|
|
||||||
# 更新商机阶段(如果存在关联商机)
|
# 更新商机阶段(如果存在关联商机)
|
||||||
contract = await dbp.select_one("contract", {"id": contract_id})
|
contract = await sor.select_one("contract", {"id": contract_id})
|
||||||
if contract and contract.get("opportunity_id"):
|
if contract and contract.get("opportunity_id"):
|
||||||
# 这里应该调用商机管理模块的API来更新阶段
|
# 这里应该调用商机管理模块的API来更新阶段
|
||||||
# 模拟实现:当进度达到100%时,将商机状态更新为成交
|
# 模拟实现:当进度达到100%时,将商机状态更新为成交
|
||||||
if progress >= 100:
|
if progress >= 100:
|
||||||
await dbp.update(
|
await sor.update(
|
||||||
"opportunities",
|
"opportunities",
|
||||||
{"status": "won", "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")},
|
{"status": "won", "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")},
|
||||||
{"id": contract["opportunity_id"]}
|
{"id": contract["opportunity_id"]}
|
||||||
@ -338,7 +337,7 @@ class EnhancedContractManager:
|
|||||||
ORDER BY cm.planned_date ASC
|
ORDER BY cm.planned_date ASC
|
||||||
"""
|
"""
|
||||||
|
|
||||||
overdue_milestones = await dbp.doQuery(sql, {"org_id": org_id})
|
overdue_milestones = await sor.doQuery(sql, {"org_id": org_id})
|
||||||
return overdue_milestones
|
return overdue_milestones
|
||||||
|
|
||||||
async def send_milestone_reminders(self, org_id: str) -> int:
|
async def send_milestone_reminders(self, org_id: str) -> int:
|
||||||
@ -498,202 +497,3 @@ class EnhancedContractManager:
|
|||||||
|
|
||||||
for pattern in patterns:
|
for pattern in patterns:
|
||||||
match = re.search(pattern, content)
|
match = re.search(pattern, content)
|
||||||
if match:
|
|
||||||
return f"{match.group(1)}%"
|
|
||||||
|
|
||||||
return "未明确"
|
|
||||||
|
|
||||||
def _extract_key_dates(self, content: str) -> Dict:
|
|
||||||
"""提取关键日期"""
|
|
||||||
import re
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
key_dates = {}
|
|
||||||
|
|
||||||
# 提取合同开始日期
|
|
||||||
start_patterns = [r'合同生效日期[::]?\s*(\d{4}[-年]\d{1,2}[-月]\d{1,2})', r'开始日期[::]?\s*(\d{4}[-年]\d{1,2}[-月]\d{1,2})']
|
|
||||||
for pattern in start_patterns:
|
|
||||||
match = re.search(pattern, content)
|
|
||||||
if match:
|
|
||||||
date_str = match.group(1).replace('年', '-').replace('月', '-').replace('日', '')
|
|
||||||
try:
|
|
||||||
key_dates["start_date"] = {"type": "start_date", "date": date_str, "description": "合同开始日期"}
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 提取合同结束日期
|
|
||||||
end_patterns = [r'合同终止日期[::]?\s*(\d{4}[-年]\d{1,2}[-月]\d{1,2})', r'结束日期[::]?\s*(\d{4}[-年]\d{1,2}[-月]\d{1,2})']
|
|
||||||
for pattern in end_patterns:
|
|
||||||
match = re.search(pattern, content)
|
|
||||||
if match:
|
|
||||||
date_str = match.group(1).replace('年', '-').replace('月', '-').replace('日', '')
|
|
||||||
try:
|
|
||||||
key_dates["end_date"] = {"type": "end_date", "date": date_str, "description": "合同结束日期"}
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 提取签署日期
|
|
||||||
sign_patterns = [r'签署日期[::]?\s*(\d{4}[-年]\d{1,2}[-月]\d{1,2})', r'签订日期[::]?\s*(\d{4}[-年]\d{1,2}[-月]\d{1,2})']
|
|
||||||
for pattern in sign_patterns:
|
|
||||||
match = re.search(pattern, content)
|
|
||||||
if match:
|
|
||||||
date_str = match.group(1).replace('年', '-').replace('月', '-').replace('日', '')
|
|
||||||
try:
|
|
||||||
key_dates["sign_date"] = {"type": "sign_date", "date": date_str, "description": "合同签署日期"}
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return key_dates
|
|
||||||
|
|
||||||
def _is_long_credit_period(self, credit_period: str) -> bool:
|
|
||||||
"""判断是否为超长账期(>90天)"""
|
|
||||||
import re
|
|
||||||
if credit_period == "未明确":
|
|
||||||
return False
|
|
||||||
|
|
||||||
match = re.search(r'(\d+)', credit_period)
|
|
||||||
if match:
|
|
||||||
days = int(match.group(1))
|
|
||||||
return days > 90
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _is_abnormal_penalty(self, penalty_clause: str) -> bool:
|
|
||||||
"""判断是否为异常违约金条款"""
|
|
||||||
import re
|
|
||||||
if penalty_clause == "未明确":
|
|
||||||
return False
|
|
||||||
|
|
||||||
match = re.search(r'(\d+)', penalty_clause)
|
|
||||||
if match:
|
|
||||||
percentage = int(match.group(1))
|
|
||||||
return percentage > 30 # 超过30%视为异常
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 保留原有的合同管理方法
|
|
||||||
async def update_contract(self, contract_id: str, contract_data: Dict, org_id: str) -> bool:
|
|
||||||
"""更新合同"""
|
|
||||||
dbp = await self.get_db_connection(org_id)
|
|
||||||
|
|
||||||
# 构建更新SQL
|
|
||||||
update_fields = []
|
|
||||||
params = {'id': contract_id, 'org_id': org_id}
|
|
||||||
|
|
||||||
for field, value in contract_data.items():
|
|
||||||
if field in ['contract_number', 'title', 'party_a', 'party_b', 'contract_type',
|
|
||||||
'status', 'amount', 'start_date', 'end_date', 'sign_date', 'description',
|
|
||||||
'payment_terms', 'credit_period', 'penalty_clause', 'tax_rate']:
|
|
||||||
update_fields.append(f"{field} = %({field})s")
|
|
||||||
params[field] = value
|
|
||||||
|
|
||||||
update_fields.append("updated_at = NOW()")
|
|
||||||
sql = f"UPDATE contract SET {', '.join(update_fields)} WHERE id = %(id)s AND org_id = %(org_id)s"
|
|
||||||
|
|
||||||
result = await dbp.doTransaction([{'sql': sql, 'params': params}])
|
|
||||||
return result.rowcount > 0
|
|
||||||
|
|
||||||
async def delete_contract(self, contract_id: str, org_id: str) -> bool:
|
|
||||||
"""删除合同(软删除)"""
|
|
||||||
dbp = await self.get_db_connection(org_id)
|
|
||||||
sql = "UPDATE contract SET status = 'deleted', updated_at = NOW() WHERE id = %(id)s AND org_id = %(org_id)s"
|
|
||||||
result = await dbp.doTransaction([{'sql': sql, 'params': {'id': contract_id, 'org_id': org_id}}])
|
|
||||||
return result.rowcount > 0
|
|
||||||
|
|
||||||
async def get_contract_by_id(self, contract_id: str, org_id: str) -> Optional[Dict]:
|
|
||||||
"""根据ID获取合同"""
|
|
||||||
dbp = await self.get_db_connection(org_id)
|
|
||||||
sql = "SELECT * FROM contract WHERE id = %(id)s AND org_id = %(org_id)s AND status != 'deleted'"
|
|
||||||
result = await dbp.doQuery(sql, {'id': contract_id, 'org_id': org_id})
|
|
||||||
return result[0] if result else None
|
|
||||||
|
|
||||||
async def list_contracts(self, org_id: str, filters: Optional[Dict] = None,
|
|
||||||
page: int = 1, page_size: int = 20) -> Tuple[List[Dict], int]:
|
|
||||||
"""列出合同"""
|
|
||||||
dbp = await self.get_db_connection(org_id)
|
|
||||||
|
|
||||||
# 构建查询条件
|
|
||||||
where_clauses = ["org_id = %(org_id)s", "status != 'deleted'"]
|
|
||||||
params = {'org_id': org_id}
|
|
||||||
|
|
||||||
if filters:
|
|
||||||
if filters.get('contract_number'):
|
|
||||||
where_clauses.append("contract_number LIKE %(contract_number)s")
|
|
||||||
params['contract_number'] = f"%{filters['contract_number']}%"
|
|
||||||
if filters.get('title'):
|
|
||||||
where_clauses.append("title LIKE %(title)s")
|
|
||||||
params['title'] = f"%{filters['title']}%"
|
|
||||||
if filters.get('party_b'):
|
|
||||||
where_clauses.append("party_b LIKE %(party_b)s")
|
|
||||||
params['party_b'] = f"%{filters['party_b']}%"
|
|
||||||
if filters.get('status'):
|
|
||||||
where_clauses.append("status = %(status)s")
|
|
||||||
params['status'] = filters['status']
|
|
||||||
if filters.get('contract_type'):
|
|
||||||
where_clauses.append("contract_type = %(contract_type)s")
|
|
||||||
params['contract_type'] = filters['contract_type']
|
|
||||||
if filters.get('start_date_from'):
|
|
||||||
where_clauses.append("start_date >= %(start_date_from)s")
|
|
||||||
params['start_date_from'] = filters['start_date_from']
|
|
||||||
if filters.get('end_date_to'):
|
|
||||||
where_clauses.append("end_date <= %(end_date_to)s")
|
|
||||||
params['end_date_to'] = filters['end_date_to']
|
|
||||||
|
|
||||||
where_sql = " AND ".join(where_clauses)
|
|
||||||
|
|
||||||
# 获取总数
|
|
||||||
count_sql = f"SELECT COUNT(*) as total FROM contract WHERE {where_sql}"
|
|
||||||
count_result = await dbp.doQuery(count_sql, params)
|
|
||||||
total = count_result[0]['total'] if count_result else 0
|
|
||||||
|
|
||||||
# 获取分页数据
|
|
||||||
offset = (page - 1) * page_size
|
|
||||||
data_sql = f"""
|
|
||||||
SELECT * FROM contract
|
|
||||||
WHERE {where_sql}
|
|
||||||
ORDER BY created_at DESC
|
|
||||||
LIMIT %(limit)s OFFSET %(offset)s
|
|
||||||
"""
|
|
||||||
params['limit'] = page_size
|
|
||||||
params['offset'] = offset
|
|
||||||
|
|
||||||
data_result = await dbp.doQuery(data_sql, params)
|
|
||||||
return data_result, total
|
|
||||||
|
|
||||||
|
|
||||||
# 全局实例
|
|
||||||
enhanced_contract_manager = EnhancedContractManager()
|
|
||||||
|
|
||||||
# 导出函数
|
|
||||||
async def create_contract_from_opportunity(opportunity_id: str, contract_data: Dict, user_id: str, org_id: str) -> str:
|
|
||||||
return await enhanced_contract_manager.create_contract_from_opportunity(opportunity_id, contract_data, user_id, org_id)
|
|
||||||
|
|
||||||
async def create_contract(contract_data: Dict, user_id: str, org_id: str) -> str:
|
|
||||||
return await enhanced_contract_manager.create_contract(contract_data, user_id, org_id)
|
|
||||||
|
|
||||||
async def create_contract_version(contract_id: str, content: str, user_id: str, org_id: str,
|
|
||||||
version_number: str, modified_reason: str = '') -> str:
|
|
||||||
return await enhanced_contract_manager.create_contract_version(contract_id, content, user_id, org_id, version_number, modified_reason)
|
|
||||||
|
|
||||||
async def create_manual_order(contract_id: str, order_data: Dict, user_id: str, org_id: str) -> str:
|
|
||||||
return await enhanced_contract_manager.create_manual_order(contract_id, order_data, user_id, org_id)
|
|
||||||
|
|
||||||
async def update_contract_milestone_status(milestone_id: str, status: str, actual_date: str = None, org_id: str = None):
|
|
||||||
return await enhanced_contract_manager.update_contract_milestone_status(milestone_id, status, actual_date, org_id)
|
|
||||||
|
|
||||||
async def ai_contract_analysis(contract_content: str, org_id: str) -> Dict:
|
|
||||||
return await enhanced_contract_manager.ai_contract_analysis(contract_content, org_id)
|
|
||||||
|
|
||||||
# 保留原有接口
|
|
||||||
async def update_contract(contract_id: str, contract_data: Dict, org_id: str) -> bool:
|
|
||||||
return await enhanced_contract_manager.update_contract(contract_id, contract_data, org_id)
|
|
||||||
|
|
||||||
async def delete_contract(contract_id: str, org_id: str) -> bool:
|
|
||||||
return await enhanced_contract_manager.delete_contract(contract_id, org_id)
|
|
||||||
|
|
||||||
async def get_contract_by_id(contract_id: str, org_id: str) -> Optional[Dict]:
|
|
||||||
return await enhanced_contract_manager.get_contract_by_id(contract_id, org_id)
|
|
||||||
|
|
||||||
async def list_contracts(org_id: str, filters: Optional[Dict] = None,
|
|
||||||
page: int = 1, page_size: int = 20) -> Tuple[List[Dict], int]:
|
|
||||||
return await enhanced_contract_manager.list_contracts(org_id, filters, page, page_size)
|
|
||||||
151
mysql.ddl.sql
151
mysql.ddl.sql
@ -0,0 +1,151 @@
|
|||||||
|
-- Table from contract.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `contract` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '合同ID,主键',
|
||||||
|
`contract_number` VARCHAR(50) NOT NULL COMMENT '合同编号,唯一标识',
|
||||||
|
`title` VARCHAR(200) NOT NULL COMMENT '合同标题',
|
||||||
|
`party_a` VARCHAR(100) NOT NULL COMMENT '甲方(我方)',
|
||||||
|
`party_b` VARCHAR(100) NOT NULL COMMENT '乙方(对方)',
|
||||||
|
`contract_type` VARCHAR(32) NOT NULL COMMENT '合同类型,引用appcodes表的id',
|
||||||
|
`status` VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT '合同状态:draft-草稿, active-生效, expired-过期, terminated-终止',
|
||||||
|
`amount` DECIMAL(15,2) COMMENT '合同金额',
|
||||||
|
`start_date` DATE NOT NULL COMMENT '合同开始日期',
|
||||||
|
`end_date` DATE NOT NULL COMMENT '合同结束日期',
|
||||||
|
`sign_date` DATE COMMENT '签署日期',
|
||||||
|
`owner_id` VARCHAR(32) NOT NULL COMMENT '合同负责人,引用users表的id',
|
||||||
|
`org_id` VARCHAR(32) NOT NULL COMMENT '所属组织,引用organization表的id',
|
||||||
|
`ai_compliance_result` TEXT COMMENT 'AI合规检查结果',
|
||||||
|
`ai_key_dates` TEXT COMMENT 'AI提取的关键时点JSON数据',
|
||||||
|
`payment_terms` TEXT COMMENT '付款节点规则,如\'30%预付款+50%进度款+20%尾款\'',
|
||||||
|
`credit_period` INT COMMENT '账期天数',
|
||||||
|
`penalty_clause` TEXT COMMENT '违约金相关条款',
|
||||||
|
`opportunity_id` VARCHAR(32) COMMENT '关联的商机ID',
|
||||||
|
`customer_id` VARCHAR(32) COMMENT '关联的客户ID',
|
||||||
|
`tax_rate` DECIMAL(5,4) DEFAULT '0.1300' COMMENT '税率,默认13%',
|
||||||
|
`created_at` TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` TIMESTAMP NOT NULL COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同表';
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX `idx_contract_number` ON `contract` (`contract_number`);
|
||||||
|
CREATE INDEX `idx_contract_org` ON `contract` (`org_id`);
|
||||||
|
CREATE INDEX `idx_contract_status` ON `contract` (`status`);
|
||||||
|
|
||||||
|
-- Table from contract_ai_config.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `contract_ai_config` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT 'AI配置ID',
|
||||||
|
`ai_service_url` VARCHAR(500) NOT NULL COMMENT 'AI服务URL地址',
|
||||||
|
`api_key` VARCHAR(255) NOT NULL COMMENT 'API密钥',
|
||||||
|
`org_id` VARCHAR(32) NOT NULL COMMENT '所属组织',
|
||||||
|
`created_at` TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` TIMESTAMP NOT NULL COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='AI配置表';
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX `idx_ai_config_org` ON `contract_ai_config` (`org_id`);
|
||||||
|
|
||||||
|
-- Table from contract_attachment.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `contract_attachment` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '附件ID,主键',
|
||||||
|
`contract_id` VARCHAR(32) NOT NULL COMMENT '关联的合同ID,引用contract表的id',
|
||||||
|
`file_name` VARCHAR(255) NOT NULL COMMENT '文件名',
|
||||||
|
`file_path` VARCHAR(500) NOT NULL COMMENT '文件存储路径',
|
||||||
|
`file_size` INT NOT NULL COMMENT '文件大小(字节)',
|
||||||
|
`file_type` VARCHAR(50) NOT NULL COMMENT '文件类型(MIME类型)',
|
||||||
|
`version` INT NOT NULL DEFAULT '1' COMMENT '文件版本号',
|
||||||
|
`description` VARCHAR(200) COMMENT '附件描述',
|
||||||
|
`uploaded_by` VARCHAR(32) NOT NULL COMMENT '上传人,引用users表的id',
|
||||||
|
`org_id` VARCHAR(32) NOT NULL COMMENT '所属组织',
|
||||||
|
`created_at` TIMESTAMP NOT NULL COMMENT '上传时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同附件表';
|
||||||
|
|
||||||
|
CREATE INDEX `idx_attachment_contract` ON `contract_attachment` (`contract_id`);
|
||||||
|
CREATE INDEX `idx_attachment_org` ON `contract_attachment` (`org_id`);
|
||||||
|
CREATE INDEX `idx_attachment_version` ON `contract_attachment` (`file_name`, `version`);
|
||||||
|
|
||||||
|
-- Table from contract_milestones.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `contract_milestones` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键 - UUID格式',
|
||||||
|
`contract_id` VARCHAR(32) NOT NULL COMMENT '关联的合同ID',
|
||||||
|
`milestone_name` VARCHAR(100) NOT NULL COMMENT '里程碑名称,如\'预付款到账\'、\'产品交付\'、\'验收完成\'',
|
||||||
|
`milestone_type` VARCHAR(20) NOT NULL COMMENT '里程碑类型:payment=付款, delivery=交付, acceptance=验收',
|
||||||
|
`planned_date` DATE NOT NULL COMMENT '计划完成日期',
|
||||||
|
`actual_date` DATE COMMENT '实际完成日期',
|
||||||
|
`amount` DECIMAL(15,2) COMMENT '该里程碑关联的金额',
|
||||||
|
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态:pending=待处理, completed=已完成, overdue=已逾期',
|
||||||
|
`description` TEXT COMMENT '里程碑详细描述',
|
||||||
|
`created_at` TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` TIMESTAMP NOT NULL COMMENT '最后更新时间',
|
||||||
|
`reminder_sent` VARCHAR(1) NOT NULL DEFAULT '0' COMMENT '逾期提醒是否已发送:1=是, 0=否',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同里程碑管理表';
|
||||||
|
|
||||||
|
CREATE INDEX `idx_milestones_contract` ON `contract_milestones` (`contract_id`);
|
||||||
|
CREATE INDEX `idx_milestones_status` ON `contract_milestones` (`status`);
|
||||||
|
CREATE INDEX `idx_milestones_planned` ON `contract_milestones` (`planned_date`);
|
||||||
|
|
||||||
|
-- Table from contract_versions.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `contract_versions` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键 - UUID格式',
|
||||||
|
`contract_id` VARCHAR(32) NOT NULL COMMENT '关联的合同ID',
|
||||||
|
`version_number` VARCHAR(20) NOT NULL COMMENT '版本号,如v1.0, v1.1等',
|
||||||
|
`content` TEXT NOT NULL COMMENT '合同完整内容或差异内容',
|
||||||
|
`diff_content` TEXT COMMENT '与上一版本的差异内容(HTML格式)',
|
||||||
|
`modified_by` VARCHAR(32) NOT NULL COMMENT '修改人用户ID',
|
||||||
|
`modified_reason` TEXT COMMENT '版本修改原因',
|
||||||
|
`created_at` TIMESTAMP NOT NULL COMMENT '版本创建时间',
|
||||||
|
`is_current` VARCHAR(1) NOT NULL DEFAULT '0' COMMENT '是否为当前生效版本:1=是, 0=否',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='合同版本管理表';
|
||||||
|
|
||||||
|
CREATE INDEX `idx_contract_versions_contract` ON `contract_versions` (`contract_id`);
|
||||||
|
CREATE UNIQUE INDEX `idx_contract_versions_version` ON `contract_versions` (`contract_id`, `version_number`);
|
||||||
|
CREATE INDEX `idx_contract_versions_current` ON `contract_versions` (`contract_id`, `is_current`);
|
||||||
|
|
||||||
|
-- Table from order_payments.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `order_payments` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键 - UUID格式',
|
||||||
|
`order_id` VARCHAR(32) NOT NULL COMMENT '关联的订单ID',
|
||||||
|
`payment_amount` DECIMAL(15,2) NOT NULL COMMENT '本次付款金额',
|
||||||
|
`payment_date` DATE NOT NULL COMMENT '付款日期',
|
||||||
|
`payment_method` VARCHAR(50) COMMENT '付款方式',
|
||||||
|
`payment_reference` VARCHAR(100) COMMENT '付款凭证号或参考号',
|
||||||
|
`status` VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态:pending=待确认, confirmed=已确认, rejected=已拒绝',
|
||||||
|
`notes` TEXT COMMENT '付款备注',
|
||||||
|
`created_at` TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||||
|
`confirmed_at` TIMESTAMP COMMENT '付款确认时间',
|
||||||
|
`confirmed_by` VARCHAR(32) COMMENT '付款确认人ID',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单付款记录表';
|
||||||
|
|
||||||
|
CREATE INDEX `idx_payments_order` ON `order_payments` (`order_id`);
|
||||||
|
CREATE INDEX `idx_payments_status` ON `order_payments` (`status`);
|
||||||
|
CREATE INDEX `idx_payments_date` ON `order_payments` (`payment_date`);
|
||||||
|
|
||||||
|
-- Table from orders.json
|
||||||
|
CREATE TABLE IF NOT EXISTS `orders` (
|
||||||
|
`id` VARCHAR(32) NOT NULL COMMENT '主键 - UUID格式',
|
||||||
|
`order_number` VARCHAR(50) NOT NULL COMMENT '订单编号,唯一标识',
|
||||||
|
`contract_id` VARCHAR(32) NOT NULL COMMENT '关联的合同ID',
|
||||||
|
`customer_id` VARCHAR(32) NOT NULL COMMENT '客户ID',
|
||||||
|
`order_type` VARCHAR(20) NOT NULL COMMENT '订单类型:advance=预付款, progress=进度款, final=尾款, other=其他',
|
||||||
|
`delivery_batch` VARCHAR(100) COMMENT '交付批次,如\'Q3季度服务交付\'',
|
||||||
|
`acceptance_deadline` DATE COMMENT '验收截止日期',
|
||||||
|
`amount` DECIMAL(15,2) NOT NULL COMMENT '该订单的金额',
|
||||||
|
`paid_amount` DECIMAL(15,2) NOT NULL DEFAULT '0.00' COMMENT '已支付金额',
|
||||||
|
`tax_rate` DECIMAL(5,4) NOT NULL DEFAULT '0.1300' COMMENT '税率',
|
||||||
|
`credit_period` INT COMMENT '账期天数要求',
|
||||||
|
`status` VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态:active=活跃, completed=完成, cancelled=取消',
|
||||||
|
`description` TEXT COMMENT '订单详细描述',
|
||||||
|
`created_at` TIMESTAMP NOT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` TIMESTAMP NOT NULL COMMENT '最后更新时间',
|
||||||
|
`owner_id` VARCHAR(32) NOT NULL COMMENT '订单负责人ID',
|
||||||
|
`org_id` VARCHAR(32) NOT NULL COMMENT '所属组织ID',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='订单表';
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX `idx_orders_number` ON `orders` (`order_number`);
|
||||||
|
CREATE INDEX `idx_orders_contract` ON `orders` (`contract_id`);
|
||||||
|
CREATE INDEX `idx_orders_customer` ON `orders` (`customer_id`);
|
||||||
|
CREATE INDEX `idx_orders_status` ON `orders` (`status`);
|
||||||
|
CREATE INDEX `idx_orders_owner` ON `orders` (`owner_id`);
|
||||||
19
wwwroot/api/check_contract.dspy
Normal file
19
wwwroot/api/check_contract.dspy
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import json
|
||||||
|
result = {'keys': [], 'rows': []}
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('contract_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
ns = {'page': 1, 'rows': 50, 'sort': 'COLUMN_NAME'}
|
||||||
|
sql = "SELECT COLUMN_NAME, COLUMN_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='crm_db' AND TABLE_NAME='contract'"
|
||||||
|
rows = await sor.sqlExe(sql, ns)
|
||||||
|
if isinstance(rows, dict):
|
||||||
|
rows = rows.get('rows', [])
|
||||||
|
if rows:
|
||||||
|
result['keys'] = list(dict(rows[0]).keys())
|
||||||
|
result['rows'] = [list(dict(r).values()) for r in rows]
|
||||||
|
result['success'] = True
|
||||||
|
except Exception as e:
|
||||||
|
result['error'] = str(e)
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
30
wwwroot/api/contract_list.dspy
Normal file
30
wwwroot/api/contract_list.dspy
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Contract list API"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = {'success': False, 'rows': [], 'total': 0}
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('contract_management')
|
||||||
|
ns = {
|
||||||
|
'page': int(params_kw.get('page', 1)),
|
||||||
|
'rows': int(params_kw.get('rows', 20)),
|
||||||
|
'sort': 'created_at desc'
|
||||||
|
}
|
||||||
|
sql = "SELECT id, contract_number, title, party_a, party_b, contract_type, status, amount, start_date, end_date, sign_date, owner_id, created_at FROM contract"
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
data = await sor.sqlExe(sql, ns)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
result['total'] = data.get('total', 0)
|
||||||
|
result['rows'] = [dict(r) for r in data.get('rows', [])]
|
||||||
|
else:
|
||||||
|
result['rows'] = [dict(r) for r in (data or [])]
|
||||||
|
result['total'] = len(result['rows'])
|
||||||
|
result['success'] = True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
result['error'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
49
wwwroot/api/contracts_create.dspy
Normal file
49
wwwroot/api/contracts_create.dspy
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Contract create API"""
|
||||||
|
import json, uuid, time
|
||||||
|
|
||||||
|
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||||
|
|
||||||
|
try:
|
||||||
|
contract_number = params_kw.get('contract_number', '').strip()
|
||||||
|
title = params_kw.get('title', '').strip()
|
||||||
|
party_a = params_kw.get('party_a', '').strip()
|
||||||
|
party_b = params_kw.get('party_b', '').strip()
|
||||||
|
contract_type = params_kw.get('contract_type', '').strip()
|
||||||
|
amount = params_kw.get('amount', '0').strip()
|
||||||
|
start_date = params_kw.get('start_date', '').strip() or None
|
||||||
|
end_date = params_kw.get('end_date', '').strip() or None
|
||||||
|
owner_id = params_kw.get('owner_id', '').strip()
|
||||||
|
|
||||||
|
if not contract_number or not title or not party_a or not party_b or not owner_id:
|
||||||
|
result['options'] = {'title': 'Error', 'message': '请填写必填字段', 'type': 'error'}
|
||||||
|
else:
|
||||||
|
dbname = get_module_dbname('contract_management')
|
||||||
|
new_id = str(uuid.uuid4()).replace('-', '')
|
||||||
|
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
sign_date = params_kw.get('sign_date', '').strip() or None
|
||||||
|
org_id = params_kw.get('org_id', '').strip() or 'org001'
|
||||||
|
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
existing = await sor.sqlExe("SELECT id FROM contract WHERE contract_number = ${contract_number}$", {'contract_number': contract_number})
|
||||||
|
if existing:
|
||||||
|
result['options'] = {'title': 'Error', 'message': '合同编号已存在', 'type': 'error'}
|
||||||
|
else:
|
||||||
|
await sor.sqlExe("""INSERT INTO contract (id, contract_number, title, party_a, party_b, contract_type, amount, start_date, end_date, sign_date, status, owner_id, org_id, created_at)
|
||||||
|
VALUES (${id}$, ${contract_number}$, ${title}$, ${party_a}$, ${party_b}$, ${contract_type}$, ${amount}$, ${start_date}$, ${end_date}$, ${sign_date}$, ${status}$, ${owner_id}$, ${org_id}$, ${created_at}$)""", {
|
||||||
|
'id': new_id, 'contract_number': contract_number, 'title': title,
|
||||||
|
'party_a': party_a, 'party_b': party_b, 'contract_type': contract_type,
|
||||||
|
'amount': float(amount) if amount else 0,
|
||||||
|
'start_date': start_date, 'end_date': end_date,
|
||||||
|
'sign_date': sign_date,
|
||||||
|
'status': params_kw.get('status', 'draft').strip(),
|
||||||
|
'owner_id': owner_id, 'org_id': org_id,
|
||||||
|
'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)
|
||||||
25
wwwroot/api/contracts_delete.dspy
Normal file
25
wwwroot/api/contracts_delete.dspy
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Contract delete API"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||||
|
|
||||||
|
try:
|
||||||
|
record_id = params_kw.get('id', '').strip()
|
||||||
|
if not record_id:
|
||||||
|
result['options'] = {'title': 'Error', 'message': '记录ID不能为空', 'type': 'error'}
|
||||||
|
else:
|
||||||
|
dbname = get_module_dbname('contract_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
existing = await sor.sqlExe("SELECT id FROM contract WHERE id = ${id}$", {'id': record_id})
|
||||||
|
if not existing:
|
||||||
|
result['options'] = {'title': 'Error', 'message': '合同不存在', 'type': 'error'}
|
||||||
|
else:
|
||||||
|
await sor.sqlExe("DELETE FROM contract WHERE id = ${id}$", {'id': record_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)
|
||||||
39
wwwroot/api/contracts_update.dspy
Normal file
39
wwwroot/api/contracts_update.dspy
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Contract update API"""
|
||||||
|
import json, time
|
||||||
|
|
||||||
|
result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}}
|
||||||
|
|
||||||
|
try:
|
||||||
|
record_id = params_kw.get('id', '').strip()
|
||||||
|
if not record_id:
|
||||||
|
result['options'] = {'title': 'Error', 'message': '记录ID不能为空', 'type': 'error'}
|
||||||
|
else:
|
||||||
|
dbname = get_module_dbname('contract_management')
|
||||||
|
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
fields = ['contract_number', 'title', 'party_a', 'party_b', 'contract_type', 'amount', 'start_date', 'end_date', 'sign_date', 'status', 'owner_id', 'org_id']
|
||||||
|
set_parts = []
|
||||||
|
params = {'id': record_id}
|
||||||
|
for f in fields:
|
||||||
|
val = params_kw.get(f, None)
|
||||||
|
if val is not None:
|
||||||
|
set_parts.append(f"{f} = %({f})s")
|
||||||
|
params[f] = val
|
||||||
|
|
||||||
|
set_parts.append("created_at = ${created_at}$")
|
||||||
|
params['created_at'] = now
|
||||||
|
|
||||||
|
if len(set_parts) <= 1:
|
||||||
|
result['options'] = {'title': 'Error', 'message': '没有可更新的字段', 'type': 'error'}
|
||||||
|
else:
|
||||||
|
sql = f"UPDATE contract SET {', '.join(set_parts)} WHERE id = ${id}$"
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
await sor.sqlExe(sql, params)
|
||||||
|
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)
|
||||||
@ -56,7 +56,7 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"url": "{{entire_url('contract_edit.ui')}}",
|
"url": "{{entire_url('contract_edit.ui')}}",
|
||||||
"params": {
|
"params": {
|
||||||
"id": "{{params_kw.get('contract_id')}}"
|
"id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,7 +74,7 @@
|
|||||||
"widgettype": "Form",
|
"widgettype": "Form",
|
||||||
"options": {
|
"options": {
|
||||||
"tblname": "contract",
|
"tblname": "contract",
|
||||||
"record_id": "{{params_kw.get('contract_id')}}",
|
"record_id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}",
|
||||||
"readonly": true,
|
"readonly": true,
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"style": {
|
"style": {
|
||||||
@ -122,7 +122,7 @@
|
|||||||
"tblname": "contract_attachment",
|
"tblname": "contract_attachment",
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"parent_field": "contract_id",
|
"parent_field": "contract_id",
|
||||||
"parent_value": "{{params_kw.get('contract_id')}}"
|
"parent_value": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -164,7 +164,7 @@
|
|||||||
"widgettype": "Text",
|
"widgettype": "Text",
|
||||||
"options": {
|
"options": {
|
||||||
"fieldname": "ai_compliance_result",
|
"fieldname": "ai_compliance_result",
|
||||||
"record_id": "{{params_kw.get('contract_id')}}",
|
"record_id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}",
|
||||||
"tblname": "contract",
|
"tblname": "contract",
|
||||||
"style": {
|
"style": {
|
||||||
"whiteSpace": "pre-wrap",
|
"whiteSpace": "pre-wrap",
|
||||||
@ -194,7 +194,7 @@
|
|||||||
"widgettype": "Text",
|
"widgettype": "Text",
|
||||||
"options": {
|
"options": {
|
||||||
"fieldname": "ai_key_dates",
|
"fieldname": "ai_key_dates",
|
||||||
"record_id": "{{params_kw.get('contract_id')}}",
|
"record_id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}",
|
||||||
"tblname": "contract",
|
"tblname": "contract",
|
||||||
"style": {
|
"style": {
|
||||||
"whiteSpace": "pre-wrap",
|
"whiteSpace": "pre-wrap",
|
||||||
|
|||||||
@ -1,150 +1,51 @@
|
|||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "Page",
|
||||||
"options": {
|
"options": {
|
||||||
"width": "100%",
|
"title": "合同编辑",
|
||||||
"height": "100%"
|
"style": {"height": "100vh", "padding": "0"}
|
||||||
},
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"padding": "16px", "flex": 1, "overflow": "auto"}},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "Form",
|
"widgettype": "Form",
|
||||||
|
"id": "contract_form",
|
||||||
"options": {
|
"options": {
|
||||||
"title": "合同信息",
|
"submit_url": "{{entire_url('api/contracts_create.dspy')}}",
|
||||||
"tblname": "contract",
|
"method": "POST",
|
||||||
"submit_url": "/api/contract/save",
|
"layout": "vertical",
|
||||||
"width": "100%",
|
"style": {"maxWidth": "600px"},
|
||||||
"style": {
|
"fields": [
|
||||||
"maxWidth": "1200px",
|
{"name": "contract_number", "label": "合同编号", "uitype": "text", "required": true},
|
||||||
"margin": "0 auto"
|
{"name": "title", "label": "合同名称", "uitype": "text", "required": true},
|
||||||
}
|
{"name": "party_a", "label": "甲方", "uitype": "text", "required": true},
|
||||||
},
|
{"name": "party_b", "label": "乙方", "uitype": "text", "required": true},
|
||||||
"binds": [
|
{"name": "contract_type", "label": "合同类型", "uitype": "code", "required": true, "data": [
|
||||||
{
|
{"value": "sales", "text": "销售合同"},
|
||||||
"wid": "self",
|
{"value": "purchase", "text": "采购合同"},
|
||||||
"event": "submited",
|
{"value": "service", "text": "服务合同"},
|
||||||
"actiontype": "script",
|
{"value": "other", "text": "其他"}
|
||||||
"script": "await bricks.show_resp_message_or_error(event.params); if (event.params.success) { bricks.app.goto_url('{{entire_url(\"contract_list.ui\")}}'); }"
|
]},
|
||||||
}
|
{"name": "amount", "label": "合同金额", "uitype": "number"},
|
||||||
]
|
{"name": "start_date", "label": "开始日期", "uitype": "date", "required": true},
|
||||||
},
|
{"name": "end_date", "label": "结束日期", "uitype": "date", "required": true},
|
||||||
{
|
{"name": "sign_date", "label": "签署日期", "uitype": "date"},
|
||||||
"widgettype": "Filler",
|
{"name": "owner_id", "label": "负责人ID", "uitype": "text", "required": true},
|
||||||
"options": {
|
{"name": "org_id", "label": "组织ID", "uitype": "text"},
|
||||||
"height": "20px"
|
{"name": "status", "label": "状态", "uitype": "code", "data": [
|
||||||
}
|
{"value": "draft", "text": "草稿"},
|
||||||
},
|
{"value": "active", "text": "生效"},
|
||||||
{
|
{"value": "expired", "text": "过期"},
|
||||||
"widgettype": "HBox",
|
{"value": "terminated", "text": "终止"}
|
||||||
"options": {
|
]}
|
||||||
"width": "100%",
|
],
|
||||||
"style": {
|
"buttons": [
|
||||||
"maxWidth": "1200px",
|
{"type": "submit", "text": "保存", "variant": "primary"},
|
||||||
"margin": "0 auto"
|
{"type": "button", "text": "取消", "action": "navigate('main/contract_management/contract_list.ui')"}
|
||||||
}
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"width": "50%"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "合同附件",
|
|
||||||
"style": {
|
|
||||||
"fontSize": "18px",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginBottom": "10px"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "DataGrid",
|
|
||||||
"options": {
|
|
||||||
"tblname": "contract_attachment",
|
|
||||||
"width": "100%",
|
|
||||||
"parent_field": "contract_id",
|
|
||||||
"parent_value": "{{params_kw.get('id') or 'new'}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Filler",
|
|
||||||
"options": {
|
|
||||||
"width": "20px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "VBox",
|
|
||||||
"options": {
|
|
||||||
"width": "50%"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
|
||||||
{
|
|
||||||
"widgettype": "Text",
|
|
||||||
"options": {
|
|
||||||
"text": "AI分析结果",
|
|
||||||
"style": {
|
|
||||||
"fontSize": "18px",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginBottom": "10px"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"text": "执行合规检查",
|
|
||||||
"styleType": "primary",
|
|
||||||
"style": {
|
|
||||||
"marginBottom": "10px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"script": "console.log('AI合规检查功能待实现')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"text": "提取关键时点",
|
|
||||||
"styleType": "primary",
|
|
||||||
"style": {
|
|
||||||
"marginBottom": "10px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"script": "console.log('AI关键时点提取功能待实现')"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"text": "版本对比",
|
|
||||||
"styleType": "primary"
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "script",
|
|
||||||
"script": "console.log('AI版本对比功能待实现')"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
57
wwwroot/contract_list.dspy
Normal file
57
wwwroot/contract_list.dspy
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""List contract"""
|
||||||
|
import json
|
||||||
|
|
||||||
|
result = {'success': False, 'rows': [], 'total': 0}
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbname = get_module_dbname('contract_management')
|
||||||
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
|
where_clauses = []
|
||||||
|
where_ns = {}
|
||||||
|
|
||||||
|
customer_id = params_kw.get('customer_id', '')
|
||||||
|
status = params_kw.get('status', '')
|
||||||
|
|
||||||
|
if customer_id:
|
||||||
|
where_clauses.append("customer_id=${customer_id}$")
|
||||||
|
where_ns['customer_id'] = customer_id
|
||||||
|
if status:
|
||||||
|
where_clauses.append("status=${status}$")
|
||||||
|
where_ns['status'] = status
|
||||||
|
|
||||||
|
where_sql = " AND ".join(where_clauses)
|
||||||
|
where_prefix = " WHERE " if where_clauses else ""
|
||||||
|
|
||||||
|
count_sql = f"SELECT count(*) rcnt FROM contract{where_prefix}{where_sql}"
|
||||||
|
count_rows = await sor.sqlExe(count_sql, where_ns)
|
||||||
|
total = 0
|
||||||
|
if count_rows and len(count_rows) > 0:
|
||||||
|
r = count_rows[0]
|
||||||
|
if hasattr(r, 'keys'):
|
||||||
|
total = r.get('rcnt', 0)
|
||||||
|
elif isinstance(r, dict):
|
||||||
|
total = r.get('rcnt', 0)
|
||||||
|
elif hasattr(r, 'rcnt'):
|
||||||
|
total = r.rcnt
|
||||||
|
|
||||||
|
if total > 0:
|
||||||
|
ns = {'page': int(params_kw.get('page', 1)), 'rows': int(params_kw.get('rows', 20)), 'sort': params_kw.get('sort', 'created_at')}
|
||||||
|
sql = f"SELECT id, title, contract_number, contract_type, customer_id, amount, sign_date, start_date, end_date, status, owner_id, org_id, created_at, updated_at FROM contract{where_prefix}{where_sql}"
|
||||||
|
|
||||||
|
query_ns = dict(list(ns.items()) + list(where_ns.items()))
|
||||||
|
rows = await sor.sqlExe(sql, query_ns)
|
||||||
|
|
||||||
|
if isinstance(rows, dict):
|
||||||
|
result['rows'] = rows.get('rows', [])
|
||||||
|
result['total'] = rows.get('total', total)
|
||||||
|
elif rows:
|
||||||
|
result['rows'] = [dict(r) if hasattr(r, 'keys') else r for r in rows]
|
||||||
|
result['total'] = total
|
||||||
|
|
||||||
|
result['success'] = True
|
||||||
|
except Exception as e:
|
||||||
|
result['error'] = str(e)
|
||||||
|
|
||||||
|
return json.dumps(result, ensure_ascii=False, default=str)
|
||||||
@ -1,81 +1,45 @@
|
|||||||
{
|
{
|
||||||
"widgettype": "VBox",
|
"widgettype": "Page",
|
||||||
"options": {
|
"options": {
|
||||||
"width": "100%",
|
"title": "合同管理",
|
||||||
"height": "100%"
|
"style": {"height": "100vh", "padding": "0"}
|
||||||
},
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "VBox",
|
||||||
|
"options": {"style": {"padding": "16px", "flex": 1, "overflow": "hidden"}},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "HBox",
|
"widgettype": "HBox",
|
||||||
"options": {
|
"options": {"style": {"marginBottom": "16px", "gap": "8px"}},
|
||||||
"width": "100%",
|
|
||||||
"height": "50px"
|
|
||||||
},
|
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{"widgettype": "TextField", "id": "search_keyword", "options": {"label": "搜索", "placeholder": "合同编号/名称", "style": {"flex": 1}}},
|
||||||
"widgettype": "Text",
|
{"widgettype": "Button", "id": "btn_search", "options": {"text": "搜索", "variant": "primary"}},
|
||||||
"options": {
|
{"widgettype": "Button", "id": "btn_add", "options": {"text": "新增", "variant": "primary", "action": "navigate('main/contract_management/contract_edit.ui')"}}
|
||||||
"text": "合同管理",
|
|
||||||
"style": {
|
|
||||||
"fontSize": "24px",
|
|
||||||
"fontWeight": "600",
|
|
||||||
"marginLeft": "20px",
|
|
||||||
"marginTop": "10px"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Filler"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"widgettype": "Button",
|
|
||||||
"options": {
|
|
||||||
"text": "新建合同",
|
|
||||||
"styleType": "primary",
|
|
||||||
"style": {
|
|
||||||
"marginRight": "20px",
|
|
||||||
"marginTop": "8px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"binds": [
|
|
||||||
{
|
|
||||||
"wid": "self",
|
|
||||||
"event": "click",
|
|
||||||
"actiontype": "urlwidget",
|
|
||||||
"target": "main_content",
|
|
||||||
"options": {
|
|
||||||
"url": "{{entire_url('contract_edit.ui')}}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"widgettype": "Filler",
|
|
||||||
"options": {
|
|
||||||
"height": "10px"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "main_content",
|
|
||||||
"widgettype": "DataGrid",
|
"widgettype": "DataGrid",
|
||||||
|
"id": "contract_grid",
|
||||||
"options": {
|
"options": {
|
||||||
"tblname": "contract",
|
"url": "{{entire_url('api/contract_list.dspy')}}",
|
||||||
"width": "100%",
|
"style": {"flex": 1},
|
||||||
"height": "calc(100% - 70px)"
|
"columns": [
|
||||||
},
|
{"field": "contract_number", "header": "合同编号", "width": 140},
|
||||||
"binds": [
|
{"field": "title", "header": "合同名称", "width": 200},
|
||||||
{
|
{"field": "party_a", "header": "甲方", "width": 150},
|
||||||
"wid": "self",
|
{"field": "party_b", "header": "乙方", "width": 150},
|
||||||
"event": "rowSelected",
|
{"field": "contract_type", "header": "类型", "width": 100},
|
||||||
"actiontype": "urlwidget",
|
{"field": "amount", "header": "金额", "width": 120},
|
||||||
"target": "main_content",
|
{"field": "start_date", "header": "开始日期", "width": 110},
|
||||||
"options": {
|
{"field": "end_date", "header": "结束日期", "width": 110},
|
||||||
"url": "{{entire_url('contract_detail.ui')}}",
|
{"field": "status", "header": "状态", "width": 90}
|
||||||
"params": {
|
],
|
||||||
"contract_id": "{{params.row.id}}"
|
"toolbar": [
|
||||||
}
|
{"type": "button", "text": "详情", "icon": "view", "action": "navigate('main/contract_management/contract_detail.ui?id={% raw %}{{selectedRow.id}}{% endraw %}')"},
|
||||||
|
{"type": "button", "text": "编辑", "icon": "edit", "action": "navigate('main/contract_management/contract_edit.ui?id={% raw %}{{selectedRow.id}}{% endraw %}')"},
|
||||||
|
{"type": "button", "text": "删除", "icon": "delete", "action": "doDelete('{% raw %}{{selectedRow.id}}{% endraw %}')"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user