Skip to content

Commit

Permalink
Clone Settings - Add Core APIs (#224)
Browse files Browse the repository at this point in the history
* Prefill API - POC

* Get latest updated workspace

* Prefill availability

* Rename and refactor APIs

* Add unit tests

* Fix PR comments

* fix test
  • Loading branch information
ashwin1111 authored Apr 18, 2023
1 parent afa26f8 commit ee018b2
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 1 deletion.
9 changes: 8 additions & 1 deletion apps/users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from .views import UserProfileView, FyleOrgsView

from apps.workspaces.apis.clone_settings.views \
import CloneSettingsExistsView

urlpatterns = [
path('profile/', UserProfileView.as_view()),
path('orgs/', FyleOrgsView.as_view())
path('orgs/', FyleOrgsView.as_view()),
path(
'clone_settings/exists/',
CloneSettingsExistsView.as_view()
),
]
Empty file.
23 changes: 23 additions & 0 deletions apps/workspaces/apis/clone_settings/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.contrib.auth import get_user_model
from apps.workspaces.models import Workspace, WorkspaceGeneralSettings

User = get_user_model()


def get_latest_workspace(user_id: str):
"""
Get latest workspace for user
:param user_id: user id
:return: workspace id / None
"""
user = User.objects.get(user_id=user_id)
user_workspaces = Workspace.objects.filter(user__in=[user]).values_list('id', flat=True)

workspace_general_setting = WorkspaceGeneralSettings.objects.filter(
workspace_id__in=user_workspaces,
workspace__onboarding_state='COMPLETE'
).order_by('-updated_at').first()

if workspace_general_setting:
return workspace_general_setting.workspace
82 changes: 82 additions & 0 deletions apps/workspaces/apis/clone_settings/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from apps.workspaces.models import Workspace
from rest_framework import serializers

from django.db import transaction

from apps.workspaces.apis.export_settings.serializers import (
ExportSettingsSerializer, ReadWriteSerializerMethodField
)

from apps.workspaces.apis.import_settings.serializers import ImportSettingsSerializer
from apps.workspaces.apis.advanced_settings.serializers import AdvancedSettingsSerializer


class CloneSettingsSerializer(serializers.ModelSerializer):
export_settings = ReadWriteSerializerMethodField()
import_settings = ReadWriteSerializerMethodField()
advanced_settings = ReadWriteSerializerMethodField()
workspace_id = serializers.SerializerMethodField()

class Meta:
model = Workspace
fields = [
'workspace_id',
'export_settings',
'import_settings',
'advanced_settings'
]
read_only_fields = ['workspace_id']

def get_workspace_id(self, instance):
return instance.id

def get_export_settings(self, instance):
return ExportSettingsSerializer(instance).data

def get_import_settings(self, instance):
return ImportSettingsSerializer(instance).data

def get_advanced_settings(self, instance):
return AdvancedSettingsSerializer(instance).data

def update(self, instance, validated):
export_settings = validated.pop('export_settings')
import_settings = validated.pop('import_settings')
advanced_settings = validated.pop('advanced_settings')

export_settings_serializer = ExportSettingsSerializer(
instance, data=export_settings, partial=True
)

import_settings_serializer = ImportSettingsSerializer(
instance, data=import_settings, partial=True
)

advanced_settings_serializer = AdvancedSettingsSerializer(
instance, data=advanced_settings, partial=True
)

if export_settings_serializer.is_valid(raise_exception=True) and \
import_settings_serializer.is_valid(raise_exception=True) and \
advanced_settings_serializer.is_valid(raise_exception=True):

# Doing all these in a transaction block to make sure we revert
# to old state when one of the serializer fails
with transaction.atomic():
export_settings_serializer.save()
import_settings_serializer.save()
advanced_settings_serializer.save()

return instance

def validate(self, data):
if not data.get('export_settings'):
raise serializers.ValidationError('Export Settings are required')

if not data.get('import_settings'):
raise serializers.ValidationError('Import Settings are required')

if not data.get('advanced_settings'):
raise serializers.ValidationError('Advanced Settings are required')

return data
33 changes: 33 additions & 0 deletions apps/workspaces/apis/clone_settings/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from rest_framework import generics, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

from apps.workspaces.models import Workspace

from .helpers import get_latest_workspace
from .serializers import CloneSettingsSerializer


class CloneSettingsView(generics.RetrieveUpdateAPIView):
serializer_class = CloneSettingsSerializer

def get_object(self):
latest_workspace = get_latest_workspace(self.request.user)

return Workspace.objects.filter(id=latest_workspace.id).first()


class CloneSettingsExistsView(generics.RetrieveAPIView):

permission_classes = [IsAuthenticated]

def get(self, request, *args, **kwargs):
latest_workspace = get_latest_workspace(self.request.user)

return Response(
data={
'is_available': True if latest_workspace else False,
'workspace_name': latest_workspace.name if latest_workspace else None
},
status=status.HTTP_200_OK
)
2 changes: 2 additions & 0 deletions apps/workspaces/apis/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
from .export_settings.views import ExportSettingsView
from .import_settings.views import ImportSettingsView
from .advanced_settings.views import AdvancedSettingsView
from .clone_settings.views import CloneSettingsView
from .errors.views import ErrorsView

urlpatterns = [
path('<int:workspace_id>/export_settings/', ExportSettingsView.as_view()),
path('<int:workspace_id>/import_settings/', ImportSettingsView.as_view()),
path('<int:workspace_id>/advanced_settings/', AdvancedSettingsView.as_view()),
path('<int:workspace_id>/clone_settings/', CloneSettingsView.as_view()),
path('<int:workspace_id>/errors/', ErrorsView.as_view())
]
18 changes: 18 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from fyle.platform import Platform

from apps.fyle.helpers import get_access_token
from apps.fyle.models import ExpenseGroupSettings
from apps.workspaces.models import Workspace, WorkspaceGeneralSettings
from fyle_xero_api.tests import settings
from .test_fyle.fixtures import data as fyle_data

Expand Down Expand Up @@ -98,4 +100,20 @@ def test_connection(db):
)
auth_token.save()

workspace = Workspace.objects.create(
name='Test Workspace 2',
fyle_org_id='fyle_org_id_dummy',
fyle_currency='USD',
xero_currency='USD',
app_version='v2',
xero_short_code='xero_short_code_dummy',
onboarding_state='COMPLETE'
)
workspace.user.add(user)
WorkspaceGeneralSettings.objects.create(
workspace=workspace,
reimbursable_expenses_object='BILL'
)
ExpenseGroupSettings.objects.create(workspace=workspace)

return fyle_connection
Empty file.
176 changes: 176 additions & 0 deletions tests/test_workspaces/test_apis/test_clone_settings/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
data = {
'clone_settings_response': {
'export_settings': {
"workspace_general_settings": {
"reimbursable_expenses_object": "PURCHASE BILL",
"corporate_credit_card_expenses_object": "BANK TRANSACTION",
"auto_map_employees": "EMAIL",
'is_simplify_report_closure_enabled': True
},
"expense_group_settings": {
"reimbursable_expense_group_fields": [
"fund_source",
"report_id",
"employee_email",
"claim_number"
],
"reimbursable_export_date_type": "current_date",
"reimbursable_expense_state": "PAYMENT_PROCESSING",
"corporate_credit_card_expense_group_fields": [
"employee_email",
"claim_number",
"fund_source",
"report_id",
"expense_id",
"spent_at"
],
"ccc_export_date_type": "spent_at",
"ccc_expense_state": "PAYMENT_PROCESSING",
"import_card_credits": True
},
"general_mappings": {
"bank_account": {
"id": "10",
"name": "Visa"
},
"payment_account": {
"id": "11",
"name": "Credit card"
}
},
"workspace_id": 1
},
'import_settings': {
"workspace_general_settings": {
"import_categories": True,
"charts_of_accounts": [
"Expense",
"COST of goods"
],
"import_tax_codes": True,
"import_customers": True
},
"general_mappings": {
"default_tax_code": {
"name": "GST@0%",
"id": "129"
}
},
"mapping_settings": [
{
"source_field": "KRATOS",
"destination_field": "ITEM",
"import_to_fyle": True,
"is_custom": True,
"source_placeholder": "This is a custom placeholder"
},
{
"source_field": "WEEKEND",
"destination_field": "REGION",
"import_to_fyle": True,
"is_custom": True,
"source_placeholder": "This will be added by postman"
}
],
"workspace_id": 1
},
'advanced_settings': {
"workspace_general_settings": {
"change_accounting_period": True,
"sync_fyle_to_xero_payments": False,
"sync_xero_to_fyle_payments": True,
"auto_create_destination_entity": True,
"auto_create_merchant_destination_entity": True
},
"general_mappings": {
"payment_account": {
"id": "2",
"name": "Business Savings Account"
}
},
"workspace_schedules": {
"enabled": True,
"interval_hours": 1,
"additional_email_options": [],
"emails_selected": ['[email protected]']
},
"workspace_id": 1
},
"workspace_id": 1
},
'clone_settings_missing_values': {
'export_settings': {},
'import_settings': None,
'advanced_settings': False,
"workspace_id": 1
},
'clone_settings': {
"export_settings": {
"expense_group_settings": {
"reimbursable_expense_state": "PAYMENT_PROCESSING",
"reimbursable_export_date_type": "current_date",
"ccc_expense_state": "PAID"
},
"workspace_general_settings": {
"reimbursable_expenses_object": "PURCHASE BILL",
"corporate_credit_card_expenses_object": None,
"auto_map_employees": "NAME"
},
"general_mappings": {
"bank_account": {
"id": None,
"name": None
}
}
},
"import_settings": {
"workspace_general_settings": {
"import_categories": False,
"charts_of_accounts": [
"EXPENSE"
],
"import_tax_codes": False,
"import_customers": False
},
"general_mappings": {
"default_tax_code": {
"id": None,
"name": None
}
},
"mapping_settings": []
},
"advanced_settings": {
"workspace_general_settings": {
"sync_fyle_to_xero_payments": False,
"sync_xero_to_fyle_payments": False,
"auto_create_destination_entity": False,
"change_accounting_period": False,
"auto_create_merchant_destination_entity": False
},
"general_mappings": {
"payment_account": {
"name": None,
"id": None
}
},
"workspace_schedules": {
"enabled": True,
"interval_hours": 1,
"start_datetime": "2023-04-04T14:14:02.462Z",
"emails_selected": [
"[email protected]"
],
"additional_email_options": []
}
}
},
'clone_settings_exists': {
'is_available': True,
'workspace_name': 'FAE'
},
'clone_settings_not_exists': {
'is_available': False,
'workspace_name': None
}
}
Loading

0 comments on commit ee018b2

Please sign in to comment.