# -*- coding: utf-8 -*- # Copyright 2024-2025 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Claim Assistant product family. import logging from odoo import models, api, _ _logger = logging.getLogger(__name__) class FusionCentralConfig(models.TransientModel): _name = 'fusion_claims.config' _description = 'Fusion Central Configuration Manager' # ========================================================================= # ACTION METHODS # ========================================================================= def action_detect_existing_fields(self): """Detect existing custom x_* fields and map them.""" ICP = self.env['ir.config_parameter'].sudo() IrModelFields = self.env['ir.model.fields'].sudo() detected = [] detected_details = [] # Search for all custom fields on relevant models models_to_search = ['sale.order', 'sale.order.line', 'account.move', 'account.move.line', 'product.template'] # Find all custom x_* fields all_custom_fields = IrModelFields.search([ ('model', 'in', models_to_search), ('name', '=like', 'x_%'), ('state', '=', 'manual'), ]) _logger.debug("Found %d custom fields across models", len(all_custom_fields)) # Field patterns to detect (model, keywords, param_key, display_name) # Keywords are checked if they appear anywhere in the field name # NOTE: param_key must match the config_parameter in res_config_settings.py field_mappings = [ # Sale Order header fields ('sale.order', ['sale_type', 'saletype', 'type_of_sale'], 'fusion_claims.field_sale_type', 'Sale Type'), ('sale.order', ['client_type', 'clienttype', 'customer_type'], 'fusion_claims.field_so_client_type', 'SO Client Type'), ('sale.order', ['authorizer', 'authorized', 'approver'], 'fusion_claims.field_so_authorizer', 'SO Authorizer'), ('sale.order', ['claim_number', 'claimnumber', 'claim_no', 'claim_num'], 'fusion_claims.field_so_claim_number', 'SO Claim Number'), ('sale.order', ['client_ref_1', 'clientref1', 'reference_1'], 'fusion_claims.field_so_client_ref_1', 'SO Client Ref 1'), ('sale.order', ['client_ref_2', 'clientref2', 'reference_2'], 'fusion_claims.field_so_client_ref_2', 'SO Client Ref 2'), ('sale.order', ['delivery_date', 'deliverydate', 'adp_delivery'], 'fusion_claims.field_so_delivery_date', 'SO Delivery Date'), ('sale.order', ['service_start', 'servicestart'], 'fusion_claims.field_so_service_start', 'SO Service Start'), ('sale.order', ['service_end', 'serviceend'], 'fusion_claims.field_so_service_end', 'SO Service End'), ('sale.order', ['adp_status', 'adpstatus'], 'fusion_claims.field_so_adp_status', 'SO ADP Status'), # Sale Order line fields ('sale.order.line', ['serial', 'sn', 's_n'], 'fusion_claims.field_sol_serial', 'SO Line Serial'), ('sale.order.line', ['placement', 'device_placement'], 'fusion_claims.field_sol_placement', 'SO Line Placement'), # Invoice header fields ('account.move', ['invoice_type', 'invoicetype', 'inv_type', 'type_of_invoice'], 'fusion_claims.field_invoice_type', 'Invoice Type'), ('account.move', ['client_type', 'clienttype', 'customer_type'], 'fusion_claims.field_inv_client_type', 'Invoice Client Type'), ('account.move', ['authorizer', 'authorized', 'approver'], 'fusion_claims.field_inv_authorizer', 'Invoice Authorizer'), ('account.move', ['claim_number', 'claimnumber', 'claim_no'], 'fusion_claims.field_inv_claim_number', 'Invoice Claim Number'), ('account.move', ['client_ref_1', 'clientref1', 'reference_1'], 'fusion_claims.field_inv_client_ref_1', 'Invoice Client Ref 1'), ('account.move', ['client_ref_2', 'clientref2', 'reference_2'], 'fusion_claims.field_inv_client_ref_2', 'Invoice Client Ref 2'), ('account.move', ['delivery_date', 'deliverydate', 'adp_delivery'], 'fusion_claims.field_inv_delivery_date', 'Invoice Delivery Date'), ('account.move', ['service_start', 'servicestart'], 'fusion_claims.field_inv_service_start', 'Invoice Service Start'), ('account.move', ['service_end', 'serviceend'], 'fusion_claims.field_inv_service_end', 'Invoice Service End'), # Invoice line fields ('account.move.line', ['serial', 'sn', 's_n'], 'fusion_claims.field_aml_serial', 'Invoice Line Serial'), ('account.move.line', ['placement', 'device_placement'], 'fusion_claims.field_aml_placement', 'Invoice Line Placement'), # Product fields ('product.template', ['adp_device', 'adp_code', 'adp_sku', 'device_code', 'sku'], 'fusion_claims.field_product_code', 'Product ADP Code'), ] for model, keywords, param_key, display_name in field_mappings: # Find fields on this model that contain any of the keywords model_fields = all_custom_fields.filtered(lambda f: f.model == model) model_fields_sorted = sorted(model_fields, key=lambda f: f.name) matched_field = None for field in model_fields_sorted: field_name_lower = field.name.lower() for keyword in keywords: if keyword in field_name_lower: # Skip our own x_fc_* fields - we want to find other custom fields if field.name.startswith('x_fc_'): continue matched_field = field break if matched_field: break if matched_field: ICP.set_param(param_key, matched_field.name) detected.append(matched_field.name) detected_details.append(f"• {display_name}: {matched_field.name} ({model})") _logger.debug("Mapped %s -> %s on %s", param_key, matched_field.name, model) # Also list any unmapped custom fields for reference unmapped = [] for field in all_custom_fields: if field.name not in detected: unmapped.append(f"{field.model}.{field.name}") if detected_details: message = _("Detected and mapped %d fields:\n%s") % (len(detected), "\n".join(detected_details)) if unmapped: message += _("\n\nOther custom fields found (not mapped):\n• ") + "\n• ".join(unmapped[:10]) if len(unmapped) > 10: message += f"\n... and {len(unmapped) - 10} more" message += _("\n\n⚠️ IMPORTANT: Save settings and reload page to see changes.") else: message = _("No matching fields found.\n\nCustom fields found:\n• ") + "\n• ".join(unmapped[:15]) if unmapped else _("No custom fields found on relevant models.") return { 'type': 'ir.actions.client', 'tag': 'display_notification', 'params': { 'title': _("Field Detection Complete"), 'message': message, 'type': 'success' if detected else 'warning', 'sticky': True, } } # (Migration and field protection methods removed)