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..8be9e715 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,32 @@ 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 + memo_structure = workspace_general_settings.memo_structure 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 = '' + 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 +261,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 +288,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 +448,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 +480,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..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 @@ -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( 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 7bdfe6ca..7b15fc0d 100644 --- a/tests/test_xero/test_models.py +++ b/tests/test_xero/test_models.py @@ -26,15 +26,16 @@ 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 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" @@ -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).first() + for lineitem in expenses: category = ( lineitem.category @@ -164,11 +168,11 @@ 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 - == "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 - WIP / None - - 2022-05-25 - C/2022/05/R/13 - " )