Skip to content

Commit

Permalink
add support for accounting export and expense cretions
Browse files Browse the repository at this point in the history
  • Loading branch information
NileshPant1999 committed Nov 6, 2023
1 parent 015355b commit d90c28a
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 1 deletion.
89 changes: 88 additions & 1 deletion apps/accounting_exports/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
from datetime import datetime
from typing import List
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields.jsonb import KeyTextTransform

from django.db.models import Count

from fyle_accounting_mappings.models import ExpenseAttribute

Expand All @@ -13,9 +19,16 @@
StringOptionsField,
IntegerNullField
)
from apps.workspaces.models import BaseForeignWorkspaceModel, BaseModel
from apps.workspaces.models import BaseForeignWorkspaceModel, BaseModel, ExportSetting
from apps.fyle.models import Expense


ALLOWED_FIELDS = [
'employee_email', 'report_id', 'claim_number', 'settlement_id',
'fund_source', 'vendor', 'category', 'project', 'cost_center',
'verified_at', 'approved_at', 'spent_at', 'expense_id', 'expense_number', 'payment_number', 'posted_at'
]

TYPE_CHOICES = (
('INVOICES', 'INVOICES'),
('DIRECT_COST', 'DIRECT_COST'),
Expand All @@ -30,6 +43,30 @@
('AUTO', 'AUTO')
)

ALLOWED_FORM_INPUT = {
'group_expenses_by': ['settlement_id', 'claim_number', 'report_id', 'category', 'vendor', 'expense_id', 'expense_number', 'payment_number'],
'export_date_type': ['current_date', 'approved_at', 'spent_at', 'verified_at', 'last_spent_at', 'posted_at']
}


def _group_expenses(expenses, group_fields, workspace_id):
expense_ids = list(map(lambda expense: expense.id, expenses))
expenses = Expense.objects.filter(id__in=expense_ids).all()

custom_fields = {}

for field in group_fields:
if field.lower() not in ALLOWED_FIELDS:
group_fields.pop(group_fields.index(field))
field = ExpenseAttribute.objects.filter(workspace_id=workspace_id,
attribute_type=field.upper()).first()
if field:
custom_fields[field.attribute_type.lower()] = KeyTextTransform(field.display_name, 'custom_properties')

expense_groups = list(expenses.values(*group_fields, **custom_fields).annotate(
total=Count('*'), expense_ids=ArrayAgg('id')))
return expense_groups


class AccountingExport(BaseForeignWorkspaceModel):
"""
Expand All @@ -50,6 +87,56 @@ class AccountingExport(BaseForeignWorkspaceModel):
class Meta:
db_table = 'accounting_exports'

@staticmethod
def create_expense_groups_by_report_id_fund_source(expense_objects: List[Expense], workspace_id):
"""
Group expense by and fund_source
"""
export_setting = ExportSetting.objects.get(workspace_id=workspace_id)

reimbursable_expense_group_fields = export_setting.reimbursable_expense_grouped_by
reimbursable_expenses = list(filter(lambda expense: expense.fund_source == 'PERSONAL', expense_objects))

expense_groups = _group_expenses(reimbursable_expenses, reimbursable_expense_group_fields, workspace_id)

corporate_credit_card_expense_group_field = export_setting.credit_card_expense_grouped_by
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)

for expense_group in expense_groups:
if export_setting.reimbursable_expense_date == 'last_spent_at':
expense_group['last_spent_at'] = Expense.objects.filter(id__in=expense_group['expense_ids']).order_by('-spent_at').first().spent_at

if export_setting.credit_card_expense_date == 'last_spent_at':
expense_group['last_spent_at'] = Expense.objects.filter(id__in=expense_group['expense_ids']).order_by('-spent_at').first().spent_at

employee_name = Expense.objects.filter(
id__in=expense_group['expense_ids']
).first().employee_name

expense_ids = expense_group['expense_ids']
expense_group.pop('total')
expense_group.pop('expense_ids')

for key in expense_group:
if key in ALLOWED_FORM_INPUT['export_date_type']:
if expense_group[key]:
expense_group[key] = expense_group[key].strftime('%Y-%m-%dT%H:%M:%S')
else:
expense_group[key] = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')

expense_group_object = AccountingExport.objects.create(
workspace_id=workspace_id,
fund_source=expense_group['fund_source'],
description=expense_group,
employee_name=employee_name
)

expense_group_object.expenses.add(*expense_ids)


class Error(BaseForeignWorkspaceModel):
"""
Expand Down
64 changes: 64 additions & 0 deletions apps/fyle/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import List, Dict
from django.db import models
from django.contrib.postgres.fields import ArrayField

from sage_desktop_api.models.fields import (
StringNotNullField,
StringNullField,
Expand All @@ -13,6 +15,7 @@
IntegerNotNullField,
)
from apps.workspaces.models import BaseModel, BaseForeignWorkspaceModel
from apps.accounting_exports.models import AccountingExport


EXPENSE_FILTER_RANK = (
Expand Down Expand Up @@ -41,6 +44,11 @@
('not_in', 'not_in')
)

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


class ExpenseFilter(BaseForeignWorkspaceModel):
"""
Expand Down Expand Up @@ -104,6 +112,62 @@ class Expense(BaseModel):
class Meta:
db_table = 'expenses'

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

for expense in expenses:
for custom_property_field in expense['custom_properties']:
if expense['custom_properties'][custom_property_field] == '':
expense['custom_properties'][custom_property_field] = None
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'],
'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'],
}
)

if not AccountingExport.objects.filter(expenses__id=expense_object.id).first():
expense_objects.append(expense_object)

return expense_objects


class DependentFieldSetting(BaseModel):
"""
Expand Down

0 comments on commit d90c28a

Please sign in to comment.