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 :

ChampTypeRôle
x_stub_attachment_idMany2one → ir.attachmentle stub “navigable” dans Odoo
x_signed_url_last_atDatetimedate de dernière génération
x_signed_url_ttl_sIntegerTTL 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 :
    1. calcule une URL signée via votre service (API interne)
    2. met à jour x_stub_attachment_id.url
    3. 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) :

  1. Créer x_media_asset dès réception de l’événement (même si fichier non final)
  2. À disponibilité du fichier (fin d’enregistrement), renseigner :
    • x_external_key, x_size_bytes, x_checksum_sha256, x_mime_type
  3. Créer ir.attachment(type='url') stub attaché à x_media_asset (recommandé)
  4. Créer/mettre à jour documents.document dans le folder du Case (optionnel mais très utile)
  5. À 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é.