Skip to content

Commit

Permalink
Feat: Add sync method for bank accounts, dimensions and dimension_val…
Browse files Browse the repository at this point in the history
…ues (#173)

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

* pylint solve

* minor changes

* added limit for bank account and dimensions

* fix test

* comment resolved

* Feat: Support for dimension during export Journal Entry and purchase Invoice (#174)

* Feat: Support for dimension during export Journal Entry and purchase Invoice

* comment resolved

* added dimension support for JE and bug fix

* implementation of exporting dimensions for JE and PI

* bug fixed

* comment resolved

* flake fixed

* Feat: Adding new Journal Entry changes (#175)

* Feat: Adding new Journal Entry changes

* comment resolved

* pylint

* comment line

* test fixed

* remove print

* Feat: Add chart of accounts type when importing accounts to category in Fyle (#177)

* Feat: Add chart of accounts type when importing accounts to category in Fyle

* resolved comment and added sanitizing

* resolved comment

* remove unnessary line

* comment resolved

* test resolve

* pylint

---------

Co-authored-by: Ashwin Thanaraj <[email protected]>
  • Loading branch information
Ashutosh619-sudo and ashwin1111 authored Dec 10, 2024
1 parent b273d38 commit 4ed6d99
Show file tree
Hide file tree
Showing 27 changed files with 725 additions and 169 deletions.
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()

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)

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
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()

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()
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)

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 = lineitem.expense_number

dimensions = self.get_dimension_object(accounting_export, lineitem)

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)
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


@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

0 comments on commit 4ed6d99

Please sign in to comment.