@@ -330,7 +330,145 @@ def __init__(
330330 )
331331
332332
333- def _create_batch_individually ( # noqa: C901
333+ def _convert_external_id_field (
334+ model : Any ,
335+ field_name : str ,
336+ field_value : str ,
337+ converted_vals : dict [str , Any ],
338+ ) -> list [str ]:
339+ """Convert an external ID field to a database ID.
340+
341+ Args:
342+ model: The Odoo model object
343+ field_name: The field name (e.g., 'parent_id/id')
344+ field_value: The external ID value
345+ converted_vals: Dictionary to store converted values
346+
347+ Returns:
348+ List of external ID field names that were processed
349+ """
350+ external_id_fields = [field_name ]
351+ base_field_name = field_name [:- 3 ] # Remove '/id' suffix
352+
353+ # Handle empty external ID references
354+ if not field_value :
355+ # Empty external ID means no value for this field
356+ converted_vals [base_field_name ] = False
357+ log .debug (
358+ f"Converted empty external ID { field_name } -> { base_field_name } (False)"
359+ )
360+ else :
361+ # Convert external ID to database ID
362+ try :
363+ # Look up the database ID for this external ID
364+ record_ref = model .browse ().env .ref (field_value , raise_if_not_found = False )
365+ if record_ref :
366+ converted_vals [base_field_name ] = record_ref .id
367+ log .debug (
368+ f"Converted external ID { field_name } ({ field_value } ) -> "
369+ f"{ base_field_name } ({ record_ref .id } )"
370+ )
371+ else :
372+ # If we can't find the external ID, set to False
373+ converted_vals [base_field_name ] = False
374+ log .warning (
375+ f"Could not find record for external ID '{ field_value } ', "
376+ f"setting { base_field_name } to False"
377+ )
378+ except Exception as e :
379+ log .warning (
380+ f"Error looking up external ID '{ field_value } ' for field "
381+ f"'{ field_name } ': { e } "
382+ )
383+ # On error, set to False
384+ converted_vals [base_field_name ] = False
385+
386+ return external_id_fields
387+
388+
389+ def _process_external_id_fields (
390+ model : Any ,
391+ clean_vals : dict [str , Any ],
392+ ) -> tuple [dict [str , Any ], list [str ]]:
393+ """Process all external ID fields in the clean values.
394+
395+ Args:
396+ model: The Odoo model object
397+ clean_vals: Dictionary of clean field values
398+
399+ Returns:
400+ Tuple of (converted_vals, external_id_fields)
401+ """
402+ converted_vals : dict [str , Any ] = {}
403+ external_id_fields : list [str ] = []
404+
405+ for field_name , field_value in clean_vals .items ():
406+ # Handle external ID references (e.g., 'parent_id/id' -> 'parent_id')
407+ if field_name .endswith ("/id" ):
408+ fields = _convert_external_id_field (
409+ model , field_name , field_value , converted_vals
410+ )
411+ external_id_fields .extend (fields )
412+ else :
413+ # Regular field - pass through as-is
414+ converted_vals [field_name ] = field_value
415+
416+ return converted_vals , external_id_fields
417+
418+
419+ def _handle_create_error (
420+ i : int ,
421+ create_error : Exception ,
422+ line : list [Any ],
423+ error_summary : str ,
424+ ) -> tuple [str , list [list [Any ]], str ]:
425+ """Handle errors during record creation.
426+
427+ Args:
428+ i: The row index
429+ create_error: The exception that occurred
430+ line: The data line being processed
431+ error_summary: Current error summary
432+
433+ Returns:
434+ Tuple of (error_message, failed_lines, error_summary)
435+ """
436+ failed_lines = []
437+
438+ # Handle JSON-RPC exceptions that contain IndexErrors
439+ error_str = str (create_error ).lower ()
440+
441+ # Check if this is a JSON-RPC exception containing an IndexError/tuple index error
442+ if "tuple index out of range" in error_str or "indexerror" in error_str :
443+ error_message = f"Tuple unpacking error in row { i + 1 } : { create_error } "
444+ failed_line = [* list (line ), error_message ]
445+ failed_lines .append (failed_line )
446+ if "Fell back to create" in error_summary :
447+ error_summary = "Tuple unpacking error detected"
448+ return error_message , failed_lines , error_summary
449+
450+ # Handle other specific error types
451+ error_message = str (create_error ).replace ("\n " , " | " )
452+
453+ # Handle "tuple index out of range" errors specifically
454+ if "tuple index out of range" in error_message :
455+ error_message = f"Tuple unpacking error in row { i + 1 } : { error_message } "
456+
457+ # Handle invalid field errors (the new issue we discovered)
458+ elif "Invalid field" in error_message and "/id" in error_message :
459+ error_message = (
460+ f"Invalid external ID field detected in row { i + 1 } : { error_message } "
461+ )
462+
463+ failed_line = [* list (line ), error_message ]
464+ failed_lines .append (failed_line )
465+ if "Fell back to create" in error_summary :
466+ error_summary = error_message
467+
468+ return error_message , failed_lines , error_summary
469+
470+
471+ def _create_batch_individually (
334472 model : Any ,
335473 batch_lines : list [list [Any ]],
336474 batch_header : list [str ],
@@ -367,59 +505,15 @@ def _create_batch_individually( # noqa: C901
367505 clean_vals = {
368506 k : v
369507 for k , v in vals .items ()
370- if k .split ("/" )[0 ]
371- not in ignore_set # Allow external ID fields through for conversion
508+ if k .split ("/" )[0 ] not in ignore_set
509+ # Allow external ID fields through for conversion
372510 }
373511
374512 # 3. CREATE
375513 # Convert external ID references to actual database IDs before creating
376- converted_vals = {}
377- external_id_fields = []
378- for field_name , field_value in clean_vals .items ():
379- # Handle external ID references (e.g., 'parent_id/id' -> 'parent_id')
380- if field_name .endswith ("/id" ):
381- external_id_fields .append (field_name )
382- base_field_name = field_name [:- 3 ] # Remove '/id' suffix
383- # Handle empty external ID references
384- if not field_value :
385- # Empty external ID means no value for this field
386- converted_vals [base_field_name ] = False
387- log .debug (
388- f"Converted empty external ID { field_name } -> "
389- f"{ base_field_name } (False)"
390- )
391- else :
392- # Convert external ID to database ID
393- try :
394- # Look up the database ID for this external ID
395- record_ref = model .browse ().env .ref (
396- field_value , raise_if_not_found = False
397- )
398- if record_ref :
399- converted_vals [base_field_name ] = record_ref .id
400- log .debug (
401- f"Converted external ID { field_name } "
402- f"({ field_value } ) -> { base_field_name } "
403- f"({ record_ref .id } )"
404- )
405- else :
406- # If we can't find the external ID, set to False
407- converted_vals [base_field_name ] = False
408- log .warning (
409- f"Could not find record for external ID "
410- f"'{ field_value } ' setting { base_field_name } "
411- f" to False"
412- )
413- except Exception as e :
414- log .warning (
415- f"Error looking up external ID '{ field_value } ' "
416- f"for field '{ field_name } ': { e } "
417- )
418- # On error, set to False
419- converted_vals [base_field_name ] = False
420- else :
421- # Regular field - pass through as-is
422- converted_vals [field_name ] = field_value
514+ converted_vals , external_id_fields = _process_external_id_fields (
515+ model , clean_vals
516+ )
423517
424518 log .debug (f"External ID fields found: { external_id_fields } " )
425519 log .debug (f"Converted vals keys: { list (converted_vals .keys ())} " )
@@ -433,33 +527,10 @@ def _create_batch_individually( # noqa: C901
433527 error_summary = "Malformed CSV row detected"
434528 continue
435529 except Exception as create_error :
436- # Handle JSON-RPC exceptions that contain IndexErrors
437- error_str = str (create_error ).lower ()
438-
439- # Check if this is a JSON-RPC exception containing an
440- # IndexError/tuple index error
441- if "tuple index out of range" in error_str or "indexerror" in error_str :
442- error_message = f"Tuple unpacking error in row { i + 1 } : { create_error } "
443- failed_line = [* list (line ), error_message ]
444- failed_lines .append (failed_line )
445- if "Fell back to create" in error_summary :
446- error_summary = "Tuple unpacking error detected"
447- continue
448-
449- # Handle other specific error types
450- error_message = str (create_error ).replace ("\n " , " | " )
451-
452- # Handle "tuple index out of range" errors specifically
453- if "tuple index out of range" in error_message :
454- error_message = f"Tuple unpacking error in row { i + 1 } : { error_message } "
455-
456- # Handle invalid field errors (the new issue we discovered)
457- elif "Invalid field" in error_message and "/id" in error_message :
458- error_message = "Invalid external ID field detected in row "
459- f"{ i + 1 } : { error_message } "
460-
461- failed_line = [* list (line ), error_message ]
462- failed_lines .append (failed_line )
530+ error_message , new_failed_lines , error_summary = _handle_create_error (
531+ i , create_error , line , error_summary
532+ )
533+ failed_lines .extend (new_failed_lines )
463534 if "Fell back to create" in error_summary :
464535 error_summary = error_message
465536 return {
0 commit comments