# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. import logging from odoo import api, models _logger = logging.getLogger(__name__) # Map of quality-module res_model -> bridge tag XML id. # Kept module-level so both create() and write() can consult it cheaply. _QUALITY_MODELS_TO_TAG = { 'fusion.plating.ncr': 'fusion_plating_bridge_documents.documents_tag_ncr', 'fusion.plating.capa': 'fusion_plating_bridge_documents.documents_tag_capa', 'fusion.plating.fair': 'fusion_plating_bridge_documents.documents_tag_fair', 'fusion.plating.doc.control': 'fusion_plating_bridge_documents.documents_tag_doc_control', } _FOLDER_XMLID = 'fusion_plating_bridge_documents.documents_folder_plating_quality' class IrAttachment(models.Model): """Bridge ir.attachment with Odoo EE `documents.document`. Whenever an attachment is created on one of the Fusion Plating QMS record types (NCR, CAPA, FAIR, Doc Control) we silently mirror it as a `documents.document` record inside the "Plating — Quality" workspace, tagged with the corresponding record type. The original `ir.attachment` record is untouched and continues to live on the quality record as before — the bridge is purely additive. Design notes ------------ * We resolve the folder and tag XML ids via ``env.ref`` with ``raise_if_not_found=False`` so that a partial install, a missing demo record, or a future schema change can never break attachment creation on a quality record — the worst case is that the `documents.document` mirror record isn't created and a line goes to the log. * The write is wrapped in a broad try/except for the same reason: user-facing attachment creation must never be blocked by a bridge failure. * We use ``sudo()`` on the `documents.document` create because the user uploading the attachment may not have write access to the Documents app — the bridge is a system-level convenience. """ _inherit = 'ir.attachment' @api.model_create_multi def create(self, vals_list): attachments = super().create(vals_list) try: self._fusion_plating_bridge_promote_to_documents(attachments) except Exception: # pragma: no cover - defensive only _logger.exception( "Fusion Plating Documents bridge: failed to promote attachments %s", attachments.ids, ) return attachments def _fusion_plating_bridge_promote_to_documents(self, attachments): """Create `documents.document` mirror records for quality attachments. Silently skips if: - the documents module isn't in the registry (defensive, the manifest already depends on it but this module may be tested on CE) - the target folder hasn't been created yet - the attachment is not attached to a quality record """ if 'documents.document' not in self.env: return folder = self.env.ref(_FOLDER_XMLID, raise_if_not_found=False) if not folder: _logger.warning( "Fusion Plating Documents bridge: target folder %s not found", _FOLDER_XMLID, ) return Document = self.env['documents.document'].sudo() # Cache tag lookups across the batch so we don't hit env.ref per attachment. tag_cache = {} for att in attachments: if att.res_model not in _QUALITY_MODELS_TO_TAG: continue # Skip attachments linked to a specific field (e.g. image_1920) — # those are UI artefacts, not user-uploaded docs. if att.res_field: continue # Skip records that have no concrete res_id (drafts being built). if not att.res_id: continue tag_xmlid = _QUALITY_MODELS_TO_TAG[att.res_model] if tag_xmlid not in tag_cache: tag = self.env.ref(tag_xmlid, raise_if_not_found=False) tag_cache[tag_xmlid] = tag.id if tag else False tag_id = tag_cache[tag_xmlid] doc_vals = { 'attachment_id': att.id, 'folder_id': folder.id, 'name': att.name or 'Untitled', } if tag_id: doc_vals['tag_ids'] = [(4, tag_id)] try: Document.create(doc_vals) except Exception: # pragma: no cover - defensive only _logger.exception( "Fusion Plating Documents bridge: could not create " "documents.document for attachment id=%s (res_model=%s, res_id=%s)", att.id, att.res_model, att.res_id, )