diff --git a/src/form/question/models.tsp b/src/form/question/models.tsp index 5aece472..f995273c 100644 --- a/src/form/question/models.tsp +++ b/src/form/question/models.tsp @@ -402,4 +402,40 @@ namespace CoreSystem.Forms { @doc("If set, choices are dynamically generated from answers to this question.") sourceId?: uuid; } + + @doc("A single answer option available for filtering.") + model FilterAnswerItem { + @doc("The text displayed to the user in the filter UI.") + displayValue: string; + + @doc("The raw value used for backend querying and matching.") + matchValue: string; + + @doc("Whether this filter option is selected. Defaults to false from server.") + selected: boolean; + } + + @doc("Response model for getting question filters.") + @example(#{ + viewId: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", + formId: "550e8400-e29b-41d4-a716-446655440000", + questionId: "74723192-bc11-412e-a576-928b76c8c9e0", + answers: #[ + #{ displayValue: "開發部", matchValue: "abc-123-def", selected: false }, + #{ displayValue: "品牌部", matchValue: "ghi-456-jkl", selected: false } + ] + }) + model QuestionFiltersResponse { + @doc("The unique identifier of the view.") + viewId: uuid; + + @doc("The unique identifier of the form.") + formId: uuid; + + @doc("The unique identifier of the question.") + questionId: uuid; + + @doc("The list of available unique answers for this question.") + answers: FilterAnswerItem[]; + } } diff --git a/src/form/question/operations.tsp b/src/form/question/operations.tsp index 068f1277..77c39b61 100644 --- a/src/form/question/operations.tsp +++ b/src/form/question/operations.tsp @@ -1,3 +1,5 @@ +import "../response/models.tsp"; + using Http; @tag("Forms") @@ -37,4 +39,21 @@ namespace CoreSystem.Forms { op deleteQuestion(@path sectionId: uuid, @path questionId: uuid): { @statusCode statusCode: 204; }; -} + + @summary("Get Filters") + @doc("Filter and query distinct answers of the question for filtering.") + @route("/forms/{formId}/views/{viewId}/questions/{questionId}/filters") + @post + op filterAndQueryAnswers( + @path formId: uuid, + @path viewId: uuid, + @path questionId: uuid, + @body req: CoreSystem.Responses.AnswerFilterRequest + ): { + @statusCode statusCode: 200; + @body response: QuestionFiltersResponse; + } | { + @statusCode statusCode: 404; + @body error: NotFound; + }; +} \ No newline at end of file diff --git a/src/form/response/models.tsp b/src/form/response/models.tsp index f24e6f52..e4ca3edf 100644 --- a/src/form/response/models.tsp +++ b/src/form/response/models.tsp @@ -176,4 +176,44 @@ namespace CoreSystem.Responses { questionIds: uuid[]; } + @doc("A single answer option selected by the user") + model QueryFilterAnswerItem { + @doc("The raw value used for backend querying and matching.") + option: uuid; + } + + @doc("Filter group for a specific question") + model ResponseQueryFilter { + @doc("The unique identifier of the question.") + questionId: uuid; + + @doc("The list of answers selected for this question.") + answers: QueryFilterAnswerItem[]; + } + + @doc("Request model for filtering form responses") + @example(#{ + filters: #[ + #{ + questionId: "a1b2c3d4-1111-2222-3333-444455556666", + answers: #[ + #{ option: "9a843aa0-8451-4e3b-b6a0-8c0e994e9040" }, + #{ option: "00000000-0000-0000-0000-000000000001" } + ] + } + ] + }) + model AnswerFilterRequest { + @doc("The List of questions selected.") + filters: ResponseQueryFilter[]; + } + + @doc("Response model for filtering form responses") + model AnswerFilterResponse { + @doc("The form unique identifier.") + formId: uuid; + + @doc("The form responses that match the selected answers.") + responses: ExportRow[]; + } } diff --git a/src/form/response/operations.tsp b/src/form/response/operations.tsp index 12deaeb9..af8ed404 100644 --- a/src/form/response/operations.tsp +++ b/src/form/response/operations.tsp @@ -114,4 +114,19 @@ namespace CoreSystem.Responses { @statusCode statusCode: 404; @body error: NotFound; }; + + @summary("Filter And Query Form Responses") + @doc("Filter and query form responses based on a list of selected question answers.") + @route("/forms/{formId}/responses/query") + @post + op filterAndQueryFormResponses(@path formId: uuid, @body req: AnswerFilterRequest): { + @statusCode statusCode: 200; + @body response: AnswerFilterResponse; + } | { + @statusCode statusCode: 400; + @body error: BadRequest; + } | { + @statusCode statusCode: 404; + @body error: NotFound; + }; } diff --git a/src/form/view/models.tsp b/src/form/view/models.tsp index c447a12c..b4f61a04 100644 --- a/src/form/view/models.tsp +++ b/src/form/view/models.tsp @@ -1,6 +1,16 @@ using Http; namespace CoreSystem.Views { + + @doc("A single question option available for filtering.") + model Column{ + @doc("The question's unique identifier.") + questionId: uuid; + + @doc("The text displayed to the user in the filter UI.") + questionTitle: string; + } + @doc("Response model for a single form view.") @example(#{ id: "f1a2b3c4-5d6e-7f8a-9b0c-1d2e3f4a5b6c", @@ -9,7 +19,12 @@ namespace CoreSystem.Views { order: 0, createdAt: utcDateTime.fromISO("2025-01-15T10:00:00Z"), updatedAt: utcDateTime.fromISO("2025-02-10T14:30:00Z"), + columns: #[ + #{ questionId: "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", questionTitle: "部門" }, + #{ questionId: "6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b", questionTitle: "姓名" } + ] }) + model ViewResponse { @doc("The view's unique identifier.") id: uuid; @@ -28,6 +43,9 @@ namespace CoreSystem.Views { @doc("The last updated timestamp of the view.") updatedAt: utcDateTime; + + @doc("The columns of the filters.") + columns: Column[]; } @doc("Request model for updating a view. Only provided fields will be updated.")