diff --git a/models/approval_instance.json b/models/approval_instance.json index c6cb71f..c2ecea0 100644 --- a/models/approval_instance.json +++ b/models/approval_instance.json @@ -1,137 +1,27 @@ { - "summary": [ - { - "name": "approval_instance", - "title": "审批实例", - "primary": "id", - "catelog": "entity" - } - ], + "table_name": "approval_instance", "fields": [ - { - "name": "id", - "title": "ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "主键UUID" - }, - { - "name": "workflow_id", - "title": "工作流ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "关联的工作流定义" - }, - { - "name": "module_type", - "title": "模块类型", - "type": "str", - "length": 50, - "nullable": "no", - "comments": "customer/opportunity/contract/financial" - }, - { - "name": "module_record_id", - "title": "模块记录ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "关联的具体业务记录ID" - }, - { - "name": "current_step_id", - "title": "当前步骤ID", - "type": "str", - "length": 32, - "nullable": "yes", - "comments": "当前待审批的步骤" - }, - { - "name": "status", - "title": "状态", - "type": "str", - "length": 20, - "nullable": "no", - "comments": "pending/approved/rejected/cancelled" - }, - { - "name": "initiator_id", - "title": "发起人ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "审批发起人用户ID" - }, - { - "name": "title", - "title": "标题", - "type": "str", - "length": 200, - "nullable": "no", - "comments": "审批标题" - }, - { - "name": "description", - "title": "描述", - "type": "str", - "length": 1000, - "nullable": "yes", - "comments": "审批详细描述" - }, - { - "name": "org_id", - "title": "组织ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "多租户组织隔离" - }, - { - "name": "created_at", - "title": "创建时间", - "type": "timestamp", - "nullable": "no", - "comments": "创建时间" - }, - { - "name": "completed_at", - "title": "完成时间", - "type": "timestamp", - "nullable": "yes", - "comments": "完成时间" - } + {"name": "id", "type": "varchar(32)", "not_null": true, "comment": "主键ID"}, + {"name": "workflow_id", "type": "varchar(32)", "not_null": true, "comment": "工作流ID"}, + {"name": "business_type", "type": "varchar(50)", "not_null": true, "comment": "业务类型: contract/customer/opportunity"}, + {"name": "business_id", "type": "varchar(64)", "not_null": true, "comment": "业务记录ID"}, + {"name": "current_step", "type": "int", "comment": "当前步骤号"}, + {"name": "status", "type": "varchar(20)", "comment": "状态: pending/approved/rejected/cancelled"}, + {"name": "initiator_id", "type": "varchar(32)", "comment": "发起人ID"}, + {"name": "org_id", "type": "varchar(32)", "comment": "组织ID"}, + {"name": "initiated_at", "type": "timestamp", "comment": "发起时间"}, + {"name": "completed_at", "type": "timestamp", "comment": "完成时间"} ], "indexes": [ - { - "name": "idx_instance_workflow", - "idxtype": "index", - "idxfields": ["workflow_id"] - }, - { - "name": "idx_instance_module", - "idxtype": "index", - "idxfields": ["module_type", "module_record_id"] - }, - { - "name": "idx_instance_status", - "idxtype": "index", - "idxfields": ["status"] - }, - { - "name": "idx_instance_org", - "idxtype": "index", - "idxfields": ["org_id"] - } + {"name": "idx_instance_workflow", "fields": ["workflow_id"], "type": "normal"}, + {"name": "idx_instance_business", "fields": ["business_type", "business_id"], "type": "normal", "comment": "复合索引:按业务类型和记录查询"}, + {"name": "idx_instance_status", "fields": ["status"], "type": "normal"}, + {"name": "idx_instance_initiator", "fields": ["initiator_id"], "type": "normal"} ], "codes": [ - { - "field": "status", - "table": "appcodes", - "valuefield": "k", - "textfield": "v", - "cond": "codetype='APPROVAL_STATUS'" - } + {"key": "pending", "name": "审批中"}, + {"key": "approved", "name": "已通过"}, + {"key": "rejected", "name": "已驳回"}, + {"key": "cancelled", "name": "已取消"} ] } \ No newline at end of file diff --git a/models/approval_step.json b/models/approval_step.json index 8c93d41..bf13b67 100644 --- a/models/approval_step.json +++ b/models/approval_step.json @@ -1,118 +1,21 @@ { - "summary": [ - { - "name": "approval_step", - "title": "审批步骤", - "primary": "id", - "catelog": "entity" - } - ], + "table_name": "approval_step", "fields": [ - { - "name": "id", - "title": "ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "主键UUID" - }, - { - "name": "workflow_id", - "title": "工作流ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "关联的工作流" - }, - { - "name": "step_name", - "title": "步骤名称", - "type": "str", - "length": 100, - "nullable": "no", - "comments": "审批步骤名称" - }, - { - "name": "step_order", - "title": "步骤顺序", - "type": "long", - "nullable": "no", - "comments": "步骤执行顺序" - }, - { - "name": "approver_type", - "title": "审批人类型", - "type": "str", - "length": 20, - "nullable": "no", - "comments": "role/user/department/dynamic" - }, - { - "name": "approver_value", - "title": "审批人值", - "type": "str", - "length": 100, - "nullable": "yes", - "comments": "角色ID/用户ID/部门ID/动态表达式" - }, - { - "name": "approval_type", - "title": "审批类型", - "type": "str", - "length": 20, - "nullable": "no", - "comments": "single/multiple/sequential/parallel" - }, - { - "name": "timeout_hours", - "title": "超时小时数", - "type": "long", - "nullable": "yes", - "comments": "审批超时时间(小时)" - }, - { - "name": "description", - "title": "描述", - "type": "str", - "length": 500, - "nullable": "yes", - "comments": "步骤描述" - }, - { - "name": "org_id", - "title": "组织ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "多租户组织隔离" - } + {"name": "id", "type": "varchar(32)", "not_null": true, "comment": "主键ID"}, + {"name": "workflow_id", "type": "varchar(32)", "not_null": true, "comment": "所属工作流ID"}, + {"name": "step_name", "type": "varchar(100)", "not_null": true, "comment": "步骤名称"}, + {"name": "step_number", "type": "int", "not_null": true, "comment": "步骤顺序号"}, + {"name": "approver_role", "type": "varchar(20)", "comment": "审批角色: role/user"}, + {"name": "approver_id", "type": "varchar(32)", "comment": "审批人ID"}, + {"name": "description", "type": "varchar(500)", "comment": "步骤描述"}, + {"name": "timeout_hours", "type": "int", "comment": "超时小时数"} ], "indexes": [ - { - "name": "idx_step_workflow", - "idxtype": "index", - "idxfields": ["workflow_id"] - }, - { - "name": "idx_step_order", - "idxtype": "index", - "idxfields": ["workflow_id", "step_order"] - } + {"name": "idx_step_workflow", "fields": ["workflow_id"], "type": "normal"}, + {"name": "idx_step_order", "fields": ["workflow_id", "step_number"], "type": "normal", "comment": "复合索引:按工作流和顺序查询"} ], "codes": [ - { - "field": "approver_type", - "table": "appcodes", - "valuefield": "k", - "textfield": "v", - "cond": "codetype='APPROVER_TYPE'" - }, - { - "field": "approval_type", - "table": "appcodes", - "valuefield": "k", - "textfield": "v", - "cond": "codetype='APPROVAL_TYPE'" - } + {"key": "role", "name": "角色审批"}, + {"key": "user", "name": "指定人审批"} ] } \ No newline at end of file diff --git a/models/approval_task.json b/models/approval_task.json index 8c64f10..d7d6023 100644 --- a/models/approval_task.json +++ b/models/approval_task.json @@ -1,115 +1,24 @@ { - "summary": [ - { - "name": "approval_task", - "title": "审批任务", - "primary": "id", - "catelog": "entity" - } - ], + "table_name": "approval_task", "fields": [ - { - "name": "id", - "title": "ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "主键UUID" - }, - { - "name": "instance_id", - "title": "实例ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "关联的审批实例" - }, - { - "name": "step_id", - "title": "步骤ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "关联的审批步骤" - }, - { - "name": "approver_id", - "title": "审批人ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "具体审批人用户ID" - }, - { - "name": "status", - "title": "状态", - "type": "str", - "length": 20, - "nullable": "no", - "comments": "pending/approved/rejected" - }, - { - "name": "decision", - "title": "决策", - "type": "str", - "length": 1000, - "nullable": "yes", - "comments": "审批意见" - }, - { - "name": "org_id", - "title": "组织ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "多租户组织隔离" - }, - { - "name": "assigned_at", - "title": "分配时间", - "type": "timestamp", - "nullable": "no", - "comments": "任务分配时间" - }, - { - "name": "completed_at", - "title": "完成时间", - "type": "timestamp", - "nullable": "yes", - "comments": "任务完成时间" - }, - { - "name": "due_at", - "title": "截止时间", - "type": "timestamp", - "nullable": "yes", - "comments": "任务截止时间" - } + {"name": "id", "type": "varchar(32)", "not_null": true, "comment": "主键ID"}, + {"name": "instance_id", "type": "varchar(32)", "not_null": true, "comment": "所属实例ID"}, + {"name": "step_id", "type": "varchar(32)", "not_null": true, "comment": "审批步骤ID"}, + {"name": "assignee_id", "type": "varchar(32)", "comment": "审批人ID"}, + {"name": "status", "type": "varchar(20)", "comment": "状态: pending/approved/rejected"}, + {"name": "comment", "type": "varchar(1000)", "comment": "审批意见"}, + {"name": "org_id", "type": "varchar(32)", "comment": "组织ID"}, + {"name": "assigned_at", "type": "timestamp", "comment": "分配时间"}, + {"name": "completed_at", "type": "timestamp", "comment": "完成时间"} ], "indexes": [ - { - "name": "idx_task_instance", - "idxtype": "index", - "idxfields": ["instance_id"] - }, - { - "name": "idx_task_approver", - "idxtype": "index", - "idxfields": ["approver_id"] - }, - { - "name": "idx_task_status", - "idxtype": "index", - "idxfields": ["status"] - } + {"name": "idx_task_instance", "fields": ["instance_id"], "type": "normal"}, + {"name": "idx_task_assignee", "fields": ["assignee_id"], "type": "normal"}, + {"name": "idx_task_status", "fields": ["status"], "type": "normal"} ], "codes": [ - { - "field": "status", - "table": "appcodes", - "valuefield": "k", - "textfield": "v", - "cond": "codetype='TASK_STATUS'" - } + {"key": "pending", "name": "待审批"}, + {"key": "approved", "name": "已通过"}, + {"key": "rejected", "name": "已驳回"} ] } \ No newline at end of file diff --git a/models/approval_workflow.json b/models/approval_workflow.json index 4aebc32..faa04e0 100644 --- a/models/approval_workflow.json +++ b/models/approval_workflow.json @@ -1,100 +1,24 @@ { - "summary": [ - { - "name": "approval_workflow", - "title": "审批工作流", - "primary": "id", - "catelog": "entity" - } - ], + "table_name": "approval_workflow", "fields": [ - { - "name": "id", - "title": "ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "主键UUID" - }, - { - "name": "workflow_name", - "title": "工作流名称", - "type": "str", - "length": 100, - "nullable": "no", - "comments": "审批工作流名称" - }, - { - "name": "module_type", - "title": "模块类型", - "type": "str", - "length": 50, - "nullable": "no", - "comments": "关联的模块类型(customer/opportunity/contract/financial)" - }, - { - "name": "trigger_condition", - "title": "触发条件", - "type": "str", - "length": 500, - "nullable": "yes", - "comments": "JSON格式的触发条件表达式" - }, - { - "name": "description", - "title": "描述", - "type": "str", - "length": 500, - "nullable": "yes", - "comments": "工作流描述" - }, - { - "name": "org_id", - "title": "组织ID", - "type": "str", - "length": 32, - "nullable": "no", - "comments": "多租户组织隔离" - }, - { - "name": "created_at", - "title": "创建时间", - "type": "timestamp", - "nullable": "no", - "comments": "创建时间" - }, - { - "name": "updated_at", - "title": "更新时间", - "type": "timestamp", - "nullable": "no", - "comments": "更新时间" - }, - { - "name": "is_active", - "title": "是否激活", - "type": "str", - "length": 1, - "nullable": "no", - "comments": "Y/N" - } + {"name": "id", "type": "varchar(32)", "not_null": true, "comment": "主键ID"}, + {"name": "name", "type": "varchar(100)", "not_null": true, "comment": "工作流名称"}, + {"name": "module", "type": "varchar(50)", "not_null": true, "comment": "所属模块"}, + {"name": "trigger_event", "type": "varchar(100)", "comment": "触发事件"}, + {"name": "description", "type": "text", "comment": "描述"}, + {"name": "org_id", "type": "varchar(32)", "comment": "组织ID"}, + {"name": "status", "type": "varchar(20)", "default": "Y", "comment": "状态 Y-启用 N-禁用"}, + {"name": "created_at", "type": "timestamp", "comment": "创建时间"}, + {"name": "updated_at", "type": "timestamp", "comment": "更新时间"} ], "indexes": [ - { - "name": "idx_workflow_org", - "idxtype": "index", - "idxfields": ["org_id"] - }, - { - "name": "idx_workflow_module", - "idxtype": "index", - "idxfields": ["module_type"] - }, - { - "name": "uk_workflow_name_org", - "idxtype": "unique", - "idxfields": ["workflow_name", "org_id"] - } + {"name": "idx_workflow_module", "fields": ["module"], "type": "normal"}, + {"name": "idx_workflow_org", "fields": ["org_id"], "type": "normal"}, + {"name": "idx_workflow_status", "fields": ["status"], "type": "normal"} ], - "codes": [] + "codes": [ + {"key": "contract_approval", "name": "合同审批"}, + {"key": "customer_approval", "name": "客户审批"}, + {"key": "opportunity_approval", "name": "商机审批"} + ] } \ No newline at end of file diff --git a/mysql.ddl.sql b/mysql.ddl.sql index e69de29..6d67fb1 100644 --- a/mysql.ddl.sql +++ b/mysql.ddl.sql @@ -0,0 +1,76 @@ +-- Table from approval_instance.json +CREATE TABLE IF NOT EXISTS `approval_instance` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键UUID', + `workflow_id` VARCHAR(32) NOT NULL COMMENT '关联的工作流定义', + `module_type` VARCHAR(50) NOT NULL COMMENT 'customer/opportunity/contract/financial', + `module_record_id` VARCHAR(32) NOT NULL COMMENT '关联的具体业务记录ID', + `current_step_id` VARCHAR(32) COMMENT '当前待审批的步骤', + `status` VARCHAR(20) NOT NULL COMMENT 'pending/approved/rejected/cancelled', + `initiator_id` VARCHAR(32) NOT NULL COMMENT '审批发起人用户ID', + `title` VARCHAR(200) NOT NULL COMMENT '审批标题', + `description` VARCHAR(1000) COMMENT '审批详细描述', + `org_id` VARCHAR(32) NOT NULL COMMENT '多租户组织隔离', + `created_at` TIMESTAMP NOT NULL COMMENT '创建时间', + `completed_at` TIMESTAMP COMMENT '完成时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批实例'; + +CREATE INDEX `idx_instance_workflow` ON `approval_instance` (`workflow_id`); +CREATE INDEX `idx_instance_module` ON `approval_instance` (`module_type`, `module_record_id`); +CREATE INDEX `idx_instance_status` ON `approval_instance` (`status`); +CREATE INDEX `idx_instance_org` ON `approval_instance` (`org_id`); + +-- Table from approval_step.json +CREATE TABLE IF NOT EXISTS `approval_step` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键UUID', + `workflow_id` VARCHAR(32) NOT NULL COMMENT '关联的工作流', + `step_name` VARCHAR(100) NOT NULL COMMENT '审批步骤名称', + `step_order` INT NOT NULL COMMENT '步骤执行顺序', + `approver_type` VARCHAR(20) NOT NULL COMMENT 'role/user/department/dynamic', + `approver_value` VARCHAR(100) COMMENT '角色ID/用户ID/部门ID/动态表达式', + `approval_type` VARCHAR(20) NOT NULL COMMENT 'single/multiple/sequential/parallel', + `timeout_hours` INT COMMENT '审批超时时间(小时)', + `description` VARCHAR(500) COMMENT '步骤描述', + `org_id` VARCHAR(32) NOT NULL COMMENT '多租户组织隔离', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批步骤'; + +CREATE INDEX `idx_step_workflow` ON `approval_step` (`workflow_id`); +CREATE INDEX `idx_step_order` ON `approval_step` (`workflow_id`, `step_order`); + +-- Table from approval_task.json +CREATE TABLE IF NOT EXISTS `approval_task` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键UUID', + `instance_id` VARCHAR(32) NOT NULL COMMENT '关联的审批实例', + `step_id` VARCHAR(32) NOT NULL COMMENT '关联的审批步骤', + `approver_id` VARCHAR(32) NOT NULL COMMENT '具体审批人用户ID', + `status` VARCHAR(20) NOT NULL COMMENT 'pending/approved/rejected', + `decision` VARCHAR(1000) COMMENT '审批意见', + `org_id` VARCHAR(32) NOT NULL COMMENT '多租户组织隔离', + `assigned_at` TIMESTAMP NOT NULL COMMENT '任务分配时间', + `completed_at` TIMESTAMP COMMENT '任务完成时间', + `due_at` TIMESTAMP COMMENT '任务截止时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批任务'; + +CREATE INDEX `idx_task_instance` ON `approval_task` (`instance_id`); +CREATE INDEX `idx_task_approver` ON `approval_task` (`approver_id`); +CREATE INDEX `idx_task_status` ON `approval_task` (`status`); + +-- Table from approval_workflow.json +CREATE TABLE IF NOT EXISTS `approval_workflow` ( + `id` VARCHAR(32) NOT NULL COMMENT '主键UUID', + `workflow_name` VARCHAR(100) NOT NULL COMMENT '审批工作流名称', + `module_type` VARCHAR(50) NOT NULL COMMENT '关联的模块类型(customer/opportunity/contract/financial)', + `trigger_condition` VARCHAR(500) COMMENT 'JSON格式的触发条件表达式', + `description` VARCHAR(500) COMMENT '工作流描述', + `org_id` VARCHAR(32) NOT NULL COMMENT '多租户组织隔离', + `created_at` TIMESTAMP NOT NULL COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL COMMENT '更新时间', + `is_active` VARCHAR(1) NOT NULL COMMENT 'Y/N', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='审批工作流'; + +CREATE INDEX `idx_workflow_org` ON `approval_workflow` (`org_id`); +CREATE INDEX `idx_workflow_module` ON `approval_workflow` (`module_type`); +CREATE UNIQUE INDEX `uk_workflow_name_org` ON `approval_workflow` (`workflow_name`, `org_id`); diff --git a/workflow_approval/__init__.py b/workflow_approval/__init__.py new file mode 100644 index 0000000..ca5c5cc --- /dev/null +++ b/workflow_approval/__init__.py @@ -0,0 +1 @@ +# Workflow Approval Module diff --git a/workflow_approval/init.py b/workflow_approval/init.py index 10018b8..a2eb99d 100644 --- a/workflow_approval/init.py +++ b/workflow_approval/init.py @@ -1,6 +1,14 @@ from ahserver.serverenv import ServerEnv from appPublic.worker import awaitify from .core import WorkflowCore +from sqlor.dbpools import DBPools + + +def get_workflow_dbname(): + """获取workflow_approval模块使用的数据库名""" + env = ServerEnv() + return env.get_module_dbname('workflow_approval') + def load_workflow_approval(): """加载跨模块审批流程模块""" @@ -8,8 +16,10 @@ def load_workflow_approval(): # 创建核心实例的工厂函数 async def create_workflow_core(org_id): - from sqlor.dbp import getDBP - db = await getDBP(org_id) - return WorkflowCore(db) + db = DBPools() + dbname = env.get_module_dbname('workflow_approval') + sor = await db.sqlorContext(dbname) + return WorkflowCore(sor) - env.create_workflow_core = create_workflow_core \ No newline at end of file + env.create_workflow_core = create_workflow_core + env.get_workflow_dbname = get_workflow_dbname \ No newline at end of file diff --git a/wwwroot/api/instance_create.dspy b/wwwroot/api/instance_create.dspy new file mode 100644 index 0000000..703e6c1 --- /dev/null +++ b/wwwroot/api/instance_create.dspy @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Create approval instance""" +import json, time, uuid + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + workflow_id = params_kw.get('workflow_id', '') + module_type = params_kw.get('module_type', '') + module_record_id = params_kw.get('module_record_id', '') + title = params_kw.get('title', '') + description = params_kw.get('description', '') + + if not workflow_id or not module_type or not module_record_id or not title: + result['options'] = {'title': 'Error', 'message': '工作流ID、模块类型、记录ID和标题不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + new_id = str(uuid.uuid4()).replace('-', '')[:32] + now = time.strftime('%Y-%m-%d %H:%M:%S') + org_id = '0' + initiator_id = '0' + + async with DBPools().sqlorContext(dbname) as sor: + workflows = await sor.sqlExe("SELECT id FROM approval_workflow WHERE id=${id}$ AND status='Y'", {'id': workflow_id}) + if not workflows: + result['options'] = {'title': 'Error', 'message': '工作流不存在或未激活', 'type': 'error'} + return json.dumps(result, ensure_ascii=False) + + first_step = await sor.sqlExe("SELECT id, step_number FROM approval_step WHERE workflow_id=${workflow_id}$ ORDER BY step_number ASC LIMIT 1", {'workflow_id': workflow_id}) + + current_step_num = first_step[0]['step_number'] if first_step else 0 + first_step_id = first_step[0]['id'] if first_step else '' + + await sor.sqlExe(""" + INSERT INTO approval_instance (id, workflow_id, business_type, business_id, current_step, status, initiator_id, org_id, initiated_at) + VALUES (${id}$, ${workflow_id}$, ${business_type}$, ${business_id}$, ${current_step}$, 'pending', ${initiator_id}$, ${org_id}$, ${initiated_at}$) + """, { + 'id': new_id, 'workflow_id': workflow_id, 'business_type': module_type, + 'business_id': module_record_id, 'current_step': current_step_num, + 'initiator_id': initiator_id, 'org_id': org_id, 'initiated_at': now + }) + + if first_step_id: + task_id = str(uuid.uuid4()).replace('-', '')[:32] + async with DBPools().sqlorContext(dbname) as sor2: + await sor2.sqlExe(""" + INSERT INTO approval_task (id, instance_id, step_id, assignee_id, status, org_id, assigned_at) + VALUES (${id}$, ${instance_id}$, ${step_id}$, ${assignee_id}$, 'pending', ${org_id}$, ${assigned_at}$) + """, { + 'id': task_id, 'instance_id': new_id, 'step_id': first_step_id, + 'assignee_id': initiator_id, + 'org_id': org_id, 'assigned_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/instance_list.dspy b/wwwroot/api/instance_list.dspy new file mode 100644 index 0000000..788c41f --- /dev/null +++ b/wwwroot/api/instance_list.dspy @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""List approval instances""" +import json + +result = {'success': False, 'rows': [], 'total': 0} + +try: + dbname = get_module_dbname('workflow_approval') + ns = { + 'page': int(params_kw.get('page', 1)), + 'rows': int(params_kw.get('rows', 20)), + 'sort': 'id' + } + sql = """ + SELECT ai.id, ai.workflow_id, aw.name as workflow_name, ai.business_type as module_type, ai.business_id as module_record_id, + ai.current_step as current_step_id, ai.status, ai.initiator_id, ai.org_id, + ai.initiated_at as created_at, ai.completed_at + FROM approval_instance ai + LEFT JOIN approval_workflow aw ON ai.workflow_id = aw.id + """ + + 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/step_create.dspy b/wwwroot/api/step_create.dspy new file mode 100644 index 0000000..c7ca334 --- /dev/null +++ b/wwwroot/api/step_create.dspy @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Create approval step""" +import json, uuid + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + workflow_id = params_kw.get('workflow_id', '') + step_name = params_kw.get('step_name', '') + step_order = params_kw.get('step_order', '1') + approver_type = params_kw.get('approver_type', '') + approver_value = params_kw.get('approver_value', '') + description = params_kw.get('description', '') + timeout_hours = params_kw.get('timeout_hours', '') + + if not workflow_id or not step_name: + result['options'] = {'title': 'Error', 'message': '工作流ID和步骤名称不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + new_id = str(uuid.uuid4()).replace('-', '')[:32] + org_id = '0' + + async with DBPools().sqlorContext(dbname) as sor: + await sor.sqlExe(""" + INSERT INTO approval_step (id, workflow_id, step_name, step_number, approver_role, approver_id, description, timeout_hours) + VALUES (${id}$, ${workflow_id}$, ${step_name}$, ${step_number}$, ${approver_role}$, ${approver_id}$, ${description}$, ${timeout_hours}$) + """, { + 'id': new_id, 'workflow_id': workflow_id, 'step_name': step_name, + 'step_number': int(step_order), 'approver_role': approver_type, + 'approver_id': approver_value, + 'description': description, + 'timeout_hours': int(timeout_hours) if timeout_hours else None + }) + + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '步骤创建成功', 'type': 'success'}} +except Exception as e: + result['options'] = {'title': 'Error', 'message': f'创建失败: {str(e)}', 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/step_delete.dspy b/wwwroot/api/step_delete.dspy new file mode 100644 index 0000000..038257e --- /dev/null +++ b/wwwroot/api/step_delete.dspy @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Delete approval step""" +import json + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + record_id = params_kw.get('id', '') + if not record_id: + result['options'] = {'title': 'Error', 'message': '记录ID不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + async with DBPools().sqlorContext(dbname) as sor: + existing = await sor.sqlExe("SELECT id FROM approval_step WHERE id=${id}$", {'id': record_id}) + if not existing: + result['options'] = {'title': 'Error', 'message': '步骤不存在', 'type': 'error'} + else: + await sor.sqlExe("DELETE FROM approval_step 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/step_list.dspy b/wwwroot/api/step_list.dspy new file mode 100644 index 0000000..eec291e --- /dev/null +++ b/wwwroot/api/step_list.dspy @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""List approval steps""" +import json + +result = {'success': False, 'rows': [], 'total': 0} + +try: + dbname = get_module_dbname('workflow_approval') + workflow_id = params_kw.get('workflow_id', '') + + sql = "SELECT id, workflow_id, step_name, step_number as step_order, approver_role as approver_type, approver_id as approver_value, description, timeout_hours FROM approval_step" + ns = { + 'page': int(params_kw.get('page', 1)), + 'rows': int(params_kw.get('rows', 50)), + 'sort': 'id' + } + if workflow_id: + ns['where'] = f"workflow_id='{workflow_id}'" + + 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/step_update.dspy b/wwwroot/api/step_update.dspy new file mode 100644 index 0000000..fef549b --- /dev/null +++ b/wwwroot/api/step_update.dspy @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Update approval step""" +import json + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + record_id = params_kw.get('id', '') + workflow_id = params_kw.get('workflow_id', '') + step_name = params_kw.get('step_name', '') + step_order = params_kw.get('step_order', '1') + approver_type = params_kw.get('approver_type', '') + approver_value = params_kw.get('approver_value', '') + description = params_kw.get('description', '') + timeout_hours = params_kw.get('timeout_hours', '') + + if not record_id: + result['options'] = {'title': 'Error', 'message': '记录ID不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + + async with DBPools().sqlorContext(dbname) as sor: + existing = await sor.sqlExe("SELECT id FROM approval_step WHERE id=${id}$", {'id': record_id}) + if not existing: + result['options'] = {'title': 'Error', 'message': '步骤不存在', 'type': 'error'} + else: + await sor.sqlExe(""" + UPDATE approval_step SET workflow_id=${workflow_id}$, step_name=${step_name}$, + step_number=${step_number}$, approver_role=${approver_role}$, approver_id=${approver_id}$, + description=${description}$, timeout_hours=${timeout_hours}$ + WHERE id=${id}$ + """, { + 'id': record_id, 'workflow_id': workflow_id, 'step_name': step_name, + 'step_number': int(step_order), 'approver_role': approver_type, + 'approver_id': approver_value, + 'description': description, + 'timeout_hours': int(timeout_hours) if timeout_hours else None + }) + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '步骤更新成功', 'type': 'success'}} +except Exception as e: + result['options'] = {'title': 'Error', 'message': f'更新失败: {str(e)}', 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/task_approve.dspy b/wwwroot/api/task_approve.dspy new file mode 100644 index 0000000..f1583a4 --- /dev/null +++ b/wwwroot/api/task_approve.dspy @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Approve an approval task""" +import json, time, uuid + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + task_id = params_kw.get('id', '') + decision = params_kw.get('decision', '') + + if not task_id: + result['options'] = {'title': 'Error', 'message': '任务ID不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + now = time.strftime('%Y-%m-%d %H:%M:%S') + org_id = '0' + + async with DBPools().sqlorContext(dbname) as sor: + tasks = await sor.sqlExe("SELECT * FROM approval_task WHERE id=${id}$ AND status='pending'", {'id': task_id}) + if not tasks: + result['options'] = {'title': 'Error', 'message': '任务不存在或已处理', 'type': 'error'} + return json.dumps(result, ensure_ascii=False) + + task = tasks[0] + instance_id = task['instance_id'] + step_id = task['step_id'] + + await sor.sqlExe(""" + UPDATE approval_task SET status='approved', comment=${comment}$, completed_at=${completed_at}$ + WHERE id=${id}$ + """, {'id': task_id, 'comment': decision, 'completed_at': now}) + + pending = await sor.sqlExe("SELECT id FROM approval_task WHERE instance_id=${instance_id}$ AND step_id=${step_id}$ AND status='pending'", {'instance_id': instance_id, 'step_id': step_id}) + + if not pending: + current_step_order = await sor.sqlExe(""" + SELECT step_number FROM approval_step WHERE id=${step_id}$ + """, {'step_id': step_id}) + + if current_step_order: + order = current_step_order[0]['step_number'] + next_step = await sor.sqlExe(""" + SELECT id, approver_id FROM approval_step WHERE workflow_id=(SELECT workflow_id FROM approval_step WHERE id=${step_id}$) + AND step_number > ${order}$ ORDER BY step_number ASC LIMIT 1 + """, {'step_id': step_id, 'order': order}) + + if next_step: + ns = next_step[0] + new_task_id = str(uuid.uuid4()).replace('-', '')[:32] + await sor.sqlExe(""" + UPDATE approval_instance SET current_step=${step_id}$ WHERE id=${instance_id}$ + """, {'step_id': ns['id'], 'instance_id': instance_id}) + + await sor.sqlExe(""" + INSERT INTO approval_task (id, instance_id, step_id, assignee_id, status, org_id, assigned_at) + VALUES (${id}$, ${instance_id}$, ${step_id}$, ${assignee_id}$, 'pending', ${org_id}$, ${assigned_at}$) + """, { + 'id': new_task_id, 'instance_id': instance_id, 'step_id': ns['id'], + 'assignee_id': ns.get('approver_id', '0'), + 'org_id': org_id, 'assigned_at': now + }) + else: + await sor.sqlExe(""" + UPDATE approval_instance SET status='approved', completed_at=${completed_at}$ WHERE id=${instance_id}$ + """, {'completed_at': now, 'instance_id': instance_id}) + else: + await sor.sqlExe(""" + UPDATE approval_instance SET status='approved', completed_at=${completed_at}$ WHERE id=${instance_id}$ + """, {'completed_at': now, 'instance_id': instance_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/task_list.dspy b/wwwroot/api/task_list.dspy new file mode 100644 index 0000000..d8dbbbe --- /dev/null +++ b/wwwroot/api/task_list.dspy @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""List approval tasks""" +import json + +result = {'success': False, 'rows': [], 'total': 0} + +try: + dbname = get_module_dbname('workflow_approval') + ns = { + 'page': int(params_kw.get('page', 1)), + 'rows': int(params_kw.get('rows', 20)), + 'sort': 'assigned_at desc' + } + sql = """ + SELECT at.id, at.instance_id, at.step_id, at.assignee_id as approver_id, at.status, at.comment as decision, + at.org_id, at.assigned_at, at.completed_at, + ai.business_type as module_type, ai.business_id as module_record_id, + ast.step_name, ast.approver_role as approver_type + FROM approval_task at + LEFT JOIN approval_instance ai ON at.instance_id = ai.id + LEFT JOIN approval_step ast ON at.step_id = ast.id + """ + + 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/task_reject.dspy b/wwwroot/api/task_reject.dspy new file mode 100644 index 0000000..722b085 --- /dev/null +++ b/wwwroot/api/task_reject.dspy @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Reject an approval task""" +import json, time + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + task_id = params_kw.get('id', '') + decision = params_kw.get('decision', '') + + if not task_id: + result['options'] = {'title': 'Error', 'message': '任务ID不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + now = time.strftime('%Y-%m-%d %H:%M:%S') + + async with DBPools().sqlorContext(dbname) as sor: + tasks = await sor.sqlExe("SELECT * FROM approval_task WHERE id=${id}$ AND status='pending'", {'id': task_id}) + if not tasks: + result['options'] = {'title': 'Error', 'message': '任务不存在或已处理', 'type': 'error'} + return json.dumps(result, ensure_ascii=False) + + task = tasks[0] + instance_id = task['instance_id'] + + await sor.sqlExe(""" + UPDATE approval_task SET status='rejected', comment=${comment}$, completed_at=${completed_at}$ + WHERE id=${id}$ + """, {'id': task_id, 'comment': decision, 'completed_at': now}) + + await sor.sqlExe(""" + UPDATE approval_instance SET status='rejected', completed_at=${completed_at}$ WHERE id=${instance_id}$ + """, {'completed_at': now, 'instance_id': instance_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/workflow_create.dspy b/wwwroot/api/workflow_create.dspy new file mode 100644 index 0000000..a2133b1 --- /dev/null +++ b/wwwroot/api/workflow_create.dspy @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Create approval workflow""" +import json, time, uuid + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + workflow_name = params_kw.get('workflow_name', '') + module_type = params_kw.get('module_type', '') + trigger_condition = params_kw.get('trigger_condition', '') + description = params_kw.get('description', '') + is_active = params_kw.get('is_active', 'Y') + + if not workflow_name or not module_type: + result['options'] = {'title': 'Error', 'message': '工作流名称和模块类型不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + new_id = str(uuid.uuid4()).replace('-', '')[:32] + now = time.strftime('%Y-%m-%d %H:%M:%S') + org_id = '0' + + async with DBPools().sqlorContext(dbname) as sor: + await sor.sqlExe(""" + INSERT INTO approval_workflow (id, name, module, trigger_event, description, org_id, status, created_at, updated_at) + VALUES (${id}$, ${name}$, ${module}$, ${trigger_event}$, ${description}$, ${org_id}$, ${status}$, ${created_at}$, ${updated_at}$) + """, { + 'id': new_id, 'name': workflow_name, 'module': module_type, + 'trigger_event': trigger_condition, 'description': description, + 'org_id': org_id, 'status': is_active, 'created_at': now, 'updated_at': now + }) + + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '工作流创建成功', 'type': 'success'}} +except Exception as e: + result['options'] = {'title': 'Error', 'message': f'创建失败: {str(e)}', 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/api/workflow_delete.dspy b/wwwroot/api/workflow_delete.dspy new file mode 100644 index 0000000..8f5b662 --- /dev/null +++ b/wwwroot/api/workflow_delete.dspy @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Delete approval workflow""" +import json + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + record_id = params_kw.get('id', '') + if not record_id: + result['options'] = {'title': 'Error', 'message': '记录ID不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + async with DBPools().sqlorContext(dbname) as sor: + existing = await sor.sqlExe("SELECT id FROM approval_workflow WHERE id=${id}$", {'id': record_id}) + if not existing: + result['options'] = {'title': 'Error', 'message': '工作流不存在', 'type': 'error'} + else: + await sor.sqlExe("DELETE FROM approval_step WHERE workflow_id=${id}$", {'id': record_id}) + await sor.sqlExe("DELETE FROM approval_workflow 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/workflow_list.dspy b/wwwroot/api/workflow_list.dspy new file mode 100644 index 0000000..8f49c3a --- /dev/null +++ b/wwwroot/api/workflow_list.dspy @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""List approval workflows""" +import json + +result = {'success': False, 'rows': [], 'total': 0} + +try: + dbname = get_module_dbname('workflow_approval') + ns = { + 'page': int(params_kw.get('page', 1)), + 'rows': int(params_kw.get('rows', 20)), + 'sort': 'id' + } + sql = "SELECT id, name as workflow_name, module as module_type, trigger_event as trigger_condition, description, org_id, status as is_active, created_at, updated_at FROM approval_workflow" + + 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/workflow_update.dspy b/wwwroot/api/workflow_update.dspy new file mode 100644 index 0000000..97b2f89 --- /dev/null +++ b/wwwroot/api/workflow_update.dspy @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Update approval workflow""" +import json, time + +result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Invalid request', 'type': 'error'}} + +try: + record_id = params_kw.get('id', '') + workflow_name = params_kw.get('workflow_name', '') + module_type = params_kw.get('module_type', '') + trigger_condition = params_kw.get('trigger_condition', '') + description = params_kw.get('description', '') + is_active = params_kw.get('is_active', 'Y') + + if not record_id: + result['options'] = {'title': 'Error', 'message': '记录ID不能为空', 'type': 'error'} + else: + dbname = get_module_dbname('workflow_approval') + now = time.strftime('%Y-%m-%d %H:%M:%S') + + async with DBPools().sqlorContext(dbname) as sor: + existing = await sor.sqlExe("SELECT id FROM approval_workflow WHERE id=${id}$", {'id': record_id}) + if not existing: + result['options'] = {'title': 'Error', 'message': '工作流不存在', 'type': 'error'} + else: + await sor.sqlExe(""" + UPDATE approval_workflow SET name=${name}$, module=${module}$, + trigger_event=${trigger_event}$, description=${description}$, status=${status}$, + updated_at=${updated_at}$ WHERE id=${id}$ + """, { + 'id': record_id, 'name': workflow_name, 'module': module_type, + 'trigger_event': trigger_condition, 'description': description, + 'status': is_active, 'updated_at': now + }) + result = {'widgettype': 'Message', 'options': {'title': 'Success', 'message': '工作流更新成功', 'type': 'success'}} +except Exception as e: + result['options'] = {'title': 'Error', 'message': f'更新失败: {str(e)}', 'type': 'error'} + +return json.dumps(result, ensure_ascii=False) diff --git a/wwwroot/approval_instance.ui b/wwwroot/approval_instance.ui new file mode 100644 index 0000000..83c03e3 --- /dev/null +++ b/wwwroot/approval_instance.ui @@ -0,0 +1,79 @@ +{ + "widgettype": "Page", + "options": { + "title": "审批实例管理", + "style": {"height": "100vh", "padding": "0"} + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"style": {"padding": "16px", "height": "100%"}}, + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"style": {"marginBottom": "16px"}}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "审批实例管理", "style": {"fontSize": "20px", "fontWeight": "bold"}} + }, + { + "widgettype": "Button", + "options": { + "label": "发起审批", + "style": {"backgroundColor": "#007bff", "color": "#fff", "border": "none", "padding": "8px 16px", "borderRadius": "4px", "marginLeft": "auto"}, + "onclick": "openDialog('newInstanceDialog')" + } + } + ] + }, + { + "widgettype": "DataViewer", + "options": { + "title": "审批实例", + "data_url": "/main/workflow_approval/api/instance_list.dspy", + "page_rows": 20, + "row_options": { + "fields": [ + {"name": "title", "label": "审批标题", "uitype": "text"}, + {"name": "workflow_name", "label": "工作流", "uitype": "text"}, + {"name": "module_type", "label": "模块类型", "uitype": "text"}, + {"name": "status", "label": "状态", "uitype": "text"}, + {"name": "created_at", "label": "发起时间", "uitype": "text"}, + {"name": "completed_at", "label": "完成时间", "uitype": "text"} + ] + } + } + }, + { + "widgettype": "Dialog", + "id": "newInstanceDialog", + "options": { + "title": "发起审批", + "width": 500, + "content": { + "widgettype": "Form", + "id": "newInstanceForm", + "fields": [ + {"name": "workflow_id", "label": "选择工作流", "uitype": "code", "data_url": "/main/workflow_approval/api/workflow_list.dspy", "required": true}, + {"name": "module_type", "label": "模块类型", "uitype": "code", "data": [ + {"value": "customer", "text": "客户管理"}, + {"value": "opportunity", "text": "商机管理"}, + {"value": "contract", "text": "合同管理"}, + {"value": "financial", "text": "财务管理"} + ], "required": true}, + {"name": "module_record_id", "label": "业务记录ID", "uitype": "text", "required": true}, + {"name": "title", "label": "审批标题", "uitype": "text", "required": true}, + {"name": "description", "label": "审批描述", "uitype": "textarea"} + ], + "actions": [ + {"widgettype": "Button", "options": {"label": "取消", "onclick": "closeDialog('newInstanceDialog')"}}, + {"widgettype": "Button", "options": {"label": "提交", "style": {"backgroundColor": "#007bff", "color": "#fff"}, "onclick": "submitForm('newInstanceForm', '/main/workflow_approval/api/instance_create.dspy', 'newInstanceDialog', null)"}} + ] + } + } + } + ] + } + ] +} diff --git a/wwwroot/approval_task.ui b/wwwroot/approval_task.ui new file mode 100644 index 0000000..e77c736 --- /dev/null +++ b/wwwroot/approval_task.ui @@ -0,0 +1,107 @@ +{ + "widgettype": "Page", + "options": { + "title": "待办任务", + "style": {"height": "100vh", "padding": "0"} + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"style": {"padding": "16px", "height": "100%"}}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "待办审批任务", "style": {"fontSize": "20px", "fontWeight": "bold", "marginBottom": "16px"}} + }, + { + "widgettype": "DataViewer", + "options": { + "title": "任务列表", + "data_url": "/main/workflow_approval/api/task_list.dspy", + "page_rows": 20, + "row_options": { + "fields": [ + {"name": "instance_title", "label": "审批事项", "uitype": "text"}, + {"name": "step_name", "label": "审批步骤", "uitype": "text"}, + {"name": "module_type", "label": "模块", "uitype": "text"}, + {"name": "status", "label": "状态", "uitype": "text"}, + {"name": "assigned_at", "label": "分配时间", "uitype": "text"}, + {"name": "due_at", "label": "截止时间", "uitype": "text"} + ] + }, + "row_actions": [ + { + "label": "审批", + "onclick": "openDialog('approveDialog'); setDialogField('approveDialog', 'task_id', '${id}'); setDialogField('approveDialog', 'task_title', '${instance_title}')", + "condition": "status == 'pending'" + }, + { + "label": "查看详情", + "onclick": "openDialog('taskDetailDialog'); setDialogField('taskDetailDialog', 'task_id', '${id}'); setDialogField('taskDetailDialog', 'task_title', '${instance_title}'); setDialogField('taskDetailDialog', 'step_name', '${step_name}'); setDialogField('taskDetailDialog', 'assigned_at', '${assigned_at}'); setDialogField('taskDetailDialog', 'due_at', '${due_at}'); setDialogField('taskDetailDialog', 'approval_type', '${approval_type}')" + } + ] + } + }, + { + "widgettype": "Dialog", + "id": "approveDialog", + "options": { + "title": "审批处理", + "width": 500, + "content": { + "widgettype": "Form", + "id": "approveForm", + "fields": [ + {"name": "task_id", "label": "任务ID", "uitype": "hidden"}, + {"name": "task_title", "label": "审批事项", "uitype": "text", "readonly": true}, + {"name": "decision", "label": "审批意见", "uitype": "textarea"} + ], + "actions": [ + {"widgettype": "Button", "options": {"label": "取消", "onclick": "closeDialog('approveDialog')"}}, + {"widgettype": "Button", "options": {"label": "拒绝", "style": {"backgroundColor": "#dc3545", "color": "#fff"}, "onclick": "submitForm('approveForm', '/main/workflow_approval/api/task_reject.dspy', 'approveDialog', null)"}}, + {"widgettype": "Button", "options": {"label": "批准", "style": {"backgroundColor": "#28a745", "color": "#fff"}, "onclick": "submitForm('approveForm', '/main/workflow_approval/api/task_approve.dspy', 'approveDialog', null)"}} + ] + } + } + }, + { + "widgettype": "Dialog", + "id": "taskDetailDialog", + "options": { + "title": "任务详情", + "width": 500, + "content": { + "widgettype": "VBox", + "options": {"gap": 8}, + "subwidgets": [ + {"widgettype": "Text", "id": "detail_title", "options": {"text": ""}}, + {"widgettype": "Divider", "options": {}}, + {"widgettype": "HBox", "options": {"gap": 8}, "subwidgets": [ + {"widgettype": "Text", "options": {"text": "步骤: ", "style": {"fontWeight": "bold"}}}, + {"widgettype": "Text", "id": "detail_step", "options": {"text": ""}} + ]}, + {"widgettype": "HBox", "options": {"gap": 8}, "subwidgets": [ + {"widgettype": "Text", "options": {"text": "审批类型: ", "style": {"fontWeight": "bold"}}}, + {"widgettype": "Text", "id": "detail_type", "options": {"text": ""}} + ]}, + {"widgettype": "HBox", "options": {"gap": 8}, "subwidgets": [ + {"widgettype": "Text", "options": {"text": "分配时间: ", "style": {"fontWeight": "bold"}}}, + {"widgettype": "Text", "id": "detail_assigned", "options": {"text": ""}} + ]}, + {"widgettype": "HBox", "options": {"gap": 8}, "subwidgets": [ + {"widgettype": "Text", "options": {"text": "截止时间: ", "style": {"fontWeight": "bold"}}}, + {"widgettype": "Text", "id": "detail_due", "options": {"text": ""}} + ]}, + {"widgettype": "Divider", "options": {}}, + {"widgettype": "HBox", "options": {"gap": 8}, "subwidgets": [ + {"widgettype": "Button", "options": {"label": "关闭", "onclick": "closeDialog('taskDetailDialog')"}}, + {"widgettype": "Button", "options": {"label": "去审批", "style": {"backgroundColor": "#007bff", "color": "#fff"}, "onclick": "closeDialog('taskDetailDialog'); openDialog('approveDialog'); setDialogField('approveDialog', 'task_id', getDialogField('taskDetailDialog', 'task_id')); setDialogField('approveDialog', 'task_title', getDialogField('taskDetailDialog', 'task_title'))"}} + ]} + ] + } + } + } + ] + } + ] +} diff --git a/wwwroot/approval_workflow.ui b/wwwroot/approval_workflow.ui new file mode 100644 index 0000000..dfb750b --- /dev/null +++ b/wwwroot/approval_workflow.ui @@ -0,0 +1,115 @@ +{ + "widgettype": "Page", + "options": { + "title": "审批工作流管理", + "style": {"height": "100vh", "padding": "0"} + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"style": {"padding": "16px", "height": "100%"}}, + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"style": {"marginBottom": "16px"}}, + "subwidgets": [ + { + "widgettype": "Text", + "options": {"text": "审批工作流管理", "style": {"fontSize": "20px", "fontWeight": "bold"}} + } + ] + }, + { + "widgettype": "DataViewer", + "options": { + "title": "工作流列表", + "data_url": "/main/workflow_approval/api/workflow_list.dspy", + "page_rows": 20, + "editable": { + "new_data_url": "/main/workflow_approval/api/workflow_create.dspy", + "update_data_url": "/main/workflow_approval/api/workflow_update.dspy", + "delete_data_url": "/main/workflow_approval/api/workflow_delete.dspy", + "form_cheight": 10, + "fields": [ + {"name": "workflow_name", "label": "工作流名称", "uitype": "text", "required": true}, + {"name": "module_type", "label": "模块类型", "uitype": "code", "data": [ + {"value": "customer", "text": "客户管理"}, + {"value": "opportunity", "text": "商机管理"}, + {"value": "contract", "text": "合同管理"}, + {"value": "financial", "text": "财务管理"} + ], "required": true}, + {"name": "trigger_condition", "label": "触发条件", "uitype": "textarea"}, + {"name": "description", "label": "描述", "uitype": "textarea"}, + {"name": "is_active", "label": "是否激活", "uitype": "code", "data": [ + {"value": "Y", "text": "是"}, + {"value": "N", "text": "否"} + ], "value": "Y"} + ] + }, + "row_options": { + "fields": [ + {"name": "workflow_name", "label": "工作流名称", "uitype": "text"}, + {"name": "module_type", "label": "模块类型", "uitype": "text"}, + {"name": "description", "label": "描述", "uitype": "text"}, + {"name": "is_active", "label": "状态", "uitype": "text"}, + {"name": "created_at", "label": "创建时间", "uitype": "text"} + ] + } + } + }, + { + "widgettype": "Divider", + "options": {"style": {"margin": "20px 0"}} + }, + { + "widgettype": "Text", + "options": {"text": "审批步骤配置", "style": {"fontSize": "16px", "fontWeight": "bold", "marginBottom": "12px"}} + }, + { + "widgettype": "DataViewer", + "options": { + "title": "审批步骤", + "data_url": "/main/workflow_approval/api/step_list.dspy", + "page_rows": 20, + "editable": { + "new_data_url": "/main/workflow_approval/api/step_create.dspy", + "update_data_url": "/main/workflow_approval/api/step_update.dspy", + "delete_data_url": "/main/workflow_approval/api/step_delete.dspy", + "form_cheight": 10, + "fields": [ + {"name": "workflow_id", "label": "所属工作流", "uitype": "code", "data_url": "/main/workflow_approval/api/workflow_list.dspy", "required": true}, + {"name": "step_name", "label": "步骤名称", "uitype": "text", "required": true}, + {"name": "step_order", "label": "步骤顺序", "uitype": "number", "required": true}, + {"name": "approver_type", "label": "审批人类型", "uitype": "code", "data": [ + {"value": "role", "text": "角色"}, + {"value": "user", "text": "用户"}, + {"value": "department", "text": "部门"}, + {"value": "dynamic", "text": "动态"} + ], "required": true}, + {"name": "approver_value", "label": "审批人值", "uitype": "text"}, + {"name": "approval_type", "label": "审批类型", "uitype": "code", "data": [ + {"value": "single", "text": "单人审批"}, + {"value": "multiple", "text": "多人审批"}, + {"value": "sequential", "text": "顺序审批"}, + {"value": "parallel", "text": "并行审批"} + ], "required": true}, + {"name": "timeout_hours", "label": "超时时间(小时)", "uitype": "number"}, + {"name": "description", "label": "描述", "uitype": "textarea"} + ] + }, + "row_options": { + "fields": [ + {"name": "workflow_id", "label": "工作流ID", "uitype": "text"}, + {"name": "step_name", "label": "步骤名称", "uitype": "text"}, + {"name": "step_order", "label": "顺序", "uitype": "text"}, + {"name": "approver_type", "label": "审批人类型", "uitype": "text"}, + {"name": "approval_type", "label": "审批类型", "uitype": "text"}, + {"name": "timeout_hours", "label": "超时(小时)", "uitype": "text"} + ] + } + } + } + ] + } + ] +} diff --git a/wwwroot/base.ui b/wwwroot/base.ui new file mode 100644 index 0000000..746cfe3 --- /dev/null +++ b/wwwroot/base.ui @@ -0,0 +1,70 @@ +{ + "widgettype": "Page", + "options": { + "title": "审批管理", + "style": {"height": "100vh", "overflow": "hidden"} + }, + "subwidgets": [ + { + "widgettype": "HBox", + "options": {"style": {"height": "100vh"}}, + "subwidgets": [ + { + "widgettype": "Drawer", + "options": {"width": 220, "variant": "permanent", "style": {"backgroundColor": "#1a1a2e"}}, + "subwidgets": [ + { + "widgettype": "VBox", + "options": {"gap": 4, "style": {"padding": "8px"}}, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "审批管理", + "style": {"color": "#fff", "fontSize": "18px", "fontWeight": "bold", "padding": "12px 8px"} + } + }, + { + "widgettype": "Divider", + "options": {"style": {"backgroundColor": "#333", "margin": "8px 0"}} + }, + { + "widgettype": "ListTile", + "options": { + "leading": "account_tree", + "title": "工作流配置", + "style": {"color": "#ccc"}, + "onclick": "navigate('main/workflow_approval/approval_workflow.ui')" + } + }, + { + "widgettype": "ListTile", + "options": { + "leading": "assignment", + "title": "审批实例", + "style": {"color": "#ccc"}, + "onclick": "navigate('main/workflow_approval/approval_instance.ui')" + } + }, + { + "widgettype": "ListTile", + "options": { + "leading": "assignment_turned_in", + "title": "待办任务", + "style": {"color": "#ccc"}, + "onclick": "navigate('main/workflow_approval/approval_task.ui')" + } + } + ] + } + ] + }, + { + "widgettype": "Frame", + "id": "workflow_frame", + "options": {"flex": 1, "src": "main/workflow_approval/approval_workflow.ui"} + } + ] + } + ] +} diff --git a/wwwroot/mobile_base.ui b/wwwroot/mobile_base.ui index ded2a81..5dd74b4 100644 --- a/wwwroot/mobile_base.ui +++ b/wwwroot/mobile_base.ui @@ -40,7 +40,7 @@ { "widgettype": "TabPanel", "options": { - "tabs": [ + "items": [ { "title": "待我审批", "content": {