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

exportable expense API and date range filter for expense group view API #454

Merged
merged 9 commits into from
Nov 29, 2023
22 changes: 20 additions & 2 deletions apps/fyle/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,32 @@
from django.conf import settings
from django.db.models import Q

from apps.fyle.models import ExpenseGroupSettings, ExpenseFilter
from apps.fyle.models import ExpenseGroupSettings, ExpenseFilter, ExpenseGroup
from apps.mappings.models import GeneralMapping
from apps.workspaces.models import FyleCredential, Workspace
from apps.workspaces.models import FyleCredential, Workspace, Configuration
from typing import List

logger = logging.getLogger(__name__)


def get_exportable_expense_group_ids(workspace_id):
configuration = Configuration.objects.get(workspace_id=workspace_id)
fund_source = []

if configuration.reimbursable_expenses_object:
fund_source.append('PERSONAL')
if configuration.corporate_credit_card_expenses_object:
fund_source.append('CCC')

expense_group_ids = ExpenseGroup.objects.filter(
workspace_id=workspace_id,
exported_at__isnull=True,
fund_source__in=fund_source
).values_list('id', flat=True)

return expense_group_ids


def post_request(url, body, refresh_token=None):
"""
Create a HTTP post request.
Expand Down
18 changes: 18 additions & 0 deletions apps/fyle/migrations/0027_expensegroup_employee_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.1.14 on 2023-11-29 11:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('fyle', '0026_auto_20231025_0913'),
]

operations = [
migrations.AddField(
model_name='expensegroup',
name='employee_name',
field=models.CharField(help_text='Expense Group Employee Name', max_length=100, null=True),
),
]
18 changes: 18 additions & 0 deletions apps/fyle/migrations/0028_expensegroup_export_url.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.1.14 on 2023-11-22 10:11

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('fyle', '0027_expensegroup_employee_name'),
]

operations = [
migrations.AddField(
model_name='expensegroup',
name='export_url',
field=models.CharField(help_text='Netsuite URL for the exported expenses', max_length=255, null=True),
),
]
14 changes: 12 additions & 2 deletions apps/fyle/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,10 @@ class ExpenseGroup(models.Model):
help_text='To which workspace this expense group belongs to')
fund_source = models.CharField(max_length=255, help_text='Expense fund source')
expenses = models.ManyToManyField(Expense, help_text="Expenses under this Expense Group")
employee_name = models.CharField(max_length=100, help_text='Expense Group Employee Name', null=True)
description = JSONField(max_length=255, help_text='Description', null=True)
response_logs = JSONField(help_text='Reponse log of the export', null=True)
export_url = models.CharField(max_length=255, help_text='Netsuite URL for the exported expenses', null=True)
created_at = models.DateTimeField(auto_now_add=True, help_text='Created at')
exported_at = models.DateTimeField(help_text='Exported at', null=True)
updated_at = models.DateTimeField(auto_now=True, help_text='Updated at')
Expand Down Expand Up @@ -392,7 +394,14 @@ def create_expense_groups_by_report_id_fund_source(expense_objects: List[Expense
if expense_group_settings.ccc_export_date_type == 'last_spent_at':
expense_group['last_spent_at'] = Expense.objects.filter(
id__in=expense_group['expense_ids']).order_by('-spent_at').first().spent_at


employee_name = Expense.objects.filter(
id__in=expense_group['expense_ids']
).first().employee_name

employee_name = Expense.objects.filter(
id__in=expense_group['expense_ids']
).first().employee_name
expense_ids = expense_group['expense_ids']
expense_group.pop('total')
expense_group.pop('expense_ids')
Expand All @@ -407,7 +416,8 @@ def create_expense_groups_by_report_id_fund_source(expense_objects: List[Expense
expense_group_object = ExpenseGroup.objects.create(
workspace_id=workspace_id,
fund_source=expense_group['fund_source'],
description=expense_group
description=expense_group,
employee_name=employee_name
)

expense_group_object.expenses.add(*expense_ids)
Expand Down
20 changes: 11 additions & 9 deletions apps/fyle/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@
from .models import Expense, ExpenseFilter, ExpenseGroup, ExpenseGroupSettings


class ExpenseSerializer(serializers.ModelSerializer):
"""
Expense serializer
"""
class Meta:
model = Expense
fields = ['updated_at', 'claim_number', 'employee_email', 'employee_name', 'fund_source', 'expense_number', 'vendor', 'category', 'amount',
'report_id', 'settlement_id', 'expense_id']

class ExpenseGroupSerializer(serializers.ModelSerializer):
"""
Expense group serializer
"""
expenses = ExpenseSerializer(many=True)
class Meta:
model = ExpenseGroup
fields = '__all__'
extra_fields = ['expenses']


class ExpenseGroupExpenseSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -60,12 +71,3 @@ def create(self, validated_data):
)

return expense_filter


class ExpenseSerializer(serializers.ModelSerializer):
"""
Expense serializer
"""
class Meta:
model = Expense
fields = ['updated_at', 'claim_number', 'employee_email', 'employee_name', 'fund_source']
23 changes: 14 additions & 9 deletions apps/fyle/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,32 @@
'CCC': 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT'
}

def schedule_expense_group_creation(workspace_id: int):
"""
Schedule Expense group creation
:param workspace_id: Workspace id
:param user: User email
:return: None
"""
def get_task_log_and_fund_source(workspace_id: int):
task_log, _ = TaskLog.objects.update_or_create(
workspace_id=workspace_id,
type='FETCHING_EXPENSES',
defaults={
'status': 'IN_PROGRESS'
'status': 'IN_PROGRESS'
}
)

configuration = Configuration.objects.get(workspace_id=workspace_id)

fund_source = ['PERSONAL']
if configuration.corporate_credit_card_expenses_object is not None:
fund_source.append('CCC')

return task_log, fund_source, configuration

def schedule_expense_group_creation(workspace_id: int):
"""
Schedule Expense group creation
:param workspace_id: Workspace id
:param user: User email
:return: None
"""

task_log, fund_source, configuration = get_task_log_and_fund_source(workspace_id)

async_task('apps.fyle.tasks.create_expense_groups', workspace_id, configuration, fund_source, task_log)


Expand Down
6 changes: 4 additions & 2 deletions apps/fyle/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.urls import path

from .views import ExpenseGroupView, ExpenseGroupByIdView, ExpenseGroupScheduleView, FyleFieldsView, ExpenseView,\
from .views import ExpenseGroupSyncView, ExpenseGroupView, ExpenseGroupByIdView, ExpenseGroupScheduleView, ExportableExpenseGroupsView, FyleFieldsView, ExpenseView,\
ExpenseAttributesView, ExpenseGroupSettingsView, SyncFyleDimensionView, RefreshFyleDimensionView,\
ExpenseGroupCountView, ExpenseFilterView, ExpenseGroupExpenseView, CustomFieldView

Expand All @@ -12,7 +12,9 @@
path('expense_groups/trigger/', ExpenseGroupScheduleView.as_view(), name='expense-groups-trigger'),
path('expense_groups/<int:pk>/', ExpenseGroupByIdView.as_view(), name='expense-group-by-id'),
path('expense_groups/<int:expense_group_id>/expenses/', ExpenseGroupExpenseView.as_view(), name='expense-group-expenses'),
path('expense_group_settings/', ExpenseGroupSettingsView.as_view(), name='expense-group-settings')
path('expense_group_settings/', ExpenseGroupSettingsView.as_view(), name='expense-group-settings'),
path('exportable_expense_groups/', ExportableExpenseGroupsView.as_view(), name='expense-expense-groups'),
path('expense_groups/sync/', ExpenseGroupSyncView.as_view(), name='sync-expense-groups'),
]

fyle_dimension_paths = [
Expand Down
63 changes: 59 additions & 4 deletions apps/fyle/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from datetime import datetime
from django.db.models import Q
from apps.fyle.helpers import get_exportable_expense_group_ids

from rest_framework.views import status
from rest_framework import generics
Expand All @@ -12,9 +13,9 @@
from fyle_accounting_mappings.models import ExpenseAttribute
from fyle_accounting_mappings.serializers import ExpenseAttributeSerializer

from apps.workspaces.models import FyleCredential, Workspace
from apps.workspaces.models import Configuration, FyleCredential, Workspace

from .tasks import schedule_expense_group_creation
from .tasks import schedule_expense_group_creation, get_task_log_and_fund_source, create_expense_groups
from .helpers import check_interval_and_sync_dimension, sync_dimensions
from .models import Expense, ExpenseGroup, ExpenseGroupSettings, ExpenseFilter
from .serializers import ExpenseGroupSerializer, ExpenseSerializer, ExpenseFieldSerializer, \
Expand All @@ -26,6 +27,8 @@

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


class ExpenseGroupView(generics.ListCreateAPIView):
"""
List Fyle Expenses
Expand All @@ -34,14 +37,36 @@ class ExpenseGroupView(generics.ListCreateAPIView):

def get_queryset(self):
state = self.request.query_params.get('state')
start_date = self.request.query_params.get('start_date', None)
end_date = self.request.query_params.get('end_date', None)
expense_group_ids = self.request.query_params.get('expense_group_ids', None)
exported_at = self.request.query_params.get('exported_at', None)

if expense_group_ids:
return ExpenseGroup.objects.filter(
workspace_id=self.kwargs['workspace_id'],
id__in=expense_group_ids.split(',')
)

if state == 'ALL':
return ExpenseGroup.objects.filter(workspace_id=self.kwargs['workspace_id']).order_by('-updated_at')

if state == 'FAILED':
return ExpenseGroup.objects.filter(
tasklog__status='FAILED', workspace_id=self.kwargs['workspace_id']).order_by('-updated_at')

elif state == 'COMPLETE':
return ExpenseGroup.objects.filter(
tasklog__status='COMPLETE', workspace_id=self.kwargs['workspace_id']).order_by('-exported_at')
filters = {
'workspace_id': self.kwargs['workspace_id'],
'tasklog__status': 'COMPLETE'
}

if start_date and end_date:
filters['exported_at__range'] = [start_date, end_date]

if exported_at:
filters['exported_at__gte'] = exported_at
return ExpenseGroup.objects.filter(**filters).order_by('-exported_at')

elif state == 'READY':
return ExpenseGroup.objects.filter(
Expand All @@ -53,6 +78,19 @@ def get_queryset(self):
).order_by('-updated_at')


class ExportableExpenseGroupsView(generics.RetrieveAPIView):
"""
List Exportable Expense Groups
"""
def get(self, request, *args, **kwargs):

expense_group_ids = get_exportable_expense_group_ids(workspace_id=kwargs['workspace_id'])
return Response(
data={'exportable_expense_group_ids': expense_group_ids},
status=status.HTTP_200_OK
)


class ExpenseGroupCountView(generics.ListAPIView):
"""
Expense Group Count View
Expand Down Expand Up @@ -370,3 +408,20 @@ def get(self, request, *args, **kwargs):
},
status=status.HTTP_400_BAD_REQUEST
)


class ExpenseGroupSyncView(generics.CreateAPIView):
"""
Create expense groups
"""
def post(self, request, *args, **kwargs):
"""
Post expense groups creation
"""
task_log, fund_source, configuration = get_task_log_and_fund_source(kwargs['workspace_id'])

create_expense_groups(kwargs['workspace_id'], configuration ,fund_source, task_log)

return Response(
status=status.HTTP_200_OK
)
28 changes: 21 additions & 7 deletions apps/netsuite/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from apps.netsuite.exceptions import handle_netsuite_exceptions
from django_q.models import Schedule
from django_q.tasks import Chain, async_task
from fyle_netsuite_api.utils import generate_netsuite_export_url

from netsuitesdk.internal.exceptions import NetSuiteRequestError
from netsuitesdk import NetSuiteRateLimitError, NetSuiteLoginError
Expand Down Expand Up @@ -444,11 +445,16 @@ def create_bill(expense_group, task_log_id, last_export):

expense_group.exported_at = datetime.now()
expense_group.response_logs = created_bill
expense_group.url = generate_netsuite_export_url(response_logs=created_bill, ns_account_id=netsuite_credentials)

expense_group.save()

resolve_errors_for_exported_expense_group(expense_group)

task_log.save()

async_task(
'apps.netsuite.tasks.upload_attachments_and_update_export',
expense_group.expenses.all(), task_log, fyle_credentials, expense_group.workspace_id
)


@handle_netsuite_exceptions(payment=False)
def create_credit_card_charge(expense_group, task_log_id, last_export):
Expand Down Expand Up @@ -513,6 +519,7 @@ def create_credit_card_charge(expense_group, task_log_id, last_export):

expense_group.exported_at = datetime.now()
expense_group.response_logs = created_credit_card_charge
expense_group.export_url = generate_netsuite_export_url(response_logs=created_credit_card_charge, ns_account_id=netsuite_credentials)
expense_group.save()
resolve_errors_for_exported_expense_group(expense_group)

Expand Down Expand Up @@ -558,10 +565,14 @@ def create_expense_report(expense_group, task_log_id, last_export):

expense_group.exported_at = datetime.now()
expense_group.response_logs = created_expense_report
expense_group.export_url = generate_netsuite_export_url(response_logs=created_expense_report, ns_account_id=netsuite_credentials)
expense_group.save()
resolve_errors_for_exported_expense_group(expense_group)
async_task(
'apps.netsuite.tasks.upload_attachments_and_update_export',
expense_group.expenses.all(), task_log, fyle_credentials, expense_group.workspace_id
)

task_log.save()


@handle_netsuite_exceptions(payment=False)
Expand Down Expand Up @@ -606,11 +617,14 @@ def create_journal_entry(expense_group, task_log_id, last_export):

expense_group.exported_at = datetime.now()
expense_group.response_logs = created_journal_entry
expense_group.export_url = generate_netsuite_export_url(response_logs=created_journal_entry, ns_account_id=netsuite_credentials)
expense_group.save()
resolve_errors_for_exported_expense_group(expense_group)

task_log.save()

async_task(
'apps.netsuite.tasks.upload_attachments_and_update_export',
expense_group.expenses.all(), task_log, fyle_credentials, expense_group.workspace_id
)


def __validate_general_mapping(expense_group: ExpenseGroup, configuration: Configuration) -> List[BulkError]:
bulk_errors = []
Expand Down
Loading
Loading