Skip to content

Commit

Permalink
Merge pull request #38 from fylein/accounting_export_apis
Browse files Browse the repository at this point in the history
Accounting export apis added
  • Loading branch information
ruuushhh authored Oct 26, 2023
2 parents 543a46a + c808437 commit e0385a1
Show file tree
Hide file tree
Showing 16 changed files with 562 additions and 4 deletions.
68 changes: 68 additions & 0 deletions apps/accounting_exports/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from django.db import models
from django.contrib.postgres.fields import ArrayField

from fyle_accounting_mappings.models import ExpenseAttribute

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

TYPE_CHOICES = (
('INVOICES', 'INVOICES'),
('DIRECT_COST', 'DIRECT_COST'),
('FETCHING_REIMBURSABLE_EXPENSES', 'FETCHING_REIMBURSABLE_EXPENSES'),
('FETCHING_CREDIT_CARD_EXPENENSES', 'FETCHING_CREDIT_CARD_EXPENENSES')
)


ERROR_TYPE_CHOICES = (('EMPLOYEE_MAPPING', 'EMPLOYEE_MAPPING'), ('CATEGORY_MAPPING', 'CATEGORY_MAPPING'), ('SAGE300_ERROR', 'SAGE300_ERROR'))


class AccountingExport(BaseForeignWorkspaceModel):
"""
Table to store accounting exports
"""
id = models.AutoField(primary_key=True)
type = StringOptionsField(choices=TYPE_CHOICES, help_text='Task type')
fund_source = StringNotNullField(help_text='Expense fund source')
mapping_errors = ArrayField(help_text='Mapping errors', base_field=models.CharField(max_length=255), blank=True, null=True)
expenses = models.ManyToManyField(Expense, help_text="Expenses under this Expense Group")
task_id = StringNullField(help_text='Fyle Jobs task reference')
description = CustomJsonField(help_text='Description')
status = StringNotNullField(help_text='Task Status')
detail = CustomJsonField(help_text='Task Response')
sage_300_errors = CustomJsonField(help_text='Sage 300 Errors')
exported_at = CustomDateTimeField(help_text='time of export')

class Meta:
db_table = 'accounting_exports'


class Error(BaseForeignWorkspaceModel):
"""
Table to store errors
"""
id = models.AutoField(primary_key=True)
type = StringOptionsField(max_length=50, choices=ERROR_TYPE_CHOICES, help_text='Error type')
accounting_export = models.ForeignKey(
AccountingExport, on_delete=models.PROTECT,
null=True, help_text='Reference to Expense group'
)
expense_attribute = models.OneToOneField(
ExpenseAttribute, on_delete=models.PROTECT,
null=True, help_text='Reference to Expense Attribute'
)
is_resolved = BooleanFalseField(help_text='Is resolved')
error_title = StringNotNullField(help_text='Error title')
error_detail = TextNotNullField(help_text='Error detail')

class Meta:
db_table = 'errors'
23 changes: 23 additions & 0 deletions apps/accounting_exports/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from rest_framework import serializers

from .models import AccountingExport, Error


class AccountingExportSerializer(serializers.ModelSerializer):
"""
Accounting Export serializer
"""

class Meta:
model = AccountingExport
fields = '__all__'


class ErrorSerializer(serializers.ModelSerializer):
"""
Serializer for the Errors
"""

class Meta:
model = Error
fields = '__all__'
23 changes: 23 additions & 0 deletions apps/accounting_exports/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""sage_desktop_api URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path

from .views import AccountingExportView, ErrorsView


urlpatterns = [
path('', AccountingExportView.as_view(), name='accounting-exports'),
path('errors/', ErrorsView.as_view(), name='errors'),
]
27 changes: 27 additions & 0 deletions apps/accounting_exports/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import logging

from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import generics
from sage_desktop_api.utils import LookupFieldMixin
from apps.accounting_exports.serializers import AccountingExportSerializer, ErrorSerializer
from apps.accounting_exports.models import AccountingExport, Error

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


class AccountingExportView(LookupFieldMixin, generics.ListAPIView):
"""
Retrieve or Create Accounting Export
"""
serializer_class = AccountingExportSerializer
queryset = AccountingExport.objects.all().order_by("-updated_at")
filter_backends = (DjangoFilterBackend,)
filterset_fields = {"type": {"in"}, "updated_at": {"lte", "gte"}, "id": {"in"}, "status": {"in"}}


class ErrorsView(LookupFieldMixin, generics.ListAPIView):
serializer_class = ErrorSerializer
queryset = Error.objects.all()
filter_backends = (DjangoFilterBackend,)
filterset_fields = {"type": {"exact"}, "is_resolved": {"exact"}}
52 changes: 50 additions & 2 deletions apps/fyle/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
from django.contrib.postgres.fields import ArrayField
from sage_desktop_api.models.fields import (
StringNotNullField,
StringNullField,
StringOptionsField,
BooleanFalseField,
IntegerOptionsField
IntegerOptionsField,
CustomJsonField,
CustomDateTimeField,
CustomEmailField,
FloatNullField
)
from apps.workspaces.models import BaseForeignWorkspaceModel
from apps.workspaces.models import BaseModel, BaseForeignWorkspaceModel


EXPENSE_FILTER_RANK = (
Expand Down Expand Up @@ -53,6 +58,49 @@ class Meta:
db_table = 'expense_filters'


class Expense(BaseModel):
"""
Expense
"""
id = models.AutoField(primary_key=True)
employee_email = CustomEmailField(help_text='Email id of the Fyle employee')
employee_name = StringNullField(help_text='Name of the Fyle employee')
category = StringNullField(help_text='Fyle Expense Category')
sub_category = StringNullField(help_text='Fyle Expense Sub-Category')
project = StringNullField(help_text='Project')
expense_id = StringNotNullField(unique=True, help_text='Expense ID')
org_id = StringNullField(help_text='Organization ID')
expense_number = StringNotNullField(help_text='Expense Number')
claim_number = StringNotNullField(help_text='Claim Number')
amount = models.FloatField(help_text='Home Amount')
currency = StringNotNullField(max_length=5, help_text='Home Currency')
foreign_amount = models.FloatField(null=True, help_text='Foreign Amount')
foreign_currency = StringNotNullField(max_length=5, help_text='Foreign Currency')
settlement_id = StringNullField(help_text='Settlement ID')
reimbursable = BooleanFalseField(help_text='Expense reimbursable or not')
state = StringNotNullField(help_text='Expense state')
vendor = StringNotNullField(help_text='Vendor')
cost_center = StringNullField(help_text='Fyle Expense Cost Center')
corporate_card_id = StringNullField(help_text='Corporate Card ID')
purpose = models.TextField(null=True, blank=True, help_text='Purpose')
report_id = StringNotNullField(help_text='Report ID')
billable = BooleanFalseField(help_text='Expense billable or not')
file_ids = ArrayField(base_field=models.CharField(max_length=255), null=True, help_text='File IDs')
spent_at = CustomDateTimeField(help_text='Expense spent at')
approved_at = CustomDateTimeField(help_text='Expense approved at')
posted_at = CustomDateTimeField(help_text='Date when the money is taken from the bank')
expense_created_at = CustomDateTimeField(help_text='Expense created at')
expense_updated_at = CustomDateTimeField(help_text='Expense created at')
fund_source = StringNotNullField(help_text='Expense fund source')
verified_at = CustomDateTimeField(help_text='Report verified at')
custom_properties = CustomJsonField(help_text="Custom Properties")
tax_amount = FloatNullField(help_text='Tax Amount')
tax_group_id = StringNullField(help_text='Tax Group ID')
exported = BooleanFalseField(help_text='Expense reimbursable or not')
previous_export_state = StringNullField(max_length=255, help_text='Previous export state')
accounting_export_summary = CustomJsonField(default=dict, help_text='Accounting Export Summary')


class Reimbursement:
"""
Creating a dummy class to be able to user
Expand Down
40 changes: 40 additions & 0 deletions apps/fyle/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
Fyle Serializers
"""
import logging
from django.db.models import Q
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework.views import status
from fyle_integrations_platform_connector import PlatformConnector

from fyle_accounting_mappings.models import ExpenseAttribute

from datetime import datetime, timezone
from apps.workspaces.models import Workspace, FyleCredential
from apps.fyle.models import ExpenseFilter
Expand Down Expand Up @@ -91,3 +95,39 @@ def get_expense_fields(self, validated_data):
expense_fields = get_expense_fields(workspace_id=workspace_id)

return expense_fields


class FyleFieldsSerializer(serializers.Serializer):
"""
Fyle Fields Serializer
"""

attribute_type = serializers.CharField()
display_name = serializers.CharField()
is_dependant = serializers.BooleanField()

def format_fyle_fields(self, workspace_id):
"""
Get Fyle Fields
"""

attribute_types = ['EMPLOYEE', 'CATEGORY', 'PROJECT', 'COST_CENTER', 'TAX_GROUP', 'CORPORATE_CARD', 'MERCHANT']

attributes = ExpenseAttribute.objects.filter(
~Q(attribute_type__in=attribute_types),
workspace_id=workspace_id
).values('attribute_type', 'display_name', 'detail__is_dependent').distinct()

attributes_list = [
{'attribute_type': 'COST_CENTER', 'display_name': 'Cost Center', 'is_dependant': False},
{'attribute_type': 'PROJECT', 'display_name': 'Project', 'is_dependant': False}
]

for attr in attributes:
attributes_list.append({
'attribute_type': attr['attribute_type'],
'display_name': attr['display_name'],
'is_dependant': attr['detail__is_dependent']
})

return attributes_list
3 changes: 2 additions & 1 deletion apps/fyle/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
"""

from django.urls import path
from apps.fyle.views import ImportFyleAttributesView, ExpenseFilterView, ExpenseFilterDeleteView, CustomFieldView
from apps.fyle.views import ImportFyleAttributesView, ExpenseFilterView, ExpenseFilterDeleteView, CustomFieldView, FyleFieldsView


urlpatterns = [
path('import_attributes/', ImportFyleAttributesView.as_view(), name='import-fyle-attributes'),
path('expense_filters/<int:pk>/', ExpenseFilterDeleteView.as_view(), name='expense-filters'),
path('expense_filters/', ExpenseFilterView.as_view(), name='expense-filters'),
path('expense_fields/', CustomFieldView.as_view(), name='fyle-expense-fields'),
path('fields/', FyleFieldsView.as_view(), name='fyle-fields'),
]
13 changes: 12 additions & 1 deletion apps/fyle/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from rest_framework import generics
from sage_desktop_api.utils import LookupFieldMixin
from apps.workspaces.models import Workspace
from apps.fyle.serializers import ImportFyleAttributesSerializer, ExpenseFilterSerializer, ExpenseFieldSerializer
from apps.fyle.serializers import ImportFyleAttributesSerializer, ExpenseFilterSerializer, ExpenseFieldSerializer, FyleFieldsSerializer
from apps.fyle.models import ExpenseFilter

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -42,3 +42,14 @@ class CustomFieldView(generics.ListAPIView):

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


class FyleFieldsView(generics.ListAPIView):
"""
Fyle Fields view
"""

serializer_class = FyleFieldsSerializer

def get_queryset(self):
return FyleFieldsSerializer().format_fyle_fields(self.kwargs["workspace_id"])
Loading

0 comments on commit e0385a1

Please sign in to comment.