Skip to content

feat: add sync_inactive_employee for missing employee attributes#922

Open
ruuushhh wants to merge 1 commit intomasterfrom
sync-inactive-employee
Open

feat: add sync_inactive_employee for missing employee attributes#922
ruuushhh wants to merge 1 commit intomasterfrom
sync-inactive-employee

Conversation

@ruuushhh
Copy link
Contributor

Description

If an expense has an inactive employee that doesn't exist in expense attributes, sync that employee from Fyle before validating mappings.

Clickup

https://app.clickup.com/

If an expense has an inactive employee that doesn't exist in expense
attributes, sync that employee from Fyle before validating mappings.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions bot added the size/M Medium PR label Feb 11, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

Two new helper functions were introduced to centralize employee attribute fetching logic in the Sage Intacct tasks module. The get_employee_expense_attribute function retrieves an existing EMPLOYEE expense attribute by value and workspace, while sync_inactive_employee creates a new attribute by syncing an inactive employee from Fyle. Both create_or_update_employee_mapping and __validate_employee_mapping were updated to use these helpers with fallback mechanisms for handling missing employee attributes.

Poem

🎯 Two helpers rise to fetch and sync,
Where inactive employees once left a gap,
Centralizing logic with a gentle link—
No duplication in the mapping map,
Clean, focused functions seal the gap! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding sync_inactive_employee functionality for handling missing employee attributes.
Description check ✅ Passed The description covers the main objective but the ClickUp link is incomplete (just placeholder URL).
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/sage_intacct/tasks.py`:
- Around line 545-577: Add a docstring to the public function
sync_inactive_employee explaining purpose, args and return value; also make
email case-consistent when creating and re-fetching the attribute to avoid
lookup failures (e.g., normalize to lower-case). Specifically, in
sync_inactive_employee ensure the attribute 'value' is set using a normalized
email (reference fyle_employee['user']['email'] and set to .lower()) and then
call get_employee_expense_attribute with the same normalized value derived from
expense_group.description.get('employee_email').lower(); keep using
ExpenseAttribute.bulk_create_or_update_expense_attributes and
get_employee_expense_attribute as the post-create lookup points.

Comment on lines +545 to +577
def sync_inactive_employee(expense_group: ExpenseGroup) -> ExpenseAttribute:
try:
fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
platform = PlatformConnector(fyle_credentials=fyle_credentials)

fyle_employee = platform.employees.get_employee_by_email(expense_group.description.get('employee_email'))
if len(fyle_employee):
fyle_employee = fyle_employee[0]
attribute = {
'attribute_type': 'EMPLOYEE',
'display_name': 'Employee',
'value': fyle_employee['user']['email'],
'source_id': fyle_employee['id'],
'active': True if fyle_employee['is_enabled'] and fyle_employee['has_accepted_invite'] else False,
'detail': {
'user_id': fyle_employee['user_id'],
'employee_code': fyle_employee['code'],
'full_name': fyle_employee['user']['full_name'],
'location': fyle_employee['location'],
'department': fyle_employee['department']['name'] if fyle_employee['department'] else None,
'department_id': fyle_employee['department_id'],
'department_code': fyle_employee['department']['code'] if fyle_employee['department'] else None
}
}
ExpenseAttribute.bulk_create_or_update_expense_attributes([attribute], 'EMPLOYEE', expense_group.workspace_id, True)
return get_employee_expense_attribute(expense_group.description.get('employee_email'), expense_group.workspace_id)
except (FyleInvalidTokenError, FyleInternalServerError) as e:
logger.info('Invalid Fyle refresh token or internal server error for workspace %s: %s', expense_group.workspace_id, str(e))
return None

except Exception as e:
logger.error('Error syncing inactive employee for workspace_id %s: %s', expense_group.workspace_id, str(e))
return None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing docstring causes CI failure; also a potential case-sensitivity bug on re-fetch.

Two issues:

  1. Pipeline failure: flake8: D103 Missing docstring in public function on line 545. Add a docstring.

  2. Bug: Line 569 stores the attribute with value=fyle_employee['user']['email'], but line 570 re-fetches using expense_group.description.get('employee_email'). If the Fyle API returns a differently-cased email (e.g. User@example.com vs user@example.com), the exact value= filter in get_employee_expense_attribute won't find the just-created record, and the function returns None despite a successful sync.

Proposed fix
 def sync_inactive_employee(expense_group: ExpenseGroup) -> ExpenseAttribute:
+    """
+    Sync inactive employee from Fyle
+    :param expense_group: Expense Group
+    :return: ExpenseAttribute or None
+    """
     try:
         fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
         platform = PlatformConnector(fyle_credentials=fyle_credentials)
 
         fyle_employee = platform.employees.get_employee_by_email(expense_group.description.get('employee_email'))
         if len(fyle_employee):
             fyle_employee = fyle_employee[0]
             attribute = {
                 'attribute_type': 'EMPLOYEE',
                 'display_name': 'Employee',
                 'value': fyle_employee['user']['email'],
                 'source_id': fyle_employee['id'],
-                'active': True if fyle_employee['is_enabled'] and fyle_employee['has_accepted_invite'] else False,
+                'active': fyle_employee['is_enabled'] and fyle_employee['has_accepted_invite'],
                 'detail': {
                     'user_id': fyle_employee['user_id'],
                     'employee_code': fyle_employee['code'],
                     'full_name': fyle_employee['user']['full_name'],
                     'location': fyle_employee['location'],
                     'department': fyle_employee['department']['name'] if fyle_employee['department'] else None,
                     'department_id': fyle_employee['department_id'],
                     'department_code': fyle_employee['department']['code'] if fyle_employee['department'] else None
                 }
             }
             ExpenseAttribute.bulk_create_or_update_expense_attributes([attribute], 'EMPLOYEE', expense_group.workspace_id, True)
-            return get_employee_expense_attribute(expense_group.description.get('employee_email'), expense_group.workspace_id)
+            return get_employee_expense_attribute(fyle_employee['user']['email'], expense_group.workspace_id)
     except (FyleInvalidTokenError, FyleInternalServerError) as e:
         logger.info('Invalid Fyle refresh token or internal server error for workspace %s: %s', expense_group.workspace_id, str(e))
         return None
 
     except Exception as e:
-        logger.error('Error syncing inactive employee for workspace_id %s: %s', expense_group.workspace_id, str(e))
+        logger.exception('Error syncing inactive employee for workspace_id %s: %s', expense_group.workspace_id, str(e))
         return None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def sync_inactive_employee(expense_group: ExpenseGroup) -> ExpenseAttribute:
try:
fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
platform = PlatformConnector(fyle_credentials=fyle_credentials)
fyle_employee = platform.employees.get_employee_by_email(expense_group.description.get('employee_email'))
if len(fyle_employee):
fyle_employee = fyle_employee[0]
attribute = {
'attribute_type': 'EMPLOYEE',
'display_name': 'Employee',
'value': fyle_employee['user']['email'],
'source_id': fyle_employee['id'],
'active': True if fyle_employee['is_enabled'] and fyle_employee['has_accepted_invite'] else False,
'detail': {
'user_id': fyle_employee['user_id'],
'employee_code': fyle_employee['code'],
'full_name': fyle_employee['user']['full_name'],
'location': fyle_employee['location'],
'department': fyle_employee['department']['name'] if fyle_employee['department'] else None,
'department_id': fyle_employee['department_id'],
'department_code': fyle_employee['department']['code'] if fyle_employee['department'] else None
}
}
ExpenseAttribute.bulk_create_or_update_expense_attributes([attribute], 'EMPLOYEE', expense_group.workspace_id, True)
return get_employee_expense_attribute(expense_group.description.get('employee_email'), expense_group.workspace_id)
except (FyleInvalidTokenError, FyleInternalServerError) as e:
logger.info('Invalid Fyle refresh token or internal server error for workspace %s: %s', expense_group.workspace_id, str(e))
return None
except Exception as e:
logger.error('Error syncing inactive employee for workspace_id %s: %s', expense_group.workspace_id, str(e))
return None
def sync_inactive_employee(expense_group: ExpenseGroup) -> ExpenseAttribute:
"""
Sync inactive employee from Fyle
:param expense_group: Expense Group
:return: ExpenseAttribute or None
"""
try:
fyle_credentials = FyleCredential.objects.get(workspace_id=expense_group.workspace_id)
platform = PlatformConnector(fyle_credentials=fyle_credentials)
fyle_employee = platform.employees.get_employee_by_email(expense_group.description.get('employee_email'))
if len(fyle_employee):
fyle_employee = fyle_employee[0]
attribute = {
'attribute_type': 'EMPLOYEE',
'display_name': 'Employee',
'value': fyle_employee['user']['email'],
'source_id': fyle_employee['id'],
'active': fyle_employee['is_enabled'] and fyle_employee['has_accepted_invite'],
'detail': {
'user_id': fyle_employee['user_id'],
'employee_code': fyle_employee['code'],
'full_name': fyle_employee['user']['full_name'],
'location': fyle_employee['location'],
'department': fyle_employee['department']['name'] if fyle_employee['department'] else None,
'department_id': fyle_employee['department_id'],
'department_code': fyle_employee['department']['code'] if fyle_employee['department'] else None
}
}
ExpenseAttribute.bulk_create_or_update_expense_attributes([attribute], 'EMPLOYEE', expense_group.workspace_id, True)
return get_employee_expense_attribute(fyle_employee['user']['email'], expense_group.workspace_id)
except (FyleInvalidTokenError, FyleInternalServerError) as e:
logger.info('Invalid Fyle refresh token or internal server error for workspace %s: %s', expense_group.workspace_id, str(e))
return None
except Exception as e:
logger.exception('Error syncing inactive employee for workspace_id %s: %s', expense_group.workspace_id, str(e))
return None
🧰 Tools
🪛 GitHub Actions: Continuous Integration

[error] 545-545: flake8: D103 Missing docstring in public function

🪛 Ruff (0.15.0)

[warning] 575-575: Do not catch blind exception: Exception

(BLE001)


[warning] 576-576: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
In `@apps/sage_intacct/tasks.py` around lines 545 - 577, Add a docstring to the
public function sync_inactive_employee explaining purpose, args and return
value; also make email case-consistent when creating and re-fetching the
attribute to avoid lookup failures (e.g., normalize to lower-case).
Specifically, in sync_inactive_employee ensure the attribute 'value' is set
using a normalized email (reference fyle_employee['user']['email'] and set to
.lower()) and then call get_employee_expense_attribute with the same normalized
value derived from expense_group.description.get('employee_email').lower(); keep
using ExpenseAttribute.bulk_create_or_update_expense_attributes and
get_employee_expense_attribute as the post-create lookup points.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Medium PR

Development

Successfully merging this pull request may close these issues.

1 participant