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

Bug Fix: After testing #35

Merged
merged 14 commits into from
Nov 30, 2023
112 changes: 103 additions & 9 deletions apps/accounting_exports/models.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
from django.db import models
from django.contrib.postgres.fields import ArrayField
from typing import List

from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.db.models import Count
from fyle_accounting_mappings.models import ExpenseAttribute

from apps.fyle.models import Expense
from apps.workspaces.models import BaseForeignWorkspaceModel, BaseModel, ExportSetting
from ms_business_central_api.models.fields import (
BooleanFalseField,
CustomDateTimeField,
CustomJsonField,
IntegerNullField,
StringNotNullField,
StringNullField,
CustomJsonField,
CustomDateTimeField,
StringOptionsField,
IntegerNullField,
BooleanFalseField,
TextNotNullField
TextNotNullField,
)
from apps.workspaces.models import BaseForeignWorkspaceModel, BaseModel
from apps.fyle.models import Expense

TYPE_CHOICES = (
('INVOICES', 'INVOICES'),
Expand All @@ -31,6 +34,55 @@
)


def _group_expenses(expenses: List[Expense], export_setting: ExportSetting, fund_source: str):
"""
Group expenses based on specified fields
"""

credit_card_expense_grouped_by = export_setting.credit_card_expense_grouped_by
credit_card_expense_date = export_setting.credit_card_expense_date
reimbursable_expense_grouped_by = export_setting.reimbursable_expense_grouped_by
reimbursable_expense_date = export_setting.reimbursable_expense_date

default_fields = ['employee_email', 'fund_source']
report_grouping_fields = ['report_id', 'claim_number']
expense_grouping_fields = ['expense_id', 'expense_number']

# Define a mapping for fund sources and their associated group fields
fund_source_mapping = {
'CCC': {
'group_by': report_grouping_fields if credit_card_expense_grouped_by == 'REPORT' else expense_grouping_fields,
'date_field': credit_card_expense_date.lower() if credit_card_expense_date != 'LAST_SPENT_AT' else None
},
'PERSONAL': {
'group_by': report_grouping_fields if reimbursable_expense_grouped_by == 'REPORT' else expense_grouping_fields,
'date_field': reimbursable_expense_date.lower() if reimbursable_expense_date != 'LAST_SPENT_AT' else None
}
}

# Update expense_group_fields based on the fund_source
fund_source_data = fund_source_mapping.get(fund_source)
group_by_field = fund_source_data.get('group_by')
date_field = fund_source_data.get('date_field')

default_fields.extend(group_by_field)

if date_field:
default_fields.append(date_field)

# Extract expense IDs from the provided expenses
expense_ids = [expense.id for expense in expenses]
# Retrieve expenses from the database
expenses = Expense.objects.filter(id__in=expense_ids).all()

# Create expense groups by grouping expenses based on specified fields
expense_groups = list(expenses.values(*default_fields).annotate(
total=Count('*'), expense_ids=ArrayAgg('id'))
)

return expense_groups


class AccountingExport(BaseForeignWorkspaceModel):
"""
Table to store accounting exports
Expand All @@ -50,6 +102,48 @@ class AccountingExport(BaseForeignWorkspaceModel):
class Meta:
db_table = 'accounting_exports'

@staticmethod
def create_accounting_export(expense_objects: List[Expense], fund_source: str, workspace_id):
"""
Group expenses by report_id and fund_source, format date fields, and create AccountingExport objects.
"""
# Retrieve the ExportSetting for the workspace
export_setting = ExportSetting.objects.get(workspace_id=workspace_id)

# Group expenses based on specified fields and fund_source
accounting_exports = _group_expenses(expense_objects, export_setting, fund_source)

fund_source_map = {
'PERSONAL': 'reimbursable',
'CCC': 'credit_card'
}

for accounting_export in accounting_exports:
# Determine the date field based on fund_source
date_field = getattr(export_setting, f"{fund_source_map.get(fund_source)}_expense_date", None)

ruuushhh marked this conversation as resolved.
Show resolved Hide resolved
# Calculate and assign 'last_spent_at' based on the chosen date field
if date_field == 'last_spent_at':
latest_expense = Expense.objects.filter(id__in=accounting_export['expense_ids']).order_by('-spent_at').first()
accounting_export['last_spent_at'] = latest_expense.spent_at if latest_expense else None

# Store expense IDs and remove unnecessary keys
expense_ids = accounting_export['expense_ids']
accounting_export[date_field] = accounting_export[date_field].strftime('%Y-%m-%dT%H:%M:%S')
accounting_export.pop('total')
accounting_export.pop('expense_ids')

# Create an AccountingExport object for the expense group
accounting_export_instance = AccountingExport.objects.create(
workspace_id=workspace_id,
fund_source=accounting_export['fund_source'],
description=accounting_export,
status='EXPORT_READY'
)

# Add related expenses to the AccountingExport object
accounting_export_instance.expenses.add(*expense_ids)


class AccountingExportSummary(BaseModel):
"""
Expand Down
28 changes: 17 additions & 11 deletions apps/business_central/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self, credentials_object: BusinessCentralCredentials, workspace_id:
refresh_token = credentials_object.refresh_token

self.connection = Dynamics(
enviroment=environment,
environment=environment,
client_id=client_id,
client_secret=client_secret,
refresh_token=refresh_token,
Expand Down Expand Up @@ -59,18 +59,20 @@ def _sync_data(self, data, attribute_type, display_name, workspace_id, field_nam
"""

destination_attributes = []

for item in data:
detail = {field: getattr(item, field) for field in field_names}
detail = {field: item[field] for field in field_names}
if (attribute_type == 'EMPLOYEE' and item['status'] == 'Active') or attribute_type == 'LOCATION' or item['blocked'] != True:
active = True
else:
active = False
destination_attributes.append(self._create_destination_attribute(
attribute_type,
display_name,
item.name,
item.id,
item.is_active,
item['displayName'],
item['id'],
active,
detail
))

DestinationAttribute.bulk_create_or_update_destination_attributes(
destination_attributes, attribute_type, workspace_id, True)

Expand All @@ -89,9 +91,10 @@ def sync_accounts(self):
"""
workspace = Workspace.objects.get(id=self.workspace_id)
self.connection.company_id = workspace.business_central_company_id
field_names = ['category', 'subCategory', 'accountType', 'directPosting', 'lastModifiedDateTime']

accounts = self.connection.accounts.get_all()
self._sync_data(accounts, 'ACCOUNT', 'accounts', self.workspace_id)
self._sync_data(accounts, 'ACCOUNT', 'accounts', self.workspace_id, field_names)
return []

def sync_vendors(self):
Expand All @@ -100,9 +103,10 @@ def sync_vendors(self):
"""
workspace = Workspace.objects.get(id=self.workspace_id)
self.connection.company_id = workspace.business_central_company_id
field_names = ['email', 'currencyId', 'currencyCode', 'lastModifiedDateTime']

vendors = self.connection.vendors.get_all()
self._sync_data(vendors, 'VENDOR', 'vendor', self.workspace_id)
self._sync_data(vendors, 'VENDOR', 'vendor', self.workspace_id, field_names)
return []

def sync_employees(self):
Expand All @@ -111,9 +115,10 @@ def sync_employees(self):
"""
workspace = Workspace.objects.get(id=self.workspace_id)
self.connection.company_id = workspace.business_central_company_id
field_names = ['email', 'email', 'personalEmail', 'lastModifiedDateTime']

employees = self.connection.employees.get_all()
self._sync_data(employees, 'EMPLOYEE', 'employee', self.workspace_id)
self._sync_data(employees, 'EMPLOYEE', 'employee', self.workspace_id, field_names)
return []

def sync_locations(self):
Expand All @@ -122,7 +127,8 @@ def sync_locations(self):
"""
workspace = Workspace.objects.get(id=self.workspace_id)
self.connection.company_id = workspace.business_central_company_id
field_names = ['code', 'city', 'country']

locations = self.connection.locations.get_all()
self._sync_data(locations, 'LOCATION', 'location', self.workspace_id)
self._sync_data(locations, 'LOCATION', 'location', self.workspace_id, field_names)
return []
10 changes: 5 additions & 5 deletions apps/fyle/queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def queue_import_reimbursable_expenses(workspace_id: int, synchronous: bool = Fa

if not synchronous:
async_task(
'apps.fyle.tasks.import_reimbursable_expenses',
workspace_id, accounting_export,
'apps.fyle.tasks.import_expenses',
workspace_id, accounting_export, 'PERSONAL_CASH_ACCOUNT', 'PERSONAL'
)
return

Expand All @@ -49,9 +49,9 @@ def queue_import_credit_card_expenses(workspace_id: int, synchronous: bool = Fal

if not synchronous:
async_task(
'apps.fyle.tasks.import_credit_card_expenses',
workspace_id, accounting_export,
'apps.fyle.tasks.import_expenses',
workspace_id, accounting_export, 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT', 'CCC'
)
return

import_expenses(workspace_id, accounting_export, 'PERSONAL_CASH_ACCOUNT', 'PERSONAL')
import_expenses(workspace_id, accounting_export, 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT', 'CCC')
19 changes: 10 additions & 9 deletions apps/fyle/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
"""
import logging
from datetime import datetime, timezone

from django.db.models import Q
from fyle_accounting_mappings.models import ExpenseAttribute
from fyle_integrations_platform_connector import PlatformConnector
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework.response import Response
from rest_framework.views import status
from fyle_integrations_platform_connector import PlatformConnector

from fyle_accounting_mappings.models import ExpenseAttribute

from apps.fyle.models import ExpenseFilter
from apps.workspaces.models import Workspace, FyleCredential
from apps.fyle.helpers import get_expense_fields
from apps.fyle.models import ExpenseFilter
from apps.workspaces.models import FyleCredential, Workspace

logger = logging.getLogger(__name__)
logger.level = logging.INFO
Expand Down Expand Up @@ -112,14 +112,15 @@ class ExpenseFieldSerializer(serializers.Serializer):
"""
Workspace Admin Serializer
"""
expense_fields = serializers.SerializerMethodField()
field_name = serializers.CharField()
type = serializers.CharField()
is_custom = serializers.BooleanField()

def get_expense_fields(self, validated_data):
def get_expense_fields(self, workspace_id:int):
"""
Get Expense Fields
"""

workspace_id = self.context['request'].parser_context.get('kwargs').get('workspace_id')
expense_fields = get_expense_fields(workspace_id=workspace_id)

return expense_fields
2 changes: 1 addition & 1 deletion apps/fyle/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ def import_expenses(workspace_id, accounting_export: AccountingExport, source_ac
)

accounting_export.status = 'COMPLETE'
accounting_export.errors = None
accounting_export.business_central_errors = None

accounting_export.save()
6 changes: 4 additions & 2 deletions apps/fyle/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
FyleFieldsSerializer,
ImportFyleAttributesSerializer,
)
from apps.workspaces.models import Workspace
from ms_business_central_api.utils import LookupFieldMixin

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -62,7 +61,10 @@ class CustomFieldView(generics.ListAPIView):
"""

serializer_class = ExpenseFieldSerializer
queryset = Workspace.objects.all()
pagination_class = None

def get_queryset(self):
return ExpenseFieldSerializer().get_expense_fields(self.kwargs["workspace_id"])


class ExportableExpenseGroupsView(generics.RetrieveAPIView):
Expand Down
4 changes: 4 additions & 0 deletions apps/mappings/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
class MappingsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.mappings"

def ready(self):
super(MappingsConfig, self).ready()
import apps.mappings.signals # noqa
45 changes: 45 additions & 0 deletions apps/mappings/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
FYLE_EXPENSE_SYSTEM_FIELDS = [
'employee id',
'organisation name',
'employee name',
'employee email',
'expense date',
'expense id',
'report id',
'employee id',
'department',
'state',
'reporter',
'report',
'purpose',
'vendor',
'category',
'category code',
'mileage distance',
'mileage unit',
'flight from city',
'flight to city',
'flight from date',
'flight to date',
'flight from class',
'flight to class',
'hotel checkin',
'hotel checkout',
'hotel location',
'hotel breakfast',
'currency',
'amount',
'foreign currency',
'foreign amount',
'tax',
'approver',
'project',
'billable',
'cost center',
'cost center code',
'approved on',
'reimbursable',
'receipts',
'paid date',
'expense created date'
]
Loading
Loading