boondmanager-mcp-server 2.15.2 → 2.17.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 (272) hide show
  1. package/LICENSE +1 -1
  2. package/NOTICE +16 -2
  3. package/README.md +60 -15
  4. package/dist/config/access-policy.d.ts.map +1 -1
  5. package/dist/config/access-policy.js +6 -18
  6. package/dist/config/access-policy.js.map +1 -1
  7. package/dist/config/dictionary-overrides.d.ts.map +1 -1
  8. package/dist/config/dictionary-overrides.js +2 -12
  9. package/dist/config/dictionary-overrides.js.map +1 -1
  10. package/dist/config/env.d.ts +63 -0
  11. package/dist/config/env.d.ts.map +1 -0
  12. package/dist/config/env.js +116 -0
  13. package/dist/config/env.js.map +1 -0
  14. package/dist/config/profiles.d.ts +22 -8
  15. package/dist/config/profiles.d.ts.map +1 -1
  16. package/dist/config/profiles.js +48 -4
  17. package/dist/config/profiles.js.map +1 -1
  18. package/dist/constants.d.ts +13 -2
  19. package/dist/constants.d.ts.map +1 -1
  20. package/dist/constants.js +25 -1
  21. package/dist/constants.js.map +1 -1
  22. package/dist/icons.d.ts.map +1 -1
  23. package/dist/icons.js +6 -1
  24. package/dist/icons.js.map +1 -1
  25. package/dist/index.js +33 -15
  26. package/dist/index.js.map +1 -1
  27. package/dist/instructions.d.ts +1 -1
  28. package/dist/instructions.d.ts.map +1 -1
  29. package/dist/instructions.js +6 -6
  30. package/dist/instructions.js.map +1 -1
  31. package/dist/prompts/index.d.ts +19 -1
  32. package/dist/prompts/index.d.ts.map +1 -1
  33. package/dist/prompts/index.js +495 -25
  34. package/dist/prompts/index.js.map +1 -1
  35. package/dist/prompts/periods.d.ts +45 -0
  36. package/dist/prompts/periods.d.ts.map +1 -0
  37. package/dist/prompts/periods.js +154 -0
  38. package/dist/prompts/periods.js.map +1 -0
  39. package/dist/resources/index.d.ts +14 -0
  40. package/dist/resources/index.d.ts.map +1 -1
  41. package/dist/resources/index.js +285 -0
  42. package/dist/resources/index.js.map +1 -1
  43. package/dist/resources/templates.d.ts +1 -1
  44. package/dist/resources/templates.d.ts.map +1 -1
  45. package/dist/resources/templates.js +2 -0
  46. package/dist/resources/templates.js.map +1 -1
  47. package/dist/schema-dialect.js.map +1 -1
  48. package/dist/schemas/filter-aliases.d.ts +1 -1
  49. package/dist/schemas/filter-aliases.d.ts.map +1 -1
  50. package/dist/schemas/filter-aliases.js +101 -2
  51. package/dist/schemas/filter-aliases.js.map +1 -1
  52. package/dist/schemas/index.d.ts +987 -161
  53. package/dist/schemas/index.d.ts.map +1 -1
  54. package/dist/schemas/index.js +853 -323
  55. package/dist/schemas/index.js.map +1 -1
  56. package/dist/server.d.ts.map +1 -1
  57. package/dist/server.js +10 -3
  58. package/dist/server.js.map +1 -1
  59. package/dist/services/boond-client.d.ts +29 -213
  60. package/dist/services/boond-client.d.ts.map +1 -1
  61. package/dist/services/boond-client.js +29 -1168
  62. package/dist/services/boond-client.js.map +1 -1
  63. package/dist/services/dictionary.d.ts +27 -3
  64. package/dist/services/dictionary.d.ts.map +1 -1
  65. package/dist/services/dictionary.js +60 -23
  66. package/dist/services/dictionary.js.map +1 -1
  67. package/dist/services/document-text.d.ts +32 -0
  68. package/dist/services/document-text.d.ts.map +1 -0
  69. package/dist/services/document-text.js +120 -0
  70. package/dist/services/document-text.js.map +1 -0
  71. package/dist/services/format/detail.d.ts +16 -0
  72. package/dist/services/format/detail.d.ts.map +1 -0
  73. package/dist/services/format/detail.js +36 -0
  74. package/dist/services/format/detail.js.map +1 -0
  75. package/dist/services/format/html.d.ts +19 -0
  76. package/dist/services/format/html.d.ts.map +1 -0
  77. package/dist/services/format/html.js +87 -0
  78. package/dist/services/format/html.js.map +1 -0
  79. package/dist/services/format/list.d.ts +3 -0
  80. package/dist/services/format/list.d.ts.map +1 -0
  81. package/dist/services/format/list.js +44 -0
  82. package/dist/services/format/list.js.map +1 -0
  83. package/dist/services/format/summary.d.ts +16 -0
  84. package/dist/services/format/summary.d.ts.map +1 -0
  85. package/dist/services/format/summary.js +172 -0
  86. package/dist/services/format/summary.js.map +1 -0
  87. package/dist/services/format/tab.d.ts +9 -0
  88. package/dist/services/format/tab.d.ts.map +1 -0
  89. package/dist/services/format/tab.js +32 -0
  90. package/dist/services/format/tab.js.map +1 -0
  91. package/dist/services/http/auth.d.ts +44 -0
  92. package/dist/services/http/auth.d.ts.map +1 -0
  93. package/dist/services/http/auth.js +146 -0
  94. package/dist/services/http/auth.js.map +1 -0
  95. package/dist/services/http/download.d.ts +60 -0
  96. package/dist/services/http/download.d.ts.map +1 -0
  97. package/dist/services/http/download.js +160 -0
  98. package/dist/services/http/download.js.map +1 -0
  99. package/dist/services/http/errors.d.ts +45 -0
  100. package/dist/services/http/errors.d.ts.map +1 -0
  101. package/dist/services/http/errors.js +188 -0
  102. package/dist/services/http/errors.js.map +1 -0
  103. package/dist/services/http/rate-limit.d.ts +40 -0
  104. package/dist/services/http/rate-limit.d.ts.map +1 -0
  105. package/dist/services/http/rate-limit.js +92 -0
  106. package/dist/services/http/rate-limit.js.map +1 -0
  107. package/dist/services/http/retry.d.ts +46 -0
  108. package/dist/services/http/retry.d.ts.map +1 -0
  109. package/dist/services/http/retry.js +101 -0
  110. package/dist/services/http/retry.js.map +1 -0
  111. package/dist/services/http/transport.d.ts +73 -0
  112. package/dist/services/http/transport.d.ts.map +1 -0
  113. package/dist/services/http/transport.js +272 -0
  114. package/dist/services/http/transport.js.map +1 -0
  115. package/dist/services/logger.d.ts +53 -2
  116. package/dist/services/logger.d.ts.map +1 -1
  117. package/dist/services/logger.js +82 -21
  118. package/dist/services/logger.js.map +1 -1
  119. package/dist/services/oauth.d.ts +12 -0
  120. package/dist/services/oauth.d.ts.map +1 -1
  121. package/dist/services/oauth.js +21 -8
  122. package/dist/services/oauth.js.map +1 -1
  123. package/dist/services/rate-limiter.d.ts +9 -2
  124. package/dist/services/rate-limiter.d.ts.map +1 -1
  125. package/dist/services/rate-limiter.js +18 -6
  126. package/dist/services/rate-limiter.js.map +1 -1
  127. package/dist/services/request-context.d.ts +47 -0
  128. package/dist/services/request-context.d.ts.map +1 -0
  129. package/dist/services/request-context.js +112 -0
  130. package/dist/services/request-context.js.map +1 -0
  131. package/dist/services/search.d.ts +26 -0
  132. package/dist/services/search.d.ts.map +1 -0
  133. package/dist/services/search.js +98 -0
  134. package/dist/services/search.js.map +1 -0
  135. package/dist/services/update-checker.d.ts.map +1 -1
  136. package/dist/services/update-checker.js +15 -8
  137. package/dist/services/update-checker.js.map +1 -1
  138. package/dist/tools/absences.d.ts +9 -0
  139. package/dist/tools/absences.d.ts.map +1 -1
  140. package/dist/tools/absences.js +65 -20
  141. package/dist/tools/absences.js.map +1 -1
  142. package/dist/tools/actions.d.ts.map +1 -1
  143. package/dist/tools/actions.js +11 -27
  144. package/dist/tools/actions.js.map +1 -1
  145. package/dist/tools/advantages.d.ts +2 -0
  146. package/dist/tools/advantages.d.ts.map +1 -1
  147. package/dist/tools/advantages.js +66 -13
  148. package/dist/tools/advantages.js.map +1 -1
  149. package/dist/tools/alerts.d.ts +18 -0
  150. package/dist/tools/alerts.d.ts.map +1 -0
  151. package/dist/tools/alerts.js +82 -0
  152. package/dist/tools/alerts.js.map +1 -0
  153. package/dist/tools/application.d.ts +2 -1
  154. package/dist/tools/application.d.ts.map +1 -1
  155. package/dist/tools/application.js +9 -3
  156. package/dist/tools/application.js.map +1 -1
  157. package/dist/tools/contacts.d.ts.map +1 -1
  158. package/dist/tools/contacts.js +3 -7
  159. package/dist/tools/contacts.js.map +1 -1
  160. package/dist/tools/contracts.d.ts +22 -0
  161. package/dist/tools/contracts.d.ts.map +1 -1
  162. package/dist/tools/contracts.js +185 -52
  163. package/dist/tools/contracts.js.map +1 -1
  164. package/dist/tools/crud-factory.d.ts +33 -2
  165. package/dist/tools/crud-factory.d.ts.map +1 -1
  166. package/dist/tools/crud-factory.js +42 -15
  167. package/dist/tools/crud-factory.js.map +1 -1
  168. package/dist/tools/deliveries.d.ts +3 -0
  169. package/dist/tools/deliveries.d.ts.map +1 -1
  170. package/dist/tools/deliveries.js +50 -94
  171. package/dist/tools/deliveries.js.map +1 -1
  172. package/dist/tools/description-builders.d.ts +0 -1
  173. package/dist/tools/description-builders.d.ts.map +1 -1
  174. package/dist/tools/description-builders.js +46 -3
  175. package/dist/tools/description-builders.js.map +1 -1
  176. package/dist/tools/documents.d.ts.map +1 -1
  177. package/dist/tools/documents.js +73 -21
  178. package/dist/tools/documents.js.map +1 -1
  179. package/dist/tools/expenses.d.ts.map +1 -1
  180. package/dist/tools/expenses.js +2 -11
  181. package/dist/tools/expenses.js.map +1 -1
  182. package/dist/tools/find.d.ts +61 -0
  183. package/dist/tools/find.d.ts.map +1 -0
  184. package/dist/tools/find.js +221 -0
  185. package/dist/tools/find.js.map +1 -0
  186. package/dist/tools/flags.d.ts +5 -0
  187. package/dist/tools/flags.d.ts.map +1 -1
  188. package/dist/tools/flags.js +116 -1
  189. package/dist/tools/flags.js.map +1 -1
  190. package/dist/tools/forms.d.ts +5 -0
  191. package/dist/tools/forms.d.ts.map +1 -0
  192. package/dist/tools/forms.js +64 -0
  193. package/dist/tools/forms.js.map +1 -0
  194. package/dist/tools/groupments.d.ts +5 -0
  195. package/dist/tools/groupments.d.ts.map +1 -0
  196. package/dist/tools/groupments.js +68 -0
  197. package/dist/tools/groupments.js.map +1 -0
  198. package/dist/tools/inactivities.d.ts +5 -0
  199. package/dist/tools/inactivities.d.ts.map +1 -0
  200. package/dist/tools/inactivities.js +59 -0
  201. package/dist/tools/inactivities.js.map +1 -0
  202. package/dist/tools/index.d.ts +4 -0
  203. package/dist/tools/index.d.ts.map +1 -1
  204. package/dist/tools/index.js +4 -0
  205. package/dist/tools/index.js.map +1 -1
  206. package/dist/tools/invoices.d.ts.map +1 -1
  207. package/dist/tools/invoices.js +27 -2
  208. package/dist/tools/invoices.js.map +1 -1
  209. package/dist/tools/linked-entity-filters.d.ts +48 -0
  210. package/dist/tools/linked-entity-filters.d.ts.map +1 -0
  211. package/dist/tools/linked-entity-filters.js +64 -0
  212. package/dist/tools/linked-entity-filters.js.map +1 -0
  213. package/dist/tools/notifications.d.ts.map +1 -1
  214. package/dist/tools/notifications.js +0 -5
  215. package/dist/tools/notifications.js.map +1 -1
  216. package/dist/tools/orders.d.ts.map +1 -1
  217. package/dist/tools/orders.js +33 -4
  218. package/dist/tools/orders.js.map +1 -1
  219. package/dist/tools/parameter-disclosure.js.map +1 -1
  220. package/dist/tools/payments.d.ts +3 -0
  221. package/dist/tools/payments.d.ts.map +1 -1
  222. package/dist/tools/payments.js +43 -109
  223. package/dist/tools/payments.js.map +1 -1
  224. package/dist/tools/positionings.d.ts.map +1 -1
  225. package/dist/tools/positionings.js +9 -39
  226. package/dist/tools/positionings.js.map +1 -1
  227. package/dist/tools/projects.d.ts.map +1 -1
  228. package/dist/tools/projects.js +5 -12
  229. package/dist/tools/projects.js.map +1 -1
  230. package/dist/tools/provider-invoices.d.ts +2 -0
  231. package/dist/tools/provider-invoices.d.ts.map +1 -1
  232. package/dist/tools/provider-invoices.js +30 -111
  233. package/dist/tools/provider-invoices.js.map +1 -1
  234. package/dist/tools/purchases.d.ts +2 -0
  235. package/dist/tools/purchases.d.ts.map +1 -1
  236. package/dist/tools/purchases.js +50 -104
  237. package/dist/tools/purchases.js.map +1 -1
  238. package/dist/tools/registration-decorators.d.ts +28 -0
  239. package/dist/tools/registration-decorators.d.ts.map +1 -1
  240. package/dist/tools/registration-decorators.js +107 -0
  241. package/dist/tools/registration-decorators.js.map +1 -1
  242. package/dist/tools/resources.d.ts.map +1 -1
  243. package/dist/tools/resources.js +27 -5
  244. package/dist/tools/resources.js.map +1 -1
  245. package/dist/tools/rights.d.ts +101 -0
  246. package/dist/tools/rights.d.ts.map +1 -0
  247. package/dist/tools/rights.js +75 -0
  248. package/dist/tools/rights.js.map +1 -0
  249. package/dist/tools/tab-tools.js +3 -3
  250. package/dist/tools/tab-tools.js.map +1 -1
  251. package/dist/tools/timesheets.d.ts +29 -0
  252. package/dist/tools/timesheets.d.ts.map +1 -1
  253. package/dist/tools/timesheets.js +196 -126
  254. package/dist/tools/timesheets.js.map +1 -1
  255. package/dist/tools/todolists.d.ts +2 -0
  256. package/dist/tools/todolists.d.ts.map +1 -1
  257. package/dist/tools/todolists.js +64 -1
  258. package/dist/tools/todolists.js.map +1 -1
  259. package/dist/tools/validations.d.ts +9 -0
  260. package/dist/tools/validations.d.ts.map +1 -1
  261. package/dist/tools/validations.js +86 -9
  262. package/dist/tools/validations.js.map +1 -1
  263. package/dist/tools/workflows.js +1 -1
  264. package/dist/tools/workflows.js.map +1 -1
  265. package/dist/transports/http.d.ts +116 -0
  266. package/dist/transports/http.d.ts.map +1 -1
  267. package/dist/transports/http.js +422 -92
  268. package/dist/transports/http.js.map +1 -1
  269. package/dist/types.d.ts +3 -3
  270. package/dist/types.d.ts.map +1 -1
  271. package/manifest.json +7 -7
  272. package/package.json +14 -10
@@ -24,7 +24,7 @@ export const fieldsField = z
24
24
  // ("Too big: expected number to be <=100") says what was refused but not what
25
25
  // to do instead, and a model that hit the page ceiling should refine its
26
26
  // filters rather than retry one page lower.
27
- const pageField = z
27
+ export const pageField = z
28
28
  .number()
29
29
  .int()
30
30
  .min(1)
@@ -33,15 +33,27 @@ const pageField = z
33
33
  })
34
34
  .default(1)
35
35
  .describe(`Numéro de page (défaut: 1, max: ${MAX_SEARCH_PAGE})`);
36
- const pageSizeField = z
36
+ export const pageSizeField = z
37
37
  .number()
38
38
  .int()
39
39
  .min(1)
40
40
  .max(MAX_PAGE_SIZE, { error: `pageSize > ${MAX_PAGE_SIZE} refusé : maximum ${MAX_PAGE_SIZE} résultats par page.` })
41
41
  .default(DEFAULT_PAGE_SIZE)
42
42
  .describe(`Nombre de résultats par page (max: ${MAX_PAGE_SIZE}, défaut: ${DEFAULT_PAGE_SIZE})`);
43
- const sortField = z.string().optional().describe("Champ de tri (ex: lastName, firstName, updateDate)");
44
- const orderField = z.enum(["asc", "desc"]).optional().describe("Ordre de tri (asc/desc)");
43
+ export const sortField = z.string().optional().describe("Champ de tri (ex: lastName, firstName, updateDate)");
44
+ export const orderField = z.enum(["asc", "desc"]).optional().describe("Ordre de tri (asc/desc)");
45
+ /** `page` / `pageSize` / `fields` — every paginated search. */
46
+ export const paginationShape = {
47
+ page: pageField,
48
+ pageSize: pageSizeField,
49
+ fields: fieldsField,
50
+ };
51
+ /** `sort` / `order` + pagination — the six entity searches that accept sorting. */
52
+ export const sortedPaginationShape = {
53
+ sort: sortField,
54
+ order: orderField,
55
+ ...paginationShape,
56
+ };
45
57
  // Integer-id filters take ids, and a model that passes labels ("actif",
46
58
  // "Acme") gets an invalid_type issue. Point it at the resolution path in the
47
59
  // message itself rather than making it re-read the field description — but at
@@ -61,9 +73,7 @@ const strArray = (doc) => z.array(z.string()).optional().describe(doc);
61
73
  export const SearchSchema = z
62
74
  .object({
63
75
  keywords: z.string().optional().describe("Mots-clés de recherche (nom, email, compétences...)"),
64
- page: pageField,
65
- pageSize: pageSizeField,
66
- fields: fieldsField,
76
+ ...paginationShape,
67
77
  })
68
78
  .strict();
69
79
  // Shared `tools` (technologies/skills) filter. Centralised so every entity
@@ -106,6 +116,90 @@ const endDateField = z
106
116
  .regex(/^\d{4}-\d{2}-\d{2}$/)
107
117
  .optional()
108
118
  .describe("Date de fin (YYYY-MM-DD), à utiliser avec `period`.");
119
+ // ---- Shared filters of the finance / activity searches (issue #248) ----
120
+ // `/invoices`, `/orders`, `/actions`, `/times-reports`, `/absences-reports`,
121
+ // `/deliveries-groupments` and `/payments` all declare the RAML trait
122
+ // `searchable`, so they take the six `perimeter*` filters — verified live on
123
+ // 2026-09-26 (`perimeterDynamic=data` reduced every one of them). The
124
+ // vocabulary below is per endpoint and was read from each `search.raml`.
125
+ /** `periodDynamic` — the relative windows the RAML lists on every dated search (no `startDate` / `endDate` needed). */
126
+ const periodDynamicField = z
127
+ .enum([
128
+ "today",
129
+ "yesterday",
130
+ "tomorrow",
131
+ "thisWeek",
132
+ "lastWeek",
133
+ "nextWeek",
134
+ "thisMonth",
135
+ "lastMonth",
136
+ "nextMonth",
137
+ "thisTrimester",
138
+ "lastTrimester",
139
+ "nextTrimester",
140
+ "thisSemester",
141
+ "lastSemester",
142
+ "nextSemester",
143
+ "thisYear",
144
+ "lastYear",
145
+ "nextYear",
146
+ "thisFiscalYear",
147
+ "lastFiscalYear",
148
+ "nextFiscalYear",
149
+ "untilToday",
150
+ ])
151
+ .optional()
152
+ .describe("Fenêtre relative appliquée à `period` (à la place de `startDate` / `endDate`) : today, thisWeek, thisMonth, lastMonth, thisYear, untilToday…");
153
+ const flagsField = entityIdArray("IDs de drapeaux (flags) — `boond_flags_search`.");
154
+ /** The validation workflow of a CRA / absence report is a string state, not a dictionary id (#250). */
155
+ const validationStatesField = z
156
+ .array(z.enum(["waitingForValidation", "validated", "rejected"]))
157
+ .optional()
158
+ .describe("États du workflow de validation : waitingForValidation (à valider), validated, rejected.");
159
+ const resourceTypesField = intArray("IDs de types de ressource — `boond://dictionary/typeOf/resources`.");
160
+ const paymentMethodsField = intArray("IDs de modes de paiement — `boond://dictionary/paymentMethods`.");
161
+ const projectTypesFilterField = intArray("IDs de types de projet — `boond://dictionary/typeOf/projects`.");
162
+ const companiesFilterField = entityIdArray("IDs de sociétés (filtre API natif, équivalent de `companyId` pour plusieurs sociétés).");
163
+ // ---- Shared shapes (issue #240) ----
164
+ // Composed by spread into the entity search schemas so a change to one field
165
+ // (a `.describe()`, the page-ceiling message) propagates everywhere. Before,
166
+ // the six perimeter fields were copied six times, `shields` five times and the
167
+ // `page`/`pageSize` pair ~16 times — ten of those copies had already lost the
168
+ // `MAX_SEARCH_PAGE` explanation `pageField` carries.
169
+ /** The six RAML-trait `searchable` perimeter filters, in API order. */
170
+ export const perimeterShape = {
171
+ perimeterManagers: perimeterManagersField,
172
+ perimeterAgencies: perimeterAgenciesField,
173
+ perimeterPoles: perimeterPolesField,
174
+ perimeterBusinessUnits: perimeterBusinessUnitsField,
175
+ perimeterDynamic: perimeterDynamicField,
176
+ narrowPerimeter: narrowPerimeterField,
177
+ };
178
+ /** Completeness level of the conditional fields (RAML trait `shields`). */
179
+ export const shieldsField = z
180
+ .array(z.enum(["uncomplete", "minimum", "complete"]))
181
+ .optional()
182
+ .describe("Niveau de complétude des champs conditionnels.");
183
+ /** `keywordsType` values shared by the two people endpoints (resources, candidates). */
184
+ export const peopleKeywordsTypeEnum = z.enum([
185
+ "resumeTd",
186
+ "lastName",
187
+ "firstName",
188
+ "fullName",
189
+ "strictFullName",
190
+ "emails",
191
+ "title",
192
+ "titleSkills",
193
+ "phones",
194
+ "reference",
195
+ "resume",
196
+ "td",
197
+ ]);
198
+ /** `perimeterManagersType` — candidates and opportunities only. */
199
+ const perimeterManagersTypeField = z
200
+ .enum(["main", "hr"])
201
+ .optional()
202
+ .describe("Type de responsable visé par `perimeterManagers`: 'main' (Main Manager) ou 'hr' (HR Manager).");
109
203
  // ---- Resource search schema (collaborateurs internes) ----
110
204
  // Source: https://doc.boondmanager.com/api-externe/raml-build/resources/resources/search.raml
111
205
  export const ResourceSearchSchema = z
@@ -115,30 +209,11 @@ export const ResourceSearchSchema = z
115
209
  .optional()
116
210
  .describe("Mots-clés (par défaut, recherche dans CV + dossier technique). Pour cibler un champ précis, " +
117
211
  "fournir aussi `keywordsType` (ex: lastName, firstName, fullName, emails, title, titleSkills, phones, reference)."),
118
- keywordsType: z
119
- .enum([
120
- "resumeTd",
121
- "lastName",
122
- "firstName",
123
- "fullName",
124
- "strictFullName",
125
- "emails",
126
- "title",
127
- "titleSkills",
128
- "phones",
129
- "reference",
130
- "resume",
131
- "td",
132
- ])
212
+ keywordsType: peopleKeywordsTypeEnum
133
213
  .optional()
134
214
  .describe("Champ ciblé par `keywords`. Défaut: 'resumeTd' (CV + dossier technique). " +
135
215
  "Pour 'fullName' utiliser `keywords = 'NOM#PRENOM'`."),
136
- perimeterManagers: perimeterManagersField,
137
- perimeterAgencies: perimeterAgenciesField,
138
- perimeterPoles: perimeterPolesField,
139
- perimeterBusinessUnits: perimeterBusinessUnitsField,
140
- perimeterDynamic: perimeterDynamicField,
141
- narrowPerimeter: narrowPerimeterField,
216
+ ...perimeterShape,
142
217
  resourceStates: intArray("IDs d'états de ressource (dictionnaire setting.state.resource via boond_application_dictionary)."),
143
218
  excludeResourceStates: intArray("IDs d'états de ressource à EXCLURE."),
144
219
  resourceTypes: intArray("IDs de types de ressource (dictionnaire setting.typeOf.resource)."),
@@ -178,15 +253,8 @@ export const ResourceSearchSchema = z
178
253
  .optional()
179
254
  .describe("Rayon en km pour la recherche géographique (5-200). Requis si `coordinates` ou `location` est fourni."),
180
255
  excludeManager: z.boolean().optional().describe("Si true, ne retourne que les ressources sans compte manager."),
181
- shields: z
182
- .array(z.enum(["uncomplete", "minimum", "complete"]))
183
- .optional()
184
- .describe("Niveau de complétude des champs conditionnels."),
185
- sort: sortField,
186
- order: orderField,
187
- page: pageField,
188
- pageSize: pageSizeField,
189
- fields: fieldsField,
256
+ shields: shieldsField,
257
+ ...sortedPaginationShape,
190
258
  })
191
259
  .strict();
192
260
  // ---- Candidate search schema ----
@@ -197,33 +265,9 @@ export const CandidateSearchSchema = z
197
265
  .string()
198
266
  .optional()
199
267
  .describe("Mots-clés (par défaut, recherche dans CV + dossier technique). Combinable avec `keywordsType`."),
200
- keywordsType: z
201
- .enum([
202
- "resumeTd",
203
- "lastName",
204
- "firstName",
205
- "fullName",
206
- "strictFullName",
207
- "emails",
208
- "title",
209
- "titleSkills",
210
- "phones",
211
- "reference",
212
- "resume",
213
- "td",
214
- ])
215
- .optional()
216
- .describe("Champ ciblé par `keywords` (défaut: 'resumeTd')."),
217
- perimeterManagers: perimeterManagersField,
218
- perimeterAgencies: perimeterAgenciesField,
219
- perimeterPoles: perimeterPolesField,
220
- perimeterBusinessUnits: perimeterBusinessUnitsField,
221
- perimeterDynamic: perimeterDynamicField,
222
- perimeterManagersType: z
223
- .enum(["main", "hr"])
224
- .optional()
225
- .describe("Type de responsable visé par `perimeterManagers`: 'main' (Main Manager) ou 'hr' (HR Manager)."),
226
- narrowPerimeter: narrowPerimeterField,
268
+ keywordsType: peopleKeywordsTypeEnum.optional().describe("Champ ciblé par `keywords` (défaut: 'resumeTd')."),
269
+ ...perimeterShape,
270
+ perimeterManagersType: perimeterManagersTypeField,
227
271
  candidateStates: intArray("IDs d'états de candidat (dictionnaire setting.state.candidate via boond_application_dictionary)."),
228
272
  candidateTypes: intArray("IDs de types de candidat (dictionnaire setting.typeOf.resource)."),
229
273
  contractTypes: intArray("IDs de types de contrat recherchés (dictionnaire setting.typeOf.contract)."),
@@ -249,15 +293,8 @@ export const CandidateSearchSchema = z
249
293
  coordinates: z.string().optional().describe("Coordonnées GPS 'lat,lon'. Requiert `geoDistance`."),
250
294
  location: z.string().optional().describe("Adresse texte. Requiert `geoDistance`."),
251
295
  geoDistance: z.number().int().min(5).max(200).optional().describe("Rayon km (5-200)."),
252
- shields: z
253
- .array(z.enum(["uncomplete", "minimum", "complete"]))
254
- .optional()
255
- .describe("Niveau de complétude."),
256
- sort: sortField,
257
- order: orderField,
258
- page: pageField,
259
- pageSize: pageSizeField,
260
- fields: fieldsField,
296
+ shields: shieldsField,
297
+ ...sortedPaginationShape,
261
298
  })
262
299
  .strict();
263
300
  // ---- Contact search schema ----
@@ -284,12 +321,7 @@ export const ContactSearchSchema = z
284
321
  .optional()
285
322
  .describe("Champ ciblé. Défaut: 'default'. Pour 'fullName' utiliser `keywords = 'NOM#PRENOM'`. " +
286
323
  "Pour 'companyFullName' utiliser `keywords = 'CSOCid#NOM#PRENOM'`."),
287
- perimeterManagers: perimeterManagersField,
288
- perimeterAgencies: perimeterAgenciesField,
289
- perimeterPoles: perimeterPolesField,
290
- perimeterBusinessUnits: perimeterBusinessUnitsField,
291
- perimeterDynamic: perimeterDynamicField,
292
- narrowPerimeter: narrowPerimeterField,
324
+ ...perimeterShape,
293
325
  states: intArray("IDs d'états de contact (dictionnaire setting.state.contact)."),
294
326
  companyStates: intArray("IDs d'états des sociétés rattachées (dictionnaire setting.state.company)."),
295
327
  typesOf: intArray("IDs de types de contact (dictionnaire setting.typeOf.contact). " +
@@ -309,15 +341,8 @@ export const ContactSearchSchema = z
309
341
  endDate: endDateField,
310
342
  completeness: strArray("Filtre par complétude des champs au format `fieldId:mode` (fieldId: email/phone/socialNetworks ; " +
311
343
  "mode: empty/filled). Logique OU par défaut, '#AND#' en 1er pour ET. Ex: ['email:empty','phone:empty']."),
312
- shields: z
313
- .array(z.enum(["uncomplete", "minimum", "complete"]))
314
- .optional()
315
- .describe("Niveau de complétude."),
316
- sort: sortField,
317
- order: orderField,
318
- page: pageField,
319
- pageSize: pageSizeField,
320
- fields: fieldsField,
344
+ shields: shieldsField,
345
+ ...sortedPaginationShape,
321
346
  })
322
347
  .strict();
323
348
  // ---- Company search schema ----
@@ -332,12 +357,7 @@ export const CompanySearchSchema = z
332
357
  .enum(["default", "name", "phones", "emails", "socialNetworks"])
333
358
  .optional()
334
359
  .describe("Champ ciblé par `keywords`. Défaut: 'default'."),
335
- perimeterManagers: perimeterManagersField,
336
- perimeterAgencies: perimeterAgenciesField,
337
- perimeterPoles: perimeterPolesField,
338
- perimeterBusinessUnits: perimeterBusinessUnitsField,
339
- perimeterDynamic: perimeterDynamicField,
340
- narrowPerimeter: narrowPerimeterField,
360
+ ...perimeterShape,
341
361
  states: intArray("IDs d'états de société (dictionnaire setting.state.company)."),
342
362
  expertiseAreas: strArray("IDs de domaines d'expertise (dictionnaire setting.expertiseArea)."),
343
363
  origins: strArray("IDs d'origines (dictionnaire setting.origin)."),
@@ -350,15 +370,8 @@ export const CompanySearchSchema = z
350
370
  "À combiner avec `startDate` + `endDate`."),
351
371
  startDate: startDateField,
352
372
  endDate: endDateField,
353
- shields: z
354
- .array(z.enum(["uncomplete", "minimum", "complete"]))
355
- .optional()
356
- .describe("Niveau de complétude."),
357
- sort: sortField,
358
- order: orderField,
359
- page: pageField,
360
- pageSize: pageSizeField,
361
- fields: fieldsField,
373
+ shields: shieldsField,
374
+ ...sortedPaginationShape,
362
375
  })
363
376
  .strict();
364
377
  // ---- Opportunity search schema ----
@@ -371,16 +384,8 @@ export const OpportunitySearchSchema = z
371
384
  .describe("Mots-clés. Pour cibler par ID préfixé : 'AOnnn' (opportunité), 'CSOCnnn' (société), " +
372
385
  "'CCONnnn' (contact), 'CANDnnn' (candidat), 'COMPnnn' (ressource), 'PRODnnn' (produit). " +
373
386
  "Sinon recherche plein texte sur titre/société."),
374
- perimeterManagers: perimeterManagersField,
375
- perimeterAgencies: perimeterAgenciesField,
376
- perimeterPoles: perimeterPolesField,
377
- perimeterBusinessUnits: perimeterBusinessUnitsField,
378
- perimeterDynamic: perimeterDynamicField,
379
- perimeterManagersType: z
380
- .enum(["main", "hr"])
381
- .optional()
382
- .describe("Type de responsable visé par `perimeterManagers` (main/hr)."),
383
- narrowPerimeter: narrowPerimeterField,
387
+ ...perimeterShape,
388
+ perimeterManagersType: perimeterManagersTypeField,
384
389
  opportunityStates: intArray("IDs d'états d'opportunité (dictionnaire setting.state.opportunity)."),
385
390
  opportunityTypes: strArray("IDs de types d'opportunité (dictionnaire setting.typeOf.project)."),
386
391
  positioningStates: strArray("IDs d'états de positionnement, ou 'none' pour les opportunités sans positionnement."),
@@ -399,15 +404,8 @@ export const OpportunitySearchSchema = z
399
404
  "À combiner avec `startDate` + `endDate`."),
400
405
  startDate: startDateField,
401
406
  endDate: endDateField,
402
- shields: z
403
- .array(z.enum(["uncomplete", "minimum", "complete"]))
404
- .optional()
405
- .describe("Niveau de complétude."),
406
- sort: sortField,
407
- order: orderField,
408
- page: pageField,
409
- pageSize: pageSizeField,
410
- fields: fieldsField,
407
+ shields: shieldsField,
408
+ ...sortedPaginationShape,
411
409
  })
412
410
  .strict();
413
411
  // ---- Project search schema ----
@@ -419,12 +417,7 @@ export const ProjectSearchSchema = z
419
417
  .optional()
420
418
  .describe("Mots-clés. Pour cibler par ID préfixé : 'PRJnnn' (projet), 'CSOCnnn' (société), " +
421
419
  "'CCONnnn' (contact), 'AOnnn' (opportunité), 'COMPnnn' (ressource), 'CTRnnn' (contrat)."),
422
- perimeterManagers: perimeterManagersField,
423
- perimeterAgencies: perimeterAgenciesField,
424
- perimeterPoles: perimeterPolesField,
425
- perimeterBusinessUnits: perimeterBusinessUnitsField,
426
- perimeterDynamic: perimeterDynamicField,
427
- narrowPerimeter: narrowPerimeterField,
420
+ ...perimeterShape,
428
421
  projectStates: intArray("IDs d'états de projet (dictionnaire setting.state.project)."),
429
422
  projectTypes: intArray("IDs de types de projet (dictionnaire setting.typeOf.project)."),
430
423
  companies: entityIdArray("IDs de sociétés clientes : projets rattachés à ces sociétés."),
@@ -438,11 +431,7 @@ export const ProjectSearchSchema = z
438
431
  "'hasAdditionalDataOrPurchase'. À combiner avec `startDate` + `endDate`."),
439
432
  startDate: startDateField,
440
433
  endDate: endDateField,
441
- sort: sortField,
442
- order: orderField,
443
- page: pageField,
444
- pageSize: pageSizeField,
445
- fields: fieldsField,
434
+ ...sortedPaginationShape,
446
435
  })
447
436
  .strict();
448
437
  // BoondManager entity ids are numeric. Constraining the schema to digits
@@ -470,6 +459,11 @@ export const IdSchema = z
470
459
  // the id still can't smuggle a path segment, `..`, `?` or `#` into the API
471
460
  // path. Letters are allowed in both cases because Boond derives the suffix
472
461
  // from camelCase parent types (`administrativeFile`).
462
+ /** `mode` of `boond_documents_get` (issue #263): extracted text by default, the raw bytes on demand. */
463
+ export const DocumentGetModeSchema = z
464
+ .enum(["text", "raw"])
465
+ .default("text")
466
+ .describe("text (défaut) : PDF et DOCX renvoyés en texte extrait côté serveur, images en contenu `image` ; raw : le fichier tel quel en ressource embarquée (base64).");
473
467
  export const DocumentIdSchema = z
474
468
  .object({
475
469
  id: z
@@ -478,6 +472,7 @@ export const DocumentIdSchema = z
478
472
  .describe("Identifiant du document, tel qu'exposé par les relations d'entités (ex. 123_resume)"),
479
473
  })
480
474
  .strict();
475
+ export const DocumentGetSchema = DocumentIdSchema.extend({ mode: DocumentGetModeSchema }).strict();
481
476
  // ID + tab param schema
482
477
  export const IdTabSchema = z
483
478
  .object({
@@ -520,13 +515,8 @@ export const CandidateCreateSchema = z
520
515
  .strict();
521
516
  export const PositioningUpdateSchema = z
522
517
  .object({
523
- id: z.string().min(1).describe("ID du positionnement \u00e0 modifier"),
524
- state: z
525
- .number()
526
- .int()
527
- .min(0)
528
- .optional()
529
- .describe("\u00c9tat du positionnement : ID num\u00e9rique du dictionnaire setting.state.positioning"),
518
+ id: EntityIdSchema.describe("ID du positionnement \u00e0 modifier"),
519
+ state: stateField("positioning", "État du positionnement : ID numérique de `boond://dictionary/states/positionings`"),
530
520
  stateReasonTypeOf: z
531
521
  .number()
532
522
  .int()
@@ -540,7 +530,7 @@ export const PositioningUpdateSchema = z
540
530
  .strict();
541
531
  export const CandidateUpdateSchema = z
542
532
  .object({
543
- id: z.string().min(1).describe("ID du candidat à modifier"),
533
+ id: EntityIdSchema.describe("ID du candidat à modifier"),
544
534
  firstName: z.string().optional().describe("Prénom"),
545
535
  lastName: z.string().optional().describe("Nom"),
546
536
  email1: z.string().email().optional().describe("Email principal"),
@@ -569,7 +559,7 @@ export const ResourceCreateSchema = z
569
559
  .strict();
570
560
  export const ResourceUpdateSchema = z
571
561
  .object({
572
- id: z.string().min(1).describe("ID de la ressource à modifier"),
562
+ id: EntityIdSchema.describe("ID de la ressource à modifier"),
573
563
  firstName: z.string().optional().describe("Prénom"),
574
564
  lastName: z.string().optional().describe("Nom"),
575
565
  email1: z.string().email().optional().describe("Email principal"),
@@ -600,7 +590,7 @@ const technicalDataLanguageItemSchema = z
600
590
  .strict();
601
591
  export const ResourceTechnicalDataUpdateSchema = z
602
592
  .object({
603
- id: z.string().min(1).describe("ID de la ressource dont le dossier technique est mis à jour."),
593
+ id: EntityIdSchema.describe("ID de la ressource dont le dossier technique est mis à jour."),
604
594
  mode: z
605
595
  .enum(["merge", "replace"])
606
596
  .default("merge")
@@ -646,7 +636,7 @@ const referenceYear = z.coerce
646
636
  .describe("Année (ex: 2024). Accepte int ou string '2024'.");
647
637
  export const ReferenceCreateSchema = z
648
638
  .object({
649
- resourceId: z.string().min(1).describe("ID de la ressource à laquelle rattacher la référence."),
639
+ resourceId: EntityIdSchema.describe("ID de la ressource à laquelle rattacher la référence."),
650
640
  title: z.string().min(1).describe("Intitulé du poste."),
651
641
  company: z.string().min(1).describe("Société / employeur."),
652
642
  description: z
@@ -663,11 +653,8 @@ export const ReferenceCreateSchema = z
663
653
  .strict();
664
654
  export const ReferenceUpdateSchema = z
665
655
  .object({
666
- resourceId: z
667
- .string()
668
- .min(1)
669
- .describe("ID de la ressource portant la référence (les references sont embarquées dans le DT)."),
670
- referenceId: z.string().min(1).describe("ID de la référence à modifier."),
656
+ resourceId: EntityIdSchema.describe("ID de la ressource portant la référence (les references sont embarquées dans le DT)."),
657
+ referenceId: EntityIdSchema.describe("ID de la référence à modifier."),
671
658
  title: z.string().optional().describe("Intitulé du poste."),
672
659
  company: z.string().optional().describe("Société / employeur."),
673
660
  description: z.string().optional().describe("Description / missions / réalisations."),
@@ -681,11 +668,8 @@ export const ReferenceUpdateSchema = z
681
668
  .strict();
682
669
  export const ReferenceIdSchema = z
683
670
  .object({
684
- resourceId: z
685
- .string()
686
- .min(1)
687
- .describe("ID de la ressource portant la référence (les references sont embarquées dans le DT)."),
688
- referenceId: z.string().min(1).describe("ID de la référence à supprimer."),
671
+ resourceId: EntityIdSchema.describe("ID de la ressource portant la référence (les references sont embarquées dans le DT)."),
672
+ referenceId: EntityIdSchema.describe("ID de la référence à supprimer."),
689
673
  })
690
674
  .strict();
691
675
  // ---- Contact schemas ----
@@ -698,13 +682,13 @@ export const ContactCreateSchema = z
698
682
  city: z.string().optional().describe("Ville"),
699
683
  country: z.string().optional().describe("Pays"),
700
684
  title: z.string().optional().describe("Titre / fonction"),
701
- companyId: z.string().optional().describe("ID de la société associée"),
685
+ companyId: EntityIdSchema.optional().describe("ID de la société associée"),
702
686
  note: z.string().optional().describe("Notes"),
703
687
  })
704
688
  .strict();
705
689
  export const ContactUpdateSchema = z
706
690
  .object({
707
- id: z.string().min(1).describe("ID du contact à modifier"),
691
+ id: EntityIdSchema.describe("ID du contact à modifier"),
708
692
  firstName: z.string().optional().describe("Prénom"),
709
693
  lastName: z.string().optional().describe("Nom"),
710
694
  email1: z.string().email().optional().describe("Email"),
@@ -730,7 +714,7 @@ export const CompanyCreateSchema = z
730
714
  .strict();
731
715
  export const CompanyUpdateSchema = z
732
716
  .object({
733
- id: z.string().min(1).describe("ID de la société à modifier"),
717
+ id: EntityIdSchema.describe("ID de la société à modifier"),
734
718
  name: z.string().optional().describe("Nom"),
735
719
  email1: z.string().email().optional().describe("Email"),
736
720
  phone1: z.string().optional().describe("Téléphone"),
@@ -759,8 +743,8 @@ const opportunityWritableShape = {
759
743
  .min(1)
760
744
  .optional()
761
745
  .describe("Type d'opportunité : ID numérique du dictionnaire setting.typeOf.project (ex: 1, 3), via boond_application_dictionary"),
762
- companyId: z.string().optional().describe("ID de la société cliente (relation company)"),
763
- contactId: z.string().optional().describe("ID du contact associé (relation contact)"),
746
+ companyId: EntityIdSchema.optional().describe("ID de la société cliente (relation company)"),
747
+ contactId: EntityIdSchema.optional().describe("ID du contact associé (relation contact)"),
764
748
  state: stateField("opportunity", "État de l'opportunité"),
765
749
  startDate: z.string().optional().describe("Date de début prévue (YYYY-MM-DD ou 'immediate')"),
766
750
  endDate: z.string().optional().describe("Date de fin prévue (YYYY-MM-DD)"),
@@ -779,13 +763,10 @@ const opportunityWritableShape = {
779
763
  .optional()
780
764
  .describe("Domaine d'expertise : ID du dictionnaire setting.expertiseArea (via boond_application_dictionary)"),
781
765
  turnoverEstimatedExcludingTax: z.coerce.number().optional().describe("Chiffre d'affaires estimé HT (montant)"),
782
- poleId: z.string().optional().describe("ID du pôle (relation pole)"),
783
- hrManagerId: z.string().optional().describe("ID de la ressource responsable RH (relation hrManager)"),
784
- mainManagerId: z
785
- .string()
786
- .optional()
787
- .describe("ID de la ressource responsable principal / commercial (relation mainManager)"),
788
- agencyId: z.string().optional().describe("ID de l'agence (relation agency)"),
766
+ poleId: EntityIdSchema.optional().describe("ID du pôle (relation pole)"),
767
+ hrManagerId: EntityIdSchema.optional().describe("ID de la ressource responsable RH (relation hrManager)"),
768
+ mainManagerId: EntityIdSchema.optional().describe("ID de la ressource responsable principal / commercial (relation mainManager)"),
769
+ agencyId: EntityIdSchema.optional().describe("ID de l'agence (relation agency)"),
789
770
  };
790
771
  export const OpportunityCreateSchema = z
791
772
  .object({
@@ -795,22 +776,35 @@ export const OpportunityCreateSchema = z
795
776
  .strict();
796
777
  export const OpportunityUpdateSchema = z
797
778
  .object({
798
- id: z.string().min(1).describe("ID de l'opportunité à modifier"),
779
+ id: EntityIdSchema.describe("ID de l'opportunité à modifier"),
799
780
  name: z.string().optional().describe("Nom / titre"),
800
781
  ...opportunityWritableShape,
801
782
  })
802
783
  .strict();
803
784
  // ---- Action schemas ----
785
+ // Source: resources/actions/search.raml (filters verified live, issue #248).
804
786
  export const ActionSearchSchema = z
805
787
  .object({
806
788
  keywords: z.string().optional().describe("Mots-clés de recherche"),
807
- candidateId: z.string().optional().describe("Filtrer par ID candidat"),
808
- resourceId: z.string().optional().describe("Filtrer par ID ressource"),
809
- contactId: z.string().optional().describe("Filtrer par ID contact"),
810
- companyId: z.string().optional().describe("Filtrer par ID société"),
811
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
812
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
813
- fields: fieldsField,
789
+ candidateId: EntityIdSchema.optional().describe("Filtrer par ID candidat (référence keywords CAND<id>)"),
790
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (référence keywords COMP<id>)"),
791
+ contactId: EntityIdSchema.optional().describe("Filtrer par ID contact (référence keywords CCON<id>)"),
792
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société (référence keywords CSOC<id>)"),
793
+ opportunityId: EntityIdSchema.optional().describe("Filtrer par ID opportunité (référence keywords AO<id>)"),
794
+ projectId: EntityIdSchema.optional().describe("Filtrer par ID projet (référence keywords PRJ<id>)"),
795
+ orderId: EntityIdSchema.optional().describe("Filtrer par ID bon de commande (référence keywords BDC<id>)"),
796
+ invoiceId: EntityIdSchema.optional().describe("Filtrer par ID facture (référence keywords FACT<id>)"),
797
+ actionTypes: intArray("IDs de types d'action — `boond://dictionary/actions/<entité>` (les types sont déclarés par entité de rattachement)."),
798
+ period: z
799
+ .enum(["started", "created", "updated"])
800
+ .optional()
801
+ .describe("Champ de date borné par `startDate` / `endDate` : started (date de l'action), created, updated."),
802
+ periodDynamic: periodDynamicField,
803
+ startDate: startDateField,
804
+ endDate: endDateField,
805
+ flags: flagsField,
806
+ ...perimeterShape,
807
+ ...sortedPaginationShape,
814
808
  })
815
809
  .strict();
816
810
  // POST /actions requires a polymorphic `dependsOn` relationship (the entity the
@@ -831,16 +825,13 @@ export const ActionCreateSchema = z
831
825
  .optional()
832
826
  .describe("Date de début au format ISO 8601 avec timezone (ex: 2026-06-05T10:00:00+0200)"),
833
827
  endDate: z.string().regex(actionDateTimeRegex).optional().describe("Date de fin (même format que startDate)"),
834
- candidateId: z.string().optional().describe("ID du candidat auquel rattacher l'action (dependsOn)"),
835
- resourceId: z.string().optional().describe("ID de la ressource à laquelle rattacher l'action (dependsOn)"),
836
- contactId: z.string().optional().describe("ID du contact auquel rattacher l'action (dependsOn)"),
837
- opportunityId: z.string().optional().describe("ID de l'opportunité à laquelle rattacher l'action (dependsOn)"),
838
- projectId: z.string().optional().describe("ID du projet auquel rattacher l'action (dependsOn)"),
839
- companyId: z.string().optional().describe("ID de la société associée (uniquement en complément d'un contactId)"),
840
- positioningId: z
841
- .string()
842
- .optional()
843
- .describe("ID du positionnement à lier à l'action (relation positioning). Requis par l'API pour les types d'action liés aux positionnements (ex. RQ) — sans lui, erreur 422 « 1002 - Wrong or missing attribute (/data/relationships/positioning) »."),
828
+ candidateId: EntityIdSchema.optional().describe("ID du candidat auquel rattacher l'action (dependsOn)"),
829
+ resourceId: EntityIdSchema.optional().describe("ID de la ressource à laquelle rattacher l'action (dependsOn)"),
830
+ contactId: EntityIdSchema.optional().describe("ID du contact auquel rattacher l'action (dependsOn)"),
831
+ opportunityId: EntityIdSchema.optional().describe("ID de l'opportunité à laquelle rattacher l'action (dependsOn)"),
832
+ projectId: EntityIdSchema.optional().describe("ID du projet auquel rattacher l'action (dependsOn)"),
833
+ companyId: EntityIdSchema.optional().describe("ID de la société associée (uniquement en complément d'un contactId)"),
834
+ positioningId: EntityIdSchema.optional().describe("ID du positionnement à lier à l'action (relation positioning). Requis par l'API pour les types d'action liés aux positionnements (ex. RQ) — sans lui, erreur 422 « 1002 - Wrong or missing attribute (/data/relationships/positioning) »."),
844
835
  })
845
836
  .strict();
846
837
  // PUT /actions/{id} (PATCH renvoie 405). Mise à jour partielle des seuls
@@ -850,7 +841,7 @@ export const ActionCreateSchema = z
850
841
  // de résolution de libellé, faute d'entité `dependsOn` connue sur l'update).
851
842
  export const ActionUpdateSchema = z
852
843
  .object({
853
- id: z.string().min(1).describe("ID de l'action à modifier"),
844
+ id: EntityIdSchema.describe("ID de l'action à modifier"),
854
845
  typeOf: z.coerce
855
846
  .number()
856
847
  .int()
@@ -870,7 +861,7 @@ export const ActionUpdateSchema = z
870
861
  // ---- Timesheet schemas ----
871
862
  export const ResourceTimesheetSchema = z
872
863
  .object({
873
- resourceId: z.string().min(1).describe("ID de la ressource"),
864
+ resourceId: EntityIdSchema.describe("ID de la ressource"),
874
865
  month: z.number().int().min(1).max(12).optional().describe("Mois (1-12). Si omis, mois courant."),
875
866
  year: z.number().int().min(2000).optional().describe("Année (ex: 2025). Si omis, année courante."),
876
867
  })
@@ -889,28 +880,103 @@ export const TimesheetSearchSchema = z
889
880
  .regex(/^\d{4}-\d{2}$/)
890
881
  .describe("Mois de fin au format YYYY-MM (ex: '2025-03'). Requis."),
891
882
  keywords: z.string().optional().describe("Mots-clés (préfixes 'TPS', 'COMP'...)."),
892
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
893
- pageSize: z
883
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (référence keywords COMP<id>)"),
884
+ resourceTypes: resourceTypesField,
885
+ validationStates: validationStatesField,
886
+ closed: z.boolean().optional().describe("true = CRA clôturés uniquement, false = non clôturés."),
887
+ ...perimeterShape,
888
+ ...sortedPaginationShape,
889
+ })
890
+ .strict();
891
+ // ---- Timesheet (CRA) write schemas (issue #249) ----
892
+ //
893
+ // A times report is a monthly container per resource (`term` + `resource`)
894
+ // whose lines live in `regularTimes[]` / `exceptionalTimes[]` — the exact
895
+ // twin of the expenses report (#179). The line shape below is the one the API
896
+ // *returns* (`GET /times-reports/{id}`, `models.time` in the dictionary, and
897
+ // the `plannedTimes` of `/times-reports/default`): `startDate`, `duration`,
898
+ // `workUnitType.reference`, `project`, `delivery`, `batch`. `state` is moved by
899
+ // the validation workflow (`validated`, `waitingForValidation`…) and is not a
900
+ // write field. See CLAUDE.md → *Timesheets* for what was and was not verified
901
+ // against the live API.
902
+ export const TimesheetLineSchema = z
903
+ .object({
904
+ startDate: z
905
+ .string()
906
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
907
+ .describe("Jour saisi (YYYY-MM-DD). Doit tomber dans le mois `term` du CRA."),
908
+ duration: z
909
+ .number()
910
+ .positive()
911
+ .default(1)
912
+ .describe("Durée en unités d'œuvre (jour par défaut : 1 = journée, 0.5 = demi-journée)."),
913
+ workUnitTypeReference: z
894
914
  .number()
895
915
  .int()
896
- .min(1)
897
- .max(MAX_PAGE_SIZE)
898
- .default(DEFAULT_PAGE_SIZE)
899
- .describe(`Nombre de résultats par page (max: ${MAX_PAGE_SIZE}, défaut: ${DEFAULT_PAGE_SIZE})`),
916
+ .default(1)
917
+ .describe("Code `reference` du type d'unité d'œuvre — voir `boond_timesheets_default` (`workUnitTypesAllowed` de la ressource). " +
918
+ "1 = « Normale » (production) sur les tenants observés ; les absences (congés, RTT, maladie…) ont leurs propres codes."),
919
+ projectId: EntityIdSchema.optional().describe("ID du projet imputé. Requis pour une activité de production ; absent pour une absence. Couples autorisés : `boond_timesheets_default`."),
920
+ deliveryId: EntityIdSchema.optional().describe("ID de la prestation (delivery) imputée. Requis avec `projectId` pour la production — voir `boond_timesheets_default`."),
921
+ batchId: EntityIdSchema.optional().describe("ID du lot. Absent = aucun lot."),
922
+ })
923
+ .strict();
924
+ export const TimesheetCreateSchema = z
925
+ .object({
926
+ resourceId: EntityIdSchema.describe("ID de la ressource (le collaborateur dont c'est le CRA)."),
927
+ agencyId: EntityIdSchema.optional().describe("ID de l'agence — voir `boond_timesheets_default`. Déduite de la ressource si omise."),
928
+ term: z
929
+ .string()
930
+ .regex(/^\d{4}-\d{2}$/)
931
+ .describe("Mois du CRA (YYYY-MM). Un CRA = un mois × une ressource ; l'API ne déduplique pas."),
932
+ informationComments: z.string().optional().describe("Commentaires du CRA."),
933
+ regularTimes: z
934
+ .array(TimesheetLineSchema)
935
+ .optional()
936
+ .describe("Lignes d'activité normale (production et absences). Omettre pour créer un CRA vide."),
937
+ exceptionalTimes: z
938
+ .array(TimesheetLineSchema)
939
+ .optional()
940
+ .describe("Lignes d'activité exceptionnelle (interventions, astreintes) — types `exceptionalTime` / `exceptionalCalendar`."),
941
+ })
942
+ .strict();
943
+ export const TimesheetUpdateSchema = z
944
+ .object({
945
+ id: EntityIdSchema.describe("ID du CRA à modifier"),
946
+ informationComments: z.string().optional().describe("Commentaires"),
947
+ closed: z.boolean().optional().describe("Clôturer le CRA"),
948
+ regularTimes: z
949
+ .array(TimesheetLineSchema)
950
+ .optional()
951
+ .describe("⚠️ Remplace TOUTES les lignes d'activité normale — envoyer la liste complète (relire le CRA avec `boond_timesheets_get` et fusionner), pas seulement les ajouts."),
952
+ exceptionalTimes: z
953
+ .array(TimesheetLineSchema)
954
+ .optional()
955
+ .describe("⚠️ Remplace TOUTES les lignes d'activité exceptionnelle — même règle que `regularTimes`."),
956
+ })
957
+ .strict();
958
+ export const TimesheetDefaultSchema = z
959
+ .object({
960
+ resourceId: EntityIdSchema.describe("ID de la ressource"),
961
+ term: z
962
+ .string()
963
+ .regex(/^\d{4}-\d{2}$/)
964
+ .describe("Mois du CRA (YYYY-MM)"),
965
+ agencyId: EntityIdSchema.optional().describe("ID de l'agence, si la ressource en a plusieurs"),
900
966
  })
901
967
  .strict();
902
968
  export const TimesheetGetSchema = z
903
969
  .object({
904
- id: z.string().min(1).describe("Identifiant unique de la feuille de temps"),
970
+ id: EntityIdSchema.describe("Identifiant unique de la feuille de temps"),
905
971
  })
906
972
  .strict();
907
973
  // ---- Project schemas ----
908
974
  export const ProjectCreateSchema = z
909
975
  .object({
910
976
  name: z.string().min(1).describe("Nom du projet / mission"),
911
- companyId: z.string().optional().describe("ID de la société cliente"),
912
- contactId: z.string().optional().describe("ID du contact associé"),
913
- opportunityId: z.string().optional().describe("ID de l'opportunité liée"),
977
+ companyId: EntityIdSchema.optional().describe("ID de la société cliente"),
978
+ contactId: EntityIdSchema.optional().describe("ID du contact associé"),
979
+ opportunityId: EntityIdSchema.optional().describe("ID de l'opportunité liée"),
914
980
  typeOf: z.number().int().optional().describe("Type de projet (ID du dictionnaire setting.typeOf.project)"),
915
981
  state: stateField("project", "État du projet (0=en cours, 1=terminé, 2=archivé...)"),
916
982
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
@@ -920,7 +986,7 @@ export const ProjectCreateSchema = z
920
986
  .strict();
921
987
  export const ProjectUpdateSchema = z
922
988
  .object({
923
- id: z.string().min(1).describe("ID du projet à modifier"),
989
+ id: EntityIdSchema.describe("ID du projet à modifier"),
924
990
  name: z.string().optional().describe("Nom du projet"),
925
991
  typeOf: z.number().int().optional().describe("Type de projet (ID du dictionnaire setting.typeOf.project)"),
926
992
  state: stateField("project", "État du projet"),
@@ -933,8 +999,8 @@ export const ProjectUpdateSchema = z
933
999
  export const InvoiceCreateSchema = z
934
1000
  .object({
935
1001
  reference: z.string().optional().describe("Référence de la facture"),
936
- orderId: z.string().optional().describe("ID du bon de commande associé"),
937
- state: z.number().int().optional().describe("État de la facture"),
1002
+ orderId: EntityIdSchema.optional().describe("ID du bon de commande associé"),
1003
+ state: stateField("invoice", "État de la facture : ID de `boond://dictionary/states/invoices` (ex: 0 = Création, 1 = Transmis au client)"),
938
1004
  invoiceDate: z.string().optional().describe("Date de facturation (YYYY-MM-DD)"),
939
1005
  expectedPaymentDate: z.string().optional().describe("Date d'échéance/paiement attendu (YYYY-MM-DD)"),
940
1006
  amountExcludingTax: z.number().optional().describe("Montant HT"),
@@ -946,9 +1012,9 @@ export const InvoiceCreateSchema = z
946
1012
  .strict();
947
1013
  export const InvoiceUpdateSchema = z
948
1014
  .object({
949
- id: z.string().min(1).describe("ID de la facture à modifier"),
1015
+ id: EntityIdSchema.describe("ID de la facture à modifier"),
950
1016
  reference: z.string().optional().describe("Référence de la facture"),
951
- state: z.number().int().optional().describe("État de la facture"),
1017
+ state: stateField("invoice", "État de la facture : ID de `boond://dictionary/states/invoices` (ex: 0 = Création, 1 = Transmis au client)"),
952
1018
  invoiceDate: z.string().optional().describe("Date de facturation (YYYY-MM-DD)"),
953
1019
  expectedPaymentDate: z.string().optional().describe("Date d'échéance/paiement attendu (YYYY-MM-DD)"),
954
1020
  amountExcludingTax: z.number().optional().describe("Montant HT"),
@@ -958,29 +1024,42 @@ export const InvoiceUpdateSchema = z
958
1024
  note: z.string().optional().describe("Notes"),
959
1025
  })
960
1026
  .strict();
1027
+ // Source: resources/invoices/search.raml (filters verified live, issue #248).
961
1028
  export const InvoiceSearchSchema = z
962
1029
  .object({
963
1030
  keywords: z.string().optional().describe("Mots-clés de recherche (référence, société...)"),
964
- companyId: z.string().optional().describe("Filtrer par ID société"),
965
- projectId: z.string().optional().describe("Filtrer par ID projet"),
966
- startDate: z.string().optional().describe("Date de début de période (YYYY-MM-DD)"),
967
- endDate: z.string().optional().describe("Date de fin de période (YYYY-MM-DD)"),
1031
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société (référence keywords CSOC<id>)"),
1032
+ projectId: EntityIdSchema.optional().describe("Filtrer par ID projet (référence keywords PRJ<id>)"),
1033
+ contactId: EntityIdSchema.optional().describe("Filtrer par ID contact (référence keywords CCON<id>)"),
1034
+ orderId: EntityIdSchema.optional().describe("Filtrer par ID bon de commande (référence keywords BDC<id>)"),
1035
+ states: intArray("IDs d'états de facture — `boond://dictionary/states/invoices` (ex. impayées = tous les états sauf « payée »)."),
1036
+ projectTypes: projectTypesFilterField,
1037
+ paymentMethods: paymentMethodsField,
1038
+ companies: companiesFilterField,
1039
+ closed: z
1040
+ .boolean()
1041
+ .optional()
1042
+ .describe("true = factures / avoirs clôturés uniquement, false = non clôturés uniquement."),
1043
+ creditNote: z.boolean().optional().describe("true = avoirs uniquement, false = factures uniquement."),
968
1044
  period: z
969
- .string()
1045
+ .enum(["created", "updated", "expectedPayment", "performedPayment", "period"])
970
1046
  .optional()
971
- .describe("Type de période (created, updated, expectedPayment, performedPayment, period)"),
972
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
973
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
974
- fields: fieldsField,
1047
+ .describe("Champ de date borné par `startDate` / `endDate` : created, updated, expectedPayment (échéance), performedPayment (règlement), period (période facturée, défaut)."),
1048
+ periodDynamic: periodDynamicField,
1049
+ startDate: startDateField,
1050
+ endDate: endDateField,
1051
+ flags: flagsField,
1052
+ ...perimeterShape,
1053
+ ...sortedPaginationShape,
975
1054
  })
976
1055
  .strict();
977
1056
  // ---- Order schemas (Bons de commande) ----
978
1057
  export const OrderCreateSchema = z
979
1058
  .object({
980
1059
  reference: z.string().optional().describe("Référence du bon de commande"),
981
- companyId: z.string().optional().describe("ID de la société"),
982
- projectId: z.string().optional().describe("ID du projet associé"),
983
- state: z.number().int().optional().describe("État du bon de commande"),
1060
+ companyId: EntityIdSchema.optional().describe("ID de la société"),
1061
+ projectId: EntityIdSchema.optional().describe("ID du projet associé"),
1062
+ state: stateField("order", "État du bon de commande : ID de `boond://dictionary/states/orders`"),
984
1063
  orderDate: z.string().optional().describe("Date du bon de commande (YYYY-MM-DD)"),
985
1064
  startDate: z.string().optional().describe("Date de début couverte (YYYY-MM-DD)"),
986
1065
  endDate: z.string().optional().describe("Date de fin couverte (YYYY-MM-DD)"),
@@ -992,9 +1071,9 @@ export const OrderCreateSchema = z
992
1071
  .strict();
993
1072
  export const OrderUpdateSchema = z
994
1073
  .object({
995
- id: z.string().min(1).describe("ID du bon de commande à modifier"),
1074
+ id: EntityIdSchema.describe("ID du bon de commande à modifier"),
996
1075
  reference: z.string().optional().describe("Référence"),
997
- state: z.number().int().optional().describe("État"),
1076
+ state: stateField("order", "État du bon de commande : ID de `boond://dictionary/states/orders`"),
998
1077
  orderDate: z.string().optional().describe("Date (YYYY-MM-DD)"),
999
1078
  startDate: z.string().optional().describe("Date de début couverte (YYYY-MM-DD)"),
1000
1079
  endDate: z.string().optional().describe("Date de fin couverte (YYYY-MM-DD)"),
@@ -1004,61 +1083,197 @@ export const OrderUpdateSchema = z
1004
1083
  note: z.string().optional().describe("Notes"),
1005
1084
  })
1006
1085
  .strict();
1086
+ // Source: resources/orders/search.raml (filters verified live, issue #248).
1007
1087
  export const OrderSearchSchema = z
1008
1088
  .object({
1009
1089
  keywords: z.string().optional().describe("Mots-clés de recherche"),
1010
- companyId: z.string().optional().describe("Filtrer par ID société"),
1011
- projectId: z.string().optional().describe("Filtrer par ID projet"),
1012
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1013
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
1014
- fields: fieldsField,
1090
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société (référence keywords CSOC<id>)"),
1091
+ projectId: EntityIdSchema.optional().describe("Filtrer par ID projet (référence keywords PRJ<id>)"),
1092
+ contactId: EntityIdSchema.optional().describe("Filtrer par ID contact (référence keywords CCON<id>)"),
1093
+ states: intArray("IDs d'états de bon de commande — `boond://dictionary/states/orders`."),
1094
+ projectTypes: projectTypesFilterField,
1095
+ paymentMethods: paymentMethodsField,
1096
+ companies: companiesFilterField,
1097
+ customerAgreement: z.boolean().optional().describe("true = accord client reçu uniquement, false = sans accord."),
1098
+ exceededOrderedTurnover: z
1099
+ .boolean()
1100
+ .optional()
1101
+ .describe("true = commandes dont le facturé dépasse le montant commandé."),
1102
+ period: z
1103
+ .enum(["created", "updated", "period"])
1104
+ .optional()
1105
+ .describe("Champ de date borné par `startDate` / `endDate` : created, updated, period (période couverte)."),
1106
+ periodDynamic: periodDynamicField,
1107
+ startDate: startDateField,
1108
+ endDate: endDateField,
1109
+ flags: flagsField,
1110
+ ...perimeterShape,
1111
+ ...sortedPaginationShape,
1015
1112
  })
1016
1113
  .strict();
1017
1114
  // ---- Delivery schemas (Livraisons / CRA) ----
1115
+ // Source: resources/deliveriesGroupments/search.raml — the list route of the
1116
+ // deliveries domain (filters verified live, issue #248).
1018
1117
  export const DeliverySearchSchema = z
1019
1118
  .object({
1020
1119
  keywords: z.string().optional().describe("Mots-clés de recherche"),
1021
- projectId: z.string().optional().describe("Filtrer par ID projet"),
1022
- companyId: z.string().optional().describe("Filtrer par ID société"),
1120
+ projectId: EntityIdSchema.optional().describe("Filtrer par ID projet (référence keywords PRJ<id>)"),
1121
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société (référence keywords CSOC<id>)"),
1122
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (référence keywords COMP<id>)"),
1123
+ opportunityId: EntityIdSchema.optional().describe("Filtrer par ID opportunité (référence keywords AO<id>)"),
1124
+ projectTypes: projectTypesFilterField,
1125
+ projectStates: intArray("IDs d'états de projet — `boond://dictionary/states/projects`."),
1126
+ deliveryStates: intArray("IDs d'états de prestation — `boond://dictionary/states/deliveries`."),
1127
+ expertiseAreas: strArray("Domaines d'expertise — `boond://dictionary/expertiseAreas`."),
1128
+ transferType: z
1129
+ .enum(["master", "slave", "none", "notSlave", "notMaster", "missing"])
1130
+ .optional()
1131
+ .describe("Filtre sur les transferts de prestation (master / slave / none / notSlave / notMaster / missing)."),
1132
+ period: z
1133
+ .enum(["started", "updated", "stopped", "projectRunning", "running", "hasAdditionalDataOrPurchase"])
1134
+ .optional()
1135
+ .describe("Champ de date borné par `startDate` / `endDate` : started, stopped, updated, running (prestations en cours sur la fenêtre), projectRunning (projet en cours)."),
1136
+ periodDynamic: periodDynamicField,
1137
+ startDate: startDateField,
1138
+ endDate: endDateField,
1139
+ companies: companiesFilterField,
1140
+ flags: flagsField,
1141
+ ...perimeterShape,
1142
+ ...sortedPaginationShape,
1143
+ })
1144
+ .strict();
1145
+ export const DeliveryCreateSchema = z
1146
+ .object({
1147
+ projectId: EntityIdSchema.describe("ID du projet"),
1148
+ resourceId: EntityIdSchema.describe("ID de la ressource portée par la prestation"),
1149
+ title: z.string().optional().describe("Titre de la prestation/livraison"),
1150
+ typeOf: z.number().int().optional().describe("Type de prestation : ID de `boond://dictionary/typeOf/deliveries`"),
1151
+ state: stateField("delivery", "État de la prestation : ID de `boond://dictionary/states/deliveries`"),
1023
1152
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1024
1153
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
1025
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1026
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
1027
- fields: fieldsField,
1154
+ quantity: z.number().optional().describe("Nombre de jours / quantité"),
1155
+ unitPrice: z.number().optional().describe("Prix journalier HT"),
1156
+ averageDailyCost: z.number().optional().describe("Coût journalier moyen"),
1157
+ forceAverageDailyPriceExcludingTax: z.boolean().optional().describe("Forcer le prix journalier HT"),
1158
+ note: z.string().optional().describe("Notes, mappées vers informationComments"),
1159
+ })
1160
+ .strict();
1161
+ // PUT /deliveries/{id} — attributes only, the project / resource attachment
1162
+ // is not moved by an update (issue #252; write path not exercised, see CLAUDE.md).
1163
+ export const DeliveryUpdateSchema = z
1164
+ .object({
1165
+ id: EntityIdSchema.describe("ID de la prestation à modifier"),
1166
+ title: z.string().optional().describe("Titre de la prestation"),
1167
+ typeOf: z.number().int().optional().describe("Type de prestation : ID de `boond://dictionary/typeOf/deliveries`"),
1168
+ state: stateField("delivery", "État de la prestation : ID de `boond://dictionary/states/deliveries`"),
1169
+ startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1170
+ endDate: z
1171
+ .string()
1172
+ .optional()
1173
+ .describe("Date de fin (YYYY-MM-DD) — prolonger une prestation = repousser cette date"),
1174
+ quantity: z.number().optional().describe("Nombre de jours / quantité (`numberOfDaysInvoicedOrQuantity`)"),
1175
+ unitPrice: z.number().optional().describe("Prix journalier HT (`averageDailyPriceExcludingTax`, forcé)"),
1176
+ averageDailyCost: z.number().optional().describe("Coût journalier moyen"),
1177
+ note: z.string().optional().describe("Notes, mappées vers informationComments"),
1028
1178
  })
1029
1179
  .strict();
1030
1180
  // ---- Absence schemas ----
1181
+ /** `GET /absences-reports/default?resource=&agency=` (issue #257) — the absence types are published there only. */
1182
+ export const AbsenceDefaultSchema = z
1183
+ .object({
1184
+ resourceId: EntityIdSchema.describe("ID de la ressource (requis par l'API)."),
1185
+ agencyId: EntityIdSchema.optional().describe("ID de l'agence — déduite de la ressource si omise."),
1186
+ })
1187
+ .strict();
1031
1188
  export const AbsenceCreateSchema = z
1032
1189
  .object({
1033
- resourceId: z.string().min(1).describe("ID de la ressource en absence"),
1034
- typeOf: z.string().min(1).describe("Libellé de l'absence (congé payé, RTT, maladie, sans solde...)"),
1190
+ resourceId: EntityIdSchema.describe("ID de la ressource en absence"),
1191
+ typeOf: z
1192
+ .string()
1193
+ .min(1)
1194
+ .describe("Libellé de l'absence (congé payé, RTT, maladie, sans solde...) — porté par `title` ; le type effectif est `workUnitTypeReference`."),
1035
1195
  startDate: z.string().min(1).describe("Date de début (YYYY-MM-DD)"),
1036
1196
  endDate: z.string().min(1).describe("Date de fin (YYYY-MM-DD)"),
1037
1197
  duration: z.number().optional().describe("Durée en jours ; calculée automatiquement si absente"),
1038
- workUnitTypeReference: z.number().int().min(1).optional().describe("Référence du type d'unité d'absence, défaut 1"),
1198
+ workUnitTypeReference: z
1199
+ .number()
1200
+ .int()
1201
+ .min(1)
1202
+ .optional()
1203
+ .describe("Code `reference` du type d'absence, publié par `boond_absences_default` (`workUnitTypesAllowed` de la ressource : RTT, maladie, congés payés…) — nulle part ailleurs. Défaut 1 (à confirmer avec le default)."),
1039
1204
  absencesPeriods: z.array(z.record(z.string(), z.unknown())).optional().describe("Périodes d'absence Boond brutes"),
1040
- state: z.number().int().optional().describe("État de la demande (0=en attente, 1=validé, 2=refusé...)"),
1205
+ // No `state` (issue #250): on `/absences-reports` it is a validation-workflow
1206
+ // string (`waitingForValidation`, `validated`…), not a dictionary integer,
1207
+ // and it is moved by the workflow — same finding as `/expenses-reports`.
1041
1208
  note: z.string().optional().describe("Commentaire / motif"),
1042
1209
  })
1043
1210
  .strict();
1044
1211
  export const AbsenceUpdateSchema = z
1045
1212
  .object({
1046
- id: z.string().min(1).describe("ID de l'absence à modifier"),
1213
+ id: EntityIdSchema.describe("ID de l'absence à modifier"),
1047
1214
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1048
1215
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
1049
- state: z.number().int().optional().describe("État de la demande"),
1050
1216
  note: z.string().optional().describe("Commentaire / motif"),
1051
1217
  })
1052
1218
  .strict();
1053
1219
  export const AbsenceSearchSchema = z
1054
1220
  .object({
1055
1221
  keywords: z.string().optional().describe("Mots-clés de recherche"),
1056
- resourceId: z.string().optional().describe("Filtrer par ID ressource"),
1057
- startMonth: z.string().optional().describe("Mois de début de période (YYYY-MM)"),
1058
- endMonth: z.string().optional().describe("Mois de fin de période (YYYY-MM)"),
1059
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1060
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
1061
- fields: fieldsField,
1222
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (référence keywords COMP<id>)"),
1223
+ // Both are REQUIRED by `GET /absences-reports` (422 `1017 - Missing required
1224
+ // attribute` otherwise — caught by scripts/smoke-live.mjs, issue #244).
1225
+ // Declaring them optional made the tool fail on its most natural call.
1226
+ startMonth: z
1227
+ .string()
1228
+ .regex(/^\d{4}-\d{2}$/, "Format attendu : YYYY-MM")
1229
+ .describe("Mois de début de période (YYYY-MM) — obligatoire pour l'API"),
1230
+ endMonth: z
1231
+ .string()
1232
+ .regex(/^\d{4}-\d{2}$/, "Format attendu : YYYY-MM")
1233
+ .describe("Mois de fin de période (YYYY-MM) — obligatoire pour l'API"),
1234
+ resourceTypes: resourceTypesField,
1235
+ validationStates: validationStatesField,
1236
+ ...perimeterShape,
1237
+ ...sortedPaginationShape,
1238
+ })
1239
+ .strict();
1240
+ // ---- Contract search (issue #253) ----
1241
+ // There is no collection GET on `/contracts` (WAF 403 page, the RAML only
1242
+ // documents `post`) and no `/resources/{id}/contracts` tab (404) — probed live
1243
+ // on 2026-09-26. A resource's contracts are only reachable through
1244
+ // `/resources/{id}/administrative` (`included` of type `contract`). This schema
1245
+ // therefore drives a *composed* search: the resource filters select the
1246
+ // resources to scan, the contract filters are applied server-side to what
1247
+ // their administrative tabs return.
1248
+ export const ContractSearchSchema = z
1249
+ .object({
1250
+ resourceId: EntityIdSchema.optional().describe("ID d'une ressource : ne lit que ses contrats (aucune recherche de ressources)."),
1251
+ keywords: z.string().optional().describe("Mots-clés de la recherche de ressources (nom, prénom…)."),
1252
+ resourceStates: intArray("IDs d'états de ressource à parcourir — `boond://dictionary/states/resources`."),
1253
+ resourceTypes: resourceTypesField,
1254
+ ...perimeterShape,
1255
+ contractTypes: intArray("IDs de types de contrat à retenir — `boond://dictionary/typeOf/contracts` (CDI, CDD…)."),
1256
+ period: z
1257
+ .enum(["running", "ending", "starting", "probationEnding"])
1258
+ .optional()
1259
+ .describe("Fenêtre `startDate` / `endDate` appliquée côté serveur : running (contrat en cours sur la fenêtre), ending (fin de contrat dans la fenêtre), starting (début dans la fenêtre), probationEnding (fin de période d'essai — `probationEndDate` ou `renewalProbationEndDate` — dans la fenêtre)."),
1260
+ startDate: startDateField,
1261
+ endDate: endDateField,
1262
+ ...paginationShape,
1263
+ })
1264
+ .strict();
1265
+ // ---- Validation decision (issue #251) ----
1266
+ // `PUT /validations/{id}` with the workflow state the RAML enumerates on
1267
+ // `validationStates` (`validated` | `rejected`). No RAML documents the write;
1268
+ // the route follows the pattern of every other single-entity PUT and has NOT
1269
+ // been exercised (production tenant) — see CLAUDE.md → *Validations*.
1270
+ export const ValidationDecisionSchema = z
1271
+ .object({
1272
+ id: EntityIdSchema.describe("ID de la validation (ligne de `boond_validations_search`), pas celui du CRA / de la note / de l'absence."),
1273
+ decision: z
1274
+ .enum(["validate", "reject"])
1275
+ .describe("validate → `validated` ; reject → `rejected` (confirmation demandée à l'utilisateur si le client la supporte)."),
1276
+ reason: z.string().optional().describe("Motif, recommandé sur un refus — transmis dans `reason`."),
1062
1277
  })
1063
1278
  .strict();
1064
1279
  // ---- Expense schemas (Notes de frais) ----
@@ -1128,15 +1343,9 @@ export const ExpenseLineSchema = z
1128
1343
  .describe("`true` pour un frais kilométrique : renseigner `numberOfKilometers` et omettre `expenseTypeReference`."),
1129
1344
  numberOfKilometers: z.number().optional().describe("Nombre de kilomètres (frais kilométrique uniquement)."),
1130
1345
  activityType: z.enum(EXPENSE_ACTIVITY_TYPES).default("production").describe("Type d'activité rattachée au frais."),
1131
- projectId: z
1132
- .string()
1133
- .min(1)
1134
- .describe("ID du projet à imputer. Obligatoire — les couples (projet, prestation) autorisés sont donnés par `boond_expenses_default`."),
1135
- deliveryId: z
1136
- .string()
1137
- .min(1)
1138
- .describe("ID de la prestation (delivery) à imputer. Obligatoire — voir `boond_expenses_default`."),
1139
- batchId: z.string().optional().describe("ID du lot. Absent = aucun lot."),
1346
+ projectId: EntityIdSchema.describe("ID du projet à imputer. Obligatoire — les couples (projet, prestation) autorisés sont donnés par `boond_expenses_default`."),
1347
+ deliveryId: EntityIdSchema.describe("ID de la prestation (delivery) à imputer. Obligatoire — voir `boond_expenses_default`."),
1348
+ batchId: EntityIdSchema.optional().describe("ID du lot. Absent = aucun lot."),
1140
1349
  reinvoiced: z.boolean().default(false).describe("Frais refacturable au client."),
1141
1350
  currency: z.number().int().default(0).describe("ID de devise (`setting.currency`, 0 = EUR)."),
1142
1351
  exchangeRate: z.number().default(1).describe("Taux de change vers la devise agence."),
@@ -1150,11 +1359,8 @@ export const ExpenseLineSchema = z
1150
1359
  .strict();
1151
1360
  export const ExpenseCreateSchema = z
1152
1361
  .object({
1153
- resourceId: z.string().min(1).describe("ID de la ressource (le collaborateur qui a engagé les frais)."),
1154
- agencyId: z
1155
- .string()
1156
- .optional()
1157
- .describe("ID de l'agence — voir `boond_expenses_default`. Déduit de la ressource si omis."),
1362
+ resourceId: EntityIdSchema.describe("ID de la ressource (le collaborateur qui a engagé les frais)."),
1363
+ agencyId: EntityIdSchema.optional().describe("ID de l'agence — voir `boond_expenses_default`. Déduit de la ressource si omis."),
1158
1364
  term: z
1159
1365
  .string()
1160
1366
  .regex(/^\d{4}-\d{2}$/)
@@ -1176,7 +1382,7 @@ export const ExpenseCreateSchema = z
1176
1382
  .strict();
1177
1383
  export const ExpenseUpdateSchema = z
1178
1384
  .object({
1179
- id: z.string().min(1).describe("ID de la note de frais à modifier"),
1385
+ id: EntityIdSchema.describe("ID de la note de frais à modifier"),
1180
1386
  exchangeRateAgency: z.number().optional().describe("Taux de change agence"),
1181
1387
  currencyAgency: z.number().int().optional().describe("ID de devise de l'agence"),
1182
1388
  informationComments: z.string().optional().describe("Commentaires"),
@@ -1192,24 +1398,22 @@ export const ExpenseUpdateSchema = z
1192
1398
  .strict();
1193
1399
  export const ExpenseDefaultSchema = z
1194
1400
  .object({
1195
- resourceId: z.string().min(1).describe("ID de la ressource"),
1401
+ resourceId: EntityIdSchema.describe("ID de la ressource"),
1196
1402
  term: z
1197
1403
  .string()
1198
1404
  .regex(/^\d{4}-\d{2}$/)
1199
1405
  .describe("Mois ciblé (YYYY-MM)"),
1200
- agencyId: z.string().optional().describe("ID de l'agence (optionnel — déduit de la ressource)"),
1406
+ agencyId: EntityIdSchema.optional().describe("ID de l'agence (optionnel — déduit de la ressource)"),
1201
1407
  })
1202
1408
  .strict();
1203
1409
  export const ExpenseSearchSchema = z
1204
1410
  .object({
1205
1411
  keywords: z.string().optional().describe("Mots-clés de recherche"),
1206
- resourceId: z.string().optional().describe("Filtrer par ID ressource"),
1207
- projectId: z.string().optional().describe("Filtrer par ID projet"),
1412
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (référence keywords COMP<id>)"),
1413
+ projectId: EntityIdSchema.optional().describe("Filtrer par ID projet (référence keywords PRJ<id>)"),
1208
1414
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1209
1415
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
1210
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1211
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
1212
- fields: fieldsField,
1416
+ ...paginationShape,
1213
1417
  })
1214
1418
  .strict();
1215
1419
  // ---- Product schemas ----
@@ -1225,7 +1429,7 @@ export const ProductCreateSchema = z
1225
1429
  .strict();
1226
1430
  export const ProductUpdateSchema = z
1227
1431
  .object({
1228
- id: z.string().min(1).describe("ID du produit à modifier"),
1432
+ id: EntityIdSchema.describe("ID du produit à modifier"),
1229
1433
  name: z.string().optional().describe("Nom du produit"),
1230
1434
  reference: z.string().optional().describe("Référence"),
1231
1435
  unitPrice: z.number().optional().describe("Prix unitaire HT"),
@@ -1237,11 +1441,11 @@ export const ProductUpdateSchema = z
1237
1441
  // ---- Positioning schemas ----
1238
1442
  export const PositioningCreateSchema = z
1239
1443
  .object({
1240
- candidateId: z.string().optional().describe("ID du candidat positionné"),
1241
- resourceId: z.string().optional().describe("ID de la ressource positionnée"),
1242
- projectId: z.string().optional().describe("ID du projet"),
1243
- opportunityId: z.string().optional().describe("ID de l'opportunité"),
1244
- state: z.number().int().optional().describe("État du positionnement"),
1444
+ candidateId: EntityIdSchema.optional().describe("ID du candidat positionné"),
1445
+ resourceId: EntityIdSchema.optional().describe("ID de la ressource positionnée"),
1446
+ projectId: EntityIdSchema.optional().describe("ID du projet"),
1447
+ opportunityId: EntityIdSchema.optional().describe("ID de l'opportunité"),
1448
+ state: stateField("positioning", "État du positionnement : ID de `boond://dictionary/states/positionings` (`won` rattache le positionnement à un projet)"),
1245
1449
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1246
1450
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
1247
1451
  note: z.string().optional().describe("Notes / commentaires"),
@@ -1253,61 +1457,403 @@ export const PositioningSearchSchema = z
1253
1457
  .string()
1254
1458
  .optional()
1255
1459
  .describe("Mots-clés de recherche. L'API y accepte aussi des références d'entités (AO<id>, CAND<id>, COMP<id>...) — les filtres *Id ci-dessous sont convertis automatiquement en de telles références."),
1256
- candidateId: z
1257
- .string()
1258
- .optional()
1259
- .describe("Filtrer par ID candidat (envoyé à l'API comme référence keywords CAND<id>)"),
1260
- resourceId: z
1261
- .string()
1262
- .optional()
1263
- .describe("Filtrer par ID ressource (envoyé à l'API comme référence keywords COMP<id>)"),
1264
- opportunityId: z
1265
- .string()
1266
- .optional()
1267
- .describe("Filtrer par ID opportunité (envoyé à l'API comme référence keywords AO<id>)"),
1268
- companyId: z
1269
- .string()
1460
+ candidateId: EntityIdSchema.optional().describe("Filtrer par ID candidat (envoyé à l'API comme référence keywords CAND<id>)"),
1461
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (envoyé à l'API comme référence keywords COMP<id>)"),
1462
+ opportunityId: EntityIdSchema.optional().describe("Filtrer par ID opportunité (envoyé à l'API comme référence keywords AO<id>)"),
1463
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société (envoyé à l'API comme référence keywords CSOC<id>)"),
1464
+ contactId: EntityIdSchema.optional().describe("Filtrer par ID contact (envoyé à l'API comme référence keywords CCON<id>)"),
1465
+ productId: EntityIdSchema.optional().describe("Filtrer par ID produit (envoyé à l'API comme référence keywords PROD<id>)"),
1466
+ ...paginationShape,
1467
+ })
1468
+ .strict();
1469
+ // ---- Payment schemas ----
1470
+ // Source: resources/payments/search.raml (filters verified live, issue #248).
1471
+ export const PaymentSearchSchema = z
1472
+ .object({
1473
+ keywords: z.string().optional().describe("Mots-clés de recherche"),
1474
+ purchaseId: EntityIdSchema.optional().describe("Filtrer par ID achat (référence keywords ACH<id>)"),
1475
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société (référence keywords CSOC<id>)"),
1476
+ projectId: EntityIdSchema.optional().describe("Filtrer par ID projet (référence keywords PRJ<id>)"),
1477
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (référence keywords COMP<id>)"),
1478
+ contactId: EntityIdSchema.optional().describe("Filtrer par ID contact (référence keywords CCON<id>)"),
1479
+ paymentStates: intArray("IDs d'états de paiement — `boond://dictionary/states/payments`."),
1480
+ paymentMethods: paymentMethodsField,
1481
+ purchaseTypes: intArray("IDs de types d'achat — `boond://dictionary/typeOf/purchases`."),
1482
+ subscriptionTypes: intArray("IDs de types d'abonnement (`setting.typeOf.subscription` via `boond_application_dictionary`)."),
1483
+ period: z
1484
+ .enum(["created", "updated", "createdPurchase", "subscription", "expected", "performed", "billing"])
1270
1485
  .optional()
1271
- .describe("Filtrer par ID société (envoyé à l'API comme référence keywords CSOC<id>)"),
1272
- contactId: z
1273
- .string()
1486
+ .describe("Champ de date borné par `startDate` / `endDate` : expected (échéance), performed (règlement effectif), created, updated, createdPurchase, subscription, billing."),
1487
+ periodDynamic: periodDynamicField,
1488
+ startDate: startDateField,
1489
+ endDate: endDateField,
1490
+ excludeProviderInvoice: z
1491
+ .boolean()
1274
1492
  .optional()
1275
- .describe("Filtrer par ID contact (envoyé à l'API comme référence keywords CCON<id>)"),
1276
- productId: z
1277
- .string()
1493
+ .describe("true = exclure les paiements adossés à une facture fournisseur."),
1494
+ deliveryPurchases: z.boolean().optional().describe("true = paiements des achats liés à une prestation uniquement."),
1495
+ flags: flagsField,
1496
+ ...perimeterShape,
1497
+ ...sortedPaginationShape,
1498
+ })
1499
+ .strict();
1500
+ export const PaymentCreateSchema = z
1501
+ .object({
1502
+ purchaseId: EntityIdSchema.describe("ID de l'achat réglé"),
1503
+ paymentDate: z.string().optional().describe("Date du paiement (YYYY-MM-DD), mappée vers date"),
1504
+ performedDate: z.string().optional().describe("Date de paiement effectif (YYYY-MM-DD)"),
1505
+ expectedDate: z.string().optional().describe("Date de paiement attendu (YYYY-MM-DD)"),
1506
+ startDate: z.string().optional().describe("Date de début couverte (YYYY-MM-DD)"),
1507
+ endDate: z.string().optional().describe("Date de fin couverte (YYYY-MM-DD)"),
1508
+ amount: z.number().optional().describe("Montant HT du paiement, mappé vers amountExcludingTax"),
1509
+ amountExcludingTax: z.number().optional().describe("Montant HT du paiement"),
1510
+ state: stateField("payment", "État du paiement : ID de `boond://dictionary/states/payments`"),
1511
+ paymentMethod: z.number().int().optional().describe("Mode de paiement : ID de `boond://dictionary/paymentMethods`"),
1512
+ taxRates: z.array(z.number()).optional().describe("Taux de taxes Boond"),
1513
+ reference: z.string().optional().describe("Référence bancaire ou règlement"),
1514
+ note: z.string().optional().describe("Note interne, mappée vers informationComments"),
1515
+ })
1516
+ .strict();
1517
+ // PUT /payments/{id} — issue #252 (write path not exercised, see CLAUDE.md).
1518
+ export const PaymentUpdateSchema = z
1519
+ .object({
1520
+ id: EntityIdSchema.describe("ID du paiement à modifier"),
1521
+ paymentDate: z.string().optional().describe("Date du paiement (YYYY-MM-DD), mappée vers date"),
1522
+ performedDate: z.string().optional().describe("Date de paiement effectif (YYYY-MM-DD) — régulariser un règlement"),
1523
+ expectedDate: z.string().optional().describe("Date de paiement attendu (YYYY-MM-DD)"),
1524
+ startDate: z.string().optional().describe("Date de début couverte (YYYY-MM-DD)"),
1525
+ endDate: z.string().optional().describe("Date de fin couverte (YYYY-MM-DD)"),
1526
+ amount: z.number().optional().describe("Montant HT, mappé vers amountExcludingTax"),
1527
+ amountExcludingTax: z.number().optional().describe("Montant HT"),
1528
+ amountIncludingTax: z.number().optional().describe("Montant TTC"),
1529
+ state: stateField("payment", "État du paiement : ID de `boond://dictionary/states/payments`"),
1530
+ paymentMethod: z.number().int().optional().describe("Mode de paiement : ID de `boond://dictionary/paymentMethods`"),
1531
+ taxRates: z.array(z.number()).optional().describe("Taux de taxes Boond"),
1532
+ reference: z.string().optional().describe("Référence bancaire ou règlement (`number`)"),
1533
+ note: z.string().optional().describe("Note interne, mappée vers informationComments"),
1534
+ })
1535
+ .strict();
1536
+ // ---- Purchase schemas (achats / sous-traitance) ----
1537
+ export const PurchaseSearchSchema = z
1538
+ .object({
1539
+ keywords: z.string().optional().describe("Mots-clés de recherche"),
1540
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société (référence keywords CSOC<id>)"),
1541
+ projectId: EntityIdSchema.optional().describe("Filtrer par ID projet (référence keywords PRJ<id>)"),
1542
+ ...paginationShape,
1543
+ })
1544
+ .strict();
1545
+ export const PurchaseCreateSchema = z
1546
+ .object({
1547
+ title: z.string().optional().describe("Titre de l'achat/sous-traitance"),
1548
+ companyId: EntityIdSchema.optional().describe("ID de la société fournisseur"),
1549
+ contactId: EntityIdSchema.optional().describe("ID du contact fournisseur"),
1550
+ projectId: EntityIdSchema.optional().describe("ID du projet associé"),
1551
+ state: stateField("purchase", "État de l'achat : ID de `boond://dictionary/states/purchases`"),
1552
+ startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1553
+ endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
1554
+ note: z.string().optional().describe("Notes / commentaires"),
1555
+ })
1556
+ .strict();
1557
+ // PUT /purchases/{id}/information (RAML `purchases/information.raml`, issue #252).
1558
+ export const PurchaseUpdateSchema = z
1559
+ .object({
1560
+ id: EntityIdSchema.describe("ID de l'achat à modifier"),
1561
+ title: z.string().optional().describe("Titre de l'achat/sous-traitance"),
1562
+ typeOf: z.number().int().optional().describe("Type d'achat : ID de `boond://dictionary/typeOf/purchases`"),
1563
+ state: stateField("purchase", "État de l'achat : ID de `boond://dictionary/states/purchases`"),
1564
+ companyId: EntityIdSchema.optional().describe("ID de la société fournisseur"),
1565
+ contactId: EntityIdSchema.optional().describe("ID du contact fournisseur"),
1566
+ projectId: EntityIdSchema.optional().describe("ID du projet associé"),
1567
+ startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1568
+ endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
1569
+ quantity: z.number().optional().describe("Quantité"),
1570
+ amountExcludingTax: z.number().optional().describe("Montant unitaire HT"),
1571
+ taxRate: z.number().optional().describe("Taux de TVA (%) — `boond://dictionary/taxRates`"),
1572
+ paymentMethod: z.number().int().optional().describe("Mode de paiement : ID de `boond://dictionary/paymentMethods`"),
1573
+ paymentTerm: z
1574
+ .number()
1575
+ .int()
1278
1576
  .optional()
1279
- .describe("Filtrer par ID produit (envoyé à l'API comme référence keywords PROD<id>)"),
1280
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1281
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
1282
- fields: fieldsField,
1577
+ .describe("Condition de paiement : ID de `boond://dictionary/paymentTerms`"),
1578
+ note: z.string().optional().describe("Notes / commentaires (`informationComments`)"),
1283
1579
  })
1284
1580
  .strict();
1285
- // ---- Payment schemas ----
1286
- export const PaymentSearchSchema = z
1581
+ // ---- Provider invoice schemas (factures fournisseur) ----
1582
+ export const ProviderInvoiceSearchSchema = z
1287
1583
  .object({
1288
1584
  keywords: z.string().optional().describe("Mots-clés de recherche"),
1289
- invoiceId: z.string().optional().describe("Filtrer par ID facture"),
1290
- purchaseId: z.string().optional().describe("Filtrer par ID achat"),
1291
- companyId: z.string().optional().describe("Filtrer par ID société"),
1292
- projectId: z.string().optional().describe("Filtrer par ID projet"),
1293
- resourceId: z.string().optional().describe("Filtrer par ID ressource"),
1585
+ companyId: EntityIdSchema.optional().describe("Filtrer par ID société fournisseur (référence keywords CSOC<id>)"),
1586
+ resourceId: EntityIdSchema.optional().describe("Filtrer par ID ressource (référence keywords COMP<id>)"),
1587
+ ...paginationShape,
1588
+ })
1589
+ .strict();
1590
+ export const ProviderInvoiceCreateSchema = z
1591
+ .object({
1592
+ reference: z.string().min(1).describe("Référence de la facture fournisseur"),
1593
+ resourceId: EntityIdSchema.describe("ID de la ressource portée par la facture fournisseur"),
1594
+ companyId: EntityIdSchema.optional().describe("ID de la société fournisseur, mappé vers providerCompany"),
1595
+ contactId: EntityIdSchema.optional().describe("ID du contact fournisseur, mappé vers providerContact"),
1596
+ invoiceDate: z.string().optional().describe("Date de facture (YYYY-MM-DD)"),
1597
+ startDate: z.string().min(1).describe("Date de début de période (YYYY-MM-DD)"),
1598
+ endDate: z.string().min(1).describe("Date de fin de période (YYYY-MM-DD)"),
1599
+ amountExcludingTax: z.number().optional().describe("Montant HT"),
1600
+ amountIncludingTax: z.number().optional().describe("Montant TTC"),
1601
+ currency: z.number().optional().describe("Devise Boond"),
1602
+ exchangeRate: z.number().optional().describe("Taux de change"),
1603
+ currencyAgency: z.number().optional().describe("Devise agence"),
1604
+ exchangeRateAgency: z.number().optional().describe("Taux de change agence"),
1605
+ state: stateField("providerinvoice", "État de la facture fournisseur : ID de `boond://dictionary/states/provider-invoices`"),
1606
+ })
1607
+ .strict();
1608
+ // PUT /provider-invoices/{id} — issue #252 (write path not exercised, see CLAUDE.md).
1609
+ export const ProviderInvoiceUpdateSchema = z
1610
+ .object({
1611
+ id: EntityIdSchema.describe("ID de la facture fournisseur à modifier"),
1612
+ reference: z.string().optional().describe("Référence de la facture fournisseur"),
1613
+ invoiceDate: z.string().optional().describe("Date de facture (YYYY-MM-DD)"),
1614
+ startDate: z.string().optional().describe("Date de début de période (YYYY-MM-DD)"),
1615
+ endDate: z.string().optional().describe("Date de fin de période (YYYY-MM-DD)"),
1616
+ dueDate: z.string().optional().describe("Date d'échéance (YYYY-MM-DD)"),
1617
+ paidDate: z.string().optional().describe("Date de règlement (YYYY-MM-DD) — marquer la facture payée"),
1618
+ amountExcludingTax: z.number().optional().describe("Montant HT"),
1619
+ amountIncludingTax: z.number().optional().describe("Montant TTC"),
1620
+ state: stateField("providerinvoice", "État de la facture fournisseur : ID de `boond://dictionary/states/provider-invoices`"),
1621
+ })
1622
+ .strict();
1623
+ // ---- Contract schemas ----
1624
+ export const ContractCreateSchema = z
1625
+ .object({
1626
+ resourceId: EntityIdSchema.optional().describe("ID de la ressource associée"),
1627
+ typeOf: z
1628
+ .number()
1629
+ .int()
1630
+ .optional()
1631
+ .describe("Type de contrat : ID entier du dictionnaire `setting.typeOf.contract` (CDI, CDD, freelance…), via `boond_application_dictionary`"),
1294
1632
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1295
1633
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
1296
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1297
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
1298
- fields: fieldsField,
1634
+ note: z.string().optional().describe("Notes / commentaires (`informationComments`)"),
1635
+ })
1636
+ .strict();
1637
+ // PUT /contracts/{id} — issue #252 (write path not exercised, see CLAUDE.md).
1638
+ export const ContractUpdateSchema = z
1639
+ .object({
1640
+ id: EntityIdSchema.describe("ID du contrat à modifier"),
1641
+ typeOf: z.number().int().optional().describe("Type de contrat : ID de `boond://dictionary/typeOf/contracts`"),
1642
+ startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
1643
+ endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD) — prolonger un CDD = repousser cette date"),
1644
+ endReason: z.number().int().optional().describe("Motif de fin : ID de `boond://dictionary/contractEndReasons`"),
1645
+ probationEndDate: z.string().optional().describe("Fin de période d'essai initiale (YYYY-MM-DD)"),
1646
+ renewalProbationEndDate: z.string().optional().describe("Fin de période d'essai renouvelée (YYYY-MM-DD)"),
1647
+ monthlySalary: z.number().optional().describe("Salaire mensuel"),
1648
+ annualSalary: z.number().optional().describe("Salaire annuel"),
1649
+ numberOfWorkingDays: z.number().optional().describe("Nombre de jours ouvrés annuel"),
1650
+ note: z.string().optional().describe("Notes / commentaires (`informationComments`)"),
1299
1651
  })
1300
1652
  .strict();
1301
1653
  // ---- Advantage schemas ----
1654
+ // `GET /advantages` does not exist (the RAML only has POST there, and the live
1655
+ // API answers with a WAF 403 page); advantages are listed per resource via
1656
+ // `GET /resources/{id}/advantages` (issue #247). Hence a required `resourceId`.
1302
1657
  export const AdvantageSearchSchema = z
1303
1658
  .object({
1304
- keywords: z.string().optional().describe("Mots-clés de recherche"),
1305
- resourceId: z.string().optional().describe("Filtrer par ID ressource"),
1306
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1307
- pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
1308
- fields: fieldsField,
1659
+ resourceId: EntityIdSchema.describe("ID de la ressource dont on liste les avantages (route /resources/{id}/advantages)"),
1660
+ advantageTypes: strArray("Types d'avantage à conserver, au format `<reference>_<agencyId>` (référence du type + ID d'agence, ex: '2_1')."),
1661
+ ...paginationShape,
1309
1662
  })
1310
1663
  .strict();
1664
+ // ---- Flags, attached flags, tasks, todolists, advantages writes (issue #254) ----
1665
+ /** Entities that carry `attachedFlags.raml` (11) — the query-parameter name of `DELETE /attached-flags` is the entity's singular. */
1666
+ export const ATTACHED_FLAG_ENTITIES = [
1667
+ "candidate",
1668
+ "resource",
1669
+ "contact",
1670
+ "company",
1671
+ "opportunity",
1672
+ "project",
1673
+ "order",
1674
+ "product",
1675
+ "purchase",
1676
+ "action",
1677
+ "positioning",
1678
+ "invoice",
1679
+ ];
1680
+ /** Entities that carry `tasks.raml` (14). */
1681
+ export const TASK_ENTITIES = [
1682
+ "candidate",
1683
+ "resource",
1684
+ "contact",
1685
+ "company",
1686
+ "opportunity",
1687
+ "project",
1688
+ "order",
1689
+ "product",
1690
+ "purchase",
1691
+ "positioning",
1692
+ "invoice",
1693
+ "contract",
1694
+ "delivery",
1695
+ "payment",
1696
+ "form",
1697
+ ];
1698
+ export const FlagCreateSchema = z
1699
+ .object({
1700
+ name: z.string().min(1).describe("Libellé du drapeau (ex. « vivier Java Q4 »)."),
1701
+ mainManagerId: EntityIdSchema.optional().describe("ID de la ressource responsable du drapeau — l'utilisateur courant si omis."),
1702
+ })
1703
+ .strict();
1704
+ export const AttachedFlagsListSchema = z
1705
+ .object({
1706
+ entity: z.enum(ATTACHED_FLAG_ENTITIES).describe("Type de l'enregistrement."),
1707
+ id: EntityIdSchema.describe("ID numérique de l'enregistrement."),
1708
+ })
1709
+ .strict();
1710
+ export const AttachedFlagSchema = AttachedFlagsListSchema.extend({
1711
+ flagId: EntityIdSchema.describe("ID du drapeau (`boond_flags_search`)."),
1712
+ }).strict();
1713
+ export const TasksGetSchema = z
1714
+ .object({
1715
+ entity: z.enum(TASK_ENTITIES).describe("Type de l'enregistrement."),
1716
+ id: EntityIdSchema.describe("ID numérique de l'enregistrement."),
1717
+ })
1718
+ .strict();
1719
+ const TodolistTaskSchema = z
1720
+ .object({
1721
+ description: z.string().min(1).describe("Intitulé de la tâche."),
1722
+ row: z.number().int().min(0).optional().describe("Ordre d'affichage."),
1723
+ state: z.number().int().optional().describe("État (0 = à faire par défaut)."),
1724
+ })
1725
+ .strict();
1726
+ // POST /todolists from `models.todolist` (title, state, profile, profileTypesOf,
1727
+ // profileStates, agencies, tasks) — the RAML documents no body; not exercised.
1728
+ export const TodolistCreateSchema = z
1729
+ .object({
1730
+ title: z.string().min(1).describe("Titre de la todolist."),
1731
+ profile: z
1732
+ .string()
1733
+ .optional()
1734
+ .describe("Fiche sur laquelle la liste s'applique (ex. candidate, resource, opportunity…)."),
1735
+ profileTypesOf: z.array(z.number().int()).optional().describe("Types de fiche concernés (IDs de dictionnaire)."),
1736
+ profileStates: z.array(z.number().int()).optional().describe("États de fiche concernés (IDs de dictionnaire)."),
1737
+ agencyIds: z.array(EntityIdSchema).optional().describe("Agences concernées (IDs)."),
1738
+ tasks: z.array(TodolistTaskSchema).min(1).describe("Tâches de la liste, dans l'ordre."),
1739
+ })
1740
+ .strict();
1741
+ /** `GET /advantages/default?resource=&contract=&project=&delivery=` (RAML `advantages/default.raml`). */
1742
+ export const AdvantageDefaultSchema = z
1743
+ .object({
1744
+ resourceId: EntityIdSchema.describe("ID de la ressource bénéficiaire."),
1745
+ contractId: EntityIdSchema.optional().describe("ID du contrat de rattachement."),
1746
+ projectId: EntityIdSchema.optional().describe("ID du projet (avantage refacturable)."),
1747
+ deliveryId: EntityIdSchema.optional().describe("ID de la prestation."),
1748
+ })
1749
+ .strict();
1750
+ // POST /advantages from `models.advantage`; the RAML documents no body — not exercised.
1751
+ export const AdvantageCreateSchema = z
1752
+ .object({
1753
+ resourceId: EntityIdSchema.describe("ID de la ressource bénéficiaire (relation `resource`, requise)."),
1754
+ advantageType: z
1755
+ .union([z.number().int(), z.string().min(1)])
1756
+ .describe("Type d'avantage : `reference` ou `<reference>_<agencyId>` tel que publié par `boond_advantages_default` (`advantageTypes`)."),
1757
+ date: z
1758
+ .string()
1759
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
1760
+ .describe("Date de l'avantage (YYYY-MM-DD)."),
1761
+ returnDate: z
1762
+ .string()
1763
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
1764
+ .optional()
1765
+ .describe("Date de restitution (véhicule, matériel)."),
1766
+ quantity: z.number().optional().describe("Quantité (ex. nombre de tickets)."),
1767
+ participationAmount: z.number().optional().describe("Participation salarié."),
1768
+ employeeAmount: z.number().optional().describe("Montant versé au salarié."),
1769
+ agencyAmount: z.number().optional().describe("Charges / coefficient de charge côté agence."),
1770
+ contractId: EntityIdSchema.optional().describe("ID du contrat de rattachement."),
1771
+ projectId: EntityIdSchema.optional().describe("ID du projet à refacturer."),
1772
+ deliveryId: EntityIdSchema.optional().describe("ID de la prestation."),
1773
+ note: z.string().optional().describe("Commentaires (`informationComments`)."),
1774
+ })
1775
+ .strict();
1776
+ // ---- Inactivities, forms, groupments (issue #256) ----
1777
+ // All three collections document `post` only (no list GET), plus `default`
1778
+ // and `rights`; bodies come from `models.*` in the dictionary and were not
1779
+ // exercised (production tenant). Detail reads follow the `/{collection}/{id}`
1780
+ // pattern of every other entity.
1781
+ const isoDate = (doc) => z
1782
+ .string()
1783
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
1784
+ .describe(doc);
1785
+ export const InactivityDefaultSchema = z
1786
+ .object({
1787
+ resourceId: EntityIdSchema.describe("ID de la ressource (requis par `GET /inactivities/default`)."),
1788
+ })
1789
+ .strict();
1790
+ export const InactivityCreateSchema = z
1791
+ .object({
1792
+ resourceId: EntityIdSchema.describe("ID de la ressource en intercontrat / inactivité (relation `resource`, requise)."),
1793
+ title: z.string().optional().describe("Intitulé (ex. « Intercontrat », « Formation interne »)."),
1794
+ inactivityType: z
1795
+ .union([z.number().int(), z.string().min(1)])
1796
+ .optional()
1797
+ .describe("Type d'inactivité tel que publié par `boond_inactivities_default`."),
1798
+ startDate: isoDate("Date de début (YYYY-MM-DD)."),
1799
+ endDate: isoDate("Date de fin (YYYY-MM-DD)."),
1800
+ numberOfDaysInvoicedOrQuantity: z.number().optional().describe("Nombre de jours de la période."),
1801
+ averageDailyCost: z.number().optional().describe("Coût journalier moyen retenu."),
1802
+ contractId: EntityIdSchema.optional().describe("ID du contrat de rattachement."),
1803
+ note: z.string().optional().describe("Commentaires (`informationComments`)."),
1804
+ })
1805
+ .strict();
1806
+ export const FormDefaultSchema = z
1807
+ .object({
1808
+ templateId: EntityIdSchema.describe("ID du modèle de formulaire (`template`)."),
1809
+ resourceId: EntityIdSchema.describe("ID de l'entité visée par le formulaire (`resource`)."),
1810
+ })
1811
+ .strict();
1812
+ export const FormCreateSchema = z
1813
+ .object({
1814
+ templateId: EntityIdSchema.describe("ID du modèle de formulaire (relation `template`, requise)."),
1815
+ resourceId: EntityIdSchema.optional().describe("Ressource visée (relation `dependsOn`, type resource)."),
1816
+ candidateId: EntityIdSchema.optional().describe("Candidat visé (relation `dependsOn`, type candidate)."),
1817
+ validatorId: EntityIdSchema.optional().describe("Ressource qui validera le formulaire (`validator`)."),
1818
+ recipientId: EntityIdSchema.optional().describe("Ressource destinataire (`recipient`)."),
1819
+ validateDate: isoDate("Date de validation souhaitée (YYYY-MM-DD).").optional(),
1820
+ remindDate: isoDate("Date de rappel (YYYY-MM-DD).").optional(),
1821
+ })
1822
+ .strict();
1823
+ export const GroupmentDefaultSchema = z
1824
+ .object({
1825
+ projectId: EntityIdSchema.describe("ID du projet (requis par `GET /groupments/default`)."),
1826
+ })
1827
+ .strict();
1828
+ const groupmentWritableShape = {
1829
+ title: z.string().optional().describe("Titre du regroupement."),
1830
+ startDate: isoDate("Date de début (YYYY-MM-DD).").optional(),
1831
+ endDate: isoDate("Date de fin (YYYY-MM-DD).").optional(),
1832
+ averageDailyPriceExcludingTax: z.number().optional().describe("Prix journalier HT du regroupement."),
1833
+ forceAverageDailyPriceExcludingTax: z.boolean().optional().describe("Forcer le prix journalier."),
1834
+ averageDailyCost: z.number().optional().describe("Coût journalier moyen."),
1835
+ numberOfDaysInvoicedOrQuantity: z.number().optional().describe("Jours / quantité facturés."),
1836
+ numberOfDaysFree: z.number().optional().describe("Jours offerts."),
1837
+ deliveryIds: z.array(EntityIdSchema).optional().describe("Prestations regroupées (relation `deliveries`)."),
1838
+ note: z.string().optional().describe("Commentaires (`informationComments`)."),
1839
+ };
1840
+ export const GroupmentCreateSchema = z
1841
+ .object({
1842
+ projectId: EntityIdSchema.describe("ID du projet (relation `project`, requise)."),
1843
+ ...groupmentWritableShape,
1844
+ })
1845
+ .strict();
1846
+ export const GroupmentUpdateSchema = z
1847
+ .object({
1848
+ id: EntityIdSchema.describe("ID du regroupement à modifier."),
1849
+ ...groupmentWritableShape,
1850
+ })
1851
+ .strict();
1852
+ // ---- Alerts (issue #255) ----
1853
+ // `GET /alerts` ("Search alerts for user dashboard") documents no parameter at
1854
+ // all — not even the searchable / paginable traits — so the tool takes only
1855
+ // the client-side projection.
1856
+ export const AlertSearchSchema = z.object({ fields: fieldsField }).strict();
1311
1857
  // ---- Application schemas ----
1312
1858
  // ---- Validation schemas ----
1313
1859
  // `/validations` requires `startMonth` + `endMonth` (YYYY-MM). Optional filters
@@ -1339,15 +1885,7 @@ export const ValidationSearchSchema = z
1339
1885
  .optional()
1340
1886
  .describe("États de validation."),
1341
1887
  validationAlerts: z.boolean().optional().describe("Filtrer sur les validations avec alertes."),
1342
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1343
- pageSize: z
1344
- .number()
1345
- .int()
1346
- .min(1)
1347
- .max(MAX_PAGE_SIZE)
1348
- .default(DEFAULT_PAGE_SIZE)
1349
- .describe(`Nombre de résultats par page (max: ${MAX_PAGE_SIZE}, défaut: ${DEFAULT_PAGE_SIZE})`),
1350
- fields: fieldsField,
1888
+ ...paginationShape,
1351
1889
  })
1352
1890
  .strict();
1353
1891
  // ---- Notification schemas ----
@@ -1364,15 +1902,7 @@ export const NotificationSearchSchema = z
1364
1902
  .array(z.string())
1365
1903
  .optional()
1366
1904
  .describe("Types de modules parents (ex: 'contract', 'global', 'project'...)."),
1367
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1368
- pageSize: z
1369
- .number()
1370
- .int()
1371
- .min(1)
1372
- .max(MAX_PAGE_SIZE)
1373
- .default(DEFAULT_PAGE_SIZE)
1374
- .describe(`Nombre de résultats par page (max: ${MAX_PAGE_SIZE}, défaut: ${DEFAULT_PAGE_SIZE})`),
1375
- fields: fieldsField,
1905
+ ...paginationShape,
1376
1906
  })
1377
1907
  .strict();
1378
1908
  // ---- Reporting schemas ----
@@ -1546,7 +2076,7 @@ export const DictionaryGetSchema = z
1546
2076
  dictionaryType: z
1547
2077
  .string()
1548
2078
  .min(1)
1549
- .describe("Type de dictionnaire (ex: typeOf/actions, typeOf/absences, states/candidates, states/resources, states/opportunities, states/projects, states/invoices, countries, currencies, languages...)"),
2079
+ .describe("Chemin dotté relatif à `data` de /application/dictionary (ex: setting.state.resource, setting.typeOf.project, setting.action.candidate, setting.tool, country, languages). La forme « states/resources » (slash) n'est pas valide."),
1550
2080
  })
1551
2081
  .strict();
1552
2082
  // ---- Documents ----