@ikatec/digisac-api-sdk 4.4.1 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/apis/cards/CardsApi.cjs +70 -0
  2. package/dist/apis/cards/CardsApi.cjs.map +1 -1
  3. package/dist/apis/cards/CardsApi.d.ts +53 -1
  4. package/dist/apis/cards/CardsApi.d.ts.map +1 -1
  5. package/dist/apis/cards/CardsApi.mjs +70 -0
  6. package/dist/apis/cards/CardsApi.mjs.map +1 -1
  7. package/dist/apis/cards/types.d.ts +84 -0
  8. package/dist/apis/cards/types.d.ts.map +1 -1
  9. package/dist/apis/kanbanBoards/types.d.ts +33 -4
  10. package/dist/apis/kanbanBoards/types.d.ts.map +1 -1
  11. package/dist/apis/kanbanCards/KanbanCardsApi.cjs +8 -0
  12. package/dist/apis/kanbanCards/KanbanCardsApi.cjs.map +1 -1
  13. package/dist/apis/kanbanCards/KanbanCardsApi.d.ts +7 -1
  14. package/dist/apis/kanbanCards/KanbanCardsApi.d.ts.map +1 -1
  15. package/dist/apis/kanbanCards/KanbanCardsApi.mjs +8 -0
  16. package/dist/apis/kanbanCards/KanbanCardsApi.mjs.map +1 -1
  17. package/dist/apis/kanbanCards/types.d.ts +17 -5
  18. package/dist/apis/kanbanCards/types.d.ts.map +1 -1
  19. package/dist/apis/kanbanColumns/KanbanColumnsApi.cjs +9 -0
  20. package/dist/apis/kanbanColumns/KanbanColumnsApi.cjs.map +1 -1
  21. package/dist/apis/kanbanColumns/KanbanColumnsApi.d.ts +8 -1
  22. package/dist/apis/kanbanColumns/KanbanColumnsApi.d.ts.map +1 -1
  23. package/dist/apis/kanbanColumns/KanbanColumnsApi.mjs +9 -0
  24. package/dist/apis/kanbanColumns/KanbanColumnsApi.mjs.map +1 -1
  25. package/dist/apis/kanbanColumns/types.d.ts +14 -3
  26. package/dist/apis/kanbanColumns/types.d.ts.map +1 -1
  27. package/dist/apis/pipeline/PipelineApi.cjs +133 -0
  28. package/dist/apis/pipeline/PipelineApi.cjs.map +1 -1
  29. package/dist/apis/pipeline/PipelineApi.d.ts +102 -1
  30. package/dist/apis/pipeline/PipelineApi.d.ts.map +1 -1
  31. package/dist/apis/pipeline/PipelineApi.mjs +133 -0
  32. package/dist/apis/pipeline/PipelineApi.mjs.map +1 -1
  33. package/dist/apis/pipeline/types.d.ts +164 -0
  34. package/dist/apis/pipeline/types.d.ts.map +1 -1
  35. package/package.json +1 -1
  36. package/src/apis/cards/CardsApi.test.ts +128 -0
  37. package/src/apis/cards/CardsApi.ts +124 -1
  38. package/src/apis/cards/types.ts +81 -0
  39. package/src/apis/kanbanBoards/types.ts +36 -4
  40. package/src/apis/kanbanCards/KanbanCardsApi.test.ts +46 -0
  41. package/src/apis/kanbanCards/KanbanCardsApi.ts +23 -1
  42. package/src/apis/kanbanCards/types.ts +18 -5
  43. package/src/apis/kanbanColumns/KanbanColumnsApi.test.ts +46 -0
  44. package/src/apis/kanbanColumns/KanbanColumnsApi.ts +24 -1
  45. package/src/apis/kanbanColumns/types.ts +15 -3
  46. package/src/apis/payloads.test-d.ts +30 -0
  47. package/src/apis/pipeline/PipelineApi.test.ts +193 -0
  48. package/src/apis/pipeline/PipelineApi.ts +274 -1
  49. package/src/apis/pipeline/types.ts +162 -0
@@ -1,4 +1,5 @@
1
1
  import type { Department } from '../departments/types';
2
+ import type { WhereClause } from '../../core/types';
2
3
  export type PipelineStageStatus = {
3
4
  id: string;
4
5
  /** The default "Finalizados" stage carries `STATUS_WON` and `STATUS_LOOSE`. */
@@ -86,4 +87,167 @@ export type UpdatePipelinePayload = {
86
87
  goBack?: boolean;
87
88
  permissionToViewCard?: boolean | null;
88
89
  };
90
+ /** Codes the stage routes answer (as a JSON string, with 200) instead of the stage. */
91
+ export type PipelineStageRefusal = 'STAGES_LIMIT' | 'STAGE_ALREADY_EXIST';
92
+ export type PipelineStageStatusInput = {
93
+ /** Send the `id` of an existing status to keep it on update; omit it to create one. */
94
+ id?: string;
95
+ name: string;
96
+ position: number;
97
+ };
98
+ export type PipelineStageReasonInput = {
99
+ /** Send the `id` of an existing reason to keep it on update; omit it to create one. */
100
+ id?: string;
101
+ name: string;
102
+ position: number;
103
+ isWon?: boolean;
104
+ };
105
+ export type CreatePipelineStagePayload = {
106
+ /** Up to 255 characters; must not repeat the name of another stage of the pipeline. */
107
+ name: string;
108
+ pipelineId: string;
109
+ /** Must be truthy — the backend `required` rule refuses `0`. */
110
+ position: number;
111
+ /** Up to 10. */
112
+ statuses?: PipelineStageStatusInput[];
113
+ /** Up to 20. */
114
+ reasons?: PipelineStageReasonInput[];
115
+ };
116
+ export type UpdatePipelineStagePayload = CreatePipelineStagePayload & {
117
+ /**
118
+ * The stage id. Send it: the duplicate-name check compares against the `id` of the body,
119
+ * so without it keeping the current name answers `'STAGE_ALREADY_EXIST'`.
120
+ */
121
+ id?: string;
122
+ };
123
+ /**
124
+ * Where the cards of a deleted stage go:
125
+ * - `typeAction: 'move'` moves them to `idNewStage` (a stage of the same pipeline), clearing
126
+ * status, reason and result;
127
+ * - without `typeAction`, they are archived and moved to the first stage of the pipeline.
128
+ */
129
+ export type DeletePipelineStagePayload = {
130
+ typeAction?: 'move';
131
+ idNewStage?: string;
132
+ /**
133
+ * Author of the card movements written by `'move'` — the backend reads it from the body, not
134
+ * from the token (the UI sends the logged user). Without it, moving a stage that has cards
135
+ * fails with 500 (`CardMovement.userId cannot be null`) after the cards were already moved,
136
+ * and the stage is kept.
137
+ */
138
+ user?: {
139
+ id: string;
140
+ };
141
+ };
142
+ /** Answer of the stage deletion; `'PIPELINES_MIN_STAGES'` = refused, the pipeline keeps 2 stages. */
143
+ export type DeletePipelineStageResult = 'MESSAGE_STAGE_DELETED' | 'PIPELINES_MIN_STAGES';
144
+ export type PipelineStatusRef = {
145
+ id: string;
146
+ name: string;
147
+ };
148
+ /** `GET /pipelines/status/{id}`: the status plus how many cards use it. */
149
+ export type PipelineStageStatusWithCount = PipelineStageStatus & {
150
+ cardsCount: number;
151
+ };
152
+ /**
153
+ * What happens to the cards of a deleted status: `'keep-without-status'` clears their status,
154
+ * `'move-to-another'` sets `newStatusId` (not validated by the backend).
155
+ */
156
+ export type DeletePipelineStatusPayload = {
157
+ action: 'keep-without-status';
158
+ } | {
159
+ action: 'move-to-another';
160
+ newStatusId: string;
161
+ };
162
+ export type PipelineAutomationType = 'notification';
163
+ export type PipelineAutomationConfig = {
164
+ notify?: 'high' | 'medium' | 'medium-high';
165
+ frequency?: 'daily' | 'weekly' | 'biweekly' | 'monthly';
166
+ day?: string;
167
+ from?: {
168
+ high?: string;
169
+ medium?: string;
170
+ };
171
+ recipient?: 'admin' | 'owner' | 'both';
172
+ };
173
+ export type PipelineAutomation = {
174
+ id: string;
175
+ pipelineId: string;
176
+ type: PipelineAutomationType;
177
+ isArchived: boolean | null;
178
+ archivedAt: string | null;
179
+ config: PipelineAutomationConfig;
180
+ createdAt: string;
181
+ updatedAt: string;
182
+ };
183
+ export type CreatePipelineAutomationPayload = {
184
+ pipelineId: string;
185
+ /** The backend does not validate it (any string is stored); `'notification'` is the only one used. */
186
+ type: PipelineAutomationType;
187
+ config?: PipelineAutomationConfig;
188
+ isArchived?: boolean;
189
+ };
190
+ /** The backend validator requires `pipelineId` and `type` on update too. */
191
+ export type UpdatePipelineAutomationPayload = CreatePipelineAutomationPayload;
192
+ /** Card date filters of the CSV export; the UI sends `'YYYY-MM-DD HH:mm:ss'`. */
193
+ export type ExportPipelinePayload = {
194
+ /** Without both `createdAt*`, the range defaults to now (empty export). */
195
+ createdAtInit?: string;
196
+ createdAtFinal?: string;
197
+ /** With both `wonAt*`, only won cards are exported. */
198
+ wonAtInit?: string;
199
+ wonAtFinal?: string;
200
+ };
201
+ export type PipelinesWithTotalsQuery = {
202
+ /** `accountId` is always injected by the backend. `$and[].createdAt` filters the cards. */
203
+ where?: WhereClause<PipelineRecord>;
204
+ order?: [string, 'ASC' | 'DESC'][];
205
+ limit?: number;
206
+ /** `true` lists only the pipelines that are not archived. */
207
+ archive?: boolean;
208
+ };
209
+ export type PipelineTotals = {
210
+ id: string;
211
+ name: string;
212
+ total: number | null;
213
+ totalSuccess: number | null;
214
+ totalFailed: number | null;
215
+ /** A bigint, sent as a string. */
216
+ totalCards: string;
217
+ };
218
+ /**
219
+ * Pipelines without stages are left out; there is no pagination. With a `createdAt` filter, only
220
+ * the stages that have matching cards are listed, but `totals` still has every stage.
221
+ */
222
+ export type PipelinesWithTotals = {
223
+ data: (PipelineRecord & {
224
+ stages: PipelineStage[];
225
+ totals: PipelineTotals[];
226
+ })[];
227
+ };
228
+ /** Card filters of the stage totals (same as the board filters of the UI). */
229
+ export type PipelineStageTotalsFilters = {
230
+ status?: {
231
+ value: string;
232
+ };
233
+ service?: {
234
+ id: string;
235
+ };
236
+ nameTitle?: string;
237
+ /** `DD/MM/YYYY`. */
238
+ createdFrom?: string;
239
+ createdUntil?: string;
240
+ owner?: {
241
+ id: string;
242
+ value?: string;
243
+ };
244
+ wonFrom?: string;
245
+ wonUntil?: string;
246
+ };
247
+ export type PipelineStageTotals = PipelineStage & {
248
+ totalCards: number;
249
+ totalCardsValue: number;
250
+ totalWonCardsValue: number;
251
+ totalLostCardsValue: number;
252
+ };
89
253
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/apis/pipeline/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/apis/pipeline/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAEnD,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAA;IAChC,OAAO,CAAC,EAAE,mBAAmB,EAAE,CAAA;IAC/B,uFAAuF;IACvF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,CAAA;AAED,kGAAkG;AAClG,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,oBAAoB,EAAE,OAAO,GAAG,IAAI,CAAA;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,OAAO,CAAA;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oBAAoB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAEzB,MAAM,CAAC,EAAE,aAAa,EAAE,CAAA;IACxB,WAAW,CAAC,EAAE,UAAU,EAAE,CAAA;CAC3B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,QAAQ,CAAA;AAE5D,MAAM,MAAM,qBAAqB,GAAG;IAClC,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gEAAgE;IAChE,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAC/B,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAClC,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,oBAAoB,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACtC,CAAA;AAED,uFAAuF;AACvF,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,qBAAqB,CAAA;AAEzE,MAAM,MAAM,wBAAwB,GAAG;IACrC,uFAAuF;IACvF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,uFAAuF;IACvF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,uFAAuF;IACvF,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB;IAChB,QAAQ,CAAC,EAAE,wBAAwB,EAAE,CAAA;IACrC,gBAAgB;IAChB,OAAO,CAAC,EAAE,wBAAwB,EAAE,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG,0BAA0B,GAAG;IACpE;;;OAGG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;CACZ,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,IAAI,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CACtB,CAAA;AAED,qGAAqG;AACrG,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,GAAG,sBAAsB,CAAA;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAE5D,2EAA2E;AAC3E,MAAM,MAAM,4BAA4B,GAAG,mBAAmB,GAAG;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvF;;;GAGG;AACH,MAAM,MAAM,2BAA2B,GACnC;IAAE,MAAM,EAAE,qBAAqB,CAAA;CAAE,GACjC;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtD,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAA;AAEnD,MAAM,MAAM,wBAAwB,GAAG;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,aAAa,CAAA;IAC1C,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAA;IACvD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACzC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;CACvC,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,sBAAsB,CAAA;IAC5B,UAAU,EAAE,OAAO,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,wBAAwB,CAAA;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,UAAU,EAAE,MAAM,CAAA;IAClB,sGAAsG;IACtG,IAAI,EAAE,sBAAsB,CAAA;IAC5B,MAAM,CAAC,EAAE,wBAAwB,CAAA;IACjC,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB,CAAA;AAED,4EAA4E;AAC5E,MAAM,MAAM,+BAA+B,GAAG,+BAA+B,CAAA;AAE7E,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,GAAG;IAClC,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,2FAA2F;IAC3F,KAAK,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAA;IACnC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC,EAAE,CAAA;IAClC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,CAAC,cAAc,GAAG;QAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QAAC,MAAM,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC,EAAE,CAAA;CACjF,CAAA;AAED,8EAA8E;AAC9E,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oBAAoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG;IAChD,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,mBAAmB,EAAE,MAAM,CAAA;CAC5B,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikatec/digisac-api-sdk",
3
- "version": "4.4.1",
3
+ "version": "4.5.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -0,0 +1,128 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import type { ApiClient } from '../../core/ApiClient'
3
+ import { CardsApi } from './CardsApi'
4
+
5
+ // ── Mock client factory ──────────────────────────────────────────────
6
+
7
+ function createMockClient(): ApiClient {
8
+ return {
9
+ setAccessToken: vi.fn().mockReturnThis(),
10
+ request: vi.fn(),
11
+ get: vi.fn(),
12
+ post: vi.fn(),
13
+ put: vi.fn(),
14
+ patch: vi.fn(),
15
+ delete: vi.fn(),
16
+ }
17
+ }
18
+
19
+ const headers = { 'X-Custom': 'value' }
20
+
21
+ // ── Tests ────────────────────────────────────────────────────────────
22
+
23
+ describe('CardsApi', () => {
24
+ let client: ReturnType<typeof createMockClient>
25
+ let api: CardsApi
26
+
27
+ beforeEach(() => {
28
+ client = createMockClient()
29
+ api = new CardsApi(client)
30
+ })
31
+
32
+ describe('bulkMove', () => {
33
+ it('calls client.put with /cards/bulk-move and body', async () => {
34
+ vi.mocked(client.put).mockResolvedValue({ movedCount: 2, ignoredCount: 0 })
35
+ const body = {
36
+ ids: ['c1', 'c2'],
37
+ originPipelineStageId: 's1',
38
+ pipelineId: 'p1',
39
+ pipelineStageId: 's2',
40
+ }
41
+ const result = await api.bulkMove(body, headers)
42
+ expect(client.put).toHaveBeenCalledWith('/cards/bulk-move', body, headers)
43
+ expect(result).toEqual({ movedCount: 2, ignoredCount: 0 })
44
+ })
45
+ })
46
+
47
+ describe('updateOrder', () => {
48
+ it('calls client.put with /cards/order/:id and the order', async () => {
49
+ await api.updateOrder('c1', 3)
50
+ expect(client.put).toHaveBeenCalledWith('/cards/order/c1', { order: 3 }, undefined)
51
+ })
52
+ })
53
+
54
+ describe('products', () => {
55
+ it('saveProduct calls client.post with /cards/product and body', async () => {
56
+ vi.mocked(client.post).mockResolvedValue({ message: 'Operação realizada com sucesso.' })
57
+ const body = { cardId: 'c1', name: 'Plano', ammount: 2, value: 99.9 }
58
+ const result = await api.saveProduct(body)
59
+ expect(client.post).toHaveBeenCalledWith('/cards/product', body, undefined)
60
+ expect(result).toEqual({ message: 'Operação realizada com sucesso.' })
61
+ })
62
+
63
+ it('deleteProduct calls client.delete with /cards/product/:id', async () => {
64
+ await api.deleteProduct('pr1', headers)
65
+ expect(client.delete).toHaveBeenCalledWith('/cards/product/pr1', headers)
66
+ })
67
+ })
68
+
69
+ describe('comments', () => {
70
+ it('saveComment calls client.post with /cards/comment and body', async () => {
71
+ const body = { cardId: 'c1', comment: 'Ligar amanhã' }
72
+ await api.saveComment(body)
73
+ expect(client.post).toHaveBeenCalledWith('/cards/comment', body, undefined)
74
+ })
75
+
76
+ it('saveComment edits by id', async () => {
77
+ const body = { id: 'cm1', comment: 'Ligar hoje', removedFileIds: ['f1'] }
78
+ await api.saveComment(body, headers)
79
+ expect(client.post).toHaveBeenCalledWith('/cards/comment', body, headers)
80
+ })
81
+
82
+ it('deleteComment calls client.delete with /cards/comment/:id', async () => {
83
+ await api.deleteComment('cm1')
84
+ expect(client.delete).toHaveBeenCalledWith('/cards/comment/cm1', undefined)
85
+ })
86
+ })
87
+
88
+ describe('reads', () => {
89
+ it('getOwners calls client.post with /cards/owners and no body', async () => {
90
+ vi.mocked(client.post).mockResolvedValue([{ id: 'u1', name: 'Ana', disabled: false }])
91
+ const result = await api.getOwners()
92
+ expect(client.post).toHaveBeenCalledWith('/cards/owners', undefined, undefined)
93
+ expect(result).toEqual([{ id: 'u1', name: 'Ana', disabled: false }])
94
+ })
95
+
96
+ it('getCards calls client.post with /cards/getCards and the query', async () => {
97
+ const body = {
98
+ where: { accountId: 'a1', pipelineStageId: 's1' },
99
+ attributes: ['id', 'title'] as ('id' | 'title')[],
100
+ order: [['contact.name', 'ASC']] as ['contact.name', 'ASC'][],
101
+ limit: 20,
102
+ }
103
+ await api.getCards(body)
104
+ expect(client.post).toHaveBeenCalledWith('/cards/getCards', body, undefined)
105
+ })
106
+
107
+ it('getContactTotals calls client.post with /cards/contact-totals/:contactId', async () => {
108
+ vi.mocked(client.post).mockResolvedValue({ totalOpen: 1 })
109
+ const result = await api.getContactTotals('ct1', headers)
110
+ expect(client.post).toHaveBeenCalledWith('/cards/contact-totals/ct1', undefined, headers)
111
+ expect(result).toEqual({ totalOpen: 1 })
112
+ })
113
+ })
114
+
115
+ describe('error propagation', () => {
116
+ it('propagates errors from bulkMove', async () => {
117
+ vi.mocked(client.put).mockRejectedValue(new Error('At least one card id is required.'))
118
+ await expect(
119
+ api.bulkMove({
120
+ ids: [],
121
+ originPipelineStageId: 's1',
122
+ pipelineId: 'p1',
123
+ pipelineStageId: 's2',
124
+ }),
125
+ ).rejects.toThrow('At least one card id is required.')
126
+ })
127
+ })
128
+ })
@@ -1,9 +1,132 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { BaseCrudApi } from '../../core/BaseCrudApi'
3
- import type { Card, CreateCardPayload, UpdateCardPayload } from './types'
3
+ import type {
4
+ BulkMoveCardsPayload,
5
+ BulkMoveCardsResult,
6
+ Card,
7
+ CardContactTotals,
8
+ CardOperationResult,
9
+ CardOwner,
10
+ CreateCardPayload,
11
+ GetCardsPayload,
12
+ SaveCardCommentPayload,
13
+ SaveCardProductPayload,
14
+ UpdateCardPayload,
15
+ } from './types'
4
16
 
5
17
  export class CardsApi extends BaseCrudApi<Card, CreateCardPayload, UpdateCardPayload> {
6
18
  constructor(client: ApiClient) {
7
19
  super(client, '/cards')
8
20
  }
21
+
22
+ /**
23
+ * Moves several cards of one stage to another stage (of the same or another pipeline),
24
+ * clearing their status/reason unless given. Cards outside the account or not in
25
+ * `originPipelineStageId` are skipped and counted in `ignoredCount`.
26
+ * @permissions pipelines.opportunity.update
27
+ */
28
+ bulkMove(
29
+ body: BulkMoveCardsPayload,
30
+ headers?: Record<string, string>,
31
+ ): Promise<BulkMoveCardsResult> {
32
+ return this.client.put<BulkMoveCardsResult, BulkMoveCardsPayload>(
33
+ `${this.basePath}/bulk-move`,
34
+ body,
35
+ headers,
36
+ )
37
+ }
38
+
39
+ /**
40
+ * Moves a card to the `order` position of its stage, shifting the cards in between. Resolves
41
+ * to the card as stored. The backend refuses `0` (400 `required`), although the first card of
42
+ * a stage has order 0.
43
+ * @permissions pipelines.opportunity.update
44
+ */
45
+ updateOrder(id: string, order: number, headers?: Record<string, string>): Promise<Card> {
46
+ return this.client.put<Card, { order: number }>(
47
+ `${this.basePath}/order/${id}`,
48
+ { order },
49
+ headers,
50
+ )
51
+ }
52
+
53
+ /**
54
+ * Adds a product to a card, or updates the product of `body.id`.
55
+ * @permissions pipelines.opportunity.update
56
+ */
57
+ saveProduct(
58
+ body: SaveCardProductPayload,
59
+ headers?: Record<string, string>,
60
+ ): Promise<CardOperationResult> {
61
+ return this.client.post<CardOperationResult, SaveCardProductPayload>(
62
+ `${this.basePath}/product`,
63
+ body,
64
+ headers,
65
+ )
66
+ }
67
+
68
+ /**
69
+ * Deletes a product of a card. An unknown id also resolves with 200, with the message
70
+ * `'Produto não encontrado'`.
71
+ * @permissions pipelines.opportunity.update
72
+ */
73
+ deleteProduct(id: string, headers?: Record<string, string>): Promise<CardOperationResult> {
74
+ return this.client.delete<CardOperationResult>(`${this.basePath}/product/${id}`, headers)
75
+ }
76
+
77
+ /**
78
+ * Adds a comment to a card, or edits the comment of `body.id`. Resolves to a message, not to
79
+ * the comment — read it back through `getById(cardId, { include: ['comments'] })`. A card or
80
+ * comment of another account fails with 404.
81
+ * @permissions pipelines.opportunity.update
82
+ */
83
+ saveComment(
84
+ body: SaveCardCommentPayload,
85
+ headers?: Record<string, string>,
86
+ ): Promise<CardOperationResult> {
87
+ return this.client.post<CardOperationResult, SaveCardCommentPayload>(
88
+ `${this.basePath}/comment`,
89
+ body,
90
+ headers,
91
+ )
92
+ }
93
+
94
+ /**
95
+ * Deletes a comment of a card. Resolves with no content; unknown → 404.
96
+ * @permissions pipelines.opportunity.update
97
+ */
98
+ deleteComment(id: string, headers?: Record<string, string>): Promise<void> {
99
+ return this.client.delete<void>(`${this.basePath}/comment/${id}`, headers)
100
+ }
101
+
102
+ /**
103
+ * Lists the users of the account that can own a card, sorted by name.
104
+ * @permissions pipelines.opportunity.view
105
+ */
106
+ getOwners(headers?: Record<string, string>): Promise<CardOwner[]> {
107
+ return this.client.post<CardOwner[]>(`${this.basePath}/owners`, undefined, headers)
108
+ }
109
+
110
+ /**
111
+ * Lists cards with the board query of the UI. Resolves to a plain array (no pagination).
112
+ * @permissions pipelines.opportunity.view
113
+ */
114
+ getCards(body: GetCardsPayload, headers?: Record<string, string>): Promise<Card[]> {
115
+ return this.client.post<Card[], GetCardsPayload>(`${this.basePath}/getCards`, body, headers)
116
+ }
117
+
118
+ /**
119
+ * Counts the open cards of a contact visible to the caller.
120
+ * @permissions pipelines.opportunity.view
121
+ */
122
+ getContactTotals(
123
+ contactId: string,
124
+ headers?: Record<string, string>,
125
+ ): Promise<CardContactTotals> {
126
+ return this.client.post<CardContactTotals>(
127
+ `${this.basePath}/contact-totals/${contactId}`,
128
+ undefined,
129
+ headers,
130
+ )
131
+ }
9
132
  }
@@ -1,3 +1,4 @@
1
+ import type { ListQuery, WhereClause } from '../../core/types'
1
2
  import type { Contact } from '../contacts/types'
2
3
  import type {
3
4
  Pipeline,
@@ -98,6 +99,8 @@ export type Card = {
98
99
  lastMessage?: unknown
99
100
  /** Only on getMany/getById answers: sum of the products (`ammount * value`). */
100
101
  totalValue?: number
102
+ /** Only on `getCards` answers. */
103
+ contactName?: string
101
104
  // relationships
102
105
  contact?: Contact | null
103
106
  owner?: User | null
@@ -158,3 +161,81 @@ export type UpdateCardPayload = {
158
161
  success?: boolean | null
159
162
  finishedAt?: string | null
160
163
  }
164
+
165
+ /** Answer of the product and comment writes — the entity itself is not returned. */
166
+ export type CardOperationResult = { message: string }
167
+
168
+ export type BulkMoveCardsPayload = {
169
+ /** At least one; ids outside the account or not in `originPipelineStageId` are ignored. */
170
+ ids: string[]
171
+ originPipelineStageId: string
172
+ /** Destination pipeline; `pipelineStageId` must be one of its stages. */
173
+ pipelineId: string
174
+ pipelineStageId: string
175
+ /** Must be a status of the destination stage, unless it is the last stage. */
176
+ statusId?: string
177
+ /** Kept only when the destination is the last stage (then `finishedAt` is set). */
178
+ success?: boolean
179
+ reasonId?: string
180
+ }
181
+
182
+ export type BulkMoveCardsResult = { movedCount: number; ignoredCount: number }
183
+
184
+ /**
185
+ * Creates a product of a card, or updates the product of `id`. No validation on the backend:
186
+ * a missing `name`/`ammount`/`value` fails with 500.
187
+ */
188
+ export type SaveCardProductPayload = {
189
+ id?: string
190
+ cardId: string
191
+ name: string
192
+ /** Quantity — integer (the backend column is spelled `ammount`). */
193
+ ammount: number
194
+ value: number
195
+ }
196
+
197
+ /**
198
+ * Creates a comment on `cardId`, or edits the comment of `id`. It needs text or at least one
199
+ * file. `files` are references to files already uploaded (see `FilesApi`) — `.pdf`, `.txt`,
200
+ * `.html` or `.md`, up to 10 files of 10 MB.
201
+ */
202
+ export type SaveCardCommentPayload =
203
+ | { id?: undefined; cardId: string; comment?: string; files?: { id: string }[] }
204
+ | {
205
+ id: string
206
+ cardId?: string
207
+ comment?: string
208
+ files?: { id: string }[]
209
+ removedFileIds?: string[]
210
+ }
211
+
212
+ export type CardOwner = {
213
+ id: string
214
+ /** Suffixed with " - Sem permissão" (in the caller's language) when `disabled`. */
215
+ name: string
216
+ /** `true` for users without `pipelines.opportunity.update`. */
217
+ disabled: boolean
218
+ }
219
+
220
+ /**
221
+ * Body of `POST /cards/getCards`: Sequelize options sent as-is. The route does **not** scope the
222
+ * query by account — without `where.accountId` it answers cards of other accounts. `order`
223
+ * accepts card fields and `contact.name`.
224
+ */
225
+ export type GetCardsPayload = Omit<
226
+ ListQuery<Card>,
227
+ 'page' | 'perPage' | 'paginate' | 'where' | 'order' | 'attributes'
228
+ > & {
229
+ where: WhereClause<Card> & { accountId: string }
230
+ /**
231
+ * The card columns to answer. Required in practice: the backend selects only these (plus the
232
+ * computed `contactName` and `totalValue`), so without them each card has no other field.
233
+ */
234
+ attributes: (keyof Card & string)[]
235
+ order?: [(keyof Card & string) | 'contact.name', 'ASC' | 'DESC'][]
236
+ }
237
+
238
+ export type CardContactTotals = {
239
+ /** Open cards of the contact (not archived, not finished, in a pipeline that exists). */
240
+ totalOpen: number
241
+ }
@@ -1,14 +1,32 @@
1
1
  import type { Department } from '../departments/types'
2
2
  import type { KanbanColumn } from '../kanbanColumns/types'
3
3
 
4
+ export type KanbanBoardVisibleTickets = 'mine' | 'all' | 'mine-and-not-assigned'
5
+
6
+ export type KanbanBoardClosedMaxTimeUnit = 'minutes' | 'hours' | 'days' | 'weeks' | 'months'
7
+
8
+ /** Card fields shown on the board. */
9
+ export type KanbanBoardSettings = {
10
+ lastMessage: boolean
11
+ departmentAndUser: boolean
12
+ tags: boolean
13
+ createdAtDate: boolean
14
+ newInteractions: boolean
15
+ timeoutAlert: boolean
16
+ }
17
+
4
18
  export type KanbanBoard = {
5
19
  id: string
6
20
  name: string
7
- timeoutAlertEnabled: boolean
8
21
  accountId: string
22
+ visibleTickets: KanbanBoardVisibleTickets | null
23
+ closedMaxTime: number | null
24
+ closedMaxTimeUnit: KanbanBoardClosedMaxTimeUnit | null
25
+ /** `'initializing'` while the cards of the open tickets are being created. */
26
+ status: 'initializing' | 'ready'
27
+ settings: KanbanBoardSettings
9
28
  createdAt: string
10
29
  updatedAt: string
11
- deletedAt: string | null
12
30
  // relationships
13
31
  departments?: Department[]
14
32
  columns?: KanbanColumn[]
@@ -18,12 +36,26 @@ export type KanbanBoardRelationships = 'departments' | 'columns'
18
36
 
19
37
  export type CreateKanbanBoardPayload = {
20
38
  name: string
21
- timeoutAlertEnabled?: boolean
22
39
  /** Department IDs to link to the board. Required by the backend on creation. */
23
40
  departmentIds: string[]
41
+ visibleTickets?: KanbanBoardVisibleTickets
42
+ /** Greater than 0. */
43
+ closedMaxTime?: number
44
+ closedMaxTimeUnit?: KanbanBoardClosedMaxTimeUnit
45
+ settings?: Partial<KanbanBoardSettings>
24
46
  }
25
47
 
26
48
  export type UpdateKanbanBoardPayload = {
27
49
  name?: string
28
- timeoutAlertEnabled?: boolean
50
+ /**
51
+ * Replaces the departments of the board (at least one): removing a department deletes the
52
+ * cards of its tickets, adding one creates cards for its open tickets.
53
+ */
54
+ departmentIds?: string[]
55
+ visibleTickets?: KanbanBoardVisibleTickets
56
+ /** Greater than 0. */
57
+ closedMaxTime?: number
58
+ closedMaxTimeUnit?: KanbanBoardClosedMaxTimeUnit
59
+ /** Merged into the current settings — send only the keys to change. */
60
+ settings?: Partial<KanbanBoardSettings>
29
61
  }
@@ -0,0 +1,46 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import type { ApiClient } from '../../core/ApiClient'
3
+ import { KanbanCardsApi } from './KanbanCardsApi'
4
+
5
+ // ── Mock client factory ──────────────────────────────────────────────
6
+
7
+ function createMockClient(): ApiClient {
8
+ return {
9
+ setAccessToken: vi.fn().mockReturnThis(),
10
+ request: vi.fn(),
11
+ get: vi.fn(),
12
+ post: vi.fn(),
13
+ put: vi.fn(),
14
+ patch: vi.fn(),
15
+ delete: vi.fn(),
16
+ }
17
+ }
18
+
19
+ const headers = { 'X-Custom': 'value' }
20
+
21
+ // ── Tests ────────────────────────────────────────────────────────────
22
+
23
+ describe('KanbanCardsApi', () => {
24
+ let client: ReturnType<typeof createMockClient>
25
+ let api: KanbanCardsApi
26
+
27
+ beforeEach(() => {
28
+ client = createMockClient()
29
+ api = new KanbanCardsApi(client)
30
+ })
31
+
32
+ describe('reorder', () => {
33
+ it('calls client.patch with /kanban-cards/:id/reorder and body', async () => {
34
+ vi.mocked(client.patch).mockResolvedValue({ id: 'kc1', position: 'N' })
35
+ const body = { columnId: 'col2', moveTo: 'end' as const }
36
+ const result = await api.reorder('kc1', body, headers)
37
+ expect(client.patch).toHaveBeenCalledWith('/kanban-cards/kc1/reorder', body, headers)
38
+ expect(result).toEqual({ id: 'kc1', position: 'N' })
39
+ })
40
+
41
+ it('propagates errors', async () => {
42
+ vi.mocked(client.patch).mockRejectedValue(new Error('Invalid reorder operation.'))
43
+ await expect(api.reorder('kc1', {})).rejects.toThrow('Invalid reorder operation.')
44
+ })
45
+ })
46
+ })