Initial commit
This commit is contained in:
191
fusion_claims/wizard/device_import_wizard.py
Normal file
191
fusion_claims/wizard/device_import_wizard.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# -*- 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 base64
|
||||
import csv
|
||||
import json
|
||||
import re
|
||||
import io
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import UserError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeviceCodeImportWizard(models.TransientModel):
|
||||
_name = 'fusion_claims.device.import.wizard'
|
||||
_description = 'Import ADP Device Codes from JSON/CSV'
|
||||
|
||||
file = fields.Binary(
|
||||
string='File',
|
||||
required=True,
|
||||
help='Upload a JSON or CSV file containing device codes',
|
||||
)
|
||||
filename = fields.Char(string='Filename')
|
||||
file_type = fields.Selection([
|
||||
('json', 'JSON'),
|
||||
('csv', 'CSV (ADP Mobility Manual)'),
|
||||
], string='File Type', default='json', required=True)
|
||||
|
||||
# Results
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('done', 'Done'),
|
||||
], default='draft')
|
||||
|
||||
result_created = fields.Integer(string='Created', readonly=True)
|
||||
result_updated = fields.Integer(string='Updated', readonly=True)
|
||||
result_errors = fields.Text(string='Errors', readonly=True)
|
||||
|
||||
@api.onchange('filename')
|
||||
def _onchange_filename(self):
|
||||
"""Auto-detect file type from filename."""
|
||||
if self.filename:
|
||||
if self.filename.lower().endswith('.csv'):
|
||||
self.file_type = 'csv'
|
||||
elif self.filename.lower().endswith('.json'):
|
||||
self.file_type = 'json'
|
||||
|
||||
def _parse_csv_content(self, content):
|
||||
"""Parse CSV content to data list."""
|
||||
data = []
|
||||
|
||||
# Try to decode as UTF-8 with BOM, then fallback
|
||||
try:
|
||||
text = content.decode('utf-8-sig')
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
text = content.decode('latin-1')
|
||||
except Exception:
|
||||
text = content.decode('utf-8', errors='ignore')
|
||||
|
||||
reader = csv.DictReader(io.StringIO(text))
|
||||
|
||||
# Log the column names for debugging
|
||||
_logger.info("CSV columns detected: %s", reader.fieldnames)
|
||||
|
||||
for row in reader:
|
||||
device_code = (row.get('Device Code', '') or '').strip()
|
||||
if not device_code:
|
||||
continue
|
||||
|
||||
# Find the price column - check multiple possible names
|
||||
price = 0.0
|
||||
price_column_names = [
|
||||
'ADP Price', 'adp_price', 'Approved Price', ' Approved Price ',
|
||||
'Price', 'price', 'ADP_Price', 'adp price'
|
||||
]
|
||||
# First try exact matches
|
||||
for col_name in price_column_names:
|
||||
if col_name in row:
|
||||
price_str = row.get(col_name, '')
|
||||
price_str = re.sub(r'[\$,"\'\s]', '', str(price_str))
|
||||
try:
|
||||
price = float(price_str)
|
||||
if price > 0:
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# If still 0, try partial match on column names
|
||||
if price == 0:
|
||||
for key in row.keys():
|
||||
key_lower = key.lower().strip()
|
||||
if 'price' in key_lower:
|
||||
price_str = row.get(key, '')
|
||||
price_str = re.sub(r'[\$,"\'\s]', '', str(price_str))
|
||||
try:
|
||||
price = float(price_str)
|
||||
if price > 0:
|
||||
_logger.info("Found price in column '%s': %s", key, price)
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Find the serial/SN required column - check multiple possible names
|
||||
sn_required = 'No'
|
||||
sn_column_names = [
|
||||
'SN Required', 'sn_required', 'Serial', 'serial',
|
||||
'SN', 'sn', 'Serial Number Required', 'Requires SN'
|
||||
]
|
||||
# First try exact matches
|
||||
for col_name in sn_column_names:
|
||||
if col_name in row:
|
||||
sn_required = row.get(col_name, 'No')
|
||||
break
|
||||
|
||||
# If not found, try partial match
|
||||
if sn_required == 'No':
|
||||
for key in row.keys():
|
||||
key_lower = key.lower().strip()
|
||||
if 'serial' in key_lower or 'sn' in key_lower:
|
||||
sn_required = row.get(key, 'No')
|
||||
_logger.info("Found SN in column '%s': %s", key, sn_required)
|
||||
break
|
||||
|
||||
data.append({
|
||||
'Device Type': row.get('Device Type', ''),
|
||||
'Manufacturer': row.get('Manufacturer', ''),
|
||||
'Device Description': row.get('Device Description', ''),
|
||||
'Device Code': device_code,
|
||||
'Quantity': row.get('Qty', 1) or row.get('Quantity', 1),
|
||||
'ADP Price': price,
|
||||
'SN Required': sn_required,
|
||||
})
|
||||
|
||||
_logger.info("Parsed %d device codes from CSV", len(data))
|
||||
return data
|
||||
|
||||
def action_import(self):
|
||||
"""Import device codes from uploaded file."""
|
||||
self.ensure_one()
|
||||
|
||||
if not self.file:
|
||||
raise UserError(_("Please upload a file."))
|
||||
|
||||
try:
|
||||
# Decode file
|
||||
file_content = base64.b64decode(self.file)
|
||||
|
||||
if self.file_type == 'csv':
|
||||
# Parse CSV
|
||||
data = self._parse_csv_content(file_content)
|
||||
else:
|
||||
# Parse JSON
|
||||
try:
|
||||
text = file_content.decode('utf-8-sig')
|
||||
except UnicodeDecodeError:
|
||||
text = file_content.decode('utf-8')
|
||||
data = json.loads(text)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
raise UserError(_("Invalid JSON file: %s") % str(e))
|
||||
except Exception as e:
|
||||
raise UserError(_("Error reading file: %s") % str(e))
|
||||
|
||||
if not data:
|
||||
raise UserError(_("No valid data found in file."))
|
||||
|
||||
# Import using the model method
|
||||
DeviceCode = self.env['fusion.adp.device.code']
|
||||
result = DeviceCode.import_from_json(data)
|
||||
|
||||
# Update wizard with results
|
||||
self.write({
|
||||
'state': 'done',
|
||||
'result_created': result['created'],
|
||||
'result_updated': result['updated'],
|
||||
'result_errors': '\n'.join(result['errors']) if result['errors'] else '',
|
||||
})
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user