Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add sync method for bank accounts, dimensions and dimension_values #173

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions apps/business_central/exports/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,43 @@ def get_location_id(accounting_export: AccountingExport, lineitem: Expense):
if mapping:
location_id = mapping.destination.destination_id
return location_id

def get_dimension_object(accounting_export: AccountingExport, lineitem: Expense):
mapping_settings = MappingSetting.objects.filter(workspace_id=accounting_export.workspace_id).all()

Comment on lines +186 to +188
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add type hints and documentation.

The method signature could be improved with type hints and documentation for better maintainability and clarity.

Apply this diff:

-    def get_dimension_object(accounting_export: AccountingExport, lineitem: Expense):
+    @staticmethod
+    def get_dimension_object(accounting_export: AccountingExport, lineitem: Expense) -> list[dict]:
+        """
+        Retrieve dimension data based on mapping settings for a given accounting export and expense.
+
+        Args:
+            accounting_export (AccountingExport): The accounting export instance
+            lineitem (Expense): The expense line item
+
+        Returns:
+            list[dict]: List of dimension data dictionaries containing id, code, valueId, valueCode,
+                       and expense_number
+        """
📝 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 get_dimension_object(accounting_export: AccountingExport, lineitem: Expense):
mapping_settings = MappingSetting.objects.filter(workspace_id=accounting_export.workspace_id).all()
@staticmethod
def get_dimension_object(accounting_export: AccountingExport, lineitem: Expense) -> list[dict]:
"""
Retrieve dimension data based on mapping settings for a given accounting export and expense.
Args:
accounting_export (AccountingExport): The accounting export instance
lineitem (Expense): The expense line item
Returns:
list[dict]: List of dimension data dictionaries containing id, code, valueId, valueCode,
and expense_number
"""
mapping_settings = MappingSetting.objects.filter(workspace_id=accounting_export.workspace_id).all()

dimensions = []
default_expense_attributes = ['CATEGORY', 'EMPLOYEE']
default_destination_attributes = ['COMPANIES', 'ACCOUNTS', 'VENDORS', 'EMPLOYEES', 'LOCATIONS', 'BANK_ACCOUNTS']

for setting in mapping_settings:
if setting.source_field not in default_expense_attributes and \
setting.destination_field not in default_destination_attributes:
if setting.source_field == 'PROJECT':
source_value = lineitem.project
elif setting.source_field == 'COST_CENTER':
source_value = lineitem.cost_center
else:
attribute = ExpenseAttribute.objects.filter(
attribute_type=setting.source_field,
workspace_id=accounting_export.workspace_id
).first()
source_value = lineitem.custom_properties.get(attribute.display_name, None)

Comment on lines +196 to +206
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize database queries and add error handling.

The current implementation may cause N+1 query issues and lacks error handling for missing attributes.

Consider these improvements:

  1. Prefetch ExpenseAttributes:
     def get_dimension_object(accounting_export: AccountingExport, lineitem: Expense):
         mapping_settings = MappingSetting.objects.filter(workspace_id=accounting_export.workspace_id).all()
+        expense_attributes = {
+            attr.attribute_type: attr.display_name
+            for attr in ExpenseAttribute.objects.filter(
+                workspace_id=accounting_export.workspace_id
+            )
+        }
  1. Add error handling:
                 else:
-                    attribute = ExpenseAttribute.objects.filter(
-                        attribute_type=setting.source_field,
-                        workspace_id=accounting_export.workspace_id
-                    ).first()
-                    source_value = lineitem.custom_properties.get(attribute.display_name, None)
+                    display_name = expense_attributes.get(setting.source_field)
+                    if not display_name:
+                        continue
+                    source_value = lineitem.custom_properties.get(display_name)

Committable suggestion skipped: line range outside the PR's diff.

mapping: Mapping = Mapping.objects.filter(
source_type=setting.source_field,
destination_type=setting.destination_field,
source__value=source_value,
workspace_id=accounting_export.workspace_id
).first()

if mapping:
dimension_data = {
'id': mapping.destination.detail['dimension_id'],
'code': mapping.destination.attribute_type,
'valueId': mapping.destination.destination_id,
'valueCode': mapping.destination.detail['code'],
'expense_number': lineitem.expense_number
}
dimensions.append(dimension_data)

return dimensions
Comment on lines +207 to +224
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation and optimize mapping lookups.

The current implementation lacks validation for required fields and may have performance issues with mapping lookups.

Consider these improvements:

  1. Prefetch mappings:
     def get_dimension_object(accounting_export: AccountingExport, lineitem: Expense):
         mapping_settings = MappingSetting.objects.filter(workspace_id=accounting_export.workspace_id).all()
+        # Prefetch all mappings for the workspace
+        all_mappings = {
+            (m.source_type, m.source.value): m
+            for m in Mapping.objects.filter(
+                workspace_id=accounting_export.workspace_id
+            ).select_related('destination')
+        }
  1. Add validation and safe access:
-                mapping: Mapping = Mapping.objects.filter(
-                    source_type=setting.source_field,
-                    destination_type=setting.destination_field,
-                    source__value=source_value,
-                    workspace_id=accounting_export.workspace_id
-                ).first()
+                mapping = all_mappings.get((setting.source_field, source_value))

                 if mapping:
+                    detail = mapping.destination.detail
+                    if not all(k in detail for k in ['dimension_id', 'code']):
+                        continue
+
                     dimension_data = {
-                        'id': mapping.destination.detail['dimension_id'],
+                        'id': detail['dimension_id'],
                         'code': mapping.destination.attribute_type,
                         'valueId': mapping.destination.destination_id,
-                        'valueCode': mapping.destination.detail['code'],
+                        'valueCode': detail['code'],
                         'expense_number': lineitem.expense_number
                     }
                     dimensions.append(dimension_data)
📝 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
mapping: Mapping = Mapping.objects.filter(
source_type=setting.source_field,
destination_type=setting.destination_field,
source__value=source_value,
workspace_id=accounting_export.workspace_id
).first()
if mapping:
dimension_data = {
'id': mapping.destination.detail['dimension_id'],
'code': mapping.destination.attribute_type,
'valueId': mapping.destination.destination_id,
'valueCode': mapping.destination.detail['code'],
'expense_number': lineitem.expense_number
}
dimensions.append(dimension_data)
return dimensions
mapping_settings = MappingSetting.objects.filter(workspace_id=accounting_export.workspace_id).all()
# Prefetch all mappings for the workspace
all_mappings = {
(m.source_type, m.source.value): m
for m in Mapping.objects.filter(
workspace_id=accounting_export.workspace_id
).select_related('destination')
}
mapping = all_mappings.get((setting.source_field, source_value))
if mapping:
detail = mapping.destination.detail
if not all(k in detail for k in ['dimension_id', 'code']):
continue
dimension_data = {
'id': detail['dimension_id'],
'code': mapping.destination.attribute_type,
'valueId': mapping.destination.destination_id,
'valueCode': detail['code'],
'expense_number': lineitem.expense_number
}
dimensions.append(dimension_data)
return dimensions

85 changes: 47 additions & 38 deletions apps/business_central/exports/journal_entry/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.db import models
from django.db.models import JSONField

from fyle_accounting_mappings.models import CategoryMapping

from apps.accounting_exports.models import AccountingExport
Expand Down Expand Up @@ -39,22 +41,24 @@ def create_or_update_object(self, accounting_export: AccountingExport, _: Advanc
:param accounting_export: expense group
:return: purchase invoices object
"""
expenses = accounting_export.expenses.all()
expense = accounting_export.expenses.first()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for missing expense.

The code assumes an expense will always exist. Add proper error handling for cases where no expense is found.

- expense = accounting_export.expenses.first()
+ expense = accounting_export.expenses.first()
+ if not expense:
+     raise ValueError('No expense found for this accounting export')

  document_number = expense.expense_number

Also applies to: 52-54


accounts_payable_account_id = export_settings.default_bank_account_id

document_number = accounting_export.description['claim_number'] if accounting_export.description and accounting_export.description.get('claim_number') else accounting_export.description['expense_number']
advance_setting = AdvancedSetting.objects.get(workspace_id=accounting_export.workspace_id)

document_number = expense.expense_number

comment = "Consolidated Credit Entry For Report/Expense {}".format(document_number)
comment = self.get_expense_comment(accounting_export.workspace_id, expense, expense.category, advance_setting)

invoice_date = self.get_invoice_date(accounting_export=accounting_export)

account_type, account_id = self.get_account_id_type(accounting_export=accounting_export, export_settings=export_settings)
account_type, account_id = self.get_account_id_type(accounting_export=accounting_export, export_settings=export_settings, merchant=expense.vendor)

journal_entry_object, _ = JournalEntry.objects.update_or_create(
accounting_export= accounting_export,
defaults={
'amount': sum([expense.amount for expense in expenses]),
'amount': expense.amount,
'document_number': document_number,
'accounts_payable_account_id': accounts_payable_account_id,
'account_id': account_id,
Expand Down Expand Up @@ -83,6 +87,9 @@ class JournalEntryLineItems(BaseExportModel):
invoice_date = CustomDateTimeField(help_text='date of invoice')
document_number = TextNotNullField(help_text='document number of the invoice')
journal_entry = models.ForeignKey(JournalEntry, on_delete=models.PROTECT, help_text='Journal Entry reference', related_name='journal_entry_lineitems')
dimensions = JSONField(default=list, help_text='Business Central dimensions')
dimension_error_log = JSONField(null=True, help_text='dimension set response log')
dimension_success_log = JSONField(null=True, help_text='dimension set success response log')

class Meta:
db_table = 'journal_entries_lineitems'
Expand All @@ -94,42 +101,44 @@ def create_or_update_object(self, accounting_export: AccountingExport, advance_s
:param accounting_export: expense group
:return: purchase invoices object
"""
expenses = accounting_export.expenses.all()
lineitem = accounting_export.expenses.first()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for missing journal entry.

Add proper error handling for cases where journal entry or category mapping is not found.

  lineitem = accounting_export.expenses.first()
+ if not lineitem:
+     raise ValueError('No expense found for this accounting export')
+
  journal_entry = JournalEntry.objects.get(accounting_export=accounting_export)
+ if not journal_entry:
+     raise ValueError('No journal entry found for this accounting export')

  # ... later in the code ...
  account = CategoryMapping.objects.filter(
      source_category__value=category,
      workspace_id=accounting_export.workspace_id
  ).first()
+ if not account:
+     raise ValueError(f'No category mapping found for category: {category}')

Also applies to: 128-143

journal_entry = JournalEntry.objects.get(accounting_export=accounting_export)

journal_entry_lineitems = []

for lineitem in expenses:
category = lineitem.category if (lineitem.category == lineitem.sub_category or lineitem.sub_category == None) else '{0} / {1}'.format(lineitem.category, lineitem.sub_category)

account = CategoryMapping.objects.filter(
source_category__value=category,
workspace_id=accounting_export.workspace_id
).first()

comment = self.get_expense_comment(accounting_export.workspace_id, lineitem, lineitem.category, advance_setting)

invoice_date = self.get_invoice_date(accounting_export=accounting_export)

account_type, account_id = self.get_account_id_type(accounting_export=accounting_export, export_settings=export_settings, merchant=lineitem.vendor)

document_number = accounting_export.description['claim_number'] if accounting_export.description and accounting_export.description.get('claim_number') else accounting_export.description['expense_number']

journal_entry_lineitems_object, _ = JournalEntryLineItems.objects.update_or_create(
journal_entry_id = journal_entry.id,
expense_id=lineitem.id,
defaults={
'amount': lineitem.amount * -1,
'account_id': account_id,
'account_type': account_type,
'document_number': document_number,
'accounts_payable_account_id': account.destination_account.destination_id,
'comment': comment,
'workspace_id': accounting_export.workspace_id,
'invoice_date': invoice_date,
'description': lineitem.purpose if lineitem.purpose else None
}
)
journal_entry_lineitems.append(journal_entry_lineitems_object)
category = lineitem.category if (lineitem.category == lineitem.sub_category or lineitem.sub_category == None) else '{0} / {1}'.format(lineitem.category, lineitem.sub_category)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix None comparison style.

Use Python's identity comparison for None.

- category = lineitem.category if (lineitem.category == lineitem.sub_category or lineitem.sub_category == None) else '{0} / {1}'.format(lineitem.category, lineitem.sub_category)
+ category = lineitem.category if (lineitem.category == lineitem.sub_category or lineitem.sub_category is None) else '{0} / {1}'.format(lineitem.category, lineitem.sub_category)
📝 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
category = lineitem.category if (lineitem.category == lineitem.sub_category or lineitem.sub_category == None) else '{0} / {1}'.format(lineitem.category, lineitem.sub_category)
category = lineitem.category if (lineitem.category == lineitem.sub_category or lineitem.sub_category is None) else '{0} / {1}'.format(lineitem.category, lineitem.sub_category)
🧰 Tools
🪛 Ruff (0.7.0)

111-111: Comparison to None should be cond is None

Replace with cond is None

(E711)


account = CategoryMapping.objects.filter(
source_category__value=category,
workspace_id=accounting_export.workspace_id
).first()
Comment on lines +111 to +114
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize database query for CategoryMapping.

The current implementation might lead to N+1 query issues. Consider prefetching related fields and adding appropriate indexes.

+ from django.db.models import Index

class CategoryMapping(models.Model):
    class Meta:
        indexes = [
            Index(fields=['source_category', 'workspace_id']),
        ]

Also, consider caching the mapping results if they're frequently accessed:

from django.core.cache import cache

def get_category_mapping(category, workspace_id):
    cache_key = f'category_mapping_{workspace_id}_{category}'
    mapping = cache.get(cache_key)
    if mapping is None:
        mapping = CategoryMapping.objects.filter(
            source_category__value=category,
            workspace_id=workspace_id
        ).first()
        cache.set(cache_key, mapping, timeout=3600)  # Cache for 1 hour
    return mapping

Comment on lines +104 to +114
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize database queries.

The current implementation makes multiple separate database queries which could lead to N+1 query issues.

Consider these optimizations:

  1. Prefetch related fields when getting the expense:
lineitem = accounting_export.expenses.select_related('expense_number', 'category', 'sub_category', 'vendor').first()
  1. Add appropriate indexes to the CategoryMapping model:
class CategoryMapping(models.Model):
    class Meta:
        indexes = [
            models.Index(fields=['source_category', 'workspace_id']),
        ]
🧰 Tools
🪛 Ruff (0.7.0)

109-109: Comparison to None should be cond is None

Replace with cond is None

(E711)


comment = self.get_expense_comment(accounting_export.workspace_id, lineitem, lineitem.category, advance_setting)

invoice_date = self.get_invoice_date(accounting_export=accounting_export)

account_type, account_id = self.get_account_id_type(accounting_export=accounting_export, export_settings=export_settings, merchant=lineitem.vendor)

document_number = lineitem.expense_number

dimensions = self.get_dimension_object(accounting_export, lineitem)

Comment on lines +124 to +125
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for dimension retrieval.

The get_dimension_object call could potentially fail, but there's no error handling in place.

Apply this diff:

- dimensions = self.get_dimension_object(accounting_export, lineitem)
+ try:
+     dimensions = self.get_dimension_object(accounting_export, lineitem)
+ except Exception as e:
+     dimensions = []
+     # Log the error or handle it appropriately
+     logger.error(f'Failed to get dimensions for expense {lineitem.id}: {str(e)}')

Committable suggestion skipped: line range outside the PR's diff.

journal_entry_lineitems_object, _ = JournalEntryLineItems.objects.update_or_create(
journal_entry_id = journal_entry.id,
expense_id=lineitem.id,
defaults={
'amount': lineitem.amount * -1,
'account_id': account_id,
'account_type': account_type,
'document_number': document_number,
'accounts_payable_account_id': account.destination_account.destination_id,
'comment': comment,
'workspace_id': accounting_export.workspace_id,
'invoice_date': invoice_date,
'description': lineitem.purpose if lineitem.purpose else None,
'dimensions': dimensions
}
)
journal_entry_lineitems.append(journal_entry_lineitems_object)

return journal_entry_lineitems
51 changes: 45 additions & 6 deletions apps/business_central/exports/journal_entry/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from apps.business_central.utils import BusinessCentralConnector
from apps.workspaces.models import BusinessCentralCredentials

from fyle_accounting_mappings.models import DestinationAttribute

logger = logging.getLogger(__name__)
logger.level = logging.INFO

Expand Down Expand Up @@ -40,6 +42,13 @@ def __construct_journal_entry(self, body: JournalEntry, lineitems: List[JournalE
:return: constructed expense_report
'''
batch_journal_entry_payload = []
dimensions = []

account_attribute_type = DestinationAttribute.objects.filter(workspace_id=body.workspace_id, destination_id=body.accounts_payable_account_id).first()

balance_account_type = 'G/L Account'
if account_attribute_type and account_attribute_type.attribute_type == 'BANK_ACCOUNT':
balance_account_type = 'Bank Account'

journal_entry_payload = {
'accountType': body.account_type,
Expand All @@ -49,14 +58,18 @@ def __construct_journal_entry(self, body: JournalEntry, lineitems: List[JournalE
'amount': body.amount,
'comment': body.comment,
'description': body.description,
'balanceAccountType': 'G/L Account',
'balanceAccountType': balance_account_type,
'balancingAccountNumber': body.accounts_payable_account_id

}

batch_journal_entry_payload.append(journal_entry_payload)

for lineitem in lineitems:
for dimension in lineitem.dimensions:
dimension['exported_module_id'] = lineitem.id

dimensions.extend(lineitem.dimensions)
Comment on lines +69 to +72
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle potential None values for lineitem.dimensions.

If lineitem.dimensions is None or not a list, iterating over it could raise an exception. Consider adding a check to ensure it is a list before proceeding.

Apply this diff to handle possible None values:

for lineitem in lineitems:
+    if lineitem.dimensions:
        for dimension in lineitem.dimensions:
            dimension['exported_module_id'] = lineitem.id

        dimensions.extend(lineitem.dimensions)

Committable suggestion skipped: line range outside the PR's diff.

journal_entry_lineitem_payload = {
'accountType': lineitem.account_type,
'accountNumber': lineitem.account_id,
Expand All @@ -65,30 +78,39 @@ def __construct_journal_entry(self, body: JournalEntry, lineitems: List[JournalE
'amount': lineitem.amount,
'comment': lineitem.comment,
'description': lineitem.description if lineitem.description else '',
'balanceAccountType': 'G/L Account',
'balanceAccountType': balance_account_type,
'balancingAccountNumber': lineitem.accounts_payable_account_id
}

batch_journal_entry_payload.append(journal_entry_lineitem_payload)

return batch_journal_entry_payload
return batch_journal_entry_payload, dimensions

def post(self, accounting_export, item, lineitem):
'''
Export the Journal Entry to Business Central.
'''

batch_journal_entry_payload = self.__construct_journal_entry(item, lineitem)
logger.info('WORKSPACE_ID: {0}, ACCOUNTING_EXPORT_ID: {1}, BATCH_PURCHASE_INVOICE_PAYLOAD: {2}'.format(accounting_export.workspace_id, accounting_export.id, batch_journal_entry_payload))
batch_journal_entry_payload, dimensions = self.__construct_journal_entry(item, lineitem)
logger.info('WORKSPACE_ID: {0}, ACCOUNTING_EXPORT_ID: {1}, JOURNAL_ENTRY_PAYLOAD: {2}'.format(accounting_export.workspace_id, accounting_export.id, batch_journal_entry_payload))
business_central_credentials = BusinessCentralCredentials.get_active_business_central_credentials(accounting_export.workspace_id)
# Establish a connection to Business Central
business_central_connection = BusinessCentralConnector(business_central_credentials, accounting_export.workspace_id)

# Post the journal entry to Business Central
response = business_central_connection.bulk_post_journal_lineitems(batch_journal_entry_payload, accounting_export)

expenses = accounting_export.expenses.all()
if dimensions:
dimension_set_line_payloads = self.construct_dimension_set_line_payload(dimensions, response['responses'])
logger.info('WORKSPACE_ID: {0}, ACCOUNTING_EXPORT_ID: {1}, DIMENSION_SET_LINE_PAYLOADS: {2}'.format(accounting_export.workspace_id, accounting_export.id, dimension_set_line_payloads))
dimension_line_responses = (
business_central_connection.post_dimension_lines(
dimension_set_line_payloads, "JOURNAL_ENTRY", item.id
)
)
response["dimension_line_responses"] = dimension_line_responses

expenses = accounting_export.expenses.all()
# Load attachments to Business Central
for i in range(1, len(response["responses"])):
load_attachments(
Expand All @@ -100,6 +122,23 @@ def post(self, accounting_export, item, lineitem):

return response

def construct_dimension_set_line_payload(self, dimensions: list, exported_response: dict):
"""
construct payload for setting dimension for Journal Entry
"""

dimension_payload = []

for response in exported_response:
parent_id = response['body']['id']
for dimension in dimensions:
dimension_copy = dimension.copy()
dimension_copy.pop('expense_number')
dimension_copy['parentId'] = parent_id
dimension_payload.append(dimension_copy)

return dimension_payload

Comment on lines +125 to +141
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential mismatch when associating dimensions with journal entries.

In the construct_dimension_set_line_payload method, the current logic assigns all dimensions to each parent_id from exported_response. This could lead to incorrect dimension assignments if multiple journal entries are involved.

Consider modifying the code to correctly associate each dimension with its corresponding journal entry. Here's a possible refactor:

def construct_dimension_set_line_payload(self, dimensions: list, exported_response: list):
    """
    Construct payload for setting dimensions for Journal Entry
    """
    dimension_payload = []

+   # Assuming that dimensions and exported_response are in the same order
+   for response, dimension in zip(exported_response, dimensions):
        parent_id = response['body']['id']
-       for dimension in dimensions:
            dimension_copy = dimension.copy()
            dimension_copy.pop('expense_number', None)
            dimension_copy['parentId'] = parent_id
            dimension_payload.append(dimension_copy)

    return dimension_payload

This change ensures that each dimension is correctly linked to its corresponding journal entry.

Committable suggestion skipped: line range outside the PR's diff.


@handle_business_central_exceptions()
def create_journal_entry(accounting_export: AccountingExport, last_export: bool):
Expand Down
9 changes: 8 additions & 1 deletion apps/business_central/exports/purchase_invoice/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from typing import List

from django.db import models
from django.db.models import JSONField

from fyle_accounting_mappings.models import CategoryMapping

from apps.accounting_exports.models import AccountingExport
Expand Down Expand Up @@ -61,6 +63,9 @@ class PurchaseInvoiceLineitems(BaseExportModel):
amount = FloatNullField(help_text='Amount of the invoice')
description = TextNotNullField(help_text='description for the invoice')
location_id = StringNullField(help_text='location id of the invoice')
dimensions = JSONField(default=list, help_text='Business Central dimensions')
dimension_error_log = JSONField(null=True, help_text='dimension set response log')
dimension_success_log = JSONField(null=True, help_text='dimension set success response log')

class Meta:
db_table = 'purchase_invoice_lineitems'
Expand Down Expand Up @@ -88,6 +93,7 @@ def create_or_update_object(self, accounting_export: AccountingExport, advance_s

description = self.get_expense_purpose(lineitem, lineitem.category, advance_setting)
location_id = self.get_location_id(accounting_export, lineitem)
dimensions = self.get_dimension_object(accounting_export, lineitem)

purchase_invoice_lineitem_object, _ = PurchaseInvoiceLineitems.objects.update_or_create(
purchase_invoice_id=purchase_invoice.id,
Expand All @@ -97,7 +103,8 @@ def create_or_update_object(self, accounting_export: AccountingExport, advance_s
'accounts_payable_account_id': account.destination_account.destination_id if account else None,
'description': description,
'workspace_id': accounting_export.workspace_id,
'location_id': location_id
'location_id': location_id,
'dimensions': dimensions
}
)
purchase_invoice_lineitem_objects.append(purchase_invoice_lineitem_object)
Expand Down
Loading
Loading