feat: add organization approval rules
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
package com.all8ai.aioa.admin.configuration
|
||||
|
||||
data class ApprovalRuleOption(
|
||||
val variable:String,val label:String,val sourceType:String,val selector:String,
|
||||
val scope:String,val emptyPolicy:String,val description:String,
|
||||
)
|
||||
|
||||
fun approvalRuleCatalog()=listOf(
|
||||
ApprovalRuleOption(
|
||||
"approverId","发起人所在部门主管","POSITION","manager","APPLICANT_DEPARTMENT","REJECT_SUBMISSION",
|
||||
"根据发起人的有效主任职定位部门,再选择该部门有效的 manager 岗位人员。",
|
||||
),
|
||||
ApprovalRuleOption(
|
||||
"oaAdministratorId","租户 OA 管理员","ROLE","oa_admin","TENANT","REJECT_SUBMISSION",
|
||||
"在当前租户内选择拥有有效 oa_admin 角色且账号状态正常的人员。",
|
||||
),
|
||||
ApprovalRuleOption(
|
||||
"hrReviewerId","租户 HR 复核人","ROLE","hr_reviewer","TENANT","REJECT_SUBMISSION",
|
||||
"在当前租户内选择拥有有效 hr_reviewer 角色且账号状态正常的人员。",
|
||||
),
|
||||
)
|
||||
+1
@@ -25,6 +25,7 @@ class ProcessConfigurationController(
|
||||
private fun actor(jwt:Jwt)=users.get(jwt.subject,jwt.getClaimAsString("tenant_id")).also { it.requirePermission(ToolPermission.PROCESS_CONFIGURATION_MANAGE_TENANT) }
|
||||
@GetMapping("/forms") fun forms(@AuthenticationPrincipal jwt:Jwt):List<Map<String,Any?>> { val a=actor(jwt);return dsl.fetch("SELECT form_key,version,status,created_at,published_at FROM form.definition WHERE tenant_id=? ORDER BY form_key,version DESC",a.tenantId).map{it.intoMap()} }
|
||||
@GetMapping("/bindings") fun bindings(@AuthenticationPrincipal jwt:Jwt):List<Map<String,Any?>> { val a=actor(jwt);return dsl.fetch("SELECT id,business_type,form_key,form_version,process_definition_key,leave_type,min_duration_minutes,max_duration_minutes,priority,status FROM workflow.process_binding WHERE tenant_id=? ORDER BY priority DESC",a.tenantId).map{it.intoMap()} }
|
||||
@GetMapping("/approver-rules") fun approverRules(@AuthenticationPrincipal jwt:Jwt):List<ApprovalRuleOption> { actor(jwt);return approvalRuleCatalog() }
|
||||
@PostMapping("/forms") @Transactional fun createForm(@AuthenticationPrincipal jwt:Jwt,@RequestBody body:FormVersionCommand):Map<String,Any?> {
|
||||
val a=actor(jwt);validateSchema(body.dataSchema,body.uiSchema);val version=(dsl.fetchOne("SELECT COALESCE(MAX(version),0)+1 v FROM form.definition WHERE tenant_id=? AND form_key=?",a.tenantId,body.formKey)?.get("v",Int::class.java)?:1)
|
||||
dsl.execute("INSERT INTO form.definition(tenant_id,form_key,version,status,data_schema,ui_schema) VALUES(?,?,?,'DRAFT',CAST(? AS JSONB),CAST(? AS JSONB))",a.tenantId,body.formKey,version,mapper.writeValueAsString(body.dataSchema),mapper.writeValueAsString(body.uiSchema))
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ fun generateProcessTemplate(command:ProcessTemplateCommand):String {
|
||||
if(command.mode !in setOf("SERIAL","PARALLEL","CONDITIONAL")) invalid("流程模式无效")
|
||||
if(command.mode=="CONDITIONAL" && command.steps.size!=2 || command.mode!="CONDITIONAL" && command.steps.size !in 1..6) invalid("审批节点数量无效")
|
||||
if(command.steps.any{it.name.isBlank()||it.name.length>80||it.assigneeVariable !in allowedAssignees}) invalid("审批节点配置无效")
|
||||
if(command.mode in setOf("PARALLEL","CONDITIONAL") && command.steps.map{it.assigneeVariable}.distinct().size!=command.steps.size) invalid("并行或条件复核节点必须使用不同的审批人规则")
|
||||
if(command.conditionVariable !in allowedConditions||command.conditionThreshold<0) invalid("条件配置无效")
|
||||
val body=when(command.mode){
|
||||
"SERIAL"->serial(command.steps)
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.all8ai.aioa.admin.configuration
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ApprovalRuleCatalogTest {
|
||||
@Test fun `catalog exposes only supported runtime variables and safe empty policy`() {
|
||||
val catalog=approvalRuleCatalog()
|
||||
assertEquals(setOf("approverId","oaAdministratorId","hrReviewerId"),catalog.map{it.variable}.toSet())
|
||||
assertTrue(catalog.all{it.emptyPolicy=="REJECT_SUBMISSION"})
|
||||
assertTrue(catalog.any{it.sourceType=="POSITION"&&it.scope=="APPLICANT_DEPARTMENT"})
|
||||
assertTrue(catalog.any{it.sourceType=="ROLE"&&it.scope=="TENANT"})
|
||||
}
|
||||
}
|
||||
+1
@@ -25,5 +25,6 @@ class ProcessTemplateGeneratorTest {
|
||||
@Test fun `rejects unsafe identifiers and assignee expressions`() {
|
||||
assertFails { generateProcessTemplate(ProcessTemplateCommand("bad key","测试","SERIAL",listOf(manager))) }
|
||||
assertFails { generateProcessTemplate(ProcessTemplateCommand("safeKey","测试","SERIAL",listOf(ApprovalStepCommand("审批","evilExpression")))) }
|
||||
assertFails { generateProcessTemplate(ProcessTemplateCommand("duplicateParallel","测试","PARALLEL",listOf(manager,manager))) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user