@ikatec/digisac-api-sdk 4.14.0 → 4.16.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 (217) hide show
  1. package/README.md +10 -3
  2. package/dist/apis/absence/AbsenceApi.cjs +2 -2
  3. package/dist/apis/absence/AbsenceApi.cjs.map +1 -1
  4. package/dist/apis/absence/AbsenceApi.d.ts +2 -2
  5. package/dist/apis/absence/AbsenceApi.mjs +2 -2
  6. package/dist/apis/absence/AbsenceApi.mjs.map +1 -1
  7. package/dist/apis/absence/types.d.ts +1 -1
  8. package/dist/apis/answers/AnswersApi.cjs +23 -2
  9. package/dist/apis/answers/AnswersApi.cjs.map +1 -1
  10. package/dist/apis/answers/AnswersApi.d.ts +16 -2
  11. package/dist/apis/answers/AnswersApi.d.ts.map +1 -1
  12. package/dist/apis/answers/AnswersApi.mjs +23 -2
  13. package/dist/apis/answers/AnswersApi.mjs.map +1 -1
  14. package/dist/apis/answers/types.d.ts +1 -2
  15. package/dist/apis/answers/types.d.ts.map +1 -1
  16. package/dist/apis/authHistory/AuthHistoryApi.cjs +9 -2
  17. package/dist/apis/authHistory/AuthHistoryApi.cjs.map +1 -1
  18. package/dist/apis/authHistory/AuthHistoryApi.d.ts +7 -2
  19. package/dist/apis/authHistory/AuthHistoryApi.d.ts.map +1 -1
  20. package/dist/apis/authHistory/AuthHistoryApi.mjs +9 -2
  21. package/dist/apis/authHistory/AuthHistoryApi.mjs.map +1 -1
  22. package/dist/apis/authHistory/types.d.ts +6 -1
  23. package/dist/apis/authHistory/types.d.ts.map +1 -1
  24. package/dist/apis/bots/BotsApi.cjs +16 -7
  25. package/dist/apis/bots/BotsApi.cjs.map +1 -1
  26. package/dist/apis/bots/BotsApi.d.ts +13 -7
  27. package/dist/apis/bots/BotsApi.d.ts.map +1 -1
  28. package/dist/apis/bots/BotsApi.mjs +16 -7
  29. package/dist/apis/bots/BotsApi.mjs.map +1 -1
  30. package/dist/apis/bots/types.d.ts +12 -2
  31. package/dist/apis/bots/types.d.ts.map +1 -1
  32. package/dist/apis/campaigns/CampaignsApi.cjs +25 -1
  33. package/dist/apis/campaigns/CampaignsApi.cjs.map +1 -1
  34. package/dist/apis/campaigns/CampaignsApi.d.ts +16 -1
  35. package/dist/apis/campaigns/CampaignsApi.d.ts.map +1 -1
  36. package/dist/apis/campaigns/CampaignsApi.mjs +25 -1
  37. package/dist/apis/campaigns/CampaignsApi.mjs.map +1 -1
  38. package/dist/apis/campaigns/types.d.ts +98 -6
  39. package/dist/apis/campaigns/types.d.ts.map +1 -1
  40. package/dist/apis/cards/types.d.ts +5 -5
  41. package/dist/apis/categories/CategoriesApi.cjs +1 -2
  42. package/dist/apis/categories/CategoriesApi.cjs.map +1 -1
  43. package/dist/apis/categories/CategoriesApi.d.ts +1 -2
  44. package/dist/apis/categories/CategoriesApi.d.ts.map +1 -1
  45. package/dist/apis/categories/CategoriesApi.mjs +1 -2
  46. package/dist/apis/categories/CategoriesApi.mjs.map +1 -1
  47. package/dist/apis/clientFeedback/ClientFeedbackApi.cjs +2 -3
  48. package/dist/apis/clientFeedback/ClientFeedbackApi.cjs.map +1 -1
  49. package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts +2 -3
  50. package/dist/apis/clientFeedback/ClientFeedbackApi.d.ts.map +1 -1
  51. package/dist/apis/clientFeedback/ClientFeedbackApi.mjs +2 -3
  52. package/dist/apis/clientFeedback/ClientFeedbackApi.mjs.map +1 -1
  53. package/dist/apis/contacts/ContactsApi.cjs +20 -6
  54. package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
  55. package/dist/apis/contacts/ContactsApi.d.ts +28 -4
  56. package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
  57. package/dist/apis/contacts/ContactsApi.mjs +20 -6
  58. package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
  59. package/dist/apis/contacts/types.d.ts +101 -2
  60. package/dist/apis/contacts/types.d.ts.map +1 -1
  61. package/dist/apis/customFields/types.d.ts +4 -4
  62. package/dist/apis/dashboard/DashboardApi.cjs +3 -3
  63. package/dist/apis/dashboard/DashboardApi.cjs.map +1 -1
  64. package/dist/apis/dashboard/DashboardApi.d.ts +3 -3
  65. package/dist/apis/dashboard/DashboardApi.mjs +3 -3
  66. package/dist/apis/dashboard/DashboardApi.mjs.map +1 -1
  67. package/dist/apis/dashboard/types.d.ts +11 -4
  68. package/dist/apis/dashboard/types.d.ts.map +1 -1
  69. package/dist/apis/departments/DepartmentsApi.cjs +5 -5
  70. package/dist/apis/departments/DepartmentsApi.cjs.map +1 -1
  71. package/dist/apis/departments/DepartmentsApi.d.ts +5 -5
  72. package/dist/apis/departments/DepartmentsApi.mjs +5 -5
  73. package/dist/apis/departments/DepartmentsApi.mjs.map +1 -1
  74. package/dist/apis/departments/types.d.ts +6 -0
  75. package/dist/apis/departments/types.d.ts.map +1 -1
  76. package/dist/apis/distribution/DistributionApi.cjs +3 -4
  77. package/dist/apis/distribution/DistributionApi.cjs.map +1 -1
  78. package/dist/apis/distribution/DistributionApi.d.ts +3 -4
  79. package/dist/apis/distribution/DistributionApi.d.ts.map +1 -1
  80. package/dist/apis/distribution/DistributionApi.mjs +3 -4
  81. package/dist/apis/distribution/DistributionApi.mjs.map +1 -1
  82. package/dist/apis/files/FilesApi.cjs +13 -1
  83. package/dist/apis/files/FilesApi.cjs.map +1 -1
  84. package/dist/apis/files/FilesApi.d.ts +12 -2
  85. package/dist/apis/files/FilesApi.d.ts.map +1 -1
  86. package/dist/apis/files/FilesApi.mjs +13 -1
  87. package/dist/apis/files/FilesApi.mjs.map +1 -1
  88. package/dist/apis/files/types.d.ts +19 -1
  89. package/dist/apis/files/types.d.ts.map +1 -1
  90. package/dist/apis/kanbanCards/KanbanCardsApi.cjs +32 -0
  91. package/dist/apis/kanbanCards/KanbanCardsApi.cjs.map +1 -1
  92. package/dist/apis/kanbanCards/KanbanCardsApi.d.ts +27 -1
  93. package/dist/apis/kanbanCards/KanbanCardsApi.d.ts.map +1 -1
  94. package/dist/apis/kanbanCards/KanbanCardsApi.mjs +32 -0
  95. package/dist/apis/kanbanCards/KanbanCardsApi.mjs.map +1 -1
  96. package/dist/apis/kanbanCards/types.d.ts +1 -1
  97. package/dist/apis/kanbanCards/types.d.ts.map +1 -1
  98. package/dist/apis/kanbanColumns/KanbanColumnsApi.cjs +19 -0
  99. package/dist/apis/kanbanColumns/KanbanColumnsApi.cjs.map +1 -1
  100. package/dist/apis/kanbanColumns/KanbanColumnsApi.d.ts +16 -1
  101. package/dist/apis/kanbanColumns/KanbanColumnsApi.d.ts.map +1 -1
  102. package/dist/apis/kanbanColumns/KanbanColumnsApi.mjs +19 -0
  103. package/dist/apis/kanbanColumns/KanbanColumnsApi.mjs.map +1 -1
  104. package/dist/apis/kanbanColumns/types.d.ts +4 -0
  105. package/dist/apis/kanbanColumns/types.d.ts.map +1 -1
  106. package/dist/apis/messages/MessagesApi.cjs +13 -4
  107. package/dist/apis/messages/MessagesApi.cjs.map +1 -1
  108. package/dist/apis/messages/MessagesApi.d.ts +13 -4
  109. package/dist/apis/messages/MessagesApi.d.ts.map +1 -1
  110. package/dist/apis/messages/MessagesApi.mjs +13 -4
  111. package/dist/apis/messages/MessagesApi.mjs.map +1 -1
  112. package/dist/apis/messages/types.d.ts +5 -0
  113. package/dist/apis/messages/types.d.ts.map +1 -1
  114. package/dist/apis/pipeline/types.d.ts +7 -8
  115. package/dist/apis/pipeline/types.d.ts.map +1 -1
  116. package/dist/apis/quickReplies/types.d.ts +4 -1
  117. package/dist/apis/quickReplies/types.d.ts.map +1 -1
  118. package/dist/apis/schedule/types.d.ts +41 -10
  119. package/dist/apis/schedule/types.d.ts.map +1 -1
  120. package/dist/apis/terms/TermsApi.cjs +2 -2
  121. package/dist/apis/terms/TermsApi.cjs.map +1 -1
  122. package/dist/apis/terms/TermsApi.d.ts +2 -2
  123. package/dist/apis/terms/TermsApi.mjs +2 -2
  124. package/dist/apis/terms/TermsApi.mjs.map +1 -1
  125. package/dist/apis/tickets/TicketsApi.cjs +18 -8
  126. package/dist/apis/tickets/TicketsApi.cjs.map +1 -1
  127. package/dist/apis/tickets/TicketsApi.d.ts +15 -8
  128. package/dist/apis/tickets/TicketsApi.d.ts.map +1 -1
  129. package/dist/apis/tickets/TicketsApi.mjs +18 -8
  130. package/dist/apis/tickets/TicketsApi.mjs.map +1 -1
  131. package/dist/apis/tickets/types.d.ts +4 -1
  132. package/dist/apis/tickets/types.d.ts.map +1 -1
  133. package/dist/apis/timetable/TimetableApi.cjs +6 -0
  134. package/dist/apis/timetable/TimetableApi.cjs.map +1 -1
  135. package/dist/apis/timetable/TimetableApi.d.ts +6 -0
  136. package/dist/apis/timetable/TimetableApi.d.ts.map +1 -1
  137. package/dist/apis/timetable/TimetableApi.mjs +6 -0
  138. package/dist/apis/timetable/TimetableApi.mjs.map +1 -1
  139. package/dist/apis/timetable/types.d.ts +8 -0
  140. package/dist/apis/timetable/types.d.ts.map +1 -1
  141. package/dist/apis/users/UsersApi.cjs +11 -3
  142. package/dist/apis/users/UsersApi.cjs.map +1 -1
  143. package/dist/apis/users/UsersApi.d.ts +10 -4
  144. package/dist/apis/users/UsersApi.d.ts.map +1 -1
  145. package/dist/apis/users/UsersApi.mjs +11 -3
  146. package/dist/apis/users/UsersApi.mjs.map +1 -1
  147. package/dist/apis/users/types.d.ts +26 -0
  148. package/dist/apis/users/types.d.ts.map +1 -1
  149. package/dist/apis/webhooks/types.d.ts +51 -6
  150. package/dist/apis/webhooks/types.d.ts.map +1 -1
  151. package/dist/core/BaseReadApi.cjs +2 -2
  152. package/dist/core/BaseReadApi.cjs.map +1 -1
  153. package/dist/core/BaseReadApi.d.ts +2 -2
  154. package/dist/core/BaseReadApi.mjs +2 -2
  155. package/dist/core/BaseReadApi.mjs.map +1 -1
  156. package/dist/core/errors.cjs +4 -1
  157. package/dist/core/errors.cjs.map +1 -1
  158. package/dist/core/errors.d.ts +4 -1
  159. package/dist/core/errors.d.ts.map +1 -1
  160. package/dist/core/errors.mjs +4 -1
  161. package/dist/core/errors.mjs.map +1 -1
  162. package/package.json +1 -1
  163. package/src/apis/absence/AbsenceApi.ts +2 -2
  164. package/src/apis/absence/types.ts +1 -1
  165. package/src/apis/answers/AnswersApi.test.ts +32 -0
  166. package/src/apis/answers/AnswersApi.ts +38 -2
  167. package/src/apis/answers/types.ts +1 -2
  168. package/src/apis/authHistory/AuthHistoryApi.test.ts +16 -0
  169. package/src/apis/authHistory/AuthHistoryApi.ts +10 -2
  170. package/src/apis/authHistory/types.ts +6 -1
  171. package/src/apis/bots/BotsApi.test.ts +16 -0
  172. package/src/apis/bots/BotsApi.ts +25 -7
  173. package/src/apis/bots/types.ts +12 -2
  174. package/src/apis/campaigns/CampaignsApi.test.ts +48 -0
  175. package/src/apis/campaigns/CampaignsApi.ts +31 -2
  176. package/src/apis/campaigns/types.ts +101 -3
  177. package/src/apis/cards/types.ts +5 -5
  178. package/src/apis/categories/CategoriesApi.ts +1 -2
  179. package/src/apis/clientFeedback/ClientFeedbackApi.ts +2 -3
  180. package/src/apis/contacts/ContactsApi.test.ts +49 -2
  181. package/src/apis/contacts/ContactsApi.ts +64 -4
  182. package/src/apis/contacts/types.ts +111 -1
  183. package/src/apis/creditMovements/CreditMovementsApi.test.ts +1 -1
  184. package/src/apis/customFields/types.ts +4 -4
  185. package/src/apis/dashboard/DashboardApi.test-d.ts +9 -0
  186. package/src/apis/dashboard/DashboardApi.ts +3 -3
  187. package/src/apis/dashboard/types.ts +11 -4
  188. package/src/apis/departments/DepartmentsApi.ts +5 -5
  189. package/src/apis/departments/types.ts +13 -0
  190. package/src/apis/distribution/DistributionApi.ts +3 -4
  191. package/src/apis/files/FilesApi.test.ts +21 -0
  192. package/src/apis/files/FilesApi.ts +19 -1
  193. package/src/apis/files/types.ts +20 -1
  194. package/src/apis/kanbanCards/KanbanCardsApi.test.ts +21 -0
  195. package/src/apis/kanbanCards/KanbanCardsApi.ts +47 -1
  196. package/src/apis/kanbanCards/types.ts +1 -1
  197. package/src/apis/kanbanColumns/KanbanColumnsApi.test.ts +26 -0
  198. package/src/apis/kanbanColumns/KanbanColumnsApi.ts +31 -0
  199. package/src/apis/kanbanColumns/types.ts +5 -0
  200. package/src/apis/messages/MessagesApi.ts +13 -4
  201. package/src/apis/messages/types.ts +5 -0
  202. package/src/apis/payloads.test-d.ts +245 -5
  203. package/src/apis/pipeline/types.ts +7 -8
  204. package/src/apis/queryFormat.test.ts +13 -0
  205. package/src/apis/quickReplies/types.ts +4 -1
  206. package/src/apis/schedule/types.ts +42 -10
  207. package/src/apis/terms/TermsApi.ts +2 -2
  208. package/src/apis/tickets/TicketsApi.ts +28 -8
  209. package/src/apis/tickets/types.ts +4 -1
  210. package/src/apis/timetable/TimetableApi.ts +6 -0
  211. package/src/apis/timetable/types.ts +9 -0
  212. package/src/apis/users/UsersApi.test.ts +17 -0
  213. package/src/apis/users/UsersApi.ts +21 -3
  214. package/src/apis/users/types.ts +28 -0
  215. package/src/apis/webhooks/types.ts +53 -6
  216. package/src/core/BaseReadApi.ts +2 -2
  217. package/src/core/errors.ts +4 -1
@@ -34,6 +34,8 @@ export type CampaignConfig = {
34
34
  nameField?: string
35
35
  /** Column name in the import file used as the phone number. */
36
36
  numberField?: string
37
+ /** Column name in the import file used as the username (contacts known only by it). */
38
+ usernameField?: string
37
39
  delimiter?: ',' | ';' | ':' | string
38
40
  error?: {
39
41
  message: string
@@ -65,7 +67,10 @@ export type Campaign = {
65
67
  marketingIntegration?: boolean
66
68
  /** Always `null`: the campaign has no such column (answered by the transformer). */
67
69
  userId?: null
68
- /** Only in `getMany`: messages sent and viewed (ack 3). */
70
+ /**
71
+ * In the pages of `getMany` (not with `paginate: false`) and in `getOne`: messages sent and
72
+ * viewed (ack 3).
73
+ */
69
74
  stats?: { sent: number; viewed: number }
70
75
  accountId: string
71
76
  serviceId: string
@@ -116,9 +121,21 @@ export type CampaignRelationships =
116
121
  | 'messages'
117
122
  | 'importFile'
118
123
 
124
+ /** The media of a header parameter of the template (send it as `{ link: '<url>' }`). */
125
+ export type CampaignTemplateMedia = {
126
+ link?: string
127
+ }
128
+
129
+ /**
130
+ * A parameter of the template: `text`, or the media of its `type` (`image`, `video` or
131
+ * `document`).
132
+ */
119
133
  export type CampaignTemplateParameterItem = {
120
134
  type: string
121
135
  text?: string
136
+ image?: CampaignTemplateMedia
137
+ video?: CampaignTemplateMedia
138
+ document?: CampaignTemplateMedia
122
139
  }
123
140
 
124
141
  export type CampaignTemplateParameter = {
@@ -135,30 +152,111 @@ export type CampaignFileTemplate = {
135
152
  name?: string
136
153
  }
137
154
 
155
+ /** A message of the campaign to create (without `hsm`, the campaign sends these). */
156
+ export type CampaignMessageInput = {
157
+ text?: string
158
+ /** The attachment of the message, as a data URL (`data:<mimetype>;base64,<content>`). */
159
+ base64Url?: string
160
+ /** @deprecated Ignored by the backend: send the attachment as `base64Url`. */
161
+ fileId?: string
162
+ }
163
+
138
164
  export type CreateCampaignPayload = {
139
165
  title: string
140
166
  serviceId: string
141
167
  config?: { minInterval: number | string; maxInterval: number | string }
142
168
  tagIds?: string[]
143
- messages?: { text?: string; fileId?: string }[]
169
+ messages?: CampaignMessageInput[]
170
+ /** On create, replaces the `text` of every message (and is the text of the template message). */
171
+ text?: string
144
172
  isScheduled?: boolean
145
173
  sendsAt?: string | null
146
174
  mustOpenTicket?: boolean
147
175
  defaultDepartmentId?: string | null
148
176
  defaultUserId?: string | null
177
+ /** @deprecated Ignored by the backend: the contacts come from `tagIds` or the import `fileId`. */
149
178
  contacts?: Record<string, string>[]
179
+ /** @deprecated Ignored by the backend: the contacts come from `tagIds` or the import `fileId`. */
150
180
  useTags?: boolean
181
+ /**
182
+ * On create, the import file (CSV) of the contacts, uploaded with
183
+ * `attachedType: 'campaign.file'`. The columns are read by `nameField`, `numberField` and
184
+ * `usernameField`.
185
+ */
151
186
  fileId?: string
187
+ /** On create, stored in `config.nameField` (a `config.nameField` sent here is replaced). */
152
188
  nameField?: string
189
+ /** On create, stored in `config.numberField` (a `config.numberField` sent here is replaced). */
153
190
  numberField?: string
191
+ /** Stored in `config.usernameField` (a `config.usernameField` sent here is replaced). */
192
+ usernameField?: string
154
193
  // whatsapp-business specific
194
+ /**
195
+ * The template to send. On create, the campaign gets one message of it and `messages` is not
196
+ * read.
197
+ */
155
198
  hsm?: WhatsappBusinessTemplate
156
199
  parameters?: CampaignTemplateParameter[]
200
+ /**
201
+ * The header file of the template message (only with `hsm`), already uploaded. Takes precedence
202
+ * over `fileTemplate`.
203
+ */
204
+ hsmFileId?: string
205
+ /**
206
+ * The header media of the template message: `base64Url` as a data URL, with its `mimetype`
207
+ * (required on create) and `name`. On create, read only when `tagIds` is sent (an empty array is
208
+ * enough); with the import `fileId`, prefer `hsmFileId`. On update, it only replaces the header
209
+ * file the template message already has: send it only with a new data URL, and leave it out to
210
+ * keep the header.
211
+ */
157
212
  fileTemplate?: CampaignFileTemplate
158
213
  marketingIntegration?: boolean
159
214
  }
160
215
 
161
- export type UpdateCampaignPayload = Partial<CreateCampaignPayload>
216
+ /**
217
+ * A message of `updateById`. With the `id` of an existing message, the message gets the `text`
218
+ * sent; without `id`, it is created (and `base64Url` is read as in the create).
219
+ */
220
+ export type UpdateCampaignMessageInput = CampaignMessageInput & {
221
+ id?: string
222
+ /**
223
+ * On WhatsApp connections, keeps the attachment of an existing message (send the `file` of the
224
+ * message read); with `file`, `base64Url` is not read. Without `file` and `base64Url`, the
225
+ * attachment is removed.
226
+ */
227
+ file?: unknown
228
+ /**
229
+ * On WhatsApp connections, the name of the new attachment in `base64Url`: to replace the
230
+ * attachment of an existing message, send `base64Url` with a new name and without `file`.
231
+ */
232
+ fileName?: string
233
+ }
234
+
235
+ /**
236
+ * `updateById` (`PUT`). A non-empty `messages` replaces the messages: the ones with the `id` of an
237
+ * existing message update it, the ones without `id` are created and the existing ones left out are
238
+ * deleted (omitted, or `[]`, keeps them).
239
+ *
240
+ * Send `isScheduled` (with `sendsAt`) and `mustOpenTicket` (with `defaultDepartmentId` and
241
+ * `defaultUserId`) on every call: without `isScheduled: true`, `sendsAt` is stored as `null` and a
242
+ * scheduled campaign is no longer started at the date; without `mustOpenTicket: true`, the default
243
+ * department and user are cleared.
244
+ *
245
+ * On WhatsApp Business connections, send the existing message by its `id` (a message without `id`
246
+ * is created without the template), together with `hsm` and `parameters`: every update that sends
247
+ * `messages` or `hsm` rewrites both on the existing messages (a missing one is stored empty).
248
+ */
249
+ export type UpdateCampaignPayload = Omit<
250
+ Partial<CreateCampaignPayload>,
251
+ 'text' | 'usernameField' | 'hsmFileId' | 'messages' | 'config'
252
+ > & {
253
+ /**
254
+ * Replaces the stored config as a whole. `minInterval` and `maxInterval` go as strings (a number
255
+ * answers 400).
256
+ */
257
+ config?: { minInterval: number | string; maxInterval: number | string }
258
+ messages?: UpdateCampaignMessageInput[]
259
+ }
162
260
 
163
261
  /** The visible states the `status` filter of `getMany` takes (`'scheduled'` is derived from `isScheduled` + `sendsAt`). */
164
262
  export type CampaignListStatus = CampaignStatus | 'scheduled'
@@ -182,8 +182,8 @@ export type BulkMoveCardsPayload = {
182
182
  export type BulkMoveCardsResult = { movedCount: number; ignoredCount: number }
183
183
 
184
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.
185
+ * Creates a product of a card, or updates the product of `id`. `name`, `ammount` and `value` are
186
+ * required (a missing one answers 500).
187
187
  */
188
188
  export type SaveCardProductPayload = {
189
189
  id?: string
@@ -218,9 +218,9 @@ export type CardOwner = {
218
218
  }
219
219
 
220
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`.
221
+ * Body of `POST /cards/getCards`: a list query of the cards. Send `where.accountId` with the id of
222
+ * the current account (`accountId` of `MeApi.getMe`). `order` accepts card fields and
223
+ * `contact.name`.
224
224
  */
225
225
  export type GetCardsPayload = Omit<
226
226
  ListQuery<Card>,
@@ -15,8 +15,7 @@ export class CategoriesApi extends BaseCrudApi<
15
15
  }
16
16
 
17
17
  /**
18
- * Deletes a category. Resolves the text `'OK'`; an unknown id answers 500. The route checks no
19
- * permission.
18
+ * Deletes a category. Resolves the text `'OK'`; an unknown id answers 500.
20
19
  */
21
20
  override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
22
21
  return super.deleteById(id, headers)
@@ -9,9 +9,8 @@ export class ClientFeedbackApi
9
9
  private readonly basePath = '/client-feedback'
10
10
 
11
11
  /**
12
- * @deprecated The route is broken in the backend: it is mounted with no authentication (the
13
- * account context throws, 500) and calls a `createFeedback` the resource does not have. The
14
- * feedback on the AI summary is stored when the ticket is closed.
12
+ * @deprecated The route is not available (it answers 500). Send the rating of the AI summary
13
+ * when closing the ticket: `ContactsApi.closeTicket` with `aiSummaryRating`.
15
14
  */
16
15
  create(
17
16
  body: CreateClientFeedbackPayload,
@@ -138,6 +138,19 @@ describe('ContactsApi', () => {
138
138
  await api.getById('cnt-1', undefined, headers)
139
139
  expect(client.get).toHaveBeenCalledWith('/contacts/cnt-1', headers)
140
140
  })
141
+
142
+ it('sends formatCustomFields and the include of the custom fields in the query', async () => {
143
+ vi.mocked(client.get).mockResolvedValue(contact)
144
+ const query = {
145
+ include: [{ model: 'customFieldValues' as const, include: ['customField' as const] }],
146
+ formatCustomFields: true,
147
+ }
148
+ await api.getById('cnt-1', query)
149
+ expect(client.get).toHaveBeenCalledWith(
150
+ `/contacts/cnt-1?query=${encodeURIComponent(JSON.stringify(query))}`,
151
+ undefined,
152
+ )
153
+ })
141
154
  })
142
155
 
143
156
  // ── create ───────────────────────────────────────────────────────
@@ -352,14 +365,26 @@ describe('ContactsApi', () => {
352
365
 
353
366
  describe('transferTicket', () => {
354
367
  it('calls client.post with /contacts/:id/ticket/transfer and payload', async () => {
355
- vi.mocked(client.post).mockResolvedValue({ ok: true })
368
+ vi.mocked(client.post).mockResolvedValue(true)
356
369
  const result = await api.transferTicket('c1', { departmentId: 'd1' })
357
370
  expect(client.post).toHaveBeenCalledWith(
358
371
  '/contacts/c1/ticket/transfer',
359
372
  { departmentId: 'd1' },
360
373
  undefined,
361
374
  )
362
- expect(result).toEqual({ ok: true })
375
+ expect(result).toBe(true)
376
+ })
377
+
378
+ it('sends includeKanbanCardId and resolves the id of the card', async () => {
379
+ vi.mocked(client.post).mockResolvedValue({ kanbanCardId: 'kc1' })
380
+ const payload = {
381
+ departmentId: 'd1',
382
+ kanbanCardColumnId: 'col1',
383
+ includeKanbanCardId: true,
384
+ } as const
385
+ const result = await api.transferTicket('c1', payload)
386
+ expect(client.post).toHaveBeenCalledWith('/contacts/c1/ticket/transfer', payload, undefined)
387
+ expect(result.kanbanCardId).toBe('kc1')
363
388
  })
364
389
 
365
390
  it('includes optional userId and forwards custom headers', async () => {
@@ -374,6 +399,28 @@ describe('ContactsApi', () => {
374
399
  })
375
400
  })
376
401
 
402
+ // ── closeTicket ──────────────────────────────────────────────────
403
+
404
+ describe('closeTicket', () => {
405
+ it('calls client.post with /contacts/:id/ticket/close and an empty body by default', async () => {
406
+ vi.mocked(client.post).mockResolvedValue(true)
407
+ expect(await api.closeTicket('c1')).toBe(true)
408
+ expect(client.post).toHaveBeenCalledWith('/contacts/c1/ticket/close', {}, undefined)
409
+ })
410
+
411
+ it('sends the subjects, the comment and the rating of the AI summary', async () => {
412
+ vi.mocked(client.post).mockResolvedValue(true)
413
+ const headers = { 'X-Custom': 'h' }
414
+ const payload = {
415
+ ticketTopicIds: ['tt1'],
416
+ comments: 'Resolvido',
417
+ aiSummaryRating: '5' as const,
418
+ }
419
+ await api.closeTicket('c1', payload, headers)
420
+ expect(client.post).toHaveBeenCalledWith('/contacts/c1/ticket/close', payload, headers)
421
+ })
422
+ })
423
+
377
424
  // ── createMany ───────────────────────────────────────────────────
378
425
 
379
426
  describe('createMany', () => {
@@ -6,7 +6,10 @@ import type {
6
6
  BlockContactPayload,
7
7
  BulkTransferTicketsPayload,
8
8
  BulkTransferTicketsResult,
9
+ CloseTicketPayload,
9
10
  Contact,
11
+ ContactGetByIdQuery,
12
+ ContactListQuery,
10
13
  ContactMediaPage,
11
14
  ContactMediaQuery,
12
15
  ContactsSelection,
@@ -19,16 +22,37 @@ import type {
19
22
  ExportTemplatePayload,
20
23
  ImportContactsPayload,
21
24
  SyncContactPayload,
25
+ TransferTicketKanbanCardResult,
22
26
  TransferTicketPayload,
23
27
  UpdateContactPayload,
24
28
  SummarizeTicketResult,
25
29
  } from './types'
26
30
 
27
- export class ContactsApi extends BaseCrudApi<Contact, CreateContactPayload, UpdateContactPayload> {
31
+ export class ContactsApi extends BaseCrudApi<
32
+ Contact,
33
+ CreateContactPayload,
34
+ UpdateContactPayload,
35
+ ContactListQuery
36
+ > {
28
37
  constructor(client: ApiClient) {
29
38
  super(client, '/contacts')
30
39
  }
31
40
 
41
+ /**
42
+ * Returns a contact by ID. When it does not exist the route answers 200 with an empty body, which
43
+ * resolves to `undefined`. With `formatCustomFields: true` (and the include
44
+ * `{ model: 'customFieldValues', include: ['customField'] }`), the custom fields come keyed by name
45
+ * in `customFields`.
46
+ * @permissions contacts.view
47
+ */
48
+ override getById(
49
+ id: string,
50
+ query?: ContactGetByIdQuery,
51
+ headers?: Record<string, string>,
52
+ ): Promise<Contact> {
53
+ return super.getById(id, query, headers)
54
+ }
55
+
32
56
  /**
33
57
  * Downloads the CSV template (`;`-separated header line, in the language of the user) for
34
58
  * importing contacts, with columns based on the service type. Answered with no Content-Type.
@@ -119,20 +143,56 @@ export class ContactsApi extends BaseCrudApi<Contact, CreateContactPayload, Upda
119
143
  /**
120
144
  * Opens (or transfers) the contact's ticket to a department, and optionally a user.
121
145
  * This is the real ticket-open flow used by the UI — there is no `POST /tickets`.
146
+ * Resolves `true`, or `{ kanbanCardId }` with `includeKanbanCardId: true`. When the current
147
+ * ticket of the contact is already closed, the call only releases it and opens nothing: call it
148
+ * again to open a ticket.
122
149
  * @permissions tickets.transfer.all OR tickets.transfer.me
123
150
  */
151
+ transferTicket(
152
+ contactId: string,
153
+ payload: TransferTicketPayload & { includeKanbanCardId: true },
154
+ headers?: Record<string, string>,
155
+ ): Promise<TransferTicketKanbanCardResult>
156
+ transferTicket(
157
+ contactId: string,
158
+ payload: TransferTicketPayload & { includeKanbanCardId?: false },
159
+ headers?: Record<string, string>,
160
+ ): Promise<true>
124
161
  transferTicket(
125
162
  contactId: string,
126
163
  payload: TransferTicketPayload,
127
164
  headers?: Record<string, string>,
128
- ): Promise<unknown> {
129
- return this.client.post<unknown, TransferTicketPayload>(
165
+ ): Promise<true | TransferTicketKanbanCardResult>
166
+ transferTicket(
167
+ contactId: string,
168
+ payload: TransferTicketPayload,
169
+ headers?: Record<string, string>,
170
+ ): Promise<true | TransferTicketKanbanCardResult> {
171
+ return this.client.post<true | TransferTicketKanbanCardResult, TransferTicketPayload>(
130
172
  `${this.basePath}/${contactId}/ticket/transfer`,
131
173
  payload,
132
174
  headers,
133
175
  )
134
176
  }
135
177
 
178
+ /**
179
+ * Closes the open ticket of the contact and resolves `true`. A contact without an open ticket
180
+ * answers 500, or `true` (closing nothing) when its current ticket is already closed. An unknown
181
+ * subject in `ticketTopicIds`, or one of another department, answers 400.
182
+ * @permissions tickets.close.me OR tickets.close.all
183
+ */
184
+ closeTicket(
185
+ contactId: string,
186
+ payload: CloseTicketPayload = {},
187
+ headers?: Record<string, string>,
188
+ ): Promise<true> {
189
+ return this.client.post<true, CloseTicketPayload>(
190
+ `${this.basePath}/${contactId}/ticket/close`,
191
+ payload,
192
+ headers,
193
+ )
194
+ }
195
+
136
196
  /**
137
197
  * Creates or updates several contacts in one call. Existing contacts (same service and
138
198
  * number or e-mail) are updated; the others are created. Returns the resulting contacts.
@@ -204,7 +264,7 @@ export class ContactsApi extends BaseCrudApi<Contact, CreateContactPayload, Upda
204
264
  /**
205
265
  * Blocks or unblocks a contact. Blocking closes the contact's open ticket. Requires the
206
266
  * block list to be active on the account (`settings.isBlockListActive`; 400 otherwise).
207
- * `byUserId` and `description` are stored as sent: the backend does not fill in the user.
267
+ * Send `byUserId` with the logged user (`id` of `MeApi.getMe`), as the web app does.
208
268
  * @permissions contacts.block
209
269
  */
210
270
  block(id: string, body: BlockContactPayload, headers?: Record<string, string>): Promise<Contact> {
@@ -1,10 +1,11 @@
1
+ import type { CustomField, CustomFieldType } from '../customFields/types'
1
2
  import type { Department } from '../departments/types'
2
3
  import type { Person } from '../people/types'
3
4
  import type { Service } from '../services/types'
4
5
  import type { Tag } from '../tags/types'
5
6
  import type { Ticket } from '../tickets/types'
6
7
  import type { User } from '../users/types'
7
- import type { IncludeItem, ListQuery, WhereClause } from '../../core/types'
8
+ import type { GetByIdQuery, IncludeItem, ListQuery, WhereClause } from '../../core/types'
8
9
 
9
10
  export type ContactStatus = 'online' | 'offline' | 'absent'
10
11
  export type ContactOrigin = 'api' | 'app' | 'campaign' | 'driver' | 'import' | 'web'
@@ -95,6 +96,56 @@ export type ContactDataBlock = {
95
96
  description: string
96
97
  }
97
98
 
99
+ /**
100
+ * A value of a custom field of the contact, as `include: ['customFieldValues']` answers it (the
101
+ * stored row).
102
+ */
103
+ export type CustomFieldValue = {
104
+ id: string
105
+ customFieldId: string
106
+ /** The contact. */
107
+ relatedId: string
108
+ relatedType: string | null
109
+ /**
110
+ * The value as stored: text, or JSON text for the composite types (`date-range`, `hour-range`,
111
+ * `numeric`, `monetary`, `phone`, `postal-code`).
112
+ */
113
+ value: string
114
+ createdAt: string
115
+ updatedAt: string
116
+ deletedAt: string | null
117
+ // relationships
118
+ customField?: CustomField
119
+ }
120
+
121
+ /**
122
+ * A custom field of the contact as `formatCustomFields: true` answers it, keyed by the name of the
123
+ * field in `Contact.customFields`.
124
+ */
125
+ export type FormattedCustomField = {
126
+ name: string
127
+ /** The value, parsed from JSON for the composite types. */
128
+ value: unknown
129
+ /** Id of the value. */
130
+ id: string
131
+ type: CustomFieldType
132
+ /** Id of the field. */
133
+ fieldId: string
134
+ /** When the field was created. */
135
+ createdAt: string
136
+ }
137
+
138
+ /** A custom field value sent in the create or update of a contact. */
139
+ export type ContactCustomFieldInput = {
140
+ /**
141
+ * Id of the custom field (`CustomField.id`, `FormattedCustomField.fieldId` or
142
+ * `CustomFieldValue.customFieldId`), not the id of the value.
143
+ */
144
+ id: string
145
+ /** Text, or an object for the composite types (stored as JSON). */
146
+ value: unknown
147
+ }
148
+
98
149
  export type Contact = {
99
150
  id: string
100
151
  idFromService: string | null
@@ -139,6 +190,13 @@ export type Contact = {
139
190
  person?: Person | null
140
191
  tags?: Tag[]
141
192
  currentTicket?: Ticket | null
193
+ /** With `include: ['customFieldValues']`, unless `formatCustomFields` is set. */
194
+ customFieldValues?: CustomFieldValue[]
195
+ /**
196
+ * With `formatCustomFields: true` and the include
197
+ * `{ model: 'customFieldValues', include: ['customField'] }`: the values keyed by the field name.
198
+ */
199
+ customFields?: Record<string, FormattedCustomField>
142
200
  }
143
201
 
144
202
  export type ContactRelationships =
@@ -148,6 +206,23 @@ export type ContactRelationships =
148
206
  | 'person'
149
207
  | 'tags'
150
208
  | 'currentTicket'
209
+ | 'customFieldValues'
210
+ | 'customFields'
211
+
212
+ type FormatCustomFieldsOption = {
213
+ /**
214
+ * Answers `customFields` keyed by the field name instead of the raw `customFieldValues` (with the
215
+ * include `{ model: 'customFieldValues', include: ['customField'] }`; without the nested
216
+ * `customField`, `customFields` comes empty).
217
+ */
218
+ formatCustomFields?: boolean
219
+ }
220
+
221
+ /** Query of `ContactsApi.getById`. */
222
+ export type ContactGetByIdQuery = GetByIdQuery<Contact> & FormatCustomFieldsOption
223
+
224
+ /** Query of `ContactsApi.getMany` and `getOne`. */
225
+ export type ContactListQuery = ListQuery<Contact> & FormatCustomFieldsOption
151
226
 
152
227
  export type CreateContactPayload = {
153
228
  name?: string
@@ -160,6 +235,8 @@ export type CreateContactPayload = {
160
235
  data?: ContactData
161
236
  note?: string
162
237
  origin?: ContactOrigin
238
+ /** Values of the custom fields of the account. An id that is not a custom field answers 400. */
239
+ customFields?: ContactCustomFieldInput[]
163
240
  }
164
241
 
165
242
  export type UpdateContactPayload = {
@@ -174,6 +251,11 @@ export type UpdateContactPayload = {
174
251
  block?: boolean
175
252
  /** Tag IDs to associate with the contact. */
176
253
  tagIds?: string[]
254
+ /**
255
+ * Values of the custom fields of the account: a value of the same field is updated. An id that is
256
+ * not a custom field is skipped.
257
+ */
258
+ customFields?: ContactCustomFieldInput[]
177
259
  }
178
260
 
179
261
  /** Result of `ContactsApi.summarizeTicket`. */
@@ -209,6 +291,34 @@ export type TransferTicketPayload = {
209
291
  kanbanCardColumnId?: string
210
292
  kanbanCardSubject?: string
211
293
  initialMessage?: string
294
+ /** `true` (the boolean) resolves the id of the kanban card instead of `true`. */
295
+ includeKanbanCardId?: boolean
296
+ }
297
+
298
+ /** What `ContactsApi.transferTicket` resolves to with `includeKanbanCardId: true`. */
299
+ export type TransferTicketKanbanCardResult = {
300
+ /**
301
+ * The kanban card of the ticket (created, or the contact's card moved, in `kanbanCardColumnId`);
302
+ * `null` without `kanbanCardColumnId` (even when the ticket gets a card in the initial column of
303
+ * the board: send `kanbanCardColumnId` to read its id), when the department has no board, or when
304
+ * the call only released a closed ticket.
305
+ */
306
+ kanbanCardId: string | null
307
+ }
308
+
309
+ /** The rating of the AI summary of the ticket. */
310
+ export type AiSummaryRating = '1' | '2' | '3' | '4' | '5'
311
+
312
+ export type CloseTicketPayload = {
313
+ /**
314
+ * Subjects of the ticket: of its department, or linked to no department. An unknown subject, or
315
+ * one of another department, answers 400.
316
+ */
317
+ ticketTopicIds?: string[]
318
+ /** The closing comment. */
319
+ comments?: string
320
+ /** Stored as the feedback of the AI summary of the ticket. */
321
+ aiSummaryRating?: AiSummaryRating
212
322
  }
213
323
 
214
324
  /**
@@ -83,6 +83,6 @@ describe('CreditMovementsApi', () => {
83
83
  const withoutRaw: ApiClient = { ...client, requestRaw: undefined }
84
84
  await expect(
85
85
  new CreditMovementsApi(withoutRaw).exportXlsx({ from: 'a', to: 'b', where: { $and: [] } }),
86
- ).rejects.toThrow('requestRaw')
86
+ ).rejects.toThrow('exportXlsx needs a client with requestRaw')
87
87
  })
88
88
  })
@@ -22,10 +22,10 @@ export type CustomFieldOption = {
22
22
  }
23
23
 
24
24
  /**
25
- * Settings of a custom field — a free JSONB the backend does not validate, echoed back as sent.
26
- * The fields below are the ones the web app form sends (`front/src/pages/custom-fields/components/
27
- * form.tsx`): `options` for the `list` and `checkbox` types, `min`/`max` for `numeric` and `monetary`,
28
- * `currency` for `monetary`, `maxLength` for `text` and `long-text`, and `placeholder` for the inputs.
25
+ * Settings of a custom field (a free JSON object, answered as stored). The fields below are the
26
+ * ones the web app form sends: `options` for the `list` and `checkbox` types, `min`/`max` for
27
+ * `numeric` and `monetary`, `currency` for `monetary`, `maxLength` for `text` and `long-text`, and
28
+ * `placeholder` for the inputs.
29
29
  */
30
30
  export type CustomFieldSettings = {
31
31
  options?: CustomFieldOption[]
@@ -47,3 +47,12 @@ expectType<Promise<DashboardPeriodItem[] | DashboardGrouped<DashboardPeriodItem>
47
47
  expectError(dashboard.getByPeriod({ grouping: 'quarterly' }))
48
48
  expectError(dashboard.getByTopic({ withTotals: true }))
49
49
  expectError(dashboard.getByService({ withTotals: true }))
50
+
51
+ // ── one connection, tag and subject (not a list) ────────────────────
52
+
53
+ expectType<Promise<DashboardPeriodItem[]>>(
54
+ dashboard.getByPeriod({ serviceId: 's-1', tagId: 't-1', ticketTopicId: 'tt-1' }),
55
+ )
56
+ expectError(dashboard.getByPeriod({ serviceId: ['s-1', 's-2'] }))
57
+ expectError(dashboard.getByPeriod({ tagId: ['t-1'] }))
58
+ expectError(dashboard.getByPeriod({ ticketTopicId: ['tt-1'] }))
@@ -12,9 +12,9 @@ import type {
12
12
  } from './types'
13
13
 
14
14
  /**
15
- * Service statistics ("Estatísticas de atendimento"). The `/dashboard` routes only require an
16
- * authenticated user (no permission check). Every route takes the same {@link DashboardQuery}
17
- * filters; the grouped routes answer one item per day, user, department, topic or connection.
15
+ * Service statistics ("Estatísticas de atendimento"). Every route takes the same
16
+ * {@link DashboardQuery} filters; the grouped routes answer one item per day, user, department,
17
+ * topic or connection.
18
18
  */
19
19
  export class DashboardApi extends BaseApi {
20
20
  public readonly basePath = '/dashboard'
@@ -14,7 +14,11 @@ export type DashboardQuery = {
14
14
  startPeriod?: string
15
15
  /** ISO date-time. Default: the end of the `startPeriod` day (or of today). */
16
16
  endPeriod?: string
17
- /** Which ticket date the period filters: opening (default) or closing. */
17
+ /**
18
+ * Which ticket date the period filters: opening (`openDate`, the default) or closing. Sent
19
+ * without it, together with `status: 'all'`, the period is not applied. `getByPeriod` needs it:
20
+ * always send it there (the `openDate` default does not apply to that route).
21
+ */
18
22
  periodType?: 'openDate' | 'closeDate'
19
23
  /** Ticket status (`closed`, not `close`). */
20
24
  status?: 'all' | 'open' | 'closed'
@@ -39,9 +43,12 @@ export type DashboardQuery = {
39
43
  */
40
44
  userParticipation?: 'last' | 'middle'
41
45
  userStatus?: 'active' | 'archived' | 'all'
42
- serviceId?: string | string[]
43
- tagId?: string | string[]
44
- ticketTopicId?: string | string[]
46
+ /** One connection id (unlike `departmentId` and `userId`, not a list). */
47
+ serviceId?: string
48
+ /** One tag id (not a list). */
49
+ tagId?: string
50
+ /** One ticket subject id (not a list). */
51
+ ticketTopicId?: string
45
52
  /**
46
53
  * Granularity of the `getByPeriod` items (the other routes ignore it). Default: `daily`. See
47
54
  * {@link DashboardGrouping} for the `name` of the items.
@@ -21,11 +21,11 @@ export class DepartmentsApi extends ArchivableCrudApi<
21
21
 
22
22
  /**
23
23
  * Archives (`archive: true`) or restores (`archive: false`) a department through the route used
24
- * by the UI, which enforces what {@link archive}/{@link unarchive} (a plain `PUT { archivedAt }`)
25
- * skip: archiving refuses (403 `Cannot archive department.`, with the reasons and the open
26
- * tickets count) a department in use, and clears it as the default department of contacts and
27
- * connections, marks its schedules as done and unlinks its users and quick replies. Resolves the
28
- * stored department (not transformed); an unknown id answers 500.
24
+ * by the UI: archiving refuses (403 `Cannot archive department.`) a department in use, or when it
25
+ * is the only active department of the account (the `extra` of the error is an
26
+ * `ArchiveDepartmentErrorExtra`). Archiving also clears it as the default department of contacts
27
+ * and connections, marks its schedules as done and unlinks its users and quick replies. Resolves
28
+ * the stored department (not transformed); an unknown id answers 500.
29
29
  * @permissions departments.view OR departments.archive
30
30
  */
31
31
  archiveWithValidation(