Skip to content

Commit

Permalink
Expense Model updated, comments fixed (#29)
Browse files Browse the repository at this point in the history
* Business central models added

* flake8 resolved

* models removed, commentrs resolved

* bug fixed

* Add Pre commit hook for linting (#30)

* Import Business Central Attributes API added (#27)

* Exportable expense api (#31)

* Exportable expense groups api

* test cases added

* test cases resolved
  • Loading branch information
ruuushhh authored Nov 29, 2023
1 parent e389d66 commit 7e135c5
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 45 deletions.
42 changes: 42 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
default_stages: [commit]
fail_fast: true
exclude: "^(sql/|(.*\/)?migrations\/)"
default_language_version: # noqa
python: python3

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-ast
- id: check-merge-conflict
- id: debug-statements
- id: detect-private-key
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/timothycrosley/isort
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black", "--trailing-comma", "--line-length=125"] # Fixed the syntax error here

- repo: https://github.com/pycqa/flake8
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies: [flake8-isort, flake8-tidy-imports]

- repo: https://github.com/Lucas-C/pre-commit-hooks-bandit
rev: v1.0.5
hooks:
- id: python-bandit-vulnerability-check
args: ["-lll", "--recursive", "-c", "bandit.yaml", "."] # Fixed the syntax error here
files: '\.py$' # Fixed the regex syntax here

- repo: local
hooks:
- id: flake8
name: flake8
entry: flake8
language: system
11 changes: 5 additions & 6 deletions apps/business_central/serializers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import logging
from django.db.models import Q
from datetime import datetime

from django.db.models import Q
from fyle_accounting_mappings.models import DestinationAttribute
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import status

from fyle_accounting_mappings.models import DestinationAttribute

from apps.workspaces.models import Workspace, BusinessCentralCredentials
from apps.business_central.helpers import sync_dimensions, check_interval_and_sync_dimension

from apps.business_central.helpers import check_interval_and_sync_dimension, sync_dimensions
from apps.workspaces.models import BusinessCentralCredentials, Workspace

logger = logging.getLogger(__name__)
logger.level = logging.INFO
Expand Down
3 changes: 1 addition & 2 deletions apps/business_central/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from django.urls import path

from apps.business_central.views import ImportBusinessCentralAttributesView, BusinessCentralFieldsView

from apps.business_central.views import BusinessCentralFieldsView, ImportBusinessCentralAttributesView

urlpatterns = [
path(
Expand Down
9 changes: 2 additions & 7 deletions apps/business_central/views.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import logging

from rest_framework import generics

from apps.business_central.serializers import ImportBusinessCentralAttributesSerializer, BusinessCentralFieldSerializer


logger = logging.getLogger(__name__)
logger.level = logging.INFO
from apps.business_central.serializers import BusinessCentralFieldSerializer, ImportBusinessCentralAttributesSerializer


class ImportBusinessCentralAttributesView(generics.CreateAPIView):
Expand All @@ -21,6 +15,7 @@ class BusinessCentralFieldsView(generics.ListAPIView):
Business Central Fields View
"""
serializer_class = BusinessCentralFieldSerializer
pagination_class = None

def get_queryset(self):
return BusinessCentralFieldSerializer().format_business_central_fields(self.kwargs["workspace_id"])
27 changes: 25 additions & 2 deletions apps/fyle/helpers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import json

import requests
from django.conf import settings

from fyle_integrations_platform_connector import PlatformConnector

from apps.workspaces.models import FyleCredential
from apps.accounting_exports.models import AccountingExport
from apps.fyle.constants import DEFAULT_FYLE_CONDITIONS
from apps.workspaces.models import ExportSetting, FyleCredential


def post_request(url, body, refresh_token=None):
Expand Down Expand Up @@ -79,3 +80,25 @@ def get_expense_fields(workspace_id: int):
})

return response


def get_exportable_accounting_exports_ids(workspace_id: int):
"""
Get List of accounting exports ids
"""

export_setting = ExportSetting.objects.get(workspace_id=workspace_id)
fund_source = []

if export_setting.reimbursable_expenses_export_type:
fund_source.append('PERSONAL')
if export_setting.credit_card_expense_export_type:
fund_source.append('CCC')

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

return accounting_export_ids
85 changes: 78 additions & 7 deletions apps/fyle/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import List, Dict

from django.db import models
from django.contrib.postgres.fields import ArrayField
from ms_business_central_api.models.fields import (
Expand All @@ -11,7 +13,7 @@
StringOptionsField,
IntegerOptionsField,
)
from apps.workspaces.models import BaseModel, BaseForeignWorkspaceModel
from apps.workspaces.models import BaseForeignWorkspaceModel


EXPENSE_FILTER_RANK = (
Expand Down Expand Up @@ -40,8 +42,13 @@
('not_in', 'not_in')
)

SOURCE_ACCOUNT_MAP = {
'PERSONAL_CASH_ACCOUNT': 'PERSONAL',
'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT': 'CCC'
}


class Expense(BaseModel):
class Expense(BaseForeignWorkspaceModel):
"""
Expense
"""
Expand All @@ -55,14 +62,14 @@ class Expense(BaseModel):
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')
amount = FloatNullField(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')
foreign_amount = FloatNullField(help_text='Foreign Amount')
foreign_currency = StringNullField(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')
vendor = StringNullField(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')
Expand All @@ -77,15 +84,79 @@ class Expense(BaseModel):
fund_source = StringNotNullField(help_text='Expense fund source')
verified_at = CustomDateTimeField(help_text='Report verified at')
custom_properties = CustomJsonField(help_text="Custom Properties")
report_title = models.TextField(null=True, blank=True, help_text='Report title')
payment_number = StringNullField(max_length=55, help_text='Expense payment number')
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 Meta:
db_table = 'expenses'

@staticmethod
def create_expense_objects(expenses: List[Dict], workspace_id: int):
"""
Bulk create expense objects
"""

# Create an empty list to store expense objects
expense_objects = []

for expense in expenses:
# Iterate through custom property fields and handle empty values
for custom_property_field in expense['custom_properties']:
if expense['custom_properties'][custom_property_field] == '':
expense['custom_properties'][custom_property_field] = None

# Create or update an Expense object based on expense_id
expense_object, _ = Expense.objects.update_or_create(
expense_id=expense['id'],
defaults={
'employee_email': expense['employee_email'],
'employee_name': expense['employee_name'],
'category': expense['category'],
'sub_category': expense['sub_category'],
'project': expense['project'],
'expense_number': expense['expense_number'],
'org_id': expense['org_id'],
'claim_number': expense['claim_number'],
'amount': round(expense['amount'], 2),
'currency': expense['currency'],
'foreign_amount': expense['foreign_amount'],
'foreign_currency': expense['foreign_currency'],
'tax_amount': expense['tax_amount'],
'tax_group_id': expense['tax_group_id'],
'settlement_id': expense['settlement_id'],
'reimbursable': expense['reimbursable'],
'billable': expense['billable'] if expense['billable'] else False,
'state': expense['state'],
'vendor': expense['vendor'][:250] if expense['vendor'] else None,
'cost_center': expense['cost_center'],
'purpose': expense['purpose'],
'report_id': expense['report_id'],
'report_title': expense['report_title'],
'spent_at': expense['spent_at'],
'approved_at': expense['approved_at'],
'posted_at': expense['posted_at'],
'expense_created_at': expense['expense_created_at'],
'expense_updated_at': expense['expense_updated_at'],
'fund_source': SOURCE_ACCOUNT_MAP[expense['source_account_type']],
'verified_at': expense['verified_at'],
'custom_properties': expense['custom_properties'],
'payment_number': expense['payment_number'],
'file_ids': expense['file_ids'],
'corporate_card_id': expense['corporate_card_id'],
'workspace_id': workspace_id
}
)

# Check if an AccountingExport related to the expense object already exists
if not Expense.objects.filter(accountingexport__isnull=False).distinct():
expense_objects.append(expense_object)

return expense_objects


class ExpenseFilter(BaseForeignWorkspaceModel):
"""
Expand Down
24 changes: 21 additions & 3 deletions apps/fyle/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,32 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""

import itertools

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

from apps.fyle.views import (
CustomFieldView,
ExpenseFilterDeleteView,
ExpenseFilterView,
ExportableExpenseGroupsView,
FyleFieldsView,
ImportFyleAttributesView,
)

accounting_exports_path = [
path('exportable_accounting_exports/', ExportableExpenseGroupsView.as_view(), name='exportable-accounting-exports')
]

urlpatterns = [
other_paths = [
path('expense_filters/<int:pk>/', ExpenseFilterDeleteView.as_view(), name='expense-filters'),
path('expense_filters/', ExpenseFilterView.as_view(), name='expense-filters'),
path('import_attributes/', ImportFyleAttributesView.as_view(), name='import-fyle-attributes'),
path('fields/', FyleFieldsView.as_view(), name='fyle-fields'),
path('expense_fields/', CustomFieldView.as_view(), name='fyle-expense-fields'),
]

fyle_dimension_paths = [
path('import_attributes/', ImportFyleAttributesView.as_view(), name='import-fyle-attributes')
]

urlpatterns = list(itertools.chain(accounting_exports_path, fyle_dimension_paths, other_paths))
30 changes: 27 additions & 3 deletions apps/fyle/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import logging

from rest_framework import generics
from ms_business_central_api.utils import LookupFieldMixin
from apps.workspaces.models import Workspace
from apps.fyle.serializers import ExpenseFilterSerializer, ImportFyleAttributesSerializer, FyleFieldsSerializer, ExpenseFieldSerializer
from rest_framework.response import Response
from rest_framework.views import status

from apps.fyle.helpers import get_exportable_accounting_exports_ids
from apps.fyle.models import ExpenseFilter
from apps.fyle.serializers import (
ExpenseFieldSerializer,
ExpenseFilterSerializer,
FyleFieldsSerializer,
ImportFyleAttributesSerializer,
)
from apps.workspaces.models import Workspace
from ms_business_central_api.utils import LookupFieldMixin

logger = logging.getLogger(__name__)
logger.level = logging.INFO
Expand Down Expand Up @@ -52,3 +62,17 @@ class CustomFieldView(generics.ListAPIView):

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


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

exportable_ids = get_exportable_accounting_exports_ids(workspace_id=kwargs['workspace_id'])

return Response(
data={'exportable_expense_group_ids': exportable_ids},
status=status.HTTP_200_OK
)
1 change: 1 addition & 0 deletions bandit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exclude_dirs: ['./venv', './.venv'] # noqa
Loading

0 comments on commit 7e135c5

Please sign in to comment.