feat: add sync_inactive_employee for missing employee attributes#922
feat: add sync_inactive_employee for missing employee attributes#922
Conversation
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>
📝 WalkthroughWalkthroughTwo new helper functions were introduced to centralize employee attribute fetching logic in the Sage Intacct tasks module. The Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ 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. Comment |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
Missing docstring causes CI failure; also a potential case-sensitivity bug on re-fetch.
Two issues:
-
Pipeline failure:
flake8: D103 Missing docstring in public functionon line 545. Add a docstring. -
Bug: Line 569 stores the attribute with
value=fyle_employee['user']['email'], but line 570 re-fetches usingexpense_group.description.get('employee_email'). If the Fyle API returns a differently-cased email (e.g.User@example.comvsuser@example.com), the exactvalue=filter inget_employee_expense_attributewon't find the just-created record, and the function returnsNonedespite 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.
| 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.
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/