diff --git a/contract_management/__init__.py b/contract_management/__init__.py index 9232540..2458468 100644 --- a/contract_management/__init__.py +++ b/contract_management/__init__.py @@ -1,15 +1,10 @@ """ -合同管理模块 - 增强版 -支持从商机创建合同、自动生成里程碑和订单、AI智能审核等功能 +合同管理模块 +支持合同全生命周期管理 """ -from .enhanced_contract_core import ( +from .contract_core import ( create_contract, - create_contract_from_opportunity, - create_contract_version, - create_manual_order, - update_contract_milestone_status, - ai_contract_analysis, update_contract, delete_contract, get_contract_by_id, @@ -17,20 +12,15 @@ from .enhanced_contract_core import ( ) # 版本信息 -__version__ = "2.0.0" +__version__ = "1.0.0" __author__ = "Hermes AI Agent" -__description__ = "Enhanced Contract Management Module with Opportunity Integration and AI Analysis" +__description__ = "Contract Management Module" # 导出所有公共接口 __all__ = [ 'create_contract', - 'create_contract_from_opportunity', - 'create_contract_version', - 'create_manual_order', - 'update_contract_milestone_status', - 'ai_contract_analysis', 'update_contract', 'delete_contract', 'get_contract_by_id', 'list_contracts' -] \ No newline at end of file +] diff --git a/contract_management/ai_config_core.py b/contract_management/ai_config_core.py index 1710638..d5682ef 100644 --- a/contract_management/ai_config_core.py +++ b/contract_management/ai_config_core.py @@ -1,6 +1,6 @@ import os import json -from sqlor.dbp import getDBP +from sqlor.dbpools import DBPools async def save_ai_config(request_data: dict, org_id: str) -> dict: """保存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_result = await dbp.doQuery(check_sql, {'org_id': org_id}) + check_result = await sor.doQuery(check_sql, {'org_id': org_id}) 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['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配置保存成功'} # 在 init.py 中添加这个函数的暴露 \ No newline at end of file diff --git a/contract_management/ai_core.py b/contract_management/ai_core.py index 3ff00b7..4e9510d 100644 --- a/contract_management/ai_core.py +++ b/contract_management/ai_core.py @@ -1,7 +1,7 @@ import json import aiohttp from typing import Dict, List, Optional, Tuple -from appPublic.jsonconfig import getConfig +from appPublic.Config import getConfig class AIContractService: def __init__(self): diff --git a/contract_management/attachment_core.py b/contract_management/attachment_core.py index 7b587a7..0ba9a5d 100644 --- a/contract_management/attachment_core.py +++ b/contract_management/attachment_core.py @@ -2,14 +2,14 @@ import os import uuid from datetime import datetime from typing import List, Dict, Optional -from appPublic.jsonconfig import getConfig -from appPublic.worker import Worker -from sqlor.dbp import getDBP +from appPublic.Config import getConfig +# Worker removed - not available +from sqlor.dbpools import DBPools class ContractAttachmentManager: def __init__(self): self.config = getConfig() - self.worker = Worker() + pass # Worker removed self.upload_dir = self.config.get('contract_upload_dir', '/var/uploads/contracts') async def get_db_connection(self, org_id: str): @@ -49,7 +49,7 @@ class ContractAttachmentManager: 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 """ - check_result = await dbp.doQuery(check_sql, { + check_result = await sor.doQuery(check_sql, { 'contract_id': contract_id, 'file_name': file_name, 'org_id': org_id @@ -81,7 +81,7 @@ class ContractAttachmentManager: 'org_id': org_id } - await dbp.doTransaction([{'sql': sql, 'params': params}]) + await sor.doTransaction([{'sql': sql, 'params': params}]) return attachment_id 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 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 # 全局实例 diff --git a/contract_management/contract_core.py b/contract_management/contract_core.py index 7dd35f8..60d6249 100644 --- a/contract_management/contract_core.py +++ b/contract_management/contract_core.py @@ -3,14 +3,12 @@ import json import uuid from datetime import datetime from typing import List, Dict, Optional, Tuple -from appPublic.jsonconfig import getConfig -from appPublic.worker import Worker -from sqlor.dbp import getDBP +from appPublic.Config import getConfig +from sqlor.dbpools import DBPools class ContractManager: def __init__(self): self.config = getConfig() - self.worker = Worker() async def get_db_connection(self, org_id: str): """获取数据库连接""" @@ -52,7 +50,7 @@ class ContractManager: 'org_id': org_id } - await dbp.doTransaction([{'sql': sql, 'params': params}]) + await sor.doTransaction([{'sql': sql, 'params': params}]) return contract_id 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()") 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 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}}]) + result = await sor.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}) + result = await sor.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, @@ -125,7 +123,7 @@ class ContractManager: # 获取总数 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 # 获取分页数据 @@ -139,7 +137,7 @@ class ContractManager: params['limit'] = page_size params['offset'] = offset - data_result = await dbp.doQuery(data_sql, params) + data_result = await sor.doQuery(data_sql, params) return data_result, total # 全局实例 diff --git a/contract_management/enhanced_contract_core.py b/contract_management/enhanced_contract_core.py index da47ac9..4ed1267 100644 --- a/contract_management/enhanced_contract_core.py +++ b/contract_management/enhanced_contract_core.py @@ -3,15 +3,14 @@ import json import uuid from datetime import datetime, date, timedelta from typing import List, Dict, Optional, Tuple -from appPublic.jsonconfig import getConfig -from appPublic.worker import Worker -from sqlor.dbp import getDBP +from appPublic.Config import getConfig +from appPublic.worker import awaitify +from sqlor.dbpools import DBPools class EnhancedContractManager: def __init__(self): self.config = getConfig() - self.worker = Worker() async def get_db_connection(self, org_id: str): """获取数据库连接""" @@ -22,7 +21,7 @@ class EnhancedContractManager: """从商机创建合同""" # 获取商机信息 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: raise ValueError("商机不存在") @@ -90,7 +89,7 @@ class EnhancedContractManager: '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', '初始版本') @@ -104,7 +103,7 @@ class EnhancedContractManager: dbp = await self.get_db_connection(org_id) # 将之前的当前版本标记为非当前 - await dbp.update( + await sor.update( "contract_versions", {"is_current": "0"}, {"contract_id": contract_id, "is_current": "1"} @@ -123,7 +122,7 @@ class EnhancedContractManager: "is_current": "1" } - await dbp.insert("contract_versions", version_data) + await sor.insert("contract_versions", version_data) return version_id 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") } 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): """根据付款条款自动生成订单""" @@ -221,7 +220,7 @@ class EnhancedContractManager: "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } 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: """手动创建订单""" @@ -232,7 +231,7 @@ class EnhancedContractManager: # 计算已收款金额 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'", {"contract_id": contract_id} ) @@ -264,7 +263,7 @@ class EnhancedContractManager: "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 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: 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": @@ -286,13 +285,13 @@ class EnhancedContractManager: async def update_contract_fulfillment_progress(self, milestone_id: str, org_id: str): """更新合同履约进度""" 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: return # 计算履约进度(简化逻辑) 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", {"contract_id": contract_id} ) @@ -303,12 +302,12 @@ class EnhancedContractManager: 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"): # 这里应该调用商机管理模块的API来更新阶段 # 模拟实现:当进度达到100%时,将商机状态更新为成交 if progress >= 100: - await dbp.update( + await sor.update( "opportunities", {"status": "won", "updated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}, {"id": contract["opportunity_id"]} @@ -338,7 +337,7 @@ class EnhancedContractManager: 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 async def send_milestone_reminders(self, org_id: str) -> int: @@ -498,202 +497,3 @@ class EnhancedContractManager: for pattern in patterns: 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) \ No newline at end of file diff --git a/mysql.ddl.sql b/mysql.ddl.sql index e69de29..7dfc001 100644 --- a/mysql.ddl.sql +++ b/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`); diff --git a/wwwroot/api/check_contract.dspy b/wwwroot/api/check_contract.dspy new file mode 100644 index 0000000..7de4ae4 --- /dev/null +++ b/wwwroot/api/check_contract.dspy @@ -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) diff --git a/wwwroot/api/contract_list.dspy b/wwwroot/api/contract_list.dspy new file mode 100644 index 0000000..d233b65 --- /dev/null +++ b/wwwroot/api/contract_list.dspy @@ -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) diff --git a/wwwroot/api/contracts_create.dspy b/wwwroot/api/contracts_create.dspy new file mode 100644 index 0000000..8dd109e --- /dev/null +++ b/wwwroot/api/contracts_create.dspy @@ -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) diff --git a/wwwroot/api/contracts_delete.dspy b/wwwroot/api/contracts_delete.dspy new file mode 100644 index 0000000..6755f0f --- /dev/null +++ b/wwwroot/api/contracts_delete.dspy @@ -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) diff --git a/wwwroot/api/contracts_update.dspy b/wwwroot/api/contracts_update.dspy new file mode 100644 index 0000000..e526c06 --- /dev/null +++ b/wwwroot/api/contracts_update.dspy @@ -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) diff --git a/wwwroot/contract_detail.ui b/wwwroot/contract_detail.ui index 31559d0..ec79700 100644 --- a/wwwroot/contract_detail.ui +++ b/wwwroot/contract_detail.ui @@ -56,7 +56,7 @@ "options": { "url": "{{entire_url('contract_edit.ui')}}", "params": { - "id": "{{params_kw.get('contract_id')}}" + "id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}" } } } @@ -74,7 +74,7 @@ "widgettype": "Form", "options": { "tblname": "contract", - "record_id": "{{params_kw.get('contract_id')}}", + "record_id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}", "readonly": true, "width": "100%", "style": { @@ -122,7 +122,7 @@ "tblname": "contract_attachment", "width": "100%", "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", "options": { "fieldname": "ai_compliance_result", - "record_id": "{{params_kw.get('contract_id')}}", + "record_id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}", "tblname": "contract", "style": { "whiteSpace": "pre-wrap", @@ -194,7 +194,7 @@ "widgettype": "Text", "options": { "fieldname": "ai_key_dates", - "record_id": "{{params_kw.get('contract_id')}}", + "record_id": "{% raw %}{{params_kw.get('contract_id')}}{% endraw %}", "tblname": "contract", "style": { "whiteSpace": "pre-wrap", diff --git a/wwwroot/contract_edit.ui b/wwwroot/contract_edit.ui index c394b40..95afec1 100644 --- a/wwwroot/contract_edit.ui +++ b/wwwroot/contract_edit.ui @@ -1,152 +1,53 @@ { - "widgettype": "VBox", + "widgettype": "Page", "options": { - "width": "100%", - "height": "100%" + "title": "合同编辑", + "style": {"height": "100vh", "padding": "0"} }, "subwidgets": [ { - "widgettype": "Form", - "options": { - "title": "合同信息", - "tblname": "contract", - "submit_url": "/api/contract/save", - "width": "100%", - "style": { - "maxWidth": "1200px", - "margin": "0 auto" - } - }, - "binds": [ - { - "wid": "self", - "event": "submited", - "actiontype": "script", - "script": "await bricks.show_resp_message_or_error(event.params); if (event.params.success) { bricks.app.goto_url('{{entire_url(\"contract_list.ui\")}}'); }" - } - ] - }, - { - "widgettype": "Filler", - "options": { - "height": "20px" - } - }, - { - "widgettype": "HBox", - "options": { - "width": "100%", - "style": { - "maxWidth": "1200px", - "margin": "0 auto" - } - }, + "widgettype": "VBox", + "options": {"style": {"padding": "16px", "flex": 1, "overflow": "auto"}}, "subwidgets": [ { - "widgettype": "VBox", + "widgettype": "Form", + "id": "contract_form", "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" + "submit_url": "{{entire_url('api/contracts_create.dspy')}}", + "method": "POST", + "layout": "vertical", + "style": {"maxWidth": "600px"}, + "fields": [ + {"name": "contract_number", "label": "合同编号", "uitype": "text", "required": true}, + {"name": "title", "label": "合同名称", "uitype": "text", "required": true}, + {"name": "party_a", "label": "甲方", "uitype": "text", "required": true}, + {"name": "party_b", "label": "乙方", "uitype": "text", "required": true}, + {"name": "contract_type", "label": "合同类型", "uitype": "code", "required": true, "data": [ + {"value": "sales", "text": "销售合同"}, + {"value": "purchase", "text": "采购合同"}, + {"value": "service", "text": "服务合同"}, + {"value": "other", "text": "其他"} + ]}, + {"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"}, + {"name": "owner_id", "label": "负责人ID", "uitype": "text", "required": true}, + {"name": "org_id", "label": "组织ID", "uitype": "text"}, + {"name": "status", "label": "状态", "uitype": "code", "data": [ + {"value": "draft", "text": "草稿"}, + {"value": "active", "text": "生效"}, + {"value": "expired", "text": "过期"}, + {"value": "terminated", "text": "终止"} + ]} + ], + "buttons": [ + {"type": "submit", "text": "保存", "variant": "primary"}, + {"type": "button", "text": "取消", "action": "navigate('main/contract_management/contract_list.ui')"} + ] } - }, - { - "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版本对比功能待实现')" - } - ] - } - ] } ] } ] -} \ No newline at end of file +} diff --git a/wwwroot/contract_list.dspy b/wwwroot/contract_list.dspy new file mode 100644 index 0000000..d75079e --- /dev/null +++ b/wwwroot/contract_list.dspy @@ -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) diff --git a/wwwroot/contract_list.ui b/wwwroot/contract_list.ui index 563b088..23e517d 100644 --- a/wwwroot/contract_list.ui +++ b/wwwroot/contract_list.ui @@ -1,84 +1,48 @@ { - "widgettype": "VBox", + "widgettype": "Page", "options": { - "width": "100%", - "height": "100%" + "title": "合同管理", + "style": {"height": "100vh", "padding": "0"} }, "subwidgets": [ { - "widgettype": "HBox", - "options": { - "width": "100%", - "height": "50px" - }, + "widgettype": "VBox", + "options": {"style": {"padding": "16px", "flex": 1, "overflow": "hidden"}}, "subwidgets": [ { - "widgettype": "Text", - "options": { - "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": "HBox", + "options": {"style": {"marginBottom": "16px", "gap": "8px"}}, + "subwidgets": [ + {"widgettype": "TextField", "id": "search_keyword", "options": {"label": "搜索", "placeholder": "合同编号/名称", "style": {"flex": 1}}}, + {"widgettype": "Button", "id": "btn_search", "options": {"text": "搜索", "variant": "primary"}}, + {"widgettype": "Button", "id": "btn_add", "options": {"text": "新增", "variant": "primary", "action": "navigate('main/contract_management/contract_edit.ui')"}} ] - } - ] - }, - { - "widgettype": "Filler", - "options": { - "height": "10px" - } - }, - { - "id": "main_content", - "widgettype": "DataGrid", - "options": { - "tblname": "contract", - "width": "100%", - "height": "calc(100% - 70px)" - }, - "binds": [ + }, { - "wid": "self", - "event": "rowSelected", - "actiontype": "urlwidget", - "target": "main_content", + "widgettype": "DataGrid", + "id": "contract_grid", "options": { - "url": "{{entire_url('contract_detail.ui')}}", - "params": { - "contract_id": "{{params.row.id}}" - } + "url": "{{entire_url('api/contract_list.dspy')}}", + "style": {"flex": 1}, + "columns": [ + {"field": "contract_number", "header": "合同编号", "width": 140}, + {"field": "title", "header": "合同名称", "width": 200}, + {"field": "party_a", "header": "甲方", "width": 150}, + {"field": "party_b", "header": "乙方", "width": 150}, + {"field": "contract_type", "header": "类型", "width": 100}, + {"field": "amount", "header": "金额", "width": 120}, + {"field": "start_date", "header": "开始日期", "width": 110}, + {"field": "end_date", "header": "结束日期", "width": 110}, + {"field": "status", "header": "状态", "width": 90} + ], + "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 %}')"} + ] } } ] } ] -} \ No newline at end of file +}