Méthode robuste, Odoo-ready, pour le cas précis : media entrant (audio) → création x_media_asset + “stub” ir.attachment(type='url')
Voici une méthode robuste, Odoo-ready, pour le cas précis : media entrant (audio) → création x_media_asset + “stub” ir.attachment(type='url') optionnel, en gardant vos objectifs : traçabilité, auditabilité, stockage externe, URLs signées à durée courte, et gouvernance via Documents.
1) Pattern recommandé (vérité vs vue)
A. Vérité (système d’enregistrement)
- x_media_asset = la vérité durable (provider, key, checksum, droits, rétention, horodatage, lien case/patient)
- L’URL “stable” est x_external_key (bucket/path) + x_external_provider
- Aucune URL signée durable n’est stockée comme vérité
B. Vue dans Odoo (navigable)
- ir.attachment (optionnel) sert de “raccourci” consultable dans Odoo / Documents
- On utilise type='url' avec une URL signée TTL court
- L’URL signée est régénérée à la demande (ou périodiquement) et révocable via vos règles côté storage
2) Modèle de données minimal pour industrialiser le “stub attachment”
A. Dans x_media_asset (ajouts utiles)
En plus de vos champs actuels, j’ajoute 3 champs très opérants :
| Champ | Type | Rôle |
|---|---|---|
| x_stub_attachment_id | Many2one → ir.attachment | le stub “navigable” dans Odoo |
| x_signed_url_last_at | Datetime | date de dernière génération |
| x_signed_url_ttl_s | Integer | TTL cible (ex 900 = 15 min) |
Vous gardez x_attachment_id pour le cas binary interne ; x_stub_attachment_id est spécifique au mode url stub.
B. Dans ir.attachment (stub)
- type = 'url'
- url = <signed_url> (éphémère)
- name = <CASE-…> – audio – <timestamp>
- res_model / res_id : 2 options (voir §3)
3) Où “attacher” le stub : 2 options (je recommande la 2)
Option 1 — stub attaché au Case
- ir.attachment(res_model='x_medical_case', res_id=case.id)
- Avantage : visible directement sur le Case
- Inconvénient : si vous avez beaucoup de médias, la vue attachments du Case devient bruyante
Option 2 — stub attaché au x_media_asset (recommandé)
- ir.attachment(res_model='x_media_asset', res_id=asset.id)
- Avantage : séparation claire “fiche média” vs “case”
- Avantage : vous pouvez avoir une vue/listing des assets filtrée, avec sécurité plus fine
- C’est le pattern le plus propre si vous industrialisez (appels, IoT, etc.)
Dans les deux cas, Documents indexe via documents.document (ou via votre lien x_documents_folder_id) sans stocker le binaire.
4) Méthode opérationnelle A2 (création Case + média entrant)
Étape A — réception événement (FreeSWITCH / Voice AI)
Vous recevez un événement avec : call_id (UUID), patient_phone, recording_started_at, provider_object_key (ou futur key), etc.
Étape B — création/qualif du Case
- créer x_medical_case
- créer/affecter documents.folder (CASE-YYYY-NNNNN …)
Étape C — création x_media_asset (immédiate, même si fichier pas encore final)
Créer x_media_asset en statut logique “pending” (si vous ajoutez un champ) ou via x_note.
Champs recommandés à ce stade :
- x_case_id, x_patient_id
- x_asset_type='audio'
- x_source='external'
- x_external_provider='supabase' (ou s3)
- x_external_key=<bucket/path provisoire ou final>
- x_recorded_at, x_access_scope='team'
- x_retention_policy='90j' (ou autre)
Étape D — stub ir.attachment(type='url') (optionnel) : création immédiate ou différée
- Immédiate si vous avez déjà un objet accessible (même provisoire)
- Différée si l’audio n’est disponible qu’à la fin (souvent le cas)
Dans les deux cas, l’attachment contient une URL signée TTL court.
5) Génération d’URL signée : 2 stratégies (je recommande “lazy refresh”)
Stratégie 1 — Lazy refresh (recommandée)
- Au moment où l’utilisateur clique “écouter / télécharger”
- Vous régénérez une URL signée (TTL 5–30 min)
- Vous mettez à jour ir.attachment.url et x_signed_url_last_at
- Avantages : pas d’URLs expirées stockées, moins de jobs planifiés, meilleure sécurité
Stratégie 2 — Refresh périodique (moins bien)
- Cron qui refresh toutes les X minutes
- Inconvénient : coûteux, et génère des URLs inutiles
Décision pratique : Lazy refresh + fallback “si URL expirée → regen”.
6) Comment déclencher le lazy refresh dans Odoo (sans complexifier)
Option A — Bouton “Ouvrir média” sur x_media_asset
- Action serveur (Studio) ou méthode python (module)
-
La méthode :
- calcule une URL signée via votre service (API interne)
- met à jour x_stub_attachment_id.url
- renvoie une action “open url”
Option B — Override léger côté ir.attachment
- Plus intrusif
- À éviter si vous voulez rester “Studio-first”
Recommandation : Option A (bouton sur x_media_asset), et dans l’UI du Case vous affichez les assets liés.
7) Service de signature : où le mettre
Vous ne voulez pas signer depuis Odoo avec des clés cloud exposées.
Pattern sain :
- Odoo appelle un micro-service “Media Gateway” (n8n, Node, FastAPI…)
- Le gateway connaît les credentials Supabase/S3
- Il renvoie signed_url + expires_at
- Odoo ne stocke que key/provider + URL éphémère “de vue”
8) Documents : indexation propre
Deux façons d’indexer dans Documents :
A. Via documents.document lié au stub attachment
- Quand vous créez ir.attachment, vous créez aussi un documents.document pointant vers ce attachment et dans le bon documents.folder
- Avantage : recherche et tags Documents
B. Via le lien x_documents_folder_id sur x_media_asset
- Et vous affichez une smart button “Documents” filtrée sur folder
- Avantage : plus simple, mais moins “document-centrique”
Recommandation : A si vous utilisez Documents comme cockpit, sinon B.
9) Méthode concrète “A2 si média entrant (audio)”
Voici la méthode que je propose (résumé exécutable) :
- Créer x_media_asset dès réception de l’événement (même si fichier non final)
-
À disponibilité du fichier (fin d’enregistrement), renseigner :
- x_external_key, x_size_bytes, x_checksum_sha256, x_mime_type
- Créer ir.attachment(type='url') stub attaché à x_media_asset (recommandé)
- Créer/mettre à jour documents.document dans le folder du Case (optionnel mais très utile)
-
À chaque ouverture, bouton “Ouvrir média” :
- appelle Media Gateway → obtient URL signée TTL court
- met à jour ir.attachment.url
- ouvre l’URL
10) Petits détails qui évitent les ennuis (important en santé)
- Ne mettez jamais d’identifiant patient dans name/url/key visibles (préférez CASE-… + hash court).
- x_access_scope doit être aligné sur les record rules (case/team).
- Ajoutez un champ x_visibility_state (ex: internal_only/shared_with_expert) si vous industrialisez les partages.
- Conservez checksum + horodatage + source(call_id) : c’est votre auditabilité.