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

Fyle Card <> Vendor Mapping setup #181

Merged
merged 10 commits into from
Jun 10, 2024
2 changes: 1 addition & 1 deletion apps/accounting_exports/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _group_expenses(expenses: List[Expense], export_setting: ExportSetting, fund
reimbursable_expense_date = export_setting.reimbursable_expense_date

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

# Define a mapping for fund sources and their associated group fields
Expand Down
14 changes: 1 addition & 13 deletions apps/fyle/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
"""
import logging

from django.db.models.signals import post_save, pre_save
from django.db.models.signals import pre_save
from django.dispatch import receiver

from fyle_integrations_platform_connector import PlatformConnector
from apps.workspaces.models import FyleCredential
from apps.fyle.models import DependentFieldSetting
from apps.sage300.dependent_fields import create_dependent_custom_field_in_fyle
from apps.mappings.imports.schedules import schedule_or_delete_dependent_field_tasks


logger = logging.getLogger(__name__)
logger.level = logging.INFO
Expand Down Expand Up @@ -51,13 +49,3 @@ def run_pre_save_dependent_field_settings_triggers(sender, instance: DependentFi
parent_field_id=instance.cost_code_field_id,
)
instance.cost_category_field_id = cost_category['data']['id']


@receiver(post_save, sender=DependentFieldSetting)
def run_post_save_dependent_field_settings_triggers(sender, instance: DependentFieldSetting, **kwargs):
"""
:param sender: Sender Class
:param instance: Row instance of Sender Class
:return: None
"""
schedule_or_delete_dependent_field_tasks(instance.workspace_id)
10 changes: 10 additions & 0 deletions apps/mappings/imports/queues.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django_q.tasks import Chain
from fyle_accounting_mappings.models import MappingSetting
from apps.workspaces.models import ImportSetting
from apps.fyle.models import DependentFieldSetting


def chain_import_fields_to_fyle(workspace_id):
Expand All @@ -11,6 +12,9 @@ def chain_import_fields_to_fyle(workspace_id):
mapping_settings = MappingSetting.objects.filter(workspace_id=workspace_id, import_to_fyle=True)
custom_field_mapping_settings = MappingSetting.objects.filter(workspace_id=workspace_id, is_custom=True, import_to_fyle=True)
import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
dependent_field_settings = DependentFieldSetting.objects.filter(workspace_id=workspace_id, is_import_enabled=True).first()
project_mapping = MappingSetting.objects.filter(workspace_id=workspace_id, source_field='PROJECT', import_to_fyle=True).first()

chain = Chain()

if import_settings.import_categories:
Expand Down Expand Up @@ -47,5 +51,11 @@ def chain_import_fields_to_fyle(workspace_id):
True
)

if project_mapping and dependent_field_settings:
chain.append(
'apps.mappings.imports.tasks.auto_import_and_map_fyle_fields',
workspace_id
)

if chain.length() > 0:
chain.run()
35 changes: 0 additions & 35 deletions apps/mappings/imports/schedules.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
from datetime import datetime
from django_q.models import Schedule
from fyle_accounting_mappings.models import MappingSetting

from apps.fyle.models import DependentFieldSetting
from apps.workspaces.models import ImportSetting


def schedule_or_delete_dependent_field_tasks(workspace_id: int):
"""
:param configuration: Workspace Configuration Instance
:return: None
"""
project_mapping = MappingSetting.objects.filter(
source_field='PROJECT',
workspace_id=workspace_id,
import_to_fyle=True
).first()
dependent_fields = DependentFieldSetting.objects.filter(workspace_id=workspace_id, is_import_enabled=True).first()

if project_mapping and dependent_fields:
start_datetime = datetime.now()
Schedule.objects.update_or_create(
func='apps.mappings.imports.tasks.auto_import_and_map_fyle_fields',
args='{}'.format(workspace_id),
defaults={
'schedule_type': Schedule.MINUTES,
'minutes': 24 * 60,
'next_run': start_datetime
}
)
elif not (project_mapping and dependent_fields):
Schedule.objects.filter(
func='apps.mappings.imports.tasks.auto_import_and_map_fyle_fields',
args='{}'.format(workspace_id)
).delete()


def schedule_or_delete_fyle_import_tasks(import_settings: ImportSetting, mapping_setting_instance: MappingSetting = None):
"""
Schedule or delete Fyle import tasks based on the import settingss.
Expand Down Expand Up @@ -78,6 +46,3 @@ def schedule_or_delete_fyle_import_tasks(import_settings: ImportSetting, mapping
func='apps.mappings.imports.queues.chain_import_fields_to_fyle',
args='{}'.format(import_settings.workspace_id)
).delete()

# Schedule or delete dependent field tasks
schedule_or_delete_dependent_field_tasks(import_settings.workspace_id)
25 changes: 13 additions & 12 deletions apps/mappings/imports/tasks.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import logging
from django_q.tasks import Chain

from fyle_accounting_mappings.models import MappingSetting

from apps.mappings.models import ImportLog
from apps.mappings.imports.modules.categories import Category
from apps.mappings.imports.modules.projects import Project
from apps.mappings.imports.modules.cost_centers import CostCenter
from apps.mappings.imports.modules.merchants import Merchant
from apps.mappings.imports.modules.expense_custom_fields import ExpenseCustomField
from apps.fyle.models import DependentFieldSetting

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

SOURCE_FIELD_CLASS_MAP = {
'CATEGORY': Category,
Expand Down Expand Up @@ -41,20 +42,20 @@ def auto_import_and_map_fyle_fields(workspace_id):
"""
Auto import and map fyle fields
"""
project_mapping = MappingSetting.objects.filter(
source_field='PROJECT',
import_log = ImportLog.objects.filter(
workspace_id=workspace_id,
import_to_fyle=True
attribute_type = 'PROJECT'
).first()
dependent_fields = DependentFieldSetting.objects.filter(workspace_id=workspace_id, is_import_enabled=True).first()

chain = Chain()

if project_mapping and dependent_fields:
chain.append('apps.mappings.tasks.sync_sage300_attributes', 'JOB', workspace_id)
chain.append('apps.mappings.tasks.sync_sage300_attributes', 'COST_CODE', workspace_id)
chain.append('apps.mappings.tasks.sync_sage300_attributes', 'COST_CATEGORY', workspace_id)
chain.append('apps.sage300.dependent_fields.import_dependent_fields_to_fyle', workspace_id)
chain.append('apps.mappings.tasks.sync_sage300_attributes', 'JOB', workspace_id)
chain.append('apps.mappings.tasks.sync_sage300_attributes', 'COST_CODE', workspace_id)
chain.append('apps.mappings.tasks.sync_sage300_attributes', 'COST_CATEGORY', workspace_id)
chain.append('apps.sage300.dependent_fields.import_dependent_fields_to_fyle', workspace_id)

if import_log and import_log.status != 'COMPLETE':
logger.error(f"Project Import is in {import_log.status} state in WORKSPACE_ID: {workspace_id} with error {str(import_log.error_log)}")

if chain.length() > 0:
chain.run()
29 changes: 14 additions & 15 deletions apps/sage300/exports/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.db import models
from django.db.models import Sum
from fyle_accounting_mappings.models import ExpenseAttribute, Mapping, MappingSetting, EmployeeMapping, DestinationAttribute
from fyle_accounting_mappings.models import ExpenseAttribute, Mapping, MappingSetting, EmployeeMapping

from apps.accounting_exports.models import AccountingExport
from apps.fyle.models import DependentFieldSetting, Expense
Expand Down Expand Up @@ -60,7 +60,6 @@ def get_expense_purpose(workspace_id, lineitem: Expense, category: str, advance_
def get_vendor_id(accounting_export: AccountingExport):
# Retrieve export settings for the given workspace
export_settings = ExportSetting.objects.get(workspace_id=accounting_export.workspace_id)

# Extract the description from the accounting export
description = accounting_export.description

Expand All @@ -81,22 +80,22 @@ def get_vendor_id(accounting_export: AccountingExport):
# Check if the fund source is 'CCC'
elif accounting_export.fund_source == 'CCC':
# Retrieve the vendor from the first expense
expense_vendor = accounting_export.expenses.first().vendor
vendor_id = None
corporate_card_id = accounting_export.expenses.first().corporate_card_id

# Query DestinationAttribute for the vendor with case-insensitive search
if expense_vendor:
vendor = DestinationAttribute.objects.filter(
if corporate_card_id:
vendor_mapping = Mapping.objects.filter(
workspace_id=accounting_export.workspace_id,
value__icontains=expense_vendor,
attribute_type='VENDOR'
).values_list('destination_id', flat=True).first()
if not vendor:
vendor = export_settings.default_vendor_id
else:
vendor = export_settings.default_vendor_id
source_type='CORPORATE_CARD',
destination_type='VENDOR',
source__source_id=corporate_card_id
).first()

# Update vendor_id with the retrieved vendor or default to export settings
vendor_id = vendor
if vendor_mapping:
vendor_id = vendor_mapping.destination.destination_id

if not vendor_id:
vendor_id = export_settings.default_vendor_id

# Return the determined vendor_id
return vendor_id
Expand Down
11 changes: 11 additions & 0 deletions apps/workspaces/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ def create(self, validated_data):
# Update workspace onboarding state
workspace = export_settings.workspace

if export_settings.credit_card_expense_export_type == 'PURCHASE_INVOICE':
ruuushhh marked this conversation as resolved.
Show resolved Hide resolved
MappingSetting.objects.update_or_create(
workspace_id = workspace_id,
defaults={
'source_field':'CORPORATE_CARD',
'destination_field':'VENDOR',
'import_to_fyle': False,
'is_custom': False
}
)

if workspace.onboarding_state == 'EXPORT_SETTINGS':
workspace.onboarding_state = 'IMPORT_SETTINGS'
workspace.save()
Expand Down
4 changes: 4 additions & 0 deletions scripts/001_delete_dep_fields_schedule.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
rollback;
begin;

delete from django_q_schedule where func = 'apps.mappings.imports.tasks.auto_import_and_map_fyle_fields';
20 changes: 20 additions & 0 deletions scripts/002_add_vendor_fyle_card_mapping.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
rollback;
begin;

insert into mapping_settings (source_field, destination_field, import_to_fyle, is_custom, workspace_id, created_at, updated_at)
select 'CORPORATE_CARD', 'VENDOR', 'f', 'f', ws.id, now(), now()
from workspaces ws
where not exists (
select 1
from mapping_settings ms
inner join export_settings es
on es.workspace_id = ms.workspace_id
where ms.source_field = 'CORPORATE_CARD'
and ms.destination_field = 'VENDOR'
and ms.workspace_id = ws.id
and es.credit_card_expense_export_type = 'PURCHASE_INVOICE'
);

select COUNT(*) from export_settings where credit_card_expense_export_type = 'PURCHASE_INVOICE';

select COUNT(distinct(workspace_id)) from mapping_settings where workspace_id not in (select distinct(workspace_id) from mapping_settings where source_field = 'CORPORATE_CARD' and destination_field = 'VENDOR');
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,7 @@ def add_dependent_field_setting(create_temp_workspace):
workspace_ids = [
1, 2, 3
]

for workspace_id in workspace_ids:
DependentFieldSetting.objects.create(
is_import_enabled=True,
Expand Down
Loading
Loading