Skip to content

Commit

Permalink
feat: support split expense grouping (#402)
Browse files Browse the repository at this point in the history
* feat: add fields to support split expense grouping

* fix: update sql fixture

* test: add unit tests for split expense grouping and update existing fixtures (#401)

* test: add unit tests for split expense grouping and update existing fixtures

* refactor: lint

* feat: add `split_expense_grouping` to the expense group settings serializer (#404)

* feat: add `split_expense_grouping` to the expense group settings serializer

* feat: implement split expense grouping functionality (#405)

* feat: implement split expense grouping functionality

* fix: skip only `expense_id` and `expense_number` while grouping

while defining the fields to group split expenses by, skip only `expense_id` and `expense_number`, NOT `spent_at` or `posted_at`

* feat: add script for split expense grouping (#406)

Set the default split expense grouping config to `SINGLE_LINE_ITEM` for all old orgs

(cherry picked from commit 86effd4)
  • Loading branch information
JustARatherRidiculouslyLongUsername committed Nov 19, 2024
1 parent 89ac6f7 commit ca388bd
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 26 deletions.
24 changes: 24 additions & 0 deletions apps/fyle/migrations/0022_support_split_expense_grouping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 3.2.14 on 2024-11-18 09:48

import apps.fyle.models
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('fyle', '0021_expense_is_posted_at_null'),
]

operations = [
migrations.AddField(
model_name='expense',
name='bank_transaction_id',
field=models.CharField(blank=True, help_text='Bank Transaction ID', max_length=255, null=True),
),
migrations.AddField(
model_name='expensegroupsettings',
name='split_expense_grouping',
field=models.CharField(choices=[('SINGLE_LINE_ITEM', 'SINGLE_LINE_ITEM'), ('MULTIPLE_LINE_ITEM', 'MULTIPLE_LINE_ITEM')], default=apps.fyle.models.get_default_split_expense_grouping, help_text='specify line items for split expenses grouping', max_length=100),
),
]
64 changes: 57 additions & 7 deletions apps/fyle/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from fyle_accounting_mappings.models import ExpenseAttribute

from apps.fyle.enums import ExpenseStateEnum, FundSourceEnum, PlatformExpensesEnum
from apps.workspaces.models import Workspace
from apps.workspaces.models import Workspace, WorkspaceGeneralSettings

logger = logging.getLogger(__name__)
logger.level = logging.INFO
Expand All @@ -35,6 +35,7 @@
"spent_at",
"expense_id",
"posted_at",
"bank_transaction_id",
]


Expand Down Expand Up @@ -62,6 +63,8 @@
PlatformExpensesEnum.PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT: FundSourceEnum.CCC,
}

SPLIT_EXPENSE_GROUPING = (('SINGLE_LINE_ITEM', 'SINGLE_LINE_ITEM'), ('MULTIPLE_LINE_ITEM', 'MULTIPLE_LINE_ITEM'))


def _format_date(date_string) -> datetime:
"""
Expand Down Expand Up @@ -126,6 +129,7 @@ class Expense(models.Model):
)
purpose = models.TextField(null=True, blank=True, help_text="Purpose")
report_id = models.CharField(max_length=255, help_text="Report ID")
bank_transaction_id = models.CharField(max_length=255, null=True, blank=True, help_text='Bank Transaction ID')
billable = models.BooleanField(default=False, help_text="Expense billable or not")
file_ids = ArrayField(
base_field=models.CharField(max_length=255), null=True, help_text="File IDs"
Expand Down Expand Up @@ -199,6 +203,7 @@ def create_expense_objects(expenses: List[Dict], workspace_id: int, skip_update:
"corporate_card_id": expense["corporate_card_id"],
"purpose": expense["purpose"],
"report_id": expense["report_id"],
"bank_transaction_id": expense["bank_transaction_id"],
"file_ids": expense["file_ids"],
"spent_at": expense["spent_at"],
"posted_at": expense["posted_at"],
Expand Down Expand Up @@ -260,6 +265,10 @@ def get_default_ccc_expense_state():
return ExpenseStateEnum.PAID


def get_default_split_expense_grouping():
return 'MULTIPLE_LINE_ITEM'


CCC_EXPENSE_STATE = (
(ExpenseStateEnum.APPROVED, ExpenseStateEnum.APPROVED),
(ExpenseStateEnum.PAYMENT_PROCESSING, ExpenseStateEnum.PAYMENT_PROCESSING),
Expand Down Expand Up @@ -314,6 +323,7 @@ class ExpenseGroupSettings(models.Model):
import_card_credits = models.BooleanField(
help_text="Import Card Credits", default=False
)
split_expense_grouping = models.CharField(max_length=100, default=get_default_split_expense_grouping, choices=SPLIT_EXPENSE_GROUPING, help_text='specify line items for split expenses grouping')
workspace = models.OneToOneField(
Workspace,
on_delete=models.PROTECT,
Expand Down Expand Up @@ -421,6 +431,7 @@ def update_expense_group_settings(expense_group_settings: Dict, workspace_id: in
],
"ccc_export_date_type": expense_group_settings["ccc_export_date_type"],
"import_card_credits": import_card_credits,
'split_expense_grouping': expense_group_settings['split_expense_grouping'],
},
)

Expand Down Expand Up @@ -506,13 +517,52 @@ def create_expense_groups_by_report_id_fund_source(
corporate_credit_card_expenses = list(
filter(lambda expense: expense.fund_source == "CCC", expense_objects)
)
corporate_credit_card_expense_groups = _group_expenses(
corporate_credit_card_expenses,
corporate_credit_card_expense_group_field,
workspace_id,
)

expense_groups.extend(corporate_credit_card_expense_groups)
if corporate_credit_card_expenses:
workspace_general_settings = WorkspaceGeneralSettings.objects.get(
workspace_id=workspace_id
)
ccc_export_module = workspace_general_settings.corporate_credit_card_expenses_object

if ccc_export_module == "BANK TRANSACTION" and expense_group_settings.split_expense_grouping == 'MULTIPLE_LINE_ITEM':
ccc_expenses_without_bank_transaction_id = list(
filter(lambda expense: not expense.bank_transaction_id, corporate_credit_card_expenses)
)

ccc_expenses_with_bank_transaction_id = list(
filter(lambda expense: expense.bank_transaction_id, corporate_credit_card_expenses)
)

if ccc_expenses_without_bank_transaction_id:
groups_without_bank_transaction_id = _group_expenses(
ccc_expenses_without_bank_transaction_id,
corporate_credit_card_expense_group_field,
workspace_id,
)
expense_groups.extend(groups_without_bank_transaction_id)

if ccc_expenses_with_bank_transaction_id:
split_expense_group_fields = [
field for field in corporate_credit_card_expense_group_field
if field not in ('expense_id', 'expense_number')
]
split_expense_group_fields.append('bank_transaction_id')

groups_with_bank_transaction_id = _group_expenses(
ccc_expenses_with_bank_transaction_id,
split_expense_group_fields,
workspace_id,
)
expense_groups.extend(groups_with_bank_transaction_id)

else:
corporate_credit_card_expense_groups = _group_expenses(
corporate_credit_card_expenses,
corporate_credit_card_expense_group_field,
workspace_id,
)

expense_groups.extend(corporate_credit_card_expense_groups)

expense_group_objects = []

Expand Down
4 changes: 4 additions & 0 deletions apps/workspaces/apis/export_settings/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class ExpenseGroupSettingsSerializer(serializers.ModelSerializer):
ccc_expense_state = serializers.CharField(
allow_null=True, allow_blank=True, required=False
)
split_expense_grouping = serializers.CharField(
allow_null=False, allow_blank=False, required=True
)

class Meta:
model = ExpenseGroupSettings
Expand All @@ -74,6 +77,7 @@ class Meta:
"corporate_credit_card_expense_group_fields",
"ccc_export_date_type",
"ccc_expense_state",
"split_expense_grouping",
]


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
rollback;
begin;

UPDATE expense_group_settings
SET split_expense_grouping = 'SINGLE_LINE_ITEM';
Loading

0 comments on commit ca388bd

Please sign in to comment.