diff --git a/README.md b/README.md index f5f6dbd..251708a 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ See [Error Handling](https://github.com/miyajan/garoon-rest/tree/master/docs/err ## References - [Schedule](https://github.com/miyajan/garoon-rest/tree/master/docs/schedule.md) +- [Workflow](https://github.com/miyajan/garoon-rest/tree/master/docs/workflow.md) ## Contribution Guide diff --git a/docs/workflow.md b/docs/workflow.md new file mode 100644 index 0000000..d01b961 --- /dev/null +++ b/docs/workflow.md @@ -0,0 +1,49 @@ +# Workflow + +- [getRequests](#getrequests) + +## Overview + +```ts +const client = new GaroonRestAPIClient(); + +(async () => { + try { + console.log(await client.workflow.getRequests()); + } catch (error) { + console.log(error); + } +})(); +``` + +- All methods are defined on the `workflow` property. +- This method returns a Promise object that is resolved with an object having properties in each `Returns` section. + +## Methods + +### getRequests + +Get the all request data. + +#### Parameters + +| Name | Type | Required | Description | +| -------------------- | :--------------: | :------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| limit | Number | | The number of requests to retrieve.
Must be between `1` and `1000`.
If nothing is specified, it will default to `100`. | +| offset | Number | | The number of retrievals that will be skipped.
Must be between `0` and `2147483647`. If nothing is specified, it will default to `0`. | +| fields | Array\ | | The response properties to get. | +| orderBy | Object | | An object containing data of sort settings. | +| orderBy.property | String | Yes | The property name. Possible values are: `createdAt`. If nothing is specified, it will default to `createdAt`. | +| orderBy.order | String | Yes | The sort order. Possible values are: `asc`, `desc`. If nothing is specified, it will default to `asc`. | +| rangeStartApprovedAt | String | | The start approved datetime for the search. The format is RFC3339. (e.g. `2020-01-01T00:00:00Z`)
If `rangeEndApprovedAt` is specified, `rangeStartApprovedAt` must be before the `rangeEndApprovedAt`. | +| rangeEndApprovedAt | String | | The end approved datetime for the search. The format is RFC3339. (e.g. `2020-01-01T00:00:00Z`)
If `rangeStartApprovedAt` is specified, `rangeEndApprovedAt` must be later than `rangeStartApprovedAt`. | +| form | Number or String | | The form ID. | +| status | Array\ | | The request status. Possible values are: `UNPROCESSING`, `IN_PROGRESS`, `REJECTED`, `WITHDRAWN`, `SENT_BACK`, `CANCELLED`, `APPROVED`, `COMPLETED`. If nothing is specified, it will default to all statuses. | + +#### Returns + +See the example response in the `Reference`. + +#### Reference + +- https://developer.cybozu.io/hc/ja/articles/360031071011#step1 diff --git a/src/GaroonRestAPIClient.ts b/src/GaroonRestAPIClient.ts index 7e0b501..7290792 100644 --- a/src/GaroonRestAPIClient.ts +++ b/src/GaroonRestAPIClient.ts @@ -9,6 +9,7 @@ import { GaroonErrorResponse, GaroonRestAPIError } from "./GaroonRestAPIError"; import { platformDeps } from "./platform"; import { UnsupportedPlatformError } from "./platform/UnsupportedPlatformError"; import { GaroonRequestConfigBuilder } from "./GaroonRequestConfigBuilder"; +import { WorkflowClient } from "./client/WorkflowClient"; export type DiscriminatedAuth = PasswordAuth | SessionAuth | OAuthTokenAuth; @@ -93,6 +94,7 @@ const buildDiscriminatedAuth = (auth: Auth): DiscriminatedAuth => { export class GaroonRestAPIClient { readonly schedule: ScheduleClient; + readonly workflow: WorkflowClient; private readonly baseUrl: string; constructor(options: Options = {}) { @@ -109,6 +111,7 @@ export class GaroonRestAPIClient { requestConfigBuilder, }); this.schedule = new ScheduleClient(httpClient); + this.workflow = new WorkflowClient(httpClient); } public getBaseUrl(): string { diff --git a/src/client/ScheduleClient.ts b/src/client/ScheduleClient.ts index 27591ac..eea5454 100644 --- a/src/client/ScheduleClient.ts +++ b/src/client/ScheduleClient.ts @@ -39,7 +39,7 @@ export class ScheduleClient { } const { fields, orderBy, excludeFromSearch, ...rest } = params; - const data: any = rest; + const data: Record = rest as Record; if (fields) { data.fields = fields.join(","); } diff --git a/src/client/WorkflowClient.ts b/src/client/WorkflowClient.ts new file mode 100644 index 0000000..e1d9bf6 --- /dev/null +++ b/src/client/WorkflowClient.ts @@ -0,0 +1,103 @@ +import { HttpClient } from "../http"; +import { Item, Operation, Status } from "./types"; +import { buildPath } from "../url"; + +export class WorkflowClient { + private readonly client: HttpClient; + + constructor(client: HttpClient) { + this.client = client; + } + + public getRequests(params?: { + limit?: number; + offset?: number; + fields?: string[]; + orderBy?: { + property: "createdAt"; + order: "asc" | "desc"; + }; + rangeStartApprovedAt?: string; + rangeEndApprovedAt?: string; + form?: string | number; + status?: Status[]; + }): Promise<{ + requests: Array<{ + id: string; + status: { + name: string; + type: Status; + }; + createdAt: string; + processingStepCode: string; + name: string; + number: string; + isUrgent: boolean; + applicant: { + id: string; + code: string; + name: string; + proxy?: { + id: string; + code: string; + name: string; + }; + }; + form: { + id: string; + name: string; + }; + items: { + [itemCode: string]: Item; + }; + steps: { + [stepCode: string]: { + id: string; + name: string; + requirement: string; + isApprovalStep: 0 | 1; + processors: Array<{ + id: string; + code: string; + name: string; + result: string; + date: string; + comment: string; + proxy: { + id: string; + code: string; + name: string; + }; + }>; + }; + }; + availableOperations: { + list: Operation[]; + sentBackTargets: string[]; + }; + folder: Array<{ + id: string; + type: "UNPROCESSED" | "SENT" | "RECEIVED" | "DRAFT" | "FINISH"; + }>; + }>; + }> { + const path = buildPath({ endpointName: "workflow/admin/requests" }); + + if (!params) { + return this.client.get(path, {}); + } + + const { fields, orderBy, status, ...rest } = params; + const data: Record = rest as Record; + if (fields) { + data.fields = fields.join(","); + } + if (orderBy) { + data.orderBy = `${orderBy.property} ${orderBy.order}`; + } + if (status) { + data.status = status.join(","); + } + return this.client.get(path, data); + } +} diff --git a/src/client/__tests__/WorkflowClient.test.ts b/src/client/__tests__/WorkflowClient.test.ts new file mode 100644 index 0000000..ca2bddc --- /dev/null +++ b/src/client/__tests__/WorkflowClient.test.ts @@ -0,0 +1,62 @@ +import { MockClient } from "../../http/MockClient"; +import { GaroonRequestConfigBuilder } from "../../GaroonRequestConfigBuilder"; +import { errorResponseHandler } from "../../GaroonRestAPIClient"; +import { WorkflowClient } from "../WorkflowClient"; +import { Status } from "../types/workflow"; + +describe("WorkflowClient", () => { + let mockClient: MockClient; + let workflowClient: WorkflowClient; + + beforeEach(() => { + const requestConfigBuilder = new GaroonRequestConfigBuilder({ + baseUrl: "https://example.cybozu.com/g", + auth: { + type: "password", + username: "cybozu", + password: "cybozu", + }, + }); + mockClient = new MockClient({ requestConfigBuilder, errorResponseHandler }); + workflowClient = new WorkflowClient(mockClient); + }); + + describe("getRequests", () => { + const params = { + limit: 100, + offset: 0, + fields: ["id", "number"], + orderBy: { + property: "createdAt" as const, + order: "desc" as const, + }, + rangeStartApprovedAt: "2020-01-01T00:00:00Z", + rangeEndApprovedAt: "2020-12-30T00:00:00Z", + form: 1, + status: ["UNPROCESSING" as const, "IN_PROGRESS" as const], + }; + beforeEach(async () => { + await workflowClient.getRequests(params); + }); + it("should pass the path to the http client", () => { + expect(mockClient.getLogs()[0].path).toBe( + "/api/v1/workflow/admin/requests" + ); + }); + it("should send a get request", () => { + expect(mockClient.getLogs()[0].method).toBe("get"); + }); + it("should pass limit, offset, fields, orderBy, rangeStartApprovedAt, rangeEndApprovedAt, form and status as a param to the http client", () => { + expect(mockClient.getLogs()[0].params).toEqual({ + limit: 100, + offset: 0, + fields: "id,number", + orderBy: "createdAt desc", + rangeStartApprovedAt: "2020-01-01T00:00:00Z", + rangeEndApprovedAt: "2020-12-30T00:00:00Z", + form: 1, + status: "UNPROCESSING,IN_PROGRESS", + }); + }); + }); +}); diff --git a/src/client/types/index.ts b/src/client/types/index.ts index a473adb..386ff83 100644 --- a/src/client/types/index.ts +++ b/src/client/types/index.ts @@ -1 +1,2 @@ export * from "./schedule"; +export * from "./workflow"; diff --git a/src/client/types/workflow/index.ts b/src/client/types/workflow/index.ts new file mode 100644 index 0000000..6013367 --- /dev/null +++ b/src/client/types/workflow/index.ts @@ -0,0 +1,95 @@ +export type Status = + | "UNPROCESSING" + | "IN_PROGRESS" + | "REJECTED" + | "WITHDRAWN" + | "SENT_BACK" + | "CANCELLED" + | "APPROVED" + | "COMPLETED"; +export type Operation = + | "SENT_BACK" + | "APPROVE" + | "REJECT" + | "WITHDRAW" + | "CANCEL" + | "CONFIRM" + | "ACKNOWLEDGE"; +export type Item = + | SingleLineTextItem + | MultiLineTextItem + | NumberItem + | CalcItem + | CheckBoxItem + | RadioButtonItem + | DropDownItem + | FileItem + | DateItem + | DateTimeItem + | RouteNaviItem; +export type SingleLineTextItem = { + name: string; + type: "SINGLE_LINE_TEXT"; + value: string; +}; +export type MultiLineTextItem = { + name: string; + type: "MULTI_LINE_TEXT"; + value: string; +}; +export type NumberItem = { + name: string; + type: "NUMBER"; + value: string; +}; +export type CalcItem = { + name: string; + type: "CALC"; + value: string; +}; +export type CheckBoxItem = { + name: string; + type: "CHECK_BOX"; + value: boolean; +}; +export type RadioButtonItem = { + name: string; + type: "RADIO_BUTTON"; + value: string; +}; +export type DropDownItem = { + name: string; + type: "DROP_DOWN"; + value: string; +}; +export type FileItem = { + name: string; + type: "FILE"; + value: Array<{ + id: string; + contentType: string; + name: string; + size: string; + }>; +}; +export type DateItem = { + name: string; + type: "DATE"; + value: string; +}; +export type DateTimeItem = { + name: string; + type: "DATETIME"; + value: { + date: string; + time: string; + }; +}; +export type RouteNaviItem = { + name: string; + type: "ROUTE_NAVI"; + value: { + route: string; + expense: string; + }; +};