From cf0de18d77e9f0e48833eb4903f93c6e33c5598f Mon Sep 17 00:00:00 2001 From: Ashutosh singh <55102089+Ashutosh619-sudo@users.noreply.github.com> Date: Tue, 24 Dec 2024 20:54:47 +0530 Subject: [PATCH] Feat: Add Mixin to add created_by and updated_by for configuration table (#128) * Feat: Add Mixin to add created_by and updated_by for configuration models * change name * fix pylint * fix linting * correct linting * fixing lint * pylint resolving * still resolving lint * again resolving pylint * ye again resolving pylint * final resolving * ignoring save method override warning * remove unused imports --- .pylintrc | 7 +- fyle_accounting_mappings/models.py | 322 +++++++++++++++++++++-------- fyle_accounting_mappings/urls.py | 3 +- fyle_accounting_mappings/views.py | 2 +- setup.py | 2 +- 5 files changed, 239 insertions(+), 97 deletions(-) diff --git a/.pylintrc b/.pylintrc index 32d2b1e..83a7380 100644 --- a/.pylintrc +++ b/.pylintrc @@ -156,7 +156,8 @@ disable=print-statement, too-many-arguments, too-many-locals, too-few-public-methods, - super-with-arguments + super-with-arguments, + arguments-differ # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option @@ -342,7 +343,7 @@ indent-string=' ' max-line-length=135 # Maximum number of lines in a module. -max-module-lines=1000 +max-module-lines=1200 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. @@ -559,7 +560,7 @@ max-attributes=7 max-bool-expr=6 # Maximum number of branch for function / method body. -max-branches=13 +max-branches=15 # Maximum number of locals for function / method body. max-locals=20 diff --git a/fyle_accounting_mappings/models.py b/fyle_accounting_mappings/models.py index 5c96268..5ee74bf 100644 --- a/fyle_accounting_mappings/models.py +++ b/fyle_accounting_mappings/models.py @@ -43,7 +43,8 @@ def validate_mapping_settings(mappings_settings: List[Dict]): def create_mappings_and_update_flag(mapping_batch: list, set_auto_mapped_flag: bool = True, **kwargs): model_type = kwargs['model_type'] if 'model_type' in kwargs else Mapping if model_type == CategoryMapping: - mappings = CategoryMapping.objects.bulk_create(mapping_batch, batch_size=50) + mappings = CategoryMapping.objects.bulk_create( + mapping_batch, batch_size=50) else: mappings = Mapping.objects.bulk_create(mapping_batch, batch_size=50) @@ -67,7 +68,8 @@ def create_mappings_and_update_flag(mapping_batch: list, set_auto_mapped_flag: b def construct_mapping_payload(employee_source_attributes: list, employee_mapping_preference: str, destination_id_value_map: dict, destination_type: str, workspace_id: int): - existing_source_ids = get_existing_source_ids(destination_type, workspace_id) + existing_source_ids = get_existing_source_ids( + destination_type, workspace_id) mapping_batch = [] for source_attribute in employee_source_attributes: @@ -110,9 +112,12 @@ def get_existing_source_ids(destination_type: str, workspace_id: int): class ExpenseAttributesDeletionCache(models.Model): id = models.AutoField(primary_key=True) - category_ids = ArrayField(default=[], base_field=models.CharField(max_length=255)) - project_ids = ArrayField(default=[], base_field=models.CharField(max_length=255)) - workspace = models.OneToOneField(Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') + category_ids = ArrayField( + default=[], base_field=models.CharField(max_length=255)) + project_ids = ArrayField( + default=[], base_field=models.CharField(max_length=255)) + workspace = models.OneToOneField( + Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') class Meta: db_table = 'expense_attributes_deletion_cache' @@ -123,18 +128,27 @@ class ExpenseAttribute(models.Model): Fyle Expense Attributes """ id = models.AutoField(primary_key=True) - attribute_type = models.CharField(max_length=255, help_text='Type of expense attribute') - display_name = models.CharField(max_length=255, help_text='Display name of expense attribute') - value = models.CharField(max_length=1000, help_text='Value of expense attribute') + attribute_type = models.CharField( + max_length=255, help_text='Type of expense attribute') + display_name = models.CharField( + max_length=255, help_text='Display name of expense attribute') + value = models.CharField( + max_length=1000, help_text='Value of expense attribute') source_id = models.CharField(max_length=255, help_text='Fyle ID') - workspace = models.ForeignKey(Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') - auto_mapped = models.BooleanField(default=False, help_text='Indicates whether the field is auto mapped or not') + workspace = models.ForeignKey( + Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') + auto_mapped = models.BooleanField( + default=False, help_text='Indicates whether the field is auto mapped or not') auto_created = models.BooleanField(default=False, help_text='Indicates whether the field is auto created by the integration') - active = models.BooleanField(null=True, help_text='Indicates whether the fields is active or not') - detail = JSONField(help_text='Detailed expense attributes payload', null=True) - created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime') - updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') + active = models.BooleanField( + null=True, help_text='Indicates whether the fields is active or not') + detail = JSONField( + help_text='Detailed expense attributes payload', null=True) + created_at = models.DateTimeField( + auto_now_add=True, help_text='Created at datetime') + updated_at = models.DateTimeField( + auto_now=True, help_text='Updated at datetime') class Meta: db_table = 'expense_attributes' @@ -165,7 +179,8 @@ def bulk_update_deleted_expense_attributes(attribute_type: str, workspace_id: in :param attribute_type: Attribute type :param workspace_id: Workspace Id """ - expense_attributes_deletion_cache = ExpenseAttributesDeletionCache.objects.get(workspace_id=workspace_id) + expense_attributes_deletion_cache = ExpenseAttributesDeletionCache.objects.get( + workspace_id=workspace_id) attributes_to_be_updated = [] if attribute_type == 'CATEGORY': @@ -258,7 +273,8 @@ def bulk_create_or_update_expense_attributes( ) ) if attributes_to_be_created: - ExpenseAttribute.objects.bulk_create(attributes_to_be_created, batch_size=50) + ExpenseAttribute.objects.bulk_create( + attributes_to_be_created, batch_size=50) if attributes_to_be_updated: ExpenseAttribute.objects.bulk_update( @@ -283,22 +299,33 @@ class DestinationAttribute(models.Model): Destination Expense Attributes """ id = models.AutoField(primary_key=True) - attribute_type = models.CharField(max_length=255, help_text='Type of expense attribute') - display_name = models.CharField(max_length=255, help_text='Display name of attribute') - value = models.CharField(max_length=255, help_text='Value of expense attribute') - destination_id = models.CharField(max_length=255, help_text='Destination ID') - workspace = models.ForeignKey(Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') + attribute_type = models.CharField( + max_length=255, help_text='Type of expense attribute') + display_name = models.CharField( + max_length=255, help_text='Display name of attribute') + value = models.CharField( + max_length=255, help_text='Value of expense attribute') + destination_id = models.CharField( + max_length=255, help_text='Destination ID') + workspace = models.ForeignKey( + Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') auto_created = models.BooleanField(default=False, help_text='Indicates whether the field is auto created by the integration') - active = models.BooleanField(null=True, help_text='Indicates whether the fields is active or not') - detail = JSONField(help_text='Detailed destination attributes payload', null=True) - code = models.CharField(max_length=255, help_text='Code of the attribute', null=True) - created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime') - updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') + active = models.BooleanField( + null=True, help_text='Indicates whether the fields is active or not') + detail = JSONField( + help_text='Detailed destination attributes payload', null=True) + code = models.CharField( + max_length=255, help_text='Code of the attribute', null=True) + created_at = models.DateTimeField( + auto_now_add=True, help_text='Created at datetime') + updated_at = models.DateTimeField( + auto_now=True, help_text='Updated at datetime') class Meta: db_table = 'destination_attributes' - unique_together = ('destination_id', 'attribute_type', 'workspace', 'display_name') + unique_together = ('destination_id', 'attribute_type', + 'workspace', 'display_name') @staticmethod def create_or_update_destination_attribute(attribute: Dict, workspace_id): @@ -344,7 +371,8 @@ def bulk_create_or_update_destination_attributes( :param attributes_disable_callback_path: API func to call when attribute is to be disabled :return: created / updated attributes """ - attribute_destination_id_list = [attribute['destination_id'] for attribute in attributes] + attribute_destination_id_list = [ + attribute['destination_id'] for attribute in attributes] filters = { 'destination_id__in': attribute_destination_id_list, @@ -362,7 +390,8 @@ def bulk_create_or_update_destination_attributes( primary_key_map = {} for existing_attribute in existing_attributes: - existing_attribute_destination_ids.append(existing_attribute['destination_id']) + existing_attribute_destination_ids.append( + existing_attribute['destination_id']) primary_key_map[existing_attribute['destination_id']] = { 'id': existing_attribute['id'], 'value': existing_attribute['value'], @@ -389,43 +418,56 @@ def bulk_create_or_update_destination_attributes( detail=attribute['detail'] if 'detail' in attribute else None, workspace_id=workspace_id, active=attribute['active'] if 'active' in attribute else None, - code=" ".join(attribute['code'].split()) if 'code' in attribute and attribute['code'] else None + code=" ".join(attribute['code'].split( + )) if 'code' in attribute and attribute['code'] else None ) ) else: - if attribute_disable_callback_path and is_import_to_fyle_enabled and ( - (attribute['value'] and primary_key_map[attribute['destination_id']]['value'] and attribute['value'].lower() != primary_key_map[attribute['destination_id']]['value'].lower()) - or ('code' in attribute and attribute['code'] and attribute['code'] != primary_key_map[attribute['destination_id']]['code']) - ): + condition = ( + (attribute['value'] and primary_key_map[attribute['destination_id']]['value'] and + attribute['value'].lower() != primary_key_map[attribute['destination_id']]['value'].lower()) or + ('code' in attribute and attribute['code'] and + attribute['code'] != primary_key_map[attribute['destination_id']]['code']) + ) + if attribute_disable_callback_path and is_import_to_fyle_enabled and condition: attributes_to_disable[attribute['destination_id']] = { - 'value': primary_key_map[attribute['destination_id']]['value'], + 'value': primary_key_map['destination_id']['value'], 'updated_value': attribute['value'], - 'code': primary_key_map[attribute['destination_id']]['code'], + 'code': primary_key_map['destination_id']['code'], 'updated_code': attribute['code'] } - if update and ( - (attribute['value'] != primary_key_map[attribute['destination_id']]['value']) - or ('detail' in attribute and attribute['detail'] != primary_key_map[attribute['destination_id']]['detail']) - or ('active' in attribute and attribute['active'] != primary_key_map[attribute['destination_id']]['active']) - or ('code' in attribute and attribute['code'] and attribute['code'] != primary_key_map[attribute['destination_id']]['code']) - ): + condition = ( + (attribute['value'] != primary_key_map[attribute['destination_id']]['value']) + or ('detail' in attribute and attribute['detail'] != primary_key_map[attribute['destination_id']]['detail']) + or ('active' in attribute and attribute['active'] != primary_key_map[attribute['destination_id']]['active']) + or ('code' in attribute and attribute['code'] and + attribute['code'] != primary_key_map[attribute['destination_id']]['code']) + ) + + if update and condition: attributes_to_be_updated.append( DestinationAttribute( id=primary_key_map[attribute['destination_id']]['id'], value=attribute['value'], detail=attribute['detail'] if 'detail' in attribute else None, active=attribute['active'] if 'active' in attribute else None, - code=" ".join(attribute['code'].split()) if 'code' in attribute and attribute['code'] else None, + code=" ".join(attribute['code'].split( + )) if 'code' in attribute and attribute['code'] else None, updated_at=datetime.now() ) ) if attribute_disable_callback_path and attributes_to_disable: - import_string(attribute_disable_callback_path)(workspace_id, attributes_to_disable, is_import_to_fyle_enabled) + import_string(attribute_disable_callback_path)( + workspace_id, + attributes_to_disable, + is_import_to_fyle_enabled + ) if attributes_to_be_created: - DestinationAttribute.objects.bulk_create(attributes_to_be_created, batch_size=50) + DestinationAttribute.objects.bulk_create( + attributes_to_be_created, batch_size=50) if attributes_to_be_updated: DestinationAttribute.objects.bulk_update( @@ -438,12 +480,17 @@ class ExpenseField(models.Model): """ id = models.AutoField(primary_key=True) - attribute_type = models.CharField(max_length=255, help_text='Attribute Type') + attribute_type = models.CharField( + max_length=255, help_text='Attribute Type') source_field_id = models.IntegerField(help_text='Field ID') - workspace = models.ForeignKey(Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') - is_enabled = models.BooleanField(default=False, help_text='Is the field Enabled') - created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime') - updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') + workspace = models.ForeignKey( + Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') + is_enabled = models.BooleanField( + default=False, help_text='Is the field Enabled') + created_at = models.DateTimeField( + auto_now_add=True, help_text='Created at datetime') + updated_at = models.DateTimeField( + auto_now=True, help_text='Updated at datetime') class Meta: db_table = 'expense_fields' @@ -459,7 +506,8 @@ def create_or_update_expense_fields(attributes: List[Dict], fields_included: Lis for expense_field in attributes: if expense_field['field_name'] in fields_included or expense_field['type'] == 'DEPENDENT_SELECT': expense_fields, _ = ExpenseField.objects.update_or_create( - attribute_type=expense_field['field_name'].replace(' ', '_').upper(), + attribute_type=expense_field['field_name'].replace( + ' ', '_').upper(), workspace_id=workspace_id, defaults={ 'source_field_id': expense_field['id'], @@ -475,11 +523,16 @@ class MappingSetting(models.Model): Mapping Settings """ id = models.AutoField(primary_key=True) - source_field = models.CharField(max_length=255, help_text='Source mapping field') - destination_field = models.CharField(max_length=255, help_text='Destination mapping field') - import_to_fyle = models.BooleanField(default=False, help_text='Import to Fyle or not') - is_custom = models.BooleanField(default=False, help_text='Custom Field or not') - source_placeholder = models.TextField(help_text='placeholder of source field', null=True) + source_field = models.CharField( + max_length=255, help_text='Source mapping field') + destination_field = models.CharField( + max_length=255, help_text='Destination mapping field') + import_to_fyle = models.BooleanField( + default=False, help_text='Import to Fyle or not') + is_custom = models.BooleanField( + default=False, help_text='Custom Field or not') + source_placeholder = models.TextField( + help_text='placeholder of source field', null=True) expense_field = models.ForeignKey( ExpenseField, on_delete=models.PROTECT, help_text='Reference to Expense Field model', related_name='expense_fields', null=True @@ -488,8 +541,10 @@ class MappingSetting(models.Model): Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model', related_name='mapping_settings' ) - created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime') - updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') + created_at = models.DateTimeField( + auto_now_add=True, help_text='Created at datetime') + updated_at = models.DateTimeField( + auto_now=True, help_text='Updated at datetime') class Meta: unique_together = ('source_field', 'destination_field', 'workspace') @@ -527,15 +582,22 @@ class Mapping(models.Model): """ id = models.AutoField(primary_key=True) source_type = models.CharField(max_length=255, help_text='Fyle Enum') - destination_type = models.CharField(max_length=255, help_text='Destination Enum') - source = models.ForeignKey(ExpenseAttribute, on_delete=models.PROTECT, related_name='mapping') - destination = models.ForeignKey(DestinationAttribute, on_delete=models.PROTECT, related_name='mapping') - workspace = models.ForeignKey(Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') - created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime') - updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') + destination_type = models.CharField( + max_length=255, help_text='Destination Enum') + source = models.ForeignKey( + ExpenseAttribute, on_delete=models.PROTECT, related_name='mapping') + destination = models.ForeignKey( + DestinationAttribute, on_delete=models.PROTECT, related_name='mapping') + workspace = models.ForeignKey( + Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') + created_at = models.DateTimeField( + auto_now_add=True, help_text='Created at datetime') + updated_at = models.DateTimeField( + auto_now=True, help_text='Updated at datetime') class Meta: - unique_together = ('source_type', 'source', 'destination_type', 'workspace') + unique_together = ('source_type', 'source', + 'destination_type', 'workspace') db_table = 'mappings' @staticmethod @@ -554,7 +616,8 @@ def create_or_update_mapping(source_type: str, destination_type: str, assert_valid( settings is not None and settings != [], - 'Settings for Destination {0} / Source {1} not found'.format(destination_type, source_type) + 'Settings for Destination {0} / Source {1} not found'.format( + destination_type, source_type) ) mapping, _ = Mapping.objects.update_or_create( @@ -599,7 +662,8 @@ def bulk_create_mappings(destination_attributes: List[DestinationAttribute], sou source_value_id_map = {} for source_attribute in source_attributes: - source_value_id_map[source_attribute.value.lower()] = source_attribute.id + source_value_id_map[source_attribute.value.lower() + ] = source_attribute.id mapping_batch = [] @@ -609,7 +673,8 @@ def bulk_create_mappings(destination_attributes: List[DestinationAttribute], sou Mapping( source_type=source_type, destination_type=destination_type, - source_id=source_value_id_map[destination_attribute.value.lower()], + source_id=source_value_id_map[destination_attribute.value.lower( + )], destination_id=destination_attribute.id, workspace_id=workspace_id ) @@ -634,12 +699,15 @@ def auto_map_employees(destination_type: str, employee_mapping_preference: str, value_to_be_appended = None if employee_mapping_preference == 'EMAIL' and destination_employee.detail \ and destination_employee.detail['email']: - value_to_be_appended = destination_employee.detail['email'].replace('*', '') + value_to_be_appended = destination_employee.detail['email'].replace( + '*', '') elif employee_mapping_preference in ['NAME', 'EMPLOYEE_CODE']: - value_to_be_appended = destination_employee.value.replace('*', '') + value_to_be_appended = destination_employee.value.replace( + '*', '') if value_to_be_appended: - destination_id_value_map[value_to_be_appended.lower()] = destination_employee.id + destination_id_value_map[value_to_be_appended.lower( + )] = destination_employee.id employee_source_attributes_count = ExpenseAttribute.objects.filter( attribute_type='EMPLOYEE', workspace_id=workspace_id, auto_mapped=False @@ -652,7 +720,8 @@ def auto_map_employees(destination_type: str, employee_mapping_preference: str, paginated_employee_source_attributes = ExpenseAttribute.objects.filter( attribute_type='EMPLOYEE', workspace_id=workspace_id, auto_mapped=False )[offset:limit] - employee_source_attributes.extend(paginated_employee_source_attributes) + employee_source_attributes.extend( + paginated_employee_source_attributes) mapping_batch = construct_mapping_payload( employee_source_attributes, employee_mapping_preference, @@ -663,7 +732,7 @@ def auto_map_employees(destination_type: str, employee_mapping_preference: str, @staticmethod def auto_map_ccc_employees(destination_type: str, default_ccc_account_id: str, workspace_id: int): - """ + """̆̆̆̆̆̆̆̆̆̆̆ Auto map ccc employees :param destination_type: Destination Type of mappings :param default_ccc_account_id: Default CCC Account @@ -677,7 +746,8 @@ def auto_map_ccc_employees(destination_type: str, default_ccc_account_id: str, w destination_id=default_ccc_account_id, workspace_id=workspace_id, attribute_type=destination_type ).first() - existing_source_ids = get_existing_source_ids(destination_type, workspace_id) + existing_source_ids = get_existing_source_ids( + destination_type, workspace_id) mapping_batch = [] for source_employee in employee_source_attributes: @@ -709,9 +779,12 @@ class EmployeeMapping(models.Model): DestinationAttribute, on_delete=models.PROTECT, null=True, related_name='destination_vendor') destination_card_account = models.ForeignKey( DestinationAttribute, on_delete=models.PROTECT, null=True, related_name='destination_card_account') - workspace = models.ForeignKey(Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') - created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime') - updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') + workspace = models.ForeignKey( + Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') + created_at = models.DateTimeField( + auto_now_add=True, help_text='Created at datetime') + updated_at = models.DateTimeField( + auto_now=True, help_text='Updated at datetime') class Meta: db_table = 'employee_mappings' @@ -748,14 +821,18 @@ class CategoryMapping(models.Model): Category Mappings """ id = models.AutoField(primary_key=True) - source_category = models.ForeignKey(ExpenseAttribute, on_delete=models.PROTECT, related_name='categorymapping') + source_category = models.ForeignKey( + ExpenseAttribute, on_delete=models.PROTECT, related_name='categorymapping') destination_account = models.ForeignKey( DestinationAttribute, on_delete=models.PROTECT, null=True, related_name='destination_account') destination_expense_head = models.ForeignKey( DestinationAttribute, on_delete=models.PROTECT, null=True, related_name='destination_expense_head') - workspace = models.ForeignKey(Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') - created_at = models.DateTimeField(auto_now_add=True, help_text='Created at datetime') - updated_at = models.DateTimeField(auto_now=True, help_text='Updated at datetime') + workspace = models.ForeignKey( + Workspace, on_delete=models.PROTECT, help_text='Reference to Workspace model') + created_at = models.DateTimeField( + auto_now_add=True, help_text='Created at datetime') + updated_at = models.DateTimeField( + auto_now=True, help_text='Updated at datetime') class Meta: db_table = 'category_mappings' @@ -803,8 +880,8 @@ def bulk_create_mappings(destination_attributes: List[DestinationAttribute], categorymapping__source_category__isnull=True ).values('id', 'value') - source_attributes_id_map = {source_attribute['value'].lower(): source_attribute['id'] \ - for source_attribute in source_attributes} + source_attributes_id_map = {source_attribute['value'].lower(): source_attribute['id'] + for source_attribute in source_attributes} mapping_creation_batch = [] @@ -818,7 +895,8 @@ def bulk_create_mappings(destination_attributes: List[DestinationAttribute], mapping_creation_batch.append( CategoryMapping( - source_category_id=source_attributes_id_map[destination_attribute.value.lower()], + source_category_id=source_attributes_id_map[destination_attribute.value.lower( + )], workspace_id=workspace_id, **destination ) @@ -843,12 +921,14 @@ def bulk_create_ccc_category_mappings(workspace_id: int): if category_mapping.destination_expense_head.detail and \ 'gl_account_no' in category_mapping.destination_expense_head.detail and \ category_mapping.destination_expense_head.detail['gl_account_no']: - destination_account_internal_ids.append(category_mapping.destination_expense_head.detail['gl_account_no']) + destination_account_internal_ids.append( + category_mapping.destination_expense_head.detail['gl_account_no']) elif category_mapping.destination_expense_head.detail and \ 'account_internal_id' in category_mapping.destination_expense_head.detail and \ category_mapping.destination_expense_head.detail['account_internal_id']: - destination_account_internal_ids.append(category_mapping.destination_expense_head.detail['account_internal_id']) + destination_account_internal_ids.append( + category_mapping.destination_expense_head.detail['account_internal_id']) # Retreiving accounts for creating ccc mapping destination_attributes = DestinationAttribute.objects.filter( @@ -859,7 +939,8 @@ def bulk_create_ccc_category_mappings(workspace_id: int): destination_id_pk_map = {} for attribute in destination_attributes: - destination_id_pk_map[attribute['destination_id'].lower()] = attribute['id'] + destination_id_pk_map[attribute['destination_id'].lower( + )] = attribute['id'] mapping_updation_batch = [] @@ -868,13 +949,15 @@ def bulk_create_ccc_category_mappings(workspace_id: int): if category_mapping.destination_expense_head.detail and \ 'gl_account_no' in category_mapping.destination_expense_head.detail and\ - category_mapping.destination_expense_head.detail['gl_account_no'].lower() in destination_id_pk_map: - ccc_account_id = destination_id_pk_map[category_mapping.destination_expense_head.detail['gl_account_no'].lower()] + category_mapping.destination_expense_head.detail['gl_account_no'].lower() in destination_id_pk_map: + ccc_account_id = destination_id_pk_map[category_mapping.destination_expense_head.detail['gl_account_no'].lower( + )] elif category_mapping.destination_expense_head.detail and \ 'account_internal_id' in category_mapping.destination_expense_head.detail and \ - category_mapping.destination_expense_head.detail['account_internal_id'].lower() in destination_id_pk_map: - ccc_account_id = destination_id_pk_map[category_mapping.destination_expense_head.detail['account_internal_id'].lower()] + category_mapping.destination_expense_head.detail['account_internal_id'].lower() in destination_id_pk_map: + ccc_account_id = destination_id_pk_map[category_mapping.destination_expense_head.detail['account_internal_id'].lower( + )] mapping_updation_batch.append( CategoryMapping( @@ -888,3 +971,60 @@ def bulk_create_ccc_category_mappings(workspace_id: int): CategoryMapping.objects.bulk_update( mapping_updation_batch, fields=['destination_account'], batch_size=50 ) + + +class AutoAddCreateUpdateInfoManager(models.Manager): + def update_or_create(self, defaults=None, **kwargs): + """ + Overrides the default update_or_create to handle 'user' keyword argument. + """ + user = kwargs.pop("user", None) + defaults = defaults or {} + + if user and hasattr(user, "email"): + defaults["updated_by"] = user.email + + instance, created = super().update_or_create(defaults=defaults, **kwargs) + + if created and user and hasattr(user, "email"): + instance.created_by = user.email + instance.save(user=user) + + return instance, created + + +class AutoAddCreateUpdateInfoMixin(models.Model): + """ + Mixin to automatically set created_by and updated_by fields. + Stores only the user's email. + """ + + created_by = models.CharField( + max_length=255, + null=True, + blank=True, + help_text="Email of the user who created this record", + ) + updated_by = models.CharField( + max_length=255, + null=True, + blank=True, + help_text="Email of the user who last updated this record", + ) + + objects = AutoAddCreateUpdateInfoManager() + + class Meta: + abstract = True + + def save(self, *args, **kwargs): + """ + Override the save method to set created_by and updated_by fields. + Expects a 'user' keyword argument containing the user instance. + """ + user = kwargs.pop('user', None) + if user and hasattr(user, 'email'): + if not self.pk: + self.created_by = user.email + self.updated_by = user.email + super().save(*args, **kwargs) diff --git a/fyle_accounting_mappings/urls.py b/fyle_accounting_mappings/urls.py index 97d3a5a..b8566b0 100644 --- a/fyle_accounting_mappings/urls.py +++ b/fyle_accounting_mappings/urls.py @@ -45,5 +45,6 @@ path('expense_fields/', ExpenseFieldView.as_view()), path('destination_attributes/', DestinationAttributesView.as_view()), path('fyle_fields/', FyleFieldsView.as_view()), - path('paginated_destination_attributes/', PaginatedDestinationAttributesView.as_view(), name='paginated_destination_attributes_view'), + path('paginated_destination_attributes/', + PaginatedDestinationAttributesView.as_view(), name='paginated_destination_attributes_view'), ] diff --git a/fyle_accounting_mappings/views.py b/fyle_accounting_mappings/views.py index 7dbce9e..abc2cd4 100644 --- a/fyle_accounting_mappings/views.py +++ b/fyle_accounting_mappings/views.py @@ -247,7 +247,7 @@ def get(self, request, *args, **kwargs): if source_type == 'CATEGORY': activity_attribute_count = ExpenseAttribute.objects.filter( attribute_type='CATEGORY', value='Activity', workspace_id=self.kwargs['workspace_id'], active=True).count() - + if app_name in ('NetSuite', 'INTACCT', 'Sage 300 CRE', 'Dynamics 365 Business Central'): activity_mapping = CategoryMapping.objects.filter( source_category__value='Activity', workspace_id=self.kwargs['workspace_id']).first() diff --git a/setup.py b/setup.py index e2165c1..03509bd 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setuptools.setup( name='fyle-accounting-mappings', - version='1.35.0', + version='1.36.0', author='Shwetabh Kumar', author_email='shwetabh.kumar@fyle.in', description='Django application to store the fyle accounting mappings in a generic manner',