feat: complete leave approval MVP

This commit is contained in:
selfrelease
2026-07-18 19:20:07 +08:00
parent 2105fe3bac
commit 090a7e33ce
133 changed files with 7845 additions and 100 deletions
+333 -2
View File
@@ -1,10 +1,170 @@
openapi: 3.1.0
info:
title: AIOA API
version: 0.4.0
version: 0.12.0
servers:
- url: /api/v1
security:
- bearerAuth: []
paths:
/devices/register:
post:
operationId: registerCurrentDevice
summary: 注册或刷新当前用户设备会话
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [id, name, platform]
properties:
id: { type: string, format: uuid }
name: { type: string, minLength: 1, maxLength: 200 }
platform: { type: string, enum: [IOS, ANDROID, OTHER] }
appVersion: { type: [string, "null"], maxLength: 64 }
responses:
"200": { description: 已注册的设备会话 }
"401": { description: 设备已被撤销,禁止重新注册 }
/devices:
get:
operationId: listOwnDevices
summary: 查询当前用户的登录设备
responses:
"200": { description: 当前用户设备列表 }
/devices/{id}:
delete:
operationId: revokeOwnDevice
summary: 撤销当前用户的一台设备并使其后续请求失效
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
responses:
"200": { description: 已撤销设备 }
"404": { description: 设备不存在或不属于当前用户 }
/devices/{id}/push-token:
put:
operationId: updateCurrentDevicePushToken
summary: 注册、刷新或清除当前设备的 FCM 推送令牌
parameters:
- { name: id, in: path, required: true, schema: { type: string, format: uuid } }
- { name: X-AIOA-Device-Id, in: header, required: true, schema: { type: string, format: uuid } }
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
token: { type: [string, "null"], maxLength: 4096 }
responses:
"200": { description: 推送令牌已更新 }
"403": { description: 路径设备与当前设备不一致 }
/ai/leave-progress-answers:
post:
operationId: answerOwnLeaveProgress
summary: 使用自然语言只读查询本人请假流程进度
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [text, timezone]
properties:
text: { type: string, minLength: 1, maxLength: 2000 }
timezone: { type: string, default: Asia/Shanghai }
selectedRequestId: { type: string, format: uuid }
responses:
"200":
description: 回答或需要用户选择的本人申请候选列表
"400": { $ref: "#/components/responses/BadRequest" }
"502":
description: AI 上游不可用或输出无效
/ai/leave-draft-suggestions:
post:
operationId: suggestLeaveDraft
summary: 将自然语言转换为需要用户确认的请假草稿建议
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [text, timezone]
properties:
text: { type: string, minLength: 1, maxLength: 2000 }
timezone: { type: string, default: Asia/Shanghai }
responses:
"200":
description: 受约束的草稿建议,不执行业务写操作
content:
application/json:
schema: { $ref: "#/components/schemas/AiLeaveDraftSuggestion" }
"400": { $ref: "#/components/responses/BadRequest" }
"502":
description: AI 上游不可用或输出无效
/notifications:
get:
operationId: listOwnNotifications
summary: 查询当前用户站内通知
responses:
"200":
description: 通知列表
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Notification" }
/notifications/unread-count:
get:
operationId: getUnreadNotificationCount
summary: 查询未读通知数
responses:
"200":
description: 未读数
content:
application/json:
schema:
type: object
required: [unreadCount]
properties:
unreadCount: { type: integer, minimum: 0 }
/notifications/{id}/read:
post:
operationId: markNotificationRead
summary: 标记本人通知已读
parameters:
- name: id
in: path
required: true
schema: { type: string, format: uuid }
responses:
"200":
description: 已读通知
content:
application/json:
schema: { $ref: "#/components/schemas/Notification" }
"404": { $ref: "#/components/responses/NotFound" }
/form-definitions/{formKey}:
get:
operationId: getFormDefinition
summary: 获取版本化表单定义
parameters:
- name: formKey
in: path
required: true
schema: { type: string }
responses:
"200":
description: JSON Schema 与移动端 UI Schema
content:
application/json:
schema:
$ref: "#/components/schemas/FormDefinition"
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
/me:
get:
operationId: getCurrentUser
@@ -169,6 +329,81 @@ paths:
$ref: "#/components/schemas/LeaveRequestEvent"
"404":
$ref: "#/components/responses/NotFound"
/leave-requests/{id}/attachments/upload-tasks:
post:
operationId: createLeaveAttachmentUpload
summary: 创建附件直传任务
parameters:
- $ref: "#/components/parameters/LeaveRequestId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateAttachmentUpload"
responses:
"201":
description: 已创建上传任务
content:
application/json:
schema:
$ref: "#/components/schemas/AttachmentUpload"
"400": { $ref: "#/components/responses/BadRequest" }
"404": { $ref: "#/components/responses/NotFound" }
"409": { $ref: "#/components/responses/Conflict" }
/leave-requests/{id}/attachments:
get:
operationId: listLeaveAttachments
summary: 查询请假附件
parameters:
- $ref: "#/components/parameters/LeaveRequestId"
responses:
"200":
description: 附件列表
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/LeaveAttachment" }
/leave-requests/{id}/attachments/{attachmentId}/complete:
post:
operationId: completeLeaveAttachmentUpload
summary: 确认附件上传完成
parameters:
- $ref: "#/components/parameters/LeaveRequestId"
- $ref: "#/components/parameters/AttachmentId"
responses:
"200":
description: 附件已就绪
content:
application/json:
schema: { $ref: "#/components/schemas/LeaveAttachment" }
/leave-requests/{id}/attachments/{attachmentId}/download:
get:
operationId: createLeaveAttachmentDownload
summary: 获取附件短期下载地址
parameters:
- $ref: "#/components/parameters/LeaveRequestId"
- $ref: "#/components/parameters/AttachmentId"
responses:
"200":
description: 短期下载地址
content:
application/json:
schema:
type: object
required: [downloadUrl]
properties:
downloadUrl: { type: string, format: uri }
/leave-requests/{id}/attachments/{attachmentId}:
delete:
operationId: deleteLeaveAttachment
summary: 删除草稿附件
parameters:
- $ref: "#/components/parameters/LeaveRequestId"
- $ref: "#/components/parameters/AttachmentId"
responses:
"204": { description: 已删除 }
/approval-tasks:
get:
operationId: listAssignedApprovalTasks
@@ -231,6 +466,17 @@ paths:
"409":
$ref: "#/components/responses/Conflict"
components:
securitySchemes:
bearerAuth:
type: oauth2
flows:
authorizationCode:
authorizationUrl: http://localhost:8081/realms/aioa/protocol/openid-connect/auth
tokenUrl: http://localhost:8081/realms/aioa/protocol/openid-connect/token
scopes:
openid: OpenID Connect identity
profile: Basic profile
email: Email address
parameters:
LeaveRequestId:
name: id
@@ -250,6 +496,11 @@ components:
in: path
required: true
schema: { type: string }
AttachmentId:
name: attachmentId
in: path
required: true
schema: { type: string, format: uuid }
responses:
BadRequest:
description: 请求无效
@@ -282,9 +533,79 @@ components:
schema:
$ref: "#/components/schemas/Problem"
schemas:
AiLeaveDraftSuggestion:
type: object
required: [suggestion, model, requiresUserConfirmation]
properties:
suggestion:
type: object
required: [assumptions, needsClarification]
properties:
type: { type: [string, "null"], enum: [PERSONAL, SICK, ANNUAL, null] }
startsAt: { type: [string, "null"], format: date-time }
endsAt: { type: [string, "null"], format: date-time }
reason: { type: [string, "null"], maxLength: 2000 }
assumptions:
type: array
items: { type: string }
needsClarification:
type: array
items: { type: string }
model: { type: string }
requiresUserConfirmation: { type: boolean, const: true }
Notification:
type: object
required: [id, type, title, body, createdAt]
properties:
id: { type: string, format: uuid }
type: { type: string }
title: { type: string }
body: { type: string }
resourceType: { type: [string, "null"] }
resourceId: { type: [string, "null"] }
createdAt: { type: string, format: date-time }
readAt: { type: [string, "null"], format: date-time }
CreateAttachmentUpload:
type: object
required: [fileName, contentType, sizeBytes]
properties:
fileName: { type: string, minLength: 1, maxLength: 255 }
contentType:
type: string
enum: [image/jpeg, image/png, application/pdf]
sizeBytes: { type: integer, format: int64, minimum: 1, maximum: 10485760 }
AttachmentUpload:
type: object
required: [attachment, uploadUrl]
properties:
attachment: { $ref: "#/components/schemas/LeaveAttachment" }
uploadUrl: { type: string, format: uri }
LeaveAttachment:
type: object
required: [id, fileName, contentType, sizeBytes, status, createdAt]
properties:
id: { type: string, format: uuid }
fileName: { type: string }
contentType: { type: string }
sizeBytes: { type: integer, format: int64 }
status: { type: string, enum: [PENDING, READY] }
createdAt: { type: string, format: date-time }
completedAt: { type: [string, "null"], format: date-time }
FormDefinition:
type: object
required: [key, version, dataSchema, uiSchema]
properties:
key: { type: string }
version: { type: integer, minimum: 1 }
dataSchema:
type: object
additionalProperties: true
uiSchema:
type: object
additionalProperties: true
CurrentUser:
type: object
required: [id, tenantId, username, displayName, roles]
required: [id, tenantId, username, displayName, roles, permissions, dataScopes]
properties:
id: { type: string, format: uuid }
tenantId: { type: string, format: uuid }
@@ -305,6 +626,16 @@ components:
type: array
uniqueItems: true
items: { type: string }
permissions:
type: array
uniqueItems: true
items:
type: string
enum: [LEAVE_REQUEST_READ_OWN, LEAVE_REQUEST_WRITE_OWN, LEAVE_ATTACHMENT_MANAGE_OWN, NOTIFICATION_READ_OWN, AI_LEAVE_DRAFT_SUGGEST, AI_LEAVE_PROGRESS_READ_OWN, APPROVAL_TASK_READ_ASSIGNED, APPROVAL_TASK_DECIDE_ASSIGNED]
dataScopes:
type: array
uniqueItems: true
items: { type: string, enum: [OWN, ASSIGNED] }
OrganizationRef:
type: object
required: [id, name]