sync: local modifications to workflow_approval

- Updated all model JSON files: approval_instance, approval_step, approval_task, approval_workflow
- Updated init.py, mysql.ddl.sql, mobile_base.ui
- Added __init__.py
- Added API files: instance CRUD, step CRUD, task approve/reject/list, workflow CRUD
- Added UI files: base.ui, approval_instance.ui, approval_task.ui, approval_workflow.ui
This commit is contained in:
yumoqing 2026-04-28 18:55:29 +08:00
parent 91270d6ced
commit 5472211972
25 changed files with 1049 additions and 445 deletions

View File

@ -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": "已取消"}
]
}

View File

@ -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": "指定人审批"}
]
}

View File

@ -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": "已驳回"}
]
}

View File

@ -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": "商机审批"}
]
}

View File

@ -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`);

View File

@ -0,0 +1 @@
# Workflow Approval Module

View File

@ -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
env.get_workflow_dbname = get_workflow_dbname

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)"}}
]
}
}
}
]
}
]
}

107
wwwroot/approval_task.ui Normal file
View File

@ -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'))"}}
]}
]
}
}
}
]
}
]
}

View File

@ -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"}
]
}
}
}
]
}
]
}

70
wwwroot/base.ui Normal file
View File

@ -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"}
}
]
}
]
}

View File

@ -40,7 +40,7 @@
{
"widgettype": "TabPanel",
"options": {
"tabs": [
"items": [
{
"title": "待我审批",
"content": {