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

Job, Dep Field code-prepending support #206

Merged
merged 13 commits into from
Jul 23, 2024
20 changes: 20 additions & 0 deletions apps/mappings/helpers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from datetime import datetime, timedelta, timezone
from apps.mappings.models import ImportLog


def format_attribute_name(use_code_in_naming: bool, attribute_name: str, attribute_code: str = None) -> str:
Hrishabh17 marked this conversation as resolved.
Show resolved Hide resolved
"""
Expand All @@ -6,3 +9,20 @@ def format_attribute_name(use_code_in_naming: bool, attribute_name: str, attribu
if use_code_in_naming and attribute_code:
return "{} {}".format(attribute_code, attribute_name)
return attribute_name


def allow_job_sync(import_log: ImportLog = None) -> bool:
"""
Check if job sync is allowed
"""
time_difference = datetime.now(timezone.utc) - timedelta(minutes=30)

if (
not import_log
or import_log.status != 'COMPLETE'
Hrishabh17 marked this conversation as resolved.
Show resolved Hide resolved
or import_log.last_successful_run_at is None
or import_log.last_successful_run_at < time_difference
):
return True

return False
10 changes: 8 additions & 2 deletions apps/mappings/imports/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from apps.workspaces.models import ImportSetting
from apps.fyle.models import DependentFieldSetting
from apps.mappings.models import ImportLog
from apps.mappings.helpers import allow_job_sync


def chain_import_fields_to_fyle(workspace_id):
Expand All @@ -17,10 +18,15 @@ def chain_import_fields_to_fyle(workspace_id):
project_mapping = MappingSetting.objects.filter(workspace_id=workspace_id, source_field='PROJECT', import_to_fyle=True).first()

import_code_fields = import_settings.import_code_fields
project_import_log = ImportLog.objects.filter(workspace_id=workspace_id, attribute_type='PROJECT').first()

# We'll only sync job when the time_difference > 30 minutes to avoid
# any dependent field import issue due to timestamp on job name update
is_sync_allowed = allow_job_sync(project_import_log)

chain = Chain()

if project_mapping and dependent_field_settings:
if project_mapping and dependent_field_settings and is_sync_allowed:
cost_code_import_log = ImportLog.create('COST_CODE', workspace_id)
cost_category_import_log = ImportLog.create('COST_CATEGORY', workspace_id)
chain.append('apps.mappings.tasks.sync_sage300_attributes', 'JOB', workspace_id)
Expand Down Expand Up @@ -64,7 +70,7 @@ def chain_import_fields_to_fyle(workspace_id):
True if custom_fields_mapping_setting.destination_field in import_code_fields else False
)

if project_mapping and dependent_field_settings:
if project_mapping and dependent_field_settings and is_sync_allowed:
chain.append('apps.sage300.dependent_fields.import_dependent_fields_to_fyle', workspace_id)

if chain.length() > 0:
Expand Down
4 changes: 1 addition & 3 deletions apps/sage300/dependent_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def post_dependent_cost_code(import_log: ImportLog, dependent_field_setting: Dep
for project in projects:
project_name = format_attribute_name(use_code_in_naming=use_job_code_in_naming, attribute_name=project['job_name'], attribute_code=project['job_code'])
projects_from_categories.append(project_name)
print(projects_from_categories)

existing_projects_in_fyle = ExpenseAttribute.objects.filter(
workspace_id=dependent_field_setting.workspace_id,
Expand All @@ -118,7 +117,6 @@ def post_dependent_cost_code(import_log: ImportLog, dependent_field_setting: Dep
active=True
).values_list('value', flat=True)

print(existing_projects_in_fyle)
import_log.total_batches_count = len(existing_projects_in_fyle)
import_log.save()

Expand Down Expand Up @@ -253,7 +251,7 @@ def post_dependent_expense_field_values(workspace_id: int, dependent_field_setti
return
else:
is_cost_type_errored = post_dependent_cost_type(cost_category_import_log, dependent_field_setting, platform, filters, posted_cost_codes)
if not is_cost_type_errored and not is_cost_code_errored:
if not is_cost_type_errored and not is_cost_code_errored and cost_category_import_log.processed_batches_count > 0:
DependentFieldSetting.objects.filter(workspace_id=workspace_id).update(last_successful_import_at=datetime.now())


Expand Down
1 change: 1 addition & 0 deletions apps/sage300/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def disable_projects(workspace_id: int, projects_to_disable: Dict):

update_and_disable_cost_code(workspace_id, projects_to_disable, platform, use_code_in_naming)
platform.projects.sync()
return bulk_payload


def update_and_disable_cost_code(workspace_id: int, cost_codes_to_disable: Dict, platform: PlatformConnector, use_code_in_naming: bool):
Expand Down
60 changes: 60 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,26 @@ def add_project_mappings():
detail='Sage 300 Project - Platform APIs, Id - 10081',
active=True
)
DestinationAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='JOB',
display_name='CRE Platform',
value='CRE Platform',
destination_id='10064',
detail='Sage 300 Project - CRE Platform, Id - 10064',
active=True,
code='123'
)
DestinationAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='JOB',
display_name='Integrations CRE',
value='Integrations CRE',
destination_id='10081',
detail='Sage 300 Project - Integrations CRE, Id - 10081',
active=True,
code='123'
)
ExpenseAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='PROJECT',
Expand All @@ -357,6 +377,24 @@ def add_project_mappings():
detail='Sage 300 Project - Platform APIs, Id - 10081',
active=True
)
ExpenseAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='PROJECT',
display_name='CRE Platform',
value='123 CRE Platform',
source_id='10065',
detail='Sage 300 Project - 123 CRE Platform, Id - 10065',
active=True
)
ExpenseAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='PROJECT',
display_name='Integrations CRE',
value='123 Integrations CRE',
source_id='10082',
detail='Sage 300 Project - 123 Integrations CRE, Id - 10082',
active=True
)


@pytest.fixture()
Expand Down Expand Up @@ -616,6 +654,28 @@ def add_cost_category(create_temp_workspace):
workspace = Workspace.objects.get(id=workspace_id),
is_imported = False
)
CostCategory.objects.create(
job_id='10065',
job_name='Integrations CRE',
cost_code_id='cost_code_id_123',
cost_code_name='Integrations CRE',
name='Integrations',
cost_category_id='cost_category_id_456',
status=True,
workspace = Workspace.objects.get(id=workspace_id),
is_imported = False
)
CostCategory.objects.create(
job_id='10082',
job_name='CRE Platform',
cost_code_id='cost_code_id_545',
cost_code_name='CRE Platform',
name='CRE',
cost_category_id='cost_category_id_583',
status=True,
workspace = Workspace.objects.get(id=workspace_id),
is_imported = False
)


@pytest.fixture()
Expand Down
36 changes: 35 additions & 1 deletion tests/test_mappings/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from apps.mappings.helpers import format_attribute_name
from datetime import datetime, timedelta, timezone
from apps.mappings.helpers import format_attribute_name, allow_job_sync
from apps.mappings.models import ImportLog


def test_format_attribute_name():
Expand All @@ -17,3 +19,35 @@ def test_format_attribute_name():
# Test case 4: use_code_in_naming is False and attribute_code is None
result = format_attribute_name(False, "attribute_name", None)
assert result == "attribute_name"


def test_allow_job_sync(db, create_temp_workspace):
import_log = ImportLog.create('PROJECT', 1)

# Test case 1: import_log is None
result = allow_job_sync(None)
assert result is True

# Test case 2: import_log is not None and last_successful_run_at is None
import_log.last_successful_run_at = None
import_log.status = 'COMPLETE'
result = allow_job_sync(import_log)
assert result is True

# Test case 3: import_log is not None and status is not 'COMPLETE'
import_log.last_successful_run_at = '2021-01-01T00:00:00Z'
import_log.status = 'FATAL'
result = allow_job_sync(import_log)
assert result is True

# Test case 4: import_log is not None and last_successful_run_at is less than 30 minutes
import_log.last_successful_run_at = datetime.now(timezone.utc) - timedelta(minutes=29)
import_log.status = 'COMPLETE'
result = allow_job_sync(import_log)
assert result is False

# Test case 5: import_log is not None and last_successful_run_at is greater than 30 minutes
import_log.last_successful_run_at = datetime.now(timezone.utc) - timedelta(minutes=31)
import_log.status = 'COMPLETE'
result = allow_job_sync(import_log)
assert result is True
30 changes: 30 additions & 0 deletions tests/test_sage300/test_dependent_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
)
from apps.fyle.models import DependentFieldSetting
from apps.mappings.models import ImportLog
from apps.sage300.models import CostCategory
from apps.workspaces.models import ImportSetting


def test_construct_custom_field_placeholder():
Expand Down Expand Up @@ -86,6 +88,19 @@ def test_post_dependent_cost_code(
)
assert cost_code_import_log.status == 'FATAL'

# Code pre-prepend case
ImportSetting.objects.filter(workspace_id=workspace_id).update(import_code_fields=['JOB', 'COST_CODE', 'COST_CATEGORY'])
CostCategory.objects.filter(workspace_id=workspace_id).update(job_code='123', cost_code_code='456', cost_category_code='789')

result, is_errored = post_dependent_cost_code(
cost_code_import_log,
dependent_field_setting=dependent_field_settings,
platform=platform.return_value,
filters=filters
)
assert result == ['CRE Platform', 'Integrations CRE']
assert is_errored == False


def test_post_dependent_cost_type(
db,
Expand Down Expand Up @@ -131,6 +146,21 @@ def test_post_dependent_cost_type(
)
assert cost_category_import_log.status == 'FATAL'

# Code pre-prepend case
ImportSetting.objects.filter(workspace_id=workspace_id).update(import_code_fields=['JOB', 'COST_CODE', 'COST_CATEGORY'])
CostCategory.objects.filter(workspace_id=workspace_id).update(job_code='123', cost_code_code='456', cost_category_code='789')

post_dependent_cost_type(
cost_category_import_log,
dependent_field_setting=dependent_field_settings,
platform=platform.return_value,
filters=filters,
posted_cost_codes=['CRE Platform', 'Integrations CRE']
)

assert platform.return_value.dependent_fields.bulk_post_dependent_expense_field_values.call_count == 4
assert cost_category_import_log.status == 'COMPLETE'


def test_post_dependent_expense_field_values(
db,
Expand Down
61 changes: 58 additions & 3 deletions tests/test_sage300/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ def test_disable_projects(
db,
mocker,
create_temp_workspace,
add_fyle_credentials
add_fyle_credentials,
add_project_mappings,
add_import_settings
):
workspace_id = 1

Expand Down Expand Up @@ -142,6 +144,42 @@ def test_disable_projects(
assert sync_call.call_count == 4
disable_cost_code_call.call_count == 2

# Test disable projects with code in naming
import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
import_settings.import_code_fields = ['JOB']
import_settings.save()

ExpenseAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='PROJECT',
display_name='Project',
value='old_project_code old_project',
source_id='source_id_123',
active=True
)

projects_to_disable = {
'destination_id': {
'value': 'old_project',
'updated_value': 'new_project',
'code': 'old_project_code',
'updated_code': 'old_project_code'
}
}

payload = [{
'name': 'old_project_code old_project',
'code': 'destination_id',
'description': 'Sage 300 Project - {0}, Id - {1}'.format(
'old_project_code old_project',
'destination_id'
),
'is_enabled': False,
'id': 'source_id_123'
}]

assert disable_projects(workspace_id, projects_to_disable) == payload


def test_update_and_disable_cost_code(
db,
Expand All @@ -158,8 +196,8 @@ def test_update_and_disable_cost_code(
'destination_id': {
'value': 'old_project',
'updated_value': 'new_project',
'code': 'old_project_code',
'updated_code': 'old_project_code'
'code': 'new_project_code',
'updated_code': 'new_project_code'
}
}

Expand Down Expand Up @@ -192,6 +230,23 @@ def test_update_and_disable_cost_code(
updated_cost_category = CostCategory.objects.filter(workspace_id=workspace_id, job_id='destination_id').first()
assert updated_cost_category.job_name == 'new_project'

# Test with code in naming
use_code_in_naming = True

ExpenseAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='PROJECT',
display_name='Project',
value='old_project_code old_project',
source_id='source_id_123',
active=True
)

update_and_disable_cost_code(workspace_id, projects_to_disable, mock_platform, use_code_in_naming)
assert updated_cost_category.job_name == 'new_project'
assert updated_cost_category.job_code == 'new_project_code'

# Delete dependent field setting
updated_cost_category.job_name = 'old_project'
updated_cost_category.save()

Expand Down
Loading