commit dd001f63b4af824ad512c1b87b80dbcb19f07c80 Author: yumoqing Date: Thu Apr 16 14:40:37 2026 +0800 bugfix diff --git a/README.md b/README.md new file mode 100644 index 0000000..4656023 --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# 跨模块审批流程模块 + +## 概述 +跨模块审批流程模块提供统一的审批工作流管理,支持客户管理、商机管理、合同管理和财务管理模块的审批需求。 + +## 功能特性 + +### 1. 工作流定义 +- **可视化配置**: 通过CRUD界面定义审批工作流 +- **条件触发**: 支持基于业务规则自动触发审批 +- **多租户支持**: 组织级别的工作流隔离 + +### 2. 审批步骤管理 +- **多种审批类型**: 单人审批、多人会签、顺序审批、并行审批 +- **灵活审批人**: 支持用户、角色、部门、动态表达式 +- **超时控制**: 可配置审批超时时间 + +### 3. 审批实例管理 +- **跨模块集成**: 自动与客户、商机、合同、财务模块集成 +- **状态跟踪**: 实时跟踪审批进度和状态 +- **审计日志**: 完整的审批历史记录 + +### 4. 移动端适配 +- **响应式设计**: 适配手机和平板设备 +- **简化操作**: 移动端优化的审批操作界面 +- **离线支持**: 关键操作的离线缓存 + +## 技术架构 + +### 数据库设计 +- **approval_workflow**: 工作流定义表 +- **approval_step**: 审批步骤表 +- **approval_instance**: 审批实例表 +- **approval_task**: 审批任务表 + +### 前端实现 +- **Bricks Framework**: JSON驱动的组件化UI +- **移动端布局**: 专门的mobile_base.ui布局 +- **响应式组件**: 适配不同屏幕尺寸 + +### 后端实现 +- **异步设计**: 基于async/await的非阻塞架构 +- **RBAC集成**: 与现有权限系统无缝集成 +- **AppBase集成**: 使用编码管理系统 + +## 使用说明 + +### 工作流配置 +1. 进入"工作流管理"标签页 +2. 创建新的审批工作流 +3. 添加审批步骤并配置审批人 +4. 设置触发条件(可选) + +### 审批操作 +1. 在移动端或桌面端访问审批中心 +2. 查看待处理的审批任务 +3. 查看审批详情并做出决策 +4. 系统自动推进到下一步或完成审批 + +### 集成方式 +- **客户模块**: 客户交接、重要信息变更 +- **商机模块**: 大额商机创建、阶段变更 +- **合同模块**: 合同创建、条款修改、金额变更 +- **财务模块**: 大额支出、特殊收款处理 + +## 扩展性 + +- **自定义条件**: 支持复杂的触发条件表达式 +- **通知集成**: 可扩展邮件、短信、微信通知 +- **API接口**: 提供RESTful API供外部系统调用 +- **报表分析**: 审批效率和瓶颈分析 + +## 版本信息 +- **版本**: 1.0.0 +- **状态**: 生产就绪 +- **兼容性**: 遵循所有模块开发规范 \ No newline at end of file diff --git a/init/data.json b/init/data.json new file mode 100644 index 0000000..c9edaff --- /dev/null +++ b/init/data.json @@ -0,0 +1,116 @@ +{ + "appcodes": [ + { + "id": "APPROVER_TYPE", + "name": "审批人类型", + "hierarchy_flg": "0" + }, + { + "id": "APPROVAL_TYPE", + "name": "审批类型", + "hierarchy_flg": "0" + }, + { + "id": "APPROVAL_STATUS", + "name": "审批状态", + "hierarchy_flg": "0" + }, + { + "id": "TASK_STATUS", + "name": "任务状态", + "hierarchy_flg": "0" + } + ], + "appcodes_kv": [ + { + "id": "APPROVER_TYPE", + "parentid": "", + "k": "user", + "v": "指定用户" + }, + { + "id": "APPROVER_TYPE", + "parentid": "", + "k": "role", + "v": "角色" + }, + { + "id": "APPROVER_TYPE", + "parentid": "", + "k": "department", + "v": "部门" + }, + { + "id": "APPROVER_TYPE", + "parentid": "", + "k": "dynamic", + "v": "动态表达式" + }, + { + "id": "APPROVAL_TYPE", + "parentid": "", + "k": "single", + "v": "单人审批" + }, + { + "id": "APPROVAL_TYPE", + "parentid": "", + "k": "multiple", + "v": "多人会签" + }, + { + "id": "APPROVAL_TYPE", + "parentid": "", + "k": "sequential", + "v": "顺序审批" + }, + { + "id": "APPROVAL_TYPE", + "parentid": "", + "k": "parallel", + "v": "并行审批" + }, + { + "id": "APPROVAL_STATUS", + "parentid": "", + "k": "pending", + "v": "审批中" + }, + { + "id": "APPROVAL_STATUS", + "parentid": "", + "k": "approved", + "v": "已批准" + }, + { + "id": "APPROVAL_STATUS", + "parentid": "", + "k": "rejected", + "v": "已拒绝" + }, + { + "id": "APPROVAL_STATUS", + "parentid": "", + "k": "cancelled", + "v": "已取消" + }, + { + "id": "TASK_STATUS", + "parentid": "", + "k": "pending", + "v": "待处理" + }, + { + "id": "TASK_STATUS", + "parentid": "", + "k": "approved", + "v": "已批准" + }, + { + "id": "TASK_STATUS", + "parentid": "", + "k": "rejected", + "v": "已拒绝" + } + ] +} \ No newline at end of file diff --git a/json/approval_instance.json b/json/approval_instance.json new file mode 100644 index 0000000..845b3ea --- /dev/null +++ b/json/approval_instance.json @@ -0,0 +1,35 @@ +{ + "tblname": "approval_instance", + "title": "审批实例", + "params": { + "sortby": "created_at DESC", + "logined_userorgid": "org_id", + "browserfields": { + "exclouded": ["id", "workflow_id", "module_record_id", "org_id"] + }, + "editexclouded": ["id", "workflow_id", "module_record_id", "org_id", "current_step_id"], + "alterations": [ + { + "field": "status", + "widgettype": "Select", + "options": { + "data": "get_code('APPROVAL_STATUS')" + } + }, + { + "field": "module_type", + "widgettype": "Text", + "options": { + "readonly": true + } + } + ], + "subtables": [ + { + "field": "id", + "title": "审批任务", + "subtable": "approval_task" + } + ] + } +} \ No newline at end of file diff --git a/json/approval_step.json b/json/approval_step.json new file mode 100644 index 0000000..cbc5578 --- /dev/null +++ b/json/approval_step.json @@ -0,0 +1,28 @@ +{ + "tblname": "approval_step", + "title": "审批步骤", + "params": { + "sortby": "step_order", + "logined_userorgid": "org_id", + "browserfields": { + "exclouded": ["id", "workflow_id", "org_id"] + }, + "editexclouded": ["id", "workflow_id", "org_id"], + "alterations": [ + { + "field": "approver_type", + "widgettype": "Select", + "options": { + "data": "get_code('APPROVER_TYPE')" + } + }, + { + "field": "approval_type", + "widgettype": "Select", + "options": { + "data": "get_code('APPROVAL_TYPE')" + } + } + ] + } +} \ No newline at end of file diff --git a/json/approval_task.json b/json/approval_task.json new file mode 100644 index 0000000..1d79ed6 --- /dev/null +++ b/json/approval_task.json @@ -0,0 +1,28 @@ +{ + "tblname": "approval_task", + "title": "审批任务", + "params": { + "sortby": "assigned_at DESC", + "logined_userorgid": "org_id", + "browserfields": { + "exclouded": ["id", "instance_id", "step_id", "org_id"] + }, + "editexclouded": ["id", "instance_id", "step_id", "org_id"], + "alterations": [ + { + "field": "status", + "widgettype": "Select", + "options": { + "data": "get_code('TASK_STATUS')" + } + }, + { + "field": "approver_id", + "widgettype": "Select", + "options": { + "data": "get_org_users(org_id)" + } + } + ] + } +} \ No newline at end of file diff --git a/json/approval_workflow.json b/json/approval_workflow.json new file mode 100644 index 0000000..ff25782 --- /dev/null +++ b/json/approval_workflow.json @@ -0,0 +1,19 @@ +{ + "tblname": "approval_workflow", + "title": "审批工作流", + "params": { + "sortby": "workflow_name", + "logined_userorgid": "org_id", + "browserfields": { + "exclouded": ["id", "org_id", "created_at", "updated_at"] + }, + "editexclouded": ["id", "org_id"], + "subtables": [ + { + "field": "id", + "title": "审批步骤", + "subtable": "approval_step" + } + ] + } +} \ No newline at end of file diff --git a/models/approval_instance.json b/models/approval_instance.json new file mode 100644 index 0000000..832a530 --- /dev/null +++ b/models/approval_instance.json @@ -0,0 +1,133 @@ +{ + "summary": { + "name": "approval_instance", + "label": "审批实例", + "comment": "具体的审批实例记录" + }, + "fields": [ + { + "name": "id", + "title": "ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "主键UUID" + }, + { + "name": "workflow_id", + "title": "工作流ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "关联的工作流定义" + }, + { + "name": "module_type", + "title": "模块类型", + "type": "str", + "length": 50, + "nullable": false, + "comments": "customer/opportunity/contract/financial" + }, + { + "name": "module_record_id", + "title": "模块记录ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "关联的具体业务记录ID" + }, + { + "name": "current_step_id", + "title": "当前步骤ID", + "type": "str", + "length": 32, + "nullable": true, + "comments": "当前待审批的步骤" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "nullable": false, + "comments": "pending/approved/rejected/cancelled" + }, + { + "name": "initiator_id", + "title": "发起人ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "审批发起人用户ID" + }, + { + "name": "title", + "title": "标题", + "type": "str", + "length": 200, + "nullable": false, + "comments": "审批标题" + }, + { + "name": "description", + "title": "描述", + "type": "str", + "length": 1000, + "nullable": true, + "comments": "审批详细描述" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "多租户组织隔离" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "nullable": false, + "comments": "创建时间" + }, + { + "name": "completed_at", + "title": "完成时间", + "type": "timestamp", + "nullable": true, + "comments": "完成时间" + } + ], + "indexes": [ + { + "name": "idx_instance_workflow", + "idxtype": "index", + "fields": ["workflow_id"] + }, + { + "name": "idx_instance_module", + "idxtype": "index", + "fields": ["module_type", "module_record_id"] + }, + { + "name": "idx_instance_status", + "idxtype": "index", + "fields": ["status"] + }, + { + "name": "idx_instance_org", + "idxtype": "index", + "fields": ["org_id"] + } + ], + "codes": [ + { + "tblname": "appcodes", + "codevalue": "APPROVAL_STATUS", + "valuefield": "k", + "textfield": "v" + } + ] +} \ No newline at end of file diff --git a/models/approval_step.json b/models/approval_step.json new file mode 100644 index 0000000..5512d15 --- /dev/null +++ b/models/approval_step.json @@ -0,0 +1,113 @@ +{ + "summary": { + "name": "approval_step", + "label": "审批步骤", + "comment": "审批工作流步骤定义" + }, + "fields": [ + { + "name": "id", + "title": "ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "主键UUID" + }, + { + "name": "workflow_id", + "title": "工作流ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "关联的工作流" + }, + { + "name": "step_name", + "title": "步骤名称", + "type": "str", + "length": 100, + "nullable": false, + "comments": "审批步骤名称" + }, + { + "name": "step_order", + "title": "步骤顺序", + "type": "long", + "nullable": false, + "comments": "步骤执行顺序" + }, + { + "name": "approver_type", + "title": "审批人类型", + "type": "str", + "length": 20, + "nullable": false, + "comments": "role/user/department/dynamic" + }, + { + "name": "approver_value", + "title": "审批人值", + "type": "str", + "length": 100, + "nullable": true, + "comments": "角色ID/用户ID/部门ID/动态表达式" + }, + { + "name": "approval_type", + "title": "审批类型", + "type": "str", + "length": 20, + "nullable": false, + "comments": "single/multiple/sequential/parallel" + }, + { + "name": "timeout_hours", + "title": "超时小时数", + "type": "long", + "nullable": true, + "comments": "审批超时时间(小时)" + }, + { + "name": "description", + "title": "描述", + "type": "str", + "length": 500, + "nullable": true, + "comments": "步骤描述" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "多租户组织隔离" + } + ], + "indexes": [ + { + "name": "idx_step_workflow", + "idxtype": "index", + "fields": ["workflow_id"] + }, + { + "name": "idx_step_order", + "idxtype": "index", + "fields": ["workflow_id", "step_order"] + } + ], + "codes": [ + { + "tblname": "appcodes", + "codevalue": "APPROVER_TYPE", + "valuefield": "k", + "textfield": "v" + }, + { + "tblname": "appcodes", + "codevalue": "APPROVAL_TYPE", + "valuefield": "k", + "textfield": "v" + } + ] +} \ No newline at end of file diff --git a/models/approval_task.json b/models/approval_task.json new file mode 100644 index 0000000..d8d6e3b --- /dev/null +++ b/models/approval_task.json @@ -0,0 +1,111 @@ +{ + "summary": { + "name": "approval_task", + "label": "审批任务", + "comment": "具体的审批任务分配" + }, + "fields": [ + { + "name": "id", + "title": "ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "主键UUID" + }, + { + "name": "instance_id", + "title": "实例ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "关联的审批实例" + }, + { + "name": "step_id", + "title": "步骤ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "关联的审批步骤" + }, + { + "name": "approver_id", + "title": "审批人ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "具体审批人用户ID" + }, + { + "name": "status", + "title": "状态", + "type": "str", + "length": 20, + "nullable": false, + "comments": "pending/approved/rejected" + }, + { + "name": "decision", + "title": "决策", + "type": "str", + "length": 1000, + "nullable": true, + "comments": "审批意见" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "多租户组织隔离" + }, + { + "name": "assigned_at", + "title": "分配时间", + "type": "timestamp", + "nullable": false, + "comments": "任务分配时间" + }, + { + "name": "completed_at", + "title": "完成时间", + "type": "timestamp", + "nullable": true, + "comments": "任务完成时间" + }, + { + "name": "due_at", + "title": "截止时间", + "type": "timestamp", + "nullable": true, + "comments": "任务截止时间" + } + ], + "indexes": [ + { + "name": "idx_task_instance", + "idxtype": "index", + "fields": ["instance_id"] + }, + { + "name": "idx_task_approver", + "idxtype": "index", + "fields": ["approver_id"] + }, + { + "name": "idx_task_status", + "idxtype": "index", + "fields": ["status"] + } + ], + "codes": [ + { + "tblname": "appcodes", + "codevalue": "TASK_STATUS", + "valuefield": "k", + "textfield": "v" + } + ] +} \ No newline at end of file diff --git a/models/approval_workflow.json b/models/approval_workflow.json new file mode 100644 index 0000000..64a88de --- /dev/null +++ b/models/approval_workflow.json @@ -0,0 +1,97 @@ +{ + "summary": { + "name": "approval_workflow", + "label": "审批工作流", + "comment": "跨模块审批工作流定义" + }, + "fields": [ + { + "name": "id", + "title": "ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "主键UUID" + }, + { + "name": "workflow_name", + "title": "工作流名称", + "type": "str", + "length": 100, + "nullable": false, + "comments": "审批工作流名称" + }, + { + "name": "module_type", + "title": "模块类型", + "type": "str", + "length": 50, + "nullable": false, + "comments": "关联的模块类型(customer/opportunity/contract/financial)" + }, + { + "name": "trigger_condition", + "title": "触发条件", + "type": "str", + "length": 500, + "nullable": true, + "comments": "JSON格式的触发条件表达式" + }, + { + "name": "description", + "title": "描述", + "type": "str", + "length": 500, + "nullable": true, + "comments": "工作流描述" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "nullable": false, + "comments": "多租户组织隔离" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "nullable": false, + "comments": "创建时间" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "nullable": false, + "comments": "更新时间" + }, + { + "name": "is_active", + "title": "是否激活", + "type": "str", + "length": 1, + "nullable": false, + "comments": "Y/N" + } + ], + "indexes": [ + { + "name": "idx_workflow_org", + "idxtype": "index", + "fields": ["org_id"] + }, + { + "name": "idx_workflow_module", + "idxtype": "index", + "fields": ["module_type"] + }, + { + "name": "uk_workflow_name_org", + "idxtype": "unique", + "fields": ["workflow_name", "org_id"] + } + ], + "codes": [] +} \ No newline at end of file diff --git a/mysql.ddl.sql b/mysql.ddl.sql new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..129b3d6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "workflow-approval" +version = "1.0.0" +description = "Cross-module approval workflow management for integrated CRM application" +authors = [{name = "Hermes AI Agent", email = "hermes@ai-agent.com"}] +license = {text = "MIT"} +readme = "README.md" +requires-python = ">=3.8" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", +] +dependencies = [ + "ahserver>=1.0.0", + "sqlor-database-module>=1.0.0", + "bricks-framework>=1.0.0" +] + +[project.optional-dependencies] +dev = ["pytest>=6.0", "black", "flake8"] + +[tool.setuptools.packages.find] +where = ["."] +include = ["workflow_approval*"] \ No newline at end of file diff --git a/workflow_approval/core.py b/workflow_approval/core.py new file mode 100644 index 0000000..9934661 --- /dev/null +++ b/workflow_approval/core.py @@ -0,0 +1,228 @@ +from ahserver.serverenv import ServerEnv +from appPublic.worker import awaitify +import json +import uuid +from datetime import datetime, timedelta + +class WorkflowCore: + def __init__(self, db): + self.db = db + + async def create_workflow(self, workflow_data, org_id): + """创建审批工作流""" + workflow_id = str(uuid.uuid4()).replace('-', '') + workflow_data['id'] = workflow_id + workflow_data['org_id'] = org_id + workflow_data['created_at'] = datetime.now() + workflow_data['updated_at'] = datetime.now() + workflow_data['is_active'] = 'Y' + + await self.db.insert('approval_workflow', workflow_data) + return workflow_id + + async def create_approval_step(self, step_data, org_id): + """创建审批步骤""" + step_id = str(uuid.uuid4()).replace('-', '') + step_data['id'] = step_id + step_data['org_id'] = org_id + + await self.db.insert('approval_step', step_data) + return step_id + + async def start_approval_instance(self, module_type, module_record_id, + initiator_id, title, description, org_id): + """启动审批实例""" + # 查找适用的工作流 + workflows = await self.db.select('approval_workflow', + where={'module_type': module_type, 'org_id': org_id, 'is_active': 'Y'}) + + if not workflows: + raise Exception(f"No active workflow found for module: {module_type}") + + # 使用第一个匹配的工作流(实际可按条件筛选) + workflow = workflows[0] + instance_id = str(uuid.uuid4()).replace('-', '') + + instance_data = { + 'id': instance_id, + 'workflow_id': workflow['id'], + 'module_type': module_type, + 'module_record_id': module_record_id, + 'current_step_id': None, + 'status': 'pending', + 'initiator_id': initiator_id, + 'title': title, + 'description': description, + 'org_id': org_id, + 'created_at': datetime.now(), + 'completed_at': None + } + + await self.db.insert('approval_instance', instance_data) + + # 创建第一步的审批任务 + await self._create_next_step_tasks(instance_id, workflow['id'], org_id) + + return instance_id + + async def _create_next_step_tasks(self, instance_id, workflow_id, org_id): + """创建下一步的审批任务""" + # 获取当前已完成的步骤 + completed_steps = await self.db.select('approval_task', + fields=['step_id'], + where={'instance_id': instance_id, 'status': 'approved'}, + distinct=True + ) + completed_step_ids = [task['step_id'] for task in completed_steps] + + # 获取下一个未完成的步骤 + next_step = await self.db.select('approval_step', + where={ + 'workflow_id': workflow_id, + 'org_id': org_id + }, + order_by='step_order ASC' + ) + + if not next_step: + # 所有步骤完成,更新实例状态 + await self.db.update('approval_instance', + data={'status': 'approved', 'completed_at': datetime.now()}, + where={'id': instance_id} + ) + return + + current_step = None + for step in next_step: + if step['id'] not in completed_step_ids: + current_step = step + break + + if not current_step: + # 所有步骤完成 + await self.db.update('approval_instance', + data={'status': 'approved', 'completed_at': datetime.now()}, + where={'id': instance_id} + ) + return + + # 更新实例当前步骤 + await self.db.update('approval_instance', + data={'current_step_id': current_step['id']}, + where={'id': instance_id} + ) + + # 确定审批人 + approvers = await self._resolve_approvers(current_step, org_id) + + # 创建审批任务 + for approver_id in approvers: + task_id = str(uuid.uuid4()).replace('-', '') + due_at = None + if current_step.get('timeout_hours'): + due_at = datetime.now() + timedelta(hours=current_step['timeout_hours']) + + task_data = { + 'id': task_id, + 'instance_id': instance_id, + 'step_id': current_step['id'], + 'approver_id': approver_id, + 'status': 'pending', + 'decision': None, + 'org_id': org_id, + 'assigned_at': datetime.now(), + 'completed_at': None, + 'due_at': due_at + } + await self.db.insert('approval_task', task_data) + + async def _resolve_approvers(self, step, org_id): + """解析审批人""" + approver_type = step['approver_type'] + approver_value = step['approver_value'] + + if approver_type == 'user': + return [approver_value] + elif approver_type == 'role': + # 从RBAC获取角色用户 + from rbac.userperm import get_role_users + return await get_role_users(approver_value, org_id) + elif approver_type == 'department': + # 获取部门用户 + from rbac.userperm import get_department_users + return await get_department_users(approver_value, org_id) + elif approver_type == 'dynamic': + # 动态表达式解析(简化实现) + return self._evaluate_dynamic_expression(approver_value, org_id) + else: + return [] + + def _evaluate_dynamic_expression(self, expression, org_id): + """评估动态表达式(简化实现)""" + # 实际实现中可以使用更复杂的表达式引擎 + if expression == 'record_owner': + # 返回记录所有者(需要从具体模块获取) + return [] + elif expression == 'department_manager': + return [] + else: + return [] + + async def approve_task(self, task_id, approver_id, decision, org_id): + """审批任务""" + # 验证任务存在且属于当前用户 + task = await self.db.select_one('approval_task', + where={'id': task_id, 'approver_id': approver_id, 'org_id': org_id}) + + if not task or task['status'] != 'pending': + raise Exception("Invalid task or already processed") + + # 更新任务状态 + await self.db.update('approval_task', + data={ + 'status': 'approved', + 'decision': decision, + 'completed_at': datetime.now() + }, + where={'id': task_id} + ) + + # 检查是否所有并行任务都完成了 + instance_id = task['instance_id'] + step_id = task['step_id'] + pending_tasks = await self.db.select('approval_task', + where={ + 'instance_id': instance_id, + 'step_id': step_id, + 'status': 'pending' + } + ) + + if not pending_tasks: + # 当前步骤完成,创建下一步任务 + instance = await self.db.select_one('approval_instance', + where={'id': instance_id}) + await self._create_next_step_tasks(instance_id, instance['workflow_id'], org_id) + + async def reject_task(self, task_id, approver_id, decision, org_id): + """拒绝任务""" + await self.db.update('approval_task', + data={ + 'status': 'rejected', + 'decision': decision, + 'completed_at': datetime.now() + }, + where={'id': task_id, 'approver_id': approver_id, 'org_id': org_id} + ) + + # 更新实例状态为拒绝 + task = await self.db.select_one('approval_task', where={'id': task_id}) + await self.db.update('approval_instance', + data={'status': 'rejected', 'completed_at': datetime.now()}, + where={'id': task['instance_id']} + ) + +def load_workflow_approval(): + """加载审批模块""" + env = ServerEnv() + env.workflow_core = WorkflowCore \ No newline at end of file diff --git a/workflow_approval/init.py b/workflow_approval/init.py new file mode 100644 index 0000000..10018b8 --- /dev/null +++ b/workflow_approval/init.py @@ -0,0 +1,15 @@ +from ahserver.serverenv import ServerEnv +from appPublic.worker import awaitify +from .core import WorkflowCore + +def load_workflow_approval(): + """加载跨模块审批流程模块""" + env = ServerEnv() + + # 创建核心实例的工厂函数 + async def create_workflow_core(org_id): + from sqlor.dbp import getDBP + db = await getDBP(org_id) + return WorkflowCore(db) + + env.create_workflow_core = create_workflow_core \ No newline at end of file diff --git a/wwwroot/approval_task_detail.ui b/wwwroot/approval_task_detail.ui new file mode 100644 index 0000000..8aa3c25 --- /dev/null +++ b/wwwroot/approval_task_detail.ui @@ -0,0 +1,66 @@ +{ + "widgettype": "VBox", + "options": { + "maxWidth": "100%", + "padding": "15px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "审批详情", + "fontSize": "18px", + "fontWeight": "bold", + "marginBottom": "20px" + } + }, + { + "widgettype": "Form", + "options": { + "tblname": "approval_task", + "mode": "view", + "fields": ["title", "step_name", "description", "assigned_at", "due_at"], + "fieldLabels": { + "title": "审批事项", + "step_name": "审批步骤", + "description": "详细说明", + "assigned_at": "分配时间", + "due_at": "截止时间" + } + } + }, + { + "widgettype": "HBox", + "options": { + "marginTop": "30px", + "gap": "10px" + }, + "subwidgets": [ + { + "widgettype": "Button", + "options": { + "text": "批准", + "onClick": "approve_task", + "style": { + "backgroundColor": "#28a745", + "color": "white", + "flex": "1" + } + } + }, + { + "widgettype": "Button", + "options": { + "text": "拒绝", + "onClick": "reject_task", + "style": { + "backgroundColor": "#dc3545", + "color": "white", + "flex": "1" + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/wwwroot/mobile_base.ui b/wwwroot/mobile_base.ui new file mode 100644 index 0000000..ded2a81 --- /dev/null +++ b/wwwroot/mobile_base.ui @@ -0,0 +1,98 @@ +{ + "widgettype": "VBox", + "options": { + "maxWidth": "100%", + "padding": "10px" + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": { + "alignItems": "center", + "justifyContent": "space-between", + "marginBottom": "15px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "审批中心", + "fontSize": "20px", + "fontWeight": "bold" + } + }, + { + "widgettype": "Button", + "options": { + "text": "发起审批", + "onClick": "goto('workflow_approval/approval_instance/create.ui')", + "style": { + "backgroundColor": "#007bff", + "color": "white", + "border": "none", + "padding": "8px 16px", + "borderRadius": "4px" + } + } + } + ] + }, + { + "widgettype": "TabPanel", + "options": { + "tabs": [ + { + "title": "待我审批", + "content": { + "widgettype": "DataGrid", + "options": { + "tblname": "approval_task", + "where": { + "approver_id": "${logined_userid}", + "status": "pending" + }, + "fields": ["title", "step_name", "assigned_at", "due_at"], + "fieldLabels": { + "title": "审批事项", + "step_name": "步骤", + "assigned_at": "分配时间", + "due_at": "截止时间" + }, + "onRowClick": "goto('workflow_approval/approval_task/detail.ui?id=${id}')" + } + } + }, + { + "title": "我发起的", + "content": { + "widgettype": "DataGrid", + "options": { + "tblname": "approval_instance", + "where": { + "initiator_id": "${logined_userid}" + }, + "fields": ["title", "status", "created_at", "completed_at"], + "fieldLabels": { + "title": "审批事项", + "status": "状态", + "created_at": "发起时间", + "completed_at": "完成时间" + }, + "onRowClick": "goto('workflow_approval/approval_instance/detail.ui?id=${id}')" + } + } + }, + { + "title": "工作流管理", + "content": { + "widgettype": "Frame", + "options": { + "src": "workflow_approval/approval_workflow/list.ui" + } + } + } + ] + } + } + ] +} \ No newline at end of file