Initial commit
This commit is contained in:
460
fusion_claims/wizard/field_mapping_config_wizard.py
Normal file
460
fusion_claims/wizard/field_mapping_config_wizard.py
Normal file
@@ -0,0 +1,460 @@
|
||||
# -*- 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, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# DEFAULT FIELD MAPPINGS
|
||||
# =============================================================================
|
||||
# These define the default FC field mappings and their config parameter keys
|
||||
|
||||
DEFAULT_FIELD_MAPPINGS = [
|
||||
# Sale Order Header Fields
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Sale Type',
|
||||
'default_fc_field': 'x_fc_sale_type',
|
||||
'config_param_key': 'fusion_claims.field_sale_type',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Client Type',
|
||||
'default_fc_field': 'x_fc_client_type',
|
||||
'config_param_key': 'fusion_claims.field_so_client_type',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Authorizer',
|
||||
'default_fc_field': 'x_fc_authorizer_id',
|
||||
'config_param_key': 'fusion_claims.field_so_authorizer',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Claim Number',
|
||||
'default_fc_field': 'x_fc_claim_number',
|
||||
'config_param_key': 'fusion_claims.field_so_claim_number',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Client Reference 1',
|
||||
'default_fc_field': 'x_fc_client_ref_1',
|
||||
'config_param_key': 'fusion_claims.field_so_client_ref_1',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Client Reference 2',
|
||||
'default_fc_field': 'x_fc_client_ref_2',
|
||||
'config_param_key': 'fusion_claims.field_so_client_ref_2',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Delivery Date',
|
||||
'default_fc_field': 'x_fc_adp_delivery_date',
|
||||
'config_param_key': 'fusion_claims.field_so_delivery_date',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Service Start Date',
|
||||
'default_fc_field': 'x_fc_service_start_date',
|
||||
'config_param_key': 'fusion_claims.field_so_service_start',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Service End Date',
|
||||
'default_fc_field': 'x_fc_service_end_date',
|
||||
'config_param_key': 'fusion_claims.field_so_service_end',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'ADP Status',
|
||||
'default_fc_field': 'x_fc_adp_status',
|
||||
'config_param_key': 'fusion_claims.field_so_adp_status',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order',
|
||||
'label': 'Primary Serial Number',
|
||||
'default_fc_field': 'x_fc_primary_serial',
|
||||
'config_param_key': 'fusion_claims.field_so_primary_serial',
|
||||
},
|
||||
# Sale Order Line Fields
|
||||
{
|
||||
'model_name': 'sale.order.line',
|
||||
'label': 'Serial Number',
|
||||
'default_fc_field': 'x_fc_serial_number',
|
||||
'config_param_key': 'fusion_claims.field_sol_serial',
|
||||
},
|
||||
{
|
||||
'model_name': 'sale.order.line',
|
||||
'label': 'Device Placement',
|
||||
'default_fc_field': 'x_fc_device_placement',
|
||||
'config_param_key': 'fusion_claims.field_sol_placement',
|
||||
},
|
||||
# Invoice Header Fields
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Invoice Type',
|
||||
'default_fc_field': 'x_fc_invoice_type',
|
||||
'config_param_key': 'fusion_claims.field_invoice_type',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Client Type',
|
||||
'default_fc_field': 'x_fc_client_type',
|
||||
'config_param_key': 'fusion_claims.field_inv_client_type',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Authorizer',
|
||||
'default_fc_field': 'x_fc_authorizer_id',
|
||||
'config_param_key': 'fusion_claims.field_inv_authorizer',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Claim Number',
|
||||
'default_fc_field': 'x_fc_claim_number',
|
||||
'config_param_key': 'fusion_claims.field_inv_claim_number',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Client Reference 1',
|
||||
'default_fc_field': 'x_fc_client_ref_1',
|
||||
'config_param_key': 'fusion_claims.field_inv_client_ref_1',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Client Reference 2',
|
||||
'default_fc_field': 'x_fc_client_ref_2',
|
||||
'config_param_key': 'fusion_claims.field_inv_client_ref_2',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Delivery Date',
|
||||
'default_fc_field': 'x_fc_adp_delivery_date',
|
||||
'config_param_key': 'fusion_claims.field_inv_delivery_date',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Service Start Date',
|
||||
'default_fc_field': 'x_fc_service_start_date',
|
||||
'config_param_key': 'fusion_claims.field_inv_service_start',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Service End Date',
|
||||
'default_fc_field': 'x_fc_service_end_date',
|
||||
'config_param_key': 'fusion_claims.field_inv_service_end',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move',
|
||||
'label': 'Primary Serial Number',
|
||||
'default_fc_field': 'x_fc_primary_serial',
|
||||
'config_param_key': 'fusion_claims.field_inv_primary_serial',
|
||||
},
|
||||
# Invoice Line Fields
|
||||
{
|
||||
'model_name': 'account.move.line',
|
||||
'label': 'Serial Number',
|
||||
'default_fc_field': 'x_fc_serial_number',
|
||||
'config_param_key': 'fusion_claims.field_aml_serial',
|
||||
},
|
||||
{
|
||||
'model_name': 'account.move.line',
|
||||
'label': 'Device Placement',
|
||||
'default_fc_field': 'x_fc_device_placement',
|
||||
'config_param_key': 'fusion_claims.field_aml_placement',
|
||||
},
|
||||
# Product Fields
|
||||
{
|
||||
'model_name': 'product.template',
|
||||
'label': 'ADP Device Code',
|
||||
'default_fc_field': 'x_fc_adp_device_code',
|
||||
'config_param_key': 'fusion_claims.field_product_code',
|
||||
},
|
||||
{
|
||||
'model_name': 'product.template',
|
||||
'label': 'ADP Price',
|
||||
'default_fc_field': 'x_fc_adp_price',
|
||||
'config_param_key': 'fusion_claims.field_product_adp_price',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# AUTO-DETECT PATTERNS
|
||||
# =============================================================================
|
||||
# Keywords to look for when auto-detecting existing custom fields
|
||||
|
||||
AUTO_DETECT_PATTERNS = {
|
||||
# Sale Order Header
|
||||
'fusion_claims.field_sale_type': ['sale_type', 'saletype', 'type_of_sale', 'order_type'],
|
||||
'fusion_claims.field_so_client_type': ['client_type', 'clienttype', 'customer_type', 'cust_type'],
|
||||
'fusion_claims.field_so_authorizer': ['authorizer', 'authorized', 'approver', 'authorizer_name'],
|
||||
'fusion_claims.field_so_claim_number': ['claim_number', 'claimnumber', 'claim_no', 'adp_claim', 'claim_num'],
|
||||
'fusion_claims.field_so_client_ref_1': ['client_ref_1', 'clientref1', 'reference_1', 'client_reference_1', 'ref1', 'ref_1'],
|
||||
'fusion_claims.field_so_client_ref_2': ['client_ref_2', 'clientref2', 'reference_2', 'client_reference_2', 'ref2', 'ref_2'],
|
||||
'fusion_claims.field_so_delivery_date': ['delivery_date', 'deliverydate', 'adp_delivery', 'deliver_date', 'date_delivery'],
|
||||
'fusion_claims.field_so_service_start': ['service_start', 'servicestart', 'start_date', 'service_start_date', 'svc_start'],
|
||||
'fusion_claims.field_so_service_end': ['service_end', 'serviceend', 'end_date', 'service_end_date', 'svc_end'],
|
||||
'fusion_claims.field_so_adp_status': ['adp_status', 'adpstatus', 'claim_status', 'status'],
|
||||
'fusion_claims.field_so_primary_serial': ['primary_serial', 'primaryserial', 'main_serial', 'serial_primary'],
|
||||
# Sale Order Line
|
||||
'fusion_claims.field_sol_serial': ['serial_number', 'serial', 'sn', 'serialno'],
|
||||
'fusion_claims.field_sol_placement': ['placement', 'device_placement', 'place'],
|
||||
# Invoice Header
|
||||
'fusion_claims.field_invoice_type': ['invoice_type', 'invoicetype', 'inv_type', 'type_of_invoice', 'bill_type'],
|
||||
'fusion_claims.field_inv_client_type': ['client_type', 'clienttype', 'customer_type', 'cust_type'],
|
||||
'fusion_claims.field_inv_authorizer': ['authorizer', 'authorized', 'approver', 'authorizer_name'],
|
||||
'fusion_claims.field_inv_claim_number': ['claim_number', 'claimnumber', 'claim_no', 'claim_num'],
|
||||
'fusion_claims.field_inv_client_ref_1': ['client_ref_1', 'clientref1', 'reference_1', 'client_reference_1', 'ref1', 'ref_1'],
|
||||
'fusion_claims.field_inv_client_ref_2': ['client_ref_2', 'clientref2', 'reference_2', 'client_reference_2', 'ref2', 'ref_2'],
|
||||
'fusion_claims.field_inv_delivery_date': ['delivery_date', 'deliverydate', 'adp_delivery', 'deliver_date', 'date_delivery'],
|
||||
'fusion_claims.field_inv_service_start': ['service_start', 'servicestart', 'start_date', 'service_start_date', 'svc_start'],
|
||||
'fusion_claims.field_inv_service_end': ['service_end', 'serviceend', 'end_date', 'service_end_date', 'svc_end'],
|
||||
'fusion_claims.field_inv_primary_serial': ['primary_serial', 'primaryserial', 'main_serial', 'serial_primary'],
|
||||
# Invoice Line
|
||||
'fusion_claims.field_aml_serial': ['serial_number', 'serial', 'sn', 'serialno'],
|
||||
'fusion_claims.field_aml_placement': ['placement', 'device_placement', 'place'],
|
||||
# Product
|
||||
'fusion_claims.field_product_code': ['adp_code', 'adp_device', 'device_code', 'adp_sku', 'product_code'],
|
||||
'fusion_claims.field_product_adp_price': ['adp_price', 'adp_retail_price', 'retail_price'],
|
||||
}
|
||||
|
||||
|
||||
class FieldMappingLine(models.TransientModel):
|
||||
"""Individual field mapping configuration line."""
|
||||
_name = 'fusion_claims.field_mapping_line'
|
||||
_description = 'Field Mapping Line'
|
||||
_order = 'sequence, id'
|
||||
|
||||
wizard_id = fields.Many2one(
|
||||
'fusion_claims.field_mapping_config',
|
||||
string='Wizard',
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
sequence = fields.Integer(default=10)
|
||||
|
||||
model_name = fields.Selection(
|
||||
selection=[
|
||||
('sale.order', 'Sale Order'),
|
||||
('sale.order.line', 'Sale Order Line'),
|
||||
('account.move', 'Invoice'),
|
||||
('account.move.line', 'Invoice Line'),
|
||||
('product.template', 'Product'),
|
||||
],
|
||||
string='Model',
|
||||
)
|
||||
|
||||
label = fields.Char(string='Field Label')
|
||||
field_name = fields.Char(string='Field Name', help='The field name to use on the model')
|
||||
default_fc_field = fields.Char(string='Default FC Field')
|
||||
config_param_key = fields.Char(string='Config Parameter')
|
||||
|
||||
field_exists = fields.Boolean(
|
||||
string='Valid',
|
||||
compute='_compute_field_exists',
|
||||
help='Does this field exist on the model?',
|
||||
)
|
||||
|
||||
@api.depends('field_name', 'model_name')
|
||||
def _compute_field_exists(self):
|
||||
"""Check if the configured field exists on the model."""
|
||||
IrModelFields = self.env['ir.model.fields'].sudo()
|
||||
for line in self:
|
||||
line.field_exists = False
|
||||
if not line.field_name or not line.model_name:
|
||||
continue
|
||||
# Check database for field existence (more reliable for custom fields)
|
||||
field_count = IrModelFields.search_count([
|
||||
('model', '=', line.model_name),
|
||||
('name', '=', line.field_name),
|
||||
])
|
||||
line.field_exists = field_count > 0
|
||||
|
||||
@api.onchange('field_name')
|
||||
def _onchange_field_name(self):
|
||||
"""Force recomputation of field_exists when field_name changes."""
|
||||
# This triggers the compute to run when user edits the field
|
||||
pass
|
||||
|
||||
|
||||
class FieldMappingConfigWizard(models.TransientModel):
|
||||
"""Wizard for configuring field mappings."""
|
||||
_name = 'fusion_claims.field_mapping_config'
|
||||
_description = 'Field Mapping Configuration Wizard'
|
||||
|
||||
# =========================================================================
|
||||
# FIELD MAPPINGS
|
||||
# =========================================================================
|
||||
mapping_ids = fields.One2many(
|
||||
'fusion_claims.field_mapping_line',
|
||||
'wizard_id',
|
||||
string='Field Mappings',
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# SUMMARY FIELDS
|
||||
# =========================================================================
|
||||
total_mappings = fields.Integer(
|
||||
string='Total Mappings',
|
||||
compute='_compute_summary',
|
||||
)
|
||||
valid_mappings = fields.Integer(
|
||||
string='Valid Mappings',
|
||||
compute='_compute_summary',
|
||||
)
|
||||
invalid_mappings = fields.Integer(
|
||||
string='Invalid Mappings',
|
||||
compute='_compute_summary',
|
||||
)
|
||||
|
||||
@api.depends('mapping_ids.field_exists')
|
||||
def _compute_summary(self):
|
||||
"""Compute summary statistics."""
|
||||
for wizard in self:
|
||||
wizard.total_mappings = len(wizard.mapping_ids)
|
||||
wizard.valid_mappings = len(wizard.mapping_ids.filtered('field_exists'))
|
||||
wizard.invalid_mappings = wizard.total_mappings - wizard.valid_mappings
|
||||
|
||||
# =========================================================================
|
||||
# DEFAULT VALUES
|
||||
# =========================================================================
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
"""Load field mappings from ir.config_parameter when wizard opens."""
|
||||
res = super().default_get(fields_list)
|
||||
|
||||
if 'mapping_ids' in fields_list:
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
mappings = []
|
||||
seq = 10
|
||||
|
||||
for mapping_def in DEFAULT_FIELD_MAPPINGS:
|
||||
# Get current value from config, fall back to default
|
||||
current_value = ICP.get_param(
|
||||
mapping_def['config_param_key'],
|
||||
mapping_def['default_fc_field']
|
||||
)
|
||||
|
||||
mappings.append((0, 0, {
|
||||
'sequence': seq,
|
||||
'model_name': mapping_def['model_name'],
|
||||
'label': mapping_def['label'],
|
||||
'field_name': current_value,
|
||||
'default_fc_field': mapping_def['default_fc_field'],
|
||||
'config_param_key': mapping_def['config_param_key'],
|
||||
}))
|
||||
seq += 10
|
||||
|
||||
res['mapping_ids'] = mappings
|
||||
|
||||
return res
|
||||
|
||||
# =========================================================================
|
||||
# ACTION METHODS
|
||||
# =========================================================================
|
||||
|
||||
def action_save(self):
|
||||
"""Save all field mappings to ir.config_parameter."""
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
saved_count = 0
|
||||
|
||||
for line in self.mapping_ids:
|
||||
if line.config_param_key and line.field_name:
|
||||
ICP.set_param(line.config_param_key, line.field_name)
|
||||
saved_count += 1
|
||||
|
||||
_logger.info("Saved %d field mapping configurations", saved_count)
|
||||
# Return False to indicate no follow-up action needed
|
||||
return False
|
||||
|
||||
def action_save_and_close(self):
|
||||
"""Save mappings and close the wizard."""
|
||||
self.action_save()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def action_reset_defaults(self):
|
||||
"""Reset all mappings to their default FC field values."""
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
reset_count = 0
|
||||
|
||||
for line in self.mapping_ids:
|
||||
if line.config_param_key and line.default_fc_field:
|
||||
# Update the transient record so the form shows the new value
|
||||
line.field_name = line.default_fc_field
|
||||
# Also save to ir.config_parameter so it persists
|
||||
ICP.set_param(line.config_param_key, line.default_fc_field)
|
||||
reset_count += 1
|
||||
|
||||
_logger.info("Reset %d field mappings to defaults", reset_count)
|
||||
# Return False to indicate no follow-up action needed
|
||||
return False
|
||||
|
||||
def action_auto_detect(self):
|
||||
"""Auto-detect existing custom fields and update mappings."""
|
||||
IrModelFields = self.env['ir.model.fields'].sudo()
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
detected_count = 0
|
||||
detected_fields = []
|
||||
|
||||
# Get all custom fields
|
||||
models_to_search = ['sale.order', 'sale.order.line', 'account.move', 'account.move.line', 'product.template']
|
||||
all_custom_fields = IrModelFields.search([
|
||||
('model', 'in', models_to_search),
|
||||
('name', '=like', 'x_%'),
|
||||
('state', '=', 'manual'),
|
||||
])
|
||||
|
||||
for line in self.mapping_ids:
|
||||
if not line.config_param_key:
|
||||
continue
|
||||
|
||||
patterns = AUTO_DETECT_PATTERNS.get(line.config_param_key, [])
|
||||
if not patterns:
|
||||
continue
|
||||
|
||||
# Get fields for this model
|
||||
model_fields = all_custom_fields.filtered(lambda f: f.model == line.model_name)
|
||||
|
||||
model_fields_sorted = sorted(model_fields, key=lambda f: f.name)
|
||||
|
||||
# Find matching field
|
||||
matched_field = None
|
||||
for field in model_fields_sorted:
|
||||
# Skip our own x_fc_* fields
|
||||
if field.name.startswith('x_fc_'):
|
||||
continue
|
||||
|
||||
field_name_lower = field.name.lower()
|
||||
for pattern in patterns:
|
||||
if pattern in field_name_lower:
|
||||
matched_field = field
|
||||
break
|
||||
if matched_field:
|
||||
break
|
||||
|
||||
if matched_field and matched_field.name != line.field_name:
|
||||
# Update the transient record so the form shows the new value
|
||||
line.field_name = matched_field.name
|
||||
# Also save to ir.config_parameter so it persists across sessions
|
||||
ICP.set_param(line.config_param_key, matched_field.name)
|
||||
detected_fields.append(f"{line.label}: {matched_field.name}")
|
||||
detected_count += 1
|
||||
|
||||
# Log results
|
||||
_logger.info("Auto-detected %d Studio field mappings: %s", detected_count, detected_fields)
|
||||
|
||||
# Return False to indicate no follow-up action needed
|
||||
return False
|
||||
|
||||
def action_close(self):
|
||||
"""Close the wizard without saving."""
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
Reference in New Issue
Block a user