import { BaseEntity } from "./BaseEntity.js"; import type { CostumProjectActionRequestNewData } from "./EndpointApi.types.js"; import type { ActionItemNormalized } from "./serverDataType/Action.js"; /** * Entité Action — tâche/jalon attaché à un Project. * * Toujours liée à un Project parent (parentType="projects" est const côté backend). * S'instancie via `project.action(...)` plutôt que directement. * * Endpoints utilisés : * - `get()` : hérité (GET_ELEMENTS_ABOUT via `/co2/element/about/type/actions/id/{id}`) * - `_add()` : COSTUM_PROJECT_ACTION_REQUEST_NEW (parentId/parentType injectés depuis le parent) * - `_update()` : UPDATE_PATH_VALUE field par field via CUSTOM_FIELD_HANDLERS * (pas d'endpoint dédié par bloc côté backend — on dispatche chaque champ modifié) */ export declare class Action extends BaseEntity { static entityType: string; static entityTag: string; static SCHEMA_CONSTANTS: string[]; /** * Schémas virtuels : enregistrent les champs éditables auprès du draft proxy * via _composeAllowedFields. Aucun endpoint backend réel — l'update passe par * UPDATE_PATH_VALUE field par field. */ static VIRTUAL_SCHEMAS: { VIRTUAL_ACTION_EDITABLE: { type: string; properties: { name: { type: string; }; credits: { type: string; }; min: { type: string; minimum: number; }; max: { type: string; minimum: number; }; status: { type: string; enum: string[]; }; tags: { type: string; items: { type: string; }; }; links: { type: string; }; startDate: { oneOf: { type: string; }[]; }; endDate: { oneOf: { type: string; }[]; }; milestone: { oneOf: { type: string; }[]; }; importance: { type: string; }; }; }; }; static ADD_BLOCKS: Map<"COSTUM_PROJECT_ACTION_REQUEST_NEW", "addAction">; /** * Champs éditables → méthode publique qui réalise l'update (via UPDATE_PATH_VALUE). * Même pattern qu'Organization.openingHours : chaque field nom → method name. * Les `updateXxx` méthodes ci-dessous délèguent toutes à `_updatePath`. */ static CUSTOM_FIELD_HANDLERS: Map<"name" | "tags" | "startDate" | "endDate" | "links" | "status" | "credits" | "milestone" | "min" | "max" | "importance", { readonly updateMethod: "updateName"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateCredits"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateMin"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateMax"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateStatus"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateTags"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateContributors"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateStartDate"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateEndDate"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateMilestone"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; } | { readonly updateMethod: "updateImportance"; readonly schemaConstant: "VIRTUAL_ACTION_EDITABLE"; }>; /** * Champs présents dans le schéma de création (donc writable dans le draft) mais * **non modifiables après création** — soit pas exposés en update backend, soit * gérés par un autre flow. * * Si un caller modifie un de ces champs après get() puis save(), `_update` throw * avec un message d'aide indiquant la voie alternative. Sans cette garde, le * changement serait silencieusement ignoré. */ private static CREATE_ONLY_FIELDS; /** * Slot UPDATE_BLOCKS vide pour l'instant : aucun endpoint dédié côté backend. * Préservé pour cohérence structurelle avec Organization/Project — quand un endpoint * `UPDATE_BLOCK_ACTION_XXX` arrivera, il s'ajoutera ici sans toucher au flow. */ static UPDATE_BLOCKS: Map; /** * `parentType` est injecté au payload de création (required par le schéma * COSTUM_PROJECT_ACTION_REQUEST_NEW). `_add` ne filtre pas avec removeFields, * donc il passe bien à la requête. Reste défensif si AJV `useDefaults` change. */ defaultFields: Record; /** * Champs techniques exclus du draft writable (set au moment de la création, * jamais modifiables ensuite par le caller). Restent accessibles en lecture * via `action.serverData.parentId` / `.parentType`. */ removeFields: string[]; _add: (payload: Record) => Promise; /** * Crée une nouvelle action via /costum/project/action/request/new. * Méthode de bas niveau — utiliser `save()` après modification du draft à la place. */ addAction(data?: Partial): Promise; _update: (payload: Record) => Promise; updateName(value: string): Promise; /** * Met à jour `credits` (entier). `setType: "int"` force le cast côté backend * (form-urlencoded transmet en string sinon). */ updateCredits(value: number): Promise; /** Met à jour `min` (entier ≥ 0). `setType: "int"`. */ updateMin(value: number): Promise; /** Met à jour `max` (entier ≥ 0). `setType: "int"`. */ updateMax(value: number): Promise; /** * Change le statut de l'action via l'endpoint dédié backend `set_status`. * * **PAS via UPDATE_PATH_VALUE** : utiliser cet endpoint préserve la logique métier * critique : * - Ajoute une entrée dans `updateStatus[]` (historique des changements) * - Auto endDate=now si status="done", auto startDate=now si "tracking" * - Auto-purge des tags discuter/totest/next à la transition * - Auto-set du tag pour discuter/next/totest (alias de "todo") * - Notification Rocket.Chat au projet parent * * Statuses supportés côté backend : todo, done, tracking, discuter, next, totest, disabled, closed. */ updateStatus(value: "todo" | "done" | "tracking" | "discuter" | "next" | "totest" | "disabled" | "closed"): Promise; updateTags(value: string[]): Promise; /** * Met à jour `importance` ("low" / "medium" / "high" / autres valeurs custom). * Champ simple sans effets de bord backend connus. */ updateImportance(value: string): Promise; /** * Remplace la liste complète des contributeurs via l'endpoint dédié `set_contributors`. * * **Sécurité** : touche plusieurs users → réservé aux admins du projet parent. * * Avantages vs UPDATE_PATH_VALUE : * - Validation min/max côté backend (refus si hors bornes) * - Notification socket aux autres clients (.set-contributors, .cd-int-contributors) * - Réponse enrichie avec name/image des contributeurs * * @param value objet `{ contributors: { userId: {...} } }` — on extrait juste les keys (userIds). * Les méta (type, isAdmin, name) sont gérées côté backend (type="citoyens" par défaut). * Pour vider : passer `{ contributors: {} }`. */ updateContributors(value: { contributors?: Record; }): Promise; /** * Ajoute un user comme contributeur de l'action (mode "join"). * Préserve les autres contributeurs existants. * * **Sécurité** : * - Sans argument (ou userId = soi-même) → autorisé pour tout user connecté ("je participe") * - Avec un autre userId → réservé aux admins du projet parent ("j'ajoute X") * * @param userId userId à ajouter. Par défaut : l'utilisateur connecté. */ joinContributor(userId?: string): Promise; /** * Retire un user de la liste des contributeurs (mode "quit"). * Préserve les autres contributeurs existants. * * **Sécurité** : * - Sans argument (ou userId = soi-même) → autorisé pour tout user connecté ("je quitte") * - Avec un autre userId → réservé aux admins du projet parent ("je retire X") * * @param userId userId à retirer. Par défaut : l'utilisateur connecté. */ leaveContributor(userId?: string): Promise; /** * Vérifie que l'utilisateur connecté est admin du projet parent — requis pour les * mutations affectant des tiers (replace bulk ou agir sur un autre userId). */ private _assertParentAdminForContributorMutation; /** * Met à jour ou efface le milestone d'une action. * - `{ milestoneId }` : attache (validé via `Project.hasMilestone`). * - `null` : détache via `$unset` Mongo côté backend. * * **Détail technique de l'effacement** : * Le backend (`Element::updatePathValue`) fait `$verb = '$unset'` quand `$value === null`. * Notre SDK strip les `null` avant l'envoi (`stripNullsInPlace`), donc on contourne en * envoyant la chaîne vide `""` : la coercion PHP `(!empty($value) || $value == "0" || $value === false)` * la transforme en `null` → `$unset`. Bonus : évite aussi le bug backend * `"text" => $_POST["value"]` (accès direct, crash si value absent). * * NOTE: changer/effacer le milestone peut avoir des effets de bord côté backend non * gérés (compteurs par milestone, idParentRoom, statuts dérivés). À utiliser avec discernement. */ updateMilestone(value: { milestoneId: string; } | null): Promise; /** * Met à jour startDate via l'endpoint dédié `set_date`. * * Utilise `set_date` plutôt qu'UPDATE_PATH_VALUE car : * - Support natif de l'effacement : `null` → envoyer "" → backend fait `$unset` * - Parser de date robuste côté backend (vs notre validation client-side) * * @param value chaîne ISO 8601 ("2026-05-20T08:00:00.000Z") ou null pour effacer. */ updateStartDate(value: string | null): Promise; /** * Met à jour endDate via l'endpoint dédié `set_date`. * @param value chaîne ISO 8601 ou null pour effacer. */ updateEndDate(value: string | null): Promise; /** * Vérifie si l'utilisateur connecté est contributeur de CETTE action. * * Override la version de BaseEntity car la sémantique diffère : * - Project : regarde `user.serverData.links.projects[projectId]` (sens user→project) * - Action : regarde `action.serverData.links.contributors[userId]` (sens inverse) * * Pour modifier des données de l'action, c'est `parent.isAdmin()` qui compte * (admin du projet parent) — pas cette méthode. * * @param options.silent si `false`, throw en cas de précondition manquante. Par défaut `true`. */ isContributor(options?: { silent?: boolean; }): boolean; /** * Annule l'action via l'endpoint dédié `cancel`. * Effets : status=closed + unset tracking + retire le tag "totest". */ cancel(): Promise; /** * Archive l'action via l'endpoint dédié `archive`. * Effets : status=disabled + unset tracking. */ archive(): Promise; /** * Supprime définitivement cette Action via `DELETE_ELEMENT` (`type=actions`). * * **Guard** : autorisation client via `isAuthorOrAdmin({checkHierarchy: true})` : * - **auteur** de l'Action (`serverData.creator === userId`), OU * - **admin** du projet parent via la hiérarchie * (`serverData.parent[projectId]` × `userContext.links.projects[projectId].isAdmin`). * * Le check exploite `_isAdminViaHierarchy` (étendu à `actions` côté BaseEntity) * qui remonte vers le Project parent via `serverData.parent`. * * **Pré-requis** : * - `userContext` (User connecté) doit avoir `serverData.links.projects` chargés * (cas `api.me()`). * - L'Action doit avoir `serverData.parent` populé (cas après `get()` ou * `project.action({id})` qui fait un get automatique). * * Après succès : * - `serverData` et `draftData` sont vidés (sans casser la réactivité) * - `_isDeleted = true` → toute mutation ultérieure (`save`, `updateField`, etc.) throw * * @param reason - Raison libre transmise au backend (audit). Défaut : `"delete action"`. * @throws {ApiError} 400 si l'Action n'a pas d'id (jamais sauvegardée). * @throws {ApiError} 403 si ni auteur ni admin du projet parent. * * @example * const action = await project.action({ id: actionId }); * await action.delete(); // OK si auteur OU admin projet * action._isDeleted; // true * * @example * // Raison custom (audit) * await action.delete("Annulation suite à doublon (ticket #123)"); */ delete(reason?: string): Promise; /** * Helper privé : délègue à `BaseEntity.updateField()` pour un path donné. * Bénéficie du normalize automatique (R0 Date → isoDate, R1 collapse setType, * R2 pull + empty → "", R6 drop setType vide). */ private _updatePath; /** * Validation stricte int (refus float, NaN, etc.). */ private _assertInt; /** * Validation stricte ISO 8601 (refus DD/MM/YYYY, "5 août", etc.). `null` accepté (clear). */ private _assertIsoDate; /** * Vérifie un format ISO 8601 date-time strict (avec T et option timezone Z ou ±HH:MM). */ private _isIsoDateTime; form(): Promise; }