boondmanager-mcp-server 2.4.0 → 2.6.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 (40) hide show
  1. package/README.md +24 -4
  2. package/dist/config/dictionary-overrides.d.ts +57 -0
  3. package/dist/config/dictionary-overrides.d.ts.map +1 -0
  4. package/dist/config/dictionary-overrides.js +193 -0
  5. package/dist/config/dictionary-overrides.js.map +1 -0
  6. package/dist/resources/index.d.ts.map +1 -1
  7. package/dist/resources/index.js +25 -0
  8. package/dist/resources/index.js.map +1 -1
  9. package/dist/schemas/index.d.ts +209 -22
  10. package/dist/schemas/index.d.ts.map +1 -1
  11. package/dist/schemas/index.js +241 -58
  12. package/dist/schemas/index.js.map +1 -1
  13. package/dist/services/boond-client.d.ts +7 -0
  14. package/dist/services/boond-client.d.ts.map +1 -1
  15. package/dist/services/boond-client.js +22 -0
  16. package/dist/services/boond-client.js.map +1 -1
  17. package/dist/tools/actions.d.ts.map +1 -1
  18. package/dist/tools/actions.js +59 -3
  19. package/dist/tools/actions.js.map +1 -1
  20. package/dist/tools/candidates.js +2 -2
  21. package/dist/tools/candidates.js.map +1 -1
  22. package/dist/tools/companies.js +2 -2
  23. package/dist/tools/companies.js.map +1 -1
  24. package/dist/tools/contacts.js +2 -2
  25. package/dist/tools/contacts.js.map +1 -1
  26. package/dist/tools/opportunities.d.ts.map +1 -1
  27. package/dist/tools/opportunities.js +3 -3
  28. package/dist/tools/opportunities.js.map +1 -1
  29. package/dist/tools/positionings.d.ts.map +1 -1
  30. package/dist/tools/positionings.js +75 -5
  31. package/dist/tools/positionings.js.map +1 -1
  32. package/dist/tools/projects.js +2 -2
  33. package/dist/tools/projects.js.map +1 -1
  34. package/dist/tools/reporting.d.ts.map +1 -1
  35. package/dist/tools/reporting.js +20 -13
  36. package/dist/tools/reporting.js.map +1 -1
  37. package/dist/tools/resources.js +2 -2
  38. package/dist/tools/resources.js.map +1 -1
  39. package/manifest.json +10 -3
  40. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, MAX_SEARCH_PAGE } from "../constants.js";
3
+ import { appendOverridesToDescription, resolveLabel } from "../config/dictionary-overrides.js";
3
4
  // Common search schema
4
5
  export const SearchSchema = z
5
6
  .object({
@@ -437,6 +438,24 @@ export const IdTabSchema = z
437
438
  tab: TabSchema.optional().describe("Onglet spécifique à récupérer (information, technical, financial, actions, contracts, documents)"),
438
439
  })
439
440
  .strict();
441
+ // ---- Dictionary-override-aware `state` field ----
442
+ // Accepts the usual numeric dictionary id, plus a custom label when the
443
+ // operator configured BOOND_DICTIONARY_OVERRIDES (see docs/dictionary-overrides.md).
444
+ // IMPORTANT: the label is resolved inside `z.preprocess`, i.e. at PARSE time,
445
+ // not at schema definition time -- so overrides injected after this module is
446
+ // imported (tests, late env) are honoured. Unknown labels are left as-is so
447
+ // the integer validation fails with an explicit error.
448
+ export const stateField = (entity, baseDescription) => z
449
+ .preprocess((value) => {
450
+ if (typeof value === "string") {
451
+ const resolved = resolveLabel("state", entity, value);
452
+ if (resolved !== undefined)
453
+ return resolved;
454
+ }
455
+ return value;
456
+ }, z.coerce.number().int())
457
+ .optional()
458
+ .describe(appendOverridesToDescription(baseDescription, "state", entity));
440
459
  // ---- Candidate schemas ----
441
460
  export const CandidateCreateSchema = z
442
461
  .object({
@@ -447,11 +466,31 @@ export const CandidateCreateSchema = z
447
466
  city: z.string().optional().describe("Ville"),
448
467
  country: z.string().optional().describe("Pays"),
449
468
  title: z.string().optional().describe("Titre du poste / fonction"),
450
- state: z.number().int().optional().describe("État du candidat (0=en cours, 1=placé, 2=archivé...)"),
469
+ state: stateField("candidate", "État du candidat (0=en cours, 1=placé, 2=archivé...)"),
451
470
  mainSkills: z.string().optional().describe("Compétences principales (texte libre)"),
452
471
  note: z.string().optional().describe("Notes / commentaires"),
453
472
  })
454
473
  .strict();
474
+ export const PositioningUpdateSchema = z
475
+ .object({
476
+ id: z.string().min(1).describe("ID du positionnement \u00e0 modifier"),
477
+ state: z
478
+ .number()
479
+ .int()
480
+ .min(0)
481
+ .optional()
482
+ .describe("\u00c9tat du positionnement : ID num\u00e9rique du dictionnaire setting.state.positioning"),
483
+ stateReasonTypeOf: z
484
+ .number()
485
+ .int()
486
+ .optional()
487
+ .describe("Motif d'\u00e9tat : ID num\u00e9rique du type de motif (stateReason.typeOf)"),
488
+ stateReasonDetail: z.string().optional().describe("Motif d'\u00e9tat : d\u00e9tail libre (stateReason.detail)"),
489
+ startDate: z.string().optional().describe("Date de d\u00e9but (YYYY-MM-DD, ou cha\u00eene vide pour effacer)"),
490
+ endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD, ou cha\u00eene vide pour effacer)"),
491
+ informationComments: z.string().max(250).optional().describe("Commentaires (max 250 caract\u00e8res)"),
492
+ })
493
+ .strict();
455
494
  export const CandidateUpdateSchema = z
456
495
  .object({
457
496
  id: z.string().min(1).describe("ID du candidat à modifier"),
@@ -462,7 +501,7 @@ export const CandidateUpdateSchema = z
462
501
  city: z.string().optional().describe("Ville"),
463
502
  country: z.string().optional().describe("Pays"),
464
503
  title: z.string().optional().describe("Titre / fonction"),
465
- state: z.number().int().optional().describe("État du candidat"),
504
+ state: stateField("candidate", "État du candidat"),
466
505
  mainSkills: z.string().optional().describe("Compétences principales"),
467
506
  note: z.string().optional().describe("Notes"),
468
507
  })
@@ -477,7 +516,7 @@ export const ResourceCreateSchema = z
477
516
  city: z.string().optional().describe("Ville"),
478
517
  country: z.string().optional().describe("Pays"),
479
518
  title: z.string().optional().describe("Titre / poste"),
480
- state: z.number().int().optional().describe("État de la ressource"),
519
+ state: stateField("resource", "État de la ressource"),
481
520
  note: z.string().optional().describe("Notes"),
482
521
  })
483
522
  .strict();
@@ -491,7 +530,7 @@ export const ResourceUpdateSchema = z
491
530
  city: z.string().optional().describe("Ville"),
492
531
  country: z.string().optional().describe("Pays"),
493
532
  title: z.string().optional().describe("Titre / poste"),
494
- state: z.number().int().optional().describe("État"),
533
+ state: stateField("resource", "État"),
495
534
  note: z.string().optional().describe("Notes"),
496
535
  })
497
536
  .strict();
@@ -638,7 +677,7 @@ export const CompanyCreateSchema = z
638
677
  country: z.string().optional().describe("Pays"),
639
678
  website: z.string().optional().describe("Site web"),
640
679
  siret: z.string().optional().describe("Numéro SIRET"),
641
- state: z.number().int().optional().describe("État de la société"),
680
+ state: stateField("company", "État de la société"),
642
681
  note: z.string().optional().describe("Notes"),
643
682
  })
644
683
  .strict();
@@ -652,7 +691,7 @@ export const CompanyUpdateSchema = z
652
691
  country: z.string().optional().describe("Pays"),
653
692
  website: z.string().optional().describe("Site web"),
654
693
  siret: z.string().optional().describe("Numéro SIRET"),
655
- state: z.number().int().optional().describe("État"),
694
+ state: stateField("company", "État"),
656
695
  note: z.string().optional().describe("Notes"),
657
696
  })
658
697
  .strict();
@@ -662,7 +701,7 @@ export const OpportunityCreateSchema = z
662
701
  name: z.string().min(1).describe("Nom / titre de l'opportunité"),
663
702
  companyId: z.string().optional().describe("ID de la société cliente"),
664
703
  contactId: z.string().optional().describe("ID du contact associé"),
665
- state: z.number().int().optional().describe("État de l'opportunité"),
704
+ state: stateField("opportunity", "État de l'opportunité"),
666
705
  startDate: z.string().optional().describe("Date de début prévue (YYYY-MM-DD)"),
667
706
  endDate: z.string().optional().describe("Date de fin prévue (YYYY-MM-DD)"),
668
707
  note: z.string().optional().describe("Notes / description"),
@@ -672,7 +711,7 @@ export const OpportunityUpdateSchema = z
672
711
  .object({
673
712
  id: z.string().min(1).describe("ID de l'opportunité à modifier"),
674
713
  name: z.string().optional().describe("Nom / titre"),
675
- state: z.number().int().optional().describe("État"),
714
+ state: stateField("opportunity", "État"),
676
715
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
677
716
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
678
717
  note: z.string().optional().describe("Notes"),
@@ -696,11 +735,10 @@ export const ActionSearchSchema = z
696
735
  // and `text` (not subject/content) — anything else triggers a 422.
697
736
  export const ActionCreateSchema = z
698
737
  .object({
699
- typeOf: z.coerce
700
- .number()
701
- .int()
702
- .min(0)
703
- .describe("Type d'action : ID numérique du dictionnaire BoondManager (setting.action.*, via boond_application_dictionary)"),
738
+ typeOf: z
739
+ .union([z.coerce.number().int().min(0), z.string().min(1)])
740
+ .describe("Type d'action : ID numérique du dictionnaire (setting.action.*, via boond_application_dictionary) " +
741
+ "ou libellé personnalisé si BOOND_DICTIONARY_OVERRIDES est configuré"),
704
742
  title: z.string().optional().describe("Titre de l'action"),
705
743
  text: z.string().optional().describe("Contenu / notes de l'action"),
706
744
  startDate: z
@@ -714,6 +752,10 @@ export const ActionCreateSchema = z
714
752
  opportunityId: z.string().optional().describe("ID de l'opportunité à laquelle rattacher l'action (dependsOn)"),
715
753
  projectId: z.string().optional().describe("ID du projet auquel rattacher l'action (dependsOn)"),
716
754
  companyId: z.string().optional().describe("ID de la société associée (uniquement en complément d'un contactId)"),
755
+ positioningId: z
756
+ .string()
757
+ .optional()
758
+ .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) »."),
717
759
  })
718
760
  .strict();
719
761
  // ---- Timesheet schemas ----
@@ -760,7 +802,7 @@ export const ProjectCreateSchema = z
760
802
  companyId: z.string().optional().describe("ID de la société cliente"),
761
803
  contactId: z.string().optional().describe("ID du contact associé"),
762
804
  opportunityId: z.string().optional().describe("ID de l'opportunité liée"),
763
- state: z.number().int().optional().describe("État du projet (0=en cours, 1=terminé, 2=archivé...)"),
805
+ state: stateField("project", "État du projet (0=en cours, 1=terminé, 2=archivé...)"),
764
806
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
765
807
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
766
808
  note: z.string().optional().describe("Notes / description du projet"),
@@ -770,7 +812,7 @@ export const ProjectUpdateSchema = z
770
812
  .object({
771
813
  id: z.string().min(1).describe("ID du projet à modifier"),
772
814
  name: z.string().optional().describe("Nom du projet"),
773
- state: z.number().int().optional().describe("État du projet"),
815
+ state: stateField("project", "État du projet"),
774
816
  startDate: z.string().optional().describe("Date de début (YYYY-MM-DD)"),
775
817
  endDate: z.string().optional().describe("Date de fin (YYYY-MM-DD)"),
776
818
  note: z.string().optional().describe("Notes"),
@@ -959,11 +1001,34 @@ export const PositioningCreateSchema = z
959
1001
  .strict();
960
1002
  export const PositioningSearchSchema = z
961
1003
  .object({
962
- keywords: z.string().optional().describe("Mots-clés de recherche"),
963
- candidateId: z.string().optional().describe("Filtrer par ID candidat"),
964
- resourceId: z.string().optional().describe("Filtrer par ID ressource"),
965
- projectId: z.string().optional().describe("Filtrer par ID projet"),
966
- opportunityId: z.string().optional().describe("Filtrer par ID opportunité"),
1004
+ keywords: z
1005
+ .string()
1006
+ .optional()
1007
+ .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."),
1008
+ candidateId: z
1009
+ .string()
1010
+ .optional()
1011
+ .describe("Filtrer par ID candidat (envoyé à l'API comme référence keywords CAND<id>)"),
1012
+ resourceId: z
1013
+ .string()
1014
+ .optional()
1015
+ .describe("Filtrer par ID ressource (envoyé à l'API comme référence keywords COMP<id>)"),
1016
+ opportunityId: z
1017
+ .string()
1018
+ .optional()
1019
+ .describe("Filtrer par ID opportunité (envoyé à l'API comme référence keywords AO<id>)"),
1020
+ companyId: z
1021
+ .string()
1022
+ .optional()
1023
+ .describe("Filtrer par ID société (envoyé à l'API comme référence keywords CSOC<id>)"),
1024
+ contactId: z
1025
+ .string()
1026
+ .optional()
1027
+ .describe("Filtrer par ID contact (envoyé à l'API comme référence keywords CCON<id>)"),
1028
+ productId: z
1029
+ .string()
1030
+ .optional()
1031
+ .describe("Filtrer par ID produit (envoyé à l'API comme référence keywords PROD<id>)"),
967
1032
  page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
968
1033
  pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Résultats par page"),
969
1034
  })
@@ -1055,51 +1120,169 @@ export const NotificationSearchSchema = z
1055
1120
  })
1056
1121
  .strict();
1057
1122
  // ---- Reporting schemas ----
1058
- // `/reporting-companies` and `/reporting-resources` require `startDate` +
1059
- // `endDate` (YYYY-MM-DD); the others tolerate omission but still benefit from
1060
- // a period filter.
1061
- export const ReportingDateRequiredSchema = z
1062
- .object({
1063
- startDate: z
1064
- .string()
1065
- .regex(/^\d{4}-\d{2}-\d{2}$/)
1066
- .describe("Date de début YYYY-MM-DD. Requis."),
1067
- endDate: z
1068
- .string()
1069
- .regex(/^\d{4}-\d{2}-\d{2}$/)
1070
- .describe("Date de fin YYYY-MM-DD. Requis."),
1123
+ // Sources (one search.raml per endpoint):
1124
+ // https://doc.boondmanager.com/api-externe/raml-build/resources/reportingCompanies/search.raml
1125
+ // .../reportingProjects/search.raml .../reportingResources/search.raml
1126
+ // .../reportingSynthesis/search.raml .../reportingProductionPlans/search.raml
1127
+ // Every reporting endpoint carries the `searchable` trait (perimeter filters)
1128
+ // plus endpoint-specific filters. These were previously dropped — only
1129
+ // startDate/endDate/keywords were forwarded — so a "filtered" reporting query
1130
+ // silently returned the full perimeter. Each tool now exposes its real filters.
1131
+ //
1132
+ // `period`/`periodDynamic` value sets are large and differ slightly per endpoint,
1133
+ // so they stay `z.string()` (documented in the description) rather than a strict
1134
+ // enum that could reject a value the API actually accepts. The small, stable
1135
+ // enums (reportingCategory, reportingType, positioningPeriod, useCache) are typed.
1136
+ const reportingPeriodField = z
1137
+ .string()
1138
+ .optional()
1139
+ .describe("Découpage temporel : 'onePeriod' (unique, entre startDate/endDate), 'dynamicPeriod' (selon periodDynamic), " +
1140
+ "'monthly' (6 mois depuis startDate), 'quarterly', 'semiAnnual', 'annual'. La synthèse accepte aussi 'weekly'.");
1141
+ const reportingPeriodDynamicField = z
1142
+ .string()
1143
+ .optional()
1144
+ .describe("Période dynamique relative à aujourd'hui (avec period='dynamicPeriod') : today, thisWeek, thisMonth, " +
1145
+ "thisTrimester, thisSemester, thisYear, thisFiscalYear, yesterday, lastWeek, lastMonth, lastTrimester, " +
1146
+ "lastSemester, lastYear, lastFiscalYear, tomorrow, nextWeek, nextMonth, nextTrimester, nextSemester, " +
1147
+ "nextYear, nextFiscalYear, lastCustomPeriod, nextCustomPeriod.");
1148
+ const reportingPeriodDynamicParametersField = z
1149
+ .string()
1150
+ .optional()
1151
+ .describe("Paramètres de la période personnalisée (utilisé avec periodDynamic=lastCustomPeriod/nextCustomPeriod).");
1152
+ const reportingScorecardsField = strArray("IDs des scorecards (indicateurs) à retourner.");
1153
+ const reportingUseCacheField = z
1154
+ .enum(["withCache", "withoutCache"])
1155
+ .optional()
1156
+ .describe("Cache de reporting : 'withCache' (valeurs mises en cache) ou 'withoutCache' (recalcul, défaut).");
1157
+ const reportingResourcesField = intArray("Filtrer sur ces IDs de ressources.");
1158
+ const reportingProjectsField = intArray("Filtrer sur ces IDs de projets.");
1159
+ const reportingContactsField = intArray("Filtrer sur ces IDs de contacts.");
1160
+ const reportingCompaniesField = intArray("Filtrer sur ces IDs de sociétés.");
1161
+ const reportingMaxField = (entity) => z
1162
+ .number()
1163
+ .int()
1164
+ .min(1)
1165
+ .max(10)
1166
+ .optional()
1167
+ .describe(`Nombre de ${entity} par page (1-10, défaut 1). Le nombre de résultats = ${entity} × indicateurs.`);
1168
+ // Perimeter + period filters shared by every reporting endpoint (searchable trait).
1169
+ const reportingCommonFields = {
1071
1170
  keywords: z.string().optional().describe("Mots-clés."),
1072
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1073
- pageSize: z
1074
- .number()
1075
- .int()
1076
- .min(1)
1077
- .max(MAX_PAGE_SIZE)
1078
- .default(DEFAULT_PAGE_SIZE)
1079
- .describe(`Nombre de résultats par page (max: ${MAX_PAGE_SIZE}, défaut: ${DEFAULT_PAGE_SIZE})`),
1171
+ perimeterManagers: perimeterManagersField,
1172
+ perimeterAgencies: perimeterAgenciesField,
1173
+ perimeterPoles: perimeterPolesField,
1174
+ perimeterBusinessUnits: perimeterBusinessUnitsField,
1175
+ perimeterDynamic: perimeterDynamicField,
1176
+ narrowPerimeter: narrowPerimeterField,
1177
+ periodDynamic: reportingPeriodDynamicField,
1178
+ periodDynamicParameters: reportingPeriodDynamicParametersField,
1179
+ scorecards: reportingScorecardsField,
1180
+ useCache: reportingUseCacheField,
1181
+ page: pageField,
1182
+ pageSize: pageSizeField,
1183
+ };
1184
+ const requiredDate = (label) => z
1185
+ .string()
1186
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
1187
+ .describe(`${label} (YYYY-MM-DD). Requis par l'API.`);
1188
+ const optionalDate = (label) => z
1189
+ .string()
1190
+ .regex(/^\d{4}-\d{2}-\d{2}$/)
1191
+ .optional()
1192
+ .describe(`${label} (YYYY-MM-DD).`);
1193
+ export const ReportingCompaniesSchema = z
1194
+ .object({
1195
+ ...reportingCommonFields,
1196
+ startDate: requiredDate("Date de début"),
1197
+ endDate: requiredDate("Date de fin"),
1198
+ companiesStates: intArray("IDs d'états de sociétés (dictionnaire setting.state.company)."),
1199
+ maxCompanies: reportingMaxField("sociétés"),
1200
+ showPercentage: z.boolean().optional().describe("Afficher les valeurs en pourcentage plutôt qu'en valeur réelle."),
1201
+ companies: reportingCompaniesField,
1080
1202
  })
1081
1203
  .strict();
1082
- export const ReportingDateOptionalSchema = z
1204
+ export const ReportingProjectsSchema = z
1083
1205
  .object({
1084
- startDate: z
1085
- .string()
1086
- .regex(/^\d{4}-\d{2}-\d{2}$/)
1206
+ ...reportingCommonFields,
1207
+ startDate: optionalDate("Date de début"),
1208
+ endDate: optionalDate("Date de fin"),
1209
+ projectTypes: intArray("IDs de types de projets (dictionnaire setting.typeOf.project)."),
1210
+ projectStates: intArray("IDs d'états de projets (dictionnaire setting.state.project)."),
1211
+ maxProjects: reportingMaxField("projets"),
1212
+ resources: reportingResourcesField,
1213
+ projects: reportingProjectsField,
1214
+ contacts: reportingContactsField,
1215
+ companies: reportingCompaniesField,
1216
+ })
1217
+ .strict();
1218
+ export const ReportingResourcesSchema = z
1219
+ .object({
1220
+ ...reportingCommonFields,
1221
+ startDate: optionalDate("Date de début"),
1222
+ endDate: optionalDate("Date de fin"),
1223
+ reportingCategory: z
1224
+ .enum(["showByResources", "showByPeriods"])
1087
1225
  .optional()
1088
- .describe("Date de début YYYY-MM-DD."),
1089
- endDate: z
1226
+ .describe("Vue : 'showByResources' (défaut, sans returnedPeriod) ou 'showByPeriods' (returnedPeriod requis)."),
1227
+ maxResources: reportingMaxField("ressources"),
1228
+ resourceTypes: intArray("IDs de types de ressources (dictionnaire setting.typeOf.resource)."),
1229
+ resourceStates: intArray("IDs d'états de ressources (dictionnaire setting.state.resource)."),
1230
+ period: reportingPeriodField,
1231
+ resources: reportingResourcesField,
1232
+ projects: reportingProjectsField,
1233
+ contacts: reportingContactsField,
1234
+ companies: reportingCompaniesField,
1235
+ })
1236
+ .strict();
1237
+ export const ReportingSynthesisSchema = z
1238
+ .object({
1239
+ ...reportingCommonFields,
1240
+ startDate: requiredDate("Date de début"),
1241
+ endDate: optionalDate("Date de fin"),
1242
+ reportingType: z
1243
+ .enum(["realData", "targetsData"])
1244
+ .optional()
1245
+ .describe("Type de données : 'realData' (défaut, réel) ou 'targetsData' (objectifs)."),
1246
+ reportingCategory: z
1247
+ .enum([
1248
+ "commercialSynthesis",
1249
+ "humanResourcesSynthesis",
1250
+ "recruitmentSynthesis",
1251
+ "activityExpensesSynthesis",
1252
+ "billingSynthesis",
1253
+ "globalSynthesis",
1254
+ ])
1255
+ .optional()
1256
+ .describe("Catégorie de synthèse (défaut 'commercialSynthesis') : commercial, RH, recrutement, activité & frais, " +
1257
+ "facturation, ou globale. `resources` n'est pas disponible hors d'une vue par ressources."),
1258
+ period: reportingPeriodField,
1259
+ resources: reportingResourcesField,
1260
+ projects: reportingProjectsField,
1261
+ contacts: reportingContactsField,
1262
+ companies: reportingCompaniesField,
1263
+ compareIndicators: strArray("Indicateurs à comparer entre deux périodes."),
1264
+ compareIndicatorsPeriod: z
1090
1265
  .string()
1091
- .regex(/^\d{4}-\d{2}-\d{2}$/)
1092
1266
  .optional()
1093
- .describe("Date de fin YYYY-MM-DD."),
1094
- keywords: z.string().optional().describe("Mots-clés."),
1095
- page: z.number().int().min(1).max(MAX_SEARCH_PAGE).default(1).describe(`Numéro de page (max: ${MAX_SEARCH_PAGE})`),
1096
- pageSize: z
1097
- .number()
1098
- .int()
1099
- .min(1)
1100
- .max(MAX_PAGE_SIZE)
1101
- .default(DEFAULT_PAGE_SIZE)
1102
- .describe(`Nombre de résultats par page (max: ${MAX_PAGE_SIZE}, défaut: ${DEFAULT_PAGE_SIZE})`),
1267
+ .describe("Période de comparaison des indicateurs (défaut 'period')."),
1268
+ })
1269
+ .strict();
1270
+ export const ReportingProductionPlansSchema = z
1271
+ .object({
1272
+ ...reportingCommonFields,
1273
+ startDate: requiredDate("Date de début"),
1274
+ endDate: requiredDate("Date de fin"),
1275
+ resourceTypes: intArray("IDs de types de ressources (dictionnaire setting.typeOf.resource)."),
1276
+ resourceStates: intArray("IDs d'états de ressources (dictionnaire setting.state.resource)."),
1277
+ positioningStates: intArray("IDs d'états de positionnement (dictionnaire setting.state.positioning)."),
1278
+ positioningPeriod: z
1279
+ .enum(["created", "running"])
1280
+ .optional()
1281
+ .describe("'created' (positionnements créés entre les dates, défaut) ou 'running' (en cours sur la période)."),
1282
+ showContracts: z.boolean().optional().describe("Afficher les contrats associés."),
1283
+ projects: reportingProjectsField,
1284
+ contacts: reportingContactsField,
1285
+ companies: reportingCompaniesField,
1103
1286
  })
1104
1287
  .strict();
1105
1288
  export const DictionaryGetSchema = z