From 10421db5261287fbd4f7f6fc9040177df9302591 Mon Sep 17 00:00:00 2001 From: Nilesh Pant Date: Tue, 3 Dec 2024 19:46:58 +0530 Subject: [PATCH 1/5] feat: allow selection of memo structure in advance settings --- .../apis/advanced_settings/serializers.py | 4 ++ ...workspacegeneralsettings_memo_structure.py | 20 +++++++ apps/workspaces/models.py | 9 ++++ apps/workspaces/views.py | 2 +- apps/xero/models.py | 52 +++++++++---------- apps/xero/tasks.py | 2 +- 6 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 apps/workspaces/migrations/0040_workspacegeneralsettings_memo_structure.py diff --git a/apps/workspaces/apis/advanced_settings/serializers.py b/apps/workspaces/apis/advanced_settings/serializers.py index c3cfb9d0..f5e9687a 100644 --- a/apps/workspaces/apis/advanced_settings/serializers.py +++ b/apps/workspaces/apis/advanced_settings/serializers.py @@ -40,6 +40,7 @@ class Meta: model = WorkspaceGeneralSettings fields = [ "change_accounting_period", + "memo_structure", "sync_fyle_to_xero_payments", "sync_xero_to_fyle_payments", "auto_create_destination_entity", @@ -94,6 +95,9 @@ def update(self, instance, validated): "change_accounting_period": workspace_general_settings.get( "change_accounting_period" ), + "memo_structure": workspace_general_settings.get( + "memo_structure" + ), "sync_fyle_to_xero_payments": workspace_general_settings.get( "sync_fyle_to_xero_payments" ), diff --git a/apps/workspaces/migrations/0040_workspacegeneralsettings_memo_structure.py b/apps/workspaces/migrations/0040_workspacegeneralsettings_memo_structure.py new file mode 100644 index 00000000..937b4c6e --- /dev/null +++ b/apps/workspaces/migrations/0040_workspacegeneralsettings_memo_structure.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.14 on 2024-12-03 10:44 + +import apps.workspaces.models +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('workspaces', '0039_alter_workspacegeneralsettings_change_accounting_period'), + ] + + operations = [ + migrations.AddField( + model_name='workspacegeneralsettings', + name='memo_structure', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), default=apps.workspaces.models.get_default_memo_fields, help_text='list of system fields for creating custom memo', size=None), + ), + ] diff --git a/apps/workspaces/models.py b/apps/workspaces/models.py index e849297a..f018c98c 100644 --- a/apps/workspaces/models.py +++ b/apps/workspaces/models.py @@ -30,6 +30,10 @@ EXPORT_MODE_CHOICES = (("MANUAL", "MANUAL"), ("AUTO", "AUTO")) +def get_default_memo_fields(): + return ['employee_email', 'category', 'merchant', 'spent_on', 'report_number', 'purpose'] + + def get_default_chart_of_accounts(): return ["EXPENSE"] @@ -178,6 +182,11 @@ class WorkspaceGeneralSettings(models.Model): null=True, choices=AUTO_MAP_EMPLOYEE, ) + memo_structure = ArrayField( + base_field=models.CharField(max_length=100), default=get_default_memo_fields, + help_text='list of system fields for creating custom memo' + ) + auto_create_destination_entity = models.BooleanField( default=False, help_text="Auto create contact" ) diff --git a/apps/workspaces/views.py b/apps/workspaces/views.py index 00cd099e..19da105e 100644 --- a/apps/workspaces/views.py +++ b/apps/workspaces/views.py @@ -158,7 +158,7 @@ def post(self, request, **kwargs): ) -class GeneralSettingsView(generics.RetrieveAPIView): +class GeneralSettingsView(generics.RetrieveUpdateAPIView): """ General Settings """ diff --git a/apps/xero/models.py b/apps/xero/models.py index 6b316c01..40961fb0 100644 --- a/apps/xero/models.py +++ b/apps/xero/models.py @@ -9,7 +9,7 @@ from apps.fyle.enums import FyleAttributeEnum from apps.fyle.models import Expense, ExpenseGroup from apps.mappings.models import GeneralMapping -from apps.workspaces.models import FyleCredential, Workspace +from apps.workspaces.models import FyleCredential, Workspace, WorkspaceGeneralSettings def get_tracking_category(expense_group: ExpenseGroup, lineitem: Expense): @@ -144,33 +144,33 @@ def get_customer_id_or_none(expense_group: ExpenseGroup, lineitem: Expense): return customer_id -def get_expense_purpose(workspace_id, lineitem, category) -> str: - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) +def get_expense_purpose(workspace_id, lineitem, category, workspace_general_settings) -> str: + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) org_id = Workspace.objects.get(id=workspace_id).fyle_org_id fyle_url = fyle_credentials.cluster_domain if settings.BRAND_ID == 'fyle' else settings.FYLE_APP_URL - expense_link = "{0}/app/admin/#/enterprise/view_expense/{1}?org_id={2}".format( - fyle_url, lineitem.expense_id, org_id - ) + details = { + 'employee_email': lineitem.employee_email, + 'merchant': '{} - '.format(lineitem.vendor) if lineitem.vendor else '', + 'category': '{0}'.format(category) if lineitem.category else '', + 'purpose': '{0}'.format(lineitem.purpose) if lineitem.purpose else '', + 'report_number': '{0}'.format(lineitem.claim_number), + 'spent_on': '{0}'.format(lineitem.spent_at.date()) if lineitem.spent_at else '', + 'expense_link': '{0}/app/admin/#/enterprise/view_expense/{1}?org_id={2}'.format(fyle_url, lineitem.expense_id, org_id) + } - expense_purpose = ( - "purpose - {0}".format(lineitem.purpose) if lineitem.purpose else "" - ) - spent_at = ( - "spent on {0}".format(lineitem.spent_at.date()) if lineitem.spent_at else "" - ) - vendor = "{} - ".format(lineitem.vendor) if lineitem.vendor else "" - return "{0}{1}, category - {2} {3}, report number - {4} {5} - {6}".format( - vendor, - lineitem.employee_email, - category, - spent_at, - lineitem.claim_number, - expense_purpose, - expense_link, - ) + memo = '' + memo_structure = workspace_general_settings.memo_structure + + for id, field in enumerate(memo_structure): + if field in details: + memo += details[field] + if id + 1 != len(memo_structure): + memo = '{0} - '.format(memo) + + return memo def get_tax_code_id_or_none(expense_group: ExpenseGroup, lineitem: Expense): @@ -262,7 +262,7 @@ class Meta: db_table = "bill_lineitems" @staticmethod - def create_bill_lineitems(expense_group: ExpenseGroup): + def create_bill_lineitems(expense_group: ExpenseGroup, workspace_general_settings: WorkspaceGeneralSettings): expenses = expense_group.expenses.all() bill = Bill.objects.get(expense_group=expense_group) @@ -289,7 +289,7 @@ def create_bill_lineitems(expense_group: ExpenseGroup): customer_id = get_customer_id_or_none(expense_group, lineitem) description = get_expense_purpose( - expense_group.workspace_id, lineitem, category + expense_group.workspace_id, lineitem, category, workspace_general_settings ) tracking_categories = get_tracking_category(expense_group, lineitem) @@ -449,7 +449,7 @@ class Meta: db_table = "bank_transaction_lineitems" @staticmethod - def create_bank_transaction_lineitems(expense_group: ExpenseGroup): + def create_bank_transaction_lineitems(expense_group: ExpenseGroup, workspace_general_settings: WorkspaceGeneralSettings): """ Create bank transaction lineitems :param expense_group: expense group @@ -481,7 +481,7 @@ def create_bank_transaction_lineitems(expense_group: ExpenseGroup): customer_id = get_customer_id_or_none(expense_group, lineitem) description = get_expense_purpose( - expense_group.workspace_id, lineitem, category + expense_group.workspace_id, lineitem, category, workspace_general_settings ) tracking_categories = get_tracking_category(expense_group, lineitem) diff --git a/apps/xero/tasks.py b/apps/xero/tasks.py index d37cd630..63a068e9 100644 --- a/apps/xero/tasks.py +++ b/apps/xero/tasks.py @@ -456,7 +456,7 @@ def create_bank_transaction( ) bank_transaction_lineitems_objects = ( - BankTransactionLineItem.create_bank_transaction_lineitems(expense_group) + BankTransactionLineItem.create_bank_transaction_lineitems(expense_group, general_settings) ) created_bank_transaction = xero_connection.post_bank_transaction( From 18d045cbba142a15ce528de78818054102db70fd Mon Sep 17 00:00:00 2001 From: Nilesh Pant Date: Wed, 4 Dec 2024 02:24:46 +0530 Subject: [PATCH 2/5] minor fix --- apps/xero/tasks.py | 2 +- tests/test_xero/test_models.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/xero/tasks.py b/apps/xero/tasks.py index 63a068e9..d59b10f9 100644 --- a/apps/xero/tasks.py +++ b/apps/xero/tasks.py @@ -281,7 +281,7 @@ def create_bill( with transaction.atomic(): bill_object = Bill.create_bill(expense_group) - bill_lineitems_objects = BillLineItem.create_bill_lineitems(expense_group) + bill_lineitems_objects = BillLineItem.create_bill_lineitems(expense_group, general_settings) created_bill = xero_connection.post_bill( bill_object, bill_lineitems_objects, general_settings diff --git a/tests/test_xero/test_models.py b/tests/test_xero/test_models.py index 7bdfe6ca..048cfd36 100644 --- a/tests/test_xero/test_models.py +++ b/tests/test_xero/test_models.py @@ -26,9 +26,10 @@ def test_create_bill(db): expense_group = ExpenseGroup.objects.get(id=4) + workspace_general_settings = WorkspaceGeneralSettings.objects.filter(workspace_id=expense_group.workspace_id).first() bill = Bill.create_bill(expense_group) - bill_lineitems = BillLineItem.create_bill_lineitems(expense_group) + bill_lineitems = BillLineItem.create_bill_lineitems(expense_group, workspace_general_settings) for bill_lineitem in bill_lineitems: assert bill_lineitem.amount == 10.0 @@ -42,10 +43,11 @@ def test_create_bill(db): def test_bank_transaction(db): expense_group = ExpenseGroup.objects.get(id=5) + workspace_general_settings = WorkspaceGeneralSettings.objects.filter(workspace_id=expense_group.workspace_id).first() bank_transaction = BankTransaction.create_bank_transaction(expense_group, True) bank_transaction_lineitems = ( - BankTransactionLineItem.create_bank_transaction_lineitems(expense_group) + BankTransactionLineItem.create_bank_transaction_lineitems(expense_group, workspace_general_settings) ) for bank_transaction_lineitem in bank_transaction_lineitems: @@ -157,6 +159,8 @@ def test_get_expense_purpose(db): expense_group = ExpenseGroup.objects.get(id=10) expenses = expense_group.expenses.all() + workspace_general_settings = WorkspaceGeneralSettings.objects.filter(workspace_id=workspace_id) + for lineitem in expenses: category = ( lineitem.category @@ -164,7 +168,7 @@ def test_get_expense_purpose(db): else "{0} / {1}".format(lineitem.category, lineitem.sub_category) ) - expense_purpose = get_expense_purpose(workspace_id, lineitem, category) + expense_purpose = get_expense_purpose(workspace_id, lineitem, category, workspace_general_settings) assert ( expense_purpose From d03d776cf9e2ff68f3778169af5979bb12fe516d Mon Sep 17 00:00:00 2001 From: Nilesh Pant Date: Wed, 4 Dec 2024 03:10:38 +0530 Subject: [PATCH 3/5] fix tests --- apps/xero/models.py | 3 +-- tests/sql_fixtures/reset_db_fixtures/reset_db.sql | 14 ++++++++------ tests/test_workspaces/fixtures.py | 1 + .../test_apis/test_advanced_settings/fixtures.py | 2 ++ .../test_apis/test_clone_settings/fixtures.py | 2 ++ tests/test_xero/test_models.py | 6 +++--- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/xero/models.py b/apps/xero/models.py index 40961fb0..f33b47c1 100644 --- a/apps/xero/models.py +++ b/apps/xero/models.py @@ -148,6 +148,7 @@ def get_expense_purpose(workspace_id, lineitem, category, workspace_general_sett fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) org_id = Workspace.objects.get(id=workspace_id).fyle_org_id + memo_structure = workspace_general_settings.memo_structure fyle_url = fyle_credentials.cluster_domain if settings.BRAND_ID == 'fyle' else settings.FYLE_APP_URL @@ -162,8 +163,6 @@ def get_expense_purpose(workspace_id, lineitem, category, workspace_general_sett } memo = '' - memo_structure = workspace_general_settings.memo_structure - for id, field in enumerate(memo_structure): if field in details: memo += details[field] diff --git a/tests/sql_fixtures/reset_db_fixtures/reset_db.sql b/tests/sql_fixtures/reset_db_fixtures/reset_db.sql index 0193f29b..7a4c0a28 100644 --- a/tests/sql_fixtures/reset_db_fixtures/reset_db.sql +++ b/tests/sql_fixtures/reset_db_fixtures/reset_db.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 15.9 (Debian 15.9-1.pgdg120+1) --- Dumped by pg_dump version 15.8 (Debian 15.8-1.pgdg120+1) +-- Dumped from database version 15.10 (Debian 15.10-1.pgdg120+1) +-- Dumped by pg_dump version 15.10 (Debian 15.10-1.pgdg120+1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -1535,7 +1535,8 @@ CREATE TABLE public.workspace_general_settings ( change_accounting_period boolean NOT NULL, auto_create_merchant_destination_entity boolean NOT NULL, is_simplify_report_closure_enabled boolean NOT NULL, - import_suppliers_as_merchants boolean NOT NULL + import_suppliers_as_merchants boolean NOT NULL, + memo_structure character varying(100)[] NOT NULL ); @@ -2643,6 +2644,7 @@ COPY public.django_migrations (id, app, name, applied) FROM stdin; 157 tasks 0010_alter_tasklog_expense_group 2024-11-17 21:11:41.133814+00 158 workspaces 0039_alter_workspacegeneralsettings_change_accounting_period 2024-11-18 04:43:45.472917+00 159 fyle 0022_support_split_expense_grouping 2024-11-18 10:49:49.550689+00 +160 workspaces 0040_workspacegeneralsettings_memo_structure 2024-12-03 21:13:46.617079+00 \. @@ -5066,8 +5068,8 @@ COPY public.users (password, last_login, id, email, user_id, full_name, active, -- Data for Name: workspace_general_settings; Type: TABLE DATA; Schema: public; Owner: postgres -- -COPY public.workspace_general_settings (id, reimbursable_expenses_object, corporate_credit_card_expenses_object, created_at, updated_at, workspace_id, sync_fyle_to_xero_payments, sync_xero_to_fyle_payments, import_categories, auto_map_employees, auto_create_destination_entity, map_merchant_to_contact, skip_cards_mapping, import_tax_codes, charts_of_accounts, import_customers, change_accounting_period, auto_create_merchant_destination_entity, is_simplify_report_closure_enabled, import_suppliers_as_merchants) FROM stdin; -1 PURCHASE BILL BANK TRANSACTION 2022-08-02 20:25:24.644164+00 2022-08-02 20:25:24.644209+00 1 f t t \N t t f t {EXPENSE} t t f f f +COPY public.workspace_general_settings (id, reimbursable_expenses_object, corporate_credit_card_expenses_object, created_at, updated_at, workspace_id, sync_fyle_to_xero_payments, sync_xero_to_fyle_payments, import_categories, auto_map_employees, auto_create_destination_entity, map_merchant_to_contact, skip_cards_mapping, import_tax_codes, charts_of_accounts, import_customers, change_accounting_period, auto_create_merchant_destination_entity, is_simplify_report_closure_enabled, import_suppliers_as_merchants, memo_structure) FROM stdin; +1 PURCHASE BILL BANK TRANSACTION 2022-08-02 20:25:24.644164+00 2022-08-02 20:25:24.644209+00 1 f t t \N t t f t {EXPENSE} t t f f f {employee_email,category,merchant,spent_on,report_number,purpose} \. @@ -5180,7 +5182,7 @@ SELECT pg_catalog.setval('public.django_content_type_id_seq', 40, true); -- Name: django_migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres -- -SELECT pg_catalog.setval('public.django_migrations_id_seq', 159, true); +SELECT pg_catalog.setval('public.django_migrations_id_seq', 160, true); -- diff --git a/tests/test_workspaces/fixtures.py b/tests/test_workspaces/fixtures.py index 3fad6c7d..ee90f9d6 100644 --- a/tests/test_workspaces/fixtures.py +++ b/tests/test_workspaces/fixtures.py @@ -12,6 +12,7 @@ "import_suppliers_as_merchants": True, "auto_map_employees": "EMAIL", "auto_create_destination_entity": True, + "memo_structure": ["report_number", "spent_on"], "skip_cards_mapping": False, "import_tax_codes": True, "import_customers": False, diff --git a/tests/test_workspaces/test_apis/test_advanced_settings/fixtures.py b/tests/test_workspaces/test_apis/test_advanced_settings/fixtures.py index cb5044de..d28305d2 100644 --- a/tests/test_workspaces/test_apis/test_advanced_settings/fixtures.py +++ b/tests/test_workspaces/test_apis/test_advanced_settings/fixtures.py @@ -5,6 +5,7 @@ "sync_fyle_to_xero_payments": False, "sync_xero_to_fyle_payments": True, "auto_create_destination_entity": True, + "memo_structure": ["report_number", "expense_link", "spent_on"], "auto_create_merchant_destination_entity": True, }, "general_mappings": { @@ -24,6 +25,7 @@ "sync_xero_to_fyle_payments": True, "auto_create_destination_entity": True, "auto_create_merchant_destination_entity": True, + "memo_structure": ["report_number", "expense_link", "spent_on"] }, "general_mappings": { "payment_account": {"id": "2", "name": "Business Savings Account"} diff --git a/tests/test_workspaces/test_apis/test_clone_settings/fixtures.py b/tests/test_workspaces/test_apis/test_clone_settings/fixtures.py index d24a8009..5a6df6d0 100644 --- a/tests/test_workspaces/test_apis/test_clone_settings/fixtures.py +++ b/tests/test_workspaces/test_apis/test_clone_settings/fixtures.py @@ -69,6 +69,7 @@ "sync_xero_to_fyle_payments": True, "auto_create_destination_entity": True, "auto_create_merchant_destination_entity": True, + "memo_structure": ["report_number", "expense_link", "spent_on"] }, "general_mappings": { "payment_account": {"id": "2", "name": "Business Savings Account"} @@ -122,6 +123,7 @@ "sync_xero_to_fyle_payments": False, "auto_create_destination_entity": False, "change_accounting_period": False, + "memo_structure": ["report_number", "expense_link", "spent_on"], "auto_create_merchant_destination_entity": False, }, "general_mappings": {"payment_account": {"name": None, "id": None}}, diff --git a/tests/test_xero/test_models.py b/tests/test_xero/test_models.py index 048cfd36..cbf3fc2f 100644 --- a/tests/test_xero/test_models.py +++ b/tests/test_xero/test_models.py @@ -35,7 +35,7 @@ def test_create_bill(db): assert bill_lineitem.amount == 10.0 assert ( bill_lineitem.description - == "sravan.kumar@fyle.in, category - Food spent on 2020-01-01, report number - C/2022/06/R/1 - https://staging.fyle.tech/app/admin/#/enterprise/view_expense/txGilVGolf60?org_id=orPJvXuoLqvJ" + == "sravan.kumar@fyle.in - Food - - 2020-01-01 - C/2022/06/R/1 - " ) assert bill.currency == "USD" @@ -159,7 +159,7 @@ def test_get_expense_purpose(db): expense_group = ExpenseGroup.objects.get(id=10) expenses = expense_group.expenses.all() - workspace_general_settings = WorkspaceGeneralSettings.objects.filter(workspace_id=workspace_id) + workspace_general_settings = WorkspaceGeneralSettings.objects.filter(workspace_id=workspace_id).first() for lineitem in expenses: category = ( @@ -172,7 +172,7 @@ def test_get_expense_purpose(db): assert ( expense_purpose - == "sravan.kumar@fyle.in, category - WIP / None spent on 2022-05-25, report number - C/2022/05/R/13 - https://staging.fyle.tech/app/admin/#/enterprise/view_expense/txBMQRkBQciI?org_id=orPJvXuoLqvJ" + == "sravan.kumar@fyle.in - Food - - 2020-01-01 - C/2022/06/R/1 - " ) From 5659af3ca78d8387d08c2e03d5feff6c29be80d2 Mon Sep 17 00:00:00 2001 From: Nilesh Pant Date: Wed, 4 Dec 2024 03:16:15 +0530 Subject: [PATCH 4/5] fix tests --- tests/test_xero/test_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_xero/test_models.py b/tests/test_xero/test_models.py index cbf3fc2f..7b15fc0d 100644 --- a/tests/test_xero/test_models.py +++ b/tests/test_xero/test_models.py @@ -172,7 +172,7 @@ def test_get_expense_purpose(db): assert ( expense_purpose - == "sravan.kumar@fyle.in - Food - - 2020-01-01 - C/2022/06/R/1 - " + == "sravan.kumar@fyle.in - WIP / None - - 2022-05-25 - C/2022/05/R/13 - " ) From 974b24ad63b2999fe9da2bbf94323b355c25be60 Mon Sep 17 00:00:00 2001 From: Nilesh Pant Date: Wed, 4 Dec 2024 11:54:33 +0530 Subject: [PATCH 5/5] fix comments --- apps/xero/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/xero/models.py b/apps/xero/models.py index f33b47c1..8be9e715 100644 --- a/apps/xero/models.py +++ b/apps/xero/models.py @@ -154,7 +154,7 @@ def get_expense_purpose(workspace_id, lineitem, category, workspace_general_sett details = { 'employee_email': lineitem.employee_email, - 'merchant': '{} - '.format(lineitem.vendor) if lineitem.vendor else '', + 'merchant': '{}'.format(lineitem.vendor) if lineitem.vendor else '', 'category': '{0}'.format(category) if lineitem.category else '', 'purpose': '{0}'.format(lineitem.purpose) if lineitem.purpose else '', 'report_number': '{0}'.format(lineitem.claim_number),