From 21ce0e8c66aa2282c06816b97c1e919232d529e5 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 20 Jul 2021 19:59:54 +0530 Subject: [PATCH 01/85] Dynamic modal test --- fyle_slack_app/slack/commands/handlers.py | 22 +++-- fyle_slack_app/slack/commands/views.py | 5 +- .../interactives/block_action_handlers.py | 27 +++++- fyle_slack_app/slack/interactives/views.py | 38 +++++++- fyle_slack_app/slack/ui/dashboard/messages.py | 90 +++++++++++++++++++ 5 files changed, 174 insertions(+), 8 deletions(-) diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index 243945a6..70912b67 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -5,6 +5,7 @@ from django_q.tasks import async_task from fyle.platform import exceptions +from slack_sdk.web.client import WebClient from fyle_slack_app import tracking from fyle_slack_app.libs import utils, assertions, logger @@ -25,7 +26,8 @@ class SlackCommandHandler: def _initialize_command_handlers(self): self._command_handlers = { 'fyle_unlink_account': self.handle_fyle_unlink_account, - 'fyle_notification_preferences': self.handle_fyle_notification_preferences + 'fyle_notification_preferences': self.handle_fyle_notification_preferences, + 'modal': self.modal_test } def handle_invalid_command(self, user_id: str, team_id: str, user_dm_channel_id: str) -> JsonResponse: @@ -38,17 +40,17 @@ def handle_invalid_command(self, user_id: str, team_id: str, user_dm_channel_id: return JsonResponse({}, status=200) - def handle_slack_command(self, command: str, user_id: str, team_id: str, user_dm_channel_id: str) -> Callable: + def handle_slack_command(self, command: str, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id) -> Callable: # Initialize slack command handlers self._initialize_command_handlers() handler = self._command_handlers.get(command, self.handle_invalid_command) - return handler(user_id, team_id, user_dm_channel_id) + return handler(user_id, team_id, user_dm_channel_id, trigger_id) - def handle_fyle_unlink_account(self, user_id: str, team_id: str, user_dm_channel_id: str) -> JsonResponse: + def handle_fyle_unlink_account(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id) -> JsonResponse: async_task( 'fyle_slack_app.slack.commands.tasks.fyle_unlink_account', user_id, @@ -74,7 +76,7 @@ def update_home_tab_with_pre_auth_message(self, user_id: str, team_id: str) -> N slack_client.views_publish(user_id=user_id, view=pre_auth_message_view) - def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_dm_channel_id: str) -> JsonResponse: + def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id) -> JsonResponse: user = utils.get_or_none(User, slack_user_id=user_id) assertions.assert_found(user, 'Slack user not found') @@ -111,3 +113,13 @@ def track_fyle_account_unlinked(self, user: User) -> None: tracking.identify_user(user.email) tracking.track_event(user.email, 'Fyle Account Unlinked From Slack', event_data) + + + def modal_test(self, user_id, team_id, user_dm_channel_id, trigger_id): + slack_client: WebClient = slack_utils.get_slack_client(team_id) + + modal = dashboard_messages.mock_message() + + slack_client.views_open(user_id=user_id, view=modal, trigger_id=trigger_id) + + return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/commands/views.py b/fyle_slack_app/slack/commands/views.py index fc385839..37b97e2c 100644 --- a/fyle_slack_app/slack/commands/views.py +++ b/fyle_slack_app/slack/commands/views.py @@ -10,8 +10,11 @@ def post(self, request: HttpRequest, command: str) -> HttpResponse: team_id = request.POST['team_id'] user_id = request.POST['user_id'] user_dm_channel_id = request.POST['channel_id'] + trigger_id = request.POST['trigger_id'] - self.handle_slack_command(command, user_id, team_id, user_dm_channel_id) + print('REQIEST -> ', request.POST) + + self.handle_slack_command(command, user_id, team_id, user_dm_channel_id, trigger_id) # Empty "" HttpResponse beacause for slash commands slack return the response as message to user return HttpResponse("", status=200) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 681d8fb2..6cceb795 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -30,10 +30,33 @@ def _initialize_block_action_handlers(self): 'report_submitted_notification_preference': self.handle_notification_preference_selection, 'report_partially_approved_notification_preference': self.handle_notification_preference_selection, 'report_payment_processing_notification_preference': self.handle_notification_preference_selection, - 'report_approver_sendback_notification_preference': self.handle_notification_preference_selection + 'report_approver_sendback_notification_preference': self.handle_notification_preference_selection, + + # Dynamic options + 'external_select_option': self.enternal_select } + def enternal_select(self, slack_payload: Dict, user_id: str, team_id: str): + trigger_id = slack_payload['trigger_id'] + + from fyle_slack_app.slack.ui.dashboard.messages import mock_message_2, mock_message + + slack_client = get_slack_client(team_id) + + + value = slack_payload['actions'][0]['selected_option']['value'] + + view_id = slack_payload['container']['view_id'] + + + if value == 'category': + slack_client.views_update(view=mock_message_2(), view_id=view_id) + else: + slack_client.views_update(view_id=view_id, view=mock_message()) + + return JsonResponse({}, status=200) + # Gets called when function with an action is not found def _handle_invalid_block_actions(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: slack_client = get_slack_client(team_id) @@ -59,6 +82,8 @@ def handle_block_actions(self, slack_payload: Dict, user_id: str, team_id: str) action_id = slack_payload['actions'][0]['action_id'] + print('ACTIONM ID -> ', action_id) + handler = self._block_action_handlers.get(action_id, self._handle_invalid_block_actions) return handler(slack_payload, user_id, team_id) diff --git a/fyle_slack_app/slack/interactives/views.py b/fyle_slack_app/slack/interactives/views.py index 550217d4..b58c22bb 100644 --- a/fyle_slack_app/slack/interactives/views.py +++ b/fyle_slack_app/slack/interactives/views.py @@ -12,7 +12,7 @@ class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler): def post(self, request: HttpRequest) -> JsonResponse: payload = request.POST.get('payload') slack_payload = json.loads(payload) - + print('SLACK PAYLOAD -> ', slack_payload) # Extract details from payload user_id = slack_payload['user']['id'] team_id = slack_payload['team']['id'] @@ -28,4 +28,40 @@ def post(self, request: HttpRequest) -> JsonResponse: # Call handler function from ShortcutHandler return self.handle_shortcuts(slack_payload, user_id, team_id) + elif event_type == 'block_suggestion': + # Call handler function from BlockActionHandler + a = { + 'options': [ + { + "text": { + "type": "plain_text", + "text": "Category" + }, + "value": "category" + }, + { + "text": { + "type": "plain_text", + "text": "Discrepancy" + }, + "value": "discrepancy" + }, + { + "text": { + "type": "plain_text", + "text": "Projects" + }, + "value": "projects" + }, + { + "text": { + "type": "plain_text", + "text": "Currency" + }, + "value": "currency" + } + ] + } + return JsonResponse(a) + return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index dd39ee7b..d202f859 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -17,3 +17,93 @@ def get_post_authorization_message() -> Dict: 'type': 'home', 'blocks': post_authorization_message_blocks } + + +def mock_message(): + return { + "type": "modal", + "title": { + "type": "plain_text", + "text": "My App", + "emoji": True + }, + "submit": { + "type": "plain_text", + "text": "Submit", + "emoji": True + }, + "close": { + "type": "plain_text", + "text": "Cancel", + "emoji": True + }, + "blocks": [ + { + "dispatch_action": True, + "type": "input", + "element": { + "type": "external_select", + "placeholder": { + "type": "plain_text", + "text": "Select an item", + "emoji": True + }, + "action_id": "external_select_option" + }, + "label": { + "type": "plain_text", + "text": "Label", + "emoji": True + } + } + ] +} + + +def mock_message_2(): + return { + "type": "modal", + "title": { + "type": "plain_text", + "text": "My App", + "emoji": True + }, + "submit": { + "type": "plain_text", + "text": "Submit", + "emoji": True + }, + "close": { + "type": "plain_text", + "text": "Cancel", + "emoji": True + }, + "blocks": [ + { + "dispatch_action": True, + "type": "input", + "element": { + "type": "external_select", + "placeholder": { + "type": "plain_text", + "text": "Select an item", + "emoji": True + }, + "action_id": "external_select_option" + }, + "label": { + "type": "plain_text", + "text": "Label", + "emoji": True + } + }, + { + "type": "section", + "text": { + "type": "plain_text", + "text": "Dynamic updated view", + "emoji": True + } + } + ] +} \ No newline at end of file From cf24ed2e3628392922f1b2717c9dfda4927c7df6 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Sat, 21 Aug 2021 18:59:27 +0530 Subject: [PATCH 02/85] Expense fields stuff --- fyle_slack_app/admin.py | 1906 +++++++++++++++++ fyle_slack_app/slack/commands/handlers.py | 2 +- fyle_slack_app/slack/ui/dashboard/messages.py | 249 ++- 3 files changed, 2066 insertions(+), 91 deletions(-) diff --git a/fyle_slack_app/admin.py b/fyle_slack_app/admin.py index e69de29b..5bc69f9c 100644 --- a/fyle_slack_app/admin.py +++ b/fyle_slack_app/admin.py @@ -0,0 +1,1906 @@ +expense_fields = [ + { + "id": 6182, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "bus_travel_class", + "field_name": "Travel Class", + "seq": 1, + "type": "TEXT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Enter class of travel", + "default_value": None, + "options": [], + "org_category_ids": [ + 136517 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6183, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "cost_center_id", + "field_name": "Cost Center", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Cost Center", + "default_value": None, + "options": [], + "org_category_ids": [ + 136506, + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136514, + 136515, + 136516, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6184, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance", + "field_name": "Distance", + "seq": 1, + "type": "NUMBER", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Enter Distance", + "default_value": None, + "options": [], + "org_category_ids": [ + 136514 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6185, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance", + "field_name": "Distance", + "seq": 2, + "type": "NUMBER", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Enter Distance", + "default_value": None, + "options": [], + "org_category_ids": [ + 136519 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6186, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance_unit", + "field_name": "Unit", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Unit", + "default_value": "MILES", + "options": [ + "KM", + "MILES" + ], + "org_category_ids": [ + 136514 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6187, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance_unit", + "field_name": "Unit", + "seq": 2, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Unit", + "default_value": "MILES", + "options": [ + "KM", + "MILES" + ], + "org_category_ids": [ + 136519 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6188, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "flight_journey_travel_class", + "field_name": "Onward Travel Class", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select travel class", + "default_value": None, + "options": [ + "BUSINESS", + "ECONOMY", + "FIRST_CLASS" + ], + "org_category_ids": [ + 136525 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6189, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "flight_return_travel_class", + "field_name": "Return Travel Class", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select travel class", + "default_value": None, + "options": [ + "BUSINESS", + "ECONOMY", + "FIRST_CLASS" + ], + "org_category_ids": [ + 136525 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6190, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "from_dt", + "field_name": "From", + "seq": 1, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136516 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6191, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "from_dt", + "field_name": "Onward Date", + "seq": 2, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136507, + 136517, + 136525 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6192, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "from_dt", + "field_name": "Check-in Date", + "seq": 3, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136521 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6193, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "location1", + "field_name": "From", + "seq": 1, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select a City", + "default_value": None, + "options": [], + "org_category_ids": [ + 136507, + 136517, + 136525 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6194, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "location1", + "field_name": "City", + "seq": 2, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select a City", + "default_value": None, + "options": [], + "org_category_ids": [ + 136521 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6195, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "location1", + "field_name": "From", + "seq": 3, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Choose Location - Search for Place or Area", + "default_value": None, + "options": [], + "org_category_ids": [ + 136514 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6196, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "location2", + "field_name": "To", + "seq": 1, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select a City", + "default_value": None, + "options": [], + "org_category_ids": [ + 136507, + 136517, + 136525 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6197, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "location2", + "field_name": "To", + "seq": 2, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Choose Location - Search for Place or Area", + "default_value": None, + "options": [], + "org_category_ids": [ + 136514 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6198, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "num_days", + "field_name": "No. of Days", + "seq": 1, + "type": "NUMBER", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "No. of Days", + "default_value": None, + "options": [], + "org_category_ids": [ + 136516 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6199, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "purpose", + "field_name": "Purpose", + "seq": 1, + "type": "TEXT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "E.g. Client Meeting", + "default_value": None, + "options": [], + "org_category_ids": [ + 136506, + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136514, + 136515, + 136516, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6200, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "to_dt", + "field_name": "To", + "seq": 1, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136516 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6201, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "to_dt", + "field_name": "Return Date", + "seq": 2, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136507, + 136517, + 136525 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6202, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "to_dt", + "field_name": "Check-out Date", + "seq": 3, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136521 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6199, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "train_travel_class", + "field_name": "Travel class", + "seq": 1, + "type": "TEXT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Enter class of travel", + "default_value": None, + "options": [], + "org_category_ids": [ + 136507 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6204, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "txn_dt", + "field_name": "Date of Spend", + "seq": 1, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136506, + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136515, + 136516, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6205, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "txn_dt", + "field_name": "Date of Travel", + "seq": 2, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136514 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 6206, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "vendor_id", + "field_name": "Merchant", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "E.g. Uber", + "default_value": None, + "options": [], + "org_category_ids": [ + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136515, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 185323, + "created_at": "2021-07-28T08:48:55.152Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-07-28T08:48:55.152Z", + "updated_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "org_id": "orsReFb3Oou8", + "column_name": "project_id", + "field_name": "Project", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Project", + "default_value": None, + "options": [], + "org_category_ids": [], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 189621, + "created_at": "2021-07-29T11:05:38.811Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-07-29T11:05:38.811Z", + "updated_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "org_id": "orsReFb3Oou8", + "column_name": "billable", + "field_name": "Billable", + "seq": 1, + "type": "BOOLEAN", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Billable", + "default_value": "False", + "options": [], + "org_category_ids": [], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 194141, + "created_at": "2021-08-17T04:48:57.792Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "updated_at": "2021-08-17T04:48:57.792Z", + "updated_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None + }, + "org_id": "orsReFb3Oou8", + "column_name": "tax_group", + "field_name": "Tax Group", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Tax Group", + "default_value": None, + "options": [], + "org_category_ids": [], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + }, + { + "id": 195217, + "created_at": "2021-08-18T07:35:59.906Z", + "created_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN", + "APPROVER" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "updated_at": "2021-08-18T07:35:59.906Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": [ + "OWNER", + "FYLER", + "ADMIN", + "APPROVER" + ], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": "\"https://staging.fyle.tech\"", + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq" + }, + "org_id": "orsReFb3Oou8", + "column_name": "text_column1", + "field_name": "TEST 1", + "seq": 1, + "type": "TEXT", + "is_custom": True, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "testiing", + "default_value": None, + "options": [], + "org_category_ids": [ + 136520 + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR" + ] + } +] \ No newline at end of file diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index 70912b67..771ee377 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -118,7 +118,7 @@ def track_fyle_account_unlinked(self, user: User) -> None: def modal_test(self, user_id, team_id, user_dm_channel_id, trigger_id): slack_client: WebClient = slack_utils.get_slack_client(team_id) - modal = dashboard_messages.mock_message() + modal = dashboard_messages.expense_dialog_form() slack_client.views_open(user_id=user_id, view=modal, trigger_id=trigger_id) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index d202f859..17a9ada5 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -1,109 +1,178 @@ +import json +from random import random from typing import Dict +import random, copy + from fyle_slack_app.slack.ui.authorization import messages def get_pre_authorization_message(user_name: str, fyle_oauth_url: str) -> Dict: - pre_authorization_message_blocks = messages.get_pre_authorization_message(user_name, fyle_oauth_url) - return { - 'type': 'home', - 'blocks': pre_authorization_message_blocks - } + pre_authorization_message_blocks = messages.get_pre_authorization_message( + user_name, fyle_oauth_url + ) + return {"type": "home", "blocks": pre_authorization_message_blocks} def get_post_authorization_message() -> Dict: post_authorization_message_blocks = messages.get_post_authorization_message() - return { - 'type': 'home', - 'blocks': post_authorization_message_blocks - } + return {"type": "home", "blocks": post_authorization_message_blocks} def mock_message(): return { - "type": "modal", - "title": { - "type": "plain_text", - "text": "My App", - "emoji": True - }, - "submit": { - "type": "plain_text", - "text": "Submit", - "emoji": True - }, - "close": { - "type": "plain_text", - "text": "Cancel", - "emoji": True - }, - "blocks": [ - { - "dispatch_action": True, - "type": "input", - "element": { - "type": "external_select", - "placeholder": { - "type": "plain_text", - "text": "Select an item", - "emoji": True - }, - "action_id": "external_select_option" - }, - "label": { - "type": "plain_text", - "text": "Label", - "emoji": True - } - } - ] -} + "type": "modal", + "title": {"type": "plain_text", "text": "My App", "emoji": True}, + "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "blocks": [ + { + "dispatch_action": True, + "type": "input", + "element": { + "type": "external_select", + "placeholder": { + "type": "plain_text", + "text": "Select an item", + "emoji": True, + }, + "action_id": "external_select_option", + }, + "label": {"type": "plain_text", "text": "Label", "emoji": True}, + } + ], + } def mock_message_2(): return { - "type": "modal", - "title": { - "type": "plain_text", - "text": "My App", - "emoji": True - }, - "submit": { - "type": "plain_text", - "text": "Submit", - "emoji": True - }, - "close": { - "type": "plain_text", - "text": "Cancel", - "emoji": True - }, - "blocks": [ - { - "dispatch_action": True, - "type": "input", - "element": { - "type": "external_select", - "placeholder": { - "type": "plain_text", - "text": "Select an item", - "emoji": True - }, - "action_id": "external_select_option" - }, - "label": { - "type": "plain_text", - "text": "Label", - "emoji": True - } - }, - { - "type": "section", - "text": { - "type": "plain_text", - "text": "Dynamic updated view", - "emoji": True + "type": "modal", + "title": {"type": "plain_text", "text": "My App", "emoji": True}, + "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "blocks": [ + { + "dispatch_action": True, + "type": "input", + "element": { + "type": "external_select", + "placeholder": { + "type": "plain_text", + "text": "Select an item", + "emoji": True, + }, + "action_id": "external_select_option", + }, + "label": {"type": "plain_text", "text": "Label", "emoji": True}, + }, + { + "type": "section", + "text": { + "type": "plain_text", + "text": "Dynamic updated view", + "emoji": True, + }, + }, + ], + } + + +from fyle_slack_app.admin import expense_fields + + +def generate_category_field_mapping(expense_fields): + mapping = {} + for ef in expense_fields: + for ci in ef['org_category_ids']: + if ci not in mapping: + mapping[ci] = [] + ci_details = { + 'field_name': ef['field_name'], + 'type': ef['type'], + 'is_custom': ef['is_custom'], + 'is_enabled': ef['is_enabled'], + 'is_mandatory': ef['is_mandatory'], + 'placeholder': ef['placeholder'], + 'default_value': ef['default_value'] } - } - ] -} \ No newline at end of file + if ef['type'] == 'SELECT': + ci_details['options'] = ef['options'] + mapping[ci].append(ci_details) + + print(json.dumps(mapping, indent=2)) + + +def expense_dialog_form(): + blocks = [] + for expense_field in expense_fields: + if expense_field["type"] in ["NUMBER", "TEXT"]: + block = { + "type": "input", + # "block_id": "{}".format(random), + "label": { + "type": "plain_text", + "text": "{}".format(expense_field["field_name"]), + }, + "element": { + "type": "plain_text_input", + "action_id": "{}".format(expense_field["field_name"].lower()), + "placeholder": { + "type": "plain_text", + "text": "{}".format(expense_field["placeholder"]), + }, + }, + } + elif expense_field["type"] == "SELECT": + block = { + "type": "input", + # "block_id": "{}".format(uuid.uuid4()), + "label": { + "type": "plain_text", + "text": "{}".format(expense_field["field_name"]), + }, + "element": { + "type": "static_select", + "action_id": "{}".format(expense_field["field_name"].lower()), + "placeholder": { + "type": "plain_text", + "text": "{}".format(expense_field["placeholder"]), + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "*this is plain_text text*", + }, + "value": "value-0", + }, + { + "text": { + "type": "plain_text", + "text": "*this is plain_text text*", + }, + "value": "value-1", + }, + { + "text": { + "type": "plain_text", + "text": "*this is plain_text text*", + }, + "value": "value-2", + }, + ], + }, + } + + blocks.append(block) + generate_category_field_mapping(expense_fields) + view = { + "type": "modal", + "callback_id": "create_expense", + "title": {"type": "plain_text", "text": "Add Expense"}, + "submit": {"type": "plain_text", "text": "Add Expense"}, + "close": {"type": "plain_text", "text": "Cancel"}, + "notify_on_close": True, + "blocks": blocks, + } + + return view From 364cbdac22222ef7d72768b204cfade6b7b33d69 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 23 Aug 2021 13:22:16 +0530 Subject: [PATCH 03/85] dynamic expense form poc --- fyle_slack_app/admin.py | 343 +++++++++++++++++- .../interactives/block_action_handlers.py | 29 +- fyle_slack_app/slack/ui/dashboard/messages.py | 328 ++++++++++++++--- 3 files changed, 631 insertions(+), 69 deletions(-) diff --git a/fyle_slack_app/admin.py b/fyle_slack_app/admin.py index 5bc69f9c..56dee427 100644 --- a/fyle_slack_app/admin.py +++ b/fyle_slack_app/admin.py @@ -1887,7 +1887,7 @@ "default_value": None, "options": [], "org_category_ids": [ - 136520 + 123456 ], "code": None, "roles_editable": [ @@ -1903,4 +1903,345 @@ "AUDITOR" ] } +] + +categories = [ + { + "id":835229, + "created_at":"2019-02-13T08:24:43.405Z", + "updated_at":"2019-02-13T08:24:44.405Z", + "org_id":"orKPte3WEwuJ", + "name":"Activity", + "code":None, + "fyle_category":"Activity", + "sub_category":"Activity", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835240, + "created_at":"2019-02-13T08:24:43.463Z", + "updated_at":"2019-02-13T08:24:44.463Z", + "org_id":"orKPte3WEwuJ", + "name":"Bus", + "code":None, + "fyle_category":"Bus", + "sub_category":"Bus", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835243, + "created_at":"2019-02-13T08:24:43.476Z", + "updated_at":"2019-02-13T08:24:44.476Z", + "org_id":"orKPte3WEwuJ", + "name":"Courier", + "code":None, + "fyle_category":"Courier", + "sub_category":"Courier", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835235, + "created_at":"2019-02-13T08:24:43.441Z", + "updated_at":"2019-02-13T08:24:44.441Z", + "org_id":"orKPte3WEwuJ", + "name":"Entertainment", + "code":None, + "fyle_category":"Entertainment", + "sub_category":"Entertainment", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835248, + "created_at":"2019-02-13T08:24:43.498Z", + "updated_at":"2019-02-13T08:24:44.498Z", + "org_id":"orKPte3WEwuJ", + "name":"Flight", + "code":None, + "fyle_category":"Flight", + "sub_category":"Flight", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835238, + "created_at":"2019-02-13T08:24:43.455Z", + "updated_at":"2019-02-13T08:24:44.455Z", + "org_id":"orKPte3WEwuJ", + "name":"Food", + "code":None, + "fyle_category":"Food", + "sub_category":"Food", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835231, + "created_at":"2019-02-13T08:24:43.413Z", + "updated_at":"2019-02-13T08:24:44.413Z", + "org_id":"orKPte3WEwuJ", + "name":"Fuel", + "code":None, + "fyle_category":"Fuel", + "sub_category":"Fuel", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835244, + "created_at":"2019-02-13T08:24:43.481Z", + "updated_at":"2019-02-13T08:24:44.481Z", + "org_id":"orKPte3WEwuJ", + "name":"Hotel", + "code":None, + "fyle_category":"Hotel", + "sub_category":"Hotel", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835241, + "created_at":"2019-02-13T08:24:43.468Z", + "updated_at":"2019-02-13T08:24:44.468Z", + "org_id":"orKPte3WEwuJ", + "name":"Internet", + "code":None, + "fyle_category":"Internet", + "sub_category":"Internet", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835237, + "created_at":"2019-02-13T08:24:43.451Z", + "updated_at":"2019-02-13T08:24:44.451Z", + "org_id":"orKPte3WEwuJ", + "name":"Mileage", + "code":None, + "fyle_category":"Mileage", + "sub_category":"Mileage", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835247, + "created_at":"2019-02-13T08:24:43.494Z", + "updated_at":"2019-02-13T08:24:44.494Z", + "org_id":"orKPte3WEwuJ", + "name":"Office Party", + "code":None, + "fyle_category":"Office Party", + "sub_category":"Office Party", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835233, + "created_at":"2019-02-13T08:24:43.421Z", + "updated_at":"2019-02-13T08:24:44.421Z", + "org_id":"orKPte3WEwuJ", + "name":"Office Supplies", + "code":None, + "fyle_category":"Office Supplies", + "sub_category":"Office Supplies", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835236, + "created_at":"2019-02-13T08:24:43.447Z", + "updated_at":"2019-02-13T08:24:44.447Z", + "org_id":"orKPte3WEwuJ", + "name":"Others", + "code":None, + "fyle_category":"Others", + "sub_category":"Others", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835250, + "created_at":"2019-02-13T08:24:43.507Z", + "updated_at":"2019-02-13T08:24:44.507Z", + "org_id":"orKPte3WEwuJ", + "name":"Parking", + "code":None, + "fyle_category":"Parking", + "sub_category":"Parking", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835239, + "created_at":"2019-02-13T08:24:43.459Z", + "updated_at":"2019-02-13T08:24:44.459Z", + "org_id":"orKPte3WEwuJ", + "name":"Per Diem", + "code":None, + "fyle_category":"Per Diem", + "sub_category":"Per Diem", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835246, + "created_at":"2019-02-13T08:24:43.489Z", + "updated_at":"2019-02-13T08:24:44.489Z", + "org_id":"orKPte3WEwuJ", + "name":"Phone", + "code":None, + "fyle_category":"Phone", + "sub_category":"Phone", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835245, + "created_at":"2019-02-13T08:24:43.485Z", + "updated_at":"2019-02-13T08:24:44.485Z", + "org_id":"orKPte3WEwuJ", + "name":"Professional Services", + "code":None, + "fyle_category":"Professional Services", + "sub_category":"Professional Services", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835232, + "created_at":"2019-02-13T08:24:43.417Z", + "updated_at":"2019-02-13T08:24:44.417Z", + "org_id":"orKPte3WEwuJ", + "name":"Snacks", + "code":None, + "fyle_category":"Snacks", + "sub_category":"Snacks", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835249, + "created_at":"2019-02-13T08:24:43.502Z", + "updated_at":"2019-02-13T08:24:44.502Z", + "org_id":"orKPte3WEwuJ", + "name":"Software", + "code":None, + "fyle_category":"Software", + "sub_category":"Software", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835252, + "created_at":"2019-02-13T08:24:43.515Z", + "updated_at":"2019-02-13T08:24:44.515Z", + "org_id":"orKPte3WEwuJ", + "name":"Tax", + "code":None, + "fyle_category":"Tax", + "sub_category":"Tax", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835242, + "created_at":"2019-02-13T08:24:43.472Z", + "updated_at":"2019-02-13T08:24:44.472Z", + "org_id":"orKPte3WEwuJ", + "name":"Taxi", + "code":None, + "fyle_category":"Taxi", + "sub_category":"Taxi", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835251, + "created_at":"2019-02-13T08:24:43.511Z", + "updated_at":"2019-02-13T08:24:44.511Z", + "org_id":"orKPte3WEwuJ", + "name":"Toll Charge", + "code":None, + "fyle_category":"Toll Charge", + "sub_category":"Toll Charge", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835230, + "created_at":"2019-02-13T08:24:43.409Z", + "updated_at":"2019-02-13T08:24:44.409Z", + "org_id":"orKPte3WEwuJ", + "name":"Train", + "code":None, + "fyle_category":"Train", + "sub_category":"Train", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835253, + "created_at":"2019-02-13T08:24:43.522Z", + "updated_at":"2019-02-13T08:24:44.522Z", + "org_id":"orKPte3WEwuJ", + "name":"Training", + "code":None, + "fyle_category":"Training", + "sub_category":"Training", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835254, + "created_at":"2019-02-13T08:24:43.526Z", + "updated_at":"2019-02-13T08:24:44.526Z", + "org_id":"orKPte3WEwuJ", + "name":"Unspecified", + "code":None, + "fyle_category":"Unspecified", + "sub_category":"Unspecified", + "enabled":True, + "creator_id":None, + "last_updated_by":None + }, + { + "id":835234, + "created_at":"2019-02-13T08:24:43.426Z", + "updated_at":"2019-02-13T08:24:44.426Z", + "org_id":"orKPte3WEwuJ", + "name":"Utility", + "code":None, + "fyle_category":"Utility", + "sub_category":"Utility", + "enabled":True, + "creator_id":None, + "last_updated_by":None + } ] \ No newline at end of file diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 6cceb795..cda9c23b 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -1,3 +1,4 @@ +import json from typing import Callable, Dict from django.http import JsonResponse @@ -33,27 +34,39 @@ def _initialize_block_action_handlers(self): 'report_approver_sendback_notification_preference': self.handle_notification_preference_selection, # Dynamic options - 'external_select_option': self.enternal_select + 'category_select': self.category_select } - def enternal_select(self, slack_payload: Dict, user_id: str, team_id: str): + def category_select(self, slack_payload: Dict, user_id: str, team_id: str): trigger_id = slack_payload['trigger_id'] - from fyle_slack_app.slack.ui.dashboard.messages import mock_message_2, mock_message + from fyle_slack_app.slack.ui.dashboard.messages import mock_message_2, mock_message, generate_category_field_mapping, expense_dialog_form + from fyle_slack_app.admin import expense_fields slack_client = get_slack_client(team_id) - value = slack_payload['actions'][0]['selected_option']['value'] + category_id = slack_payload['actions'][0]['selected_option']['value'] view_id = slack_payload['container']['view_id'] + mappings = generate_category_field_mapping(expense_fields) + + # print('MAPINGS -> ', json.dumps(mappings, indent=2)) + + extra_fields = [] + + for key, value in mappings.items(): + for val in value: + if int(category_id) in val['category_ids']: + extra_fields.append(val) + + # print('EF -> ', json.dumps(extra_fields, indent=2)) - if value == 'category': - slack_client.views_update(view=mock_message_2(), view_id=view_id) - else: - slack_client.views_update(view_id=view_id, view=mock_message()) + if len(extra_fields) > 0: + new_expense_dialog_form = expense_dialog_form(extra_fields) + slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index 17a9ada5..b34d2f33 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -2,7 +2,7 @@ from random import random from typing import Dict -import random, copy +import random, copy, zlib, base64 from fyle_slack_app.slack.ui.authorization import messages @@ -78,101 +78,309 @@ def mock_message_2(): from fyle_slack_app.admin import expense_fields +from fyle_slack_app.libs.utils import encode_state def generate_category_field_mapping(expense_fields): - mapping = {} - for ef in expense_fields: - for ci in ef['org_category_ids']: - if ci not in mapping: - mapping[ci] = [] - ci_details = { - 'field_name': ef['field_name'], - 'type': ef['type'], - 'is_custom': ef['is_custom'], - 'is_enabled': ef['is_enabled'], - 'is_mandatory': ef['is_mandatory'], - 'placeholder': ef['placeholder'], - 'default_value': ef['default_value'] - } - if ef['type'] == 'SELECT': - ci_details['options'] = ef['options'] - mapping[ci].append(ci_details) - - print(json.dumps(mapping, indent=2)) - - -def expense_dialog_form(): + mapping = {} + for ef in expense_fields: + # for ci in ef['org_category_ids']: + # if ci not in mapping: + # mapping[ci] = [] + # if not any(ef['column_name'] in cd for cd in mapping[ci]): + # ci_details = { + # 'field_name': ef['field_name'], + # 'column_name': ef['column_name'], + # 'type': ef['type'], + # 'is_custom': ef['is_custom'], + # 'is_enabled': ef['is_enabled'], + # 'is_mandatory': ef['is_mandatory'], + # 'placeholder': ef['placeholder'], + # 'default_value': ef['default_value'], + # 'category_ids': ef['org_category_ids'] + # } + # if ef['type'] == 'SELECT': + # ci_details['options'] = ef['options'] + # mapping[ci].append(ci_details) + + if ef["column_name"] not in mapping: + mapping[ef["column_name"]] = [] + + ci_details = { + "field_name": ef["field_name"], + "column_name": ef["column_name"], + "type": ef["type"], + "is_custom": ef["is_custom"], + "is_enabled": ef["is_enabled"], + "is_mandatory": ef["is_mandatory"], + "placeholder": ef["placeholder"], + "default_value": ef["default_value"], + "category_ids": ef["org_category_ids"], + } + if ef["type"] == "SELECT": + ci_details["options"] = ef["options"] + mapping[ef["column_name"]].append(ci_details) + + # print(json.dumps(mapping, indent=2)) + # print('MAPPINGS -> ', zlib.compress(base64.b64encode(json.dumps(mapping).encode())).decode()) + return mapping + + +def expense_dialog_form(extra_fields=None): blocks = [] - for expense_field in expense_fields: - if expense_field["type"] in ["NUMBER", "TEXT"]: - block = { + # mappings = generate_category_field_mapping(expense_fields) + # for expense_field in expense_fields: + # if expense_field["type"] in ["NUMBER", "TEXT"]: + # block = { + # "type": "input", + # # "block_id": "{}".format(random), + # "label": { + # "type": "plain_text", + # "text": "{}".format(expense_field["field_name"]), + # }, + # "element": { + # "type": "plain_text_input", + # "action_id": "{}".format(expense_field["field_name"].lower()), + # "placeholder": { + # "type": "plain_text", + # "text": "{}".format(expense_field["placeholder"]), + # }, + # }, + # } + # elif expense_field["type"] == "SELECT": + # block = { + # "type": "input", + # # "block_id": "{}".format(uuid.uuid4()), + # "label": { + # "type": "plain_text", + # "text": "{}".format(expense_field["field_name"]), + # }, + # "element": { + # "type": "static_select", + # "action_id": "{}".format(expense_field["field_name"].lower()), + # "placeholder": { + # "type": "plain_text", + # "text": "{}".format(expense_field["placeholder"]), + # }, + # "options": [ + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-0", + # }, + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-1", + # }, + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-2", + # }, + # ], + # }, + # } + + # blocks.append(block) + view = { + "type": "modal", + "title": {"type": "plain_text", "text": "Create Expense", "emoji": True}, + "submit": {"type": "plain_text", "text": "Add Expense", "emoji": True}, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "blocks": [ + { "type": "input", - # "block_id": "{}".format(random), - "label": { - "type": "plain_text", - "text": "{}".format(expense_field["field_name"]), + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Select Currency", + "emoji": True, + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "INR", + "emoji": True, + }, + "value": "inr", + }, + { + "text": { + "type": "plain_text", + "text": "USD", + "emoji": True, + }, + "value": "usd", + }, + ], + "action_id": "category_select", }, + "label": {"type": "plain_text", "text": "Currency", "emoji": True}, + }, + { + "type": "input", "element": { "type": "plain_text_input", - "action_id": "{}".format(expense_field["field_name"].lower()), "placeholder": { "type": "plain_text", - "text": "{}".format(expense_field["placeholder"]), + "text": "Enter Amount", + "emoji": True, }, + "action_id": "amount", }, - } - elif expense_field["type"] == "SELECT": - block = { + "label": {"type": "plain_text", "text": "Amount", "emoji": True}, + }, + { "type": "input", - # "block_id": "{}".format(uuid.uuid4()), - "label": { - "type": "plain_text", - "text": "{}".format(expense_field["field_name"]), - }, "element": { "type": "static_select", - "action_id": "{}".format(expense_field["field_name"].lower()), "placeholder": { "type": "plain_text", - "text": "{}".format(expense_field["placeholder"]), + "text": "Select Payment Mode", + "emoji": True, + }, + "initial_option": { + "text": { + "type": "plain_text", + "text": "Paid by me", + "emoji": True, + }, + "value": "paid_by_me", }, "options": [ { "text": { "type": "plain_text", - "text": "*this is plain_text text*", + "text": "Paid by me", + "emoji": True, }, - "value": "value-0", + "value": "paid_by_me", }, { "text": { "type": "plain_text", - "text": "*this is plain_text text*", + "text": "Paid by company", + "emoji": True, }, - "value": "value-1", + "value": "paid_by_company", + }, + ], + "action_id": "payment_mode", + }, + "label": {"type": "plain_text", "text": "Payment Mode", "emoji": True}, + }, + { + "type": "input", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Eg. Client Meeting", + "emoji": True, + }, + "action_id": "purpose", + }, + "label": {"type": "plain_text", "text": "Purpose", "emoji": True}, + }, + { + "type": "input", + "element": { + "type": "datepicker", + "initial_date": "2021-08-23", + "placeholder": { + "type": "plain_text", + "text": "Select a date", + "emoji": True, + }, + "action_id": "datepicker-action", + }, + "label": {"type": "plain_text", "text": "Date of Spend", "emoji": True}, + }, + { + "type": "input", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Eg. Uber", + "emoji": True, + }, + "action_id": "merchant", + }, + "label": {"type": "plain_text", "text": "Merchant", "emoji": True}, + }, + { + "type": "input", + "dispatch_action": True, + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Eg. Travel", + "emoji": True, + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Custom Field Category", + "emoji": True, + }, + "value": "123456", }, { "text": { "type": "plain_text", - "text": "*this is plain_text text*", + "text": "Internet", + "emoji": True, }, - "value": "value-2", + "value": "1234", }, ], + "action_id": "category_select", }, - } - - blocks.append(block) - generate_category_field_mapping(expense_fields) - view = { - "type": "modal", - "callback_id": "create_expense", - "title": {"type": "plain_text", "text": "Add Expense"}, - "submit": {"type": "plain_text", "text": "Add Expense"}, - "close": {"type": "plain_text", "text": "Cancel"}, - "notify_on_close": True, - "blocks": blocks, + "label": {"type": "plain_text", "text": "Category", "emoji": True}, + }, + ], } + # view = { + # "type": "modal", + # "callback_id": "create_expense", + # "title": {"type": "plain_text", "text": "Add Expense"}, + # "submit": {"type": "plain_text", "text": "Add Expense"}, + # "close": {"type": "plain_text", "text": "Cancel"}, + # "notify_on_close": False, + # "blocks": blocks, + # # "private_metadata": zlib.compress(base64.b64encode(json.dumps(mappings).encode())).decode() + # } + if extra_fields is not None: + for field in extra_fields: + if field["type"] in ["NUMBER", "TEXT"]: + fld = { + "type": "input", + # "block_id": "{}".format(random), + "label": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + }, + "element": { + "type": "plain_text_input", + "action_id": "{}".format(field["field_name"].lower()), + "placeholder": { + "type": "plain_text", + "text": "{}".format(field["placeholder"]), + }, + }, + } + view["blocks"].append(fld) return view From 13a3d6b5e204b0744ccbd3558b1b1eebfe125134 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 23 Aug 2021 13:33:28 +0530 Subject: [PATCH 04/85] dynamic expense form poc --- fyle_slack_app/admin.py | 2 +- .../slack/interactives/block_action_handlers.py | 14 ++++++++------ fyle_slack_app/slack/ui/dashboard/messages.py | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/fyle_slack_app/admin.py b/fyle_slack_app/admin.py index 56dee427..f95ccdf3 100644 --- a/fyle_slack_app/admin.py +++ b/fyle_slack_app/admin.py @@ -1887,7 +1887,7 @@ "default_value": None, "options": [], "org_category_ids": [ - 123456 + 136250 ], "code": None, "roles_editable": [ diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index cda9c23b..bb59d6aa 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -41,7 +41,7 @@ def _initialize_block_action_handlers(self): def category_select(self, slack_payload: Dict, user_id: str, team_id: str): trigger_id = slack_payload['trigger_id'] - from fyle_slack_app.slack.ui.dashboard.messages import mock_message_2, mock_message, generate_category_field_mapping, expense_dialog_form + from fyle_slack_app.slack.ui.dashboard.messages import generate_category_field_mapping, expense_dialog_form from fyle_slack_app.admin import expense_fields slack_client = get_slack_client(team_id) @@ -55,14 +55,16 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): # print('MAPINGS -> ', json.dumps(mappings, indent=2)) - extra_fields = [] + extra_fields = [] + default_fields = ['purpose', 'txn_dt', 'vendor_id', 'cost_center_id'] for key, value in mappings.items(): - for val in value: - if int(category_id) in val['category_ids']: - extra_fields.append(val) + if key not in default_fields: + for val in value: + if int(category_id) in val['category_ids']: + extra_fields.append(val) - # print('EF -> ', json.dumps(extra_fields, indent=2)) + print('EF -> ', json.dumps(extra_fields, indent=2)) if len(extra_fields) > 0: new_expense_dialog_form = expense_dialog_form(extra_fields) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index b34d2f33..7f8a738f 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -335,7 +335,7 @@ def expense_dialog_form(extra_fields=None): "text": "Custom Field Category", "emoji": True, }, - "value": "123456", + "value": "136250", }, { "text": { From 582b85b803eb6ee50ca2fff3be3525fb22bdf405 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 23 Aug 2021 18:39:31 +0530 Subject: [PATCH 05/85] Some more testing with dynamic forms --- .../interactives/block_action_handlers.py | 18 +- fyle_slack_app/slack/ui/dashboard/messages.py | 729 +++++++++--------- 2 files changed, 390 insertions(+), 357 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index bb59d6aa..e53303e6 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -55,7 +55,7 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): # print('MAPINGS -> ', json.dumps(mappings, indent=2)) - extra_fields = [] + extra_fields = [] default_fields = ['purpose', 'txn_dt', 'vendor_id', 'cost_center_id'] for key, value in mappings.items(): @@ -63,12 +63,22 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): for val in value: if int(category_id) in val['category_ids']: extra_fields.append(val) - + print('EF -> ', json.dumps(extra_fields, indent=2)) + form_state = slack_payload['view']['state']['values'] + current_form_state = {} + + # for block_key, block_value in form_state.items(): + # pass + if len(extra_fields) > 0: - new_expense_dialog_form = expense_dialog_form(extra_fields) - slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) + new_expense_dialog_form = expense_dialog_form(extra_fields, form_state) + else: + new_expense_dialog_form = expense_dialog_form() + + slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) + return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index 7f8a738f..a69dba6a 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -8,73 +8,73 @@ def get_pre_authorization_message(user_name: str, fyle_oauth_url: str) -> Dict: - pre_authorization_message_blocks = messages.get_pre_authorization_message( - user_name, fyle_oauth_url - ) - return {"type": "home", "blocks": pre_authorization_message_blocks} + pre_authorization_message_blocks = messages.get_pre_authorization_message( + user_name, fyle_oauth_url + ) + return {"type": "home", "blocks": pre_authorization_message_blocks} def get_post_authorization_message() -> Dict: - post_authorization_message_blocks = messages.get_post_authorization_message() - return {"type": "home", "blocks": post_authorization_message_blocks} + post_authorization_message_blocks = messages.get_post_authorization_message() + return {"type": "home", "blocks": post_authorization_message_blocks} def mock_message(): - return { - "type": "modal", - "title": {"type": "plain_text", "text": "My App", "emoji": True}, - "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, - "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - "blocks": [ - { - "dispatch_action": True, - "type": "input", - "element": { - "type": "external_select", - "placeholder": { - "type": "plain_text", - "text": "Select an item", - "emoji": True, - }, - "action_id": "external_select_option", - }, - "label": {"type": "plain_text", "text": "Label", "emoji": True}, - } - ], - } + return { + "type": "modal", + "title": {"type": "plain_text", "text": "My App", "emoji": True}, + "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "blocks": [ + { + "dispatch_action": True, + "type": "input", + "element": { + "type": "external_select", + "placeholder": { + "type": "plain_text", + "text": "Select an item", + "emoji": True, + }, + "action_id": "external_select_option", + }, + "label": {"type": "plain_text", "text": "Label", "emoji": True}, + } + ], + } def mock_message_2(): - return { - "type": "modal", - "title": {"type": "plain_text", "text": "My App", "emoji": True}, - "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, - "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - "blocks": [ - { - "dispatch_action": True, - "type": "input", - "element": { - "type": "external_select", - "placeholder": { - "type": "plain_text", - "text": "Select an item", - "emoji": True, - }, - "action_id": "external_select_option", - }, - "label": {"type": "plain_text", "text": "Label", "emoji": True}, - }, - { - "type": "section", - "text": { - "type": "plain_text", - "text": "Dynamic updated view", - "emoji": True, - }, - }, - ], - } + return { + "type": "modal", + "title": {"type": "plain_text", "text": "My App", "emoji": True}, + "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "blocks": [ + { + "dispatch_action": True, + "type": "input", + "element": { + "type": "external_select", + "placeholder": { + "type": "plain_text", + "text": "Select an item", + "emoji": True, + }, + "action_id": "external_select_option", + }, + "label": {"type": "plain_text", "text": "Label", "emoji": True}, + }, + { + "type": "section", + "text": { + "type": "plain_text", + "text": "Dynamic updated view", + "emoji": True, + }, + }, + ], + } from fyle_slack_app.admin import expense_fields @@ -82,305 +82,328 @@ def mock_message_2(): def generate_category_field_mapping(expense_fields): - mapping = {} - for ef in expense_fields: - # for ci in ef['org_category_ids']: - # if ci not in mapping: - # mapping[ci] = [] - # if not any(ef['column_name'] in cd for cd in mapping[ci]): - # ci_details = { - # 'field_name': ef['field_name'], - # 'column_name': ef['column_name'], - # 'type': ef['type'], - # 'is_custom': ef['is_custom'], - # 'is_enabled': ef['is_enabled'], - # 'is_mandatory': ef['is_mandatory'], - # 'placeholder': ef['placeholder'], - # 'default_value': ef['default_value'], - # 'category_ids': ef['org_category_ids'] - # } - # if ef['type'] == 'SELECT': - # ci_details['options'] = ef['options'] - # mapping[ci].append(ci_details) + mapping = {} + for ef in expense_fields: + # for ci in ef['org_category_ids']: + # if ci not in mapping: + # mapping[ci] = [] + # if not any(ef['column_name'] in cd for cd in mapping[ci]): + # ci_details = { + # 'field_name': ef['field_name'], + # 'column_name': ef['column_name'], + # 'type': ef['type'], + # 'is_custom': ef['is_custom'], + # 'is_enabled': ef['is_enabled'], + # 'is_mandatory': ef['is_mandatory'], + # 'placeholder': ef['placeholder'], + # 'default_value': ef['default_value'], + # 'category_ids': ef['org_category_ids'] + # } + # if ef['type'] == 'SELECT': + # ci_details['options'] = ef['options'] + # mapping[ci].append(ci_details) - if ef["column_name"] not in mapping: - mapping[ef["column_name"]] = [] + if ef["column_name"] not in mapping: + mapping[ef["column_name"]] = [] - ci_details = { - "field_name": ef["field_name"], - "column_name": ef["column_name"], - "type": ef["type"], - "is_custom": ef["is_custom"], - "is_enabled": ef["is_enabled"], - "is_mandatory": ef["is_mandatory"], - "placeholder": ef["placeholder"], - "default_value": ef["default_value"], - "category_ids": ef["org_category_ids"], - } - if ef["type"] == "SELECT": - ci_details["options"] = ef["options"] - mapping[ef["column_name"]].append(ci_details) + ci_details = { + "field_name": ef["field_name"], + "column_name": ef["column_name"], + "type": ef["type"], + "is_custom": ef["is_custom"], + "is_enabled": ef["is_enabled"], + "is_mandatory": ef["is_mandatory"], + "placeholder": ef["placeholder"], + "default_value": ef["default_value"], + "category_ids": ef["org_category_ids"], + } + if ef["type"] == "SELECT": + ci_details["options"] = ef["options"] + mapping[ef["column_name"]].append(ci_details) - # print(json.dumps(mapping, indent=2)) - # print('MAPPINGS -> ', zlib.compress(base64.b64encode(json.dumps(mapping).encode())).decode()) - return mapping + # print(json.dumps(mapping, indent=2)) + # print('MAPPINGS -> ', zlib.compress(base64.b64encode(json.dumps(mapping).encode())).decode()) + return mapping -def expense_dialog_form(extra_fields=None): - blocks = [] - # mappings = generate_category_field_mapping(expense_fields) - # for expense_field in expense_fields: - # if expense_field["type"] in ["NUMBER", "TEXT"]: - # block = { - # "type": "input", - # # "block_id": "{}".format(random), - # "label": { - # "type": "plain_text", - # "text": "{}".format(expense_field["field_name"]), - # }, - # "element": { - # "type": "plain_text_input", - # "action_id": "{}".format(expense_field["field_name"].lower()), - # "placeholder": { - # "type": "plain_text", - # "text": "{}".format(expense_field["placeholder"]), - # }, - # }, - # } - # elif expense_field["type"] == "SELECT": - # block = { - # "type": "input", - # # "block_id": "{}".format(uuid.uuid4()), - # "label": { - # "type": "plain_text", - # "text": "{}".format(expense_field["field_name"]), - # }, - # "element": { - # "type": "static_select", - # "action_id": "{}".format(expense_field["field_name"].lower()), - # "placeholder": { - # "type": "plain_text", - # "text": "{}".format(expense_field["placeholder"]), - # }, - # "options": [ - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-0", - # }, - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-1", - # }, - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-2", - # }, - # ], - # }, - # } +def expense_dialog_form(extra_fields=None, form_state=None): + blocks = [] + # mappings = generate_category_field_mapping(expense_fields) + # for expense_field in expense_fields: + # if expense_field["type"] in ["NUMBER", "TEXT"]: + # block = { + # "type": "input", + # # "block_id": "{}".format(random), + # "label": { + # "type": "plain_text", + # "text": "{}".format(expense_field["field_name"]), + # }, + # "element": { + # "type": "plain_text_input", + # "action_id": "{}".format(expense_field["field_name"].lower()), + # "placeholder": { + # "type": "plain_text", + # "text": "{}".format(expense_field["placeholder"]), + # }, + # }, + # } + # elif expense_field["type"] == "SELECT": + # block = { + # "type": "input", + # # "block_id": "{}".format(uuid.uuid4()), + # "label": { + # "type": "plain_text", + # "text": "{}".format(expense_field["field_name"]), + # }, + # "element": { + # "type": "static_select", + # "action_id": "{}".format(expense_field["field_name"].lower()), + # "placeholder": { + # "type": "plain_text", + # "text": "{}".format(expense_field["placeholder"]), + # }, + # "options": [ + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-0", + # }, + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-1", + # }, + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-2", + # }, + # ], + # }, + # } - # blocks.append(block) - view = { - "type": "modal", - "title": {"type": "plain_text", "text": "Create Expense", "emoji": True}, - "submit": {"type": "plain_text", "text": "Add Expense", "emoji": True}, - "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - "blocks": [ - { - "type": "input", - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Select Currency", - "emoji": True, - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "INR", - "emoji": True, - }, - "value": "inr", - }, - { - "text": { - "type": "plain_text", - "text": "USD", - "emoji": True, - }, - "value": "usd", - }, - ], - "action_id": "category_select", - }, - "label": {"type": "plain_text", "text": "Currency", "emoji": True}, - }, - { - "type": "input", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Enter Amount", - "emoji": True, - }, - "action_id": "amount", - }, - "label": {"type": "plain_text", "text": "Amount", "emoji": True}, - }, - { - "type": "input", - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Select Payment Mode", - "emoji": True, - }, - "initial_option": { - "text": { - "type": "plain_text", - "text": "Paid by me", - "emoji": True, - }, - "value": "paid_by_me", - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "Paid by me", - "emoji": True, - }, - "value": "paid_by_me", - }, - { - "text": { - "type": "plain_text", - "text": "Paid by company", - "emoji": True, - }, - "value": "paid_by_company", - }, - ], - "action_id": "payment_mode", - }, - "label": {"type": "plain_text", "text": "Payment Mode", "emoji": True}, - }, - { - "type": "input", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Eg. Client Meeting", - "emoji": True, - }, - "action_id": "purpose", - }, - "label": {"type": "plain_text", "text": "Purpose", "emoji": True}, - }, - { - "type": "input", - "element": { - "type": "datepicker", - "initial_date": "2021-08-23", - "placeholder": { - "type": "plain_text", - "text": "Select a date", - "emoji": True, - }, - "action_id": "datepicker-action", - }, - "label": {"type": "plain_text", "text": "Date of Spend", "emoji": True}, - }, - { - "type": "input", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Eg. Uber", - "emoji": True, - }, - "action_id": "merchant", - }, - "label": {"type": "plain_text", "text": "Merchant", "emoji": True}, - }, - { - "type": "input", - "dispatch_action": True, - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Eg. Travel", - "emoji": True, - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "Custom Field Category", - "emoji": True, - }, - "value": "136250", - }, - { - "text": { - "type": "plain_text", - "text": "Internet", - "emoji": True, - }, - "value": "1234", - }, - ], - "action_id": "category_select", - }, - "label": {"type": "plain_text", "text": "Category", "emoji": True}, - }, - ], - } - # view = { - # "type": "modal", - # "callback_id": "create_expense", - # "title": {"type": "plain_text", "text": "Add Expense"}, - # "submit": {"type": "plain_text", "text": "Add Expense"}, - # "close": {"type": "plain_text", "text": "Cancel"}, - # "notify_on_close": False, - # "blocks": blocks, - # # "private_metadata": zlib.compress(base64.b64encode(json.dumps(mappings).encode())).decode() - # } - if extra_fields is not None: - for field in extra_fields: - if field["type"] in ["NUMBER", "TEXT"]: - fld = { - "type": "input", - # "block_id": "{}".format(random), - "label": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - }, - "element": { - "type": "plain_text_input", - "action_id": "{}".format(field["field_name"].lower()), - "placeholder": { - "type": "plain_text", - "text": "{}".format(field["placeholder"]), - }, - }, - } - view["blocks"].append(fld) + # blocks.append(block) + view = { + "type": "modal", + "callback_id": "create_expense", + "title": {"type": "plain_text", "text": "Create Expense", "emoji": True}, + "submit": {"type": "plain_text", "text": "Add Expense", "emoji": True}, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "blocks": [ + { + "type": "input", + "block_id": "currency_block", + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Select Currency", + "emoji": True, + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "INR", + "emoji": True, + }, + "value": "INR", + }, + { + "text": { + "type": "plain_text", + "text": "USD", + "emoji": True, + }, + "value": "USD", + }, + ], + "action_id": "currency_select", + }, + "label": {"type": "plain_text", "text": "Currency", "emoji": True}, + }, + { + "type": "input", + "block_id": "amount_block", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Enter Amount", + "emoji": True, + }, + "action_id": "amount", + }, + "label": {"type": "plain_text", "text": "Amount", "emoji": True}, + }, + { + "type": "input", + "block_id": "payment_mode_block", + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Select Payment Mode", + "emoji": True, + }, + "initial_option": { + "text": { + "type": "plain_text", + "text": "Paid by me", + "emoji": True, + }, + "value": "paid_by_me", + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Paid by me", + "emoji": True, + }, + "value": "paid_by_me", + }, + { + "text": { + "type": "plain_text", + "text": "Paid by company", + "emoji": True, + }, + "value": "paid_by_company", + }, + ], + "action_id": "payment_mode", + }, + "label": {"type": "plain_text", "text": "Payment Mode", "emoji": True}, + }, + { + "type": "input", + "block_id": "purpose_block", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Eg. Client Meeting", + "emoji": True, + }, + "action_id": "purpose", + }, + "label": {"type": "plain_text", "text": "Purpose", "emoji": True}, + }, + { + "type": "input", + "block_id": "date_of_spend_block", + "element": { + "type": "datepicker", + "initial_date": "2021-08-23", + "placeholder": { + "type": "plain_text", + "text": "Select a date", + "emoji": True, + }, + "action_id": "date_of_spend", + }, + "label": {"type": "plain_text", "text": "Date of Spend", "emoji": True}, + }, + { + "type": "input", + "block_id": "merchant_block", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Eg. Uber", + "emoji": True, + }, + "action_id": "merchant", + }, + "label": {"type": "plain_text", "text": "Merchant", "emoji": True}, + }, + { + "type": "input", + "block_id": "category_block", + "dispatch_action": True, + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Eg. Travel", + "emoji": True, + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Custom Field Category", + "emoji": True, + }, + "value": "136250", + }, + { + "text": { + "type": "plain_text", + "text": "Internet", + "emoji": True, + }, + "value": "1234", + }, + ], + "action_id": "category_select", + }, + "label": {"type": "plain_text", "text": "Category", "emoji": True}, + }, + ], + } + # view = { + # "type": "modal", + # "callback_id": "create_expense", + # "title": {"type": "plain_text", "text": "Add Expense"}, + # "submit": {"type": "plain_text", "text": "Add Expense"}, + # "close": {"type": "plain_text", "text": "Cancel"}, + # "notify_on_close": False, + # "blocks": blocks, + # # "private_metadata": zlib.compress(base64.b64encode(json.dumps(mappings).encode())).decode() + # } + if extra_fields is not None: + for field in extra_fields: + if field["type"] in ["NUMBER", "TEXT"]: + fld = { + "type": "input", + "block_id": "{}_block".format(field['column_name']), + "label": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + }, + "element": { + "type": "plain_text_input", + "action_id": "{}".format(field["column_name"].lower()), + "placeholder": { + "type": "plain_text", + "text": "{}".format(field["placeholder"]), + }, + }, + } + view["blocks"].append(fld) - return view + + # if form_state is not None: + # for block in view['blocks']: + # print('BLOCK ID -> ', block['block_id']) + # value_key = 'value' + # initial_value_key = 'initial_value' + # if block['element']['type'] == 'static_select': + # value_key = 'selected_option' + # initial_value_key = 'initial_option' + # elif block['element']['type'] == 'datepicker': + # value_key = 'selected_date' + # initial_value_key = 'initial_date' + + # block['element'][initial_value_key] = form_state[block['block_id']][block['element']['action_id']][value_key] + + return view From 9c2720e4b0e969f0473d53874f682e167138d725 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 24 Aug 2021 13:28:04 +0530 Subject: [PATCH 06/85] Expense form dynamic details extraction --- fyle_slack_app/admin.py | 4259 ++++++++--------- .../interactives/block_action_handlers.py | 4 - fyle_slack_app/slack/interactives/views.py | 4 + fyle_slack_app/slack/ui/dashboard/messages.py | 46 +- 4 files changed, 2117 insertions(+), 2196 deletions(-) diff --git a/fyle_slack_app/admin.py b/fyle_slack_app/admin.py index f95ccdf3..f518d470 100644 --- a/fyle_slack_app/admin.py +++ b/fyle_slack_app/admin.py @@ -1,2247 +1,2128 @@ expense_fields = [ - { - "id": 6182, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6182, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "bus_travel_class", + "field_name": "Travel Class", + "seq": 1, + "type": "TEXT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Enter class of travel", + "default_value": None, + "options": [], + "org_category_ids": [136517], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6183, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "cost_center_id", + "field_name": "Cost Center", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Cost Center", + "default_value": None, + "options": [], + "org_category_ids": [ + 136506, + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136514, + 136515, + 136516, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531, + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "bus_travel_class", - "field_name": "Travel Class", - "seq": 1, - "type": "TEXT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Enter class of travel", - "default_value": None, - "options": [], - "org_category_ids": [ - 136517 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6183, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6184, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance", + "field_name": "Distance", + "seq": 1, + "type": "NUMBER", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Enter Distance", + "default_value": None, + "options": [], + "org_category_ids": [136514], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6185, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance", + "field_name": "Distance", + "seq": 2, + "type": "NUMBER", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Enter Distance", + "default_value": None, + "options": [], + "org_category_ids": [136519], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "cost_center_id", - "field_name": "Cost Center", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Cost Center", - "default_value": None, - "options": [], - "org_category_ids": [ - 136506, - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136514, - 136515, - 136516, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6184, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6186, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance_unit", + "field_name": "Unit", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Unit", + "default_value": "MILES", + "options": ["KM", "MILES"], + "org_category_ids": [136514], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6187, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "distance_unit", + "field_name": "Unit", + "seq": 2, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Unit", + "default_value": "MILES", + "options": ["KM", "MILES"], + "org_category_ids": [136519], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "distance", - "field_name": "Distance", - "seq": 1, - "type": "NUMBER", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Enter Distance", - "default_value": None, - "options": [], - "org_category_ids": [ - 136514 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6185, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6188, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "flight_journey_travel_class", + "field_name": "Onward Travel Class", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select travel class", + "default_value": None, + "options": ["BUSINESS", "ECONOMY", "FIRST_CLASS"], + "org_category_ids": [136525], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6189, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "flight_return_travel_class", + "field_name": "Return Travel Class", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select travel class", + "default_value": None, + "options": ["BUSINESS", "ECONOMY", "FIRST_CLASS"], + "org_category_ids": [136525], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "distance", - "field_name": "Distance", - "seq": 2, - "type": "NUMBER", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Enter Distance", - "default_value": None, - "options": [], - "org_category_ids": [ - 136519 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6186, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6190, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "from_dt", + "field_name": "From", + "seq": 1, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [136516], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6191, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "from_dt", + "field_name": "Onward Date", + "seq": 2, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [136507, 136517, 136525], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "distance_unit", - "field_name": "Unit", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Unit", - "default_value": "MILES", - "options": [ - "KM", - "MILES" - ], - "org_category_ids": [ - 136514 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6187, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6192, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "from_dt", + "field_name": "Check-in Date", + "seq": 3, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [136521], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6193, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "location1", + "field_name": "From", + "seq": 1, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select a City", + "default_value": None, + "options": [], + "org_category_ids": [136507, 136517, 136525], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "distance_unit", - "field_name": "Unit", - "seq": 2, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Unit", - "default_value": "MILES", - "options": [ - "KM", - "MILES" - ], - "org_category_ids": [ - 136519 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6188, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6194, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "location1", + "field_name": "City", + "seq": 2, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select a City", + "default_value": None, + "options": [], + "org_category_ids": [136521], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6195, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "location1", + "field_name": "From", + "seq": 3, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Choose Location - Search for Place or Area", + "default_value": None, + "options": [], + "org_category_ids": [136514], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "flight_journey_travel_class", - "field_name": "Onward Travel Class", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select travel class", - "default_value": None, - "options": [ - "BUSINESS", - "ECONOMY", - "FIRST_CLASS" - ], - "org_category_ids": [ - 136525 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6189, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6196, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "location2", + "field_name": "To", + "seq": 1, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select a City", + "default_value": None, + "options": [], + "org_category_ids": [136507, 136517, 136525], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6197, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "location2", + "field_name": "To", + "seq": 2, + "type": "LOCATION", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Choose Location - Search for Place or Area", + "default_value": None, + "options": [], + "org_category_ids": [136514], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "flight_return_travel_class", - "field_name": "Return Travel Class", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select travel class", - "default_value": None, - "options": [ - "BUSINESS", - "ECONOMY", - "FIRST_CLASS" - ], - "org_category_ids": [ - 136525 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6190, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6198, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "num_days", + "field_name": "No. of Days", + "seq": 1, + "type": "NUMBER", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "No. of Days", + "default_value": None, + "options": [], + "org_category_ids": [136516], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6199, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "purpose", + "field_name": "Purpose", + "seq": 1, + "type": "TEXT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "E.g. Client Meeting", + "default_value": None, + "options": [], + "org_category_ids": [ + 136506, + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136514, + 136515, + 136516, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531, + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "from_dt", - "field_name": "From", - "seq": 1, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136516 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6191, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6200, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "to_dt", + "field_name": "To", + "seq": 1, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [136516], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6201, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "to_dt", + "field_name": "Return Date", + "seq": 2, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [136507, 136517, 136525], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "from_dt", - "field_name": "Onward Date", - "seq": 2, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136507, - 136517, - 136525 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6192, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6202, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "to_dt", + "field_name": "Check-out Date", + "seq": 3, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [136521], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6199, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "train_travel_class", + "field_name": "Travel class", + "seq": 1, + "type": "TEXT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Enter class of travel", + "default_value": None, + "options": [], + "org_category_ids": [136507], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "from_dt", - "field_name": "Check-in Date", - "seq": 3, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136521 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6193, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6204, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "txn_dt", + "field_name": "Date of Spend", + "seq": 1, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [ + 136506, + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136515, + 136516, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531, + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 6205, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "txn_dt", + "field_name": "Date of Travel", + "seq": 2, + "type": "DATE", + "is_custom": False, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "Select Date", + "default_value": None, + "options": [], + "org_category_ids": [136514], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "location1", - "field_name": "From", - "seq": 1, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select a City", - "default_value": None, - "options": [], - "org_category_ids": [ - 136507, - 136517, - 136525 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6194, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 6206, + "created_at": "2021-04-22T07:46:07.007Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-04-22T07:48:35.072Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "vendor_id", + "field_name": "Merchant", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "E.g. Uber", + "default_value": None, + "options": [], + "org_category_ids": [ + 136507, + 136508, + 136509, + 136510, + 136511, + 136512, + 136513, + 136515, + 136517, + 136518, + 136519, + 136520, + 136521, + 136522, + 136523, + 136524, + 136525, + 136526, + 136527, + 136528, + 136529, + 136530, + 136531, + ], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 185323, + "created_at": "2021-07-28T08:48:55.152Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-07-28T08:48:55.152Z", + "updated_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "org_id": "orsReFb3Oou8", + "column_name": "project_id", + "field_name": "Project", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Select Project", + "default_value": None, + "options": [], + "org_category_ids": [], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "location1", - "field_name": "City", - "seq": 2, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select a City", - "default_value": None, - "options": [], - "org_category_ids": [ - 136521 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6195, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 189621, + "created_at": "2021-07-29T11:05:38.811Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-07-29T11:05:38.811Z", + "updated_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "org_id": "orsReFb3Oou8", + "column_name": "billable", + "field_name": "Billable", + "seq": 1, + "type": "BOOLEAN", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Billable", + "default_value": "False", + "options": [], + "org_category_ids": [], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 194141, + "created_at": "2021-08-17T04:48:57.792Z", + "created_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "updated_at": "2021-08-17T04:48:57.792Z", + "updated_by": { + "user_id": "SYSTEM", + "org_user_id": None, + "org_id": None, + "roles": [], + "scopes": [], + "allowed_CIDRs": None, + "cluster_domain": None, + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": None, + }, + "org_id": "orsReFb3Oou8", + "column_name": "tax_group", + "field_name": "Tax Group", + "seq": 1, + "type": "SELECT", + "is_custom": False, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Tax Group", + "default_value": None, + "options": [], + "org_category_ids": [], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "location1", - "field_name": "From", - "seq": 3, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Choose Location - Search for Place or Area", - "default_value": None, - "options": [], - "org_category_ids": [ - 136514 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6196, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 195217, + "created_at": "2021-08-18T07:35:59.906Z", + "created_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "updated_at": "2021-08-18T07:35:59.906Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "text_column1", + "field_name": "TEST 1", + "seq": 1, + "type": "TEXT", + "is_custom": True, + "is_enabled": True, + "is_mandatory": True, + "placeholder": "testiing", + "default_value": None, + "options": [], + # "org_category_ids": [136250, 136518], - For two custom fields + "org_category_ids": [136250], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 195510, + "created_at": "2021-08-23T13:18:38.309Z", + "created_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "updated_at": "2021-08-23T13:18:38.309Z", + "updated_by": { + "user_id": "usuTaTN12iqG", + "org_user_id": "ouZH5KbJaBFq", + "org_id": "orsReFb3Oou8", + "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], + "scopes": [], + "allowed_CIDRs": [], + "cluster_domain": '"https://staging.fyle.tech"', + "proxy_org_user_id": None, + "tpa_id": None, + "tpa_name": None, + "name": "ouZH5KbJaBFq", + }, + "org_id": "orsReFb3Oou8", + "column_name": "text_column2", + "field_name": "Test select field", + "seq": 1, + "type": "SELECT", + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "testing select fields", + "default_value": None, + "options": ["test choice 1", "test choice 2"], + "org_category_ids": [136518], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], }, - "org_id": "orsReFb3Oou8", - "column_name": "location2", - "field_name": "To", - "seq": 1, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select a City", - "default_value": None, - "options": [], - "org_category_ids": [ - 136507, - 136517, - 136525 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6197, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" - }, - "org_id": "orsReFb3Oou8", - "column_name": "location2", - "field_name": "To", - "seq": 2, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Choose Location - Search for Place or Area", - "default_value": None, - "options": [], - "org_category_ids": [ - 136514 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6198, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None +] + +categories = [ + { + "id": 835229, + "created_at": "2019-02-13T08:24:43.405Z", + "updated_at": "2019-02-13T08:24:44.405Z", + "org_id": "orKPte3WEwuJ", + "name": "Activity", + "code": None, + "fyle_category": "Activity", + "sub_category": "Activity", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835240, + "created_at": "2019-02-13T08:24:43.463Z", + "updated_at": "2019-02-13T08:24:44.463Z", + "org_id": "orKPte3WEwuJ", + "name": "Bus", + "code": None, + "fyle_category": "Bus", + "sub_category": "Bus", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "num_days", - "field_name": "No. of Days", - "seq": 1, - "type": "NUMBER", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "No. of Days", - "default_value": None, - "options": [], - "org_category_ids": [ - 136516 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6199, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835243, + "created_at": "2019-02-13T08:24:43.476Z", + "updated_at": "2019-02-13T08:24:44.476Z", + "org_id": "orKPte3WEwuJ", + "name": "Courier", + "code": None, + "fyle_category": "Courier", + "sub_category": "Courier", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835235, + "created_at": "2019-02-13T08:24:43.441Z", + "updated_at": "2019-02-13T08:24:44.441Z", + "org_id": "orKPte3WEwuJ", + "name": "Entertainment", + "code": None, + "fyle_category": "Entertainment", + "sub_category": "Entertainment", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "purpose", - "field_name": "Purpose", - "seq": 1, - "type": "TEXT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "E.g. Client Meeting", - "default_value": None, - "options": [], - "org_category_ids": [ - 136506, - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136514, - 136515, - 136516, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6200, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835248, + "created_at": "2019-02-13T08:24:43.498Z", + "updated_at": "2019-02-13T08:24:44.498Z", + "org_id": "orKPte3WEwuJ", + "name": "Flight", + "code": None, + "fyle_category": "Flight", + "sub_category": "Flight", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835238, + "created_at": "2019-02-13T08:24:43.455Z", + "updated_at": "2019-02-13T08:24:44.455Z", + "org_id": "orKPte3WEwuJ", + "name": "Food", + "code": None, + "fyle_category": "Food", + "sub_category": "Food", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "to_dt", - "field_name": "To", - "seq": 1, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136516 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6201, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835231, + "created_at": "2019-02-13T08:24:43.413Z", + "updated_at": "2019-02-13T08:24:44.413Z", + "org_id": "orKPte3WEwuJ", + "name": "Fuel", + "code": None, + "fyle_category": "Fuel", + "sub_category": "Fuel", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835244, + "created_at": "2019-02-13T08:24:43.481Z", + "updated_at": "2019-02-13T08:24:44.481Z", + "org_id": "orKPte3WEwuJ", + "name": "Hotel", + "code": None, + "fyle_category": "Hotel", + "sub_category": "Hotel", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "to_dt", - "field_name": "Return Date", - "seq": 2, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136507, - 136517, - 136525 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6202, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835241, + "created_at": "2019-02-13T08:24:43.468Z", + "updated_at": "2019-02-13T08:24:44.468Z", + "org_id": "orKPte3WEwuJ", + "name": "Internet", + "code": None, + "fyle_category": "Internet", + "sub_category": "Internet", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835237, + "created_at": "2019-02-13T08:24:43.451Z", + "updated_at": "2019-02-13T08:24:44.451Z", + "org_id": "orKPte3WEwuJ", + "name": "Mileage", + "code": None, + "fyle_category": "Mileage", + "sub_category": "Mileage", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "to_dt", - "field_name": "Check-out Date", - "seq": 3, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136521 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6199, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835247, + "created_at": "2019-02-13T08:24:43.494Z", + "updated_at": "2019-02-13T08:24:44.494Z", + "org_id": "orKPte3WEwuJ", + "name": "Office Party", + "code": None, + "fyle_category": "Office Party", + "sub_category": "Office Party", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835233, + "created_at": "2019-02-13T08:24:43.421Z", + "updated_at": "2019-02-13T08:24:44.421Z", + "org_id": "orKPte3WEwuJ", + "name": "Office Supplies", + "code": None, + "fyle_category": "Office Supplies", + "sub_category": "Office Supplies", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "train_travel_class", - "field_name": "Travel class", - "seq": 1, - "type": "TEXT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Enter class of travel", - "default_value": None, - "options": [], - "org_category_ids": [ - 136507 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6204, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835236, + "created_at": "2019-02-13T08:24:43.447Z", + "updated_at": "2019-02-13T08:24:44.447Z", + "org_id": "orKPte3WEwuJ", + "name": "Others", + "code": None, + "fyle_category": "Others", + "sub_category": "Others", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835250, + "created_at": "2019-02-13T08:24:43.507Z", + "updated_at": "2019-02-13T08:24:44.507Z", + "org_id": "orKPte3WEwuJ", + "name": "Parking", + "code": None, + "fyle_category": "Parking", + "sub_category": "Parking", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "txn_dt", - "field_name": "Date of Spend", - "seq": 1, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136506, - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136515, - 136516, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6205, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835239, + "created_at": "2019-02-13T08:24:43.459Z", + "updated_at": "2019-02-13T08:24:44.459Z", + "org_id": "orKPte3WEwuJ", + "name": "Per Diem", + "code": None, + "fyle_category": "Per Diem", + "sub_category": "Per Diem", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835246, + "created_at": "2019-02-13T08:24:43.489Z", + "updated_at": "2019-02-13T08:24:44.489Z", + "org_id": "orKPte3WEwuJ", + "name": "Phone", + "code": None, + "fyle_category": "Phone", + "sub_category": "Phone", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "txn_dt", - "field_name": "Date of Travel", - "seq": 2, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136514 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 6206, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835245, + "created_at": "2019-02-13T08:24:43.485Z", + "updated_at": "2019-02-13T08:24:44.485Z", + "org_id": "orKPte3WEwuJ", + "name": "Professional Services", + "code": None, + "fyle_category": "Professional Services", + "sub_category": "Professional Services", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835232, + "created_at": "2019-02-13T08:24:43.417Z", + "updated_at": "2019-02-13T08:24:44.417Z", + "org_id": "orKPte3WEwuJ", + "name": "Snacks", + "code": None, + "fyle_category": "Snacks", + "sub_category": "Snacks", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "vendor_id", - "field_name": "Merchant", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "E.g. Uber", - "default_value": None, - "options": [], - "org_category_ids": [ - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136515, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 185323, - "created_at": "2021-07-28T08:48:55.152Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835249, + "created_at": "2019-02-13T08:24:43.502Z", + "updated_at": "2019-02-13T08:24:44.502Z", + "org_id": "orKPte3WEwuJ", + "name": "Software", + "code": None, + "fyle_category": "Software", + "sub_category": "Software", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-07-28T08:48:55.152Z", - "updated_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835252, + "created_at": "2019-02-13T08:24:43.515Z", + "updated_at": "2019-02-13T08:24:44.515Z", + "org_id": "orKPte3WEwuJ", + "name": "Tax", + "code": None, + "fyle_category": "Tax", + "sub_category": "Tax", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "project_id", - "field_name": "Project", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Project", - "default_value": None, - "options": [], - "org_category_ids": [], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 189621, - "created_at": "2021-07-29T11:05:38.811Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835242, + "created_at": "2019-02-13T08:24:43.472Z", + "updated_at": "2019-02-13T08:24:44.472Z", + "org_id": "orKPte3WEwuJ", + "name": "Taxi", + "code": None, + "fyle_category": "Taxi", + "sub_category": "Taxi", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-07-29T11:05:38.811Z", - "updated_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835251, + "created_at": "2019-02-13T08:24:43.511Z", + "updated_at": "2019-02-13T08:24:44.511Z", + "org_id": "orKPte3WEwuJ", + "name": "Toll Charge", + "code": None, + "fyle_category": "Toll Charge", + "sub_category": "Toll Charge", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "billable", - "field_name": "Billable", - "seq": 1, - "type": "BOOLEAN", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Billable", - "default_value": "False", - "options": [], - "org_category_ids": [], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 194141, - "created_at": "2021-08-17T04:48:57.792Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835230, + "created_at": "2019-02-13T08:24:43.409Z", + "updated_at": "2019-02-13T08:24:44.409Z", + "org_id": "orKPte3WEwuJ", + "name": "Train", + "code": None, + "fyle_category": "Train", + "sub_category": "Train", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-08-17T04:48:57.792Z", - "updated_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None + { + "id": 835253, + "created_at": "2019-02-13T08:24:43.522Z", + "updated_at": "2019-02-13T08:24:44.522Z", + "org_id": "orKPte3WEwuJ", + "name": "Training", + "code": None, + "fyle_category": "Training", + "sub_category": "Training", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "tax_group", - "field_name": "Tax Group", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Tax Group", - "default_value": None, - "options": [], - "org_category_ids": [], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - }, - { - "id": 195217, - "created_at": "2021-08-18T07:35:59.906Z", - "created_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN", - "APPROVER" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835254, + "created_at": "2019-02-13T08:24:43.526Z", + "updated_at": "2019-02-13T08:24:44.526Z", + "org_id": "orKPte3WEwuJ", + "name": "Unspecified", + "code": None, + "fyle_category": "Unspecified", + "sub_category": "Unspecified", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "updated_at": "2021-08-18T07:35:59.906Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": [ - "OWNER", - "FYLER", - "ADMIN", - "APPROVER" - ], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": "\"https://staging.fyle.tech\"", - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq" + { + "id": 835234, + "created_at": "2019-02-13T08:24:43.426Z", + "updated_at": "2019-02-13T08:24:44.426Z", + "org_id": "orKPte3WEwuJ", + "name": "Utility", + "code": None, + "fyle_category": "Utility", + "sub_category": "Utility", + "enabled": True, + "creator_id": None, + "last_updated_by": None, }, - "org_id": "orsReFb3Oou8", - "column_name": "text_column1", - "field_name": "TEST 1", - "seq": 1, - "type": "TEXT", - "is_custom": True, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "testiing", - "default_value": None, - "options": [], - "org_category_ids": [ - 136250 - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR" - ] - } ] - -categories = [ - { - "id":835229, - "created_at":"2019-02-13T08:24:43.405Z", - "updated_at":"2019-02-13T08:24:44.405Z", - "org_id":"orKPte3WEwuJ", - "name":"Activity", - "code":None, - "fyle_category":"Activity", - "sub_category":"Activity", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835240, - "created_at":"2019-02-13T08:24:43.463Z", - "updated_at":"2019-02-13T08:24:44.463Z", - "org_id":"orKPte3WEwuJ", - "name":"Bus", - "code":None, - "fyle_category":"Bus", - "sub_category":"Bus", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835243, - "created_at":"2019-02-13T08:24:43.476Z", - "updated_at":"2019-02-13T08:24:44.476Z", - "org_id":"orKPte3WEwuJ", - "name":"Courier", - "code":None, - "fyle_category":"Courier", - "sub_category":"Courier", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835235, - "created_at":"2019-02-13T08:24:43.441Z", - "updated_at":"2019-02-13T08:24:44.441Z", - "org_id":"orKPte3WEwuJ", - "name":"Entertainment", - "code":None, - "fyle_category":"Entertainment", - "sub_category":"Entertainment", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835248, - "created_at":"2019-02-13T08:24:43.498Z", - "updated_at":"2019-02-13T08:24:44.498Z", - "org_id":"orKPte3WEwuJ", - "name":"Flight", - "code":None, - "fyle_category":"Flight", - "sub_category":"Flight", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835238, - "created_at":"2019-02-13T08:24:43.455Z", - "updated_at":"2019-02-13T08:24:44.455Z", - "org_id":"orKPte3WEwuJ", - "name":"Food", - "code":None, - "fyle_category":"Food", - "sub_category":"Food", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835231, - "created_at":"2019-02-13T08:24:43.413Z", - "updated_at":"2019-02-13T08:24:44.413Z", - "org_id":"orKPte3WEwuJ", - "name":"Fuel", - "code":None, - "fyle_category":"Fuel", - "sub_category":"Fuel", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835244, - "created_at":"2019-02-13T08:24:43.481Z", - "updated_at":"2019-02-13T08:24:44.481Z", - "org_id":"orKPte3WEwuJ", - "name":"Hotel", - "code":None, - "fyle_category":"Hotel", - "sub_category":"Hotel", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835241, - "created_at":"2019-02-13T08:24:43.468Z", - "updated_at":"2019-02-13T08:24:44.468Z", - "org_id":"orKPte3WEwuJ", - "name":"Internet", - "code":None, - "fyle_category":"Internet", - "sub_category":"Internet", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835237, - "created_at":"2019-02-13T08:24:43.451Z", - "updated_at":"2019-02-13T08:24:44.451Z", - "org_id":"orKPte3WEwuJ", - "name":"Mileage", - "code":None, - "fyle_category":"Mileage", - "sub_category":"Mileage", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835247, - "created_at":"2019-02-13T08:24:43.494Z", - "updated_at":"2019-02-13T08:24:44.494Z", - "org_id":"orKPte3WEwuJ", - "name":"Office Party", - "code":None, - "fyle_category":"Office Party", - "sub_category":"Office Party", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835233, - "created_at":"2019-02-13T08:24:43.421Z", - "updated_at":"2019-02-13T08:24:44.421Z", - "org_id":"orKPte3WEwuJ", - "name":"Office Supplies", - "code":None, - "fyle_category":"Office Supplies", - "sub_category":"Office Supplies", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835236, - "created_at":"2019-02-13T08:24:43.447Z", - "updated_at":"2019-02-13T08:24:44.447Z", - "org_id":"orKPte3WEwuJ", - "name":"Others", - "code":None, - "fyle_category":"Others", - "sub_category":"Others", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835250, - "created_at":"2019-02-13T08:24:43.507Z", - "updated_at":"2019-02-13T08:24:44.507Z", - "org_id":"orKPte3WEwuJ", - "name":"Parking", - "code":None, - "fyle_category":"Parking", - "sub_category":"Parking", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835239, - "created_at":"2019-02-13T08:24:43.459Z", - "updated_at":"2019-02-13T08:24:44.459Z", - "org_id":"orKPte3WEwuJ", - "name":"Per Diem", - "code":None, - "fyle_category":"Per Diem", - "sub_category":"Per Diem", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835246, - "created_at":"2019-02-13T08:24:43.489Z", - "updated_at":"2019-02-13T08:24:44.489Z", - "org_id":"orKPte3WEwuJ", - "name":"Phone", - "code":None, - "fyle_category":"Phone", - "sub_category":"Phone", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835245, - "created_at":"2019-02-13T08:24:43.485Z", - "updated_at":"2019-02-13T08:24:44.485Z", - "org_id":"orKPte3WEwuJ", - "name":"Professional Services", - "code":None, - "fyle_category":"Professional Services", - "sub_category":"Professional Services", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835232, - "created_at":"2019-02-13T08:24:43.417Z", - "updated_at":"2019-02-13T08:24:44.417Z", - "org_id":"orKPte3WEwuJ", - "name":"Snacks", - "code":None, - "fyle_category":"Snacks", - "sub_category":"Snacks", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835249, - "created_at":"2019-02-13T08:24:43.502Z", - "updated_at":"2019-02-13T08:24:44.502Z", - "org_id":"orKPte3WEwuJ", - "name":"Software", - "code":None, - "fyle_category":"Software", - "sub_category":"Software", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835252, - "created_at":"2019-02-13T08:24:43.515Z", - "updated_at":"2019-02-13T08:24:44.515Z", - "org_id":"orKPte3WEwuJ", - "name":"Tax", - "code":None, - "fyle_category":"Tax", - "sub_category":"Tax", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835242, - "created_at":"2019-02-13T08:24:43.472Z", - "updated_at":"2019-02-13T08:24:44.472Z", - "org_id":"orKPte3WEwuJ", - "name":"Taxi", - "code":None, - "fyle_category":"Taxi", - "sub_category":"Taxi", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835251, - "created_at":"2019-02-13T08:24:43.511Z", - "updated_at":"2019-02-13T08:24:44.511Z", - "org_id":"orKPte3WEwuJ", - "name":"Toll Charge", - "code":None, - "fyle_category":"Toll Charge", - "sub_category":"Toll Charge", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835230, - "created_at":"2019-02-13T08:24:43.409Z", - "updated_at":"2019-02-13T08:24:44.409Z", - "org_id":"orKPte3WEwuJ", - "name":"Train", - "code":None, - "fyle_category":"Train", - "sub_category":"Train", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835253, - "created_at":"2019-02-13T08:24:43.522Z", - "updated_at":"2019-02-13T08:24:44.522Z", - "org_id":"orKPte3WEwuJ", - "name":"Training", - "code":None, - "fyle_category":"Training", - "sub_category":"Training", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835254, - "created_at":"2019-02-13T08:24:43.526Z", - "updated_at":"2019-02-13T08:24:44.526Z", - "org_id":"orKPte3WEwuJ", - "name":"Unspecified", - "code":None, - "fyle_category":"Unspecified", - "sub_category":"Unspecified", - "enabled":True, - "creator_id":None, - "last_updated_by":None - }, - { - "id":835234, - "created_at":"2019-02-13T08:24:43.426Z", - "updated_at":"2019-02-13T08:24:44.426Z", - "org_id":"orKPte3WEwuJ", - "name":"Utility", - "code":None, - "fyle_category":"Utility", - "sub_category":"Utility", - "enabled":True, - "creator_id":None, - "last_updated_by":None - } -] \ No newline at end of file diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index e53303e6..bd86d288 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -67,10 +67,6 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): print('EF -> ', json.dumps(extra_fields, indent=2)) form_state = slack_payload['view']['state']['values'] - current_form_state = {} - - # for block_key, block_value in form_state.items(): - # pass if len(extra_fields) > 0: new_expense_dialog_form = expense_dialog_form(extra_fields, form_state) diff --git a/fyle_slack_app/slack/interactives/views.py b/fyle_slack_app/slack/interactives/views.py index b58c22bb..12abef96 100644 --- a/fyle_slack_app/slack/interactives/views.py +++ b/fyle_slack_app/slack/interactives/views.py @@ -5,6 +5,7 @@ from fyle_slack_app.slack import SlackView from fyle_slack_app.slack.interactives.block_action_handlers import BlockActionHandler from fyle_slack_app.slack.interactives.shortcut_handlers import ShortcutHandler +from fyle_slack_app.slack.interactives.view_submission_handlers import handle_view_submission class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler): @@ -27,6 +28,9 @@ def post(self, request: HttpRequest) -> JsonResponse: elif event_type == 'shortcut': # Call handler function from ShortcutHandler return self.handle_shortcuts(slack_payload, user_id, team_id) + + elif event_type == 'view_submission': + return handle_view_submission(slack_payload, user_id, team_id) elif event_type == 'block_suggestion': # Call handler function from BlockActionHandler diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index a69dba6a..da012651 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -2,7 +2,7 @@ from random import random from typing import Dict -import random, copy, zlib, base64 +import datetime from fyle_slack_app.slack.ui.authorization import messages @@ -189,6 +189,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): # } # blocks.append(block) + current_date = datetime.datetime.today().strftime('%Y-%m-%d') view = { "type": "modal", "callback_id": "create_expense", @@ -301,7 +302,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): "block_id": "date_of_spend_block", "element": { "type": "datepicker", - "initial_date": "2021-08-23", + "initial_date": current_date, "placeholder": { "type": "plain_text", "text": "Select a date", @@ -351,6 +352,14 @@ def expense_dialog_form(extra_fields=None, form_state=None): "text": "Internet", "emoji": True, }, + "value": "136518", + }, + { + "text": { + "type": "plain_text", + "text": "Office Supplies", + "emoji": True, + }, "value": "1234", }, ], @@ -389,7 +398,38 @@ def expense_dialog_form(extra_fields=None, form_state=None): }, }, } - view["blocks"].append(fld) + elif field['type'] == 'SELECT': + fld = { + "type": "input", + "label": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + "emoji": True + }, + "block_id": "{}_block".format(field['column_name']), + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "{}".format(field["placeholder"]), + "emoji": True + }, + "action_id": "{}".format(field["column_name"].lower()), + } + } + fld['element']['options'] = [] + for option in field['options']: + fld['element']['options'].append( + { + "text": { + "type": "plain_text", + "text": option, + "emoji": True + }, + "value": option + } + ) + view["blocks"].append(fld) # if form_state is not None: From 7cc1164b335479ab3aeea93f42e29cba7ec3c6f8 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 24 Aug 2021 13:28:22 +0530 Subject: [PATCH 07/85] Expense form dynamic details extraction --- .../interactives/view_submission_handlers.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 fyle_slack_app/slack/interactives/view_submission_handlers.py diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py new file mode 100644 index 00000000..8dd45314 --- /dev/null +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -0,0 +1,24 @@ +import json +from django.http.response import JsonResponse + + +def handle_view_submission(slack_payload, user_id, team_id): + callback_id = slack_payload['view']['callback_id'] + + if callback_id == 'create_expense': + form_values = slack_payload['view']['state']['values'] + print("REACHED CREATE EXPENSE") + + expense_mapping = {} + + for key, value in form_values.items(): + for inner_key, inner_value in value.items(): + if inner_value['type'] == 'static_select': + expense_mapping[inner_key] = inner_value['selected_option']['value'] + elif inner_value['type'] == 'datepicker': + expense_mapping[inner_key] = inner_value['selected_date'] + elif inner_value['type'] == 'plain_text_input': + expense_mapping[inner_key] = inner_value['value'] + + print('EXPENSE -> ', json.dumps(expense_mapping, indent=2)) + return JsonResponse({}) From 4e683c6e502e7e0aeae36727f390d4b334de04f8 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Fri, 27 Aug 2021 15:56:29 +0530 Subject: [PATCH 08/85] Fetching expense id from parent message --- fyle_slack_app/slack/events/views.py | 27 +++++++ .../interactives/block_action_handlers.py | 2 +- .../interactives/view_submission_handlers.py | 77 ++++++++++++++++--- fyle_slack_app/slack/ui/dashboard/messages.py | 9 ++- 4 files changed, 100 insertions(+), 15 deletions(-) diff --git a/fyle_slack_app/slack/events/views.py b/fyle_slack_app/slack/events/views.py index 4d977d40..c282cafd 100644 --- a/fyle_slack_app/slack/events/views.py +++ b/fyle_slack_app/slack/events/views.py @@ -1,3 +1,4 @@ +from fyle_slack_app.models.users import User import json from django.http import JsonResponse, HttpRequest @@ -5,12 +6,16 @@ from fyle_slack_app.slack import SlackView from fyle_slack_app.slack.events.handlers import SlackEventHandler +from fyle_slack_app.slack.utils import get_slack_client + class SlackEventView(SlackView, SlackEventHandler): def post(self, request: HttpRequest) -> JsonResponse: slack_payload = json.loads(request.body) + print('SLACK EVENT -> ', json.dumps(slack_payload, indent=2)) + event_type = slack_payload['type'] event_response = {} @@ -25,6 +30,28 @@ def post(self, request: HttpRequest) -> JsonResponse: subevent_type = slack_payload['event']['type'] team_id = slack_payload['team_id'] + if subevent_type == 'file_shared': + slack_client = get_slack_client(team_id) + file_id = slack_payload['event']['file_id'] + + file_info = slack_client.files_info(file=file_id) + + user_id = slack_payload['event']['user_id'] + + user = User.objects.get(slack_user_id=user_id) + + thread_ts = file_info['file']['shares']['private'][user.slack_dm_channel_id][0]['thread_ts'] + + parent_message = slack_client.conversations_history(channel=user.slack_dm_channel_id, latest=thread_ts, inclusive=True, limit=1) + + for block in parent_message['messages'][0]['blocks']: + if block['type'] == 'context': + expense_id = block['block_id'] + break + print('EXPENSE ID -> ', expense_id) + + return JsonResponse({}, status=200) + self.handle_event_callback(subevent_type, slack_payload, team_id) return JsonResponse(event_response, status=200) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index bd86d288..9fba3ca8 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -34,7 +34,7 @@ def _initialize_block_action_handlers(self): 'report_approver_sendback_notification_preference': self.handle_notification_preference_selection, # Dynamic options - 'category_select': self.category_select + 'category': self.category_select, } diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 8dd45314..178e0f02 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,24 +1,79 @@ +from fyle_slack_app.slack.utils import get_slack_client import json from django.http.response import JsonResponse def handle_view_submission(slack_payload, user_id, team_id): - callback_id = slack_payload['view']['callback_id'] + callback_id = slack_payload["view"]["callback_id"] - if callback_id == 'create_expense': - form_values = slack_payload['view']['state']['values'] + if callback_id == "create_expense": + form_values = slack_payload["view"]["state"]["values"] print("REACHED CREATE EXPENSE") expense_mapping = {} for key, value in form_values.items(): for inner_key, inner_value in value.items(): - if inner_value['type'] == 'static_select': - expense_mapping[inner_key] = inner_value['selected_option']['value'] - elif inner_value['type'] == 'datepicker': - expense_mapping[inner_key] = inner_value['selected_date'] - elif inner_value['type'] == 'plain_text_input': - expense_mapping[inner_key] = inner_value['value'] - - print('EXPENSE -> ', json.dumps(expense_mapping, indent=2)) + if inner_value["type"] == "static_select": + expense_mapping[inner_key] = inner_value["selected_option"]["value"] + elif inner_value["type"] == "datepicker": + expense_mapping[inner_key] = inner_value["selected_date"] + elif inner_value["type"] == "plain_text_input": + expense_mapping[inner_key] = inner_value["value"] + + print("EXPENSE -> ", json.dumps(expense_mapping, indent=2)) + + slack_client = get_slack_client(team_id) + blocks = [ + { + "type": "section", + "text": { + "type": "plain_text", + "text": "Expense created successfully :clipboard:", + "emoji": True, + }, + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": "*Amount*: \n {} {}".format(expense_mapping['currency'], expense_mapping['amount'])}, + {"type": "mrkdwn", "text": "*Merchant*: \n {}".format(expense_mapping['merchant'])}, + ], + }, + { + "type": "section", + "fields": [ + {"type": "mrkdwn", "text": "*Date of Spend*: \n {}".format(expense_mapping['spent_at'])}, + {"type": "mrkdwn", "text": "*Purpose*: \n {}".format(expense_mapping['purpose'])}, + ], + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "Edit Expense", + "emoji": True, + }, + "value": "tx123456", + "action_id": "edit_expense", + } + ], + }, + { + "type": "context", + "block_id": "tx123456", + "elements": [ + { + "type": "plain_text", + "text": "Powered by Fyle", + "emoji": True, + } + ], + }, + ] + + slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=blocks) return JsonResponse({}) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index da012651..9ed9094a 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -190,6 +190,9 @@ def expense_dialog_form(extra_fields=None, form_state=None): # blocks.append(block) current_date = datetime.datetime.today().strftime('%Y-%m-%d') + cf_category = {'id': 136250, 'name': 'Custom Field Category'} + internet_category = {'id': 136518, 'name': 'Internet'} + os_category = {'id': 1234, 'name': 'Office Supplies'} view = { "type": "modal", "callback_id": "create_expense", @@ -225,7 +228,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): "value": "USD", }, ], - "action_id": "currency_select", + "action_id": "currency", }, "label": {"type": "plain_text", "text": "Currency", "emoji": True}, }, @@ -308,7 +311,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): "text": "Select a date", "emoji": True, }, - "action_id": "date_of_spend", + "action_id": "spent_at", }, "label": {"type": "plain_text", "text": "Date of Spend", "emoji": True}, }, @@ -363,7 +366,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): "value": "1234", }, ], - "action_id": "category_select", + "action_id": "category", }, "label": {"type": "plain_text", "text": "Category", "emoji": True}, }, From 4a65b102b4845a47e0a7ed93f142085cead47bfa Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 31 Aug 2021 14:59:57 +0530 Subject: [PATCH 09/85] tested multi select --- fyle_slack_app/admin.py | 29 +++++++++++++++++++ .../interactives/view_submission_handlers.py | 7 ++++- fyle_slack_app/slack/ui/dashboard/messages.py | 10 +++++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/fyle_slack_app/admin.py b/fyle_slack_app/admin.py index f518d470..ca2ba302 100644 --- a/fyle_slack_app/admin.py +++ b/fyle_slack_app/admin.py @@ -1,4 +1,33 @@ expense_fields = [ + { + "id": 83, + "created_at": "2017-04-06T04:52:07.825Z", + "org_id": "orrjqbDbeP9p", + "column_name": "text_array_column3", + "field_name": "Multi Type", + "seq": 1, + "type": "MULTI_SELECT", + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "Multi Type", + "default_value": None, + "options": ["Multi 1", "Multi 2", "Multi 3"], + "org_category_ids": [136250], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], + }, { "id": 6182, "created_at": "2021-04-22T07:46:07.007Z", diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 178e0f02..334f8056 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -8,7 +8,7 @@ def handle_view_submission(slack_payload, user_id, team_id): if callback_id == "create_expense": form_values = slack_payload["view"]["state"]["values"] - print("REACHED CREATE EXPENSE") + print("REACHED CREATE EXPENSE -> ", form_values) expense_mapping = {} @@ -16,6 +16,11 @@ def handle_view_submission(slack_payload, user_id, team_id): for inner_key, inner_value in value.items(): if inner_value["type"] == "static_select": expense_mapping[inner_key] = inner_value["selected_option"]["value"] + if inner_value["type"] == "multi_static_select": + values_list = [] + for val in inner_value["selected_options"]: + values_list.append(val['value']) + expense_mapping[inner_key] = values_list elif inner_value["type"] == "datepicker": expense_mapping[inner_key] = inner_value["selected_date"] elif inner_value["type"] == "plain_text_input": diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index 9ed9094a..f7cca504 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -117,7 +117,7 @@ def generate_category_field_mapping(expense_fields): "default_value": ef["default_value"], "category_ids": ef["org_category_ids"], } - if ef["type"] == "SELECT": + if ef["type"] in ["SELECT", "MULTI_SELECT"]: ci_details["options"] = ef["options"] mapping[ef["column_name"]].append(ci_details) @@ -401,7 +401,11 @@ def expense_dialog_form(extra_fields=None, form_state=None): }, }, } - elif field['type'] == 'SELECT': + elif field['type'] in ['SELECT', 'MULTI_SELECT']: + if field['type'] == 'SELECT': + field_type = 'static_select' + elif field['type'] == 'MULTI_SELECT': + field_type = 'multi_static_select' fld = { "type": "input", "label": { @@ -411,7 +415,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): }, "block_id": "{}_block".format(field['column_name']), "element": { - "type": "static_select", + "type": field_type, "placeholder": { "type": "plain_text", "text": "{}".format(field["placeholder"]), From b5a3fdbb36a1d2f72cbe9f68be58ace6e3987097 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 31 Aug 2021 16:01:47 +0530 Subject: [PATCH 10/85] Separated out custom fields in expense --- .../interactives/view_submission_handlers.py | 44 ++++++++++++++----- fyle_slack_app/slack/ui/dashboard/messages.py | 8 ++-- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 334f8056..65d4ed7b 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -11,20 +11,40 @@ def handle_view_submission(slack_payload, user_id, team_id): print("REACHED CREATE EXPENSE -> ", form_values) expense_mapping = {} + custom_fields = [] for key, value in form_values.items(): - for inner_key, inner_value in value.items(): - if inner_value["type"] == "static_select": - expense_mapping[inner_key] = inner_value["selected_option"]["value"] - if inner_value["type"] == "multi_static_select": - values_list = [] - for val in inner_value["selected_options"]: - values_list.append(val['value']) - expense_mapping[inner_key] = values_list - elif inner_value["type"] == "datepicker": - expense_mapping[inner_key] = inner_value["selected_date"] - elif inner_value["type"] == "plain_text_input": - expense_mapping[inner_key] = inner_value["value"] + custom_field_mappings = {} + if 'custom_field' in key: + for inner_key, inner_value in value.items(): + if inner_value["type"] == "static_select": + custom_field_mappings[inner_key] = inner_value["selected_option"]["value"] + if inner_value["type"] == "multi_static_select": + values_list = [] + for val in inner_value["selected_options"]: + values_list.append(val['value']) + custom_field_mappings[inner_key] = values_list + elif inner_value["type"] == "datepicker": + custom_field_mappings[inner_key] = inner_value["selected_date"] + elif inner_value["type"] == "plain_text_input": + custom_field_mappings[inner_key] = inner_value["value"] + + custom_fields.append(custom_field_mappings) + else: + for inner_key, inner_value in value.items(): + if inner_value["type"] == "static_select": + expense_mapping[inner_key] = inner_value["selected_option"]["value"] + if inner_value["type"] == "multi_static_select": + values_list = [] + for val in inner_value["selected_options"]: + values_list.append(val['value']) + expense_mapping[inner_key] = values_list + elif inner_value["type"] == "datepicker": + expense_mapping[inner_key] = inner_value["selected_date"] + elif inner_value["type"] == "plain_text_input": + expense_mapping[inner_key] = inner_value["value"] + + expense_mapping['custom_fields'] = custom_fields print("EXPENSE -> ", json.dumps(expense_mapping, indent=2)) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index f7cca504..61120120 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -387,14 +387,14 @@ def expense_dialog_form(extra_fields=None, form_state=None): if field["type"] in ["NUMBER", "TEXT"]: fld = { "type": "input", - "block_id": "{}_block".format(field['column_name']), + "block_id": "custom_field_{}_block".format(field['column_name']), "label": { "type": "plain_text", "text": "{}".format(field["field_name"]), }, "element": { "type": "plain_text_input", - "action_id": "{}".format(field["column_name"].lower()), + "action_id": "{}".format(field["field_name"]), "placeholder": { "type": "plain_text", "text": "{}".format(field["placeholder"]), @@ -413,7 +413,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): "text": "{}".format(field["field_name"]), "emoji": True }, - "block_id": "{}_block".format(field['column_name']), + "block_id": "custom_field_{}_block".format(field['column_name']), "element": { "type": field_type, "placeholder": { @@ -421,7 +421,7 @@ def expense_dialog_form(extra_fields=None, form_state=None): "text": "{}".format(field["placeholder"]), "emoji": True }, - "action_id": "{}".format(field["column_name"].lower()), + "action_id": "{}".format(field["field_name"]), } } fld['element']['options'] = [] From b41e4c624b66b08efadde8974d5bcde9a4043c0b Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 1 Sep 2021 12:12:01 +0530 Subject: [PATCH 11/85] Tested boolean field custom fields --- fyle_slack_app/admin.py | 28 ++++++ .../interactives/view_submission_handlers.py | 15 +++- fyle_slack_app/slack/ui/dashboard/messages.py | 86 ++++++------------- 3 files changed, 64 insertions(+), 65 deletions(-) diff --git a/fyle_slack_app/admin.py b/fyle_slack_app/admin.py index ca2ba302..b5300c79 100644 --- a/fyle_slack_app/admin.py +++ b/fyle_slack_app/admin.py @@ -1,4 +1,32 @@ expense_fields = [ + { + "id": 39, + "org_id": "orrjqbDbeP9p", + "column_name": "boolean_column7", + "field_name": "Test BOOLEAN", + "seq": 1, + "type": "BOOLEAN", + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "placeholder": "rfve", + "default_value": None, + "options": [], + "org_category_ids": [136518], + "code": None, + "roles_editable": [ + "FYLER", + "APPROVER", + "TRAVEL_ADMIN", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "TRAVEL_AGENT", + "OWNER", + "AUDITOR", + ], + }, { "id": 83, "created_at": "2017-04-06T04:52:07.825Z", diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 65d4ed7b..d48d2314 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,6 +1,7 @@ -from fyle_slack_app.slack.utils import get_slack_client import json + from django.http.response import JsonResponse +from fyle_slack_app.slack.utils import get_slack_client def handle_view_submission(slack_payload, user_id, team_id): @@ -28,7 +29,11 @@ def handle_view_submission(slack_payload, user_id, team_id): custom_field_mappings[inner_key] = inner_value["selected_date"] elif inner_value["type"] == "plain_text_input": custom_field_mappings[inner_key] = inner_value["value"] - + elif inner_value["type"] == "checkboxes": + custom_field_mappings[inner_key] = False + if len(inner_value["selected_options"]) > 0: + custom_field_mappings[inner_key] = True + custom_fields.append(custom_field_mappings) else: for inner_key, inner_value in value.items(): @@ -43,7 +48,11 @@ def handle_view_submission(slack_payload, user_id, team_id): expense_mapping[inner_key] = inner_value["selected_date"] elif inner_value["type"] == "plain_text_input": expense_mapping[inner_key] = inner_value["value"] - + elif inner_value["type"] == "checkboxes": + expense_mapping[inner_key] = False + if len(inner_value["selected_options"]) > 0: + expense_mapping[inner_key] = True + expense_mapping['custom_fields'] = custom_fields print("EXPENSE -> ", json.dumps(expense_mapping, indent=2)) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index 61120120..140eebbc 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -19,68 +19,6 @@ def get_post_authorization_message() -> Dict: return {"type": "home", "blocks": post_authorization_message_blocks} -def mock_message(): - return { - "type": "modal", - "title": {"type": "plain_text", "text": "My App", "emoji": True}, - "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, - "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - "blocks": [ - { - "dispatch_action": True, - "type": "input", - "element": { - "type": "external_select", - "placeholder": { - "type": "plain_text", - "text": "Select an item", - "emoji": True, - }, - "action_id": "external_select_option", - }, - "label": {"type": "plain_text", "text": "Label", "emoji": True}, - } - ], - } - - -def mock_message_2(): - return { - "type": "modal", - "title": {"type": "plain_text", "text": "My App", "emoji": True}, - "submit": {"type": "plain_text", "text": "Submit", "emoji": True}, - "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - "blocks": [ - { - "dispatch_action": True, - "type": "input", - "element": { - "type": "external_select", - "placeholder": { - "type": "plain_text", - "text": "Select an item", - "emoji": True, - }, - "action_id": "external_select_option", - }, - "label": {"type": "plain_text", "text": "Label", "emoji": True}, - }, - { - "type": "section", - "text": { - "type": "plain_text", - "text": "Dynamic updated view", - "emoji": True, - }, - }, - ], - } - - -from fyle_slack_app.admin import expense_fields -from fyle_slack_app.libs.utils import encode_state - - def generate_category_field_mapping(expense_fields): mapping = {} for ef in expense_fields: @@ -436,6 +374,30 @@ def expense_dialog_form(extra_fields=None, form_state=None): "value": option } ) + elif field['type'] == 'BOOLEAN': + fld = { + "type": "input", + "block_id": "custom_field_{}_block".format(field['column_name']), + "optional": True, + "element": { + "type": "checkboxes", + "options": [ + { + "text": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + "emoji": True + } + } + ], + "action_id": "{}".format(field["field_name"]), + }, + "label": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + "emoji": True + } + } view["blocks"].append(fld) From 51207854ae71b27d2a2b33cfad1345cfa902f81c Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Thu, 2 Sep 2021 11:18:22 +0530 Subject: [PATCH 12/85] Minor stuff --- .../interactives/block_action_handlers.py | 15 +- fyle_slack_app/slack/ui/dashboard/messages.py | 787 +++++++++--------- 2 files changed, 398 insertions(+), 404 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 9fba3ca8..f02f05cc 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -9,6 +9,8 @@ from fyle_slack_app.models.notification_preferences import NotificationType from fyle_slack_app.libs import assertions, utils, logger from fyle_slack_app.slack.utils import get_slack_user_dm_channel_id, get_slack_client +from fyle_slack_app.slack.ui.dashboard.messages import generate_category_field_mapping, expense_dialog_form +from fyle_slack_app.admin import expense_fields from fyle_slack_app import tracking @@ -41,20 +43,15 @@ def _initialize_block_action_handlers(self): def category_select(self, slack_payload: Dict, user_id: str, team_id: str): trigger_id = slack_payload['trigger_id'] - from fyle_slack_app.slack.ui.dashboard.messages import generate_category_field_mapping, expense_dialog_form - from fyle_slack_app.admin import expense_fields - slack_client = get_slack_client(team_id) category_id = slack_payload['actions'][0]['selected_option']['value'] - + view_id = slack_payload['container']['view_id'] mappings = generate_category_field_mapping(expense_fields) - # print('MAPINGS -> ', json.dumps(mappings, indent=2)) - extra_fields = [] default_fields = ['purpose', 'txn_dt', 'vendor_id', 'cost_center_id'] @@ -69,12 +66,12 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): form_state = slack_payload['view']['state']['values'] if len(extra_fields) > 0: - new_expense_dialog_form = expense_dialog_form(extra_fields, form_state) + new_expense_dialog_form = expense_dialog_form(extra_fields) else: new_expense_dialog_form = expense_dialog_form() - + slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) - + return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index 140eebbc..04106262 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -8,411 +8,408 @@ def get_pre_authorization_message(user_name: str, fyle_oauth_url: str) -> Dict: - pre_authorization_message_blocks = messages.get_pre_authorization_message( - user_name, fyle_oauth_url - ) - return {"type": "home", "blocks": pre_authorization_message_blocks} + pre_authorization_message_blocks = messages.get_pre_authorization_message( + user_name, fyle_oauth_url + ) + return {"type": "home", "blocks": pre_authorization_message_blocks} def get_post_authorization_message() -> Dict: - post_authorization_message_blocks = messages.get_post_authorization_message() - return {"type": "home", "blocks": post_authorization_message_blocks} + post_authorization_message_blocks = messages.get_post_authorization_message() + return {"type": "home", "blocks": post_authorization_message_blocks} def generate_category_field_mapping(expense_fields): - mapping = {} - for ef in expense_fields: - # for ci in ef['org_category_ids']: - # if ci not in mapping: - # mapping[ci] = [] - # if not any(ef['column_name'] in cd for cd in mapping[ci]): - # ci_details = { - # 'field_name': ef['field_name'], - # 'column_name': ef['column_name'], - # 'type': ef['type'], - # 'is_custom': ef['is_custom'], - # 'is_enabled': ef['is_enabled'], - # 'is_mandatory': ef['is_mandatory'], - # 'placeholder': ef['placeholder'], - # 'default_value': ef['default_value'], - # 'category_ids': ef['org_category_ids'] - # } - # if ef['type'] == 'SELECT': - # ci_details['options'] = ef['options'] - # mapping[ci].append(ci_details) + mapping = {} + for ef in expense_fields: + # for ci in ef['org_category_ids']: + # if ci not in mapping: + # mapping[ci] = [] + # if not any(ef['column_name'] in cd for cd in mapping[ci]): + # ci_details = { + # 'field_name': ef['field_name'], + # 'column_name': ef['column_name'], + # 'type': ef['type'], + # 'is_custom': ef['is_custom'], + # 'is_enabled': ef['is_enabled'], + # 'is_mandatory': ef['is_mandatory'], + # 'placeholder': ef['placeholder'], + # 'default_value': ef['default_value'], + # 'category_ids': ef['org_category_ids'] + # } + # if ef['type'] == 'SELECT': + # ci_details['options'] = ef['options'] + # mapping[ci].append(ci_details) - if ef["column_name"] not in mapping: - mapping[ef["column_name"]] = [] + if ef["column_name"] not in mapping: + mapping[ef["column_name"]] = [] - ci_details = { - "field_name": ef["field_name"], - "column_name": ef["column_name"], - "type": ef["type"], - "is_custom": ef["is_custom"], - "is_enabled": ef["is_enabled"], - "is_mandatory": ef["is_mandatory"], - "placeholder": ef["placeholder"], - "default_value": ef["default_value"], - "category_ids": ef["org_category_ids"], - } - if ef["type"] in ["SELECT", "MULTI_SELECT"]: - ci_details["options"] = ef["options"] - mapping[ef["column_name"]].append(ci_details) + ci_details = { + "field_name": ef["field_name"], + "column_name": ef["column_name"], + "type": ef["type"], + "is_custom": ef["is_custom"], + "is_enabled": ef["is_enabled"], + "is_mandatory": ef["is_mandatory"], + "placeholder": ef["placeholder"], + "default_value": ef["default_value"], + "category_ids": ef["org_category_ids"], + } + if ef["type"] in ["SELECT", "MULTI_SELECT"]: + ci_details["options"] = ef["options"] + mapping[ef["column_name"]].append(ci_details) - # print(json.dumps(mapping, indent=2)) - # print('MAPPINGS -> ', zlib.compress(base64.b64encode(json.dumps(mapping).encode())).decode()) - return mapping + return mapping -def expense_dialog_form(extra_fields=None, form_state=None): - blocks = [] - # mappings = generate_category_field_mapping(expense_fields) - # for expense_field in expense_fields: - # if expense_field["type"] in ["NUMBER", "TEXT"]: - # block = { - # "type": "input", - # # "block_id": "{}".format(random), - # "label": { - # "type": "plain_text", - # "text": "{}".format(expense_field["field_name"]), - # }, - # "element": { - # "type": "plain_text_input", - # "action_id": "{}".format(expense_field["field_name"].lower()), - # "placeholder": { - # "type": "plain_text", - # "text": "{}".format(expense_field["placeholder"]), - # }, - # }, - # } - # elif expense_field["type"] == "SELECT": - # block = { - # "type": "input", - # # "block_id": "{}".format(uuid.uuid4()), - # "label": { - # "type": "plain_text", - # "text": "{}".format(expense_field["field_name"]), - # }, - # "element": { - # "type": "static_select", - # "action_id": "{}".format(expense_field["field_name"].lower()), - # "placeholder": { - # "type": "plain_text", - # "text": "{}".format(expense_field["placeholder"]), - # }, - # "options": [ - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-0", - # }, - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-1", - # }, - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-2", - # }, - # ], - # }, - # } +def expense_dialog_form(extra_fields=None): + # blocks = [] + # mappings = generate_category_field_mapping(expense_fields) + # for expense_field in expense_fields: + # if expense_field["type"] in ["NUMBER", "TEXT"]: + # block = { + # "type": "input", + # # "block_id": "{}".format(random), + # "label": { + # "type": "plain_text", + # "text": "{}".format(expense_field["field_name"]), + # }, + # "element": { + # "type": "plain_text_input", + # "action_id": "{}".format(expense_field["field_name"].lower()), + # "placeholder": { + # "type": "plain_text", + # "text": "{}".format(expense_field["placeholder"]), + # }, + # }, + # } + # elif expense_field["type"] == "SELECT": + # block = { + # "type": "input", + # # "block_id": "{}".format(uuid.uuid4()), + # "label": { + # "type": "plain_text", + # "text": "{}".format(expense_field["field_name"]), + # }, + # "element": { + # "type": "static_select", + # "action_id": "{}".format(expense_field["field_name"].lower()), + # "placeholder": { + # "type": "plain_text", + # "text": "{}".format(expense_field["placeholder"]), + # }, + # "options": [ + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-0", + # }, + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-1", + # }, + # { + # "text": { + # "type": "plain_text", + # "text": "*this is plain_text text*", + # }, + # "value": "value-2", + # }, + # ], + # }, + # } - # blocks.append(block) - current_date = datetime.datetime.today().strftime('%Y-%m-%d') - cf_category = {'id': 136250, 'name': 'Custom Field Category'} - internet_category = {'id': 136518, 'name': 'Internet'} - os_category = {'id': 1234, 'name': 'Office Supplies'} - view = { - "type": "modal", - "callback_id": "create_expense", - "title": {"type": "plain_text", "text": "Create Expense", "emoji": True}, - "submit": {"type": "plain_text", "text": "Add Expense", "emoji": True}, - "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - "blocks": [ - { - "type": "input", - "block_id": "currency_block", - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Select Currency", - "emoji": True, - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "INR", - "emoji": True, - }, - "value": "INR", - }, - { - "text": { - "type": "plain_text", - "text": "USD", - "emoji": True, - }, - "value": "USD", - }, - ], - "action_id": "currency", - }, - "label": {"type": "plain_text", "text": "Currency", "emoji": True}, - }, - { - "type": "input", - "block_id": "amount_block", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Enter Amount", - "emoji": True, - }, - "action_id": "amount", - }, - "label": {"type": "plain_text", "text": "Amount", "emoji": True}, - }, - { - "type": "input", - "block_id": "payment_mode_block", - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Select Payment Mode", - "emoji": True, - }, - "initial_option": { - "text": { - "type": "plain_text", - "text": "Paid by me", - "emoji": True, - }, - "value": "paid_by_me", - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "Paid by me", - "emoji": True, - }, - "value": "paid_by_me", - }, - { - "text": { - "type": "plain_text", - "text": "Paid by company", - "emoji": True, - }, - "value": "paid_by_company", - }, - ], - "action_id": "payment_mode", - }, - "label": {"type": "plain_text", "text": "Payment Mode", "emoji": True}, - }, - { - "type": "input", - "block_id": "purpose_block", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Eg. Client Meeting", - "emoji": True, - }, - "action_id": "purpose", - }, - "label": {"type": "plain_text", "text": "Purpose", "emoji": True}, - }, - { - "type": "input", - "block_id": "date_of_spend_block", - "element": { - "type": "datepicker", - "initial_date": current_date, - "placeholder": { - "type": "plain_text", - "text": "Select a date", - "emoji": True, - }, - "action_id": "spent_at", - }, - "label": {"type": "plain_text", "text": "Date of Spend", "emoji": True}, - }, - { - "type": "input", - "block_id": "merchant_block", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Eg. Uber", - "emoji": True, - }, - "action_id": "merchant", - }, - "label": {"type": "plain_text", "text": "Merchant", "emoji": True}, - }, - { - "type": "input", - "block_id": "category_block", - "dispatch_action": True, - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Eg. Travel", - "emoji": True, - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "Custom Field Category", - "emoji": True, - }, - "value": "136250", - }, - { - "text": { - "type": "plain_text", - "text": "Internet", - "emoji": True, - }, - "value": "136518", - }, - { - "text": { - "type": "plain_text", - "text": "Office Supplies", - "emoji": True, - }, - "value": "1234", - }, - ], - "action_id": "category", - }, - "label": {"type": "plain_text", "text": "Category", "emoji": True}, - }, - ], - } - # view = { - # "type": "modal", - # "callback_id": "create_expense", - # "title": {"type": "plain_text", "text": "Add Expense"}, - # "submit": {"type": "plain_text", "text": "Add Expense"}, - # "close": {"type": "plain_text", "text": "Cancel"}, - # "notify_on_close": False, - # "blocks": blocks, - # # "private_metadata": zlib.compress(base64.b64encode(json.dumps(mappings).encode())).decode() - # } - if extra_fields is not None: - for field in extra_fields: - if field["type"] in ["NUMBER", "TEXT"]: - fld = { - "type": "input", - "block_id": "custom_field_{}_block".format(field['column_name']), - "label": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - }, - "element": { - "type": "plain_text_input", - "action_id": "{}".format(field["field_name"]), - "placeholder": { - "type": "plain_text", - "text": "{}".format(field["placeholder"]), - }, - }, - } - elif field['type'] in ['SELECT', 'MULTI_SELECT']: - if field['type'] == 'SELECT': - field_type = 'static_select' - elif field['type'] == 'MULTI_SELECT': - field_type = 'multi_static_select' - fld = { - "type": "input", - "label": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - "emoji": True - }, - "block_id": "custom_field_{}_block".format(field['column_name']), - "element": { - "type": field_type, - "placeholder": { - "type": "plain_text", - "text": "{}".format(field["placeholder"]), - "emoji": True - }, - "action_id": "{}".format(field["field_name"]), - } - } - fld['element']['options'] = [] - for option in field['options']: - fld['element']['options'].append( - { - "text": { - "type": "plain_text", - "text": option, - "emoji": True - }, - "value": option - } - ) - elif field['type'] == 'BOOLEAN': - fld = { - "type": "input", - "block_id": "custom_field_{}_block".format(field['column_name']), - "optional": True, - "element": { - "type": "checkboxes", - "options": [ - { - "text": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - "emoji": True - } - } - ], - "action_id": "{}".format(field["field_name"]), - }, - "label": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - "emoji": True - } - } - view["blocks"].append(fld) + # blocks.append(block) + current_date = datetime.datetime.today().strftime("%Y-%m-%d") + cf_category = {"id": 136250, "name": "Custom Field Category"} + internet_category = {"id": 136518, "name": "Internet"} + os_category = {"id": 1234, "name": "Office Supplies"} + view = { + "type": "modal", + "callback_id": "create_expense", + "title": {"type": "plain_text", "text": "Create Expense", "emoji": True}, + "submit": {"type": "plain_text", "text": "Add Expense", "emoji": True}, + "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + "blocks": [ + { + "type": "input", + "block_id": "currency_block", + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Select Currency", + "emoji": True, + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "INR", + "emoji": True, + }, + "value": "INR", + }, + { + "text": { + "type": "plain_text", + "text": "USD", + "emoji": True, + }, + "value": "USD", + }, + ], + "action_id": "currency", + }, + "label": {"type": "plain_text", "text": "Currency", "emoji": True}, + }, + { + "type": "input", + "block_id": "amount_block", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Enter Amount", + "emoji": True, + }, + "action_id": "amount", + }, + "label": {"type": "plain_text", "text": "Amount", "emoji": True}, + }, + { + "type": "input", + "block_id": "payment_mode_block", + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Select Payment Mode", + "emoji": True, + }, + "initial_option": { + "text": { + "type": "plain_text", + "text": "Paid by me", + "emoji": True, + }, + "value": "paid_by_me", + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Paid by me", + "emoji": True, + }, + "value": "paid_by_me", + }, + { + "text": { + "type": "plain_text", + "text": "Paid by company", + "emoji": True, + }, + "value": "paid_by_company", + }, + ], + "action_id": "payment_mode", + }, + "label": {"type": "plain_text", "text": "Payment Mode", "emoji": True}, + }, + { + "type": "input", + "block_id": "purpose_block", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Eg. Client Meeting", + "emoji": True, + }, + "action_id": "purpose", + }, + "label": {"type": "plain_text", "text": "Purpose", "emoji": True}, + }, + { + "type": "input", + "block_id": "date_of_spend_block", + "element": { + "type": "datepicker", + "initial_date": current_date, + "placeholder": { + "type": "plain_text", + "text": "Select a date", + "emoji": True, + }, + "action_id": "spent_at", + }, + "label": {"type": "plain_text", "text": "Date of Spend", "emoji": True}, + }, + { + "type": "input", + "block_id": "merchant_block", + "element": { + "type": "plain_text_input", + "placeholder": { + "type": "plain_text", + "text": "Eg. Uber", + "emoji": True, + }, + "action_id": "merchant", + }, + "label": {"type": "plain_text", "text": "Merchant", "emoji": True}, + }, + { + "type": "input", + "block_id": "category_block", + "dispatch_action": True, + "element": { + "type": "static_select", + "placeholder": { + "type": "plain_text", + "text": "Eg. Travel", + "emoji": True, + }, + "options": [ + { + "text": { + "type": "plain_text", + "text": "Custom Field Category", + "emoji": True, + }, + "value": "136250", + }, + { + "text": { + "type": "plain_text", + "text": "Internet", + "emoji": True, + }, + "value": "136518", + }, + { + "text": { + "type": "plain_text", + "text": "Office Supplies", + "emoji": True, + }, + "value": "1234", + }, + ], + "action_id": "category", + }, + "label": {"type": "plain_text", "text": "Category", "emoji": True}, + }, + ], + } + # view = { + # "type": "modal", + # "callback_id": "create_expense", + # "title": {"type": "plain_text", "text": "Add Expense"}, + # "submit": {"type": "plain_text", "text": "Add Expense"}, + # "close": {"type": "plain_text", "text": "Cancel"}, + # "notify_on_close": False, + # "blocks": blocks, + # # "private_metadata": zlib.compress(base64.b64encode(json.dumps(mappings).encode())).decode() + # } + if extra_fields is not None: + for field in extra_fields: + if field["type"] in ["NUMBER", "TEXT"]: + fld = { + "type": "input", + "block_id": "custom_field_{}_block".format(field["column_name"]), + "label": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + }, + "element": { + "type": "plain_text_input", + "action_id": "{}".format(field["field_name"]), + "placeholder": { + "type": "plain_text", + "text": "{}".format(field["placeholder"]), + }, + }, + } + elif field["type"] in ["SELECT", "MULTI_SELECT"]: + if field["type"] == "SELECT": + field_type = "static_select" + elif field["type"] == "MULTI_SELECT": + field_type = "multi_static_select" + fld = { + "type": "input", + "label": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + "emoji": True, + }, + "block_id": "custom_field_{}_block".format(field["column_name"]), + "element": { + "type": field_type, + "placeholder": { + "type": "plain_text", + "text": "{}".format(field["placeholder"]), + "emoji": True, + }, + "action_id": "{}".format(field["field_name"]), + }, + } + fld["element"]["options"] = [] + for option in field["options"]: + fld["element"]["options"].append( + { + "text": { + "type": "plain_text", + "text": option, + "emoji": True, + }, + "value": option, + } + ) + elif field["type"] == "BOOLEAN": + fld = { + "type": "input", + "block_id": "custom_field_{}_block".format(field["column_name"]), + "optional": True, + "element": { + "type": "checkboxes", + "options": [ + { + "text": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + "emoji": True, + } + } + ], + "action_id": "{}".format(field["field_name"]), + }, + "label": { + "type": "plain_text", + "text": "{}".format(field["field_name"]), + "emoji": True, + }, + } + view["blocks"].append(fld) - - # if form_state is not None: - # for block in view['blocks']: - # print('BLOCK ID -> ', block['block_id']) - # value_key = 'value' - # initial_value_key = 'initial_value' - # if block['element']['type'] == 'static_select': - # value_key = 'selected_option' - # initial_value_key = 'initial_option' - # elif block['element']['type'] == 'datepicker': - # value_key = 'selected_date' - # initial_value_key = 'initial_date' - - # block['element'][initial_value_key] = form_state[block['block_id']][block['element']['action_id']][value_key] - - return view + # if form_state is not None: + # for block in view['blocks']: + # print('BLOCK ID -> ', block['block_id']) + # value_key = 'value' + # initial_value_key = 'initial_value' + # if block['element']['type'] == 'static_select': + # value_key = 'selected_option' + # initial_value_key = 'initial_option' + # elif block['element']['type'] == 'datepicker': + # value_key = 'selected_date' + # initial_value_key = 'initial_date' + + # block['element'][initial_value_key] = form_state[block['block_id']][block['element']['action_id']][value_key] + + return view From ffd1632ea430eef572b5212f527b31d3634bc4d0 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Thu, 2 Sep 2021 16:16:46 +0530 Subject: [PATCH 13/85] Added class based handler --- .../slack/interactives/shortcut_handlers.py | 4 +- .../interactives/view_submission_handlers.py | 261 ++++++++++-------- fyle_slack_app/slack/interactives/views.py | 46 +-- 3 files changed, 162 insertions(+), 149 deletions(-) diff --git a/fyle_slack_app/slack/interactives/shortcut_handlers.py b/fyle_slack_app/slack/interactives/shortcut_handlers.py index daf7db65..1b1c50c0 100644 --- a/fyle_slack_app/slack/interactives/shortcut_handlers.py +++ b/fyle_slack_app/slack/interactives/shortcut_handlers.py @@ -57,6 +57,8 @@ def handle_notification_preferences(self, slack_payload: Dict, user_id: str, tea user_dm_channel_id = slack_utils.get_slack_user_dm_channel_id(slack_client, user_id) - SlackCommandHandler().handle_fyle_notification_preferences(user_id, team_id, user_dm_channel_id) + trigger_id = slack_payload['trigger_id'] + + SlackCommandHandler().handle_fyle_notification_preferences(user_id, team_id, user_dm_channel_id, trigger_id) return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index d48d2314..4c5923ea 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,113 +1,156 @@ import json +from typing import Callable, Dict + from django.http.response import JsonResponse -from fyle_slack_app.slack.utils import get_slack_client - - -def handle_view_submission(slack_payload, user_id, team_id): - callback_id = slack_payload["view"]["callback_id"] - - if callback_id == "create_expense": - form_values = slack_payload["view"]["state"]["values"] - print("REACHED CREATE EXPENSE -> ", form_values) - - expense_mapping = {} - custom_fields = [] - - for key, value in form_values.items(): - custom_field_mappings = {} - if 'custom_field' in key: - for inner_key, inner_value in value.items(): - if inner_value["type"] == "static_select": - custom_field_mappings[inner_key] = inner_value["selected_option"]["value"] - if inner_value["type"] == "multi_static_select": - values_list = [] - for val in inner_value["selected_options"]: - values_list.append(val['value']) - custom_field_mappings[inner_key] = values_list - elif inner_value["type"] == "datepicker": - custom_field_mappings[inner_key] = inner_value["selected_date"] - elif inner_value["type"] == "plain_text_input": - custom_field_mappings[inner_key] = inner_value["value"] - elif inner_value["type"] == "checkboxes": - custom_field_mappings[inner_key] = False - if len(inner_value["selected_options"]) > 0: - custom_field_mappings[inner_key] = True - - custom_fields.append(custom_field_mappings) - else: - for inner_key, inner_value in value.items(): - if inner_value["type"] == "static_select": - expense_mapping[inner_key] = inner_value["selected_option"]["value"] - if inner_value["type"] == "multi_static_select": - values_list = [] - for val in inner_value["selected_options"]: - values_list.append(val['value']) - expense_mapping[inner_key] = values_list - elif inner_value["type"] == "datepicker": - expense_mapping[inner_key] = inner_value["selected_date"] - elif inner_value["type"] == "plain_text_input": - expense_mapping[inner_key] = inner_value["value"] - elif inner_value["type"] == "checkboxes": - expense_mapping[inner_key] = False - if len(inner_value["selected_options"]) > 0: - expense_mapping[inner_key] = True - - expense_mapping['custom_fields'] = custom_fields - - print("EXPENSE -> ", json.dumps(expense_mapping, indent=2)) - - slack_client = get_slack_client(team_id) - blocks = [ - { - "type": "section", - "text": { - "type": "plain_text", - "text": "Expense created successfully :clipboard:", - "emoji": True, + +from fyle_slack_app.slack import utils as slack_utils + +class ViewSubmissionHandler: + + _view_submission_handlers: Dict = {} + + # Maps action_id with it's respective function + def _initialize_view_submission_handlers(self): + self._view_submission_handlers = { + 'create_expense': self.handle_create_expense + } + + + # Gets called when function with a callback id is not found + def _handle_invalid_view_submission(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + slack_client = slack_utils.get_slack_client(team_id) + + user_dm_channel_id = slack_utils.get_slack_user_dm_channel_id(slack_client, user_id) + slack_client.chat_postMessage( + channel=user_dm_channel_id, + text='Looks like something went wrong :zipper_mouth_face: \n Please try again' + ) + return JsonResponse({}, status=200) + + + # Handle all the view_submission from slack + def handle_view_submission(self, slack_payload: Dict, user_id: str, team_id: str) -> Callable: + ''' + Check if any function is associated with the action + If present handler will call the respective function + If not present call `handle_invalid_view_submission` to send a prompt to user + ''' + + # Initialize handlers + self._initialize_view_submission_handlers() + + callback_id = slack_payload['view']['callback_id'] + + handler = self._view_submission_handlers.get(callback_id, self._handle_invalid_view_submission) + + return handler(slack_payload, user_id, team_id) + + + def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str): + callback_id = slack_payload['view']['callback_id'] + + if callback_id == 'create_expense': + form_values = slack_payload['view']['state']['values'] + print('REACHED CREATE EXPENSE -> ', form_values) + + expense_mapping = {} + custom_fields = [] + + for key, value in form_values.items(): + custom_field_mappings = {} + if 'custom_field' in key: + for inner_key, inner_value in value.items(): + if inner_value['type'] == 'static_select': + custom_field_mappings[inner_key] = inner_value['selected_option']['value'] + if inner_value['type'] == 'multi_static_select': + values_list = [] + for val in inner_value['selected_options']: + values_list.append(val['value']) + custom_field_mappings[inner_key] = values_list + elif inner_value['type'] == 'datepicker': + custom_field_mappings[inner_key] = inner_value['selected_date'] + elif inner_value['type'] == 'plain_text_input': + custom_field_mappings[inner_key] = inner_value['value'] + elif inner_value['type'] == 'checkboxes': + custom_field_mappings[inner_key] = False + if len(inner_value['selected_options']) > 0: + custom_field_mappings[inner_key] = True + + custom_fields.append(custom_field_mappings) + else: + for inner_key, inner_value in value.items(): + if inner_value['type'] == 'static_select': + expense_mapping[inner_key] = inner_value['selected_option']['value'] + if inner_value['type'] == 'multi_static_select': + values_list = [] + for val in inner_value['selected_options']: + values_list.append(val['value']) + expense_mapping[inner_key] = values_list + elif inner_value['type'] == 'datepicker': + expense_mapping[inner_key] = inner_value['selected_date'] + elif inner_value['type'] == 'plain_text_input': + expense_mapping[inner_key] = inner_value['value'] + elif inner_value['type'] == 'checkboxes': + expense_mapping[inner_key] = False + if len(inner_value['selected_options']) > 0: + expense_mapping[inner_key] = True + + expense_mapping['custom_fields'] = custom_fields + + print('EXPENSE -> ', json.dumps(expense_mapping, indent=2)) + + slack_client = slack_utils.get_slack_client(team_id) + blocks = [ + { + 'type': 'section', + 'text': { + 'type': 'plain_text', + 'text': 'Expense created successfully :clipboard:', + 'emoji': True, + }, + }, + { + 'type': 'section', + 'fields': [ + {'type': 'mrkdwn', 'text': '*Amount*: \n {} {}'.format(expense_mapping['currency'], expense_mapping['amount'])}, + {'type': 'mrkdwn', 'text': '*Merchant*: \n {}'.format(expense_mapping['merchant'])}, + ], + }, + { + 'type': 'section', + 'fields': [ + {'type': 'mrkdwn', 'text': '*Date of Spend*: \n {}'.format(expense_mapping['spent_at'])}, + {'type': 'mrkdwn', 'text': '*Purpose*: \n {}'.format(expense_mapping['purpose'])}, + ], }, - }, - { - "type": "section", - "fields": [ - {"type": "mrkdwn", "text": "*Amount*: \n {} {}".format(expense_mapping['currency'], expense_mapping['amount'])}, - {"type": "mrkdwn", "text": "*Merchant*: \n {}".format(expense_mapping['merchant'])}, - ], - }, - { - "type": "section", - "fields": [ - {"type": "mrkdwn", "text": "*Date of Spend*: \n {}".format(expense_mapping['spent_at'])}, - {"type": "mrkdwn", "text": "*Purpose*: \n {}".format(expense_mapping['purpose'])}, - ], - }, - { - "type": "actions", - "elements": [ - { - "type": "button", - "text": { - "type": "plain_text", - "text": "Edit Expense", - "emoji": True, - }, - "value": "tx123456", - "action_id": "edit_expense", - } - ], - }, - { - "type": "context", - "block_id": "tx123456", - "elements": [ - { - "type": "plain_text", - "text": "Powered by Fyle", - "emoji": True, - } - ], - }, - ] - - slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=blocks) - return JsonResponse({}) + { + 'type': 'actions', + 'elements': [ + { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Edit Expense', + 'emoji': True, + }, + 'value': 'tx123456', + 'action_id': 'edit_expense', + } + ], + }, + { + 'type': 'context', + 'block_id': 'tx123456', + 'elements': [ + { + 'type': 'plain_text', + 'text': 'Powered by Fyle', + 'emoji': True, + } + ], + }, + ] + + slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=blocks) + return JsonResponse({}) diff --git a/fyle_slack_app/slack/interactives/views.py b/fyle_slack_app/slack/interactives/views.py index 12abef96..d35262be 100644 --- a/fyle_slack_app/slack/interactives/views.py +++ b/fyle_slack_app/slack/interactives/views.py @@ -5,10 +5,10 @@ from fyle_slack_app.slack import SlackView from fyle_slack_app.slack.interactives.block_action_handlers import BlockActionHandler from fyle_slack_app.slack.interactives.shortcut_handlers import ShortcutHandler -from fyle_slack_app.slack.interactives.view_submission_handlers import handle_view_submission +from fyle_slack_app.slack.interactives.view_submission_handlers import ViewSubmissionHandler -class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler): +class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler, ViewSubmissionHandler): def post(self, request: HttpRequest) -> JsonResponse: payload = request.POST.get('payload') @@ -28,44 +28,12 @@ def post(self, request: HttpRequest) -> JsonResponse: elif event_type == 'shortcut': # Call handler function from ShortcutHandler return self.handle_shortcuts(slack_payload, user_id, team_id) - + elif event_type == 'view_submission': - return handle_view_submission(slack_payload, user_id, team_id) + # Call handler function from ViewSubmissionHandler + return self.handle_view_submission(slack_payload, user_id, team_id) - elif event_type == 'block_suggestion': - # Call handler function from BlockActionHandler - a = { - 'options': [ - { - "text": { - "type": "plain_text", - "text": "Category" - }, - "value": "category" - }, - { - "text": { - "type": "plain_text", - "text": "Discrepancy" - }, - "value": "discrepancy" - }, - { - "text": { - "type": "plain_text", - "text": "Projects" - }, - "value": "projects" - }, - { - "text": { - "type": "plain_text", - "text": "Currency" - }, - "value": "currency" - } - ] - } - return JsonResponse(a) + # elif event_type == 'block_suggestion': + # # Call handler function from BlockActionHandler return JsonResponse({}, status=200) From fe0f9f2a47274bf94ded5212ed2fde61d0bcdc84 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Thu, 2 Sep 2021 17:25:11 +0530 Subject: [PATCH 14/85] showing custom fields in expense details --- .../interactives/block_action_handlers.py | 7 ---- .../interactives/view_submission_handlers.py | 32 ++++++++++++++++--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index f02f05cc..7f939d6e 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -41,11 +41,9 @@ def _initialize_block_action_handlers(self): def category_select(self, slack_payload: Dict, user_id: str, team_id: str): - trigger_id = slack_payload['trigger_id'] slack_client = get_slack_client(team_id) - category_id = slack_payload['actions'][0]['selected_option']['value'] view_id = slack_payload['container']['view_id'] @@ -63,8 +61,6 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): print('EF -> ', json.dumps(extra_fields, indent=2)) - form_state = slack_payload['view']['state']['values'] - if len(extra_fields) > 0: new_expense_dialog_form = expense_dialog_form(extra_fields) else: @@ -72,7 +68,6 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) - return JsonResponse({}, status=200) # Gets called when function with an action is not found @@ -100,8 +95,6 @@ def handle_block_actions(self, slack_payload: Dict, user_id: str, team_id: str) action_id = slack_payload['actions'][0]['action_id'] - print('ACTIONM ID -> ', action_id) - handler = self._block_action_handlers.get(action_id, self._handle_invalid_block_actions) return handler(slack_payload, user_id, team_id) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 4c5923ea..7a44c561 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -123,7 +123,28 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) {'type': 'mrkdwn', 'text': '*Date of Spend*: \n {}'.format(expense_mapping['spent_at'])}, {'type': 'mrkdwn', 'text': '*Purpose*: \n {}'.format(expense_mapping['purpose'])}, ], - }, + } + ] + + cf_section = { + 'type': 'section', + 'fields': [] + } + + for custom_field in custom_fields: + for cf in custom_field.keys(): + if isinstance(custom_field[cf], list): + value = ', '.join(custom_field[cf]) + elif isinstance(custom_field[cf], bool): + value = 'Yes' if custom_field[cf] is True else 'No' + else: + value = custom_field[cf] + cf_section['fields'].append( + {'type': 'mrkdwn', 'text': '*{}*: \n {}'.format(cf.title(), value)} + ) + + blocks.append(cf_section) + blocks.append( { 'type': 'actions', 'elements': [ @@ -138,7 +159,9 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) 'action_id': 'edit_expense', } ], - }, + } + ) + blocks.append( { 'type': 'context', 'block_id': 'tx123456', @@ -149,8 +172,7 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) 'emoji': True, } ], - }, - ] - + } + ) slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=blocks) return JsonResponse({}) From e56b92b641e6b5ce9ebce175cfa9c1a225d0775b Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 7 Sep 2021 13:09:15 +0530 Subject: [PATCH 15/85] External select of categories --- fyle_slack_app/slack/interactives/views.py | 47 ++++++++++++++++++- fyle_slack_app/slack/ui/dashboard/messages.py | 29 +----------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/fyle_slack_app/slack/interactives/views.py b/fyle_slack_app/slack/interactives/views.py index d35262be..a16e6c08 100644 --- a/fyle_slack_app/slack/interactives/views.py +++ b/fyle_slack_app/slack/interactives/views.py @@ -1,4 +1,5 @@ import json +from os import lchown from django.http import HttpRequest, JsonResponse @@ -33,7 +34,49 @@ def post(self, request: HttpRequest) -> JsonResponse: # Call handler function from ViewSubmissionHandler return self.handle_view_submission(slack_payload, user_id, team_id) - # elif event_type == 'block_suggestion': - # # Call handler function from BlockActionHandler + elif event_type == 'block_suggestion': + # Call handler function from BlockActionHandler + value = slack_payload['value'] + categories = [ + { + 'text': { + 'type': 'plain_text', + 'text': 'Custom Field Category', + 'emoji': True, + }, + 'value': '136250', + }, + { + 'text': { + 'type': 'plain_text', + 'text': 'Internet', + 'emoji': True, + }, + 'value': '136518', + }, + { + 'text': { + 'type': 'plain_text', + 'text': 'Office Supplies', + 'emoji': True, + }, + 'value': '1234', + }, + { + 'text': { + 'type': 'plain_text', + 'text': 'Stuff for office', + 'emoji': True, + }, + 'value': '1234', + }, + ] + + options = [] + for category in categories: + if value.lower() in category['text']['text'].lower(): + options.append(category) + + return JsonResponse({'options': options}) return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index 04106262..fec404ab 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -270,38 +270,13 @@ def expense_dialog_form(extra_fields=None): "block_id": "category_block", "dispatch_action": True, "element": { - "type": "static_select", + "type": "external_select", + "min_query_length": 1, "placeholder": { "type": "plain_text", "text": "Eg. Travel", "emoji": True, }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "Custom Field Category", - "emoji": True, - }, - "value": "136250", - }, - { - "text": { - "type": "plain_text", - "text": "Internet", - "emoji": True, - }, - "value": "136518", - }, - { - "text": { - "type": "plain_text", - "text": "Office Supplies", - "emoji": True, - }, - "value": "1234", - }, - ], "action_id": "category", }, "label": {"type": "plain_text", "text": "Category", "emoji": True}, From 19088128b23ec2dd7eac51ad842b12f374c7af07 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 13 Sep 2021 18:35:32 +0530 Subject: [PATCH 16/85] Minor --- fyle_slack_app/fyle/utils.py | 13 +- fyle_slack_app/slack/events/views.py | 9 + .../interactives/block_action_handlers.py | 1 + .../interactives/view_submission_handlers.py | 262 ++++++++++-------- fyle_slack_app/slack/interactives/views.py | 2 +- .../slack/ui/authorization/messages.py | 2 + fyle_slack_service/settings.py | 8 + run.sh | 1 + 8 files changed, 186 insertions(+), 112 deletions(-) diff --git a/fyle_slack_app/fyle/utils.py b/fyle_slack_app/fyle/utils.py index 99fd5ace..c046a905 100644 --- a/fyle_slack_app/fyle/utils.py +++ b/fyle_slack_app/fyle/utils.py @@ -5,6 +5,7 @@ from fyle.platform import Platform from django.conf import settings +from django.core.cache import cache from fyle_slack_app.libs import http, assertions, utils from fyle_slack_app.models.user_subscription_details import SubscriptionType @@ -76,8 +77,16 @@ def get_fyle_refresh_token(code: str) -> str: def get_fyle_profile(refresh_token: str) -> Dict: - connection = get_fyle_sdk_connection(refresh_token) - fyle_profile_response = connection.v1.fyler.my_profile.get() + fyle_profile = cache.get('abcd') + print('CACHE PROFILE -> ', fyle_profile) + if fyle_profile is None: + print('CACHE MISS') + connection = get_fyle_sdk_connection(refresh_token) + fyle_profile_response = connection.v1.fyler.my_profile.get() + cache.set('abcd', fyle_profile_response) + else: + print('CACHE HIT') + fyle_profile_response = fyle_profile return fyle_profile_response['data'] diff --git a/fyle_slack_app/slack/events/views.py b/fyle_slack_app/slack/events/views.py index c282cafd..f836a086 100644 --- a/fyle_slack_app/slack/events/views.py +++ b/fyle_slack_app/slack/events/views.py @@ -42,8 +42,17 @@ def post(self, request: HttpRequest) -> JsonResponse: thread_ts = file_info['file']['shares']['private'][user.slack_dm_channel_id][0]['thread_ts'] + print('THREAD TD -> ', thread_ts) + + a = slack_client.chat_getPermalink(channel=user.slack_dm_channel_id, message_ts=thread_ts) + pl = a['permalink'] + + print('PL -> ', a['permalink']) + parent_message = slack_client.conversations_history(channel=user.slack_dm_channel_id, latest=thread_ts, inclusive=True, limit=1) + slack_client.chat_postMessage(text=f'<{pl}|LINK>', channel=user.slack_dm_channel_id) + for block in parent_message['messages'][0]['blocks']: if block['type'] == 'context': expense_id = block['block_id'] diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 7f939d6e..467bf93e 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -2,6 +2,7 @@ from typing import Callable, Dict from django.http import JsonResponse +from django.http.response import HttpResponseRedirect from django_q.tasks import async_task diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 7a44c561..c4ee8be2 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,11 +1,12 @@ import json - from typing import Callable, Dict from django.http.response import JsonResponse +from fyle_slack_app.libs.utils import encode_state from fyle_slack_app.slack import utils as slack_utils + class ViewSubmissionHandler: _view_submission_handlers: Dict = {} @@ -13,7 +14,8 @@ class ViewSubmissionHandler: # Maps action_id with it's respective function def _initialize_view_submission_handlers(self): self._view_submission_handlers = { - 'create_expense': self.handle_create_expense + 'create_expense': self.handle_create_expense, + 'policy_violation': self.handle_policy_violation } @@ -48,89 +50,124 @@ def handle_view_submission(self, slack_payload: Dict, user_id: str, team_id: str def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str): - callback_id = slack_payload['view']['callback_id'] - if callback_id == 'create_expense': - form_values = slack_payload['view']['state']['values'] - print('REACHED CREATE EXPENSE -> ', form_values) - - expense_mapping = {} - custom_fields = [] - - for key, value in form_values.items(): - custom_field_mappings = {} - if 'custom_field' in key: - for inner_key, inner_value in value.items(): - if inner_value['type'] == 'static_select': - custom_field_mappings[inner_key] = inner_value['selected_option']['value'] - if inner_value['type'] == 'multi_static_select': - values_list = [] - for val in inner_value['selected_options']: - values_list.append(val['value']) - custom_field_mappings[inner_key] = values_list - elif inner_value['type'] == 'datepicker': - custom_field_mappings[inner_key] = inner_value['selected_date'] - elif inner_value['type'] == 'plain_text_input': - custom_field_mappings[inner_key] = inner_value['value'] - elif inner_value['type'] == 'checkboxes': - custom_field_mappings[inner_key] = False - if len(inner_value['selected_options']) > 0: - custom_field_mappings[inner_key] = True - - custom_fields.append(custom_field_mappings) - else: - for inner_key, inner_value in value.items(): - if inner_value['type'] == 'static_select': - expense_mapping[inner_key] = inner_value['selected_option']['value'] - if inner_value['type'] == 'multi_static_select': - values_list = [] - for val in inner_value['selected_options']: - values_list.append(val['value']) - expense_mapping[inner_key] = values_list - elif inner_value['type'] == 'datepicker': - expense_mapping[inner_key] = inner_value['selected_date'] - elif inner_value['type'] == 'plain_text_input': - expense_mapping[inner_key] = inner_value['value'] - elif inner_value['type'] == 'checkboxes': - expense_mapping[inner_key] = False - if len(inner_value['selected_options']) > 0: - expense_mapping[inner_key] = True - - expense_mapping['custom_fields'] = custom_fields - - print('EXPENSE -> ', json.dumps(expense_mapping, indent=2)) - - slack_client = slack_utils.get_slack_client(team_id) - blocks = [ - { - 'type': 'section', - 'text': { - 'type': 'plain_text', - 'text': 'Expense created successfully :clipboard:', - 'emoji': True, - }, - }, - { - 'type': 'section', - 'fields': [ - {'type': 'mrkdwn', 'text': '*Amount*: \n {} {}'.format(expense_mapping['currency'], expense_mapping['amount'])}, - {'type': 'mrkdwn', 'text': '*Merchant*: \n {}'.format(expense_mapping['merchant'])}, - ], + form_values = slack_payload['view']['state']['values'] + print('REACHED CREATE EXPENSE -> ', form_values) + + expense_mapping = {} + custom_fields = [] + + for key, value in form_values.items(): + custom_field_mappings = {} + if 'custom_field' in key: + for inner_key, inner_value in value.items(): + if inner_value['type'] == 'static_select': + custom_field_mappings[inner_key] = inner_value['selected_option']['value'] + if inner_value['type'] == 'multi_static_select': + values_list = [] + for val in inner_value['selected_options']: + values_list.append(val['value']) + custom_field_mappings[inner_key] = values_list + elif inner_value['type'] == 'datepicker': + custom_field_mappings[inner_key] = inner_value['selected_date'] + elif inner_value['type'] == 'plain_text_input': + custom_field_mappings[inner_key] = inner_value['value'] + elif inner_value['type'] == 'checkboxes': + custom_field_mappings[inner_key] = False + if len(inner_value['selected_options']) > 0: + custom_field_mappings[inner_key] = True + + custom_fields.append(custom_field_mappings) + else: + for inner_key, inner_value in value.items(): + if inner_value['type'] == 'static_select': + expense_mapping[inner_key] = inner_value['selected_option']['value'] + if inner_value['type'] == 'multi_static_select': + values_list = [] + for val in inner_value['selected_options']: + values_list.append(val['value']) + expense_mapping[inner_key] = values_list + elif inner_value['type'] == 'datepicker': + expense_mapping[inner_key] = inner_value['selected_date'] + elif inner_value['type'] == 'plain_text_input': + expense_mapping[inner_key] = inner_value['value'] + elif inner_value['type'] == 'checkboxes': + expense_mapping[inner_key] = False + if len(inner_value['selected_options']) > 0: + expense_mapping[inner_key] = True + + expense_mapping['custom_fields'] = custom_fields + + print('EXPENSE -> ', json.dumps(expense_mapping, indent=2)) + + slack_client = slack_utils.get_slack_client(team_id) + em = { + 'a': expense_mapping, + 'b': expense_mapping, + 'c': expense_mapping + } + # policy_view = { + # "type": "modal", + # "private_metadata": encode_state(em), + # "callback_id": "policy_violation", + # "title": {"type": "plain_text", "text": "Policy Violation", "emoji": True}, + # "submit": {"type": "plain_text", "text": "Add Reason", "emoji": True}, + # "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, + # "blocks": [ + # { + # "type": "input", + # "block_id": "amount_block", + # "element": { + # "type": "plain_text_input", + # "placeholder": { + # "type": "plain_text", + # "text": "Enter Reason", + # "emoji": True, + # }, + # "action_id": "amount", + # }, + # "label": {"type": "plain_text", "text": "Enter reason", "emoji": True}, + # } + # ] + # } + # print('TRIGGER ID -> ', slack_payload['trigger_id']) + # # a = slack_client.views_push(trigger_id=slack_payload['trigger_id'], view=policy_view) + # # print('A -> ', a) + # return JsonResponse({ + # 'response_action': 'push', + # 'view': policy_view + # }, status=200) + blocks = [ + { + 'type': 'section', + 'text': { + 'type': 'plain_text', + 'text': 'Expense created successfully :clipboard:', + 'emoji': True, }, - { - 'type': 'section', - 'fields': [ - {'type': 'mrkdwn', 'text': '*Date of Spend*: \n {}'.format(expense_mapping['spent_at'])}, - {'type': 'mrkdwn', 'text': '*Purpose*: \n {}'.format(expense_mapping['purpose'])}, - ], - } - ] - - cf_section = { + }, + { + 'type': 'section', + 'fields': [ + {'type': 'mrkdwn', 'text': '*Amount*: \n {} {}'.format(expense_mapping['currency'], expense_mapping['amount'])}, + {'type': 'mrkdwn', 'text': '*Merchant*: \n {}'.format(expense_mapping['merchant'])}, + ], + }, + { 'type': 'section', - 'fields': [] + 'fields': [ + {'type': 'mrkdwn', 'text': '*Date of Spend*: \n {}'.format(expense_mapping['spent_at'])}, + {'type': 'mrkdwn', 'text': '*Purpose*: \n {}'.format(expense_mapping['purpose'])}, + ], } + ] + cf_section = { + 'type': 'section', + 'fields': [] + } + + if len(custom_fields) > 0: for custom_field in custom_fields: for cf in custom_field.keys(): if isinstance(custom_field[cf], list): @@ -144,35 +181,42 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) ) blocks.append(cf_section) - blocks.append( - { - 'type': 'actions', - 'elements': [ - { - 'type': 'button', - 'text': { - 'type': 'plain_text', - 'text': 'Edit Expense', - 'emoji': True, - }, - 'value': 'tx123456', - 'action_id': 'edit_expense', - } - ], - } - ) - blocks.append( - { - 'type': 'context', - 'block_id': 'tx123456', - 'elements': [ - { + blocks.append( + { + 'type': 'actions', + 'elements': [ + { + 'type': 'button', + 'text': { 'type': 'plain_text', - 'text': 'Powered by Fyle', + 'text': 'Edit Expense', 'emoji': True, - } - ], - } - ) - slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=blocks) + }, + 'value': 'tx123456', + 'action_id': 'edit_expense', + } + ], + } + ) + blocks.append( + { + 'type': 'context', + 'block_id': 'tx123456', + 'elements': [ + { + 'type': 'plain_text', + 'text': 'Powered by Fyle', + 'emoji': True, + } + ], + } + ) + slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=blocks) return JsonResponse({}) + + + def handle_policy_violation(self, slack_payload: Dict, user_id: str, team_id: str): + print('POLICY PAYLOAD -> ', slack_payload) + return JsonResponse({ + 'response_action': 'clear' + }) diff --git a/fyle_slack_app/slack/interactives/views.py b/fyle_slack_app/slack/interactives/views.py index a16e6c08..852c604b 100644 --- a/fyle_slack_app/slack/interactives/views.py +++ b/fyle_slack_app/slack/interactives/views.py @@ -14,7 +14,7 @@ class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler, ViewS def post(self, request: HttpRequest) -> JsonResponse: payload = request.POST.get('payload') slack_payload = json.loads(payload) - print('SLACK PAYLOAD -> ', slack_payload) + # Extract details from payload user_id = slack_payload['user']['id'] team_id = slack_payload['team']['id'] diff --git a/fyle_slack_app/slack/ui/authorization/messages.py b/fyle_slack_app/slack/ui/authorization/messages.py index 9f764255..41660029 100644 --- a/fyle_slack_app/slack/ui/authorization/messages.py +++ b/fyle_slack_app/slack/ui/authorization/messages.py @@ -111,6 +111,7 @@ def get_post_authorization_message() -> List[Dict]: { "type": "button", "style": "primary", + "url": "slack://app?id=ARHATN286&team=TAQR2JNTF&tab=messages", "text": { "type": "plain_text", "text": "Approve", @@ -120,6 +121,7 @@ def get_post_authorization_message() -> List[Dict]: }, { "type": "button", + "url": "https://demo-bot-fyle.slack.com/archives/D01K1L9UHBP/p1630583475001000?thread_ts=1630583475.001000&cid=D01K1L9UHBP", "text": { "type": "plain_text", "text": "Review in Fyle", diff --git a/fyle_slack_service/settings.py b/fyle_slack_service/settings.py index bc2a6459..4028e218 100644 --- a/fyle_slack_service/settings.py +++ b/fyle_slack_service/settings.py @@ -89,6 +89,14 @@ } } +# Cache +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', + 'LOCATION': 'slack_cache', + } +} + # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators diff --git a/run.sh b/run.sh index 7d07454b..8e6bae0b 100644 --- a/run.sh +++ b/run.sh @@ -1,2 +1,3 @@ python manage.py migrate +python manage.py createcachetable gunicorn -c gunicorn_config.py fyle_slack_service.wsgi -b 0.0.0.0:8000 \ No newline at end of file From 0d58d6b017999b46b5b72c5e1495f0a57aff27d3 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Fri, 17 Sep 2021 17:49:09 +0530 Subject: [PATCH 17/85] Added initial expense form interaction --- fyle_slack_app/admin.py | 2185 ----------------- fyle_slack_app/fyle/expenses/__init__.py | 0 fyle_slack_app/fyle/expenses/views.py | 27 + fyle_slack_app/fyle/utils.py | 13 +- fyle_slack_app/slack/commands/handlers.py | 59 +- fyle_slack_app/slack/commands/views.py | 2 - .../interactives/block_action_handlers.py | 106 +- .../interactives/block_suggestion_handlers.py | 85 + fyle_slack_app/slack/interactives/views.py | 47 +- fyle_slack_app/slack/ui/dashboard/messages.py | 379 +-- fyle_slack_app/slack/ui/expenses/__init__.py | 0 fyle_slack_app/slack/ui/expenses/messages.py | 346 +++ fyle_slack_service/settings.py | 8 - run.sh | 1 - 14 files changed, 596 insertions(+), 2662 deletions(-) create mode 100644 fyle_slack_app/fyle/expenses/__init__.py create mode 100644 fyle_slack_app/fyle/expenses/views.py create mode 100644 fyle_slack_app/slack/interactives/block_suggestion_handlers.py create mode 100644 fyle_slack_app/slack/ui/expenses/__init__.py create mode 100644 fyle_slack_app/slack/ui/expenses/messages.py diff --git a/fyle_slack_app/admin.py b/fyle_slack_app/admin.py index b5300c79..e69de29b 100644 --- a/fyle_slack_app/admin.py +++ b/fyle_slack_app/admin.py @@ -1,2185 +0,0 @@ -expense_fields = [ - { - "id": 39, - "org_id": "orrjqbDbeP9p", - "column_name": "boolean_column7", - "field_name": "Test BOOLEAN", - "seq": 1, - "type": "BOOLEAN", - "is_custom": True, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "rfve", - "default_value": None, - "options": [], - "org_category_ids": [136518], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 83, - "created_at": "2017-04-06T04:52:07.825Z", - "org_id": "orrjqbDbeP9p", - "column_name": "text_array_column3", - "field_name": "Multi Type", - "seq": 1, - "type": "MULTI_SELECT", - "is_custom": True, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Multi Type", - "default_value": None, - "options": ["Multi 1", "Multi 2", "Multi 3"], - "org_category_ids": [136250], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6182, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "bus_travel_class", - "field_name": "Travel Class", - "seq": 1, - "type": "TEXT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Enter class of travel", - "default_value": None, - "options": [], - "org_category_ids": [136517], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6183, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "cost_center_id", - "field_name": "Cost Center", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Cost Center", - "default_value": None, - "options": [], - "org_category_ids": [ - 136506, - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136514, - 136515, - 136516, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531, - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6184, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "distance", - "field_name": "Distance", - "seq": 1, - "type": "NUMBER", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Enter Distance", - "default_value": None, - "options": [], - "org_category_ids": [136514], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6185, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "distance", - "field_name": "Distance", - "seq": 2, - "type": "NUMBER", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Enter Distance", - "default_value": None, - "options": [], - "org_category_ids": [136519], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6186, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "distance_unit", - "field_name": "Unit", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Unit", - "default_value": "MILES", - "options": ["KM", "MILES"], - "org_category_ids": [136514], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6187, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "distance_unit", - "field_name": "Unit", - "seq": 2, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Unit", - "default_value": "MILES", - "options": ["KM", "MILES"], - "org_category_ids": [136519], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6188, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "flight_journey_travel_class", - "field_name": "Onward Travel Class", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select travel class", - "default_value": None, - "options": ["BUSINESS", "ECONOMY", "FIRST_CLASS"], - "org_category_ids": [136525], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6189, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "flight_return_travel_class", - "field_name": "Return Travel Class", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select travel class", - "default_value": None, - "options": ["BUSINESS", "ECONOMY", "FIRST_CLASS"], - "org_category_ids": [136525], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6190, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "from_dt", - "field_name": "From", - "seq": 1, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [136516], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6191, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "from_dt", - "field_name": "Onward Date", - "seq": 2, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [136507, 136517, 136525], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6192, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "from_dt", - "field_name": "Check-in Date", - "seq": 3, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [136521], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6193, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "location1", - "field_name": "From", - "seq": 1, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select a City", - "default_value": None, - "options": [], - "org_category_ids": [136507, 136517, 136525], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6194, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "location1", - "field_name": "City", - "seq": 2, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select a City", - "default_value": None, - "options": [], - "org_category_ids": [136521], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6195, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "location1", - "field_name": "From", - "seq": 3, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Choose Location - Search for Place or Area", - "default_value": None, - "options": [], - "org_category_ids": [136514], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6196, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "location2", - "field_name": "To", - "seq": 1, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select a City", - "default_value": None, - "options": [], - "org_category_ids": [136507, 136517, 136525], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6197, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "location2", - "field_name": "To", - "seq": 2, - "type": "LOCATION", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Choose Location - Search for Place or Area", - "default_value": None, - "options": [], - "org_category_ids": [136514], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6198, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "num_days", - "field_name": "No. of Days", - "seq": 1, - "type": "NUMBER", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "No. of Days", - "default_value": None, - "options": [], - "org_category_ids": [136516], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6199, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "purpose", - "field_name": "Purpose", - "seq": 1, - "type": "TEXT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "E.g. Client Meeting", - "default_value": None, - "options": [], - "org_category_ids": [ - 136506, - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136514, - 136515, - 136516, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531, - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6200, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "to_dt", - "field_name": "To", - "seq": 1, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [136516], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6201, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "to_dt", - "field_name": "Return Date", - "seq": 2, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [136507, 136517, 136525], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6202, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "to_dt", - "field_name": "Check-out Date", - "seq": 3, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [136521], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6199, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "train_travel_class", - "field_name": "Travel class", - "seq": 1, - "type": "TEXT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Enter class of travel", - "default_value": None, - "options": [], - "org_category_ids": [136507], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6204, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "txn_dt", - "field_name": "Date of Spend", - "seq": 1, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [ - 136506, - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136515, - 136516, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531, - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6205, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "txn_dt", - "field_name": "Date of Travel", - "seq": 2, - "type": "DATE", - "is_custom": False, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "Select Date", - "default_value": None, - "options": [], - "org_category_ids": [136514], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 6206, - "created_at": "2021-04-22T07:46:07.007Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-04-22T07:48:35.072Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "vendor_id", - "field_name": "Merchant", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "E.g. Uber", - "default_value": None, - "options": [], - "org_category_ids": [ - 136507, - 136508, - 136509, - 136510, - 136511, - 136512, - 136513, - 136515, - 136517, - 136518, - 136519, - 136520, - 136521, - 136522, - 136523, - 136524, - 136525, - 136526, - 136527, - 136528, - 136529, - 136530, - 136531, - ], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 185323, - "created_at": "2021-07-28T08:48:55.152Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-07-28T08:48:55.152Z", - "updated_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "org_id": "orsReFb3Oou8", - "column_name": "project_id", - "field_name": "Project", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Select Project", - "default_value": None, - "options": [], - "org_category_ids": [], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 189621, - "created_at": "2021-07-29T11:05:38.811Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-07-29T11:05:38.811Z", - "updated_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "org_id": "orsReFb3Oou8", - "column_name": "billable", - "field_name": "Billable", - "seq": 1, - "type": "BOOLEAN", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Billable", - "default_value": "False", - "options": [], - "org_category_ids": [], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 194141, - "created_at": "2021-08-17T04:48:57.792Z", - "created_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "updated_at": "2021-08-17T04:48:57.792Z", - "updated_by": { - "user_id": "SYSTEM", - "org_user_id": None, - "org_id": None, - "roles": [], - "scopes": [], - "allowed_CIDRs": None, - "cluster_domain": None, - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": None, - }, - "org_id": "orsReFb3Oou8", - "column_name": "tax_group", - "field_name": "Tax Group", - "seq": 1, - "type": "SELECT", - "is_custom": False, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "Tax Group", - "default_value": None, - "options": [], - "org_category_ids": [], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 195217, - "created_at": "2021-08-18T07:35:59.906Z", - "created_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "updated_at": "2021-08-18T07:35:59.906Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "text_column1", - "field_name": "TEST 1", - "seq": 1, - "type": "TEXT", - "is_custom": True, - "is_enabled": True, - "is_mandatory": True, - "placeholder": "testiing", - "default_value": None, - "options": [], - # "org_category_ids": [136250, 136518], - For two custom fields - "org_category_ids": [136250], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, - { - "id": 195510, - "created_at": "2021-08-23T13:18:38.309Z", - "created_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "updated_at": "2021-08-23T13:18:38.309Z", - "updated_by": { - "user_id": "usuTaTN12iqG", - "org_user_id": "ouZH5KbJaBFq", - "org_id": "orsReFb3Oou8", - "roles": ["OWNER", "FYLER", "ADMIN", "APPROVER"], - "scopes": [], - "allowed_CIDRs": [], - "cluster_domain": '"https://staging.fyle.tech"', - "proxy_org_user_id": None, - "tpa_id": None, - "tpa_name": None, - "name": "ouZH5KbJaBFq", - }, - "org_id": "orsReFb3Oou8", - "column_name": "text_column2", - "field_name": "Test select field", - "seq": 1, - "type": "SELECT", - "is_custom": True, - "is_enabled": True, - "is_mandatory": False, - "placeholder": "testing select fields", - "default_value": None, - "options": ["test choice 1", "test choice 2"], - "org_category_ids": [136518], - "code": None, - "roles_editable": [ - "FYLER", - "APPROVER", - "TRAVEL_ADMIN", - "VERIFIER", - "PAYMENT_PROCESSOR", - "FINANCE", - "ADMIN", - "TRAVEL_AGENT", - "OWNER", - "AUDITOR", - ], - }, -] - -categories = [ - { - "id": 835229, - "created_at": "2019-02-13T08:24:43.405Z", - "updated_at": "2019-02-13T08:24:44.405Z", - "org_id": "orKPte3WEwuJ", - "name": "Activity", - "code": None, - "fyle_category": "Activity", - "sub_category": "Activity", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835240, - "created_at": "2019-02-13T08:24:43.463Z", - "updated_at": "2019-02-13T08:24:44.463Z", - "org_id": "orKPte3WEwuJ", - "name": "Bus", - "code": None, - "fyle_category": "Bus", - "sub_category": "Bus", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835243, - "created_at": "2019-02-13T08:24:43.476Z", - "updated_at": "2019-02-13T08:24:44.476Z", - "org_id": "orKPte3WEwuJ", - "name": "Courier", - "code": None, - "fyle_category": "Courier", - "sub_category": "Courier", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835235, - "created_at": "2019-02-13T08:24:43.441Z", - "updated_at": "2019-02-13T08:24:44.441Z", - "org_id": "orKPte3WEwuJ", - "name": "Entertainment", - "code": None, - "fyle_category": "Entertainment", - "sub_category": "Entertainment", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835248, - "created_at": "2019-02-13T08:24:43.498Z", - "updated_at": "2019-02-13T08:24:44.498Z", - "org_id": "orKPte3WEwuJ", - "name": "Flight", - "code": None, - "fyle_category": "Flight", - "sub_category": "Flight", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835238, - "created_at": "2019-02-13T08:24:43.455Z", - "updated_at": "2019-02-13T08:24:44.455Z", - "org_id": "orKPte3WEwuJ", - "name": "Food", - "code": None, - "fyle_category": "Food", - "sub_category": "Food", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835231, - "created_at": "2019-02-13T08:24:43.413Z", - "updated_at": "2019-02-13T08:24:44.413Z", - "org_id": "orKPte3WEwuJ", - "name": "Fuel", - "code": None, - "fyle_category": "Fuel", - "sub_category": "Fuel", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835244, - "created_at": "2019-02-13T08:24:43.481Z", - "updated_at": "2019-02-13T08:24:44.481Z", - "org_id": "orKPte3WEwuJ", - "name": "Hotel", - "code": None, - "fyle_category": "Hotel", - "sub_category": "Hotel", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835241, - "created_at": "2019-02-13T08:24:43.468Z", - "updated_at": "2019-02-13T08:24:44.468Z", - "org_id": "orKPte3WEwuJ", - "name": "Internet", - "code": None, - "fyle_category": "Internet", - "sub_category": "Internet", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835237, - "created_at": "2019-02-13T08:24:43.451Z", - "updated_at": "2019-02-13T08:24:44.451Z", - "org_id": "orKPte3WEwuJ", - "name": "Mileage", - "code": None, - "fyle_category": "Mileage", - "sub_category": "Mileage", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835247, - "created_at": "2019-02-13T08:24:43.494Z", - "updated_at": "2019-02-13T08:24:44.494Z", - "org_id": "orKPte3WEwuJ", - "name": "Office Party", - "code": None, - "fyle_category": "Office Party", - "sub_category": "Office Party", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835233, - "created_at": "2019-02-13T08:24:43.421Z", - "updated_at": "2019-02-13T08:24:44.421Z", - "org_id": "orKPte3WEwuJ", - "name": "Office Supplies", - "code": None, - "fyle_category": "Office Supplies", - "sub_category": "Office Supplies", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835236, - "created_at": "2019-02-13T08:24:43.447Z", - "updated_at": "2019-02-13T08:24:44.447Z", - "org_id": "orKPte3WEwuJ", - "name": "Others", - "code": None, - "fyle_category": "Others", - "sub_category": "Others", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835250, - "created_at": "2019-02-13T08:24:43.507Z", - "updated_at": "2019-02-13T08:24:44.507Z", - "org_id": "orKPte3WEwuJ", - "name": "Parking", - "code": None, - "fyle_category": "Parking", - "sub_category": "Parking", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835239, - "created_at": "2019-02-13T08:24:43.459Z", - "updated_at": "2019-02-13T08:24:44.459Z", - "org_id": "orKPte3WEwuJ", - "name": "Per Diem", - "code": None, - "fyle_category": "Per Diem", - "sub_category": "Per Diem", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835246, - "created_at": "2019-02-13T08:24:43.489Z", - "updated_at": "2019-02-13T08:24:44.489Z", - "org_id": "orKPte3WEwuJ", - "name": "Phone", - "code": None, - "fyle_category": "Phone", - "sub_category": "Phone", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835245, - "created_at": "2019-02-13T08:24:43.485Z", - "updated_at": "2019-02-13T08:24:44.485Z", - "org_id": "orKPte3WEwuJ", - "name": "Professional Services", - "code": None, - "fyle_category": "Professional Services", - "sub_category": "Professional Services", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835232, - "created_at": "2019-02-13T08:24:43.417Z", - "updated_at": "2019-02-13T08:24:44.417Z", - "org_id": "orKPte3WEwuJ", - "name": "Snacks", - "code": None, - "fyle_category": "Snacks", - "sub_category": "Snacks", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835249, - "created_at": "2019-02-13T08:24:43.502Z", - "updated_at": "2019-02-13T08:24:44.502Z", - "org_id": "orKPte3WEwuJ", - "name": "Software", - "code": None, - "fyle_category": "Software", - "sub_category": "Software", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835252, - "created_at": "2019-02-13T08:24:43.515Z", - "updated_at": "2019-02-13T08:24:44.515Z", - "org_id": "orKPte3WEwuJ", - "name": "Tax", - "code": None, - "fyle_category": "Tax", - "sub_category": "Tax", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835242, - "created_at": "2019-02-13T08:24:43.472Z", - "updated_at": "2019-02-13T08:24:44.472Z", - "org_id": "orKPte3WEwuJ", - "name": "Taxi", - "code": None, - "fyle_category": "Taxi", - "sub_category": "Taxi", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835251, - "created_at": "2019-02-13T08:24:43.511Z", - "updated_at": "2019-02-13T08:24:44.511Z", - "org_id": "orKPte3WEwuJ", - "name": "Toll Charge", - "code": None, - "fyle_category": "Toll Charge", - "sub_category": "Toll Charge", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835230, - "created_at": "2019-02-13T08:24:43.409Z", - "updated_at": "2019-02-13T08:24:44.409Z", - "org_id": "orKPte3WEwuJ", - "name": "Train", - "code": None, - "fyle_category": "Train", - "sub_category": "Train", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835253, - "created_at": "2019-02-13T08:24:43.522Z", - "updated_at": "2019-02-13T08:24:44.522Z", - "org_id": "orKPte3WEwuJ", - "name": "Training", - "code": None, - "fyle_category": "Training", - "sub_category": "Training", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835254, - "created_at": "2019-02-13T08:24:43.526Z", - "updated_at": "2019-02-13T08:24:44.526Z", - "org_id": "orKPte3WEwuJ", - "name": "Unspecified", - "code": None, - "fyle_category": "Unspecified", - "sub_category": "Unspecified", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, - { - "id": 835234, - "created_at": "2019-02-13T08:24:43.426Z", - "updated_at": "2019-02-13T08:24:44.426Z", - "org_id": "orKPte3WEwuJ", - "name": "Utility", - "code": None, - "fyle_category": "Utility", - "sub_category": "Utility", - "enabled": True, - "creator_id": None, - "last_updated_by": None, - }, -] diff --git a/fyle_slack_app/fyle/expenses/__init__.py b/fyle_slack_app/fyle/expenses/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py new file mode 100644 index 00000000..0f620172 --- /dev/null +++ b/fyle_slack_app/fyle/expenses/views.py @@ -0,0 +1,27 @@ +from typing import Dict + +from fyle_slack_app.fyle.utils import get_fyle_sdk_connection +from fyle_slack_app.models.users import User + + +class FyleExpense: + + @staticmethod + def get_expense_fields(user: User, query_params: Dict) -> Dict: + connection = get_fyle_sdk_connection(user.fyle_refresh_token) + expense_fields = connection.v1.fyler.expense_fields.list(query_params=query_params) + return expense_fields + + + @staticmethod + def get_categories(user: User, query_params: Dict) -> Dict: + connection = get_fyle_sdk_connection(user.fyle_refresh_token) + categories = connection.v1.fyler.categories.list(query_params=query_params) + return categories + + + @staticmethod + def get_projects(user: User, query_params: Dict) -> Dict: + connection = get_fyle_sdk_connection(user.fyle_refresh_token) + projects = connection.v1.fyler.projects.list(query_params=query_params) + return projects diff --git a/fyle_slack_app/fyle/utils.py b/fyle_slack_app/fyle/utils.py index c046a905..99fd5ace 100644 --- a/fyle_slack_app/fyle/utils.py +++ b/fyle_slack_app/fyle/utils.py @@ -5,7 +5,6 @@ from fyle.platform import Platform from django.conf import settings -from django.core.cache import cache from fyle_slack_app.libs import http, assertions, utils from fyle_slack_app.models.user_subscription_details import SubscriptionType @@ -77,16 +76,8 @@ def get_fyle_refresh_token(code: str) -> str: def get_fyle_profile(refresh_token: str) -> Dict: - fyle_profile = cache.get('abcd') - print('CACHE PROFILE -> ', fyle_profile) - if fyle_profile is None: - print('CACHE MISS') - connection = get_fyle_sdk_connection(refresh_token) - fyle_profile_response = connection.v1.fyler.my_profile.get() - cache.set('abcd', fyle_profile_response) - else: - print('CACHE HIT') - fyle_profile_response = fyle_profile + connection = get_fyle_sdk_connection(refresh_token) + fyle_profile_response = connection.v1.fyler.my_profile.get() return fyle_profile_response['data'] diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index b597735a..f1c2c523 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -5,15 +5,16 @@ from django_q.tasks import async_task from fyle.platform import exceptions -from slack_sdk.web.client import WebClient -from fyle_slack_app import tracking +from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.libs import utils, assertions, logger from fyle_slack_app.fyle.utils import get_fyle_oauth_url, get_fyle_profile from fyle_slack_app.models import User, NotificationPreference from fyle_slack_app.slack.ui.dashboard import messages as dashboard_messages from fyle_slack_app.slack.ui.notifications import preference_messages as notification_preference_messages +from fyle_slack_app.slack.ui.expenses import messages as expense_messages from fyle_slack_app.slack import utils as slack_utils +from fyle_slack_app import tracking logger = logger.get_logger(__name__) @@ -27,10 +28,10 @@ def _initialize_command_handlers(self): self._command_handlers = { 'fyle_unlink_account': self.handle_fyle_unlink_account, 'fyle_notification_preferences': self.handle_fyle_notification_preferences, - 'modal': self.modal_test + 'expense_form': self.handle_expense_form } - def handle_invalid_command(self, user_id: str, team_id: str, user_dm_channel_id: str) -> JsonResponse: + def handle_invalid_command(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str) -> JsonResponse: slack_client = slack_utils.get_slack_client(team_id) slack_client.chat_postMessage( @@ -40,7 +41,7 @@ def handle_invalid_command(self, user_id: str, team_id: str, user_dm_channel_id: return JsonResponse({}, status=200) - def handle_slack_command(self, command: str, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id) -> Callable: + def handle_slack_command(self, command: str, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str) -> Callable: # Initialize slack command handlers self._initialize_command_handlers() @@ -50,7 +51,7 @@ def handle_slack_command(self, command: str, user_id: str, team_id: str, user_dm return handler(user_id, team_id, user_dm_channel_id, trigger_id) - def handle_fyle_unlink_account(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id) -> JsonResponse: + def handle_fyle_unlink_account(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str) -> JsonResponse: async_task( 'fyle_slack_app.slack.commands.tasks.fyle_unlink_account', user_id, @@ -76,7 +77,7 @@ def update_home_tab_with_pre_auth_message(self, user_id: str, team_id: str) -> N slack_client.views_publish(user_id=user_id, view=pre_auth_message_view) - def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id) -> JsonResponse: + def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str) -> JsonResponse: user = utils.get_or_none(User, slack_user_id=user_id) assertions.assert_found(user, 'Slack user not found') @@ -100,6 +101,40 @@ def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_ return JsonResponse({}, status=200) + def handle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str): + user = utils.get_or_none(User, slack_user_id=user_id) + assertions.assert_found(user) + + slack_client = slack_utils.get_slack_client(team_id) + + expense_fields_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'column_name': 'in.(purpose, txn_dt, vendor_id, cost_center_id, project_id)', + 'is_enabled': 'eq.{}'.format(True), + 'is_custom': 'eq.{}'.format(False), + 'is_mandatory': 'eq.{}'.format(True), + } + + expense_fields = FyleExpense.get_expense_fields(user, expense_fields_query_params) + + projects_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + projects = FyleExpense.get_projects(user, projects_query_params) + print('projects -> ', projects) + modal = expense_messages.expense_dialog_form(projects=projects, expense_fields=expense_fields) + + slack_client.views_open(user_id=user_id, view=modal, trigger_id=trigger_id) + + return JsonResponse({}, status=200) + + def track_fyle_account_unlinked(self, user: User) -> None: event_data = { 'asset': 'SLACK_APP', @@ -113,13 +148,3 @@ def track_fyle_account_unlinked(self, user: User) -> None: tracking.identify_user(user.email) tracking.track_event(user.email, 'Fyle Account Unlinked From Slack', event_data) - - - def modal_test(self, user_id, team_id, user_dm_channel_id, trigger_id): - slack_client: WebClient = slack_utils.get_slack_client(team_id) - - modal = dashboard_messages.expense_dialog_form() - - slack_client.views_open(user_id=user_id, view=modal, trigger_id=trigger_id) - - return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/commands/views.py b/fyle_slack_app/slack/commands/views.py index 37b97e2c..8942c3bc 100644 --- a/fyle_slack_app/slack/commands/views.py +++ b/fyle_slack_app/slack/commands/views.py @@ -12,8 +12,6 @@ def post(self, request: HttpRequest, command: str) -> HttpResponse: user_dm_channel_id = request.POST['channel_id'] trigger_id = request.POST['trigger_id'] - print('REQIEST -> ', request.POST) - self.handle_slack_command(command, user_id, team_id, user_dm_channel_id, trigger_id) # Empty "" HttpResponse beacause for slash commands slack return the response as message to user diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 41cd2e2c..86537358 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -2,16 +2,15 @@ from typing import Callable, Dict from django.http import JsonResponse -from django.http.response import HttpResponseRedirect from django_q.tasks import async_task from fyle_slack_app.models import User, NotificationPreference from fyle_slack_app.models.notification_preferences import NotificationType from fyle_slack_app.libs import assertions, utils, logger +from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_user_dm_channel_id, get_slack_client -from fyle_slack_app.slack.ui.dashboard.messages import generate_category_field_mapping, expense_dialog_form -from fyle_slack_app.admin import expense_fields +from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form from fyle_slack_app import tracking @@ -40,11 +39,72 @@ def _initialize_block_action_handlers(self): 'expense_commented_notification_preference': self.handle_notification_preference_selection, # Dynamic options - 'category': self.category_select, + 'category': self.handle_category_select, + 'project': self.handle_project_select } - def category_select(self, slack_payload: Dict, user_id: str, team_id: str): + def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + print('SP -> ', json.dumps(slack_payload, indent=2)) + + slack_client = get_slack_client(team_id) + + project_id = slack_payload['actions'][0]['selected_option']['value'] + + view_id = slack_payload['container']['view_id'] + + user = utils.get_or_none(User, slack_user_id=user_id) + + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } + + project = FyleExpense.get_projects(user, project_query_params) + + print('PROJECT -> ', project) + + query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True), + 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) + } + + categories = FyleExpense.get_categories(user, query_params) + + expense_fields_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'or': '(is_mandatory.eq.{}, and(is_custom.eq.{}, is_mandatory.eq.{}))'.format(True, True, True), + 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', + 'is_enabled': 'eq.{}'.format(True), + } + + expense_fields = FyleExpense.get_expense_fields(user, expense_fields_query_params) + + projects_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + projects = FyleExpense.get_projects(user, projects_query_params) + + new_expense_dialog_form = expense_dialog_form(expense_fields=expense_fields, projects=projects, categories=categories) + + slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) + + return JsonResponse({}) + + + def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: slack_client = get_slack_client(team_id) @@ -52,23 +112,35 @@ def category_select(self, slack_payload: Dict, user_id: str, team_id: str): view_id = slack_payload['container']['view_id'] - mappings = generate_category_field_mapping(expense_fields) + user = utils.get_or_none(User, slack_user_id=user_id) - extra_fields = [] - default_fields = ['purpose', 'txn_dt', 'vendor_id', 'cost_center_id'] + expense_fields_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'or': '(is_mandatory.eq.{}, and(is_custom.eq.{}, is_mandatory.eq.{}))'.format(True, True, True), + 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', + 'is_enabled': 'eq.{}'.format(True), + 'category_ids': 'cs.[{}]'.format(int(category_id)) + } - for key, value in mappings.items(): - if key not in default_fields: - for val in value: - if int(category_id) in val['category_ids']: - extra_fields.append(val) + expense_fields = FyleExpense.get_expense_fields(user, expense_fields_query_params) + + print('expense fields -> ', json.dumps(expense_fields, indent=2)) + + projects_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } - print('EF -> ', json.dumps(extra_fields, indent=2)) + projects = FyleExpense.get_projects(user, projects_query_params) - if len(extra_fields) > 0: - new_expense_dialog_form = expense_dialog_form(extra_fields) + if expense_fields['count'] > 0: + new_expense_dialog_form = expense_dialog_form(expense_fields=expense_fields, custom_fields=expense_fields['data'], projects=projects) else: - new_expense_dialog_form = expense_dialog_form() + new_expense_dialog_form = expense_dialog_form(expense_fields=expense_fields, projects=projects) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py new file mode 100644 index 00000000..0ec7d8c7 --- /dev/null +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -0,0 +1,85 @@ +import json + +from typing import Dict, List + +from django.http import JsonResponse + +from fyle_slack_app.models.users import User +from fyle_slack_app.fyle.expenses.views import FyleExpense +from fyle_slack_app.libs import logger, utils +from fyle_slack_app.slack import utils as slack_utils + + +logger = logger.get_logger(__name__) + + +class BlockSuggestionHandler: + + _block_suggestion_handlers: Dict = {} + + # Maps action_id with it's respective function + def _initialize_block_suggestion_handlers(self): + self._block_suggestion_handlers = { + 'category': self.handle_category_suggestion + } + + + # Gets called when function with an action is not found + def _handle_invalid_block_suggestions(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + slack_client = slack_utils.get_slack_client(team_id) + + user_dm_channel_id = slack_utils.get_slack_user_dm_channel_id(slack_client, user_id) + slack_client.chat_postMessage( + channel=user_dm_channel_id, + text='Looks like something went wrong :zipper_mouth_face: \n Please try again' + ) + return JsonResponse({}, status=200) + + + # Handle all the block_suggestions from slack + def handle_block_suggestions(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + ''' + Check if any function is associated with the action + If present handler will call the respective function + If not present call `handle_invalid_block_suggestions` to send a prompt to user + ''' + + # Initialize handlers + self._initialize_block_suggestion_handlers() + + action_id = slack_payload['action_id'] + + handler = self._block_suggestion_handlers.get(action_id, self._handle_invalid_block_suggestions) + + options = handler(slack_payload, user_id, team_id) + + return JsonResponse({'options': options}) + + + def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + print('SLACK PAYLOAD -> ', json.dumps(slack_payload, indent=2)) + user = utils.get_or_none(User, slack_user_id=user_id) + category_value_entered = slack_payload['value'] + query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'display_name.asc', + 'display_name': 'ilike.%{}%'.format(category_value_entered), + 'is_enabled': 'eq.{}'.format(True) + } + suggested_categories = FyleExpense.get_categories(user, query_params) + + category_options = [] + if suggested_categories['count'] > 0: + for category in suggested_categories['data']: + option = { + 'text': { + 'type': 'plain_text', + 'text': category['display_name'], + 'emoji': True, + }, + 'value': str(category['id']), + } + category_options.append(option) + + return category_options diff --git a/fyle_slack_app/slack/interactives/views.py b/fyle_slack_app/slack/interactives/views.py index 852c604b..11160cd8 100644 --- a/fyle_slack_app/slack/interactives/views.py +++ b/fyle_slack_app/slack/interactives/views.py @@ -1,5 +1,4 @@ import json -from os import lchown from django.http import HttpRequest, JsonResponse @@ -7,9 +6,10 @@ from fyle_slack_app.slack.interactives.block_action_handlers import BlockActionHandler from fyle_slack_app.slack.interactives.shortcut_handlers import ShortcutHandler from fyle_slack_app.slack.interactives.view_submission_handlers import ViewSubmissionHandler +from fyle_slack_app.slack.interactives.block_suggestion_handlers import BlockSuggestionHandler -class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler, ViewSubmissionHandler): +class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler, ViewSubmissionHandler, BlockSuggestionHandler): def post(self, request: HttpRequest) -> JsonResponse: payload = request.POST.get('payload') @@ -36,47 +36,6 @@ def post(self, request: HttpRequest) -> JsonResponse: elif event_type == 'block_suggestion': # Call handler function from BlockActionHandler - value = slack_payload['value'] - categories = [ - { - 'text': { - 'type': 'plain_text', - 'text': 'Custom Field Category', - 'emoji': True, - }, - 'value': '136250', - }, - { - 'text': { - 'type': 'plain_text', - 'text': 'Internet', - 'emoji': True, - }, - 'value': '136518', - }, - { - 'text': { - 'type': 'plain_text', - 'text': 'Office Supplies', - 'emoji': True, - }, - 'value': '1234', - }, - { - 'text': { - 'type': 'plain_text', - 'text': 'Stuff for office', - 'emoji': True, - }, - 'value': '1234', - }, - ] - - options = [] - for category in categories: - if value.lower() in category['text']['text'].lower(): - options.append(category) - - return JsonResponse({'options': options}) + return self.handle_block_suggestions(slack_payload, user_id, team_id) return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index fec404ab..6d9bdd40 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -1,9 +1,5 @@ -import json -from random import random from typing import Dict -import datetime - from fyle_slack_app.slack.ui.authorization import messages @@ -11,380 +7,9 @@ def get_pre_authorization_message(user_name: str, fyle_oauth_url: str) -> Dict: pre_authorization_message_blocks = messages.get_pre_authorization_message( user_name, fyle_oauth_url ) - return {"type": "home", "blocks": pre_authorization_message_blocks} + return {'type': 'home', 'blocks': pre_authorization_message_blocks} def get_post_authorization_message() -> Dict: post_authorization_message_blocks = messages.get_post_authorization_message() - return {"type": "home", "blocks": post_authorization_message_blocks} - - -def generate_category_field_mapping(expense_fields): - mapping = {} - for ef in expense_fields: - # for ci in ef['org_category_ids']: - # if ci not in mapping: - # mapping[ci] = [] - # if not any(ef['column_name'] in cd for cd in mapping[ci]): - # ci_details = { - # 'field_name': ef['field_name'], - # 'column_name': ef['column_name'], - # 'type': ef['type'], - # 'is_custom': ef['is_custom'], - # 'is_enabled': ef['is_enabled'], - # 'is_mandatory': ef['is_mandatory'], - # 'placeholder': ef['placeholder'], - # 'default_value': ef['default_value'], - # 'category_ids': ef['org_category_ids'] - # } - # if ef['type'] == 'SELECT': - # ci_details['options'] = ef['options'] - # mapping[ci].append(ci_details) - - if ef["column_name"] not in mapping: - mapping[ef["column_name"]] = [] - - ci_details = { - "field_name": ef["field_name"], - "column_name": ef["column_name"], - "type": ef["type"], - "is_custom": ef["is_custom"], - "is_enabled": ef["is_enabled"], - "is_mandatory": ef["is_mandatory"], - "placeholder": ef["placeholder"], - "default_value": ef["default_value"], - "category_ids": ef["org_category_ids"], - } - if ef["type"] in ["SELECT", "MULTI_SELECT"]: - ci_details["options"] = ef["options"] - mapping[ef["column_name"]].append(ci_details) - - return mapping - - -def expense_dialog_form(extra_fields=None): - # blocks = [] - # mappings = generate_category_field_mapping(expense_fields) - # for expense_field in expense_fields: - # if expense_field["type"] in ["NUMBER", "TEXT"]: - # block = { - # "type": "input", - # # "block_id": "{}".format(random), - # "label": { - # "type": "plain_text", - # "text": "{}".format(expense_field["field_name"]), - # }, - # "element": { - # "type": "plain_text_input", - # "action_id": "{}".format(expense_field["field_name"].lower()), - # "placeholder": { - # "type": "plain_text", - # "text": "{}".format(expense_field["placeholder"]), - # }, - # }, - # } - # elif expense_field["type"] == "SELECT": - # block = { - # "type": "input", - # # "block_id": "{}".format(uuid.uuid4()), - # "label": { - # "type": "plain_text", - # "text": "{}".format(expense_field["field_name"]), - # }, - # "element": { - # "type": "static_select", - # "action_id": "{}".format(expense_field["field_name"].lower()), - # "placeholder": { - # "type": "plain_text", - # "text": "{}".format(expense_field["placeholder"]), - # }, - # "options": [ - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-0", - # }, - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-1", - # }, - # { - # "text": { - # "type": "plain_text", - # "text": "*this is plain_text text*", - # }, - # "value": "value-2", - # }, - # ], - # }, - # } - - # blocks.append(block) - current_date = datetime.datetime.today().strftime("%Y-%m-%d") - cf_category = {"id": 136250, "name": "Custom Field Category"} - internet_category = {"id": 136518, "name": "Internet"} - os_category = {"id": 1234, "name": "Office Supplies"} - view = { - "type": "modal", - "callback_id": "create_expense", - "title": {"type": "plain_text", "text": "Create Expense", "emoji": True}, - "submit": {"type": "plain_text", "text": "Add Expense", "emoji": True}, - "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - "blocks": [ - { - "type": "input", - "block_id": "currency_block", - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Select Currency", - "emoji": True, - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "INR", - "emoji": True, - }, - "value": "INR", - }, - { - "text": { - "type": "plain_text", - "text": "USD", - "emoji": True, - }, - "value": "USD", - }, - ], - "action_id": "currency", - }, - "label": {"type": "plain_text", "text": "Currency", "emoji": True}, - }, - { - "type": "input", - "block_id": "amount_block", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Enter Amount", - "emoji": True, - }, - "action_id": "amount", - }, - "label": {"type": "plain_text", "text": "Amount", "emoji": True}, - }, - { - "type": "input", - "block_id": "payment_mode_block", - "element": { - "type": "static_select", - "placeholder": { - "type": "plain_text", - "text": "Select Payment Mode", - "emoji": True, - }, - "initial_option": { - "text": { - "type": "plain_text", - "text": "Paid by me", - "emoji": True, - }, - "value": "paid_by_me", - }, - "options": [ - { - "text": { - "type": "plain_text", - "text": "Paid by me", - "emoji": True, - }, - "value": "paid_by_me", - }, - { - "text": { - "type": "plain_text", - "text": "Paid by company", - "emoji": True, - }, - "value": "paid_by_company", - }, - ], - "action_id": "payment_mode", - }, - "label": {"type": "plain_text", "text": "Payment Mode", "emoji": True}, - }, - { - "type": "input", - "block_id": "purpose_block", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Eg. Client Meeting", - "emoji": True, - }, - "action_id": "purpose", - }, - "label": {"type": "plain_text", "text": "Purpose", "emoji": True}, - }, - { - "type": "input", - "block_id": "date_of_spend_block", - "element": { - "type": "datepicker", - "initial_date": current_date, - "placeholder": { - "type": "plain_text", - "text": "Select a date", - "emoji": True, - }, - "action_id": "spent_at", - }, - "label": {"type": "plain_text", "text": "Date of Spend", "emoji": True}, - }, - { - "type": "input", - "block_id": "merchant_block", - "element": { - "type": "plain_text_input", - "placeholder": { - "type": "plain_text", - "text": "Eg. Uber", - "emoji": True, - }, - "action_id": "merchant", - }, - "label": {"type": "plain_text", "text": "Merchant", "emoji": True}, - }, - { - "type": "input", - "block_id": "category_block", - "dispatch_action": True, - "element": { - "type": "external_select", - "min_query_length": 1, - "placeholder": { - "type": "plain_text", - "text": "Eg. Travel", - "emoji": True, - }, - "action_id": "category", - }, - "label": {"type": "plain_text", "text": "Category", "emoji": True}, - }, - ], - } - # view = { - # "type": "modal", - # "callback_id": "create_expense", - # "title": {"type": "plain_text", "text": "Add Expense"}, - # "submit": {"type": "plain_text", "text": "Add Expense"}, - # "close": {"type": "plain_text", "text": "Cancel"}, - # "notify_on_close": False, - # "blocks": blocks, - # # "private_metadata": zlib.compress(base64.b64encode(json.dumps(mappings).encode())).decode() - # } - if extra_fields is not None: - for field in extra_fields: - if field["type"] in ["NUMBER", "TEXT"]: - fld = { - "type": "input", - "block_id": "custom_field_{}_block".format(field["column_name"]), - "label": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - }, - "element": { - "type": "plain_text_input", - "action_id": "{}".format(field["field_name"]), - "placeholder": { - "type": "plain_text", - "text": "{}".format(field["placeholder"]), - }, - }, - } - elif field["type"] in ["SELECT", "MULTI_SELECT"]: - if field["type"] == "SELECT": - field_type = "static_select" - elif field["type"] == "MULTI_SELECT": - field_type = "multi_static_select" - fld = { - "type": "input", - "label": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - "emoji": True, - }, - "block_id": "custom_field_{}_block".format(field["column_name"]), - "element": { - "type": field_type, - "placeholder": { - "type": "plain_text", - "text": "{}".format(field["placeholder"]), - "emoji": True, - }, - "action_id": "{}".format(field["field_name"]), - }, - } - fld["element"]["options"] = [] - for option in field["options"]: - fld["element"]["options"].append( - { - "text": { - "type": "plain_text", - "text": option, - "emoji": True, - }, - "value": option, - } - ) - elif field["type"] == "BOOLEAN": - fld = { - "type": "input", - "block_id": "custom_field_{}_block".format(field["column_name"]), - "optional": True, - "element": { - "type": "checkboxes", - "options": [ - { - "text": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - "emoji": True, - } - } - ], - "action_id": "{}".format(field["field_name"]), - }, - "label": { - "type": "plain_text", - "text": "{}".format(field["field_name"]), - "emoji": True, - }, - } - view["blocks"].append(fld) - - # if form_state is not None: - # for block in view['blocks']: - # print('BLOCK ID -> ', block['block_id']) - # value_key = 'value' - # initial_value_key = 'initial_value' - # if block['element']['type'] == 'static_select': - # value_key = 'selected_option' - # initial_value_key = 'initial_option' - # elif block['element']['type'] == 'datepicker': - # value_key = 'selected_date' - # initial_value_key = 'initial_date' - - # block['element'][initial_value_key] = form_state[block['block_id']][block['element']['action_id']][value_key] - - return view + return {'type': 'home', 'blocks': post_authorization_message_blocks} diff --git a/fyle_slack_app/slack/ui/expenses/__init__.py b/fyle_slack_app/slack/ui/expenses/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py new file mode 100644 index 00000000..80dc003c --- /dev/null +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -0,0 +1,346 @@ +import datetime +from typing import Dict + + +def generate_fields_ui(field_details: Dict) -> Dict: + block_id = '{}_block'.format(field_details['column_name']) + action_id = field_details['column_name'] + + if field_details['is_custom'] is True: + block_id = '{}_custom_field_{}_block'.format(field_details['type'], field_details['column_name']) + action_id = '{}'.format(field_details['field_name']) + + if field_details['type'] in ['NUMBER', 'TEXT']: + custom_field = { + 'type': 'input', + 'block_id': block_id, + 'label': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + }, + 'element': { + 'type': 'plain_text_input', + 'action_id': action_id, + 'placeholder': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['placeholder']), + }, + }, + } + + elif field_details['type'] in ['SELECT', 'MULTI_SELECT']: + + if field_details['type'] == 'SELECT': + field_type = 'static_select' + elif field_details['type'] == 'MULTI_SELECT': + field_type = 'multi_static_select' + + custom_field = { + 'type': 'input', + 'label': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + 'emoji': True, + }, + 'block_id': block_id, + 'element': { + 'type': field_type, + 'placeholder': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['placeholder']), + 'emoji': True, + }, + 'action_id': action_id, + }, + } + + custom_field['element']['options'] = [] + + for option in field_details['options']: + custom_field['element']['options'].append( + { + 'text': { + 'type': 'plain_text', + 'text': option, + 'emoji': True, + }, + 'value': option, + } + ) + + elif field_details['type'] == 'BOOLEAN': + custom_field = { + 'type': 'input', + 'block_id': block_id, + 'optional': True, + 'element': { + 'type': 'checkboxes', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + 'emoji': True, + } + } + ], + 'action_id': action_id, + }, + 'label': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + 'emoji': True, + }, + } + + elif field_details['type'] == 'DATE': + custom_field = { + 'type': 'input', + 'block_id': block_id, + 'element': { + 'type': 'datepicker', + 'placeholder': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['placeholder']), + 'emoji': True, + }, + 'action_id': 'spent_at', + }, + 'label': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + 'emoji': True + }, + } + + return custom_field + + +def expense_dialog_form(expense_fields, projects, custom_fields=None, categories=None): + view = { + 'type': 'modal', + 'callback_id': 'create_expense', + 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, + 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, + 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True}, + 'blocks': [ + { + 'type': 'input', + 'block_id': 'currency_block', + 'element': { + 'type': 'static_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select Currency', + 'emoji': True, + }, + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'INR', + 'emoji': True, + }, + 'value': 'INR', + }, + { + 'text': { + 'type': 'plain_text', + 'text': 'USD', + 'emoji': True, + }, + 'value': 'USD', + }, + ], + 'action_id': 'currency', + }, + 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, + }, + { + 'type': 'input', + 'block_id': 'amount_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Enter Amount', + 'emoji': True, + }, + 'action_id': 'amount', + }, + 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, + }, + { + 'type': 'input', + 'block_id': 'payment_mode_block', + 'element': { + 'type': 'static_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select Payment Mode', + 'emoji': True, + }, + 'initial_option': { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by me', + 'emoji': True, + }, + 'value': 'paid_by_me', + }, + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by me', + 'emoji': True, + }, + 'value': 'paid_by_me', + }, + { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by company', + 'emoji': True, + }, + 'value': 'paid_by_company', + }, + ], + 'action_id': 'payment_mode', + }, + 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, + }, + ], + } + + purpose_block = { + 'type': 'input', + 'block_id': 'purpose_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Client Meeting', + 'emoji': True, + }, + 'action_id': 'purpose', + }, + 'label': {'type': 'plain_text', 'text': 'Purpose', 'emoji': True}, + } + view['blocks'].append(purpose_block) + + date_of_spend_block = { + 'type': 'input', + 'block_id': 'date_of_spend_block', + 'element': { + 'type': 'datepicker', + 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select a date', + 'emoji': True, + }, + 'action_id': 'spent_at', + }, + 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, + } + view['blocks'].append(date_of_spend_block) + + merchant_block = { + 'type': 'input', + 'block_id': 'merchant_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Uber', + 'emoji': True, + }, + 'action_id': 'merchant', + }, + 'label': {'type': 'plain_text', 'text': 'Merchant', 'emoji': True}, + } + view['blocks'].append(merchant_block) + + if projects['count'] > 0: + project_block = { + 'type': 'input', + 'block_id': 'project_block', + 'dispatch_action': True, + 'element': { + 'type': 'static_select', + 'action_id': 'project', + }, + 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, + } + project_options = [] + for project in projects['data']: + project_options.append({ + 'text': { + 'type': 'plain_text', + 'text': project['display_name'], + 'emoji': True, + }, + 'value': str(project['id']), + }) + project_block['element']['options'] = project_options + + view['blocks'].append(project_block) + + category_block = { + 'type': 'input', + 'block_id': 'category_block', + 'dispatch_action': True, + 'element': { + 'type': 'external_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Travel', + 'emoji': True, + }, + 'action_id': 'category', + }, + 'label': {'type': 'plain_text', 'text': 'Category', 'emoji': True}, + } + + if categories is not None: + category_block['element']['type'] = 'static_select' + category_options = [] + + for category in categories['data']: + category_options.append({ + 'text': { + 'type': 'plain_text', + 'text': category['display_name'], + 'emoji': True, + }, + 'value': str(category['id']), + }) + category_block['element']['options'] = category_options + else: + category_block['element']['min_query_length'] = 0 + + view['blocks'].append(category_block) + + + + if custom_fields is not None: + for field in custom_fields: + custom_field = generate_fields_ui(field) + view['blocks'].append(custom_field) + + # if form_state is not None: + # for block in view['blocks']: + # print('BLOCK ID -> ', block['block_id']) + # value_key = 'value' + # initial_value_key = 'initial_value' + # if block['element']['type'] == 'static_select': + # value_key = 'selected_option' + # initial_value_key = 'initial_option' + # elif block['element']['type'] == 'datepicker': + # value_key = 'selected_date' + # initial_value_key = 'initial_date' + + # block['element'][initial_value_key] = form_state[block['block_id']][block['element']['action_id']][value_key] + + return view diff --git a/fyle_slack_service/settings.py b/fyle_slack_service/settings.py index 26b75059..c2d3870e 100644 --- a/fyle_slack_service/settings.py +++ b/fyle_slack_service/settings.py @@ -92,14 +92,6 @@ } } -# Cache -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', - 'LOCATION': 'slack_cache', - } -} - # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators diff --git a/run.sh b/run.sh index 8e6bae0b..7d07454b 100644 --- a/run.sh +++ b/run.sh @@ -1,3 +1,2 @@ python manage.py migrate -python manage.py createcachetable gunicorn -c gunicorn_config.py fyle_slack_service.wsgi -b 0.0.0.0:8000 \ No newline at end of file From 0e49ed77d2a84de10e07fb212d8f037962e2f536 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 21 Sep 2021 13:55:21 +0530 Subject: [PATCH 18/85] Handling project & category selection in background and rendering of dependent fields --- fyle_slack_app/fyle/expenses/views.py | 18 + fyle_slack_app/slack/commands/handlers.py | 43 +- fyle_slack_app/slack/commands/tasks.py | 21 + .../interactives/block_action_handlers.py | 147 ++----- .../interactives/block_suggestion_handlers.py | 7 +- fyle_slack_app/slack/interactives/tasks.py | 129 ++++++ .../interactives/view_submission_handlers.py | 6 - fyle_slack_app/slack/ui/expenses/messages.py | 377 ++++++++++-------- 8 files changed, 448 insertions(+), 300 deletions(-) create mode 100644 fyle_slack_app/slack/interactives/tasks.py diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 0f620172..7b96b284 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -13,6 +13,24 @@ def get_expense_fields(user: User, query_params: Dict) -> Dict: return expense_fields + @staticmethod + def get_default_expense_fields(user: User) -> Dict: + default_expense_fields_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'column_name': 'in.(purpose, txn_dt, vendor_id, cost_center_id, project_id)', + 'is_enabled': 'eq.{}'.format(True), + 'is_custom': 'eq.{}'.format(False), + 'is_mandatory': 'eq.{}'.format(True) + } + + default_expense_fields = FyleExpense.get_expense_fields(user, default_expense_fields_query_params) + + return default_expense_fields + + + @staticmethod def get_categories(user: User, query_params: Dict) -> Dict: connection = get_fyle_sdk_connection(user.fyle_refresh_token) diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index f1c2c523..8c9fc02e 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -6,7 +6,6 @@ from fyle.platform import exceptions -from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.libs import utils, assertions, logger from fyle_slack_app.fyle.utils import get_fyle_oauth_url, get_fyle_profile from fyle_slack_app.models import User, NotificationPreference @@ -103,34 +102,34 @@ def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_ def handle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str): user = utils.get_or_none(User, slack_user_id=user_id) - assertions.assert_found(user) slack_client = slack_utils.get_slack_client(team_id) - expense_fields_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'column_name': 'in.(purpose, txn_dt, vendor_id, cost_center_id, project_id)', - 'is_enabled': 'eq.{}'.format(True), - 'is_custom': 'eq.{}'.format(False), - 'is_mandatory': 'eq.{}'.format(True), - } + # default_expense_fields = FyleExpense.get_default_expense_fields(user) - expense_fields = FyleExpense.get_expense_fields(user, expense_fields_query_params) + # projects_query_params = { + # 'offset': 0, + # 'limit': '100', + # 'order': 'created_at.desc', + # 'is_enabled': 'eq.{}'.format(True) + # } - projects_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } + # projects = FyleExpense.get_projects(user, projects_query_params) + + # modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects) + + # slack_client.views_open(user_id=user_id, view=modal, trigger_id=trigger_id) - projects = FyleExpense.get_projects(user, projects_query_params) - print('projects -> ', projects) - modal = expense_messages.expense_dialog_form(projects=projects, expense_fields=expense_fields) + loading_modal = expense_messages.expense_form_loading_modal() - slack_client.views_open(user_id=user_id, view=modal, trigger_id=trigger_id) + response = slack_client.views_open(user=user_id, view=loading_modal, trigger_id=trigger_id) + + async_task( + 'fyle_slack_app.slack.commands.tasks.open_expense_form', + user, + team_id, + response['view']['id'] + ) return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 49c6a63b..f36dc1d4 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -6,6 +6,8 @@ from fyle_slack_app.models import User, UserSubscriptionDetail from fyle_slack_app.models.user_subscription_details import SubscriptionType from fyle_slack_app.slack.commands.handlers import SlackCommandHandler +from fyle_slack_app.fyle.expenses.views import FyleExpense +from fyle_slack_app.slack.ui.expenses import messages as expense_messages logger = logger.get_logger(__name__) @@ -89,3 +91,22 @@ def fyle_unlink_account(user_id: str, team_id: str, user_dm_channel_id: str) -> channel=user_dm_channel_id, text=text ) + + +def open_expense_form(user: User, team_id: str, view_id: str) -> None: + default_expense_fields = FyleExpense.get_default_expense_fields(user) + + slack_client = slack_utils.get_slack_client(team_id) + + projects_query_params = { + 'offset': 0, + 'limit': '100', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + projects = FyleExpense.get_projects(user, projects_query_params) + + modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects) + + slack_client.views_update(view=modal, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 86537358..9d418b3b 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -1,4 +1,3 @@ -import json from typing import Callable, Dict from django.http import JsonResponse @@ -8,9 +7,7 @@ from fyle_slack_app.models import User, NotificationPreference from fyle_slack_app.models.notification_preferences import NotificationType from fyle_slack_app.libs import assertions, utils, logger -from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_user_dm_channel_id, get_slack_client -from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form from fyle_slack_app import tracking @@ -44,108 +41,6 @@ def _initialize_block_action_handlers(self): } - def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - print('SP -> ', json.dumps(slack_payload, indent=2)) - - slack_client = get_slack_client(team_id) - - project_id = slack_payload['actions'][0]['selected_option']['value'] - - view_id = slack_payload['container']['view_id'] - - user = utils.get_or_none(User, slack_user_id=user_id) - - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } - - project = FyleExpense.get_projects(user, project_query_params) - - print('PROJECT -> ', project) - - query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True), - 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) - } - - categories = FyleExpense.get_categories(user, query_params) - - expense_fields_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'or': '(is_mandatory.eq.{}, and(is_custom.eq.{}, is_mandatory.eq.{}))'.format(True, True, True), - 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', - 'is_enabled': 'eq.{}'.format(True), - } - - expense_fields = FyleExpense.get_expense_fields(user, expense_fields_query_params) - - projects_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } - - projects = FyleExpense.get_projects(user, projects_query_params) - - new_expense_dialog_form = expense_dialog_form(expense_fields=expense_fields, projects=projects, categories=categories) - - slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) - - return JsonResponse({}) - - - def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - - slack_client = get_slack_client(team_id) - - category_id = slack_payload['actions'][0]['selected_option']['value'] - - view_id = slack_payload['container']['view_id'] - - user = utils.get_or_none(User, slack_user_id=user_id) - - expense_fields_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'or': '(is_mandatory.eq.{}, and(is_custom.eq.{}, is_mandatory.eq.{}))'.format(True, True, True), - 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', - 'is_enabled': 'eq.{}'.format(True), - 'category_ids': 'cs.[{}]'.format(int(category_id)) - } - - expense_fields = FyleExpense.get_expense_fields(user, expense_fields_query_params) - - print('expense fields -> ', json.dumps(expense_fields, indent=2)) - - projects_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } - - projects = FyleExpense.get_projects(user, projects_query_params) - - if expense_fields['count'] > 0: - new_expense_dialog_form = expense_dialog_form(expense_fields=expense_fields, custom_fields=expense_fields['data'], projects=projects) - else: - new_expense_dialog_form = expense_dialog_form(expense_fields=expense_fields, projects=projects) - - slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) - - return JsonResponse({}, status=200) - # Gets called when function with an action is not found def _handle_invalid_block_actions(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: slack_client = get_slack_client(team_id) @@ -250,6 +145,48 @@ def handle_notification_preference_selection(self, slack_payload: Dict, user_id: return JsonResponse({}, status=200) + def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + + project_id = slack_payload['actions'][0]['selected_option']['value'] + + view_id = slack_payload['container']['view_id'] + + user = utils.get_or_none(User, slack_user_id=user_id) + + blocks = slack_payload['view']['blocks'] + + async_task( + 'fyle_slack_app.slack.interactives.tasks.handle_project_select', + user, + team_id, + project_id, + view_id, + blocks + ) + + return JsonResponse({}) + + + def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + + category_id = slack_payload['actions'][0]['selected_option']['value'] + + view_id = slack_payload['container']['view_id'] + + user = utils.get_or_none(User, slack_user_id=user_id) + + async_task( + 'fyle_slack_app.slack.interactives.tasks.handle_category_select', + user, + team_id, + category_id, + view_id, + slack_payload + ) + + return JsonResponse({}, status=200) + + def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: user = utils.get_or_none(User, slack_user_id=user_id) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 0ec7d8c7..a6efb73d 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -1,5 +1,3 @@ -import json - from typing import Dict, List from django.http import JsonResponse @@ -57,14 +55,15 @@ def handle_block_suggestions(self, slack_payload: Dict, user_id: str, team_id: s def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: - print('SLACK PAYLOAD -> ', json.dumps(slack_payload, indent=2)) + user = utils.get_or_none(User, slack_user_id=user_id) category_value_entered = slack_payload['value'] query_params = { 'offset': 0, - 'limit': '20', + 'limit': '100', 'order': 'display_name.asc', 'display_name': 'ilike.%{}%'.format(category_value_entered), + 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', 'is_enabled': 'eq.{}'.format(True) } suggested_categories = FyleExpense.get_categories(user, query_params) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py new file mode 100644 index 00000000..5651866f --- /dev/null +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -0,0 +1,129 @@ +from typing import Dict, List + +from fyle_slack_app.slack.utils import get_slack_client + +from fyle_slack_app.models import User +from fyle_slack_app.fyle.expenses.views import FyleExpense +from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form + + +def handle_project_select(user: User, team_id: str, project_id: str, view_id: str, blocks: List[Dict]) -> None: + + slack_client = get_slack_client(team_id) + + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } + + project = FyleExpense.get_projects(user, project_query_params) + + query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True), + 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', + 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) + } + + categories = FyleExpense.get_categories(user, query_params) + + # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered + current_ui_blocks = [] + for block in blocks: + if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: + current_ui_blocks.append(block) + + # Get projects from UI blocks itself, since projects won't be updated in the short span of expense form + project_options = [] + for block in blocks: + if block['block_id'] == 'project_block': + for option in block['element']['options']: + project_options.append({ + 'display_name': option['text']['text'], + 'id': int(option['value']) + }) + + projects = { + 'data': project_options, + 'count': len(project_options) + } + + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, projects=projects, categories=categories) + + slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) + + +def handle_category_select(user: User, team_id: str, category_id: str, view_id: str, slack_payload: str) -> None: + + slack_client = get_slack_client(team_id) + + custom_fields_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'or': '(is_mandatory.eq.{}, and(is_custom.eq.{}, is_mandatory.eq.{}))'.format(True, True, True), + 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', + 'is_enabled': 'eq.{}'.format(True), + 'category_ids': 'cs.[{}]'.format(int(category_id)) + } + + + custom_fields = FyleExpense.get_expense_fields(user, custom_fields_query_params) + + # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered + current_ui_blocks = [] + for block in slack_payload['view']['blocks']: + if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: + current_ui_blocks.append(block) + + categories = None + projects = None + + if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project']['selected_option'] is not None: + + project_id = int(slack_payload['view']['state']['values']['project_block']['project']['selected_option']['value']) + + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } + + project = FyleExpense.get_projects(user, project_query_params) + + query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True), + 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', + 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) + } + + categories = FyleExpense.get_categories(user, query_params) + + # Get projects from UI blocks itself, since projects won't be updated in the short span of expense form + project_options = [] + for block in slack_payload['view']['blocks']: + if block['block_id'] == 'project_block': + for option in block['element']['options']: + project_options.append({ + 'display_name': option['text']['text'], + 'id': int(option['value']) + }) + + projects = { + 'data': project_options, + 'count': len(project_options) + } + + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, projects=projects, categories=categories) + + slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index c4ee8be2..1fd3bc58 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -3,7 +3,6 @@ from django.http.response import JsonResponse -from fyle_slack_app.libs.utils import encode_state from fyle_slack_app.slack import utils as slack_utils @@ -101,11 +100,6 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) print('EXPENSE -> ', json.dumps(expense_mapping, indent=2)) slack_client = slack_utils.get_slack_client(team_id) - em = { - 'a': expense_mapping, - 'b': expense_mapping, - 'c': expense_mapping - } # policy_view = { # "type": "modal", # "private_metadata": encode_state(em), diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 80dc003c..dd3be94f 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -1,14 +1,17 @@ import datetime -from typing import Dict +from typing import Dict, List -def generate_fields_ui(field_details: Dict) -> Dict: +# is_additional_field is for fields which are not custom fields but are part of a specific categories +def generate_fields_ui(field_details: Dict, is_additional_field: bool = False) -> Dict: block_id = '{}_block'.format(field_details['column_name']) action_id = field_details['column_name'] - if field_details['is_custom'] is True: + # We need to define addtional fields as custom fields so that we can clear them out in form when category is changed + if field_details['is_custom'] is True or is_additional_field is True: block_id = '{}_custom_field_{}_block'.format(field_details['type'], field_details['column_name']) - action_id = '{}'.format(field_details['field_name']) + if field_details['is_custom'] is True: + action_id = '{}'.format(field_details['field_name']) if field_details['type'] in ['NUMBER', 'TEXT']: custom_field = { @@ -116,71 +119,90 @@ def generate_fields_ui(field_details: Dict) -> Dict: return custom_field -def expense_dialog_form(expense_fields, projects, custom_fields=None, categories=None): - view = { - 'type': 'modal', - 'callback_id': 'create_expense', - 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, - 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, - 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True}, - 'blocks': [ - { - 'type': 'input', - 'block_id': 'currency_block', - 'element': { - 'type': 'static_select', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Select Currency', - 'emoji': True, - }, - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'INR', - 'emoji': True, - }, - 'value': 'INR', +def generate_expense_fields_mandatory_mapping(expense_fields: Dict) -> Dict: + mandatory_mapping = { + 'purpose': False, + 'txn_dt': False, + 'vendor_id': False, + 'project_id': False, + 'cost_center_id': False + } + + for field in expense_fields['data']: + if field['column_name'] in mandatory_mapping: + mandatory_mapping[field['column_name']] = field['is_mandatory'] + + return mandatory_mapping + + +def get_default_fields_blocks(mandatory_mapping: Dict) -> List: + default_fields_blocks = [ + { + 'type': 'input', + 'block_id': 'default_field_currency_block', + 'element': { + 'type': 'static_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select Currency', + 'emoji': True, + }, + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'INR', + 'emoji': True, }, - { - 'text': { - 'type': 'plain_text', - 'text': 'USD', - 'emoji': True, - }, - 'value': 'USD', + 'value': 'INR', + }, + { + 'text': { + 'type': 'plain_text', + 'text': 'USD', + 'emoji': True, }, - ], - 'action_id': 'currency', - }, - 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, - }, - { - 'type': 'input', - 'block_id': 'amount_block', - 'element': { - 'type': 'plain_text_input', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Enter Amount', - 'emoji': True, + 'value': 'USD', }, - 'action_id': 'amount', + ], + 'action_id': 'currency', + }, + 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, + }, + { + 'type': 'input', + 'block_id': 'default_field_amount_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Enter Amount', + 'emoji': True, }, - 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, + 'action_id': 'amount', }, - { - 'type': 'input', - 'block_id': 'payment_mode_block', - 'element': { - 'type': 'static_select', - 'placeholder': { + 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, + }, + { + 'type': 'input', + 'block_id': 'default_field_payment_mode_block', + 'element': { + 'type': 'static_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select Payment Mode', + 'emoji': True, + }, + 'initial_option': { + 'text': { 'type': 'plain_text', - 'text': 'Select Payment Mode', + 'text': 'Paid by me', 'emoji': True, }, - 'initial_option': { + 'value': 'paid_by_me', + }, + 'options': [ + { 'text': { 'type': 'plain_text', 'text': 'Paid by me', @@ -188,105 +210,120 @@ def expense_dialog_form(expense_fields, projects, custom_fields=None, categories }, 'value': 'paid_by_me', }, - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by me', - 'emoji': True, - }, - 'value': 'paid_by_me', - }, - { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by company', - 'emoji': True, - }, - 'value': 'paid_by_company', + { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by company', + 'emoji': True, }, - ], - 'action_id': 'payment_mode', - }, - 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, - }, - ], - } - - purpose_block = { - 'type': 'input', - 'block_id': 'purpose_block', - 'element': { - 'type': 'plain_text_input', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Eg. Client Meeting', - 'emoji': True, - }, - 'action_id': 'purpose', - }, - 'label': {'type': 'plain_text', 'text': 'Purpose', 'emoji': True}, - } - view['blocks'].append(purpose_block) - - date_of_spend_block = { - 'type': 'input', - 'block_id': 'date_of_spend_block', - 'element': { - 'type': 'datepicker', - 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), - 'placeholder': { - 'type': 'plain_text', - 'text': 'Select a date', - 'emoji': True, + 'value': 'paid_by_company', + }, + ], + 'action_id': 'payment_mode', }, - 'action_id': 'spent_at', + 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, }, - 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, - } - view['blocks'].append(date_of_spend_block) - - merchant_block = { - 'type': 'input', - 'block_id': 'merchant_block', - 'element': { - 'type': 'plain_text_input', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Eg. Uber', - 'emoji': True, + ] + if mandatory_mapping['purpose'] is True: + purpose_block = { + 'type': 'input', + 'block_id': 'default_field_purpose_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Client Meeting', + 'emoji': True, + }, + 'action_id': 'purpose', }, - 'action_id': 'merchant', - }, - 'label': {'type': 'plain_text', 'text': 'Merchant', 'emoji': True}, - } - view['blocks'].append(merchant_block) + 'label': {'type': 'plain_text', 'text': 'Purpose', 'emoji': True}, + } + default_fields_blocks.append(purpose_block) - if projects['count'] > 0: - project_block = { + if mandatory_mapping['txn_dt'] is True: + date_of_spend_block = { 'type': 'input', - 'block_id': 'project_block', - 'dispatch_action': True, + 'block_id': 'default_field_date_of_spend_block', 'element': { - 'type': 'static_select', - 'action_id': 'project', + 'type': 'datepicker', + 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select a date', + 'emoji': True, + }, + 'action_id': 'spent_at', }, - 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, } - project_options = [] - for project in projects['data']: - project_options.append({ - 'text': { + default_fields_blocks.append(date_of_spend_block) + + if mandatory_mapping['vendor_id'] is True: + merchant_block = { + 'type': 'input', + 'block_id': 'default_field_merchant_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { 'type': 'plain_text', - 'text': project['display_name'], + 'text': 'Eg. Uber', 'emoji': True, }, - 'value': str(project['id']), - }) - project_block['element']['options'] = project_options + 'action_id': 'merchant', + }, + 'label': {'type': 'plain_text', 'text': 'Merchant', 'emoji': True}, + } + default_fields_blocks.append(merchant_block) + + return default_fields_blocks + + +def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, custom_fields: Dict = None, categories: Dict = None, current_ui_blocks: List = None) -> Dict: + view = { + 'type': 'modal', + 'callback_id': 'create_expense', + 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, + 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, + 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True} + } + + view['blocks'] = [] + + # If current UI blocks are passed use them as it is for faster processing of UI elements. + if current_ui_blocks is not None: + view['blocks'] = current_ui_blocks + else: + mandatory_mapping = generate_expense_fields_mandatory_mapping(expense_fields) + + view['blocks'] = get_default_fields_blocks(mandatory_mapping) + + if mandatory_mapping['project_id'] is True and projects is not None and projects['count'] > 0: + project_block = { + 'type': 'input', + 'block_id': 'project_block', + 'dispatch_action': True, + 'element': { + 'type': 'static_select', + 'action_id': 'project', + }, + 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, + } + project_options = [] + for project in projects['data']: + project_options.append({ + 'text': { + 'type': 'plain_text', + 'text': project['display_name'], + 'emoji': True, + }, + 'value': str(project['id']), + }) + project_block['element']['options'] = project_options - view['blocks'].append(project_block) + view['blocks'].append(project_block) + # Since category block is dependent of projects sometimes, render them everytime. category_block = { 'type': 'input', 'block_id': 'category_block', @@ -303,7 +340,7 @@ def expense_dialog_form(expense_fields, projects, custom_fields=None, categories 'label': {'type': 'plain_text', 'text': 'Category', 'emoji': True}, } - if categories is not None: + if categories is not None and categories['count'] > 0: category_block['element']['type'] = 'static_select' category_options = [] @@ -316,31 +353,45 @@ def expense_dialog_form(expense_fields, projects, custom_fields=None, categories }, 'value': str(category['id']), }) + category_block['element']['options'] = category_options + category_block['element']['initial_option'] = category_options[0] else: category_block['element']['min_query_length'] = 0 view['blocks'].append(category_block) + # If custom fields are present, render them in the form + if custom_fields is not None and custom_fields['count'] > 0: + for field in custom_fields['data']: + + # Additional fields are field which are not custom fields but are dependent on categories + is_additional_field = False + if field['is_custom'] is False: + is_additional_field = True - if custom_fields is not None: - for field in custom_fields: - custom_field = generate_fields_ui(field) + custom_field = generate_fields_ui(field, is_additional_field=is_additional_field) view['blocks'].append(custom_field) - # if form_state is not None: - # for block in view['blocks']: - # print('BLOCK ID -> ', block['block_id']) - # value_key = 'value' - # initial_value_key = 'initial_value' - # if block['element']['type'] == 'static_select': - # value_key = 'selected_option' - # initial_value_key = 'initial_option' - # elif block['element']['type'] == 'datepicker': - # value_key = 'selected_date' - # initial_value_key = 'initial_date' + return view - # block['element'][initial_value_key] = form_state[block['block_id']][block['element']['action_id']][value_key] - return view +def expense_form_loading_modal() -> Dict: + loading_modal = { + 'type': 'modal', + 'callback_id': 'create_expense', + 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, + 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True}, + 'blocks': [ + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': 'Loading the best expense form :zap:' + } + } + ] + } + + return loading_modal From 8f96243739b9eb741997c119fc978e3603924ad3 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 21 Sep 2021 14:40:00 +0530 Subject: [PATCH 19/85] Added loading below input elements --- .../interactives/block_action_handlers.py | 58 ++++++++++++++++++- fyle_slack_app/slack/interactives/tasks.py | 22 +++++-- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 9d418b3b..3d86d120 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -1,4 +1,6 @@ -from typing import Callable, Dict +from fyle_slack_app.slack.ui.expenses.messages import expense_form_loading_modal +import json +from typing import Callable, Dict, List from django.http import JsonResponse @@ -153,15 +155,40 @@ def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) user = utils.get_or_none(User, slack_user_id=user_id) + current_view = expense_form_loading_modal() + current_view['submit'] = {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True} + blocks = slack_payload['view']['blocks'] + # Adding loading info below project input element + project_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'project_block'), None) + + project_loading_block = { + 'type': 'context', + 'block_id': 'project_loading_block', + 'elements': [ + { + 'type': 'mrkdwn', + 'text': 'Loading categories for this project' + } + ] + } + + blocks.insert(project_block_index + 1, project_loading_block) + + current_view['blocks'] = blocks + + slack_client = get_slack_client(team_id) + + slack_client.views_update(view_id=view_id, view=current_view) + async_task( 'fyle_slack_app.slack.interactives.tasks.handle_project_select', user, team_id, project_id, view_id, - blocks + slack_payload ) return JsonResponse({}) @@ -175,6 +202,33 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str user = utils.get_or_none(User, slack_user_id=user_id) + current_view = expense_form_loading_modal() + current_view['submit'] = {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True} + + blocks = slack_payload['view']['blocks'] + + # Adding loading info below category input element + category_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'category_block'), None) + + category_loading_block = { + 'type': 'context', + 'block_id': 'category_loading_block', + 'elements': [ + { + 'type': 'mrkdwn', + 'text': 'Loading additional fields for this category if any' + } + ] + } + + blocks.insert(category_block_index + 1, category_loading_block) + + current_view['blocks'] = blocks + + slack_client = get_slack_client(team_id) + + slack_client.views_update(view_id=view_id, view=current_view) + async_task( 'fyle_slack_app.slack.interactives.tasks.handle_category_select', user, diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 5651866f..2f20e073 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,4 +1,4 @@ -from typing import Dict, List +from typing import Dict from fyle_slack_app.slack.utils import get_slack_client @@ -7,7 +7,7 @@ from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form -def handle_project_select(user: User, team_id: str, project_id: str, view_id: str, blocks: List[Dict]) -> None: +def handle_project_select(user: User, team_id: str, project_id: str, view_id: str, slack_payload: Dict) -> None: slack_client = get_slack_client(team_id) @@ -32,6 +32,12 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st categories = FyleExpense.get_categories(user, query_params) + blocks = slack_payload['view']['blocks'] + + # Removing loading info from below project input element + project_loading_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'project_loading_block'), None) + blocks.pop(project_loading_block_index) + # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered current_ui_blocks = [] for block in blocks: @@ -75,9 +81,15 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: custom_fields = FyleExpense.get_expense_fields(user, custom_fields_query_params) + blocks = slack_payload['view']['blocks'] + + # Removing loading info from below category input element + category_loading_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'category_loading_block'), None) + blocks.pop(category_loading_block_index) + # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered current_ui_blocks = [] - for block in slack_payload['view']['blocks']: + for block in blocks: if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: current_ui_blocks.append(block) @@ -109,9 +121,11 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: categories = FyleExpense.get_categories(user, query_params) + blocks = slack_payload['view']['blocks'] + # Get projects from UI blocks itself, since projects won't be updated in the short span of expense form project_options = [] - for block in slack_payload['view']['blocks']: + for block in blocks: if block['block_id'] == 'project_block': for option in block['element']['options']: project_options.append({ From ccce79eabc5cb42b40fdaa9288f23f1876ad3273 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 21 Sep 2021 18:41:16 +0530 Subject: [PATCH 20/85] Common connection for fyle expense --- fyle_slack_app/fyle/expenses/views.py | 37 +++++++------ fyle_slack_app/slack/commands/handlers.py | 15 ------ fyle_slack_app/slack/commands/tasks.py | 7 ++- .../interactives/block_action_handlers.py | 53 ++++++++++++++++--- .../interactives/block_suggestion_handlers.py | 29 +++++++++- fyle_slack_app/slack/interactives/tasks.py | 13 +++-- fyle_slack_app/slack/ui/expenses/messages.py | 21 +------- fyle_slack_service/settings.py | 2 +- 8 files changed, 111 insertions(+), 66 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 7b96b284..ed49d6b3 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -1,20 +1,25 @@ from typing import Dict +from fyle.platform.platform import Platform + from fyle_slack_app.fyle.utils import get_fyle_sdk_connection from fyle_slack_app.models.users import User class FyleExpense: - @staticmethod - def get_expense_fields(user: User, query_params: Dict) -> Dict: - connection = get_fyle_sdk_connection(user.fyle_refresh_token) - expense_fields = connection.v1.fyler.expense_fields.list(query_params=query_params) + connection: Platform = None + + def __init__(self, user: User) -> None: + self.connection = get_fyle_sdk_connection(user.fyle_refresh_token) + + + def get_expense_fields(self, query_params: Dict) -> Dict: + expense_fields = self.connection.v1.fyler.expense_fields.list(query_params=query_params) return expense_fields - @staticmethod - def get_default_expense_fields(user: User) -> Dict: + def get_default_expense_fields(self) -> Dict: default_expense_fields_query_params = { 'offset': 0, 'limit': '20', @@ -25,21 +30,21 @@ def get_default_expense_fields(user: User) -> Dict: 'is_mandatory': 'eq.{}'.format(True) } - default_expense_fields = FyleExpense.get_expense_fields(user, default_expense_fields_query_params) + default_expense_fields = self.get_expense_fields(default_expense_fields_query_params) return default_expense_fields - - @staticmethod - def get_categories(user: User, query_params: Dict) -> Dict: - connection = get_fyle_sdk_connection(user.fyle_refresh_token) - categories = connection.v1.fyler.categories.list(query_params=query_params) + def get_categories(self, query_params: Dict) -> Dict: + categories = self.connection.v1.fyler.categories.list(query_params=query_params) return categories - @staticmethod - def get_projects(user: User, query_params: Dict) -> Dict: - connection = get_fyle_sdk_connection(user.fyle_refresh_token) - projects = connection.v1.fyler.projects.list(query_params=query_params) + def get_projects(self, query_params: Dict) -> Dict: + projects = self.connection.v1.fyler.projects.list(query_params=query_params) return projects + + + @staticmethod + def get_currencies(): + return ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index 8c9fc02e..7039de6c 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -105,21 +105,6 @@ def handle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: st slack_client = slack_utils.get_slack_client(team_id) - # default_expense_fields = FyleExpense.get_default_expense_fields(user) - - # projects_query_params = { - # 'offset': 0, - # 'limit': '100', - # 'order': 'created_at.desc', - # 'is_enabled': 'eq.{}'.format(True) - # } - - # projects = FyleExpense.get_projects(user, projects_query_params) - - # modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects) - - # slack_client.views_open(user_id=user_id, view=modal, trigger_id=trigger_id) - loading_modal = expense_messages.expense_form_loading_modal() response = slack_client.views_open(user=user_id, view=loading_modal, trigger_id=trigger_id) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index f36dc1d4..658f5b62 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -94,7 +94,10 @@ def fyle_unlink_account(user_id: str, team_id: str, user_dm_channel_id: str) -> def open_expense_form(user: User, team_id: str, view_id: str) -> None: - default_expense_fields = FyleExpense.get_default_expense_fields(user) + + fyle_expense = FyleExpense(user) + + default_expense_fields = fyle_expense.get_default_expense_fields() slack_client = slack_utils.get_slack_client(team_id) @@ -105,7 +108,7 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: 'is_enabled': 'eq.{}'.format(True) } - projects = FyleExpense.get_projects(user, projects_query_params) + projects = fyle_expense.get_projects(projects_query_params) modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 3d86d120..d36c9740 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -1,15 +1,16 @@ -from fyle_slack_app.slack.ui.expenses.messages import expense_form_loading_modal -import json -from typing import Callable, Dict, List +from typing import Callable, Dict from django.http import JsonResponse from django_q.tasks import async_task +from fyle_slack_app.fyle.expenses.views import FyleExpense +from fyle_slack_app.fyle.utils import get_fyle_profile from fyle_slack_app.models import User, NotificationPreference from fyle_slack_app.models.notification_preferences import NotificationType from fyle_slack_app.libs import assertions, utils, logger from fyle_slack_app.slack.utils import get_slack_user_dm_channel_id, get_slack_client +from fyle_slack_app.slack.ui.expenses import messages as expense_messages from fyle_slack_app import tracking @@ -39,7 +40,8 @@ def _initialize_block_action_handlers(self): # Dynamic options 'category': self.handle_category_select, - 'project': self.handle_project_select + 'project': self.handle_project_select, + # 'currency': self.handle_currency_select } @@ -155,7 +157,7 @@ def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) user = utils.get_or_none(User, slack_user_id=user_id) - current_view = expense_form_loading_modal() + current_view = expense_messages.expense_form_loading_modal() current_view['submit'] = {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True} blocks = slack_payload['view']['blocks'] @@ -202,7 +204,7 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str user = utils.get_or_none(User, slack_user_id=user_id) - current_view = expense_form_loading_modal() + current_view = expense_messages.expense_form_loading_modal() current_view['submit'] = {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True} blocks = slack_payload['view']['blocks'] @@ -241,6 +243,45 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str return JsonResponse({}, status=200) + # def handle_currency_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + # selected_currency = slack_payload['actions'][0]['selected_option']['value'] + + # view_id = slack_payload['container']['view_id'] + + # user = utils.get_or_none(User, slack_user_id=user_id) + + # fyle_profile = get_fyle_profile(user.fyle_refresh_token) + + # home_currency = fyle_profile['org']['currency'] + + # is_home_currency_selected = True + # if home_currency != selected_currency: + # is_home_currency_selected = False + + # fyle_expense = FyleExpense(user) + + # default_expense_fields = fyle_expense.get_default_expense_fields() + + # slack_client = get_slack_client(team_id) + + # projects_query_params = { + # 'offset': 0, + # 'limit': '100', + # 'order': 'created_at.desc', + # 'is_enabled': 'eq.{}'.format(True) + # } + + # projects = fyle_expense.get_projects(projects_query_params) + + # expense_form = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects) + + # if is_home_currency_selected is False: + + + # slack_client.views_update(view_id=view_id, view=expense_form) + + + def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: user = utils.get_or_none(User, slack_user_id=user_id) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index a6efb73d..7500c265 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -18,7 +18,8 @@ class BlockSuggestionHandler: # Maps action_id with it's respective function def _initialize_block_suggestion_handlers(self): self._block_suggestion_handlers = { - 'category': self.handle_category_suggestion + 'category': self.handle_category_suggestion, + 'currency': self.handle_currency_suggestion } @@ -66,7 +67,9 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', 'is_enabled': 'eq.{}'.format(True) } - suggested_categories = FyleExpense.get_categories(user, query_params) + + fyle_expense = FyleExpense(user) + suggested_categories = fyle_expense.get_categories(query_params) category_options = [] if suggested_categories['count'] > 0: @@ -82,3 +85,25 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: category_options.append(option) return category_options + + + def handle_currency_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + + currencies = ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] + + currency_value_entered = slack_payload['value'] + + currency_options = [] + for currency in currencies: + if currency_value_entered.upper() in currency: + option = { + 'text': { + 'type': 'plain_text', + 'text': currency, + 'emoji': True, + }, + 'value': currency, + } + currency_options.append(option) + + return currency_options diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 2f20e073..3cae1130 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -19,7 +19,9 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st 'is_enabled': 'eq.{}'.format(True) } - project = FyleExpense.get_projects(user, project_query_params) + fyle_expense = FyleExpense(user) + + project = fyle_expense.get_projects(project_query_params) query_params = { 'offset': 0, @@ -30,7 +32,7 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) } - categories = FyleExpense.get_categories(user, query_params) + categories = fyle_expense.get_categories(query_params) blocks = slack_payload['view']['blocks'] @@ -78,8 +80,9 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: 'category_ids': 'cs.[{}]'.format(int(category_id)) } + fyle_expense = FyleExpense(user) - custom_fields = FyleExpense.get_expense_fields(user, custom_fields_query_params) + custom_fields = fyle_expense.get_expense_fields(custom_fields_query_params) blocks = slack_payload['view']['blocks'] @@ -108,7 +111,7 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: 'is_enabled': 'eq.{}'.format(True) } - project = FyleExpense.get_projects(user, project_query_params) + project = fyle_expense.get_projects(project_query_params) query_params = { 'offset': 0, @@ -119,7 +122,7 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) } - categories = FyleExpense.get_categories(user, query_params) + categories = fyle_expense.get_categories(query_params) blocks = slack_payload['view']['blocks'] diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index dd3be94f..1bcb934f 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -141,30 +141,13 @@ def get_default_fields_blocks(mandatory_mapping: Dict) -> List: 'type': 'input', 'block_id': 'default_field_currency_block', 'element': { - 'type': 'static_select', + 'type': 'external_select', 'placeholder': { 'type': 'plain_text', 'text': 'Select Currency', 'emoji': True, }, - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'INR', - 'emoji': True, - }, - 'value': 'INR', - }, - { - 'text': { - 'type': 'plain_text', - 'text': 'USD', - 'emoji': True, - }, - 'value': 'USD', - }, - ], + 'min_query_length': 0, 'action_id': 'currency', }, 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, diff --git a/fyle_slack_service/settings.py b/fyle_slack_service/settings.py index c2d3870e..802738c4 100644 --- a/fyle_slack_service/settings.py +++ b/fyle_slack_service/settings.py @@ -187,7 +187,7 @@ 'level': 'ERROR', 'propagate': False }, - 'fyle_slack_app ': { + 'fyle_slack_app': { 'handlers': ['debug_logs'], 'level': 'ERROR', 'propagate': False From 649602e97f84c1be975c425346a6ba436460cb31 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 22 Sep 2021 16:10:16 +0530 Subject: [PATCH 21/85] Added cost center and billable and following expense form order --- fyle_slack_app/fyle/expenses/views.py | 5 + fyle_slack_app/slack/commands/tasks.py | 11 +- .../interactives/block_action_handlers.py | 49 ++++- .../interactives/block_suggestion_handlers.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 37 +--- .../interactives/view_submission_handlers.py | 4 +- fyle_slack_app/slack/ui/expenses/messages.py | 195 ++++++++++++------ 7 files changed, 192 insertions(+), 111 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index ed49d6b3..f17832f7 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -45,6 +45,11 @@ def get_projects(self, query_params: Dict) -> Dict: return projects + def get_cost_centers(self, query_params: Dict) -> Dict: + cost_centers = self.connection.v1.fyler.cost_centers.list(query_params=query_params) + return cost_centers + + @staticmethod def get_currencies(): return ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 658f5b62..9208b595 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -110,6 +110,15 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: projects = fyle_expense.get_projects(projects_query_params) - modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects) + cost_centers_query_params = { + 'offset': 0, + 'limit': '100', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) + + modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects, cost_centers=cost_centers) slack_client.views_update(view=modal, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index d36c9740..b5314107 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -41,6 +41,7 @@ def _initialize_block_action_handlers(self): # Dynamic options 'category': self.handle_category_select, 'project': self.handle_project_select, + 'billable': self.handle_billable # 'currency': self.handle_currency_select } @@ -149,6 +150,10 @@ def handle_notification_preference_selection(self, slack_payload: Dict, user_id: return JsonResponse({}, status=200) + def handle_billable(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + return JsonResponse({}) + + def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: project_id = slack_payload['actions'][0]['selected_option']['value'] @@ -250,6 +255,8 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str # user = utils.get_or_none(User, slack_user_id=user_id) + # slack_client = get_slack_client(team_id) + # fyle_profile = get_fyle_profile(user.fyle_refresh_token) # home_currency = fyle_profile['org']['currency'] @@ -258,28 +265,48 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str # if home_currency != selected_currency: # is_home_currency_selected = False + # blocks = slack_payload['view']['blocks'] + + # # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered + # current_ui_blocks = [] + # for block in blocks: + # if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: + # current_ui_blocks.append(block) + # fyle_expense = FyleExpense(user) - # default_expense_fields = fyle_expense.get_default_expense_fields() + # categories = None - # slack_client = get_slack_client(team_id) + # if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project']['selected_option'] is not None: + + # project_id = int(slack_payload['view']['state']['values']['project_block']['project']['selected_option']['value']) - # projects_query_params = { - # 'offset': 0, - # 'limit': '100', - # 'order': 'created_at.desc', - # 'is_enabled': 'eq.{}'.format(True) - # } + # project_query_params = { + # 'offset': 0, + # 'limit': '1', + # 'order': 'created_at.desc', + # 'id': 'eq.{}'.format(int(project_id)), + # 'is_enabled': 'eq.{}'.format(True) + # } - # projects = fyle_expense.get_projects(projects_query_params) + # project = fyle_expense.get_projects(project_query_params) - # expense_form = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects) + # query_params = { + # 'offset': 0, + # 'limit': '20', + # 'order': 'created_at.desc', + # 'is_enabled': 'eq.{}'.format(True), + # 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', + # 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) + # } - # if is_home_currency_selected is False: + # categories = fyle_expense.get_categories(query_params) + # expense_form = expense_messages.expense_dialog_form(current_ui_blocks=current_ui_blocks, categories=categories) # slack_client.views_update(view_id=view_id, view=expense_form) + # return JsonResponse({}) def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 7500c265..d6a8d9e3 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -95,7 +95,7 @@ def handle_currency_suggestion(self, slack_payload: Dict, user_id: str, team_id: currency_options = [] for currency in currencies: - if currency_value_entered.upper() in currency: + if currency.startswith(currency_value_entered.upper()): option = { 'text': { 'type': 'plain_text', diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 3cae1130..deb6bc3a 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -46,22 +46,7 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: current_ui_blocks.append(block) - # Get projects from UI blocks itself, since projects won't be updated in the short span of expense form - project_options = [] - for block in blocks: - if block['block_id'] == 'project_block': - for option in block['element']['options']: - project_options.append({ - 'display_name': option['text']['text'], - 'id': int(option['value']) - }) - - projects = { - 'data': project_options, - 'count': len(project_options) - } - - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, projects=projects, categories=categories) + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, categories=categories) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -97,7 +82,6 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: current_ui_blocks.append(block) categories = None - projects = None if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project']['selected_option'] is not None: @@ -124,23 +108,6 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: categories = fyle_expense.get_categories(query_params) - blocks = slack_payload['view']['blocks'] - - # Get projects from UI blocks itself, since projects won't be updated in the short span of expense form - project_options = [] - for block in blocks: - if block['block_id'] == 'project_block': - for option in block['element']['options']: - project_options.append({ - 'display_name': option['text']['text'], - 'id': int(option['value']) - }) - - projects = { - 'data': project_options, - 'count': len(project_options) - } - - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, projects=projects, categories=categories) + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, categories=categories) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 1fd3bc58..ca838a9f 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -60,7 +60,7 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) custom_field_mappings = {} if 'custom_field' in key: for inner_key, inner_value in value.items(): - if inner_value['type'] == 'static_select': + if inner_value['type'] in ['static_select', 'external_select']: custom_field_mappings[inner_key] = inner_value['selected_option']['value'] if inner_value['type'] == 'multi_static_select': values_list = [] @@ -79,7 +79,7 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) custom_fields.append(custom_field_mappings) else: for inner_key, inner_value in value.items(): - if inner_value['type'] == 'static_select': + if inner_value['type'] in ['static_select', 'external_select']: expense_mapping[inner_key] = inner_value['selected_option']['value'] if inner_value['type'] == 'multi_static_select': values_list = [] diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 1bcb934f..8925c7ee 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -3,7 +3,7 @@ # is_additional_field is for fields which are not custom fields but are part of a specific categories -def generate_fields_ui(field_details: Dict, is_additional_field: bool = False) -> Dict: +def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> Dict: block_id = '{}_block'.format(field_details['column_name']) action_id = field_details['column_name'] @@ -119,7 +119,7 @@ def generate_fields_ui(field_details: Dict, is_additional_field: bool = False) - return custom_field -def generate_expense_fields_mandatory_mapping(expense_fields: Dict) -> Dict: +def generate_expense_fields_type_mandatory_mapping(expense_fields: List[Dict]) -> Dict: mandatory_mapping = { 'purpose': False, 'txn_dt': False, @@ -135,7 +135,7 @@ def generate_expense_fields_mandatory_mapping(expense_fields: Dict) -> Dict: return mandatory_mapping -def get_default_fields_blocks(mandatory_mapping: Dict) -> List: +def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: default_fields_blocks = [ { 'type': 'input', @@ -147,7 +147,7 @@ def get_default_fields_blocks(mandatory_mapping: Dict) -> List: 'text': 'Select Currency', 'emoji': True, }, - 'min_query_length': 0, + 'min_query_length': 1, 'action_id': 'currency', }, 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, @@ -166,48 +166,27 @@ def get_default_fields_blocks(mandatory_mapping: Dict) -> List: }, 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, }, - { + ] + + if field_type_mandatory_mapping['txn_dt'] is True: + date_of_spend_block = { 'type': 'input', - 'block_id': 'default_field_payment_mode_block', + 'block_id': 'default_field_date_of_spend_block', 'element': { - 'type': 'static_select', + 'type': 'datepicker', + 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), 'placeholder': { 'type': 'plain_text', - 'text': 'Select Payment Mode', + 'text': 'Select a date', 'emoji': True, }, - 'initial_option': { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by me', - 'emoji': True, - }, - 'value': 'paid_by_me', - }, - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by me', - 'emoji': True, - }, - 'value': 'paid_by_me', - }, - { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by company', - 'emoji': True, - }, - 'value': 'paid_by_company', - }, - ], - 'action_id': 'payment_mode', + 'action_id': 'spent_at', }, - 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, - }, - ] - if mandatory_mapping['purpose'] is True: + 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, + } + default_fields_blocks.append(date_of_spend_block) + + if field_type_mandatory_mapping['purpose'] is True: purpose_block = { 'type': 'input', 'block_id': 'default_field_purpose_block', @@ -224,25 +203,49 @@ def get_default_fields_blocks(mandatory_mapping: Dict) -> List: } default_fields_blocks.append(purpose_block) - if mandatory_mapping['txn_dt'] is True: - date_of_spend_block = { - 'type': 'input', - 'block_id': 'default_field_date_of_spend_block', - 'element': { - 'type': 'datepicker', - 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), - 'placeholder': { + payment_mode_block = { + 'type': 'input', + 'block_id': 'default_field_payment_mode_block', + 'element': { + 'type': 'static_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select Payment Mode', + 'emoji': True, + }, + 'initial_option': { + 'text': { 'type': 'plain_text', - 'text': 'Select a date', + 'text': 'Paid by me', 'emoji': True, }, - 'action_id': 'spent_at', + 'value': 'paid_by_me', }, - 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, - } - default_fields_blocks.append(date_of_spend_block) + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by me', + 'emoji': True, + }, + 'value': 'paid_by_me', + }, + { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by company', + 'emoji': True, + }, + 'value': 'paid_by_company', + }, + ], + 'action_id': 'payment_mode', + }, + 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, + } + default_fields_blocks.append(payment_mode_block) - if mandatory_mapping['vendor_id'] is True: + if field_type_mandatory_mapping['vendor_id'] is True: merchant_block = { 'type': 'input', 'block_id': 'default_field_merchant_block', @@ -262,7 +265,7 @@ def get_default_fields_blocks(mandatory_mapping: Dict) -> List: return default_fields_blocks -def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, custom_fields: Dict = None, categories: Dict = None, current_ui_blocks: List = None) -> Dict: +def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cost_centers: Dict = None, custom_fields: Dict = None, categories: Dict = None, current_ui_blocks: List = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', @@ -275,13 +278,23 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cust # If current UI blocks are passed use them as it is for faster processing of UI elements. if current_ui_blocks is not None: - view['blocks'] = current_ui_blocks + ui_blocks = [] + # Removing cost center of present to maintain order of cost center at end of form + cost_center_block = None + for block in current_ui_blocks: + if block['block_id'] == 'cost_center_block': + cost_center_block = block + else: + ui_blocks.append(block) + + view['blocks'] = ui_blocks + else: - mandatory_mapping = generate_expense_fields_mandatory_mapping(expense_fields) + field_type_mandatory_mapping = generate_expense_fields_type_mandatory_mapping(expense_fields) - view['blocks'] = get_default_fields_blocks(mandatory_mapping) + view['blocks'] = get_default_fields_blocks(field_type_mandatory_mapping) - if mandatory_mapping['project_id'] is True and projects is not None and projects['count'] > 0: + if field_type_mandatory_mapping['project_id'] is True and projects is not None and projects['count'] > 0: project_block = { 'type': 'input', 'block_id': 'project_block', @@ -294,10 +307,15 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cust } project_options = [] for project in projects['data']: + + project_display_name = project['display_name'] + if project['name'] == project['sub_project']: + project_display_name = project['name'] + project_options.append({ 'text': { 'type': 'plain_text', - 'text': project['display_name'], + 'text': project_display_name, 'emoji': True, }, 'value': str(project['id']), @@ -306,6 +324,27 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cust view['blocks'].append(project_block) + billable_block = { + 'type': 'actions', + 'block_id': 'billable_block', + 'elements': [ + { + 'type': 'checkboxes', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'Billable', + 'emoji': True + } + } + ], + 'action_id': 'billable' + } + ] + } + view['blocks'].append(billable_block) + # Since category block is dependent of projects sometimes, render them everytime. category_block = { 'type': 'input', @@ -328,10 +367,15 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cust category_options = [] for category in categories['data']: + + category_display_name = category['display_name'] + if category['name'] == category['sub_category']: + category_display_name = category['name'] + category_options.append({ 'text': { 'type': 'plain_text', - 'text': category['display_name'], + 'text': category_display_name, 'emoji': True, }, 'value': str(category['id']), @@ -354,9 +398,38 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cust if field['is_custom'] is False: is_additional_field = True - custom_field = generate_fields_ui(field, is_additional_field=is_additional_field) + custom_field = generate_field_ui(field, is_additional_field=is_additional_field) view['blocks'].append(custom_field) + # Render cost center from current ui blocks if present + # This check is needed again here to maintain order + if current_ui_blocks is not None and cost_center_block is not None: + view['blocks'].append(cost_center_block) + else: + if field_type_mandatory_mapping['cost_center_id'] is True and cost_centers is not None and cost_centers['count'] > 0: + cost_center_block = { + 'type': 'input', + 'block_id': 'cost_center_block', + 'element': { + 'type': 'static_select', + 'action_id': 'cost_center', + }, + 'label': {'type': 'plain_text', 'text': 'Cost Center', 'emoji': True}, + } + cost_center_options = [] + for cost_center in cost_centers['data']: + cost_center_options.append({ + 'text': { + 'type': 'plain_text', + 'text': cost_center['name'], + 'emoji': True, + }, + 'value': str(cost_center['id']), + }) + cost_center_block['element']['options'] = cost_center_options + + view['blocks'].append(cost_center_block) + return view From b27f03a6e96829d4092396d2feea565346f947c9 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 22 Sep 2021 16:47:20 +0530 Subject: [PATCH 22/85] Moved current ui logic to message for reusability --- .../interactives/block_action_handlers.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 23 +++++-------------- fyle_slack_app/slack/ui/expenses/messages.py | 11 ++++++--- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index b5314107..4e4db391 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -270,7 +270,7 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str # # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered # current_ui_blocks = [] # for block in blocks: - # if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: + # if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id'] and 'additional_field' not in block['block_id']: # current_ui_blocks.append(block) # fyle_expense = FyleExpense(user) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index deb6bc3a..b7086951 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -34,17 +34,12 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st categories = fyle_expense.get_categories(query_params) - blocks = slack_payload['view']['blocks'] + current_ui_blocks = slack_payload['view']['blocks'] # Removing loading info from below project input element - project_loading_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'project_loading_block'), None) - blocks.pop(project_loading_block_index) + project_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'project_loading_block'), None) + current_ui_blocks.pop(project_loading_block_index) - # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered - current_ui_blocks = [] - for block in blocks: - if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: - current_ui_blocks.append(block) new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, categories=categories) @@ -69,17 +64,11 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: custom_fields = fyle_expense.get_expense_fields(custom_fields_query_params) - blocks = slack_payload['view']['blocks'] + current_ui_blocks = slack_payload['view']['blocks'] # Removing loading info from below category input element - category_loading_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'category_loading_block'), None) - blocks.pop(category_loading_block_index) - - # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered - current_ui_blocks = [] - for block in blocks: - if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id']: - current_ui_blocks.append(block) + category_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'category_loading_block'), None) + current_ui_blocks.pop(category_loading_block_index) categories = None diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 8925c7ee..95101f5c 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -9,8 +9,9 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> # We need to define addtional fields as custom fields so that we can clear them out in form when category is changed if field_details['is_custom'] is True or is_additional_field is True: - block_id = '{}_custom_field_{}_block'.format(field_details['type'], field_details['column_name']) + block_id = '{}_additional_field_{}_block'.format(field_details['type'], field_details['column_name']) if field_details['is_custom'] is True: + block_id = '{}_custom_field_{}_block'.format(field_details['type'], field_details['column_name']) action_id = '{}'.format(field_details['field_name']) if field_details['type'] in ['NUMBER', 'TEXT']: @@ -279,12 +280,16 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cost # If current UI blocks are passed use them as it is for faster processing of UI elements. if current_ui_blocks is not None: ui_blocks = [] - # Removing cost center of present to maintain order of cost center at end of form + cost_center_block = None for block in current_ui_blocks: + + # Removing cost center if present to maintain order of cost center at end of form if block['block_id'] == 'cost_center_block': cost_center_block = block - else: + + # Removing these block as these should be rendered conditionally + if block['block_id'] not in ['custom_field', 'category_block', 'cost_center_block', 'additional_field']: ui_blocks.append(block) view['blocks'] = ui_blocks From ab8f255f91e6a404f2efa13bf50ebf3f06072f2f Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 22 Sep 2021 17:53:52 +0530 Subject: [PATCH 23/85] Added validation checks --- .../interactives/view_submission_handlers.py | 208 ++++++++++-------- fyle_slack_app/slack/ui/expenses/messages.py | 22 +- 2 files changed, 132 insertions(+), 98 deletions(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index ca838a9f..36971a0f 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,5 +1,6 @@ +import datetime import json -from typing import Callable, Dict +from typing import Callable, Dict, Union from django.http.response import JsonResponse @@ -13,8 +14,7 @@ class ViewSubmissionHandler: # Maps action_id with it's respective function def _initialize_view_submission_handlers(self): self._view_submission_handlers = { - 'create_expense': self.handle_create_expense, - 'policy_violation': self.handle_policy_violation + 'create_expense': self.handle_create_expense } @@ -53,84 +53,21 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) form_values = slack_payload['view']['state']['values'] print('REACHED CREATE EXPENSE -> ', form_values) - expense_mapping = {} - custom_fields = [] + expense_details, validation_errors = self.extract_form_values_and_validate(form_values) - for key, value in form_values.items(): - custom_field_mappings = {} - if 'custom_field' in key: - for inner_key, inner_value in value.items(): - if inner_value['type'] in ['static_select', 'external_select']: - custom_field_mappings[inner_key] = inner_value['selected_option']['value'] - if inner_value['type'] == 'multi_static_select': - values_list = [] - for val in inner_value['selected_options']: - values_list.append(val['value']) - custom_field_mappings[inner_key] = values_list - elif inner_value['type'] == 'datepicker': - custom_field_mappings[inner_key] = inner_value['selected_date'] - elif inner_value['type'] == 'plain_text_input': - custom_field_mappings[inner_key] = inner_value['value'] - elif inner_value['type'] == 'checkboxes': - custom_field_mappings[inner_key] = False - if len(inner_value['selected_options']) > 0: - custom_field_mappings[inner_key] = True + print('VALIDATION ERRORS -> ', validation_errors) - custom_fields.append(custom_field_mappings) - else: - for inner_key, inner_value in value.items(): - if inner_value['type'] in ['static_select', 'external_select']: - expense_mapping[inner_key] = inner_value['selected_option']['value'] - if inner_value['type'] == 'multi_static_select': - values_list = [] - for val in inner_value['selected_options']: - values_list.append(val['value']) - expense_mapping[inner_key] = values_list - elif inner_value['type'] == 'datepicker': - expense_mapping[inner_key] = inner_value['selected_date'] - elif inner_value['type'] == 'plain_text_input': - expense_mapping[inner_key] = inner_value['value'] - elif inner_value['type'] == 'checkboxes': - expense_mapping[inner_key] = False - if len(inner_value['selected_options']) > 0: - expense_mapping[inner_key] = True + # If valdiation errors are present then return errors + if bool(validation_errors) is True: + return JsonResponse({ + 'response_action': 'errors', + 'errors': validation_errors + }) - expense_mapping['custom_fields'] = custom_fields - - print('EXPENSE -> ', json.dumps(expense_mapping, indent=2)) + print('EXPENSE -> ', json.dumps(expense_details, indent=2)) slack_client = slack_utils.get_slack_client(team_id) - # policy_view = { - # "type": "modal", - # "private_metadata": encode_state(em), - # "callback_id": "policy_violation", - # "title": {"type": "plain_text", "text": "Policy Violation", "emoji": True}, - # "submit": {"type": "plain_text", "text": "Add Reason", "emoji": True}, - # "close": {"type": "plain_text", "text": "Cancel", "emoji": True}, - # "blocks": [ - # { - # "type": "input", - # "block_id": "amount_block", - # "element": { - # "type": "plain_text_input", - # "placeholder": { - # "type": "plain_text", - # "text": "Enter Reason", - # "emoji": True, - # }, - # "action_id": "amount", - # }, - # "label": {"type": "plain_text", "text": "Enter reason", "emoji": True}, - # } - # ] - # } - # print('TRIGGER ID -> ', slack_payload['trigger_id']) - # # a = slack_client.views_push(trigger_id=slack_payload['trigger_id'], view=policy_view) - # # print('A -> ', a) - # return JsonResponse({ - # 'response_action': 'push', - # 'view': policy_view - # }, status=200) + blocks = [ { 'type': 'section', @@ -143,15 +80,15 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) { 'type': 'section', 'fields': [ - {'type': 'mrkdwn', 'text': '*Amount*: \n {} {}'.format(expense_mapping['currency'], expense_mapping['amount'])}, - {'type': 'mrkdwn', 'text': '*Merchant*: \n {}'.format(expense_mapping['merchant'])}, + {'type': 'mrkdwn', 'text': '*Amount*: \n {} {}'.format(expense_details['currency'], expense_details['amount'])}, + {'type': 'mrkdwn', 'text': '*Merchant*: \n {}'.format(expense_details['merchant'])}, ], }, { 'type': 'section', 'fields': [ - {'type': 'mrkdwn', 'text': '*Date of Spend*: \n {}'.format(expense_mapping['spent_at'])}, - {'type': 'mrkdwn', 'text': '*Purpose*: \n {}'.format(expense_mapping['purpose'])}, + {'type': 'mrkdwn', 'text': '*Date of Spend*: \n {}'.format(expense_details['spent_at'])}, + {'type': 'mrkdwn', 'text': '*Purpose*: \n {}'.format(expense_details['purpose'])}, ], } ] @@ -161,8 +98,8 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) 'fields': [] } - if len(custom_fields) > 0: - for custom_field in custom_fields: + if len(expense_details['custom_fields']) > 0: + for custom_field in expense_details['custom_fields']: for cf in custom_field.keys(): if isinstance(custom_field[cf], list): value = ', '.join(custom_field[cf]) @@ -209,8 +146,105 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) return JsonResponse({}) - def handle_policy_violation(self, slack_payload: Dict, user_id: str, team_id: str): - print('POLICY PAYLOAD -> ', slack_payload) - return JsonResponse({ - 'response_action': 'clear' - }) + def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dict]: + expense_details = {} + validation_errors = {} + custom_fields = [] + + for key, value in form_values.items(): + custom_field_mappings = {} + if 'custom_field' in key: + for inner_key, inner_value in value.items(): + + if inner_value['type'] in ['static_select', 'external_select']: + custom_field_mappings[inner_key] = inner_value['selected_option']['value'] + + if inner_value['type'] == 'multi_static_select': + + values_list = [] + for val in inner_value['selected_options']: + values_list.append(val['value']) + custom_field_mappings[inner_key] = values_list + + elif inner_value['type'] == 'datepicker': + + if datetime.datetime.strptime(inner_value['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): + validation_errors[key] = 'Date selected cannot be in future' + + custom_field_mappings[inner_key] = inner_value['selected_date'] + + elif inner_value['type'] == 'plain_text_input': + + if 'TEXT' in key: + value = inner_value['value'].strip() + + elif 'NUMBER' in key: + value = inner_value['value'] + try: + value = float(inner_value['value']) + + if value < 0: + validation_errors[key] = 'Negative numbers are not allowed' + + value = round(value, 2) + + except ValueError: + validation_errors[key] = 'Only numbers are allowed in this fields' + + custom_field_mappings[inner_key] = value + + elif inner_value['type'] == 'checkboxes': + + custom_field_mappings[inner_key] = False + if len(inner_value['selected_options']) > 0: + custom_field_mappings[inner_key] = True + + custom_fields.append(custom_field_mappings) + else: + for inner_key, inner_value in value.items(): + + if inner_value['type'] in ['static_select', 'external_select']: + expense_details[inner_key] = inner_value['selected_option']['value'] + + if inner_value['type'] == 'multi_static_select': + + values_list = [] + for val in inner_value['selected_options']: + values_list.append(val['value']) + expense_details[inner_key] = values_list + + + elif inner_value['type'] == 'datepicker': + + if datetime.datetime.strptime(inner_value['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): + validation_errors[key] = 'Date selected cannot be for future' + + expense_details[inner_key] = inner_value['selected_date'] + + elif inner_value['type'] == 'plain_text_input': + if 'TEXT' in key: + value = inner_value['value'].strip() + + elif 'NUMBER' in key: + value = inner_value['value'] + try: + value = float(inner_value['value']) + + if value < 0: + validation_errors[key] = 'Negative numbers are not allowed' + + value = round(value, 2) + + except ValueError: + validation_errors[key] = 'Only numbers are allowed in this fields' + expense_details[inner_key] = value + + elif inner_value['type'] == 'checkboxes': + + expense_details[inner_key] = False + if len(inner_value['selected_options']) > 0: + expense_details[inner_key] = True + + expense_details['custom_fields'] = custom_fields + + return expense_details, validation_errors diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 95101f5c..8624b7b5 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -140,7 +140,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: default_fields_blocks = [ { 'type': 'input', - 'block_id': 'default_field_currency_block', + 'block_id': 'SELECT_default_field_currency_block', 'element': { 'type': 'external_select', 'placeholder': { @@ -155,7 +155,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: }, { 'type': 'input', - 'block_id': 'default_field_amount_block', + 'block_id': 'NUMBER_default_field_amount_block', 'element': { 'type': 'plain_text_input', 'placeholder': { @@ -172,7 +172,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: if field_type_mandatory_mapping['txn_dt'] is True: date_of_spend_block = { 'type': 'input', - 'block_id': 'default_field_date_of_spend_block', + 'block_id': 'DATE_default_field_date_of_spend_block', 'element': { 'type': 'datepicker', 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), @@ -190,7 +190,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: if field_type_mandatory_mapping['purpose'] is True: purpose_block = { 'type': 'input', - 'block_id': 'default_field_purpose_block', + 'block_id': 'TEXT_default_field_purpose_block', 'element': { 'type': 'plain_text_input', 'placeholder': { @@ -206,7 +206,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: payment_mode_block = { 'type': 'input', - 'block_id': 'default_field_payment_mode_block', + 'block_id': 'SELECT_default_field_payment_mode_block', 'element': { 'type': 'static_select', 'placeholder': { @@ -220,7 +220,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: 'text': 'Paid by me', 'emoji': True, }, - 'value': 'paid_by_me', + 'value': 'true', }, 'options': [ { @@ -229,7 +229,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: 'text': 'Paid by me', 'emoji': True, }, - 'value': 'paid_by_me', + 'value': 'true', }, { 'text': { @@ -237,10 +237,10 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: 'text': 'Paid by company', 'emoji': True, }, - 'value': 'paid_by_company', + 'value': 'false', }, ], - 'action_id': 'payment_mode', + 'action_id': 'is_reimbursable', }, 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, } @@ -249,7 +249,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: if field_type_mandatory_mapping['vendor_id'] is True: merchant_block = { 'type': 'input', - 'block_id': 'default_field_merchant_block', + 'block_id': 'TEXT_default_field_merchant_block', 'element': { 'type': 'plain_text_input', 'placeholder': { @@ -387,7 +387,7 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cost }) category_block['element']['options'] = category_options - category_block['element']['initial_option'] = category_options[0] + # category_block['element']['initial_option'] = category_options[0] else: category_block['element']['min_query_length'] = 0 From 98d061cafb7ff4b9c70d6689a05d13dcb25b62b6 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Thu, 23 Sep 2021 15:59:47 +0530 Subject: [PATCH 24/85] Projects, cost centers & category as dynamic selects --- fyle_slack_app/slack/commands/tasks.py | 15 +- .../interactives/block_action_handlers.py | 6 +- .../interactives/block_suggestion_handlers.py | 113 ++++++++- fyle_slack_app/slack/interactives/tasks.py | 37 +-- fyle_slack_app/slack/ui/expenses/messages.py | 216 +++++++++--------- 5 files changed, 238 insertions(+), 149 deletions(-) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 9208b595..8cc8062f 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -103,22 +103,31 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: projects_query_params = { 'offset': 0, - 'limit': '100', + 'limit': '1', 'order': 'created_at.desc', 'is_enabled': 'eq.{}'.format(True) } projects = fyle_expense.get_projects(projects_query_params) + is_project_available = True if projects['count'] > 0 else False + cost_centers_query_params = { 'offset': 0, - 'limit': '100', + 'limit': '1', 'order': 'created_at.desc', 'is_enabled': 'eq.{}'.format(True) } cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) - modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, projects=projects, cost_centers=cost_centers) + is_cost_centers_available = True if cost_centers['count'] > 0 else False + + fields_render_property = { + 'is_projects_available': is_project_available, + 'is_cost_centers_available': is_cost_centers_available + } + + modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, fields_render_property=fields_render_property) slack_client.views_update(view=modal, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 4e4db391..45ffa263 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -39,9 +39,9 @@ def _initialize_block_action_handlers(self): 'expense_commented_notification_preference': self.handle_notification_preference_selection, # Dynamic options - 'category': self.handle_category_select, - 'project': self.handle_project_select, - 'billable': self.handle_billable + 'category_id': self.handle_category_select, + 'project_id': self.handle_project_select, + 'is_billable': self.handle_billable # 'currency': self.handle_currency_select } diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index d6a8d9e3..f8b83e13 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -1,3 +1,4 @@ +import json from typing import Dict, List from django.http import JsonResponse @@ -18,7 +19,9 @@ class BlockSuggestionHandler: # Maps action_id with it's respective function def _initialize_block_suggestion_handlers(self): self._block_suggestion_handlers = { - 'category': self.handle_category_suggestion, + 'category_id': self.handle_category_suggestion, + 'project_id': self.handle_project_suggestion, + 'cost_center_id': self.handle_cost_center_suggestion, 'currency': self.handle_currency_suggestion } @@ -57,27 +60,52 @@ def handle_block_suggestions(self, slack_payload: Dict, user_id: str, team_id: s def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + print('CATEGORY SUGGESTION -> ', json.dumps(slack_payload['view']['state']['values'], indent=2)) + user = utils.get_or_none(User, slack_user_id=user_id) category_value_entered = slack_payload['value'] - query_params = { + + fyle_expense = FyleExpense(user) + + category_query_params = { 'offset': 0, - 'limit': '100', + 'limit': '30', 'order': 'display_name.asc', 'display_name': 'ilike.%{}%'.format(category_value_entered), 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', 'is_enabled': 'eq.{}'.format(True) } - fyle_expense = FyleExpense(user) - suggested_categories = fyle_expense.get_categories(query_params) + if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project_id']['selected_option'] is not None: + + project_id = int(slack_payload['view']['state']['values']['project_block']['project_id']['selected_option']['value']) + + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } + + project = fyle_expense.get_projects(project_query_params) + + category_query_params['id'] = 'in.{}'.format(tuple(project['data'][0]['category_ids'])) + + suggested_categories = fyle_expense.get_categories(category_query_params) category_options = [] if suggested_categories['count'] > 0: for category in suggested_categories['data']: + + category_display_name = category['display_name'] + if category['name'] == category['sub_category']: + category_display_name = category['name'] + option = { 'text': { 'type': 'plain_text', - 'text': category['display_name'], + 'text': category_display_name, 'emoji': True, }, 'value': str(category['id']), @@ -89,7 +117,7 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: def handle_currency_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: - currencies = ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] + currencies = FyleExpense.get_currencies() currency_value_entered = slack_payload['value'] @@ -107,3 +135,74 @@ def handle_currency_suggestion(self, slack_payload: Dict, user_id: str, team_id: currency_options.append(option) return currency_options + + + def handle_project_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + + # print('Project SUGGESTION -> ', json.dumps(slack_payload['view']['state'], indent=2)) + + user = utils.get_or_none(User, slack_user_id=user_id) + project_value_entered = slack_payload['value'] + query_params = { + 'offset': 0, + 'limit': '30', + 'order': 'display_name.asc', + 'display_name': 'ilike.%{}%'.format(project_value_entered), + 'is_enabled': 'eq.{}'.format(True) + } + + fyle_expense = FyleExpense(user) + suggested_projects = fyle_expense.get_projects(query_params) + + project_options = [] + if suggested_projects['count'] > 0: + for project in suggested_projects['data']: + + project_display_name = project['display_name'] + if project['name'] == project['sub_project']: + project_display_name = project['name'] + + option = { + 'text': { + 'type': 'plain_text', + 'text': project_display_name, + 'emoji': True, + }, + 'value': str(project['id']), + } + project_options.append(option) + + return project_options + + + def handle_cost_center_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + + # print('COST CENTER SUGGESTION -> ', json.dumps(slack_payload['view']['state'], indent=2)) + + user = utils.get_or_none(User, slack_user_id=user_id) + cost_center_value_entered = slack_payload['value'] + query_params = { + 'offset': 0, + 'limit': '30', + 'order': 'name.asc', + 'name': 'ilike.%{}%'.format(cost_center_value_entered), + 'is_enabled': 'eq.{}'.format(True) + } + + fyle_expense = FyleExpense(user) + suggested_cost_centers = fyle_expense.get_cost_centers(query_params) + + cost_center_options = [] + if suggested_cost_centers['count'] > 0: + for cost_center in suggested_cost_centers['data']: + option = { + 'text': { + 'type': 'plain_text', + 'text': cost_center['name'], + 'emoji': True, + }, + 'value': str(cost_center['id']), + } + cost_center_options.append(option) + + return cost_center_options diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index b7086951..068576b1 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -23,25 +23,17 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st project = fyle_expense.get_projects(project_query_params) - query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True), - 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', - 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) - } - - categories = fyle_expense.get_categories(query_params) - current_ui_blocks = slack_payload['view']['blocks'] # Removing loading info from below project input element project_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'project_loading_block'), None) current_ui_blocks.pop(project_loading_block_index) + fields_render_property = { + 'is_projects_available': True + } - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, categories=categories) + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, fields_render_property=fields_render_property, selected_project=project['data'][0]) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -70,11 +62,13 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: category_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'category_loading_block'), None) current_ui_blocks.pop(category_loading_block_index) - categories = None + fields_render_property = { + 'is_projects_available': False + } - if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project']['selected_option'] is not None: + if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project_id']['selected_option'] is not None: - project_id = int(slack_payload['view']['state']['values']['project_block']['project']['selected_option']['value']) + project_id = int(slack_payload['view']['state']['values']['project_block']['project_id']['selected_option']['value']) project_query_params = { 'offset': 0, @@ -86,17 +80,8 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: project = fyle_expense.get_projects(project_query_params) - query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True), - 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', - 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) - } - - categories = fyle_expense.get_categories(query_params) + fields_render_property['is_projects_available'] = True - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, categories=categories) + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, selected_project=project['data'][0], fields_render_property=fields_render_property) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 8624b7b5..28d911ac 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -266,7 +266,101 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: return default_fields_blocks -def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cost_centers: Dict = None, custom_fields: Dict = None, categories: Dict = None, current_ui_blocks: List = None) -> Dict: +def get_projects_and_billable_block(selected_project: Dict = None) -> Dict: + project_block = { + 'type': 'input', + 'block_id': 'project_block', + 'dispatch_action': True, + 'element': { + 'min_query_length': 0, + 'type': 'external_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Travel', + 'emoji': True, + }, + + 'action_id': 'project_id', + }, + 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, + } + if selected_project is not None: + project_display_name = selected_project['display_name'] + if selected_project['name'] == selected_project['sub_project']: + project_display_name = selected_project['name'] + project_block['element']['initial_option'] = { + 'text': { + 'type': 'plain_text', + 'text': project_display_name, + 'emoji': True, + }, + 'value': str(selected_project['id']), + } + + billable_block = { + 'type': 'actions', + 'block_id': 'billable_block', + 'elements': [ + { + 'type': 'checkboxes', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'Billable', + 'emoji': True + } + } + ], + 'action_id': 'is_billable' + } + ] + } + + return project_block, billable_block + + +def get_categories_block() -> Dict: + category_block = { + 'type': 'input', + 'block_id': 'category_block', + 'dispatch_action': True, + 'element': { + 'type': 'external_select', + 'min_query_length': 0, + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Food', + 'emoji': True, + }, + 'action_id': 'category_id', + }, + 'label': {'type': 'plain_text', 'text': 'Category', 'emoji': True}, + } + + return category_block + + +def get_cost_centers_block() -> Dict: + cost_centers_block = { + 'type': 'input', + 'block_id': 'cost_center_block', + 'element': { + 'type': 'external_select', + 'min_query_length': 0, + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Accounting', + 'emoji': True, + }, + 'action_id': 'cost_center_id', + }, + 'label': {'type': 'plain_text', 'text': 'Cost Center', 'emoji': True}, + } + return cost_centers_block + + +def expense_dialog_form(expense_fields: Dict = None, fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', @@ -288,8 +382,9 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cost if block['block_id'] == 'cost_center_block': cost_center_block = block - # Removing these block as these should be rendered conditionally - if block['block_id'] not in ['custom_field', 'category_block', 'cost_center_block', 'additional_field']: + # Removing these block as these should be rendered from current/cached UI block + ignore_block_id_list = ['custom_field', 'project_block', 'billable_block', 'category_block', 'cost_center_block', 'additional_field'] + if any(substring in block['block_id'] for substring in ignore_block_id_list) is False: ui_blocks.append(block) view['blocks'] = ui_blocks @@ -299,97 +394,16 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cost view['blocks'] = get_default_fields_blocks(field_type_mandatory_mapping) - if field_type_mandatory_mapping['project_id'] is True and projects is not None and projects['count'] > 0: - project_block = { - 'type': 'input', - 'block_id': 'project_block', - 'dispatch_action': True, - 'element': { - 'type': 'static_select', - 'action_id': 'project', - }, - 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, - } - project_options = [] - for project in projects['data']: + if fields_render_property['is_projects_available'] is True: - project_display_name = project['display_name'] - if project['name'] == project['sub_project']: - project_display_name = project['name'] + project_block, billable_block = get_projects_and_billable_block(selected_project) - project_options.append({ - 'text': { - 'type': 'plain_text', - 'text': project_display_name, - 'emoji': True, - }, - 'value': str(project['id']), - }) - project_block['element']['options'] = project_options - - view['blocks'].append(project_block) + view['blocks'].append(project_block) - billable_block = { - 'type': 'actions', - 'block_id': 'billable_block', - 'elements': [ - { - 'type': 'checkboxes', - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'Billable', - 'emoji': True - } - } - ], - 'action_id': 'billable' - } - ] - } - view['blocks'].append(billable_block) + view['blocks'].append(billable_block) # Since category block is dependent of projects sometimes, render them everytime. - category_block = { - 'type': 'input', - 'block_id': 'category_block', - 'dispatch_action': True, - 'element': { - 'type': 'external_select', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Eg. Travel', - 'emoji': True, - }, - 'action_id': 'category', - }, - 'label': {'type': 'plain_text', 'text': 'Category', 'emoji': True}, - } - - if categories is not None and categories['count'] > 0: - category_block['element']['type'] = 'static_select' - category_options = [] - - for category in categories['data']: - - category_display_name = category['display_name'] - if category['name'] == category['sub_category']: - category_display_name = category['name'] - - category_options.append({ - 'text': { - 'type': 'plain_text', - 'text': category_display_name, - 'emoji': True, - }, - 'value': str(category['id']), - }) - - category_block['element']['options'] = category_options - # category_block['element']['initial_option'] = category_options[0] - else: - category_block['element']['min_query_length'] = 0 + category_block = get_categories_block() view['blocks'].append(category_block) @@ -411,27 +425,9 @@ def expense_dialog_form(expense_fields: Dict = None, projects: Dict = None, cost if current_ui_blocks is not None and cost_center_block is not None: view['blocks'].append(cost_center_block) else: - if field_type_mandatory_mapping['cost_center_id'] is True and cost_centers is not None and cost_centers['count'] > 0: - cost_center_block = { - 'type': 'input', - 'block_id': 'cost_center_block', - 'element': { - 'type': 'static_select', - 'action_id': 'cost_center', - }, - 'label': {'type': 'plain_text', 'text': 'Cost Center', 'emoji': True}, - } - cost_center_options = [] - for cost_center in cost_centers['data']: - cost_center_options.append({ - 'text': { - 'type': 'plain_text', - 'text': cost_center['name'], - 'emoji': True, - }, - 'value': str(cost_center['id']), - }) - cost_center_block['element']['options'] = cost_center_options + if field_type_mandatory_mapping['cost_center_id'] is True and fields_render_property['is_cost_centers_available'] is True: + + cost_center_block = get_cost_centers_block() view['blocks'].append(cost_center_block) From 80918f01738644062309f8658b5544b570d802aa Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Thu, 23 Sep 2021 17:01:14 +0530 Subject: [PATCH 25/85] Moved out mandatory field check to FyleExpense and call api only if default fields are mandatory --- fyle_slack_app/fyle/expenses/views.py | 19 ++++++++- fyle_slack_app/slack/commands/tasks.py | 41 ++++++++++++-------- fyle_slack_app/slack/interactives/tasks.py | 17 ++++++-- fyle_slack_app/slack/ui/expenses/messages.py | 25 +++--------- 4 files changed, 60 insertions(+), 42 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index f17832f7..8b6402e1 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Dict, List from fyle.platform.platform import Platform @@ -53,3 +53,20 @@ def get_cost_centers(self, query_params: Dict) -> Dict: @staticmethod def get_currencies(): return ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] + + + @staticmethod + def get_expense_fields_type_mandatory_mapping(expense_fields: List[Dict]) -> Dict: + mandatory_mapping = { + 'purpose': False, + 'txn_dt': False, + 'vendor_id': False, + 'project_id': False, + 'cost_center_id': False + } + + for field in expense_fields['data']: + if field['column_name'] in mandatory_mapping: + mandatory_mapping[field['column_name']] = field['is_mandatory'] + + return mandatory_mapping diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 8cc8062f..5e3beeb5 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -101,33 +101,40 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: slack_client = slack_utils.get_slack_client(team_id) - projects_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } + field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) - projects = fyle_expense.get_projects(projects_query_params) + is_project_available = False + is_cost_centers_available = False - is_project_available = True if projects['count'] > 0 else False + if field_type_mandatory_mapping['project_id'] is True: + projects_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } - cost_centers_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } + projects = fyle_expense.get_projects(projects_query_params) + + is_project_available = True if projects['count'] > 0 else False + + if field_type_mandatory_mapping['cost_center_id'] is True: + cost_centers_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } - cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) + cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) - is_cost_centers_available = True if cost_centers['count'] > 0 else False + is_cost_centers_available = True if cost_centers['count'] > 0 else False fields_render_property = { 'is_projects_available': is_project_available, 'is_cost_centers_available': is_cost_centers_available } - modal = expense_messages.expense_dialog_form(expense_fields=default_expense_fields, fields_render_property=fields_render_property) + modal = expense_messages.expense_dialog_form(field_type_mandatory_mapping=field_type_mandatory_mapping, fields_render_property=fields_render_property) slack_client.views_update(view=modal, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 068576b1..c57951e0 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -30,10 +30,14 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st current_ui_blocks.pop(project_loading_block_index) fields_render_property = { - 'is_projects_available': True + 'is_projects_available': True, + 'is_cost_centers_available': False } - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, fields_render_property=fields_render_property, selected_project=project['data'][0]) + if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: + fields_render_property['is_cost_centers_available'] = True + + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, fields_render_property=fields_render_property, selected_project=project) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -63,9 +67,11 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: current_ui_blocks.pop(category_loading_block_index) fields_render_property = { - 'is_projects_available': False + 'is_projects_available': False, + 'is_cost_centers_available': False } + project = None if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project_id']['selected_option'] is not None: project_id = int(slack_payload['view']['state']['values']['project_block']['project_id']['selected_option']['value']) @@ -82,6 +88,9 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: fields_render_property['is_projects_available'] = True - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, selected_project=project['data'][0], fields_render_property=fields_render_property) + if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: + fields_render_property['is_cost_centers_available'] = True + + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 28d911ac..352b2654 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -120,22 +120,6 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> return custom_field -def generate_expense_fields_type_mandatory_mapping(expense_fields: List[Dict]) -> Dict: - mandatory_mapping = { - 'purpose': False, - 'txn_dt': False, - 'vendor_id': False, - 'project_id': False, - 'cost_center_id': False - } - - for field in expense_fields['data']: - if field['column_name'] in mandatory_mapping: - mandatory_mapping[field['column_name']] = field['is_mandatory'] - - return mandatory_mapping - - def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: default_fields_blocks = [ { @@ -285,6 +269,8 @@ def get_projects_and_billable_block(selected_project: Dict = None) -> Dict: 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, } if selected_project is not None: + selected_project = selected_project['data'][0] + project_display_name = selected_project['display_name'] if selected_project['name'] == selected_project['sub_project']: project_display_name = selected_project['name'] @@ -360,7 +346,7 @@ def get_cost_centers_block() -> Dict: return cost_centers_block -def expense_dialog_form(expense_fields: Dict = None, fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None) -> Dict: +def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', @@ -382,7 +368,7 @@ def expense_dialog_form(expense_fields: Dict = None, fields_render_property: Dic if block['block_id'] == 'cost_center_block': cost_center_block = block - # Removing these block as these should be rendered from current/cached UI block + # Removing these block as these should not be rendered from current/cached UI block ignore_block_id_list = ['custom_field', 'project_block', 'billable_block', 'category_block', 'cost_center_block', 'additional_field'] if any(substring in block['block_id'] for substring in ignore_block_id_list) is False: ui_blocks.append(block) @@ -390,7 +376,6 @@ def expense_dialog_form(expense_fields: Dict = None, fields_render_property: Dic view['blocks'] = ui_blocks else: - field_type_mandatory_mapping = generate_expense_fields_type_mandatory_mapping(expense_fields) view['blocks'] = get_default_fields_blocks(field_type_mandatory_mapping) @@ -425,7 +410,7 @@ def expense_dialog_form(expense_fields: Dict = None, fields_render_property: Dic if current_ui_blocks is not None and cost_center_block is not None: view['blocks'].append(cost_center_block) else: - if field_type_mandatory_mapping['cost_center_id'] is True and fields_render_property['is_cost_centers_available'] is True: + if fields_render_property['is_cost_centers_available'] is True: cost_center_block = get_cost_centers_block() From 7936ca3b60dda77f06bcbc13c3652fb8033c5b9d Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Fri, 24 Sep 2021 15:36:04 +0530 Subject: [PATCH 26/85] Support for non home currency and dynamic input value change --- .../interactives/block_action_handlers.py | 165 +++++++++++++----- fyle_slack_app/slack/interactives/tasks.py | 24 +-- fyle_slack_app/slack/ui/expenses/messages.py | 127 +++++++++++--- 3 files changed, 239 insertions(+), 77 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 45ffa263..7123fdfb 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -41,8 +41,9 @@ def _initialize_block_action_handlers(self): # Dynamic options 'category_id': self.handle_category_select, 'project_id': self.handle_project_select, - 'is_billable': self.handle_billable - # 'currency': self.handle_currency_select + 'is_billable': self.handle_billable, + 'currency': self.handle_currency_select, + 'amount': self.handle_amount_entered } @@ -248,65 +249,145 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str return JsonResponse({}, status=200) - # def handle_currency_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - # selected_currency = slack_payload['actions'][0]['selected_option']['value'] + def handle_currency_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + selected_currency = slack_payload['actions'][0]['selected_option']['value'] - # view_id = slack_payload['container']['view_id'] + view_id = slack_payload['container']['view_id'] + + user = utils.get_or_none(User, slack_user_id=user_id) + + slack_client = get_slack_client(team_id) + + fyle_profile = get_fyle_profile(user.fyle_refresh_token) + + home_currency = fyle_profile['org']['currency'] + + form_current_state = slack_payload['view']['state']['values'] + + additional_currency_details = None + + if home_currency != selected_currency: + exchange_rate = 70.12 + amount = form_current_state['NUMBER_default_field_amount_block']['amount']['value'] + + if amount is None or len(amount) == 0: + amount = 0 + else: + try: + amount = round(float(amount), 2) + except ValueError: + amount = 0 + + additional_currency_details = { + 'foreign_currency': selected_currency, + 'home_currency': home_currency, + 'total_amount': exchange_rate * amount + } + + current_ui_blocks = slack_payload['view']['blocks'] + + fields_render_property = { + 'is_projects_available': False, + 'is_cost_centers_available': False + } + + project = None + if 'project_block' in form_current_state: + + fields_render_property['is_projects_available'] = True + + if form_current_state['project_block']['project_id']['selected_option'] is not None: - # user = utils.get_or_none(User, slack_user_id=user_id) + fyle_expense = FyleExpense(user) - # slack_client = get_slack_client(team_id) + project_id = int(form_current_state['project_block']['project_id']['selected_option']['value']) - # fyle_profile = get_fyle_profile(user.fyle_refresh_token) + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } - # home_currency = fyle_profile['org']['currency'] + project = fyle_expense.get_projects(project_query_params) - # is_home_currency_selected = True - # if home_currency != selected_currency: - # is_home_currency_selected = False + if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: + fields_render_property['is_cost_centers_available'] = True - # blocks = slack_payload['view']['blocks'] + expense_form = expense_messages.expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) - # # Get current UI block for faster rendering, ignore custom field and category blocks since they are dynamically rendered - # current_ui_blocks = [] - # for block in blocks: - # if 'custom_field' not in block['block_id'] and 'category_block' not in block['block_id'] and 'additional_field' not in block['block_id']: - # current_ui_blocks.append(block) + slack_client.views_update(view_id=view_id, view=expense_form) - # fyle_expense = FyleExpense(user) + return JsonResponse({}) + + + def handle_amount_entered(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + amount_entered = slack_payload['actions'][0]['value'] + form_current_state = slack_payload['view']['state']['values'] + + view_id = slack_payload['container']['view_id'] + + user = utils.get_or_none(User, slack_user_id=user_id) + + slack_client = get_slack_client(team_id) - # categories = None + fyle_profile = get_fyle_profile(user.fyle_refresh_token) - # if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project']['selected_option'] is not None: + home_currency = fyle_profile['org']['currency'] - # project_id = int(slack_payload['view']['state']['values']['project_block']['project']['selected_option']['value']) + selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] - # project_query_params = { - # 'offset': 0, - # 'limit': '1', - # 'order': 'created_at.desc', - # 'id': 'eq.{}'.format(int(project_id)), - # 'is_enabled': 'eq.{}'.format(True) - # } + if amount_entered is None or len(amount_entered) == 0: + amount = 0 + else: + try: + amount = round(float(amount_entered), 2) + except ValueError: + amount = 0 + exchange_rate = 70.12 + additional_currency_details = { + 'foreign_currency': selected_currency, + 'home_currency': home_currency, + 'total_amount': exchange_rate * amount + } - # project = fyle_expense.get_projects(project_query_params) + current_ui_blocks = slack_payload['view']['blocks'] - # query_params = { - # 'offset': 0, - # 'limit': '20', - # 'order': 'created_at.desc', - # 'is_enabled': 'eq.{}'.format(True), - # 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', - # 'id': 'in.{}'.format(tuple(project['data'][0]['category_ids'])) - # } + fields_render_property = { + 'is_projects_available': False, + 'is_cost_centers_available': False + } + + project = None + if 'project_block' in form_current_state: + + fields_render_property['is_projects_available'] = True + + if form_current_state['project_block']['project_id']['selected_option'] is not None: + + fyle_expense = FyleExpense(user) - # categories = fyle_expense.get_categories(query_params) + project_id = int(form_current_state['project_block']['project_id']['selected_option']['value']) - # expense_form = expense_messages.expense_dialog_form(current_ui_blocks=current_ui_blocks, categories=categories) + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } - # slack_client.views_update(view_id=view_id, view=expense_form) + project = fyle_expense.get_projects(project_query_params) - # return JsonResponse({}) + if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: + fields_render_property['is_cost_centers_available'] = True + + expense_form = expense_messages.expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + + slack_client.views_update(view_id=view_id, view=expense_form) + + return JsonResponse({}) def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index c57951e0..2fc19698 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -72,21 +72,23 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: } project = None - if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project_id']['selected_option'] is not None: + if 'project_block' in slack_payload['view']['state']['values']: - project_id = int(slack_payload['view']['state']['values']['project_block']['project_id']['selected_option']['value']) + fields_render_property['is_projects_available'] = True - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } + if slack_payload['view']['state']['values']['project_block']['project_id']['selected_option'] is not None: - project = fyle_expense.get_projects(project_query_params) + project_id = int(slack_payload['view']['state']['values']['project_block']['project_id']['selected_option']['value']) - fields_render_property['is_projects_available'] = True + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } + + project = fyle_expense.get_projects(project_query_params) if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: fields_render_property['is_cost_centers_available'] = True diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 352b2654..e03b5fa1 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -120,38 +120,91 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> return custom_field -def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: - default_fields_blocks = [ - { - 'type': 'input', - 'block_id': 'SELECT_default_field_currency_block', - 'element': { - 'type': 'external_select', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Select Currency', - 'emoji': True, - }, - 'min_query_length': 1, - 'action_id': 'currency', +def get_amount_and_currency_block(additional_currency_details: Dict = None) -> List: + currency_block = { + 'type': 'input', + 'block_id': 'SELECT_default_field_currency_block', + 'dispatch_action': True, + 'element': { + 'type': 'external_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select Currency', + 'emoji': True, + }, + 'min_query_length': 1, + 'action_id': 'currency', + }, + 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, + } + + currency_context_block = None + total_amount_block = None + amount_block = { + 'type': 'input', + 'block_id': 'NUMBER_default_field_amount_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Enter Amount', + 'emoji': True, }, - 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, + 'action_id': 'amount', }, - { + 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, + } + + if additional_currency_details is not None: + amount_block['dispatch_action'] = True + amount_block['element']['dispatch_action_config'] = { + 'trigger_actions_on': [ + 'on_character_entered' + ] + } + + amount_block['element']['placeholder']['text'] = 'Enter Amount {}'.format(additional_currency_details['foreign_currency']) + + currency_context_block = { + 'type': 'context', + 'block_id': 'TEXT_default_field_currency_context_block', + 'elements': [ + { + 'type': 'mrkdwn', + 'text': ':information_source: Amount ({}) x Exchange Rate = Total ({})'.format(additional_currency_details['foreign_currency'], additional_currency_details['home_currency']) + } + ] + } + + total_amount_block = { 'type': 'input', - 'block_id': 'NUMBER_default_field_amount_block', + 'block_id': 'NUMBER_default_field_total_amount_block', 'element': { 'type': 'plain_text_input', 'placeholder': { 'type': 'plain_text', - 'text': 'Enter Amount', + 'text': 'Enter Total Amount {}'.format(additional_currency_details['home_currency']), 'emoji': True, }, - 'action_id': 'amount', + 'action_id': 'foreign_amount', }, - 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, - }, - ] + 'label': {'type': 'plain_text', 'text': 'Total Amount', 'emoji': True}, + } + + if int(additional_currency_details['total_amount']) != 0: + total_amount_block['element']['initial_value'] = str(additional_currency_details['total_amount']) + + return currency_block, amount_block, currency_context_block, total_amount_block + + +def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: + + default_fields_blocks = [] + + currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block() + + default_fields_blocks.append(currency_block) + default_fields_blocks.append(amount_block) if field_type_mandatory_mapping['txn_dt'] is True: date_of_spend_block = { @@ -346,7 +399,7 @@ def get_cost_centers_block() -> Dict: return cost_centers_block -def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None) -> Dict: +def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None, additional_currency_details: Dict = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', @@ -361,6 +414,33 @@ def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render if current_ui_blocks is not None: ui_blocks = [] + ignore_block_id_list = ['custom_field', 'project_block', 'billable_block', 'category_block', 'cost_center_block', 'additional_field'] + additional_currency_amount_blocks = ['NUMBER_default_field_total_amount_block', 'TEXT_default_field_currency_context_block'] + + is_additional_currency_amount_blocks_present = False + for block in current_ui_blocks: + if any(substring == block['block_id'] for substring in additional_currency_amount_blocks) is True: + is_additional_currency_amount_blocks_present = True + + if is_additional_currency_amount_blocks_present is True: + current_ui_blocks = current_ui_blocks[4:] + else: + current_ui_blocks = current_ui_blocks[2:] + + if additional_currency_details is not None: + + currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block(additional_currency_details) + current_ui_blocks.insert(0, currency_block) + current_ui_blocks.insert(1, currency_context_block) + + current_ui_blocks.insert(2, amount_block) + current_ui_blocks.insert(3, total_amount_block) + else: + + currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block() + current_ui_blocks.insert(0, currency_block) + current_ui_blocks.insert(1, amount_block) + cost_center_block = None for block in current_ui_blocks: @@ -369,7 +449,6 @@ def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render cost_center_block = block # Removing these block as these should not be rendered from current/cached UI block - ignore_block_id_list = ['custom_field', 'project_block', 'billable_block', 'category_block', 'cost_center_block', 'additional_field'] if any(substring in block['block_id'] for substring in ignore_block_id_list) is False: ui_blocks.append(block) From 3825b41551c812d3914dea818e125064752929fb Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Fri, 24 Sep 2021 16:47:05 +0530 Subject: [PATCH 27/85] Moved currency and amount entered select to async --- .../interactives/block_action_handlers.py | 140 ++------------- .../interactives/block_suggestion_handlers.py | 7 - fyle_slack_app/slack/interactives/tasks.py | 170 +++++++++++++++--- fyle_slack_app/slack/ui/expenses/messages.py | 5 + 4 files changed, 162 insertions(+), 160 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 7123fdfb..1d846a4b 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -4,8 +4,6 @@ from django_q.tasks import async_task -from fyle_slack_app.fyle.expenses.views import FyleExpense -from fyle_slack_app.fyle.utils import get_fyle_profile from fyle_slack_app.models import User, NotificationPreference from fyle_slack_app.models.notification_preferences import NotificationType from fyle_slack_app.libs import assertions, utils, logger @@ -250,142 +248,28 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str def handle_currency_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - selected_currency = slack_payload['actions'][0]['selected_option']['value'] - - view_id = slack_payload['container']['view_id'] - user = utils.get_or_none(User, slack_user_id=user_id) - slack_client = get_slack_client(team_id) - - fyle_profile = get_fyle_profile(user.fyle_refresh_token) - - home_currency = fyle_profile['org']['currency'] - - form_current_state = slack_payload['view']['state']['values'] - - additional_currency_details = None - - if home_currency != selected_currency: - exchange_rate = 70.12 - amount = form_current_state['NUMBER_default_field_amount_block']['amount']['value'] - - if amount is None or len(amount) == 0: - amount = 0 - else: - try: - amount = round(float(amount), 2) - except ValueError: - amount = 0 - - additional_currency_details = { - 'foreign_currency': selected_currency, - 'home_currency': home_currency, - 'total_amount': exchange_rate * amount - } - - current_ui_blocks = slack_payload['view']['blocks'] - - fields_render_property = { - 'is_projects_available': False, - 'is_cost_centers_available': False - } - - project = None - if 'project_block' in form_current_state: - - fields_render_property['is_projects_available'] = True - - if form_current_state['project_block']['project_id']['selected_option'] is not None: - - fyle_expense = FyleExpense(user) - - project_id = int(form_current_state['project_block']['project_id']['selected_option']['value']) - - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } - - project = fyle_expense.get_projects(project_query_params) - - if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: - fields_render_property['is_cost_centers_available'] = True - - expense_form = expense_messages.expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) - - slack_client.views_update(view_id=view_id, view=expense_form) + async_task( + 'fyle_slack_app.slack.interactives.tasks.handle_currency_select', + user, + team_id, + slack_payload + ) return JsonResponse({}) def handle_amount_entered(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - amount_entered = slack_payload['actions'][0]['value'] - form_current_state = slack_payload['view']['state']['values'] - - view_id = slack_payload['container']['view_id'] user = utils.get_or_none(User, slack_user_id=user_id) - slack_client = get_slack_client(team_id) - - fyle_profile = get_fyle_profile(user.fyle_refresh_token) - - home_currency = fyle_profile['org']['currency'] - - selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] - - if amount_entered is None or len(amount_entered) == 0: - amount = 0 - else: - try: - amount = round(float(amount_entered), 2) - except ValueError: - amount = 0 - exchange_rate = 70.12 - additional_currency_details = { - 'foreign_currency': selected_currency, - 'home_currency': home_currency, - 'total_amount': exchange_rate * amount - } - - current_ui_blocks = slack_payload['view']['blocks'] - - fields_render_property = { - 'is_projects_available': False, - 'is_cost_centers_available': False - } - - project = None - if 'project_block' in form_current_state: - - fields_render_property['is_projects_available'] = True - - if form_current_state['project_block']['project_id']['selected_option'] is not None: - - fyle_expense = FyleExpense(user) - - project_id = int(form_current_state['project_block']['project_id']['selected_option']['value']) - - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } - - project = fyle_expense.get_projects(project_query_params) - - if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: - fields_render_property['is_cost_centers_available'] = True - - expense_form = expense_messages.expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) - - slack_client.views_update(view_id=view_id, view=expense_form) + async_task( + 'fyle_slack_app.slack.interactives.tasks.handle_amount_entered', + user, + team_id, + slack_payload + ) return JsonResponse({}) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index f8b83e13..ae07639a 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -1,4 +1,3 @@ -import json from typing import Dict, List from django.http import JsonResponse @@ -60,8 +59,6 @@ def handle_block_suggestions(self, slack_payload: Dict, user_id: str, team_id: s def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: - print('CATEGORY SUGGESTION -> ', json.dumps(slack_payload['view']['state']['values'], indent=2)) - user = utils.get_or_none(User, slack_user_id=user_id) category_value_entered = slack_payload['value'] @@ -139,8 +136,6 @@ def handle_currency_suggestion(self, slack_payload: Dict, user_id: str, team_id: def handle_project_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: - # print('Project SUGGESTION -> ', json.dumps(slack_payload['view']['state'], indent=2)) - user = utils.get_or_none(User, slack_user_id=user_id) project_value_entered = slack_payload['value'] query_params = { @@ -177,8 +172,6 @@ def handle_project_suggestion(self, slack_payload: Dict, user_id: str, team_id: def handle_cost_center_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: - # print('COST CENTER SUGGESTION -> ', json.dumps(slack_payload['view']['state'], indent=2)) - user = utils.get_or_none(User, slack_user_id=user_id) cost_center_value_entered = slack_payload['value'] query_params = { diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 2fc19698..e789b036 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,12 +1,68 @@ -from typing import Dict +from typing import Any, Dict, Union -from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense +from fyle_slack_app.slack.utils import get_slack_client +from fyle_slack_app.fyle.utils import get_fyle_profile from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form +def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) -> Union[bool, Any]: + + is_projects_available = False + project = None + + if 'project_block' in form_current_state: + + is_projects_available = True + + if form_current_state['project_block']['project_id']['selected_option'] is not None: + + project_id = int(form_current_state['project_block']['project_id']['selected_option']['value']) + + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } + + project = fyle_expense.get_projects(project_query_params) + + return is_projects_available, project + + +def check_cost_centers_in_form(form_current_state) -> bool: + + is_cost_centers_available = False + + if 'cost_center_block' in form_current_state and form_current_state['cost_center_block']['cost_center_id']['selected_option'] is not None: + is_cost_centers_available = True + + return is_cost_centers_available + + +def get_additional_currency_details(amount: int, home_currency: str, selected_currency: str, exchange_rate: float) -> Dict: + + if amount is None or len(amount) == 0: + amount = 0 + else: + try: + amount = round(float(amount), 2) + except ValueError: + amount = 0 + + additional_currency_details = { + 'foreign_currency': selected_currency, + 'home_currency': home_currency, + 'total_amount': exchange_rate * amount + } + + return additional_currency_details + + def handle_project_select(user: User, team_id: str, project_id: str, view_id: str, slack_payload: Dict) -> None: slack_client = get_slack_client(team_id) @@ -29,13 +85,15 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st project_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'project_loading_block'), None) current_ui_blocks.pop(project_loading_block_index) + form_current_state = slack_payload['view']['state']['values'] + fields_render_property = { - 'is_projects_available': True, - 'is_cost_centers_available': False + 'is_projects_available': True } - if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: - fields_render_property['is_cost_centers_available'] = True + is_cost_centers_available = check_cost_centers_in_form(form_current_state) + + fields_render_property['is_cost_centers_available'] = is_cost_centers_available new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, fields_render_property=fields_render_property, selected_project=project) @@ -66,33 +124,95 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: category_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'category_loading_block'), None) current_ui_blocks.pop(category_loading_block_index) + form_current_state = slack_payload['view']['state']['values'] + + is_projects_available, project = check_project_in_form(form_current_state, fyle_expense) + + is_cost_centers_available = check_cost_centers_in_form(form_current_state) + fields_render_property = { - 'is_projects_available': False, - 'is_cost_centers_available': False + 'is_projects_available': is_projects_available, + 'is_cost_centers_available': is_cost_centers_available } - project = None - if 'project_block' in slack_payload['view']['state']['values']: + new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property) - fields_render_property['is_projects_available'] = True + slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) - if slack_payload['view']['state']['values']['project_block']['project_id']['selected_option'] is not None: - project_id = int(slack_payload['view']['state']['values']['project_block']['project_id']['selected_option']['value']) +def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None: - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } + selected_currency = slack_payload['actions'][0]['selected_option']['value'] - project = fyle_expense.get_projects(project_query_params) + view_id = slack_payload['container']['view_id'] - if 'cost_center_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['cost_center_block']['cost_center_id']['selected_option'] is not None: - fields_render_property['is_cost_centers_available'] = True + slack_client = get_slack_client(team_id) - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property) + fyle_profile = get_fyle_profile(user.fyle_refresh_token) - slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) + home_currency = fyle_profile['org']['currency'] + + form_current_state = slack_payload['view']['state']['values'] + + additional_currency_details = None + + if home_currency != selected_currency: + exchange_rate = 70.12 + amount = form_current_state['NUMBER_default_field_amount_block']['amount']['value'] + + additional_currency_details = get_additional_currency_details(amount, home_currency, selected_currency, exchange_rate) + + current_ui_blocks = slack_payload['view']['blocks'] + + fyle_expense = FyleExpense(user) + + is_projects_available, project = check_project_in_form(form_current_state, fyle_expense) + + is_cost_centers_available = check_cost_centers_in_form(form_current_state) + + fields_render_property = { + 'is_projects_available': is_projects_available, + 'is_cost_centers_available': is_cost_centers_available + } + + expense_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + + slack_client.views_update(view_id=view_id, view=expense_form) + + +def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: + + slack_client = get_slack_client(team_id) + + amount_entered = slack_payload['actions'][0]['value'] + + view_id = slack_payload['container']['view_id'] + + form_current_state = slack_payload['view']['state']['values'] + + fyle_profile = get_fyle_profile(user.fyle_refresh_token) + + home_currency = fyle_profile['org']['currency'] + + selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] + + exchange_rate = 70.12 + + additional_currency_details = get_additional_currency_details(amount_entered, home_currency, selected_currency, exchange_rate) + + current_ui_blocks = slack_payload['view']['blocks'] + + fyle_expense = FyleExpense(user) + + is_projects_available, project = check_project_in_form(form_current_state, fyle_expense) + + is_cost_centers_available = check_cost_centers_in_form(form_current_state) + + fields_render_property = { + 'is_projects_available': is_projects_available, + 'is_cost_centers_available': is_cost_centers_available + } + + expense_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + + slack_client.views_update(view_id=view_id, view=expense_form) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index e03b5fa1..7e4c3ec0 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -423,10 +423,13 @@ def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render is_additional_currency_amount_blocks_present = True if is_additional_currency_amount_blocks_present is True: + # Removing all exchange rate context blocks current_ui_blocks = current_ui_blocks[4:] else: + # Removing currency and amount blocks only current_ui_blocks = current_ui_blocks[2:] + # When additional currency details are present, render exchange rate context and total amount block if additional_currency_details is not None: currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block(additional_currency_details) @@ -435,6 +438,8 @@ def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render current_ui_blocks.insert(2, amount_block) current_ui_blocks.insert(3, total_amount_block) + + # When additional currency details are not present, render amount and currency blocks only else: currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block() From 1bb77437bfec3d1998283deaf6e2eac9f58dd4c4 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 27 Sep 2021 19:04:29 +0530 Subject: [PATCH 28/85] Using private metadata --- fyle_slack_app/slack/commands/tasks.py | 20 +++++-- .../interactives/block_suggestion_handlers.py | 4 +- fyle_slack_app/slack/interactives/tasks.py | 57 ++++++++++--------- fyle_slack_app/slack/ui/expenses/messages.py | 23 ++++---- 4 files changed, 59 insertions(+), 45 deletions(-) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 5e3beeb5..2172777e 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -101,6 +101,8 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: slack_client = slack_utils.get_slack_client(team_id) + fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) + field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) is_project_available = False @@ -131,10 +133,20 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: is_cost_centers_available = True if cost_centers['count'] > 0 else False fields_render_property = { - 'is_projects_available': is_project_available, - 'is_cost_centers_available': is_cost_centers_available + 'project': is_project_available, + 'cost_center': is_cost_centers_available, + 'purpose': field_type_mandatory_mapping['purpose'], + 'transaction_date': field_type_mandatory_mapping['txn_dt'], + 'vendor': field_type_mandatory_mapping['vendor_id'] + } + + private_metadata = { + 'fields_render_property': fields_render_property, + 'home_currency': fyle_profile['org']['currency'] } - modal = expense_messages.expense_dialog_form(field_type_mandatory_mapping=field_type_mandatory_mapping, fields_render_property=fields_render_property) + expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property) + + expense_form['private_metadata'] = utils.encode_state(private_metadata) - slack_client.views_update(view=modal, view_id=view_id) + slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index ae07639a..486ba420 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -140,7 +140,7 @@ def handle_project_suggestion(self, slack_payload: Dict, user_id: str, team_id: project_value_entered = slack_payload['value'] query_params = { 'offset': 0, - 'limit': '30', + 'limit': '10', 'order': 'display_name.asc', 'display_name': 'ilike.%{}%'.format(project_value_entered), 'is_enabled': 'eq.{}'.format(True) @@ -176,7 +176,7 @@ def handle_cost_center_suggestion(self, slack_payload: Dict, user_id: str, team_ cost_center_value_entered = slack_payload['value'] query_params = { 'offset': 0, - 'limit': '30', + 'limit': '10', 'order': 'name.asc', 'name': 'ilike.%{}%'.format(cost_center_value_entered), 'is_enabled': 'eq.{}'.format(True) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index e789b036..999f8530 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,21 +1,20 @@ from typing import Any, Dict, Union - from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_client -from fyle_slack_app.fyle.utils import get_fyle_profile +from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) -> Union[bool, Any]: - is_projects_available = False + is_project_available = False project = None if 'project_block' in form_current_state: - is_projects_available = True + is_project_available = True if form_current_state['project_block']['project_id']['selected_option'] is not None: @@ -31,17 +30,17 @@ def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) - project = fyle_expense.get_projects(project_query_params) - return is_projects_available, project + return is_project_available, project def check_cost_centers_in_form(form_current_state) -> bool: - is_cost_centers_available = False + cost_centers = False if 'cost_center_block' in form_current_state and form_current_state['cost_center_block']['cost_center_id']['selected_option'] is not None: - is_cost_centers_available = True + cost_centers = True - return is_cost_centers_available + return cost_centers def get_additional_currency_details(amount: int, home_currency: str, selected_currency: str, exchange_rate: float) -> Dict: @@ -88,12 +87,12 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st form_current_state = slack_payload['view']['state']['values'] fields_render_property = { - 'is_projects_available': True + 'project': True } - is_cost_centers_available = check_cost_centers_in_form(form_current_state) + is_cost_center_available = check_cost_centers_in_form(form_current_state) - fields_render_property['is_cost_centers_available'] = is_cost_centers_available + fields_render_property['cost_center'] = is_cost_center_available new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, fields_render_property=fields_render_property, selected_project=project) @@ -126,13 +125,13 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: form_current_state = slack_payload['view']['state']['values'] - is_projects_available, project = check_project_in_form(form_current_state, fyle_expense) + is_project_available, project = check_project_in_form(form_current_state, fyle_expense) - is_cost_centers_available = check_cost_centers_in_form(form_current_state) + is_cost_center_available = check_cost_centers_in_form(form_current_state) fields_render_property = { - 'is_projects_available': is_projects_available, - 'is_cost_centers_available': is_cost_centers_available + 'project': is_project_available, + 'cost_center': is_cost_center_available } new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property) @@ -148,9 +147,9 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None slack_client = get_slack_client(team_id) - fyle_profile = get_fyle_profile(user.fyle_refresh_token) + private_metadata = decode_state(slack_payload['view']['private_metadata']) - home_currency = fyle_profile['org']['currency'] + home_currency = private_metadata['home_currency'] form_current_state = slack_payload['view']['state']['values'] @@ -162,21 +161,25 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None additional_currency_details = get_additional_currency_details(amount, home_currency, selected_currency, exchange_rate) + private_metadata['additional_currency_details'] = additional_currency_details + current_ui_blocks = slack_payload['view']['blocks'] fyle_expense = FyleExpense(user) - is_projects_available, project = check_project_in_form(form_current_state, fyle_expense) + is_project_available, project = check_project_in_form(form_current_state, fyle_expense) - is_cost_centers_available = check_cost_centers_in_form(form_current_state) + is_cost_center_available = check_cost_centers_in_form(form_current_state) fields_render_property = { - 'is_projects_available': is_projects_available, - 'is_cost_centers_available': is_cost_centers_available + 'project': is_project_available, + 'cost_center': is_cost_center_available } expense_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + expense_form['private_metadata'] = encode_state(private_metadata) + slack_client.views_update(view_id=view_id, view=expense_form) @@ -190,9 +193,9 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: form_current_state = slack_payload['view']['state']['values'] - fyle_profile = get_fyle_profile(user.fyle_refresh_token) + private_metadata = decode_state(slack_payload['view']['private_metadata']) - home_currency = fyle_profile['org']['currency'] + home_currency = private_metadata['home_currency'] selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] @@ -204,13 +207,13 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: fyle_expense = FyleExpense(user) - is_projects_available, project = check_project_in_form(form_current_state, fyle_expense) + is_project_available, project = check_project_in_form(form_current_state, fyle_expense) - is_cost_centers_available = check_cost_centers_in_form(form_current_state) + is_cost_center_available = check_cost_centers_in_form(form_current_state) fields_render_property = { - 'is_projects_available': is_projects_available, - 'is_cost_centers_available': is_cost_centers_available + 'project': is_project_available, + 'cost_center': is_cost_center_available } expense_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 7e4c3ec0..af7de327 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -197,7 +197,7 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L return currency_block, amount_block, currency_context_block, total_amount_block -def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: +def get_default_fields_blocks(fields_render_property: Dict) -> List: default_fields_blocks = [] @@ -206,7 +206,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: default_fields_blocks.append(currency_block) default_fields_blocks.append(amount_block) - if field_type_mandatory_mapping['txn_dt'] is True: + if fields_render_property['transaction_date'] is True: date_of_spend_block = { 'type': 'input', 'block_id': 'DATE_default_field_date_of_spend_block', @@ -224,7 +224,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: } default_fields_blocks.append(date_of_spend_block) - if field_type_mandatory_mapping['purpose'] is True: + if fields_render_property['purpose'] is True: purpose_block = { 'type': 'input', 'block_id': 'TEXT_default_field_purpose_block', @@ -283,7 +283,7 @@ def get_default_fields_blocks(field_type_mandatory_mapping: Dict) -> List: } default_fields_blocks.append(payment_mode_block) - if field_type_mandatory_mapping['vendor_id'] is True: + if fields_render_property['vendor'] is True: merchant_block = { 'type': 'input', 'block_id': 'TEXT_default_field_merchant_block', @@ -399,7 +399,7 @@ def get_cost_centers_block() -> Dict: return cost_centers_block -def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None, additional_currency_details: Dict = None) -> Dict: +def expense_dialog_form(fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None, additional_currency_details: Dict = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', @@ -430,7 +430,7 @@ def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render current_ui_blocks = current_ui_blocks[2:] # When additional currency details are present, render exchange rate context and total amount block - if additional_currency_details is not None: + if additional_currency_details is not None or is_additional_currency_amount_blocks_present is True: currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block(additional_currency_details) current_ui_blocks.insert(0, currency_block) @@ -448,22 +448,21 @@ def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render cost_center_block = None for block in current_ui_blocks: - # Removing cost center if present to maintain order of cost center at end of form - if block['block_id'] == 'cost_center_block': + if block is not None and block['block_id'] == 'cost_center_block': cost_center_block = block # Removing these block as these should not be rendered from current/cached UI block - if any(substring in block['block_id'] for substring in ignore_block_id_list) is False: + if block is not None and any(substring in block['block_id'] for substring in ignore_block_id_list) is False: ui_blocks.append(block) view['blocks'] = ui_blocks else: - view['blocks'] = get_default_fields_blocks(field_type_mandatory_mapping) + view['blocks'] = get_default_fields_blocks(fields_render_property) - if fields_render_property['is_projects_available'] is True: + if fields_render_property['project'] is True: project_block, billable_block = get_projects_and_billable_block(selected_project) @@ -494,7 +493,7 @@ def expense_dialog_form(field_type_mandatory_mapping: Dict = None, fields_render if current_ui_blocks is not None and cost_center_block is not None: view['blocks'].append(cost_center_block) else: - if fields_render_property['is_cost_centers_available'] is True: + if fields_render_property['cost_center'] is True: cost_center_block = get_cost_centers_block() From 4ffd61d65374bef9a29b9201ef21d9ef0bf2c68e Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 27 Sep 2021 20:05:28 +0530 Subject: [PATCH 29/85] Using expense form v2 - much cleaner --- fyle_slack_app/slack/commands/tasks.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 61 ++++++++++-------- fyle_slack_app/slack/ui/expenses/messages.py | 66 +++++++++++++++++--- 3 files changed, 95 insertions(+), 34 deletions(-) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 2172777e..a54e3d9a 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -145,7 +145,7 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: 'home_currency': fyle_profile['org']['currency'] } - expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property) + expense_form = expense_messages.expense_dialog_form_v2(fields_render_property=fields_render_property) expense_form['private_metadata'] = utils.encode_state(private_metadata) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 999f8530..d7922cd0 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -4,7 +4,7 @@ from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.libs.utils import decode_state, encode_state -from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form +from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form, expense_dialog_form_v2 def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) -> Union[bool, Any]: @@ -56,7 +56,7 @@ def get_additional_currency_details(amount: int, home_currency: str, selected_cu additional_currency_details = { 'foreign_currency': selected_currency, 'home_currency': home_currency, - 'total_amount': exchange_rate * amount + 'total_amount': round(exchange_rate * amount, 2) } return additional_currency_details @@ -78,6 +78,12 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st project = fyle_expense.get_projects(project_query_params) + private_metadata = decode_state(slack_payload['view']['private_metadata']) + + fields_render_property = private_metadata['fields_render_property'] + + additional_currency_details = private_metadata.get('additional_currency_details') + current_ui_blocks = slack_payload['view']['blocks'] # Removing loading info from below project input element @@ -86,15 +92,14 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st form_current_state = slack_payload['view']['state']['values'] - fields_render_property = { - 'project': True - } - is_cost_center_available = check_cost_centers_in_form(form_current_state) + fields_render_property['project'] = True fields_render_property['cost_center'] = is_cost_center_available - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, fields_render_property=fields_render_property, selected_project=project) + new_expense_dialog_form = expense_dialog_form_v2(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details) + + new_expense_dialog_form['private_metadata'] = slack_payload['view']['private_metadata'] slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -117,6 +122,12 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: custom_fields = fyle_expense.get_expense_fields(custom_fields_query_params) + private_metadata = decode_state(slack_payload['view']['private_metadata']) + + fields_render_property = private_metadata['fields_render_property'] + + additional_currency_details = private_metadata.get('additional_currency_details') + current_ui_blocks = slack_payload['view']['blocks'] # Removing loading info from below category input element @@ -129,12 +140,12 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: is_cost_center_available = check_cost_centers_in_form(form_current_state) - fields_render_property = { - 'project': is_project_available, - 'cost_center': is_cost_center_available - } + fields_render_property['project'] = is_project_available + fields_render_property['cost_center'] = is_cost_center_available - new_expense_dialog_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property) + new_expense_dialog_form = expense_dialog_form_v2(custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + + new_expense_dialog_form['private_metadata'] = slack_payload['view']['private_metadata'] slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -149,6 +160,8 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None private_metadata = decode_state(slack_payload['view']['private_metadata']) + fields_render_property = private_metadata['fields_render_property'] + home_currency = private_metadata['home_currency'] form_current_state = slack_payload['view']['state']['values'] @@ -163,20 +176,16 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None private_metadata['additional_currency_details'] = additional_currency_details - current_ui_blocks = slack_payload['view']['blocks'] - fyle_expense = FyleExpense(user) is_project_available, project = check_project_in_form(form_current_state, fyle_expense) is_cost_center_available = check_cost_centers_in_form(form_current_state) - fields_render_property = { - 'project': is_project_available, - 'cost_center': is_cost_center_available - } + fields_render_property['project'] = is_project_available + fields_render_property['cost_center'] = is_cost_center_available - expense_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + expense_form = expense_dialog_form_v2(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) expense_form['private_metadata'] = encode_state(private_metadata) @@ -195,6 +204,8 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: private_metadata = decode_state(slack_payload['view']['private_metadata']) + fields_render_property = private_metadata['fields_render_property'] + home_currency = private_metadata['home_currency'] selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] @@ -203,19 +214,17 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: additional_currency_details = get_additional_currency_details(amount_entered, home_currency, selected_currency, exchange_rate) - current_ui_blocks = slack_payload['view']['blocks'] - fyle_expense = FyleExpense(user) is_project_available, project = check_project_in_form(form_current_state, fyle_expense) is_cost_center_available = check_cost_centers_in_form(form_current_state) - fields_render_property = { - 'project': is_project_available, - 'cost_center': is_cost_center_available - } + fields_render_property['project'] = is_project_available + fields_render_property['cost_center'] = is_cost_center_available + + expense_form = expense_dialog_form_v2(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) - expense_form = expense_dialog_form(current_ui_blocks=current_ui_blocks, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + expense_form['private_metadata'] = slack_payload['view']['private_metadata'] slack_client.views_update(view_id=view_id, view=expense_form) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index af7de327..a19603b1 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -121,6 +121,8 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> def get_amount_and_currency_block(additional_currency_details: Dict = None) -> List: + blocks = [] + currency_block = { 'type': 'input', 'block_id': 'SELECT_default_field_currency_block', @@ -138,6 +140,8 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, } + blocks.append(currency_block) + currency_context_block = None total_amount_block = None amount_block = { @@ -155,6 +159,8 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, } + blocks.append(amount_block) + if additional_currency_details is not None: amount_block['dispatch_action'] = True amount_block['element']['dispatch_action_config'] = { @@ -176,6 +182,8 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L ] } + blocks.insert(1, currency_context_block) + total_amount_block = { 'type': 'input', 'block_id': 'NUMBER_default_field_total_amount_block', @@ -194,17 +202,14 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L if int(additional_currency_details['total_amount']) != 0: total_amount_block['element']['initial_value'] = str(additional_currency_details['total_amount']) - return currency_block, amount_block, currency_context_block, total_amount_block - + blocks.insert(3, total_amount_block) -def get_default_fields_blocks(fields_render_property: Dict) -> List: + return blocks - default_fields_blocks = [] - currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block() +def get_default_fields_blocks(fields_render_property: Dict, additional_currency_details: Dict = None) -> List: - default_fields_blocks.append(currency_block) - default_fields_blocks.append(amount_block) + default_fields_blocks = get_amount_and_currency_block(additional_currency_details) if fields_render_property['transaction_date'] is True: date_of_spend_block = { @@ -520,3 +525,50 @@ def expense_form_loading_modal() -> Dict: } return loading_modal + + +def expense_dialog_form_v2(fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None) -> Dict: + view = { + 'type': 'modal', + 'callback_id': 'create_expense', + 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, + 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, + 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True} + } + + view['blocks'] = [] + + view['blocks'] = get_default_fields_blocks(fields_render_property, additional_currency_details) + + if fields_render_property['project'] is True: + + project_block, billable_block = get_projects_and_billable_block(selected_project) + + view['blocks'].append(project_block) + + view['blocks'].append(billable_block) + + category_block = get_categories_block() + + view['blocks'].append(category_block) + + + # If custom fields are present, render them in the form + if custom_fields is not None and custom_fields['count'] > 0: + for field in custom_fields['data']: + + # Additional fields are field which are not custom fields but are dependent on categories + is_additional_field = False + if field['is_custom'] is False: + is_additional_field = True + + custom_field = generate_field_ui(field, is_additional_field=is_additional_field) + view['blocks'].append(custom_field) + + if fields_render_property['cost_center'] is True: + + cost_center_block = get_cost_centers_block() + + view['blocks'].append(cost_center_block) + + return view From cf770caa8c42cfe2f7269238a59077585a8d4146 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 27 Sep 2021 20:42:42 +0530 Subject: [PATCH 30/85] Removed older expense form code and added support for custom fields rendering --- fyle_slack_app/slack/commands/tasks.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 35 ++++- fyle_slack_app/slack/ui/expenses/messages.py | 128 ++----------------- 3 files changed, 44 insertions(+), 121 deletions(-) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index a54e3d9a..2172777e 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -145,7 +145,7 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: 'home_currency': fyle_profile['org']['currency'] } - expense_form = expense_messages.expense_dialog_form_v2(fields_render_property=fields_render_property) + expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property) expense_form['private_metadata'] = utils.encode_state(private_metadata) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index d7922cd0..ed3c696c 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,10 +1,10 @@ -from typing import Any, Dict, Union +from typing import Any, Dict, List, Union from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.libs.utils import decode_state, encode_state -from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form, expense_dialog_form_v2 +from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) -> Union[bool, Any]: @@ -33,6 +33,19 @@ def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) - return is_project_available, project +def get_custom_field_blocks(current_blocks: List[Dict]) -> List[Dict]: + custom_field_blocks = [] + + for block in current_blocks: + if 'custom_field' in block['block_id']: + custom_field_blocks.append(block) + + if len(custom_field_blocks) == 0: + custom_field_blocks = None + + return custom_field_blocks + + def check_cost_centers_in_form(form_current_state) -> bool: cost_centers = False @@ -92,12 +105,14 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st form_current_state = slack_payload['view']['state']['values'] + custom_fields = get_custom_field_blocks(current_ui_blocks) + is_cost_center_available = check_cost_centers_in_form(form_current_state) fields_render_property['project'] = True fields_render_property['cost_center'] = is_cost_center_available - new_expense_dialog_form = expense_dialog_form_v2(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details) + new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details, custom_fields=custom_fields) new_expense_dialog_form['private_metadata'] = slack_payload['view']['private_metadata'] @@ -143,7 +158,7 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: fields_render_property['project'] = is_project_available fields_render_property['cost_center'] = is_cost_center_available - new_expense_dialog_form = expense_dialog_form_v2(custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + new_expense_dialog_form = expense_dialog_form(custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) new_expense_dialog_form['private_metadata'] = slack_payload['view']['private_metadata'] @@ -166,6 +181,8 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None form_current_state = slack_payload['view']['state']['values'] + current_ui_blocks = slack_payload['view']['blocks'] + additional_currency_details = None if home_currency != selected_currency: @@ -182,10 +199,12 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None is_cost_center_available = check_cost_centers_in_form(form_current_state) + custom_fields = get_custom_field_blocks(current_ui_blocks) + fields_render_property['project'] = is_project_available fields_render_property['cost_center'] = is_cost_center_available - expense_form = expense_dialog_form_v2(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields) expense_form['private_metadata'] = encode_state(private_metadata) @@ -204,6 +223,8 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: private_metadata = decode_state(slack_payload['view']['private_metadata']) + current_ui_blocks = slack_payload['view']['blocks'] + fields_render_property = private_metadata['fields_render_property'] home_currency = private_metadata['home_currency'] @@ -214,6 +235,8 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: additional_currency_details = get_additional_currency_details(amount_entered, home_currency, selected_currency, exchange_rate) + custom_fields = get_custom_field_blocks(current_ui_blocks) + fyle_expense = FyleExpense(user) is_project_available, project = check_project_in_form(form_current_state, fyle_expense) @@ -223,7 +246,7 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: fields_render_property['project'] = is_project_available fields_render_property['cost_center'] = is_cost_center_available - expense_form = expense_dialog_form_v2(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) + expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields) expense_form['private_metadata'] = slack_payload['view']['private_metadata'] diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index a19603b1..674fdeae 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -404,109 +404,6 @@ def get_cost_centers_block() -> Dict: return cost_centers_block -def expense_dialog_form(fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, current_ui_blocks: List = None, additional_currency_details: Dict = None) -> Dict: - view = { - 'type': 'modal', - 'callback_id': 'create_expense', - 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, - 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, - 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True} - } - - view['blocks'] = [] - - # If current UI blocks are passed use them as it is for faster processing of UI elements. - if current_ui_blocks is not None: - ui_blocks = [] - - ignore_block_id_list = ['custom_field', 'project_block', 'billable_block', 'category_block', 'cost_center_block', 'additional_field'] - additional_currency_amount_blocks = ['NUMBER_default_field_total_amount_block', 'TEXT_default_field_currency_context_block'] - - is_additional_currency_amount_blocks_present = False - for block in current_ui_blocks: - if any(substring == block['block_id'] for substring in additional_currency_amount_blocks) is True: - is_additional_currency_amount_blocks_present = True - - if is_additional_currency_amount_blocks_present is True: - # Removing all exchange rate context blocks - current_ui_blocks = current_ui_blocks[4:] - else: - # Removing currency and amount blocks only - current_ui_blocks = current_ui_blocks[2:] - - # When additional currency details are present, render exchange rate context and total amount block - if additional_currency_details is not None or is_additional_currency_amount_blocks_present is True: - - currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block(additional_currency_details) - current_ui_blocks.insert(0, currency_block) - current_ui_blocks.insert(1, currency_context_block) - - current_ui_blocks.insert(2, amount_block) - current_ui_blocks.insert(3, total_amount_block) - - # When additional currency details are not present, render amount and currency blocks only - else: - - currency_block, amount_block, currency_context_block, total_amount_block = get_amount_and_currency_block() - current_ui_blocks.insert(0, currency_block) - current_ui_blocks.insert(1, amount_block) - - cost_center_block = None - for block in current_ui_blocks: - # Removing cost center if present to maintain order of cost center at end of form - if block is not None and block['block_id'] == 'cost_center_block': - cost_center_block = block - - # Removing these block as these should not be rendered from current/cached UI block - if block is not None and any(substring in block['block_id'] for substring in ignore_block_id_list) is False: - ui_blocks.append(block) - - view['blocks'] = ui_blocks - - else: - - view['blocks'] = get_default_fields_blocks(fields_render_property) - - if fields_render_property['project'] is True: - - project_block, billable_block = get_projects_and_billable_block(selected_project) - - view['blocks'].append(project_block) - - view['blocks'].append(billable_block) - - # Since category block is dependent of projects sometimes, render them everytime. - category_block = get_categories_block() - - view['blocks'].append(category_block) - - - # If custom fields are present, render them in the form - if custom_fields is not None and custom_fields['count'] > 0: - for field in custom_fields['data']: - - # Additional fields are field which are not custom fields but are dependent on categories - is_additional_field = False - if field['is_custom'] is False: - is_additional_field = True - - custom_field = generate_field_ui(field, is_additional_field=is_additional_field) - view['blocks'].append(custom_field) - - # Render cost center from current ui blocks if present - # This check is needed again here to maintain order - if current_ui_blocks is not None and cost_center_block is not None: - view['blocks'].append(cost_center_block) - else: - if fields_render_property['cost_center'] is True: - - cost_center_block = get_cost_centers_block() - - view['blocks'].append(cost_center_block) - - return view - - def expense_form_loading_modal() -> Dict: loading_modal = { 'type': 'modal', @@ -527,7 +424,7 @@ def expense_form_loading_modal() -> Dict: return loading_modal -def expense_dialog_form_v2(fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None) -> Dict: +def expense_dialog_form(fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', @@ -554,16 +451,19 @@ def expense_dialog_form_v2(fields_render_property: Dict = None, selected_project # If custom fields are present, render them in the form - if custom_fields is not None and custom_fields['count'] > 0: - for field in custom_fields['data']: - - # Additional fields are field which are not custom fields but are dependent on categories - is_additional_field = False - if field['is_custom'] is False: - is_additional_field = True - - custom_field = generate_field_ui(field, is_additional_field=is_additional_field) - view['blocks'].append(custom_field) + if custom_fields is not None: + if isinstance(custom_fields, list): + view['blocks'].extend(custom_fields) + elif custom_fields['count'] > 0: + for field in custom_fields['data']: + + # Additional fields are field which are not custom fields but are dependent on categories + is_additional_field = False + if field['is_custom'] is False: + is_additional_field = True + + custom_field = generate_field_ui(field, is_additional_field=is_additional_field) + view['blocks'].append(custom_field) if fields_render_property['cost_center'] is True: From d4dc7af23d84c96cde861e334016897a13d3076e Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 28 Sep 2021 13:48:02 +0530 Subject: [PATCH 31/85] Reduced a lot of api calls and made things faster --- fyle_slack_app/slack/commands/tasks.py | 12 ++- .../interactives/block_suggestion_handlers.py | 16 ++-- fyle_slack_app/slack/interactives/tasks.py | 88 +++++++++---------- fyle_slack_app/slack/ui/expenses/messages.py | 19 ++-- 4 files changed, 70 insertions(+), 65 deletions(-) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 2172777e..5acf4992 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -103,6 +103,8 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) + home_currency = fyle_profile['org']['currency'] + field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) is_project_available = False @@ -142,11 +144,15 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: private_metadata = { 'fields_render_property': fields_render_property, - 'home_currency': fyle_profile['org']['currency'] + 'home_currency': home_currency } - expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property) + encoded_metadata = utils.encode_state(private_metadata) + + additional_currency_details = { + 'home_currency': home_currency + } - expense_form['private_metadata'] = utils.encode_state(private_metadata) + expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details) slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 486ba420..1b1d3d0e 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -6,6 +6,7 @@ from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.libs import logger, utils from fyle_slack_app.slack import utils as slack_utils +from fyle_slack_app.slack.interactives.tasks import check_project_in_form logger = logger.get_logger(__name__) @@ -73,20 +74,15 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: 'is_enabled': 'eq.{}'.format(True) } - if 'project_block' in slack_payload['view']['state']['values'] and slack_payload['view']['state']['values']['project_block']['project_id']['selected_option'] is not None: + private_metadata = slack_payload['view']['private_metadata'] - project_id = int(slack_payload['view']['state']['values']['project_block']['project_id']['selected_option']['value']) + decoded_private_metadata = utils.decode_state(private_metadata) - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } + form_current_state = slack_payload['view']['state']['values'] - project = fyle_expense.get_projects(project_query_params) + is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) + if is_project_available is True: category_query_params['id'] = 'in.{}'.format(tuple(project['data'][0]['category_ids'])) suggested_categories = fyle_expense.get_categories(category_query_params) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index ed3c696c..51205a90 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -7,7 +7,7 @@ from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form -def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) -> Union[bool, Any]: +def check_project_in_form(form_current_state: Dict, private_metadata: Dict) -> Union[bool, Any]: is_project_available = False project = None @@ -16,19 +16,7 @@ def check_project_in_form(form_current_state: Dict, fyle_expense: FyleExpense) - is_project_available = True - if form_current_state['project_block']['project_id']['selected_option'] is not None: - - project_id = int(form_current_state['project_block']['project_id']['selected_option']['value']) - - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } - - project = fyle_expense.get_projects(project_query_params) + project = private_metadata.get('project') return is_project_available, project @@ -37,7 +25,7 @@ def get_custom_field_blocks(current_blocks: List[Dict]) -> List[Dict]: custom_field_blocks = [] for block in current_blocks: - if 'custom_field' in block['block_id']: + if 'custom_field' in block['block_id'] or 'additional_field' in block['block_id']: custom_field_blocks.append(block) if len(custom_field_blocks) == 0: @@ -91,11 +79,15 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st project = fyle_expense.get_projects(project_query_params) - private_metadata = decode_state(slack_payload['view']['private_metadata']) + private_metadata = slack_payload['view']['private_metadata'] + + decoded_private_metadata = decode_state(private_metadata) + + decoded_private_metadata['project'] = project - fields_render_property = private_metadata['fields_render_property'] + fields_render_property = decoded_private_metadata['fields_render_property'] - additional_currency_details = private_metadata.get('additional_currency_details') + additional_currency_details = decoded_private_metadata.get('additional_currency_details') current_ui_blocks = slack_payload['view']['blocks'] @@ -105,16 +97,14 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st form_current_state = slack_payload['view']['state']['values'] - custom_fields = get_custom_field_blocks(current_ui_blocks) - is_cost_center_available = check_cost_centers_in_form(form_current_state) fields_render_property['project'] = True fields_render_property['cost_center'] = is_cost_center_available - new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details, custom_fields=custom_fields) + encoded_private_metadata = encode_state(decoded_private_metadata) - new_expense_dialog_form['private_metadata'] = slack_payload['view']['private_metadata'] + new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details, private_metadata=encoded_private_metadata) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -137,11 +127,13 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: custom_fields = fyle_expense.get_expense_fields(custom_fields_query_params) - private_metadata = decode_state(slack_payload['view']['private_metadata']) + private_metadata = slack_payload['view']['private_metadata'] - fields_render_property = private_metadata['fields_render_property'] + decoded_private_metadata = decode_state(private_metadata) - additional_currency_details = private_metadata.get('additional_currency_details') + fields_render_property = decoded_private_metadata['fields_render_property'] + + additional_currency_details = decoded_private_metadata.get('additional_currency_details') current_ui_blocks = slack_payload['view']['blocks'] @@ -151,16 +143,14 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: form_current_state = slack_payload['view']['state']['values'] - is_project_available, project = check_project_in_form(form_current_state, fyle_expense) + is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) is_cost_center_available = check_cost_centers_in_form(form_current_state) fields_render_property['project'] = is_project_available fields_render_property['cost_center'] = is_cost_center_available - new_expense_dialog_form = expense_dialog_form(custom_fields=custom_fields, selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details) - - new_expense_dialog_form['private_metadata'] = slack_payload['view']['private_metadata'] + new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, custom_fields=custom_fields, selected_project=project, additional_currency_details=additional_currency_details, private_metadata=private_metadata) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -173,17 +163,21 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None slack_client = get_slack_client(team_id) - private_metadata = decode_state(slack_payload['view']['private_metadata']) + private_metadata = slack_payload['view']['private_metadata'] - fields_render_property = private_metadata['fields_render_property'] + decoded_private_metadata = decode_state(private_metadata) - home_currency = private_metadata['home_currency'] + fields_render_property = decoded_private_metadata['fields_render_property'] + + home_currency = decoded_private_metadata['home_currency'] form_current_state = slack_payload['view']['state']['values'] current_ui_blocks = slack_payload['view']['blocks'] - additional_currency_details = None + additional_currency_details = { + 'home_currency': home_currency + } if home_currency != selected_currency: exchange_rate = 70.12 @@ -191,11 +185,9 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None additional_currency_details = get_additional_currency_details(amount, home_currency, selected_currency, exchange_rate) - private_metadata['additional_currency_details'] = additional_currency_details - - fyle_expense = FyleExpense(user) + decoded_private_metadata['additional_currency_details'] = additional_currency_details - is_project_available, project = check_project_in_form(form_current_state, fyle_expense) + is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) is_cost_center_available = check_cost_centers_in_form(form_current_state) @@ -204,9 +196,9 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None fields_render_property['project'] = is_project_available fields_render_property['cost_center'] = is_cost_center_available - expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields) + encoded_private_metadata = encode_state(decoded_private_metadata) - expense_form['private_metadata'] = encode_state(private_metadata) + expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, private_metadata=encoded_private_metadata) slack_client.views_update(view_id=view_id, view=expense_form) @@ -221,13 +213,15 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: form_current_state = slack_payload['view']['state']['values'] - private_metadata = decode_state(slack_payload['view']['private_metadata']) + private_metadata = slack_payload['view']['private_metadata'] + + decoded_private_metadata = decode_state(private_metadata) current_ui_blocks = slack_payload['view']['blocks'] - fields_render_property = private_metadata['fields_render_property'] + fields_render_property = decoded_private_metadata['fields_render_property'] - home_currency = private_metadata['home_currency'] + home_currency = decoded_private_metadata['home_currency'] selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] @@ -235,19 +229,19 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: additional_currency_details = get_additional_currency_details(amount_entered, home_currency, selected_currency, exchange_rate) - custom_fields = get_custom_field_blocks(current_ui_blocks) + decoded_private_metadata['additional_currency_details'] = additional_currency_details - fyle_expense = FyleExpense(user) + custom_fields = get_custom_field_blocks(current_ui_blocks) - is_project_available, project = check_project_in_form(form_current_state, fyle_expense) + is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) is_cost_center_available = check_cost_centers_in_form(form_current_state) fields_render_property['project'] = is_project_available fields_render_property['cost_center'] = is_cost_center_available - expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields) + encoded_private_metadata = encode_state(decoded_private_metadata) - expense_form['private_metadata'] = slack_payload['view']['private_metadata'] + expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, private_metadata=encoded_private_metadata) slack_client.views_update(view_id=view_id, view=expense_form) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 674fdeae..9e0f3c5f 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -135,6 +135,14 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L 'emoji': True, }, 'min_query_length': 1, + 'initial_option': { + 'text': { + 'type': 'plain_text', + 'text': additional_currency_details['home_currency'], + 'emoji': True, + }, + 'value': additional_currency_details['home_currency'], + }, 'action_id': 'currency', }, 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, @@ -161,7 +169,7 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L blocks.append(amount_block) - if additional_currency_details is not None: + if 'foreign_currency' in additional_currency_details: amount_block['dispatch_action'] = True amount_block['element']['dispatch_action_config'] = { 'trigger_actions_on': [ @@ -424,10 +432,11 @@ def expense_form_loading_modal() -> Dict: return loading_modal -def expense_dialog_form(fields_render_property: Dict = None, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None) -> Dict: +def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None, private_metadata: str = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', + 'private_metadata': private_metadata, 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True} @@ -452,9 +461,7 @@ def expense_dialog_form(fields_render_property: Dict = None, selected_project: D # If custom fields are present, render them in the form if custom_fields is not None: - if isinstance(custom_fields, list): - view['blocks'].extend(custom_fields) - elif custom_fields['count'] > 0: + if 'count' in custom_fields and custom_fields['count'] > 0: for field in custom_fields['data']: # Additional fields are field which are not custom fields but are dependent on categories @@ -464,6 +471,8 @@ def expense_dialog_form(fields_render_property: Dict = None, selected_project: D custom_field = generate_field_ui(field, is_additional_field=is_additional_field) view['blocks'].append(custom_field) + else: + view['blocks'].extend(custom_fields) if fields_render_property['cost_center'] is True: From f83d9931f8e98b49f62c12a784fa8340bf5ce875 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 29 Sep 2021 17:40:45 +0530 Subject: [PATCH 32/85] Added support for non default custom fields as well --- fyle_slack_app/fyle/expenses/views.py | 16 -- fyle_slack_app/slack/commands/tasks.py | 53 ++--- .../interactives/block_action_handlers.py | 38 +++- .../interactives/block_suggestion_handlers.py | 6 +- fyle_slack_app/slack/interactives/tasks.py | 53 ++--- fyle_slack_app/slack/ui/expenses/messages.py | 211 +++++++++++++----- 6 files changed, 237 insertions(+), 140 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 8b6402e1..99a9257d 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -19,22 +19,6 @@ def get_expense_fields(self, query_params: Dict) -> Dict: return expense_fields - def get_default_expense_fields(self) -> Dict: - default_expense_fields_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'column_name': 'in.(purpose, txn_dt, vendor_id, cost_center_id, project_id)', - 'is_enabled': 'eq.{}'.format(True), - 'is_custom': 'eq.{}'.format(False), - 'is_mandatory': 'eq.{}'.format(True) - } - - default_expense_fields = self.get_expense_fields(default_expense_fields_query_params) - - return default_expense_fields - - def get_categories(self, query_params: Dict) -> Dict: categories = self.connection.v1.fyler.categories.list(query_params=query_params) return categories diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 5acf4992..2982e8ea 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -97,62 +97,53 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: fyle_expense = FyleExpense(user) - default_expense_fields = fyle_expense.get_default_expense_fields() - slack_client = slack_utils.get_slack_client(team_id) fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) home_currency = fyle_profile['org']['currency'] - field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) - is_project_available = False is_cost_centers_available = False - if field_type_mandatory_mapping['project_id'] is True: - projects_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } + projects_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } - projects = fyle_expense.get_projects(projects_query_params) + projects = fyle_expense.get_projects(projects_query_params) - is_project_available = True if projects['count'] > 0 else False + is_project_available = True if projects['count'] > 0 else False - if field_type_mandatory_mapping['cost_center_id'] is True: - cost_centers_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } + cost_centers_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } - cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) + cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) - is_cost_centers_available = True if cost_centers['count'] > 0 else False + is_cost_centers_available = True if cost_centers['count'] > 0 else False fields_render_property = { 'project': is_project_available, - 'cost_center': is_cost_centers_available, - 'purpose': field_type_mandatory_mapping['purpose'], - 'transaction_date': field_type_mandatory_mapping['txn_dt'], - 'vendor': field_type_mandatory_mapping['vendor_id'] + 'cost_center': is_cost_centers_available + } + + additional_currency_details = { + 'home_currency': home_currency } private_metadata = { 'fields_render_property': fields_render_property, - 'home_currency': home_currency + 'additional_currency_details': additional_currency_details } encoded_metadata = utils.encode_state(private_metadata) - additional_currency_details = { - 'home_currency': home_currency - } - expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details) slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 1d846a4b..601c47a5 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -9,6 +9,7 @@ from fyle_slack_app.libs import assertions, utils, logger from fyle_slack_app.slack.utils import get_slack_user_dm_channel_id, get_slack_client from fyle_slack_app.slack.ui.expenses import messages as expense_messages +from fyle_slack_app.slack.interactives import tasks from fyle_slack_app import tracking @@ -41,7 +42,8 @@ def _initialize_block_action_handlers(self): 'project_id': self.handle_project_select, 'is_billable': self.handle_billable, 'currency': self.handle_currency_select, - 'amount': self.handle_amount_entered + 'amount': self.handle_amount_entered, + 'add_to_report': self.handle_add_to_report } @@ -274,6 +276,40 @@ def handle_amount_entered(self, slack_payload: Dict, user_id: str, team_id: str) return JsonResponse({}) + def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + add_to_report = slack_payload['actions'][0]['selected_option']['value'] + + view_id = slack_payload['container']['view_id'] + + slack_client = get_slack_client(team_id) + + private_metadata = slack_payload['view']['private_metadata'] + + decoded_private_metadata = utils.decode_state(private_metadata) + + fields_render_property = decoded_private_metadata['fields_render_property'] + + current_ui_blocks = slack_payload['view']['blocks'] + + additional_currency_details = decoded_private_metadata.get('additional_currency_details') + + is_project_available, project = tasks.check_project_in_form(fields_render_property, decoded_private_metadata) + + custom_fields = tasks.get_custom_field_blocks(current_ui_blocks) + + fields_render_property['project'] = is_project_available + + decoded_private_metadata['add_to_report'] = add_to_report + + encoded_private_metadata = utils.encode_state(decoded_private_metadata) + + expense_form = expense_messages.expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, add_to_report=add_to_report, private_metadata=encoded_private_metadata) + + slack_client.views_update(view_id=view_id, view=expense_form) + + return JsonResponse({}) + + def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: user = utils.get_or_none(User, slack_user_id=user_id) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 1b1d3d0e..fe17a30e 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -78,11 +78,11 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: decoded_private_metadata = utils.decode_state(private_metadata) - form_current_state = slack_payload['view']['state']['values'] + fields_render_property = decoded_private_metadata['fields_render_property'] - is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) + is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) - if is_project_available is True: + if is_project_available is True and project is not None: category_query_params['id'] = 'in.{}'.format(tuple(project['data'][0]['category_ids'])) suggested_categories = fyle_expense.get_categories(category_query_params) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 51205a90..89ebb485 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -7,12 +7,12 @@ from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form -def check_project_in_form(form_current_state: Dict, private_metadata: Dict) -> Union[bool, Any]: +def check_project_in_form(fields_render_property: Dict, private_metadata: Dict) -> Union[bool, Any]: is_project_available = False project = None - if 'project_block' in form_current_state: + if fields_render_property['project'] is True: is_project_available = True @@ -34,16 +34,6 @@ def get_custom_field_blocks(current_blocks: List[Dict]) -> List[Dict]: return custom_field_blocks -def check_cost_centers_in_form(form_current_state) -> bool: - - cost_centers = False - - if 'cost_center_block' in form_current_state and form_current_state['cost_center_block']['cost_center_id']['selected_option'] is not None: - cost_centers = True - - return cost_centers - - def get_additional_currency_details(amount: int, home_currency: str, selected_currency: str, exchange_rate: float) -> Dict: if amount is None or len(amount) == 0: @@ -89,22 +79,19 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st additional_currency_details = decoded_private_metadata.get('additional_currency_details') + add_to_report = decoded_private_metadata.get('add_to_report') + current_ui_blocks = slack_payload['view']['blocks'] # Removing loading info from below project input element project_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'project_loading_block'), None) current_ui_blocks.pop(project_loading_block_index) - form_current_state = slack_payload['view']['state']['values'] - - is_cost_center_available = check_cost_centers_in_form(form_current_state) - fields_render_property['project'] = True - fields_render_property['cost_center'] = is_cost_center_available encoded_private_metadata = encode_state(decoded_private_metadata) - new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details, private_metadata=encoded_private_metadata) + new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details, add_to_report=add_to_report, private_metadata=encoded_private_metadata) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -117,7 +104,6 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: 'offset': 0, 'limit': '20', 'order': 'created_at.desc', - 'or': '(is_mandatory.eq.{}, and(is_custom.eq.{}, is_mandatory.eq.{}))'.format(True, True, True), 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', 'is_enabled': 'eq.{}'.format(True), 'category_ids': 'cs.[{}]'.format(int(category_id)) @@ -135,22 +121,19 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: additional_currency_details = decoded_private_metadata.get('additional_currency_details') + add_to_report = decoded_private_metadata.get('add_to_report') + current_ui_blocks = slack_payload['view']['blocks'] # Removing loading info from below category input element category_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'category_loading_block'), None) current_ui_blocks.pop(category_loading_block_index) - form_current_state = slack_payload['view']['state']['values'] - - is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) - - is_cost_center_available = check_cost_centers_in_form(form_current_state) + is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) fields_render_property['project'] = is_project_available - fields_render_property['cost_center'] = is_cost_center_available - new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, custom_fields=custom_fields, selected_project=project, additional_currency_details=additional_currency_details, private_metadata=private_metadata) + new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, custom_fields=custom_fields, selected_project=project, additional_currency_details=additional_currency_details, add_to_report=add_to_report, private_metadata=private_metadata) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -169,7 +152,9 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None fields_render_property = decoded_private_metadata['fields_render_property'] - home_currency = decoded_private_metadata['home_currency'] + add_to_report = decoded_private_metadata.get('add_to_report') + + home_currency = decoded_private_metadata['additional_currency_details']['home_currency'] form_current_state = slack_payload['view']['state']['values'] @@ -189,16 +174,13 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) - is_cost_center_available = check_cost_centers_in_form(form_current_state) - custom_fields = get_custom_field_blocks(current_ui_blocks) fields_render_property['project'] = is_project_available - fields_render_property['cost_center'] = is_cost_center_available encoded_private_metadata = encode_state(decoded_private_metadata) - expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, private_metadata=encoded_private_metadata) + expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, add_to_report=add_to_report, private_metadata=encoded_private_metadata) slack_client.views_update(view_id=view_id, view=expense_form) @@ -221,7 +203,9 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: fields_render_property = decoded_private_metadata['fields_render_property'] - home_currency = decoded_private_metadata['home_currency'] + add_to_report = decoded_private_metadata.get('add_to_report') + + home_currency = decoded_private_metadata['additional_currency_details']['home_currency'] selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] @@ -235,13 +219,10 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) - is_cost_center_available = check_cost_centers_in_form(form_current_state) - fields_render_property['project'] = is_project_available - fields_render_property['cost_center'] = is_cost_center_available encoded_private_metadata = encode_state(decoded_private_metadata) - expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, private_metadata=encoded_private_metadata) + expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, add_to_report=add_to_report, custom_fields=custom_fields, private_metadata=encoded_private_metadata) slack_client.views_update(view_id=view_id, view=expense_form) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 9e0f3c5f..8c36f0bc 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -7,6 +7,8 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> block_id = '{}_block'.format(field_details['column_name']) action_id = field_details['column_name'] + custom_field = None + # We need to define addtional fields as custom fields so that we can clear them out in form when category is changed if field_details['is_custom'] is True or is_additional_field is True: block_id = '{}_additional_field_{}_block'.format(field_details['type'], field_details['column_name']) @@ -215,44 +217,42 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L return blocks -def get_default_fields_blocks(fields_render_property: Dict, additional_currency_details: Dict = None) -> List: +def get_default_fields_blocks(additional_currency_details: Dict = None) -> List: default_fields_blocks = get_amount_and_currency_block(additional_currency_details) - if fields_render_property['transaction_date'] is True: - date_of_spend_block = { - 'type': 'input', - 'block_id': 'DATE_default_field_date_of_spend_block', - 'element': { - 'type': 'datepicker', - 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), - 'placeholder': { - 'type': 'plain_text', - 'text': 'Select a date', - 'emoji': True, - }, - 'action_id': 'spent_at', + date_of_spend_block = { + 'type': 'input', + 'block_id': 'DATE_default_field_date_of_spend_block', + 'element': { + 'type': 'datepicker', + 'initial_date': datetime.datetime.today().strftime('%Y-%m-%d'), + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select a date', + 'emoji': True, }, - 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, - } - default_fields_blocks.append(date_of_spend_block) + 'action_id': 'spent_at', + }, + 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, + } + default_fields_blocks.append(date_of_spend_block) - if fields_render_property['purpose'] is True: - purpose_block = { - 'type': 'input', - 'block_id': 'TEXT_default_field_purpose_block', - 'element': { - 'type': 'plain_text_input', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Eg. Client Meeting', - 'emoji': True, - }, - 'action_id': 'purpose', + purpose_block = { + 'type': 'input', + 'block_id': 'TEXT_default_field_purpose_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Client Meeting', + 'emoji': True, }, - 'label': {'type': 'plain_text', 'text': 'Purpose', 'emoji': True}, - } - default_fields_blocks.append(purpose_block) + 'action_id': 'purpose', + }, + 'label': {'type': 'plain_text', 'text': 'Purpose', 'emoji': True}, + } + default_fields_blocks.append(purpose_block) payment_mode_block = { 'type': 'input', @@ -296,22 +296,21 @@ def get_default_fields_blocks(fields_render_property: Dict, additional_currency_ } default_fields_blocks.append(payment_mode_block) - if fields_render_property['vendor'] is True: - merchant_block = { - 'type': 'input', - 'block_id': 'TEXT_default_field_merchant_block', - 'element': { - 'type': 'plain_text_input', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Eg. Uber', - 'emoji': True, - }, - 'action_id': 'merchant', + merchant_block = { + 'type': 'input', + 'block_id': 'TEXT_default_field_merchant_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Eg. Uber', + 'emoji': True, }, - 'label': {'type': 'plain_text', 'text': 'Merchant', 'emoji': True}, - } - default_fields_blocks.append(merchant_block) + 'action_id': 'merchant', + }, + 'label': {'type': 'plain_text', 'text': 'Merchant', 'emoji': True}, + } + default_fields_blocks.append(merchant_block) return default_fields_blocks @@ -432,7 +431,98 @@ def expense_form_loading_modal() -> Dict: return loading_modal -def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None, private_metadata: str = None) -> Dict: +def get_add_to_report_blocks(add_to_report: str) -> Dict: + blocks = [] + add_to_existing_report_option = { + 'text': { + 'type': 'plain_text', + 'text': 'Add to Existing Report', + 'emoji': True + }, + 'value': 'existing_report' + } + + add_to_new_report_option = { + 'text': { + 'type': 'plain_text', + 'text': 'Add to New Report', + 'emoji': True + }, + 'value': 'new_report' + } + add_to_report_block = { + 'type': 'input', + 'block_id': 'add_to_report_block', + 'dispatch_action': True, + 'element': { + 'type': 'radio_buttons', + 'options': [add_to_existing_report_option, add_to_new_report_option], + 'action_id': 'add_to_report' + }, + 'label': { + 'type': 'plain_text', + 'text': 'Add to Report', + 'emoji': True + } + } + blocks.append(add_to_report_block) + + add_to_report_mapping = { + 'new_report': { + 'ui': { + 'type': 'input', + 'block_id': 'add_to_new_report_block', + 'element': { + 'type': 'plain_text_input', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Enter Report Name', + 'emoji': True + }, + 'action_id': 'report_name' + }, + 'label': { + 'type': 'plain_text', + 'text': 'Report Name', + 'emoji': True + } + }, + 'option': add_to_new_report_option + }, + 'existing_report': { + 'ui': { + 'type': 'input', + 'block_id': 'add_to_existing_report_block', + 'element': { + 'type': 'external_select', + 'placeholder': { + 'type': 'plain_text', + 'text': 'Select a Report', + 'emoji': True + }, + 'action_id': 'existing_report' + }, + 'label': { + 'type': 'plain_text', + 'text': 'Select Report', + 'emoji': True + } + }, + 'option': add_to_existing_report_option + } + } + + if add_to_report is not None: + add_to_report_details = add_to_report_mapping[add_to_report] + add_to_report_additional_block = add_to_report_details['ui'] + selected_report_option = add_to_report_details['option'] + add_to_report_block['element']['initial_option'] = selected_report_option + blocks.append(add_to_report_additional_block) + + return blocks + + +def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None, add_to_report: str = None, private_metadata: str = None) -> Dict: view = { 'type': 'modal', 'callback_id': 'create_expense', @@ -444,7 +534,7 @@ def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = N view['blocks'] = [] - view['blocks'] = get_default_fields_blocks(fields_render_property, additional_currency_details) + view['blocks'] = get_default_fields_blocks(additional_currency_details) if fields_render_property['project'] is True: @@ -461,7 +551,9 @@ def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = N # If custom fields are present, render them in the form if custom_fields is not None: - if 'count' in custom_fields and custom_fields['count'] > 0: + if isinstance(custom_fields, list): + view['blocks'].extend(custom_fields) + elif 'count' in custom_fields and custom_fields['count'] > 0: for field in custom_fields['data']: # Additional fields are field which are not custom fields but are dependent on categories @@ -470,9 +562,8 @@ def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = N is_additional_field = True custom_field = generate_field_ui(field, is_additional_field=is_additional_field) - view['blocks'].append(custom_field) - else: - view['blocks'].extend(custom_fields) + if custom_field is not None: + view['blocks'].append(custom_field) if fields_render_property['cost_center'] is True: @@ -480,4 +571,18 @@ def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = N view['blocks'].append(cost_center_block) + + # Divider for add to report section + view['blocks'].append({ + 'type': 'divider' + }) + + add_to_report_blocks = get_add_to_report_blocks(add_to_report) + + view['blocks'].extend(add_to_report_blocks) + return view + + +def view_expense_message(expense: Dict) -> Dict: + pass From 52e3abaa60ed90f85ad003f933b70f9f3b8703ec Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Fri, 1 Oct 2021 16:43:51 +0530 Subject: [PATCH 33/85] Edit expense support --- fyle_slack_app/fyle/expenses/views.py | 16 ++ fyle_slack_app/slack/commands/handlers.py | 2 +- fyle_slack_app/slack/commands/tasks.py | 10 +- .../interactives/block_action_handlers.py | 26 +- fyle_slack_app/slack/interactives/tasks.py | 62 ++++ .../interactives/view_submission_handlers.py | 91 +----- fyle_slack_app/slack/ui/expenses/messages.py | 272 ++++++++++++++---- 7 files changed, 336 insertions(+), 143 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 99a9257d..8b6402e1 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -19,6 +19,22 @@ def get_expense_fields(self, query_params: Dict) -> Dict: return expense_fields + def get_default_expense_fields(self) -> Dict: + default_expense_fields_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'column_name': 'in.(purpose, txn_dt, vendor_id, cost_center_id, project_id)', + 'is_enabled': 'eq.{}'.format(True), + 'is_custom': 'eq.{}'.format(False), + 'is_mandatory': 'eq.{}'.format(True) + } + + default_expense_fields = self.get_expense_fields(default_expense_fields_query_params) + + return default_expense_fields + + def get_categories(self, query_params: Dict) -> Dict: categories = self.connection.v1.fyler.categories.list(query_params=query_params) return categories diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index 7039de6c..002f8eef 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -105,7 +105,7 @@ def handle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: st slack_client = slack_utils.get_slack_client(team_id) - loading_modal = expense_messages.expense_form_loading_modal() + loading_modal = expense_messages.expense_form_loading_modal(title='Create Expense', loading_message='Loading the best expense form :zap:') response = slack_client.views_open(user=user_id, view=loading_modal, trigger_id=trigger_id) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 2982e8ea..8c73ea2c 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -97,12 +97,16 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: fyle_expense = FyleExpense(user) + default_expense_fields = fyle_expense.get_default_expense_fields() + slack_client = slack_utils.get_slack_client(team_id) fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) home_currency = fyle_profile['org']['currency'] + field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) + is_project_available = False is_cost_centers_available = False @@ -130,7 +134,10 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: fields_render_property = { 'project': is_project_available, - 'cost_center': is_cost_centers_available + 'cost_center': is_cost_centers_available, + 'purpose': field_type_mandatory_mapping['purpose'], + 'transaction_date': field_type_mandatory_mapping['txn_dt'], + 'vendor': field_type_mandatory_mapping['vendor_id'] } additional_currency_details = { @@ -139,6 +146,7 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: private_metadata = { 'fields_render_property': fields_render_property, + 'home_currency': home_currency, 'additional_currency_details': additional_currency_details } diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 601c47a5..80176588 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -36,6 +36,7 @@ def _initialize_block_action_handlers(self): 'report_paid_notification_preference': self.handle_notification_preference_selection, 'report_commented_notification_preference': self.handle_notification_preference_selection, 'expense_commented_notification_preference': self.handle_notification_preference_selection, + 'edit_expense': self.handle_edit_expense, # Dynamic options 'category_id': self.handle_category_select, @@ -163,7 +164,7 @@ def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) user = utils.get_or_none(User, slack_user_id=user_id) - current_view = expense_messages.expense_form_loading_modal() + current_view = expense_messages.expense_form_loading_modal(title='Create Expense', loading_message='Loading the best expense form :zap:') current_view['submit'] = {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True} blocks = slack_payload['view']['blocks'] @@ -210,7 +211,7 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str user = utils.get_or_none(User, slack_user_id=user_id) - current_view = expense_messages.expense_form_loading_modal() + current_view = expense_messages.expense_form_loading_modal(title='Create Expense', loading_message='Loading the best expense form :zap:') current_view['submit'] = {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True} blocks = slack_payload['view']['blocks'] @@ -310,6 +311,27 @@ def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) return JsonResponse({}) + def handle_edit_expense(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + + loading_modal = expense_messages.expense_form_loading_modal(title='Edit Expense', loading_message='Loading expense details :receipt: ') + + slack_client = get_slack_client(team_id) + + user = utils.get_or_none(User, slack_user_id=user_id) + + response = slack_client.views_open(view=loading_modal, trigger_id=slack_payload['trigger_id']) + + async_task( + 'fyle_slack_app.slack.interactives.tasks.handle_edit_expense', + user, + team_id, + response['view']['id'], + slack_payload + ) + + return JsonResponse({}) + + def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: user = utils.get_or_none(User, slack_user_id=user_id) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 89ebb485..89dfffc7 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -2,10 +2,13 @@ from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense +from fyle_slack_app.fyle.utils import get_fyle_profile from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form +from . import expense + def check_project_in_form(fields_render_property: Dict, private_metadata: Dict) -> Union[bool, Any]: @@ -226,3 +229,62 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, add_to_report=add_to_report, custom_fields=custom_fields, private_metadata=encoded_private_metadata) slack_client.views_update(view_id=view_id, view=expense_form) + + +def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: List[Dict]) -> None: + + fyle_expense = FyleExpense(user) + + slack_client = get_slack_client(team_id) + + fyle_profile = get_fyle_profile(user.fyle_refresh_token) + + expense_id = slack_payload['actions'][0]['value'] + + home_currency = fyle_profile['org']['currency'] + + is_project_available = False + is_cost_centers_available = False + + projects_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + projects = fyle_expense.get_projects(projects_query_params) + + is_project_available = True if projects['count'] > 0 else False + + cost_centers_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) + + is_cost_centers_available = True if cost_centers['count'] > 0 else False + + fields_render_property = { + 'project': is_project_available, + 'cost_center': is_cost_centers_available + } + + additional_currency_details = { + 'home_currency': home_currency + } + + private_metadata = { + 'fields_render_property': fields_render_property, + 'additional_currency_details': additional_currency_details, + 'expense_id': expense_id + } + + encoded_metadata = encode_state(private_metadata) + + expense_form = expense_dialog_form(expense=expense['data'], fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details) + + slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 36971a0f..1b653344 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -5,6 +5,8 @@ from django.http.response import JsonResponse from fyle_slack_app.slack import utils as slack_utils +from fyle_slack_app.libs import utils +from fyle_slack_app.slack.ui.expenses import messages as expense_messages class ViewSubmissionHandler: @@ -14,7 +16,7 @@ class ViewSubmissionHandler: # Maps action_id with it's respective function def _initialize_view_submission_handlers(self): self._view_submission_handlers = { - 'create_expense': self.handle_create_expense + 'upsert_expense': self.handle_upsert_expense } @@ -48,13 +50,22 @@ def handle_view_submission(self, slack_payload: Dict, user_id: str, team_id: str return handler(slack_payload, user_id, team_id) - def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str): + def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str): + + print('SLACK PAYLOAD -> ', slack_payload) form_values = slack_payload['view']['state']['values'] print('REACHED CREATE EXPENSE -> ', form_values) + private_metadata = utils.decode_state(slack_payload['view']['private_metadata']) + expense_details, validation_errors = self.extract_form_values_and_validate(form_values) + expense_id = private_metadata.get('expense_id') + + if expense_id is not None: + expense_details['id'] = expense_id + print('VALIDATION ERRORS -> ', validation_errors) # If valdiation errors are present then return errors @@ -68,81 +79,9 @@ def handle_create_expense(self, slack_payload: Dict, user_id: str, team_id: str) slack_client = slack_utils.get_slack_client(team_id) - blocks = [ - { - 'type': 'section', - 'text': { - 'type': 'plain_text', - 'text': 'Expense created successfully :clipboard:', - 'emoji': True, - }, - }, - { - 'type': 'section', - 'fields': [ - {'type': 'mrkdwn', 'text': '*Amount*: \n {} {}'.format(expense_details['currency'], expense_details['amount'])}, - {'type': 'mrkdwn', 'text': '*Merchant*: \n {}'.format(expense_details['merchant'])}, - ], - }, - { - 'type': 'section', - 'fields': [ - {'type': 'mrkdwn', 'text': '*Date of Spend*: \n {}'.format(expense_details['spent_at'])}, - {'type': 'mrkdwn', 'text': '*Purpose*: \n {}'.format(expense_details['purpose'])}, - ], - } - ] - - cf_section = { - 'type': 'section', - 'fields': [] - } + view_expense_message = expense_messages.view_expense_message(expense_details) - if len(expense_details['custom_fields']) > 0: - for custom_field in expense_details['custom_fields']: - for cf in custom_field.keys(): - if isinstance(custom_field[cf], list): - value = ', '.join(custom_field[cf]) - elif isinstance(custom_field[cf], bool): - value = 'Yes' if custom_field[cf] is True else 'No' - else: - value = custom_field[cf] - cf_section['fields'].append( - {'type': 'mrkdwn', 'text': '*{}*: \n {}'.format(cf.title(), value)} - ) - - blocks.append(cf_section) - blocks.append( - { - 'type': 'actions', - 'elements': [ - { - 'type': 'button', - 'text': { - 'type': 'plain_text', - 'text': 'Edit Expense', - 'emoji': True, - }, - 'value': 'tx123456', - 'action_id': 'edit_expense', - } - ], - } - ) - blocks.append( - { - 'type': 'context', - 'block_id': 'tx123456', - 'elements': [ - { - 'type': 'plain_text', - 'text': 'Powered by Fyle', - 'emoji': True, - } - ], - } - ) - slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=blocks) + slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=view_expense_message) return JsonResponse({}) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 8c36f0bc..08ad5a16 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -1,5 +1,12 @@ +from typing import Any, Dict, List + import datetime -from typing import Dict, List + +from fyle_slack_app.libs import utils + + +def get_custom_field_value(custom_fields: List, action_id) -> Any: + pass # is_additional_field is for fields which are not custom fields but are part of a specific categories @@ -122,7 +129,7 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> return custom_field -def get_amount_and_currency_block(additional_currency_details: Dict = None) -> List: +def get_amount_and_currency_block(additional_currency_details: Dict = None, expense: Dict = None) -> List: blocks = [] currency_block = { @@ -144,12 +151,16 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L 'emoji': True, }, 'value': additional_currency_details['home_currency'], - }, + }, 'action_id': 'currency', }, 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, } + if expense is not None: + currency_block['element']['initial_option']['text']['text'] = expense['currency'] + currency_block['element']['initial_option']['value'] = expense['currency'] + blocks.append(currency_block) currency_context_block = None @@ -169,8 +180,18 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, } + if expense is not None: + amount_block['element']['initial_value'] = str(expense['amount']) + blocks.append(amount_block) + if expense is not None and expense['foreign_currency'] is not None: + additional_currency_details = { + 'home_currency': expense['currency'], + 'foreign_currency': expense['foreign_currency'], + 'total_amount': str(expense['foreign_amount']) + } + if 'foreign_currency' in additional_currency_details: amount_block['dispatch_action'] = True amount_block['element']['dispatch_action_config'] = { @@ -217,9 +238,9 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None) -> L return blocks -def get_default_fields_blocks(additional_currency_details: Dict = None) -> List: +def get_default_fields_blocks(additional_currency_details: Dict = None, expense: Dict = None) -> List: - default_fields_blocks = get_amount_and_currency_block(additional_currency_details) + default_fields_blocks = get_amount_and_currency_block(additional_currency_details, expense) date_of_spend_block = { 'type': 'input', @@ -236,6 +257,10 @@ def get_default_fields_blocks(additional_currency_details: Dict = None) -> List: }, 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, } + + if expense is not None: + date_of_spend_block['element']['initial_date'] = utils.get_formatted_datetime(expense['spent_at'], '%Y-%m-%d') + default_fields_blocks.append(date_of_spend_block) purpose_block = { @@ -252,8 +277,30 @@ def get_default_fields_blocks(additional_currency_details: Dict = None) -> List: }, 'label': {'type': 'plain_text', 'text': 'Purpose', 'emoji': True}, } + + if expense is not None: + purpose_block['element']['initial_value'] = expense['purpose'] + default_fields_blocks.append(purpose_block) + paid_by_me_option = { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by me', + 'emoji': True, + }, + 'value': 'true', + } + + paid_by_company_option = { + 'text': { + 'type': 'plain_text', + 'text': 'Paid by company', + 'emoji': True, + }, + 'value': 'false', + } + payment_mode_block = { 'type': 'input', 'block_id': 'SELECT_default_field_payment_mode_block', @@ -264,36 +311,17 @@ def get_default_fields_blocks(additional_currency_details: Dict = None) -> List: 'text': 'Select Payment Mode', 'emoji': True, }, - 'initial_option': { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by me', - 'emoji': True, - }, - 'value': 'true', - }, - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by me', - 'emoji': True, - }, - 'value': 'true', - }, - { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by company', - 'emoji': True, - }, - 'value': 'false', - }, - ], + 'initial_option': paid_by_me_option, + 'options': [paid_by_me_option, paid_by_company_option], 'action_id': 'is_reimbursable', }, 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, } + + if expense is not None: + if expense['is_reimbursable'] is False: + payment_mode_block['element']['initial_option'] = paid_by_company_option + default_fields_blocks.append(payment_mode_block) merchant_block = { @@ -310,12 +338,16 @@ def get_default_fields_blocks(additional_currency_details: Dict = None) -> List: }, 'label': {'type': 'plain_text', 'text': 'Merchant', 'emoji': True}, } + + if expense is not None: + merchant_block['element']['initial_value'] = expense['merchant'] + default_fields_blocks.append(merchant_block) return default_fields_blocks -def get_projects_and_billable_block(selected_project: Dict = None) -> Dict: +def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict = None) -> Dict: project_block = { 'type': 'input', 'block_id': 'project_block', @@ -333,7 +365,17 @@ def get_projects_and_billable_block(selected_project: Dict = None) -> Dict: }, 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, } - if selected_project is not None: + + if expense is not None: + project_block['element']['initial_option'] = { + 'text': { + 'type': 'plain_text', + 'text': expense['project']['name'], + 'emoji': True, + }, + 'value': str(expense['project']['id']), + } + elif selected_project is not None: selected_project = selected_project['data'][0] project_display_name = selected_project['display_name'] @@ -349,29 +391,28 @@ def get_projects_and_billable_block(selected_project: Dict = None) -> Dict: } billable_block = { - 'type': 'actions', + 'type': 'input', 'block_id': 'billable_block', - 'elements': [ - { - 'type': 'checkboxes', - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'Billable', - 'emoji': True - } + 'element': { + 'type': 'checkboxes', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'Billable', + 'emoji': True } - ], - 'action_id': 'is_billable' - } - ] + } + ], + 'action_id': 'is_billable' + }, + 'label': {'type': 'plain_text', 'text': 'Billable', 'emoji': True}, } return project_block, billable_block -def get_categories_block() -> Dict: +def get_categories_block(expense: Dict = None) -> Dict: category_block = { 'type': 'input', 'block_id': 'category_block', @@ -389,10 +430,20 @@ def get_categories_block() -> Dict: 'label': {'type': 'plain_text', 'text': 'Category', 'emoji': True}, } + if expense is not None: + category_block['element']['initial_option'] = { + 'text': { + 'type': 'plain_text', + 'text': expense['category']['name'], + 'emoji': True, + }, + 'value': str(expense['category']['id']), + } + return category_block -def get_cost_centers_block() -> Dict: +def get_cost_centers_block(expense: Dict = None) -> Dict: cost_centers_block = { 'type': 'input', 'block_id': 'cost_center_block', @@ -408,21 +459,31 @@ def get_cost_centers_block() -> Dict: }, 'label': {'type': 'plain_text', 'text': 'Cost Center', 'emoji': True}, } + + if expense is not None: + cost_centers_block['element']['initial_option'] = { + 'text': { + 'type': 'plain_text', + 'text': expense['cost_center']['name'], + 'emoji': True, + }, + 'value': str(expense['cost_center']['id']), + } return cost_centers_block -def expense_form_loading_modal() -> Dict: +def expense_form_loading_modal(title: str, loading_message: str) -> Dict: loading_modal = { 'type': 'modal', - 'callback_id': 'create_expense', - 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, + 'callback_id': 'upsert_expense', + 'title': {'type': 'plain_text', 'text': '{}'.format(title), 'emoji': True}, 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True}, 'blocks': [ { 'type': 'section', 'text': { 'type': 'mrkdwn', - 'text': 'Loading the best expense form :zap:' + 'text': '{}'.format(loading_message) } } ] @@ -522,10 +583,19 @@ def get_add_to_report_blocks(add_to_report: str) -> Dict: return blocks -def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = None, custom_fields: Dict = None, additional_currency_details: Dict = None, add_to_report: str = None, private_metadata: str = None) -> Dict: +def expense_dialog_form( + fields_render_property: Dict, + selected_project: Dict = None, + custom_fields: Dict = None, + additional_currency_details: Dict = None, + add_to_report: str = None, + private_metadata: str = None, + expense : Dict = None + ) -> Dict: + view = { 'type': 'modal', - 'callback_id': 'create_expense', + 'callback_id': 'upsert_expense', 'private_metadata': private_metadata, 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, @@ -534,17 +604,17 @@ def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = N view['blocks'] = [] - view['blocks'] = get_default_fields_blocks(additional_currency_details) + view['blocks'] = get_default_fields_blocks(additional_currency_details, expense) if fields_render_property['project'] is True: - project_block, billable_block = get_projects_and_billable_block(selected_project) + project_block, billable_block = get_projects_and_billable_block(selected_project, expense) view['blocks'].append(project_block) view['blocks'].append(billable_block) - category_block = get_categories_block() + category_block = get_categories_block(expense) view['blocks'].append(category_block) @@ -567,7 +637,7 @@ def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = N if fields_render_property['cost_center'] is True: - cost_center_block = get_cost_centers_block() + cost_center_block = get_cost_centers_block(expense) view['blocks'].append(cost_center_block) @@ -577,12 +647,88 @@ def expense_dialog_form(fields_render_property: Dict, selected_project: Dict = N 'type': 'divider' }) - add_to_report_blocks = get_add_to_report_blocks(add_to_report) + if add_to_report is not None: + add_to_report_blocks = get_add_to_report_blocks(add_to_report) - view['blocks'].extend(add_to_report_blocks) + view['blocks'].extend(add_to_report_blocks) return view def view_expense_message(expense: Dict) -> Dict: - pass + from fyle_slack_app.slack.interactives import expense + expense = expense['data'] + + spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') + + receipt_message = ':x: Not Attached' + if len(expense['file_ids']) > 0: + receipt_message = ':white_check_mark: Attached' + + report_message = ':x: Not Added' + if expense['report_id'] is not None: + report_message = ':white_check_mark: Added' + + view_expense_blocks = [ + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) + } + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Date of spend: * \n {}'.format(spent_at) + }, + { + 'type': 'mrkdwn', + 'text': '*Receipt: * \n {}'.format(receipt_message) + } + ] + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Report: * \n {}'.format(report_message) + }, + { + 'type': 'mrkdwn', + 'text': '*Expense Details: * \n Travel to Office (Uber)' + } + ] + }, + { + 'type': 'actions', + 'elements': [ + { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Edit Expense', + 'emoji': True, + }, + 'value': expense['id'], + 'action_id': 'edit_expense', + } + ], + }, + { + 'type': 'context', + 'block_id': expense['id'], + 'elements': [ + { + 'type': 'plain_text', + 'text': 'Powered by Fyle', + 'emoji': True, + } + ], + } + ] + + return view_expense_blocks From 4670a95ebd77c09bdf0403799648eafa39b438e6 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 4 Oct 2021 16:33:04 +0530 Subject: [PATCH 34/85] Add to report dialog and edit expense --- fyle_slack_app/fyle/expenses/views.py | 15 ++ .../interactives/block_action_handlers.py | 39 +++- fyle_slack_app/slack/interactives/tasks.py | 13 +- .../interactives/view_submission_handlers.py | 17 +- fyle_slack_app/slack/ui/expenses/messages.py | 206 +++++++++++++++--- 5 files changed, 245 insertions(+), 45 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 8b6402e1..5c7ff65b 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -35,6 +35,21 @@ def get_default_expense_fields(self) -> Dict: return default_expense_fields + def get_custom_fields_by_category_id(self, category_id: str) -> Dict: + custom_fields_query_params = { + 'offset': 0, + 'limit': '20', + 'order': 'created_at.desc', + 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', + 'is_enabled': 'eq.{}'.format(True), + 'category_ids': 'cs.[{}]'.format(int(category_id)) + } + + custom_fields = self.get_expense_fields(custom_fields_query_params) + + return custom_fields + + def get_categories(self, query_params: Dict) -> Dict: categories = self.connection.v1.fyler.categories.list(query_params=query_params) return categories diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 80176588..6e3c7280 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -44,7 +44,9 @@ def _initialize_block_action_handlers(self): 'is_billable': self.handle_billable, 'currency': self.handle_currency_select, 'amount': self.handle_amount_entered, - 'add_to_report': self.handle_add_to_report + 'add_to_report': self.handle_add_to_report, + 'add_expense_to_report': self.handle_add_expense_to_report, + 'add_expense_to_report_selection': self.handle_add_expense_to_report_selection } @@ -311,6 +313,41 @@ def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) return JsonResponse({}) + def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + from . import expense + + add_to_report = slack_payload['actions'][0]['selected_option']['value'] + + view_id = slack_payload['container']['view_id'] + + slack_client = get_slack_client(team_id) + + expense_id = slack_payload['view']['private_metadata'] + + add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'], add_to_report=add_to_report) + + slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) + return JsonResponse({}) + + + def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + from . import expense + + expense_id = slack_payload['actions'][0]['value'] + + view_id = slack_payload['container']['view_id'] + + slack_client = get_slack_client(team_id) + + add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'], add_to_report='existing_report') + + add_expense_to_report_dialog['private_metadata'] = expense_id + + slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) + + return JsonResponse({}) + + def handle_edit_expense(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: loading_modal = expense_messages.expense_form_loading_modal(title='Edit Expense', loading_message='Loading expense details :receipt: ') diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 89dfffc7..ea5313a5 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -103,18 +103,9 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: slack_client = get_slack_client(team_id) - custom_fields_query_params = { - 'offset': 0, - 'limit': '20', - 'order': 'created_at.desc', - 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', - 'is_enabled': 'eq.{}'.format(True), - 'category_ids': 'cs.[{}]'.format(int(category_id)) - } - fyle_expense = FyleExpense(user) - custom_fields = fyle_expense.get_expense_fields(custom_fields_query_params) + custom_fields = fyle_expense.get_custom_fields_by_category_id(category_id) private_metadata = slack_payload['view']['private_metadata'] @@ -268,6 +259,8 @@ def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: L is_cost_centers_available = True if cost_centers['count'] > 0 else False + # custom_fields = fyle_expense.get_custom_fields_by_category_id(expense['category_id']) + fields_render_property = { 'project': is_project_available, 'cost_center': is_cost_centers_available diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 1b653344..4dabbbe7 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -52,8 +52,6 @@ def handle_view_submission(self, slack_payload: Dict, user_id: str, team_id: str def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str): - print('SLACK PAYLOAD -> ', slack_payload) - form_values = slack_payload['view']['state']['values'] print('REACHED CREATE EXPENSE -> ', form_values) @@ -96,21 +94,21 @@ def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dic for inner_key, inner_value in value.items(): if inner_value['type'] in ['static_select', 'external_select']: - custom_field_mappings[inner_key] = inner_value['selected_option']['value'] + value = inner_value['selected_option']['value'] if inner_value['type'] == 'multi_static_select': values_list = [] for val in inner_value['selected_options']: values_list.append(val['value']) - custom_field_mappings[inner_key] = values_list + value = values_list elif inner_value['type'] == 'datepicker': if datetime.datetime.strptime(inner_value['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): validation_errors[key] = 'Date selected cannot be in future' - custom_field_mappings[inner_key] = inner_value['selected_date'] + value = inner_value['selected_date'] elif inner_value['type'] == 'plain_text_input': @@ -130,13 +128,14 @@ def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dic except ValueError: validation_errors[key] = 'Only numbers are allowed in this fields' - custom_field_mappings[inner_key] = value - elif inner_value['type'] == 'checkboxes': - custom_field_mappings[inner_key] = False + value = False if len(inner_value['selected_options']) > 0: - custom_field_mappings[inner_key] = True + value = True + + custom_field_mappings['name'] = inner_key + custom_field_mappings['value'] = value custom_fields.append(custom_field_mappings) else: diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 08ad5a16..9db9e9c4 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -5,17 +5,24 @@ from fyle_slack_app.libs import utils -def get_custom_field_value(custom_fields: List, action_id) -> Any: - pass +def get_custom_field_value(custom_fields: List, action_id: str) -> Any: + value = None + for custom_field in custom_fields: + if custom_field['name'] == action_id: + value = custom_field['value'] + break + return value # is_additional_field is for fields which are not custom fields but are part of a specific categories -def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> Dict: +def generate_field_ui(field_details: Dict, is_additional_field: bool = False, expense: Dict = None) -> Dict: block_id = '{}_block'.format(field_details['column_name']) action_id = field_details['column_name'] custom_field = None + custom_field_value = None + # We need to define addtional fields as custom fields so that we can clear them out in form when category is changed if field_details['is_custom'] is True or is_additional_field is True: block_id = '{}_additional_field_{}_block'.format(field_details['type'], field_details['column_name']) @@ -23,6 +30,13 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> block_id = '{}_custom_field_{}_block'.format(field_details['type'], field_details['column_name']) action_id = '{}'.format(field_details['field_name']) + if expense is not None: + if is_additional_field is True: + custom_field_value = expense[action_id] + + elif field_details['is_custom'] is True and len(expense['custom_fields']) > 0: + custom_field_value = get_custom_field_value(expense['custom_fields'], field_details['field_name']) + if field_details['type'] in ['NUMBER', 'TEXT']: custom_field = { 'type': 'input', @@ -37,10 +51,13 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> 'placeholder': { 'type': 'plain_text', 'text': '{}'.format(field_details['placeholder']), - }, - }, + } + } } + if custom_field_value is not None: + custom_field['element']['initial_value'] = custom_field_value + elif field_details['type'] in ['SELECT', 'MULTI_SELECT']: if field_details['type'] == 'SELECT': @@ -64,7 +81,7 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> 'emoji': True, }, 'action_id': action_id, - }, + } } custom_field['element']['options'] = [] @@ -81,31 +98,59 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> } ) + if custom_field_value is not None: + if field_details['type'] == 'SELECT': + custom_field['element']['initial_option'] = { + 'text': { + 'type': 'plain_text', + 'text': custom_field_value, + 'emoji': True, + }, + 'value': custom_field_value, + } + elif field_details['type'] == 'MULTI_SELECT': + initial_options = [] + for value in custom_field_value: + initial_options.append( + { + 'text': { + 'type': 'plain_text', + 'text': value, + 'emoji': True, + }, + 'value': value, + } + ) + + custom_field['element']['initial_options'] = initial_options + elif field_details['type'] == 'BOOLEAN': + checkbox_option = { + 'text': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + 'emoji': True, + } + } custom_field = { 'type': 'input', 'block_id': block_id, 'optional': True, 'element': { 'type': 'checkboxes', - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': '{}'.format(field_details['field_name']), - 'emoji': True, - } - } - ], + 'options': [checkbox_option], 'action_id': action_id, }, 'label': { 'type': 'plain_text', 'text': '{}'.format(field_details['field_name']), 'emoji': True, - }, + } } + if custom_field_value is not None: + custom_field['element']['initial_option'] = checkbox_option + elif field_details['type'] == 'DATE': custom_field = { 'type': 'input', @@ -123,9 +168,12 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False) -> 'type': 'plain_text', 'text': '{}'.format(field_details['field_name']), 'emoji': True - }, + } } + if custom_field_value is not None: + custom_field['element']['initial_date'] = utils.get_formatted_datetime(custom_field_value, '%B %d, %Y') + return custom_field @@ -492,7 +540,7 @@ def expense_form_loading_modal(title: str, loading_message: str) -> Dict: return loading_modal -def get_add_to_report_blocks(add_to_report: str) -> Dict: +def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: blocks = [] add_to_existing_report_option = { 'text': { @@ -514,11 +562,12 @@ def get_add_to_report_blocks(add_to_report: str) -> Dict: add_to_report_block = { 'type': 'input', 'block_id': 'add_to_report_block', + 'optional': True, 'dispatch_action': True, 'element': { 'type': 'radio_buttons', 'options': [add_to_existing_report_option, add_to_new_report_option], - 'action_id': 'add_to_report' + 'action_id': action_id }, 'label': { 'type': 'plain_text', @@ -631,7 +680,7 @@ def expense_dialog_form( if field['is_custom'] is False: is_additional_field = True - custom_field = generate_field_ui(field, is_additional_field=is_additional_field) + custom_field = generate_field_ui(field, is_additional_field=is_additional_field, expense=expense) if custom_field is not None: view['blocks'].append(custom_field) @@ -648,7 +697,7 @@ def expense_dialog_form( }) if add_to_report is not None: - add_to_report_blocks = get_add_to_report_blocks(add_to_report) + add_to_report_blocks = get_add_to_report_blocks(add_to_report, action_id='add_to_report') view['blocks'].extend(add_to_report_blocks) @@ -661,13 +710,45 @@ def view_expense_message(expense: Dict) -> Dict: spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') - receipt_message = ':x: Not Attached' - if len(expense['file_ids']) > 0: - receipt_message = ':white_check_mark: Attached' - report_message = ':x: Not Added' if expense['report_id'] is not None: report_message = ':white_check_mark: Added' + primary_cta = { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Submit Report', + 'emoji': True, + }, + 'value': expense['report_id'], + 'action_id': 'submit_report', + } + else: + primary_cta = { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Add to Report', + 'emoji': True, + }, + 'action_id': 'add_expense_to_report', + 'value': expense['id'] + } + + receipt_message = ':x: Not Attached' + if len(expense['file_ids']) > 0: + receipt_message = ':white_check_mark: Attached' + else: + primary_cta = { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Attach Receipt', + 'emoji': True, + }, + 'value': expense['id'], + 'action_id': 'attach_receipt', + } view_expense_blocks = [ { @@ -706,6 +787,7 @@ def view_expense_message(expense: Dict) -> Dict: { 'type': 'actions', 'elements': [ + primary_cta, { 'type': 'button', 'text': { @@ -732,3 +814,77 @@ def view_expense_message(expense: Dict) -> Dict: ] return view_expense_blocks + + +def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) -> Dict: + add_to_report_dialog = { + 'title': { + 'type': 'plain_text', + 'text': ':mailbox: Add to Report', + 'emoji': True + }, + 'submit': { + 'type': 'plain_text', + 'text': 'Add', + 'emoji': True + }, + 'type': 'modal', + 'close': { + 'type': 'plain_text', + 'text': 'Cancel', + 'emoji': True + }, + 'blocks': [ + { + 'type': 'divider' + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Amount* \n USD 123.45' + }, + { + 'type': 'mrkdwn', + 'text': '*Date of Spend* \nJuly 02, 2021' + } + ] + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Report* \n :x: Not Added' + }, + { + 'type': 'mrkdwn', + 'text': '*Receipt* \n :white_check_mark: Attached' + } + ] + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Category* \n Food' + }, + { + 'type': 'mrkdwn', + 'text': '*Project* \n Expensive Project' + } + ] + }, + { + 'type': 'divider' + }, + ] + } + + add_to_report_blocks = get_add_to_report_blocks(add_to_report=add_to_report, action_id='add_expense_to_report_selection') + + add_to_report_dialog['blocks'].extend(add_to_report_blocks) + + return add_to_report_dialog From d8a1f6d1c19f0c5efa05ff343578e11fc8d0c7d2 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 4 Oct 2021 17:00:16 +0530 Subject: [PATCH 35/85] Added to report dialog final --- fyle_slack_app/slack/commands/tasks.py | 7 ++- .../interactives/block_action_handlers.py | 5 +- fyle_slack_app/slack/ui/expenses/messages.py | 55 +++++++++---------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 8c73ea2c..14b5384f 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -144,14 +144,17 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: 'home_currency': home_currency } + add_to_report = 'existing_report' + private_metadata = { 'fields_render_property': fields_render_property, 'home_currency': home_currency, - 'additional_currency_details': additional_currency_details + 'additional_currency_details': additional_currency_details, + 'add_to_report': add_to_report } encoded_metadata = utils.encode_state(private_metadata) - expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details) + expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details, add_to_report=add_to_report) slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 6e3c7280..6fae2ecd 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -327,6 +327,7 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'], add_to_report=add_to_report) slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) + return JsonResponse({}) @@ -335,7 +336,7 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i expense_id = slack_payload['actions'][0]['value'] - view_id = slack_payload['container']['view_id'] + trigger_id = slack_payload['trigger_id'] slack_client = get_slack_client(team_id) @@ -343,7 +344,7 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i add_expense_to_report_dialog['private_metadata'] = expense_id - slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) + slack_client.views_open(trigger_id=trigger_id, user=user_id, view=add_expense_to_report_dialog,) return JsonResponse({}) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 9db9e9c4..7b9e9518 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -710,45 +710,40 @@ def view_expense_message(expense: Dict) -> Dict: spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') + primary_cta_value = None + report_message = ':x: Not Added' if expense['report_id'] is not None: report_message = ':white_check_mark: Added' - primary_cta = { - 'type': 'button', - 'text': { - 'type': 'plain_text', - 'text': 'Submit Report', - 'emoji': True, - }, - 'value': expense['report_id'], - 'action_id': 'submit_report', - } + + primary_cta_text = 'Submit Report' + primary_cta_action_id = 'submit_report' + else: - primary_cta = { - 'type': 'button', - 'text': { - 'type': 'plain_text', - 'text': 'Add to Report', - 'emoji': True, - }, - 'action_id': 'add_expense_to_report', - 'value': expense['id'] - } + primary_cta_text = 'Add to Report' + primary_cta_action_id = 'add_expense_to_report' + primary_cta_value = expense['id'] + receipt_message = ':x: Not Attached' if len(expense['file_ids']) > 0: receipt_message = ':white_check_mark: Attached' else: - primary_cta = { - 'type': 'button', - 'text': { - 'type': 'plain_text', - 'text': 'Attach Receipt', - 'emoji': True, - }, - 'value': expense['id'], - 'action_id': 'attach_receipt', - } + primary_cta_text = 'Attach Receipt' + primary_cta_action_id = 'attach_receipt' + primary_cta_value = expense['id'] + + primary_cta = { + 'type': 'button', + 'style': 'primary', + 'text': { + 'type': 'plain_text', + 'text': primary_cta_text, + 'emoji': True, + }, + 'value': primary_cta_value, + 'action_id': primary_cta_action_id + } view_expense_blocks = [ { From e2242c52e57abea903ebd1d125c335a45ce9430a Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 5 Oct 2021 18:30:58 +0530 Subject: [PATCH 36/85] Submit report dialog and update expense changes --- .../interactives/block_action_handlers.py | 24 +- fyle_slack_app/slack/interactives/tasks.py | 8 +- .../interactives/view_submission_handlers.py | 26 +- fyle_slack_app/slack/ui/expenses/messages.py | 250 +++++++++++++++--- 4 files changed, 259 insertions(+), 49 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 6fae2ecd..72c3a125 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -46,7 +46,8 @@ def _initialize_block_action_handlers(self): 'amount': self.handle_amount_entered, 'add_to_report': self.handle_add_to_report, 'add_expense_to_report': self.handle_add_expense_to_report, - 'add_expense_to_report_selection': self.handle_add_expense_to_report_selection + 'add_expense_to_report_selection': self.handle_add_expense_to_report_selection, + 'open_submit_report_dialog': self.handle_submit_report_dialog } @@ -344,7 +345,7 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i add_expense_to_report_dialog['private_metadata'] = expense_id - slack_client.views_open(trigger_id=trigger_id, user=user_id, view=add_expense_to_report_dialog,) + slack_client.views_open(trigger_id=trigger_id, user=user_id, view=add_expense_to_report_dialog) return JsonResponse({}) @@ -370,6 +371,25 @@ def handle_edit_expense(self, slack_payload: Dict, user_id: str, team_id: str) - return JsonResponse({}) + def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + + from . import report, list_expenses + + report_id = slack_payload['actions'][0]['value'] + + trigger_id = slack_payload['trigger_id'] + + slack_client = get_slack_client(team_id) + + add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(report=report['data'][0], expenses=list_expenses['data']) + + add_expense_to_report_dialog['private_metadata'] = report_id + + slack_client.views_open(trigger_id=trigger_id, user=user_id, view=add_expense_to_report_dialog) + + return JsonResponse({}) + + def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: user = utils.get_or_none(User, slack_user_id=user_id) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index ea5313a5..f6392e75 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -270,14 +270,18 @@ def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: L 'home_currency': home_currency } + add_to_report = 'existing_report' + private_metadata = { 'fields_render_property': fields_render_property, 'additional_currency_details': additional_currency_details, - 'expense_id': expense_id + 'expense_id': expense_id, + 'add_to_report': add_to_report, + 'message_ts': slack_payload['container']['message_ts'] } encoded_metadata = encode_state(private_metadata) - expense_form = expense_dialog_form(expense=expense['data'], fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details) + expense_form = expense_dialog_form(expense=expense['data'], fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details, add_to_report=add_to_report) slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 4dabbbe7..cb513dcc 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -4,6 +4,7 @@ from django.http.response import JsonResponse +from fyle_slack_app.models import User from fyle_slack_app.slack import utils as slack_utils from fyle_slack_app.libs import utils from fyle_slack_app.slack.ui.expenses import messages as expense_messages @@ -16,7 +17,8 @@ class ViewSubmissionHandler: # Maps action_id with it's respective function def _initialize_view_submission_handlers(self): self._view_submission_handlers = { - 'upsert_expense': self.handle_upsert_expense + 'upsert_expense': self.handle_upsert_expense, + 'submit_report': self.handle_submit_report } @@ -50,7 +52,9 @@ def handle_view_submission(self, slack_payload: Dict, user_id: str, team_id: str return handler(slack_payload, user_id, team_id) - def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str): + def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + + user = utils.get_or_none(User, slack_user_id=user_id) form_values = slack_payload['view']['state']['values'] print('REACHED CREATE EXPENSE -> ', form_values) @@ -77,12 +81,20 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) slack_client = slack_utils.get_slack_client(team_id) - view_expense_message = expense_messages.view_expense_message(expense_details) + view_expense_message = expense_messages.view_expense_message(expense_details, user) + + if expense_id is None: + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=view_expense_message) + else: + slack_client.chat_update(channel=user.slack_dm_channel_id, blocks=view_expense_message, ts=private_metadata['message_ts']) - slack_client.chat_postMessage(channel='D01K1L9UHBP', blocks=view_expense_message) return JsonResponse({}) + def handle_submit_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + + return JsonResponse({}) + def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dict]: expense_details = {} validation_errors = {} @@ -94,7 +106,8 @@ def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dic for inner_key, inner_value in value.items(): if inner_value['type'] in ['static_select', 'external_select']: - value = inner_value['selected_option']['value'] + if inner_value['selected_option'] is not None: + value = inner_value['selected_option']['value'] if inner_value['type'] == 'multi_static_select': @@ -142,7 +155,8 @@ def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dic for inner_key, inner_value in value.items(): if inner_value['type'] in ['static_select', 'external_select']: - expense_details[inner_key] = inner_value['selected_option']['value'] + if inner_value['selected_option'] is not None: + expense_details[inner_key] = inner_value['selected_option']['value'] if inner_value['type'] == 'multi_static_select': diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 7b9e9518..a56fb609 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -2,7 +2,9 @@ import datetime +from fyle_slack_app.models import User from fyle_slack_app.libs import utils +from fyle_slack_app.fyle import utils as fyle_utils def get_custom_field_value(custom_fields: List, action_id: str) -> Any: @@ -541,6 +543,11 @@ def expense_form_loading_modal(title: str, loading_message: str) -> Dict: def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: + + is_report_block_optional = False + if action_id == 'add_to_report': + is_report_block_optional = True + blocks = [] add_to_existing_report_option = { 'text': { @@ -562,8 +569,8 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: add_to_report_block = { 'type': 'input', 'block_id': 'add_to_report_block', - 'optional': True, 'dispatch_action': True, + 'optional': is_report_block_optional, 'element': { 'type': 'radio_buttons', 'options': [add_to_existing_report_option, add_to_new_report_option], @@ -582,6 +589,7 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'ui': { 'type': 'input', 'block_id': 'add_to_new_report_block', + 'optional': is_report_block_optional, 'element': { 'type': 'plain_text_input', 'placeholder': { @@ -602,6 +610,7 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'existing_report': { 'ui': { 'type': 'input', + 'optional': is_report_block_optional, 'block_id': 'add_to_existing_report_block', 'element': { 'type': 'external_select', @@ -704,20 +713,25 @@ def expense_dialog_form( return view -def view_expense_message(expense: Dict) -> Dict: +def view_expense_message(expense: Dict, user: User) -> Dict: from fyle_slack_app.slack.interactives import expense expense = expense['data'] spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') + primary_cta = None primary_cta_value = None + primary_cta_text = None + primary_cta_action_id = None report_message = ':x: Not Added' if expense['report_id'] is not None: report_message = ':white_check_mark: Added' - primary_cta_text = 'Submit Report' - primary_cta_action_id = 'submit_report' + if expense['report']['state'] in ['DRAFT', 'APPROVER_INQUIRY']: + primary_cta_text = 'Submit Report' + primary_cta_action_id = 'open_submit_report_dialog' + primary_cta_value = expense['report_id'] else: primary_cta_text = 'Add to Report' @@ -733,21 +747,53 @@ def view_expense_message(expense: Dict) -> Dict: primary_cta_action_id = 'attach_receipt' primary_cta_value = expense['id'] - primary_cta = { + if primary_cta_text is not None and primary_cta_action_id is not None: + primary_cta = { + 'type': 'button', + 'style': 'primary', + 'text': { + 'type': 'plain_text', + 'text': primary_cta_text, + 'emoji': True, + }, + 'value': primary_cta_value, + 'action_id': primary_cta_action_id + } + + edit_expense_cta = { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'Edit Expense', + 'emoji': True, + }, + 'value': expense['id'], + 'action_id': 'edit_expense', + } + + view_in_fyle_cta = { 'type': 'button', - 'style': 'primary', 'text': { 'type': 'plain_text', - 'text': primary_cta_text, + 'text': 'View in Fyle', 'emoji': True, }, - 'value': primary_cta_value, - 'action_id': primary_cta_action_id + 'url': fyle_utils.get_fyle_resource_url(user.fyle_refresh_token, expense, 'EXPENSE'), + 'action_id': 'expense_view_in_fyle', } + actions = [ + edit_expense_cta, + view_in_fyle_cta + ] + + if primary_cta is not None: + actions.insert(0, primary_cta) + view_expense_blocks = [ { 'type': 'section', + 'block_id': expense['id'], 'text': { 'type': 'mrkdwn', 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) @@ -781,30 +827,7 @@ def view_expense_message(expense: Dict) -> Dict: }, { 'type': 'actions', - 'elements': [ - primary_cta, - { - 'type': 'button', - 'text': { - 'type': 'plain_text', - 'text': 'Edit Expense', - 'emoji': True, - }, - 'value': expense['id'], - 'action_id': 'edit_expense', - } - ], - }, - { - 'type': 'context', - 'block_id': expense['id'], - 'elements': [ - { - 'type': 'plain_text', - 'text': 'Powered by Fyle', - 'emoji': True, - } - ], + 'elements': actions } ] @@ -812,6 +835,16 @@ def view_expense_message(expense: Dict) -> Dict: def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) -> Dict: + + report_message = ':x: Not Added' + if expense['report_id'] is not None: + report_message = ':white_check_mark: Added' + + + receipt_message = ':x: Not Attached' + if len(expense['file_ids']) > 0: + receipt_message = ':white_check_mark: Attached' + add_to_report_dialog = { 'title': { 'type': 'plain_text', @@ -824,6 +857,7 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - 'emoji': True }, 'type': 'modal', + 'callback_id': 'add_expense_to_report', 'close': { 'type': 'plain_text', 'text': 'Cancel', @@ -838,11 +872,11 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - 'fields': [ { 'type': 'mrkdwn', - 'text': '*Amount* \n USD 123.45' + 'text': '*Amount* \n {} {}'.format(expense['currency'], expense['amount']) }, { 'type': 'mrkdwn', - 'text': '*Date of Spend* \nJuly 02, 2021' + 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) } ] }, @@ -851,11 +885,11 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - 'fields': [ { 'type': 'mrkdwn', - 'text': '*Report* \n :x: Not Added' + 'text': '*Report* \n {}'.format(report_message) }, { 'type': 'mrkdwn', - 'text': '*Receipt* \n :white_check_mark: Attached' + 'text': '*Receipt* \n {}'.format(receipt_message) } ] }, @@ -864,11 +898,11 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - 'fields': [ { 'type': 'mrkdwn', - 'text': '*Category* \n Food' + 'text': '*Category* \n {}'.format(expense['category']['name']) }, { 'type': 'mrkdwn', - 'text': '*Project* \n Expensive Project' + 'text': '*Project* \n {}'.format(expense['project']['name']) } ] }, @@ -883,3 +917,141 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - add_to_report_dialog['blocks'].extend(add_to_report_blocks) return add_to_report_dialog + + + +def get_view_report_details_dialog(report: Dict, expenses: List[Dict]) -> Dict: + view_report_dialog = { + 'type': 'modal', + 'callback_id': 'submit_report', + 'title': { + 'type': 'plain_text', + 'text': 'Report Details', + 'emoji': True + }, + 'submit': { + 'type': 'plain_text', + 'text': 'Submit Report', + 'emoji': True + }, + 'close': { + 'type': 'plain_text', + 'text': 'Cancel', + 'emoji': True + }, + 'blocks': [ + { + 'type': 'divider' + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Report Name* \n {}'.format(report['purpose']) + }, + { + 'type': 'mrkdwn', + 'text': '*Amount* \n {} {}'.format(report['currency'], str(report['amount'])) + } + ] + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Expenses* \n {}'.format(str(report['num_expenses'])) + }, + { + 'type': 'mrkdwn', + 'text': '*Created On* \n {}'.format(utils.get_formatted_datetime(report['created_at'], '%B %d, %Y')) + } + ] + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': ':page_facing_up: *Expenses*' + } + }, + { + 'type': 'divider' + }, + ] + } + + expenses_list = [] + for expense in expenses: + receipt_message = ':x: Not Attached' + if len(expense['file_ids']) > 0: + receipt_message = ':white_check_mark: Attached' + minimal_expense_detail = [ + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': '*{} ({})* expense of :money_with_wings: *{} {}*'.format(expense['purpose'], expense['merchant'], expense['currency'], str(expense['amount'])) + }, + 'accessory': { + 'type': 'overflow', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': ':pencil: Edit', + 'emoji': True + }, + 'value': 'edit_expense_accessory' + }, + { + 'text': { + 'type': 'plain_text', + 'text': ':page_facing_up: View Details', + 'emoji': True + }, + 'value': 'view_expense_accessory' + }, + { + 'text': { + 'type': 'plain_text', + 'text': ':broom: Remove from Report', + 'emoji': True + }, + 'value': 'remove_expense_from_report_accessory' + }, + { + 'text': { + 'type': 'plain_text', + 'text': ':arrow_upper_right: Open in Fyle', + 'emoji': True + }, + 'value': 'open_in_fyle_accessory' + } + ], + 'action_id': 'expense_accessory' + } + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) + }, + { + 'type': 'mrkdwn', + 'text': '*Receipt* \n {}'.format(receipt_message) + } + ] + }, + { + 'type': 'divider' + } + ] + expenses_list.extend(minimal_expense_detail) + + view_report_dialog['blocks'].extend(expenses_list) + + return view_report_dialog From 4b73f8294ecb87401a6352000f6f3c4765844559 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 5 Oct 2021 18:57:58 +0530 Subject: [PATCH 37/85] Minor fix for currency select --- .../interactives/block_suggestion_handlers.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 14 ++++++++++++-- fyle_slack_app/slack/ui/expenses/messages.py | 1 - 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index fe17a30e..2c5f82a0 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -83,7 +83,7 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) if is_project_available is True and project is not None: - category_query_params['id'] = 'in.{}'.format(tuple(project['data'][0]['category_ids'])) + category_query_params['id'] = 'in.{}'.format(tuple(project['category_ids'])) suggested_categories = fyle_expense.get_categories(category_query_params) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index f6392e75..3b1839bc 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -76,6 +76,16 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st decoded_private_metadata = decode_state(private_metadata) + project = project['data'][0] + + project = { + 'id': project['id'], + 'name': project['name'], + 'display_name': project['display_name'], + 'sub_project': project['sub_project'], + 'category_ids': project['category_ids'] + } + decoded_private_metadata['project'] = project fields_render_property = decoded_private_metadata['fields_render_property'] @@ -166,7 +176,7 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None decoded_private_metadata['additional_currency_details'] = additional_currency_details - is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) + is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) custom_fields = get_custom_field_blocks(current_ui_blocks) @@ -211,7 +221,7 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: custom_fields = get_custom_field_blocks(current_ui_blocks) - is_project_available, project = check_project_in_form(form_current_state, decoded_private_metadata) + is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) fields_render_property['project'] = is_project_available diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index a56fb609..66fec04a 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -426,7 +426,6 @@ def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict 'value': str(expense['project']['id']), } elif selected_project is not None: - selected_project = selected_project['data'][0] project_display_name = selected_project['display_name'] if selected_project['name'] == selected_project['sub_project']: From b3cdf8349b04a311657e6c050adcf041c4911b69 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 6 Oct 2021 13:06:42 +0530 Subject: [PATCH 38/85] Added report submitted message --- .../interactives/block_action_handlers.py | 17 +++- .../interactives/view_submission_handlers.py | 12 +++ .../slack/ui/authorization/messages.py | 2 - fyle_slack_app/slack/ui/expenses/messages.py | 83 ++++++++++++++++--- 4 files changed, 97 insertions(+), 17 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 72c3a125..97590ba7 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -41,13 +41,13 @@ def _initialize_block_action_handlers(self): # Dynamic options 'category_id': self.handle_category_select, 'project_id': self.handle_project_select, - 'is_billable': self.handle_billable, 'currency': self.handle_currency_select, 'amount': self.handle_amount_entered, 'add_to_report': self.handle_add_to_report, 'add_expense_to_report': self.handle_add_expense_to_report, 'add_expense_to_report_selection': self.handle_add_expense_to_report_selection, - 'open_submit_report_dialog': self.handle_submit_report_dialog + 'open_submit_report_dialog': self.handle_submit_report_dialog, + 'expense_accessory': self.handle_expense_accessory } @@ -155,7 +155,13 @@ def handle_notification_preference_selection(self, slack_payload: Dict, user_id: return JsonResponse({}, status=200) - def handle_billable(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + def handle_expense_accessory(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + expense_accessory_value = slack_payload['actions'][0]['value'] + accessory_type, expense_id = expense_accessory_value.split('.') + + if accessory_type == 'open_in_fyle_accessory': + self.track_view_in_fyle_action(user_id, 'Expense Viewed in Fyle', {'expense_id': expense_id}) + return JsonResponse({}) @@ -281,6 +287,7 @@ def handle_amount_entered(self, slack_payload: Dict, user_id: str, team_id: str) def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + add_to_report = slack_payload['actions'][0]['selected_option']['value'] view_id = slack_payload['container']['view_id'] @@ -375,13 +382,15 @@ def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id from . import report, list_expenses + user = utils.get_or_none(User, slack_user_id=user_id) + report_id = slack_payload['actions'][0]['value'] trigger_id = slack_payload['trigger_id'] slack_client = get_slack_client(team_id) - add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(report=report['data'][0], expenses=list_expenses['data']) + add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(user, report=report['data'][0], expenses=list_expenses['data']) add_expense_to_report_dialog['private_metadata'] = report_id diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index cb513dcc..d91b7bb0 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -93,6 +93,18 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) def handle_submit_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + user = utils.get_or_none(User, slack_user_id=user_id) + + report_id = slack_payload['view']['private_metadata'] + + slack_client = slack_utils.get_slack_client(team_id) + + from . import report + + report_submitted_message = expense_messages.report_submitted_message(user, report['data'][0]) + + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=report_submitted_message) + return JsonResponse({}) def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dict]: diff --git a/fyle_slack_app/slack/ui/authorization/messages.py b/fyle_slack_app/slack/ui/authorization/messages.py index 41660029..9f764255 100644 --- a/fyle_slack_app/slack/ui/authorization/messages.py +++ b/fyle_slack_app/slack/ui/authorization/messages.py @@ -111,7 +111,6 @@ def get_post_authorization_message() -> List[Dict]: { "type": "button", "style": "primary", - "url": "slack://app?id=ARHATN286&team=TAQR2JNTF&tab=messages", "text": { "type": "plain_text", "text": "Approve", @@ -121,7 +120,6 @@ def get_post_authorization_message() -> List[Dict]: }, { "type": "button", - "url": "https://demo-bot-fyle.slack.com/archives/D01K1L9UHBP/p1630583475001000?thread_ts=1630583475.001000&cid=D01K1L9UHBP", "text": { "type": "plain_text", "text": "Review in Fyle", diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 66fec04a..46adb8ba 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -253,15 +253,15 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None, expe amount_block['element']['placeholder']['text'] = 'Enter Amount {}'.format(additional_currency_details['foreign_currency']) currency_context_block = { - 'type': 'context', + 'type': 'context', 'block_id': 'TEXT_default_field_currency_context_block', - 'elements': [ - { - 'type': 'mrkdwn', - 'text': ':information_source: Amount ({}) x Exchange Rate = Total ({})'.format(additional_currency_details['foreign_currency'], additional_currency_details['home_currency']) - } - ] - } + 'elements': [ + { + 'type': 'mrkdwn', + 'text': ':information_source: Amount ({}) x Exchange Rate = Total ({})'.format(additional_currency_details['foreign_currency'], additional_currency_details['home_currency']) + } + ] + } blocks.insert(1, currency_context_block) @@ -778,14 +778,17 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'emoji': True, }, 'url': fyle_utils.get_fyle_resource_url(user.fyle_refresh_token, expense, 'EXPENSE'), + 'value': expense['id'], 'action_id': 'expense_view_in_fyle', } actions = [ - edit_expense_cta, view_in_fyle_cta ] + if expense['state'] in ['DRAFT', 'COMPLETE']: + actions.insert(0, edit_expense_cta) + if primary_cta is not None: actions.insert(0, primary_cta) @@ -919,10 +922,11 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - -def get_view_report_details_dialog(report: Dict, expenses: List[Dict]) -> Dict: +def get_view_report_details_dialog(user: User, report: Dict, expenses: List[Dict]) -> Dict: view_report_dialog = { 'type': 'modal', 'callback_id': 'submit_report', + 'private_metadata': report['id'], 'title': { 'type': 'plain_text', 'text': 'Report Details', @@ -983,9 +987,13 @@ def get_view_report_details_dialog(report: Dict, expenses: List[Dict]) -> Dict: expenses_list = [] for expense in expenses: + + expense_url = fyle_utils.get_fyle_resource_url(user.fyle_refresh_token, expense, 'EXPENSE') + receipt_message = ':x: Not Attached' if len(expense['file_ids']) > 0: receipt_message = ':white_check_mark: Attached' + minimal_expense_detail = [ { 'type': 'section', @@ -1026,7 +1034,8 @@ def get_view_report_details_dialog(report: Dict, expenses: List[Dict]) -> Dict: 'text': ':arrow_upper_right: Open in Fyle', 'emoji': True }, - 'value': 'open_in_fyle_accessory' + 'url': expense_url, + 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) } ], 'action_id': 'expense_accessory' @@ -1054,3 +1063,55 @@ def get_view_report_details_dialog(report: Dict, expenses: List[Dict]) -> Dict: view_report_dialog['blocks'].extend(expenses_list) return view_report_dialog + + +def report_submitted_message(user: User, report: Dict) -> List[Dict]: + report_url = fyle_utils.get_fyle_resource_url(user.fyle_refresh_token, report, 'REPORT') + report_message_blocks = [ + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': ':open_file_folder: An expense report *{}* has been submitted'.format(report['purpose']) + } + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Amount* \n {} {}'.format(report['currency'], report['amount']) + }, + { + 'type': 'mrkdwn', + 'text': '*Expenses* \n {}'.format(report['num_expenses']) + } + ] + }, + { + "type": "context", + "elements": [ + { + "type": "mrkdwn", + "text": ":bell: You will be notified when any action is taken by your approver" + } + ] + }, + { + 'type': 'actions', + 'elements': [ + { + 'type': 'button', + 'text': { + 'type': 'plain_text', + 'text': 'View in Fyle', + }, + 'url': report_url, + 'value': report['id'], + 'action_id': 'review_report_in_fyle' + } + ] + } + ] + + return report_message_blocks From 49c146ea497f0f8e13571f40b13f7ee17cef03ca Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 6 Oct 2021 16:58:04 +0530 Subject: [PATCH 39/85] Slack interactives inheritance fix and add to report submission --- fyle_slack_app/fyle/expenses/views.py | 10 ++++ .../interactives/block_action_handlers.py | 51 ++++++++++++++++++- .../interactives/view_submission_handlers.py | 9 +++- fyle_slack_app/slack/interactives/views.py | 10 ++-- fyle_slack_app/slack/ui/expenses/messages.py | 4 +- 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 5c7ff65b..ab098c16 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -65,6 +65,16 @@ def get_cost_centers(self, query_params: Dict) -> Dict: return cost_centers + # def get_expenses(self, query_params: Dict) -> Dict: + # expenses = self.connection.v1.fyler.expenses.list(query_params=query_params) + # return expenses + + + # def get_reports(self, query_params: Dict) -> Dict: + # reports = self.connection.v1.fyler.reports.list(query_params=query_params) + # return reports + + @staticmethod def get_currencies(): return ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 97590ba7..dfe261c6 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -1,3 +1,4 @@ +from fyle_slack_app.fyle.expenses.views import FyleExpense from typing import Callable, Dict from django.http import JsonResponse @@ -324,6 +325,8 @@ def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: from . import expense + user = utils.get_or_none(User, slack_user_id=user_id) + add_to_report = slack_payload['actions'][0]['selected_option']['value'] view_id = slack_payload['container']['view_id'] @@ -332,6 +335,17 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s expense_id = slack_payload['view']['private_metadata'] + # fyle_expense = FyleExpense(user) + + # expense_query_params = { + # 'offset': 0, + # 'limit': '1', + # 'order': 'created_at.desc', + # 'id': 'eq.{}'.format(expense_id) + # } + + # expense = fyle_expense.get_expenses(query_params=expense_query_params) + add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'], add_to_report=add_to_report) slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) @@ -342,12 +356,25 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: from . import expense + user = utils.get_or_none(User, slack_user_id=user_id) + expense_id = slack_payload['actions'][0]['value'] trigger_id = slack_payload['trigger_id'] slack_client = get_slack_client(team_id) + # fyle_expense = FyleExpense(user) + + # expense_query_params = { + # 'offset': 0, + # 'limit': '1', + # 'order': 'created_at.desc', + # 'id': 'eq.{}'.format(expense_id) + # } + + # expense = fyle_expense.get_expenses(query_params=expense_query_params) + add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'], add_to_report='existing_report') add_expense_to_report_dialog['private_metadata'] = expense_id @@ -380,7 +407,7 @@ def handle_edit_expense(self, slack_payload: Dict, user_id: str, team_id: str) - def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - from . import report, list_expenses + from . import report, list_expenses as expenses user = utils.get_or_none(User, slack_user_id=user_id) @@ -390,7 +417,27 @@ def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id slack_client = get_slack_client(team_id) - add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(user, report=report['data'][0], expenses=list_expenses['data']) + # fyle_expense = FyleExpense(user) + + # expense_query_params = { + # 'offset': 0, + # 'limit': '30', + # 'order': 'created_at.desc', + # 'report_id': 'eq.{}'.format(report_id) + # } + + # expenses = fyle_expense.get_expenses(query_params=expense_query_params) + + # report_query_params = { + # 'offset': 0, + # 'limit': '1', + # 'order': 'created_at.desc', + # 'id': 'eq.{}'.format(report_id) + # } + + # report = fyle_expense.get_reports(query_params=report_query_params) + + add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(user, report=report['data'][0], expenses=expenses['data']) add_expense_to_report_dialog['private_metadata'] = report_id diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index d91b7bb0..2d2d4e70 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -18,7 +18,8 @@ class ViewSubmissionHandler: def _initialize_view_submission_handlers(self): self._view_submission_handlers = { 'upsert_expense': self.handle_upsert_expense, - 'submit_report': self.handle_submit_report + 'submit_report': self.handle_submit_report, + 'add_expense_to_report': self.handle_add_expense_to_report } @@ -107,6 +108,12 @@ def handle_submit_report(self, slack_payload: Dict, user_id: str, team_id: str) return JsonResponse({}) + + def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + print('SLACK PAYLOAD VALUES -> ', slack_payload['view']['state']['values']) + return JsonResponse({}) + + def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dict]: expense_details = {} validation_errors = {} diff --git a/fyle_slack_app/slack/interactives/views.py b/fyle_slack_app/slack/interactives/views.py index 11160cd8..f275e9af 100644 --- a/fyle_slack_app/slack/interactives/views.py +++ b/fyle_slack_app/slack/interactives/views.py @@ -9,7 +9,7 @@ from fyle_slack_app.slack.interactives.block_suggestion_handlers import BlockSuggestionHandler -class SlackInteractiveView(SlackView, BlockActionHandler, ShortcutHandler, ViewSubmissionHandler, BlockSuggestionHandler): +class SlackInteractiveView(SlackView): def post(self, request: HttpRequest) -> JsonResponse: payload = request.POST.get('payload') @@ -24,18 +24,18 @@ def post(self, request: HttpRequest) -> JsonResponse: # Check interactive event type and call it's respective handler if event_type == 'block_actions': # Call handler function from BlockActionHandler - return self.handle_block_actions(slack_payload, user_id, team_id) + return BlockActionHandler().handle_block_actions(slack_payload, user_id, team_id) elif event_type == 'shortcut': # Call handler function from ShortcutHandler - return self.handle_shortcuts(slack_payload, user_id, team_id) + return ShortcutHandler().handle_shortcuts(slack_payload, user_id, team_id) elif event_type == 'view_submission': # Call handler function from ViewSubmissionHandler - return self.handle_view_submission(slack_payload, user_id, team_id) + return ViewSubmissionHandler().handle_view_submission(slack_payload, user_id, team_id) elif event_type == 'block_suggestion': # Call handler function from BlockActionHandler - return self.handle_block_suggestions(slack_payload, user_id, team_id) + return BlockSuggestionHandler().handle_block_suggestions(slack_payload, user_id, team_id) return JsonResponse({}, status=200) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 46adb8ba..89da1614 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -587,7 +587,7 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'new_report': { 'ui': { 'type': 'input', - 'block_id': 'add_to_new_report_block', + 'block_id': 'TEXT_add_to_new_report_block', 'optional': is_report_block_optional, 'element': { 'type': 'plain_text_input', @@ -610,7 +610,7 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'ui': { 'type': 'input', 'optional': is_report_block_optional, - 'block_id': 'add_to_existing_report_block', + 'block_id': 'SELECT_add_to_existing_report_block', 'element': { 'type': 'external_select', 'placeholder': { From b56ad20672406a72d1f182fa8ce6c784a44a5108 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Thu, 7 Oct 2021 15:50:06 +0530 Subject: [PATCH 40/85] Added proper CTAs and their dialogs and upgraded fyle sdk --- fyle_slack_app/fyle/expenses/views.py | 12 +- .../interactives/block_action_handlers.py | 83 +++++------ .../interactives/block_suggestion_handlers.py | 42 +++++- fyle_slack_app/slack/interactives/tasks.py | 20 ++- .../interactives/view_submission_handlers.py | 35 ++++- fyle_slack_app/slack/ui/expenses/messages.py | 132 ++++++++++-------- requirements.txt | 2 +- 7 files changed, 211 insertions(+), 115 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index ab098c16..f8797b20 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -65,14 +65,14 @@ def get_cost_centers(self, query_params: Dict) -> Dict: return cost_centers - # def get_expenses(self, query_params: Dict) -> Dict: - # expenses = self.connection.v1.fyler.expenses.list(query_params=query_params) - # return expenses + def get_expenses(self, query_params: Dict) -> Dict: + expenses = self.connection.v1.fyler.expenses.list(query_params=query_params) + return expenses - # def get_reports(self, query_params: Dict) -> Dict: - # reports = self.connection.v1.fyler.reports.list(query_params=query_params) - # return reports + def get_reports(self, query_params: Dict) -> Dict: + reports = self.connection.v1.fyler.reports.list(query_params=query_params) + return reports @staticmethod diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index dfe261c6..79f59dd7 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -1,10 +1,10 @@ -from fyle_slack_app.fyle.expenses.views import FyleExpense from typing import Callable, Dict from django.http import JsonResponse from django_q.tasks import async_task +from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.models import User, NotificationPreference from fyle_slack_app.models.notification_preferences import NotificationType from fyle_slack_app.libs import assertions, utils, logger @@ -88,6 +88,7 @@ def handle_pre_auth_mock_button(self, slack_payload: Dict, user_id: str, team_id # Empty function because slack still sends an interactive event on button click and expects a 200 response return JsonResponse({}, status=200) + def link_fyle_account(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: # Empty function because slack still sends an interactive event on button click and expects a 200 response return JsonResponse({}, status=200) @@ -157,12 +158,16 @@ def handle_notification_preference_selection(self, slack_payload: Dict, user_id: def handle_expense_accessory(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - expense_accessory_value = slack_payload['actions'][0]['value'] + expense_accessory_value = slack_payload['actions'][0]['selected_option']['value'] accessory_type, expense_id = expense_accessory_value.split('.') if accessory_type == 'open_in_fyle_accessory': self.track_view_in_fyle_action(user_id, 'Expense Viewed in Fyle', {'expense_id': expense_id}) + elif accessory_type == 'edit_expense_accessory': + slack_payload['actions'][0]['value'] = expense_id + self.handle_edit_expense(slack_payload, user_id, team_id) + return JsonResponse({}) @@ -323,7 +328,6 @@ def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - from . import expense user = utils.get_or_none(User, slack_user_id=user_id) @@ -335,18 +339,18 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s expense_id = slack_payload['view']['private_metadata'] - # fyle_expense = FyleExpense(user) + fyle_expense = FyleExpense(user) - # expense_query_params = { - # 'offset': 0, - # 'limit': '1', - # 'order': 'created_at.desc', - # 'id': 'eq.{}'.format(expense_id) - # } + expense_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(expense_id) + } - # expense = fyle_expense.get_expenses(query_params=expense_query_params) + expense = fyle_expense.get_expenses(query_params=expense_query_params) - add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'], add_to_report=add_to_report) + add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'][0], add_to_report=add_to_report) slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) @@ -354,7 +358,6 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - from . import expense user = utils.get_or_none(User, slack_user_id=user_id) @@ -364,18 +367,20 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i slack_client = get_slack_client(team_id) - # fyle_expense = FyleExpense(user) + expense_id = 'txCCVGvNpDMM' - # expense_query_params = { - # 'offset': 0, - # 'limit': '1', - # 'order': 'created_at.desc', - # 'id': 'eq.{}'.format(expense_id) - # } + fyle_expense = FyleExpense(user) - # expense = fyle_expense.get_expenses(query_params=expense_query_params) + expense_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(expense_id) + } - add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'], add_to_report='existing_report') + expense = fyle_expense.get_expenses(query_params=expense_query_params) + + add_expense_to_report_dialog = expense_messages.get_add_expense_to_report_dialog(expense=expense['data'][0], add_to_report='existing_report') add_expense_to_report_dialog['private_metadata'] = expense_id @@ -407,35 +412,35 @@ def handle_edit_expense(self, slack_payload: Dict, user_id: str, team_id: str) - def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - from . import report, list_expenses as expenses - user = utils.get_or_none(User, slack_user_id=user_id) report_id = slack_payload['actions'][0]['value'] + report_id = 'rpKJGi7nRzMF' + trigger_id = slack_payload['trigger_id'] slack_client = get_slack_client(team_id) - # fyle_expense = FyleExpense(user) + fyle_expense = FyleExpense(user) - # expense_query_params = { - # 'offset': 0, - # 'limit': '30', - # 'order': 'created_at.desc', - # 'report_id': 'eq.{}'.format(report_id) - # } + expense_query_params = { + 'offset': 0, + 'limit': '30', + 'order': 'created_at.desc', + 'report_id': 'eq.{}'.format(report_id) + } - # expenses = fyle_expense.get_expenses(query_params=expense_query_params) + expenses = fyle_expense.get_expenses(query_params=expense_query_params) - # report_query_params = { - # 'offset': 0, - # 'limit': '1', - # 'order': 'created_at.desc', - # 'id': 'eq.{}'.format(report_id) - # } + report_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(report_id) + } - # report = fyle_expense.get_reports(query_params=report_query_params) + report = fyle_expense.get_reports(query_params=report_query_params) add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(user, report=report['data'][0], expenses=expenses['data']) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 2c5f82a0..f5e536a2 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -22,7 +22,8 @@ def _initialize_block_suggestion_handlers(self): 'category_id': self.handle_category_suggestion, 'project_id': self.handle_project_suggestion, 'cost_center_id': self.handle_cost_center_suggestion, - 'currency': self.handle_currency_suggestion + 'currency': self.handle_currency_suggestion, + 'existing_report': self.handle_existing_report_suggestion } @@ -195,3 +196,42 @@ def handle_cost_center_suggestion(self, slack_payload: Dict, user_id: str, team_ cost_center_options.append(option) return cost_center_options + + + def handle_existing_report_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + user = utils.get_or_none(User, slack_user_id=user_id) + report_name_value_entered = slack_payload['value'] + query_params = { + 'offset': 0, + 'limit': '10', + 'order': 'state.asc', + 'purpose': 'ilike.%{}%'.format(report_name_value_entered), + 'state': 'in.(DRAFT, APPROVER_PENDING, APPROVER_INQUIRY)' + } + + fyle_expense = FyleExpense(user) + suggested_reports = fyle_expense.get_reports(query_params) + + report_state_emoji_text_mapping = { + 'DRAFT': ':mailbox: Draft', + 'APPROVER_PENDING': ':outbox_tray: Reported', + 'APPROVER_INQUIRY': ':back: Sent Back' + } + + report_options = [] + if suggested_reports['count'] > 0: + for report in suggested_reports['data']: + report_display_text = '{} ({} expenses) •'.format(report['purpose'], report['num_expenses']) + report_emoji = report_state_emoji_text_mapping[report['state']] + report_display_text = '{} {}'.format(report_display_text, report_emoji) + option = { + 'text': { + 'type': 'plain_text', + 'text': report_display_text, + 'emoji': True, + }, + 'value': str(report['id']), + } + report_options.append(option) + + return report_options diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 3b1839bc..f2f193d1 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -7,8 +7,6 @@ from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form -from . import expense - def check_project_in_form(fields_render_property: Dict, private_metadata: Dict) -> Union[bool, Any]: @@ -241,6 +239,20 @@ def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: L fyle_profile = get_fyle_profile(user.fyle_refresh_token) expense_id = slack_payload['actions'][0]['value'] + expense_id = 'txCCVGvNpDMM' + + fyle_expense = FyleExpense(user) + + expense_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(expense_id) + } + + expense = fyle_expense.get_expenses(query_params=expense_query_params) + + expense = expense['data'][0] home_currency = fyle_profile['org']['currency'] @@ -269,7 +281,7 @@ def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: L is_cost_centers_available = True if cost_centers['count'] > 0 else False - # custom_fields = fyle_expense.get_custom_fields_by_category_id(expense['category_id']) + custom_fields = fyle_expense.get_custom_fields_by_category_id(expense['category_id']) fields_render_property = { 'project': is_project_available, @@ -292,6 +304,6 @@ def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: L encoded_metadata = encode_state(private_metadata) - expense_form = expense_dialog_form(expense=expense['data'], fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details, add_to_report=add_to_report) + expense_form = expense_dialog_form(expense=expense, fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details, add_to_report=add_to_report, custom_fields=custom_fields) slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 2d2d4e70..5fc39fce 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -4,6 +4,7 @@ from django.http.response import JsonResponse +from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.models import User from fyle_slack_app.slack import utils as slack_utils from fyle_slack_app.libs import utils @@ -66,6 +67,8 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) expense_id = private_metadata.get('expense_id') + message_ts = private_metadata.get('message_ts') + if expense_id is not None: expense_details['id'] = expense_id @@ -82,12 +85,25 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) slack_client = slack_utils.get_slack_client(team_id) - view_expense_message = expense_messages.view_expense_message(expense_details, user) + expense_id = 'txCCVGvNpDMM' + + fyle_expense = FyleExpense(user) + + expense_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(expense_id) + } + + expense = fyle_expense.get_expenses(query_params=expense_query_params) + + view_expense_message = expense_messages.view_expense_message(expense['data'][0], user) - if expense_id is None: + if expense_id is None or message_ts is None: slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=view_expense_message) else: - slack_client.chat_update(channel=user.slack_dm_channel_id, blocks=view_expense_message, ts=private_metadata['message_ts']) + slack_client.chat_update(channel=user.slack_dm_channel_id, blocks=view_expense_message, ts=message_ts) return JsonResponse({}) @@ -98,9 +114,18 @@ def handle_submit_report(self, slack_payload: Dict, user_id: str, team_id: str) report_id = slack_payload['view']['private_metadata'] - slack_client = slack_utils.get_slack_client(team_id) + fyle_expense = FyleExpense(user) + + report_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(report_id) + } - from . import report + report = fyle_expense.get_reports(query_params=report_query_params) + + slack_client = slack_utils.get_slack_client(team_id) report_submitted_message = expense_messages.report_submitted_message(user, report['data'][0]) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 89da1614..414324dc 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines from typing import Any, Dict, List import datetime @@ -16,6 +17,7 @@ def get_custom_field_value(custom_fields: List, action_id: str) -> Any: return value +# pylint: disable=too-many-branches # is_additional_field is for fields which are not custom fields but are part of a specific categories def generate_field_ui(field_details: Dict, is_additional_field: bool = False, expense: Dict = None) -> Dict: block_id = '{}_block'.format(field_details['column_name']) @@ -613,6 +615,7 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'block_id': 'SELECT_add_to_existing_report_block', 'element': { 'type': 'external_select', + 'min_query_length': 0, 'placeholder': { 'type': 'plain_text', 'text': 'Select a Report', @@ -713,63 +716,80 @@ def expense_dialog_form( def view_expense_message(expense: Dict, user: User) -> Dict: - from fyle_slack_app.slack.interactives import expense - expense = expense['data'] spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') - primary_cta = None - primary_cta_value = None - primary_cta_text = None - primary_cta_action_id = None + actions = [] + + receipt_message = ':x: Not Attached' + if len(expense['file_ids']) > 0: + receipt_message = ':white_check_mark: Attached' + else: + + attach_receipt_cta = { + 'type': 'button', + 'style': 'primary', + 'text': { + 'type': 'plain_text', + 'text': 'Attach Receipt', + 'emoji': True, + }, + 'value': 'attach_receipt', + 'action_id': expense['id'] + } + + actions.append(attach_receipt_cta) report_message = ':x: Not Added' if expense['report_id'] is not None: report_message = ':white_check_mark: Added' if expense['report']['state'] in ['DRAFT', 'APPROVER_INQUIRY']: - primary_cta_text = 'Submit Report' - primary_cta_action_id = 'open_submit_report_dialog' - primary_cta_value = expense['report_id'] - else: - primary_cta_text = 'Add to Report' - primary_cta_action_id = 'add_expense_to_report' - primary_cta_value = expense['id'] + submit_report_cta = { + 'type': 'button', + 'style': 'primary', + 'text': { + 'type': 'plain_text', + 'text': 'Submit Report', + 'emoji': True, + }, + 'value': expense['report_id'], + 'action_id': 'open_submit_report_dialog' + } + actions.append(submit_report_cta) - receipt_message = ':x: Not Attached' - if len(expense['file_ids']) > 0: - receipt_message = ':white_check_mark: Attached' else: - primary_cta_text = 'Attach Receipt' - primary_cta_action_id = 'attach_receipt' - primary_cta_value = expense['id'] - if primary_cta_text is not None and primary_cta_action_id is not None: - primary_cta = { + add_to_report_cta = { 'type': 'button', 'style': 'primary', 'text': { 'type': 'plain_text', - 'text': primary_cta_text, + 'text': 'Submit Report', 'emoji': True, }, - 'value': primary_cta_value, - 'action_id': primary_cta_action_id + 'value': expense['id'], + 'action_id': 'add_expense_to_report' } - edit_expense_cta = { + actions.append(add_to_report_cta) + + complete_expense_cta = { 'type': 'button', 'text': { 'type': 'plain_text', - 'text': 'Edit Expense', + 'text': 'Complete Expense', 'emoji': True, }, 'value': expense['id'], 'action_id': 'edit_expense', } + if expense['state'] == 'DRAFT': + actions.insert(0, complete_expense_cta) + view_in_fyle_cta = { 'type': 'button', 'text': { @@ -782,15 +802,10 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'action_id': 'expense_view_in_fyle', } - actions = [ - view_in_fyle_cta - ] - - if expense['state'] in ['DRAFT', 'COMPLETE']: - actions.insert(0, edit_expense_cta) + if len(actions) == 0: + actions.append(view_in_fyle_cta) - if primary_cta is not None: - actions.insert(0, primary_cta) + expense_url = fyle_utils.get_fyle_resource_url(user.fyle_refresh_token, expense, 'EXPENSE') view_expense_blocks = [ { @@ -799,7 +814,30 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'text': { 'type': 'mrkdwn', 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) - } + }, + "accessory": { + "type": "overflow", + "options": [ + { + "text": { + "type": "plain_text", + "text": ":pencil: Edit", + "emoji": True + }, + "value": "edit_expense_accessory.{}".format(expense['id']) + }, + { + 'text': { + 'type': 'plain_text', + 'text': ':arrow_upper_right: Open in Fyle', + 'emoji': True + }, + 'url': expense_url, + 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) + } + ], + "action_id": "expense_accessory" + } }, { 'type': 'section', @@ -1004,30 +1042,6 @@ def get_view_report_details_dialog(user: User, report: Dict, expenses: List[Dict 'accessory': { 'type': 'overflow', 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': ':pencil: Edit', - 'emoji': True - }, - 'value': 'edit_expense_accessory' - }, - { - 'text': { - 'type': 'plain_text', - 'text': ':page_facing_up: View Details', - 'emoji': True - }, - 'value': 'view_expense_accessory' - }, - { - 'text': { - 'type': 'plain_text', - 'text': ':broom: Remove from Report', - 'emoji': True - }, - 'value': 'remove_expense_from_report_accessory' - }, { 'text': { 'type': 'plain_text', diff --git a/requirements.txt b/requirements.txt index 08f92ee8..d0e53d63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ Django==3.1.6 django-picklefield==3.0.1 django-q==1.3.4 future==0.18.2 -fyle==0.8.0 +fyle==0.14.0 gunicorn==20.0.4 idna==2.10 iniconfig==1.1.1 From 44216872765ae35d6e15c93b7af476a432a9375f Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 11 Oct 2021 13:09:55 +0530 Subject: [PATCH 41/85] Removed unnecessary project check and added some code comments --- fyle_slack_app/slack/commands/tasks.py | 2 + .../interactives/block_suggestion_handlers.py | 7 +-- fyle_slack_app/slack/interactives/tasks.py | 30 ++--------- fyle_slack_app/slack/ui/expenses/messages.py | 50 ++++++++++++------- 4 files changed, 40 insertions(+), 49 deletions(-) diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 14b5384f..08b4c511 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -132,6 +132,7 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: is_cost_centers_available = True if cost_centers['count'] > 0 else False + # Create a expense fields render property and set them optional in the form fields_render_property = { 'project': is_project_available, 'cost_center': is_cost_centers_available, @@ -146,6 +147,7 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: add_to_report = 'existing_report' + # Caching details in slack private metadata so that they can be reused again in the form without computing them again private_metadata = { 'fields_render_property': fields_render_property, 'home_currency': home_currency, diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index f5e536a2..c80dc3e9 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -6,7 +6,6 @@ from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.libs import logger, utils from fyle_slack_app.slack import utils as slack_utils -from fyle_slack_app.slack.interactives.tasks import check_project_in_form logger = logger.get_logger(__name__) @@ -79,11 +78,9 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: decoded_private_metadata = utils.decode_state(private_metadata) - fields_render_property = decoded_private_metadata['fields_render_property'] + project = decoded_private_metadata.get('project') - is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) - - if is_project_available is True and project is not None: + if project is not None: category_query_params['id'] = 'in.{}'.format(tuple(project['category_ids'])) suggested_categories = fyle_expense.get_categories(category_query_params) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index f2f193d1..cd423243 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Union +from typing import Dict, List from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense @@ -8,20 +8,6 @@ from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form -def check_project_in_form(fields_render_property: Dict, private_metadata: Dict) -> Union[bool, Any]: - - is_project_available = False - project = None - - if fields_render_property['project'] is True: - - is_project_available = True - - project = private_metadata.get('project') - - return is_project_available, project - - def get_custom_field_blocks(current_blocks: List[Dict]) -> List[Dict]: custom_field_blocks = [] @@ -98,8 +84,6 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st project_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'project_loading_block'), None) current_ui_blocks.pop(project_loading_block_index) - fields_render_property['project'] = True - encoded_private_metadata = encode_state(decoded_private_metadata) new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details, add_to_report=add_to_report, private_metadata=encoded_private_metadata) @@ -131,9 +115,7 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: category_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'category_loading_block'), None) current_ui_blocks.pop(category_loading_block_index) - is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) - - fields_render_property['project'] = is_project_available + project = decoded_private_metadata.get('project') new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, custom_fields=custom_fields, selected_project=project, additional_currency_details=additional_currency_details, add_to_report=add_to_report, private_metadata=private_metadata) @@ -174,12 +156,10 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None decoded_private_metadata['additional_currency_details'] = additional_currency_details - is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) + project = decoded_private_metadata.get('project') custom_fields = get_custom_field_blocks(current_ui_blocks) - fields_render_property['project'] = is_project_available - encoded_private_metadata = encode_state(decoded_private_metadata) expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, add_to_report=add_to_report, private_metadata=encoded_private_metadata) @@ -219,9 +199,7 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: custom_fields = get_custom_field_blocks(current_ui_blocks) - is_project_available, project = check_project_in_form(fields_render_property, decoded_private_metadata) - - fields_render_property['project'] = is_project_available + project = decoded_private_metadata.get('project') encoded_private_metadata = encode_state(decoded_private_metadata) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 414324dc..203da5ff 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -20,6 +20,7 @@ def get_custom_field_value(custom_fields: List, action_id: str) -> Any: # pylint: disable=too-many-branches # is_additional_field is for fields which are not custom fields but are part of a specific categories def generate_field_ui(field_details: Dict, is_additional_field: bool = False, expense: Dict = None) -> Dict: + block_id = '{}_block'.format(field_details['column_name']) action_id = field_details['column_name'] @@ -29,11 +30,15 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False, ex # We need to define addtional fields as custom fields so that we can clear them out in form when category is changed if field_details['is_custom'] is True or is_additional_field is True: + + # block_id for additional field block_id = '{}_additional_field_{}_block'.format(field_details['type'], field_details['column_name']) + if field_details['is_custom'] is True: block_id = '{}_custom_field_{}_block'.format(field_details['type'], field_details['column_name']) action_id = '{}'.format(field_details['field_name']) + # If already exisiting expense is passed then get the custom field value for that expense and add it to input fields if expense is not None: if is_additional_field is True: custom_field_value = expense[action_id] @@ -181,6 +186,7 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False, ex return custom_field +# Amount and currency block as individual function since Fyle has foreign amount and currency business logic def get_amount_and_currency_block(additional_currency_details: Dict = None, expense: Dict = None) -> List: blocks = [] @@ -585,6 +591,7 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: } blocks.append(add_to_report_block) + # Mapping of input UI to generate based on `Add to Exisiting Report` or `Add to New Report` selection add_to_report_mapping = { 'new_report': { 'ui': { @@ -681,8 +688,13 @@ def expense_dialog_form( # If custom fields are present, render them in the form if custom_fields is not None: + + # If cached custom fields are pass, render/ add them to UI directly + # Cached custom fields come from slack request payload which sends UI blocks on interaction if isinstance(custom_fields, list): view['blocks'].extend(custom_fields) + + # Generated custom fields UI and then add them to UI elif 'count' in custom_fields and custom_fields['count'] > 0: for field in custom_fields['data']: @@ -695,6 +707,7 @@ def expense_dialog_form( if custom_field is not None: view['blocks'].append(custom_field) + # Putting cost center block at end to maintain Fyle expense form order if fields_render_property['cost_center'] is True: cost_center_block = get_cost_centers_block(expense) @@ -707,6 +720,7 @@ def expense_dialog_form( 'type': 'divider' }) + # Add to report section if add_to_report is not None: add_to_report_blocks = get_add_to_report_blocks(add_to_report, action_id='add_to_report') @@ -815,17 +829,17 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'type': 'mrkdwn', 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) }, - "accessory": { - "type": "overflow", - "options": [ - { - "text": { - "type": "plain_text", - "text": ":pencil: Edit", - "emoji": True - }, - "value": "edit_expense_accessory.{}".format(expense['id']) - }, + 'accessory': { + 'type': 'overflow', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': ':pencil: Edit', + 'emoji': True + }, + 'value': 'edit_expense_accessory.{}'.format(expense['id']) + }, { 'text': { 'type': 'plain_text', @@ -835,9 +849,9 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'url': expense_url, 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) } - ], - "action_id": "expense_accessory" - } + ], + 'action_id': 'expense_accessory' + } }, { 'type': 'section', @@ -1103,11 +1117,11 @@ def report_submitted_message(user: User, report: Dict) -> List[Dict]: ] }, { - "type": "context", - "elements": [ + 'type': 'context', + 'elements': [ { - "type": "mrkdwn", - "text": ":bell: You will be notified when any action is taken by your approver" + 'type': 'mrkdwn', + 'text': ':bell: You will be notified when any action is taken by your approver' } ] }, From 2cc8e9fa8b39218623aa5eb0534f45dabc1a5f6a Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 11 Oct 2021 15:38:00 +0530 Subject: [PATCH 42/85] Handling add to report submission --- .../interactives/block_action_handlers.py | 2 + .../interactives/view_submission_handlers.py | 12 +- fyle_slack_app/slack/ui/expenses/messages.py | 120 ++++++++++-------- 3 files changed, 83 insertions(+), 51 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 79f59dd7..5d7932ec 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -341,6 +341,8 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s fyle_expense = FyleExpense(user) + expense_id = 'txCCVGvNpDMM' + expense_query_params = { 'offset': 0, 'limit': '1', diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 5fc39fce..ff2bd882 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -135,7 +135,17 @@ def handle_submit_report(self, slack_payload: Dict, user_id: str, team_id: str) def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - print('SLACK PAYLOAD VALUES -> ', slack_payload['view']['state']['values']) + + add_expense_to_report_form_values = slack_payload['view']['state']['values'] + expense_id = slack_payload['view']['private_metadata'] + + if 'TEXT_add_to_new_report_block' in add_expense_to_report_form_values: + report_name = add_expense_to_report_form_values['TEXT_add_to_new_report_block']['report_name']['value'] + + elif 'SELECT_add_to_existing_report_block' in add_expense_to_report_form_values: + existing_report_id = add_expense_to_report_form_values['SELECT_add_to_existing_report_block']['existing_report']['selected_option']['value'] + + return JsonResponse({}) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 203da5ff..3ce1af14 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -781,7 +781,7 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'style': 'primary', 'text': { 'type': 'plain_text', - 'text': 'Submit Report', + 'text': 'Add to Report', 'emoji': True, }, 'value': expense['id'], @@ -899,6 +899,8 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - if len(expense['file_ids']) > 0: receipt_message = ':white_check_mark: Attached' + add_to_report_blocks = get_add_to_report_blocks(add_to_report=add_to_report, action_id='add_expense_to_report_selection') + add_to_report_dialog = { 'title': { 'type': 'plain_text', @@ -912,63 +914,81 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - }, 'type': 'modal', 'callback_id': 'add_expense_to_report', + 'private_metadata': expense['id'], 'close': { 'type': 'plain_text', 'text': 'Cancel', 'emoji': True }, - 'blocks': [ - { - 'type': 'divider' - }, - { - 'type': 'section', - 'fields': [ - { - 'type': 'mrkdwn', - 'text': '*Amount* \n {} {}'.format(expense['currency'], expense['amount']) - }, - { - 'type': 'mrkdwn', - 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) - } - ] - }, - { - 'type': 'section', - 'fields': [ - { - 'type': 'mrkdwn', - 'text': '*Report* \n {}'.format(report_message) - }, - { - 'type': 'mrkdwn', - 'text': '*Receipt* \n {}'.format(receipt_message) - } - ] - }, - { - 'type': 'section', - 'fields': [ - { - 'type': 'mrkdwn', - 'text': '*Category* \n {}'.format(expense['category']['name']) - }, - { - 'type': 'mrkdwn', - 'text': '*Project* \n {}'.format(expense['project']['name']) - } - ] - }, - { - 'type': 'divider' - }, - ] } - add_to_report_blocks = get_add_to_report_blocks(add_to_report=add_to_report, action_id='add_expense_to_report_selection') + add_to_report_dialog['blocks'] = [] + + add_to_report_dialog['blocks'] = add_to_report_blocks + + expense_details_block = [ + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': '\n' + }, + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': ':page_facing_up: *Expense Details*' + }, + }, + { + 'type': 'divider' + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Amount* \n {} {}'.format(expense['currency'], expense['amount']) + }, + { + 'type': 'mrkdwn', + 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) + } + ] + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Report* \n {}'.format(report_message) + }, + { + 'type': 'mrkdwn', + 'text': '*Receipt* \n {}'.format(receipt_message) + } + ] + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Category* \n {}'.format(expense['category']['name']) + }, + { + 'type': 'mrkdwn', + 'text': '*Project* \n {}'.format(expense['project']['name']) + } + ] + }, + { + 'type': 'divider' + } + ] - add_to_report_dialog['blocks'].extend(add_to_report_blocks) + add_to_report_dialog['blocks'].extend(expense_details_block) return add_to_report_dialog From 5b263cd1c6161aca44879aefa6cf0ade6db581ca Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 11 Oct 2021 17:30:41 +0530 Subject: [PATCH 43/85] Remove file upload stuff --- fyle_slack_app/slack/events/views.py | 36 ------------------- .../interactives/block_action_handlers.py | 21 +++++++++-- fyle_slack_app/slack/ui/expenses/messages.py | 4 +-- 3 files changed, 21 insertions(+), 40 deletions(-) diff --git a/fyle_slack_app/slack/events/views.py b/fyle_slack_app/slack/events/views.py index f836a086..4d977d40 100644 --- a/fyle_slack_app/slack/events/views.py +++ b/fyle_slack_app/slack/events/views.py @@ -1,4 +1,3 @@ -from fyle_slack_app.models.users import User import json from django.http import JsonResponse, HttpRequest @@ -6,16 +5,12 @@ from fyle_slack_app.slack import SlackView from fyle_slack_app.slack.events.handlers import SlackEventHandler -from fyle_slack_app.slack.utils import get_slack_client - class SlackEventView(SlackView, SlackEventHandler): def post(self, request: HttpRequest) -> JsonResponse: slack_payload = json.loads(request.body) - print('SLACK EVENT -> ', json.dumps(slack_payload, indent=2)) - event_type = slack_payload['type'] event_response = {} @@ -30,37 +25,6 @@ def post(self, request: HttpRequest) -> JsonResponse: subevent_type = slack_payload['event']['type'] team_id = slack_payload['team_id'] - if subevent_type == 'file_shared': - slack_client = get_slack_client(team_id) - file_id = slack_payload['event']['file_id'] - - file_info = slack_client.files_info(file=file_id) - - user_id = slack_payload['event']['user_id'] - - user = User.objects.get(slack_user_id=user_id) - - thread_ts = file_info['file']['shares']['private'][user.slack_dm_channel_id][0]['thread_ts'] - - print('THREAD TD -> ', thread_ts) - - a = slack_client.chat_getPermalink(channel=user.slack_dm_channel_id, message_ts=thread_ts) - pl = a['permalink'] - - print('PL -> ', a['permalink']) - - parent_message = slack_client.conversations_history(channel=user.slack_dm_channel_id, latest=thread_ts, inclusive=True, limit=1) - - slack_client.chat_postMessage(text=f'<{pl}|LINK>', channel=user.slack_dm_channel_id) - - for block in parent_message['messages'][0]['blocks']: - if block['type'] == 'context': - expense_id = block['block_id'] - break - print('EXPENSE ID -> ', expense_id) - - return JsonResponse({}, status=200) - self.handle_event_callback(subevent_type, slack_payload, team_id) return JsonResponse(event_response, status=200) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 5d7932ec..783842ce 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -38,8 +38,7 @@ def _initialize_block_action_handlers(self): 'report_commented_notification_preference': self.handle_notification_preference_selection, 'expense_commented_notification_preference': self.handle_notification_preference_selection, 'edit_expense': self.handle_edit_expense, - - # Dynamic options + 'attach_receipt': self.handle_attach_receipt, 'category_id': self.handle_category_select, 'project_id': self.handle_project_select, 'currency': self.handle_currency_select, @@ -453,6 +452,24 @@ def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id return JsonResponse({}) + def handle_attach_receipt(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + message_ts = slack_payload['container']['message_ts'] + + user = utils.get_or_none(User, slack_user_id=user_id) + + attach_receipt_message = '*Drag* or *attach* a receipt (to the message box) for this expense!' + + slack_client = get_slack_client(team_id) + + slack_client.chat_postMessage( + text=attach_receipt_message, + thread_ts=message_ts, + channel=user.slack_dm_channel_id + ) + + return JsonResponse({}) + + def track_view_in_fyle_action(self, user_id: str, event_name: str, event_data: Dict) -> None: user = utils.get_or_none(User, slack_user_id=user_id) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 3ce1af14..aeee3bf2 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -748,8 +748,8 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'text': 'Attach Receipt', 'emoji': True, }, - 'value': 'attach_receipt', - 'action_id': expense['id'] + 'value': expense['id'], + 'action_id': 'attach_receipt' } actions.append(attach_receipt_cta) From 68dc646d64ecddbc5ef2157b62766afca9e0762e Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 11 Oct 2021 18:14:59 +0530 Subject: [PATCH 44/85] Expense form func indentation --- fyle_slack_app/slack/interactives/tasks.py | 44 +++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index cd423243..80b1f22e 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -86,7 +86,13 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st encoded_private_metadata = encode_state(decoded_private_metadata) - new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, selected_project=project, additional_currency_details=additional_currency_details, add_to_report=add_to_report, private_metadata=encoded_private_metadata) + new_expense_dialog_form = expense_dialog_form( + fields_render_property=fields_render_property, + selected_project=project, + additional_currency_details=additional_currency_details, + add_to_report=add_to_report, + private_metadata=encoded_private_metadata + ) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -117,7 +123,14 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: project = decoded_private_metadata.get('project') - new_expense_dialog_form = expense_dialog_form(fields_render_property=fields_render_property, custom_fields=custom_fields, selected_project=project, additional_currency_details=additional_currency_details, add_to_report=add_to_report, private_metadata=private_metadata) + new_expense_dialog_form = expense_dialog_form( + fields_render_property=fields_render_property, + custom_fields=custom_fields, + selected_project=project, + additional_currency_details=additional_currency_details, + add_to_report=add_to_report, + private_metadata=private_metadata + ) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -162,7 +175,14 @@ def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None encoded_private_metadata = encode_state(decoded_private_metadata) - expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, add_to_report=add_to_report, private_metadata=encoded_private_metadata) + expense_form = expense_dialog_form( + selected_project=project, + fields_render_property=fields_render_property, + additional_currency_details=additional_currency_details, + custom_fields=custom_fields, + add_to_report=add_to_report, + private_metadata=encoded_private_metadata + ) slack_client.views_update(view_id=view_id, view=expense_form) @@ -203,7 +223,14 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: encoded_private_metadata = encode_state(decoded_private_metadata) - expense_form = expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, add_to_report=add_to_report, custom_fields=custom_fields, private_metadata=encoded_private_metadata) + expense_form = expense_dialog_form( + selected_project=project, + fields_render_property=fields_render_property, + additional_currency_details=additional_currency_details, + add_to_report=add_to_report, + custom_fields=custom_fields, + private_metadata=encoded_private_metadata + ) slack_client.views_update(view_id=view_id, view=expense_form) @@ -282,6 +309,13 @@ def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: L encoded_metadata = encode_state(private_metadata) - expense_form = expense_dialog_form(expense=expense, fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details, add_to_report=add_to_report, custom_fields=custom_fields) + expense_form = expense_dialog_form( + expense=expense, + fields_render_property=fields_render_property, + private_metadata=encoded_metadata, + additional_currency_details=additional_currency_details, + add_to_report=add_to_report, + custom_fields=custom_fields + ) slack_client.views_update(view=expense_form, view_id=view_id) From 471ed95c9660621f34a6b8b641b9794def8e3273 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 13 Oct 2021 16:44:07 +0530 Subject: [PATCH 45/85] Separated out expense form logic in a single function --- fyle_slack_app/fyle/expenses/views.py | 78 ++++++++++++++ fyle_slack_app/slack/commands/tasks.py | 101 ++++++++++-------- fyle_slack_app/slack/interactives/tasks.py | 62 ++--------- .../interactives/view_submission_handlers.py | 1 + fyle_slack_app/slack/ui/expenses/messages.py | 52 +++++---- 5 files changed, 177 insertions(+), 117 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index f8797b20..e965de65 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -3,7 +3,9 @@ from fyle.platform.platform import Platform from fyle_slack_app.fyle.utils import get_fyle_sdk_connection +from fyle_slack_app.libs.utils import encode_state from fyle_slack_app.models.users import User +from fyle_slack_app.fyle import utils as fyle_utils class FyleExpense: @@ -95,3 +97,79 @@ def get_expense_fields_type_mandatory_mapping(expense_fields: List[Dict]) -> Dic mandatory_mapping[field['column_name']] = field['is_mandatory'] return mandatory_mapping + + + @staticmethod + def get_expense_form_details(user: User) -> Dict: + + fyle_expense = FyleExpense(user) + + fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) + + home_currency = fyle_profile['org']['currency'] + + default_expense_fields = fyle_expense.get_default_expense_fields() + + field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) + + is_project_available = False + is_cost_center_available = False + + projects_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + projects = fyle_expense.get_projects(projects_query_params) + + is_project_available = True if projects['count'] > 0 else False + + cost_centers_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) + + is_cost_center_available = True if cost_centers['count'] > 0 else False + + # Create a expense fields render property and set them optional in the form + fields_render_property = { + 'project': { + 'is_project_available': is_project_available, + 'is_mandatory': field_type_mandatory_mapping['project_id'] + }, + 'cost_center': { + 'is_cost_center_available': is_cost_center_available, + 'is_mandatory': field_type_mandatory_mapping['cost_center_id'] + }, + 'purpose': field_type_mandatory_mapping['purpose'], + 'transaction_date': field_type_mandatory_mapping['txn_dt'], + 'vendor': field_type_mandatory_mapping['vendor_id'] + } + + additional_currency_details = { + 'home_currency': home_currency + } + + add_to_report = 'existing_report' + + # Caching details in slack private metadata so that they can be reused again in the form without computing them again + private_metadata = { + 'fields_render_property': fields_render_property, + 'additional_currency_details': additional_currency_details, + 'add_to_report': add_to_report + } + + expense_form_details = { + 'fields_render_property': fields_render_property, + 'private_metadata': encode_state(private_metadata), + 'additional_currency_details': additional_currency_details, + 'add_to_report': add_to_report + } + + return expense_form_details diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 08b4c511..59df5993 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -95,68 +95,79 @@ def fyle_unlink_account(user_id: str, team_id: str, user_dm_channel_id: str) -> def open_expense_form(user: User, team_id: str, view_id: str) -> None: - fyle_expense = FyleExpense(user) + slack_client = slack_utils.get_slack_client(team_id) - default_expense_fields = fyle_expense.get_default_expense_fields() + # fyle_expense = FyleExpense(user) - slack_client = slack_utils.get_slack_client(team_id) + # default_expense_fields = fyle_expense.get_default_expense_fields() - fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) + # fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) - home_currency = fyle_profile['org']['currency'] + # home_currency = fyle_profile['org']['currency'] - field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) + # default_expense_fields = fyle_expense.get_default_expense_fields() - is_project_available = False - is_cost_centers_available = False + # field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) - projects_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } + # is_project_available = False + # is_cost_center_available = False - projects = fyle_expense.get_projects(projects_query_params) + # projects_query_params = { + # 'offset': 0, + # 'limit': '1', + # 'order': 'created_at.desc', + # 'is_enabled': 'eq.{}'.format(True) + # } - is_project_available = True if projects['count'] > 0 else False + # projects = fyle_expense.get_projects(projects_query_params) - cost_centers_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } + # is_project_available = True if projects['count'] > 0 else False - cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) + # cost_centers_query_params = { + # 'offset': 0, + # 'limit': '1', + # 'order': 'created_at.desc', + # 'is_enabled': 'eq.{}'.format(True) + # } - is_cost_centers_available = True if cost_centers['count'] > 0 else False + # cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) - # Create a expense fields render property and set them optional in the form - fields_render_property = { - 'project': is_project_available, - 'cost_center': is_cost_centers_available, - 'purpose': field_type_mandatory_mapping['purpose'], - 'transaction_date': field_type_mandatory_mapping['txn_dt'], - 'vendor': field_type_mandatory_mapping['vendor_id'] - } + # is_cost_center_available = True if cost_centers['count'] > 0 else False - additional_currency_details = { - 'home_currency': home_currency - } + # # Create a expense fields render property and set them optional in the form + # fields_render_property = { + # 'project': { + # 'is_project_available': is_project_available, + # 'is_mandatory': field_type_mandatory_mapping['project_id'] + # }, + # 'cost_center': { + # 'is_cost_center_available': is_cost_center_available, + # 'is_mandatory': field_type_mandatory_mapping['cost_center_id'] + # }, + # 'purpose': field_type_mandatory_mapping['purpose'], + # 'transaction_date': field_type_mandatory_mapping['txn_dt'], + # 'vendor': field_type_mandatory_mapping['vendor_id'] + # } - add_to_report = 'existing_report' + # additional_currency_details = { + # 'home_currency': home_currency + # } - # Caching details in slack private metadata so that they can be reused again in the form without computing them again - private_metadata = { - 'fields_render_property': fields_render_property, - 'home_currency': home_currency, - 'additional_currency_details': additional_currency_details, - 'add_to_report': add_to_report - } + # add_to_report = 'existing_report' + + # # Caching details in slack private metadata so that they can be reused again in the form without computing them again + # private_metadata = { + # 'fields_render_property': fields_render_property, + # 'additional_currency_details': additional_currency_details, + # 'add_to_report': add_to_report + # } - encoded_metadata = utils.encode_state(private_metadata) + # encoded_metadata = utils.encode_state(private_metadata) - expense_form = expense_messages.expense_dialog_form(fields_render_property=fields_render_property, private_metadata=encoded_metadata, additional_currency_details=additional_currency_details, add_to_report=add_to_report) + expense_form_details = FyleExpense.get_expense_form_details(user) + + expense_form = expense_messages.expense_dialog_form( + **expense_form_details + ) slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 80b1f22e..1cd1447f 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -237,12 +237,8 @@ def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: List[Dict]) -> None: - fyle_expense = FyleExpense(user) - slack_client = get_slack_client(team_id) - fyle_profile = get_fyle_profile(user.fyle_refresh_token) - expense_id = slack_payload['actions'][0]['value'] expense_id = 'txCCVGvNpDMM' @@ -259,63 +255,25 @@ def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: L expense = expense['data'][0] - home_currency = fyle_profile['org']['currency'] - - is_project_available = False - is_cost_centers_available = False - - projects_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } - - projects = fyle_expense.get_projects(projects_query_params) - - is_project_available = True if projects['count'] > 0 else False - - cost_centers_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } - - cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) - - is_cost_centers_available = True if cost_centers['count'] > 0 else False - custom_fields = fyle_expense.get_custom_fields_by_category_id(expense['category_id']) - fields_render_property = { - 'project': is_project_available, - 'cost_center': is_cost_centers_available - } - - additional_currency_details = { - 'home_currency': home_currency - } + expense_form_details = FyleExpense.get_expense_form_details(user) - add_to_report = 'existing_report' + private_metadata = decode_state(expense_form_details['private_metadata']) - private_metadata = { - 'fields_render_property': fields_render_property, - 'additional_currency_details': additional_currency_details, - 'expense_id': expense_id, - 'add_to_report': add_to_report, - 'message_ts': slack_payload['container']['message_ts'] - } + # Add additional metadata to differentiate create and edit expense + # message_ts to update message in edit case + private_metadata['expense_id'] = expense_id + private_metadata['message_ts'] = slack_payload['container']['message_ts'] encoded_metadata = encode_state(private_metadata) + expense_form_details['private_metadata'] = encoded_metadata + expense_form = expense_dialog_form( expense=expense, - fields_render_property=fields_render_property, - private_metadata=encoded_metadata, - additional_currency_details=additional_currency_details, - add_to_report=add_to_report, - custom_fields=custom_fields + custom_fields=custom_fields, + **expense_form_details ) slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index ff2bd882..f1141d26 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -86,6 +86,7 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) slack_client = slack_utils.get_slack_client(team_id) expense_id = 'txCCVGvNpDMM' + # expense_id = 'tx0mjvrfuizk' fyle_expense = FyleExpense(user) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index aeee3bf2..b797c1df 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -50,6 +50,7 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False, ex custom_field = { 'type': 'input', 'block_id': block_id, + 'optional': not field_details['is_mandatory'], 'label': { 'type': 'plain_text', 'text': '{}'.format(field_details['field_name']), @@ -82,6 +83,7 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False, ex 'emoji': True, }, 'block_id': block_id, + 'optional': not field_details['is_mandatory'], 'element': { 'type': field_type, 'placeholder': { @@ -164,6 +166,7 @@ def generate_field_ui(field_details: Dict, is_additional_field: bool = False, ex custom_field = { 'type': 'input', 'block_id': block_id, + 'optional': not field_details['is_mandatory'], 'element': { 'type': 'datepicker', 'placeholder': { @@ -406,6 +409,8 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict = None) -> Dict: + billable_block = None + project_block = { 'type': 'input', 'block_id': 'project_block', @@ -447,24 +452,26 @@ def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict 'value': str(selected_project['id']), } - billable_block = { - 'type': 'input', - 'block_id': 'billable_block', - 'element': { - 'type': 'checkboxes', - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': 'Billable', - 'emoji': True + # Render billable block only when project is selected + billable_block = { + 'type': 'input', + 'block_id': 'billable_block', + 'optional': True, + 'element': { + 'type': 'checkboxes', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': 'Billable', + 'emoji': True + } } - } - ], - 'action_id': 'is_billable' - }, - 'label': {'type': 'plain_text', 'text': 'Billable', 'emoji': True}, - } + ], + 'action_id': 'is_billable' + }, + 'label': {'type': 'plain_text', 'text': 'Billable', 'emoji': True}, + } return project_block, billable_block @@ -673,13 +680,16 @@ def expense_dialog_form( view['blocks'] = get_default_fields_blocks(additional_currency_details, expense) - if fields_render_property['project'] is True: + if fields_render_property['project']['is_project_available'] is True: project_block, billable_block = get_projects_and_billable_block(selected_project, expense) + project_block['optional'] = not fields_render_property['project']['is_mandatory'] + view['blocks'].append(project_block) - view['blocks'].append(billable_block) + if billable_block is not None: + view['blocks'].append(billable_block) category_block = get_categories_block(expense) @@ -708,10 +718,12 @@ def expense_dialog_form( view['blocks'].append(custom_field) # Putting cost center block at end to maintain Fyle expense form order - if fields_render_property['cost_center'] is True: + if fields_render_property['cost_center']['is_cost_center_available'] is True: cost_center_block = get_cost_centers_block(expense) + cost_center_block['optional'] = not fields_render_property['cost_center']['is_mandatory'] + view['blocks'].append(cost_center_block) From af086548ffe5463910eb47dc580389bcf7df8d7a Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 13 Oct 2021 18:00:30 +0530 Subject: [PATCH 46/85] Removed a lot of redundant code --- fyle_slack_app/fyle/expenses/views.py | 39 ++++- fyle_slack_app/slack/commands/tasks.py | 67 --------- .../interactives/block_action_handlers.py | 41 +++--- fyle_slack_app/slack/interactives/tasks.py | 134 +++++------------- 4 files changed, 101 insertions(+), 180 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index e965de65..ba2ff3f9 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -3,7 +3,7 @@ from fyle.platform.platform import Platform from fyle_slack_app.fyle.utils import get_fyle_sdk_connection -from fyle_slack_app.libs.utils import encode_state +from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.models.users import User from fyle_slack_app.fyle import utils as fyle_utils @@ -173,3 +173,40 @@ def get_expense_form_details(user: User) -> Dict: } return expense_form_details + + + @staticmethod + def get_current_expense_form_details(slack_payload: Dict) -> Dict: + + private_metadata = slack_payload['view']['private_metadata'] + + decoded_private_metadata = decode_state(private_metadata) + + fields_render_property = decoded_private_metadata['fields_render_property'] + + additional_currency_details = decoded_private_metadata.get('additional_currency_details') + + add_to_report = decoded_private_metadata.get('add_to_report') + + current_ui_blocks = slack_payload['view']['blocks'] + + project = decoded_private_metadata.get('project') + + custom_field_blocks = [] + + for block in current_ui_blocks: + if 'custom_field' in block['block_id'] or 'additional_field' in block['block_id']: + custom_field_blocks.append(block) + + if len(custom_field_blocks) == 0: + custom_field_blocks = None + + current_form_details = { + 'fields_render_property': fields_render_property, + 'selected_project': project, + 'additional_currency_details': additional_currency_details, + 'add_to_report': add_to_report, + 'private_metadata': private_metadata, + 'custom_fields': custom_field_blocks + } + return current_form_details diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 59df5993..369bec38 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -97,73 +97,6 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: slack_client = slack_utils.get_slack_client(team_id) - # fyle_expense = FyleExpense(user) - - # default_expense_fields = fyle_expense.get_default_expense_fields() - - # fyle_profile = fyle_utils.get_fyle_profile(user.fyle_refresh_token) - - # home_currency = fyle_profile['org']['currency'] - - # default_expense_fields = fyle_expense.get_default_expense_fields() - - # field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) - - # is_project_available = False - # is_cost_center_available = False - - # projects_query_params = { - # 'offset': 0, - # 'limit': '1', - # 'order': 'created_at.desc', - # 'is_enabled': 'eq.{}'.format(True) - # } - - # projects = fyle_expense.get_projects(projects_query_params) - - # is_project_available = True if projects['count'] > 0 else False - - # cost_centers_query_params = { - # 'offset': 0, - # 'limit': '1', - # 'order': 'created_at.desc', - # 'is_enabled': 'eq.{}'.format(True) - # } - - # cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) - - # is_cost_center_available = True if cost_centers['count'] > 0 else False - - # # Create a expense fields render property and set them optional in the form - # fields_render_property = { - # 'project': { - # 'is_project_available': is_project_available, - # 'is_mandatory': field_type_mandatory_mapping['project_id'] - # }, - # 'cost_center': { - # 'is_cost_center_available': is_cost_center_available, - # 'is_mandatory': field_type_mandatory_mapping['cost_center_id'] - # }, - # 'purpose': field_type_mandatory_mapping['purpose'], - # 'transaction_date': field_type_mandatory_mapping['txn_dt'], - # 'vendor': field_type_mandatory_mapping['vendor_id'] - # } - - # additional_currency_details = { - # 'home_currency': home_currency - # } - - # add_to_report = 'existing_report' - - # # Caching details in slack private metadata so that they can be reused again in the form without computing them again - # private_metadata = { - # 'fields_render_property': fields_render_property, - # 'additional_currency_details': additional_currency_details, - # 'add_to_report': add_to_report - # } - - # encoded_metadata = utils.encode_state(private_metadata) - expense_form_details = FyleExpense.get_expense_form_details(user) expense_form = expense_messages.expense_dialog_form( diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 783842ce..280ae4f9 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -10,7 +10,6 @@ from fyle_slack_app.libs import assertions, utils, logger from fyle_slack_app.slack.utils import get_slack_user_dm_channel_id, get_slack_client from fyle_slack_app.slack.ui.expenses import messages as expense_messages -from fyle_slack_app.slack.interactives import tasks from fyle_slack_app import tracking @@ -265,11 +264,17 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str def handle_currency_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + user = utils.get_or_none(User, slack_user_id=user_id) + selected_currency = slack_payload['actions'][0]['selected_option']['value'] + + view_id = slack_payload['container']['view_id'] + async_task( 'fyle_slack_app.slack.interactives.tasks.handle_currency_select', - user, + selected_currency, + view_id, team_id, slack_payload ) @@ -281,9 +286,14 @@ def handle_amount_entered(self, slack_payload: Dict, user_id: str, team_id: str) user = utils.get_or_none(User, slack_user_id=user_id) + amount_entered = slack_payload['actions'][0]['value'] + + view_id = slack_payload['container']['view_id'] + async_task( 'fyle_slack_app.slack.interactives.tasks.handle_amount_entered', - user, + amount_entered, + view_id, team_id, slack_payload ) @@ -299,27 +309,23 @@ def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) slack_client = get_slack_client(team_id) - private_metadata = slack_payload['view']['private_metadata'] + current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) - decoded_private_metadata = utils.decode_state(private_metadata) - - fields_render_property = decoded_private_metadata['fields_render_property'] - - current_ui_blocks = slack_payload['view']['blocks'] - - additional_currency_details = decoded_private_metadata.get('additional_currency_details') - - is_project_available, project = tasks.check_project_in_form(fields_render_property, decoded_private_metadata) + private_metadata = current_expense_form_details['private_metadata'] - custom_fields = tasks.get_custom_field_blocks(current_ui_blocks) + decoded_private_metadata = utils.decode_state(private_metadata) - fields_render_property['project'] = is_project_available + current_expense_form_details['add_to_report'] = add_to_report decoded_private_metadata['add_to_report'] = add_to_report encoded_private_metadata = utils.encode_state(decoded_private_metadata) - expense_form = expense_messages.expense_dialog_form(selected_project=project, fields_render_property=fields_render_property, additional_currency_details=additional_currency_details, custom_fields=custom_fields, add_to_report=add_to_report, private_metadata=encoded_private_metadata) + current_expense_form_details['private_metadata'] = encoded_private_metadata + + expense_form = expense_messages.expense_dialog_form( + **current_expense_form_details + ) slack_client.views_update(view_id=view_id, view=expense_form) @@ -398,11 +404,14 @@ def handle_edit_expense(self, slack_payload: Dict, user_id: str, team_id: str) - user = utils.get_or_none(User, slack_user_id=user_id) + expense_id = slack_payload['actions'][0]['value'] + response = slack_client.views_open(view=loading_modal, trigger_id=slack_payload['trigger_id']) async_task( 'fyle_slack_app.slack.interactives.tasks.handle_edit_expense', user, + expense_id, team_id, response['view']['id'], slack_payload diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 1cd1447f..e9b6cd63 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -2,25 +2,11 @@ from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense -from fyle_slack_app.fyle.utils import get_fyle_profile from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form -def get_custom_field_blocks(current_blocks: List[Dict]) -> List[Dict]: - custom_field_blocks = [] - - for block in current_blocks: - if 'custom_field' in block['block_id'] or 'additional_field' in block['block_id']: - custom_field_blocks.append(block) - - if len(custom_field_blocks) == 0: - custom_field_blocks = None - - return custom_field_blocks - - def get_additional_currency_details(amount: int, home_currency: str, selected_currency: str, exchange_rate: float) -> Dict: if amount is None or len(amount) == 0: @@ -56,10 +42,6 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st project = fyle_expense.get_projects(project_query_params) - private_metadata = slack_payload['view']['private_metadata'] - - decoded_private_metadata = decode_state(private_metadata) - project = project['data'][0] project = { @@ -70,13 +52,11 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st 'category_ids': project['category_ids'] } - decoded_private_metadata['project'] = project - - fields_render_property = decoded_private_metadata['fields_render_property'] + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) - additional_currency_details = decoded_private_metadata.get('additional_currency_details') + private_metadata = current_expense_form_details['private_metadata'] - add_to_report = decoded_private_metadata.get('add_to_report') + current_expense_form_details['selected_project'] = project current_ui_blocks = slack_payload['view']['blocks'] @@ -84,14 +64,16 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st project_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'project_loading_block'), None) current_ui_blocks.pop(project_loading_block_index) + decoded_private_metadata = decode_state(private_metadata) + + decoded_private_metadata['project'] = project + encoded_private_metadata = encode_state(decoded_private_metadata) + current_expense_form_details['private_metadata'] = encoded_private_metadata + new_expense_dialog_form = expense_dialog_form( - fields_render_property=fields_render_property, - selected_project=project, - additional_currency_details=additional_currency_details, - add_to_report=add_to_report, - private_metadata=encoded_private_metadata + **current_expense_form_details ) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) @@ -105,15 +87,9 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: custom_fields = fyle_expense.get_custom_fields_by_category_id(category_id) - private_metadata = slack_payload['view']['private_metadata'] - - decoded_private_metadata = decode_state(private_metadata) + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) - fields_render_property = decoded_private_metadata['fields_render_property'] - - additional_currency_details = decoded_private_metadata.get('additional_currency_details') - - add_to_report = decoded_private_metadata.get('add_to_report') + current_expense_form_details['custom_fields'] = custom_fields current_ui_blocks = slack_payload['view']['blocks'] @@ -121,125 +97,91 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: category_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'category_loading_block'), None) current_ui_blocks.pop(category_loading_block_index) - project = decoded_private_metadata.get('project') - new_expense_dialog_form = expense_dialog_form( - fields_render_property=fields_render_property, - custom_fields=custom_fields, - selected_project=project, - additional_currency_details=additional_currency_details, - add_to_report=add_to_report, - private_metadata=private_metadata + **current_expense_form_details ) slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) -def handle_currency_select(user: User, team_id: str, slack_payload: str) -> None: - - selected_currency = slack_payload['actions'][0]['selected_option']['value'] - - view_id = slack_payload['container']['view_id'] +def handle_currency_select(selected_currency: str, view_id: str, team_id: str, slack_payload: str) -> None: slack_client = get_slack_client(team_id) - private_metadata = slack_payload['view']['private_metadata'] - - decoded_private_metadata = decode_state(private_metadata) - - fields_render_property = decoded_private_metadata['fields_render_property'] + current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) - add_to_report = decoded_private_metadata.get('add_to_report') + private_metadata = current_expense_form_details['private_metadata'] - home_currency = decoded_private_metadata['additional_currency_details']['home_currency'] + decoded_private_metadata = decode_state(private_metadata) - form_current_state = slack_payload['view']['state']['values'] + additional_currency_details = current_expense_form_details['additional_currency_details'] - current_ui_blocks = slack_payload['view']['blocks'] + home_currency = additional_currency_details['home_currency'] additional_currency_details = { 'home_currency': home_currency } if home_currency != selected_currency: + form_current_state = slack_payload['view']['state']['values'] exchange_rate = 70.12 amount = form_current_state['NUMBER_default_field_amount_block']['amount']['value'] - additional_currency_details = get_additional_currency_details(amount, home_currency, selected_currency, exchange_rate) - decoded_private_metadata['additional_currency_details'] = additional_currency_details - - project = decoded_private_metadata.get('project') + current_expense_form_details['additional_currency_details'] = additional_currency_details - custom_fields = get_custom_field_blocks(current_ui_blocks) + decoded_private_metadata['additional_currency_details'] = additional_currency_details encoded_private_metadata = encode_state(decoded_private_metadata) + current_expense_form_details['private_metadata'] = encoded_private_metadata + expense_form = expense_dialog_form( - selected_project=project, - fields_render_property=fields_render_property, - additional_currency_details=additional_currency_details, - custom_fields=custom_fields, - add_to_report=add_to_report, - private_metadata=encoded_private_metadata + **current_expense_form_details ) slack_client.views_update(view_id=view_id, view=expense_form) -def handle_amount_entered(user: User, team_id: str, slack_payload: str) -> None: +def handle_amount_entered(amount_entered: float, view_id: str, team_id: str, slack_payload: str) -> None: slack_client = get_slack_client(team_id) - amount_entered = slack_payload['actions'][0]['value'] - - view_id = slack_payload['container']['view_id'] - form_current_state = slack_payload['view']['state']['values'] - private_metadata = slack_payload['view']['private_metadata'] - - decoded_private_metadata = decode_state(private_metadata) - - current_ui_blocks = slack_payload['view']['blocks'] - - fields_render_property = decoded_private_metadata['fields_render_property'] + selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] - add_to_report = decoded_private_metadata.get('add_to_report') + current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) - home_currency = decoded_private_metadata['additional_currency_details']['home_currency'] + private_metadata = current_expense_form_details['private_metadata'] - selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] + decoded_private_metadata = decode_state(private_metadata) exchange_rate = 70.12 - additional_currency_details = get_additional_currency_details(amount_entered, home_currency, selected_currency, exchange_rate) + home_currency = current_expense_form_details['additional_currency_details']['home_currency'] - decoded_private_metadata['additional_currency_details'] = additional_currency_details + additional_currency_details = get_additional_currency_details(amount_entered, home_currency, selected_currency, exchange_rate) - custom_fields = get_custom_field_blocks(current_ui_blocks) + current_expense_form_details['additional_currency_details'] = additional_currency_details - project = decoded_private_metadata.get('project') + decoded_private_metadata['additional_currency_details'] = additional_currency_details encoded_private_metadata = encode_state(decoded_private_metadata) + current_expense_form_details['private_metadata'] = encoded_private_metadata + expense_form = expense_dialog_form( - selected_project=project, - fields_render_property=fields_render_property, - additional_currency_details=additional_currency_details, - add_to_report=add_to_report, - custom_fields=custom_fields, - private_metadata=encoded_private_metadata + **current_expense_form_details ) slack_client.views_update(view_id=view_id, view=expense_form) -def handle_edit_expense(user: User, team_id: str, view_id: str, slack_payload: List[Dict]) -> None: +def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, slack_payload: List[Dict]) -> None: slack_client = get_slack_client(team_id) - expense_id = slack_payload['actions'][0]['value'] expense_id = 'txCCVGvNpDMM' fyle_expense = FyleExpense(user) From 97d6d3b9b893973d32c1a516e2c485d8289b36c5 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Thu, 14 Oct 2021 10:51:56 +0530 Subject: [PATCH 47/85] Added return statements directly --- fyle_slack_app/fyle/expenses/views.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index ba2ff3f9..223998a0 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -17,8 +17,7 @@ def __init__(self, user: User) -> None: def get_expense_fields(self, query_params: Dict) -> Dict: - expense_fields = self.connection.v1.fyler.expense_fields.list(query_params=query_params) - return expense_fields + return self.connection.v1.fyler.expense_fields.list(query_params=query_params) def get_default_expense_fields(self) -> Dict: @@ -32,9 +31,7 @@ def get_default_expense_fields(self) -> Dict: 'is_mandatory': 'eq.{}'.format(True) } - default_expense_fields = self.get_expense_fields(default_expense_fields_query_params) - - return default_expense_fields + return self.get_expense_fields(default_expense_fields_query_params) def get_custom_fields_by_category_id(self, category_id: str) -> Dict: @@ -47,34 +44,27 @@ def get_custom_fields_by_category_id(self, category_id: str) -> Dict: 'category_ids': 'cs.[{}]'.format(int(category_id)) } - custom_fields = self.get_expense_fields(custom_fields_query_params) - - return custom_fields + return self.get_expense_fields(custom_fields_query_params) def get_categories(self, query_params: Dict) -> Dict: - categories = self.connection.v1.fyler.categories.list(query_params=query_params) - return categories + return self.connection.v1.fyler.categories.list(query_params=query_params) def get_projects(self, query_params: Dict) -> Dict: - projects = self.connection.v1.fyler.projects.list(query_params=query_params) - return projects + return self.connection.v1.fyler.projects.list(query_params=query_params) def get_cost_centers(self, query_params: Dict) -> Dict: - cost_centers = self.connection.v1.fyler.cost_centers.list(query_params=query_params) - return cost_centers + return self.connection.v1.fyler.cost_centers.list(query_params=query_params) def get_expenses(self, query_params: Dict) -> Dict: - expenses = self.connection.v1.fyler.expenses.list(query_params=query_params) - return expenses + return self.connection.v1.fyler.expenses.list(query_params=query_params) def get_reports(self, query_params: Dict) -> Dict: - reports = self.connection.v1.fyler.reports.list(query_params=query_params) - return reports + return self.connection.v1.fyler.reports.list(query_params=query_params) @staticmethod From 2e1dca2d845da3169ac5af56131af01fcdd577bc Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 19 Oct 2021 11:36:27 +0530 Subject: [PATCH 48/85] Handling report details in background --- .../interactives/block_action_handlers.py | 38 ++++++------------- fyle_slack_app/slack/interactives/tasks.py | 32 ++++++++++++++++ 2 files changed, 43 insertions(+), 27 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 280ae4f9..e59307cb 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -424,39 +424,23 @@ def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id user = utils.get_or_none(User, slack_user_id=user_id) - report_id = slack_payload['actions'][0]['value'] - - report_id = 'rpKJGi7nRzMF' - - trigger_id = slack_payload['trigger_id'] + loading_modal = expense_messages.expense_form_loading_modal(title='Report Details', loading_message='Loading report details :open_file_folder: ') slack_client = get_slack_client(team_id) - fyle_expense = FyleExpense(user) - - expense_query_params = { - 'offset': 0, - 'limit': '30', - 'order': 'created_at.desc', - 'report_id': 'eq.{}'.format(report_id) - } - - expenses = fyle_expense.get_expenses(query_params=expense_query_params) - - report_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(report_id) - } - - report = fyle_expense.get_reports(query_params=report_query_params) + report_id = slack_payload['actions'][0]['value'] - add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(user, report=report['data'][0], expenses=expenses['data']) + report_id = 'rpKJGi7nRzMF' - add_expense_to_report_dialog['private_metadata'] = report_id + response = slack_client.views_open(view=loading_modal, trigger_id=slack_payload['trigger_id']) - slack_client.views_open(trigger_id=trigger_id, user=user_id, view=add_expense_to_report_dialog) + async_task( + 'fyle_slack_app.slack.interactives.tasks.handle_submit_report_dialog', + user, + team_id, + report_id, + response['view']['id'] + ) return JsonResponse({}) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index e9b6cd63..4f4c296d 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -5,6 +5,7 @@ from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form +from fyle_slack_app.slack.ui.expenses import messages as expense_messages def get_additional_currency_details(amount: int, home_currency: str, selected_currency: str, exchange_rate: float) -> Dict: @@ -219,3 +220,34 @@ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, ) slack_client.views_update(view=expense_form, view_id=view_id) + + +def handle_submit_report_dialog(user: User, team_id: str, report_id: str, view_id: str): + + slack_client = get_slack_client(team_id) + + fyle_expense = FyleExpense(user) + + expense_query_params = { + 'offset': 0, + 'limit': '30', + 'order': 'created_at.desc', + 'report_id': 'eq.{}'.format(report_id) + } + + expenses = fyle_expense.get_expenses(query_params=expense_query_params) + + report_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(report_id) + } + + report = fyle_expense.get_reports(query_params=report_query_params) + + add_expense_to_report_dialog = expense_messages.get_view_report_details_dialog(user, report=report['data'][0], expenses=expenses['data']) + + add_expense_to_report_dialog['private_metadata'] = report_id + + slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) From ab02b34191731aaa2b484c11292e7d329609a2fb Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Fri, 22 Oct 2021 09:09:50 +0530 Subject: [PATCH 49/85] indent fix --- fyle_slack_app/slack/ui/dashboard/messages.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/fyle_slack_app/slack/ui/dashboard/messages.py b/fyle_slack_app/slack/ui/dashboard/messages.py index 6d9bdd40..dd39ee7b 100644 --- a/fyle_slack_app/slack/ui/dashboard/messages.py +++ b/fyle_slack_app/slack/ui/dashboard/messages.py @@ -4,12 +4,16 @@ def get_pre_authorization_message(user_name: str, fyle_oauth_url: str) -> Dict: - pre_authorization_message_blocks = messages.get_pre_authorization_message( - user_name, fyle_oauth_url - ) - return {'type': 'home', 'blocks': pre_authorization_message_blocks} + pre_authorization_message_blocks = messages.get_pre_authorization_message(user_name, fyle_oauth_url) + return { + 'type': 'home', + 'blocks': pre_authorization_message_blocks + } def get_post_authorization_message() -> Dict: post_authorization_message_blocks = messages.get_post_authorization_message() - return {'type': 'home', 'blocks': post_authorization_message_blocks} + return { + 'type': 'home', + 'blocks': post_authorization_message_blocks + } From 9ef0a72abfb154d6e24b6187904a460e61ec9a09 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 26 Oct 2021 12:27:00 +0530 Subject: [PATCH 50/85] Minor changes --- .../interactives/view_submission_handlers.py | 5 +- fyle_slack_app/slack/ui/expenses/messages.py | 308 ++++++++++-------- 2 files changed, 170 insertions(+), 143 deletions(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index f1141d26..818999b8 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -85,8 +85,9 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) slack_client = slack_utils.get_slack_client(team_id) - expense_id = 'txCCVGvNpDMM' - # expense_id = 'tx0mjvrfuizk' + # expense_id = 'txCCVGvNpDMM' + expense_id = 'tx0mjvrfuizk' + # expense_id = 'txjNT3H5dTw1' fyle_expense = FyleExpense(user) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index b797c1df..3167fd50 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -19,7 +19,7 @@ def get_custom_field_value(custom_fields: List, action_id: str) -> Any: # pylint: disable=too-many-branches # is_additional_field is for fields which are not custom fields but are part of a specific categories -def generate_field_ui(field_details: Dict, is_additional_field: bool = False, expense: Dict = None) -> Dict: +def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = False, expense: Dict = None) -> Dict: block_id = '{}_block'.format(field_details['column_name']) action_id = field_details['column_name'] @@ -713,7 +713,7 @@ def expense_dialog_form( if field['is_custom'] is False: is_additional_field = True - custom_field = generate_field_ui(field, is_additional_field=is_additional_field, expense=expense) + custom_field = generate_custom_fields_ui(field, is_additional_field=is_additional_field, expense=expense) if custom_field is not None: view['blocks'].append(custom_field) @@ -741,10 +741,79 @@ def expense_dialog_form( return view -def view_expense_message(expense: Dict, user: User) -> Dict: +def get_expense_message_details_section(expense: Dict, expense_url: str, actions: List[Dict], receipt_message: str, report_message: str) -> List[Dict]: spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') + expense_message_details_section = [ + { + 'type': 'section', + 'block_id': expense['id'], + 'text': { + 'type': 'mrkdwn', + 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) + }, + 'accessory': { + 'type': 'overflow', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': ':pencil: Edit', + 'emoji': True + }, + 'value': 'edit_expense_accessory.{}'.format(expense['id']) + }, + { + 'text': { + 'type': 'plain_text', + 'text': ':arrow_upper_right: Open in Fyle', + 'emoji': True + }, + 'url': expense_url, + 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) + } + ], + 'action_id': 'expense_accessory' + } + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Date of spend: * \n {}'.format(spent_at) + }, + { + 'type': 'mrkdwn', + 'text': '*Receipt: * \n {}'.format(receipt_message) + } + ] + }, + { + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Report: * \n {}'.format(report_message) + }, + { + 'type': 'mrkdwn', + 'text': '*Expense Details: * \n Travel to Office (Uber)' + } + ] + }, + { + 'type': 'actions', + 'elements': actions + } + ] + + return expense_message_details_section + + +def view_expense_message(expense: Dict, user: User) -> Dict: + actions = [] receipt_message = ':x: Not Attached' @@ -833,48 +902,40 @@ def view_expense_message(expense: Dict, user: User) -> Dict: expense_url = fyle_utils.get_fyle_resource_url(user.fyle_refresh_token, expense, 'EXPENSE') - view_expense_blocks = [ + view_expense_blocks = get_expense_message_details_section(expense, expense_url, actions, receipt_message, report_message) + + return view_expense_blocks + + +def get_expense_details_block(expense: Dict, receipt_message: str, report_message: str) -> List[Dict]: + expense_details = [ { 'type': 'section', - 'block_id': expense['id'], 'text': { 'type': 'mrkdwn', - 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) + 'text': '\n' }, - 'accessory': { - 'type': 'overflow', - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': ':pencil: Edit', - 'emoji': True - }, - 'value': 'edit_expense_accessory.{}'.format(expense['id']) - }, - { - 'text': { - 'type': 'plain_text', - 'text': ':arrow_upper_right: Open in Fyle', - 'emoji': True - }, - 'url': expense_url, - 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) - } - ], - 'action_id': 'expense_accessory' - } + }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': ':page_facing_up: *Expense Details*' + }, + }, + { + 'type': 'divider' }, { 'type': 'section', 'fields': [ { 'type': 'mrkdwn', - 'text': '*Date of spend: * \n {}'.format(spent_at) + 'text': '*Amount* \n {} {}'.format(expense['currency'], expense['amount']) }, { 'type': 'mrkdwn', - 'text': '*Receipt: * \n {}'.format(receipt_message) + 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) } ] }, @@ -883,21 +944,33 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'fields': [ { 'type': 'mrkdwn', - 'text': '*Report: * \n {}'.format(report_message) + 'text': '*Report* \n {}'.format(report_message) }, { 'type': 'mrkdwn', - 'text': '*Expense Details: * \n Travel to Office (Uber)' + 'text': '*Receipt* \n {}'.format(receipt_message) } ] }, { - 'type': 'actions', - 'elements': actions + 'type': 'section', + 'fields': [ + { + 'type': 'mrkdwn', + 'text': '*Category* \n {}'.format(expense['category']['name']) + }, + { + 'type': 'mrkdwn', + 'text': '*Project* \n {}'.format(expense['project']['name']) + } + ] + }, + { + 'type': 'divider' } ] - return view_expense_blocks + return expense_details def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) -> Dict: @@ -938,47 +1011,74 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - add_to_report_dialog['blocks'] = add_to_report_blocks - expense_details_block = [ - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': '\n' - }, - }, + expense_details_block = get_expense_details_block(expense, receipt_message, report_message) + + add_to_report_dialog['blocks'].extend(expense_details_block) + + return add_to_report_dialog + + + +def get_minimal_expense_details(expense: Dict, expense_url: str, receipt_message: str) -> List[Dict]: + minimal_expense_details = [ { 'type': 'section', 'text': { 'type': 'mrkdwn', - 'text': ':page_facing_up: *Expense Details*' + 'text': '*{} ({})* expense of :money_with_wings: *{} {}*'.format(expense['purpose'], expense['merchant'], expense['currency'], str(expense['amount'])) }, - }, - { - 'type': 'divider' + 'accessory': { + 'type': 'overflow', + 'options': [ + { + 'text': { + 'type': 'plain_text', + 'text': ':arrow_upper_right: Open in Fyle', + 'emoji': True + }, + 'url': expense_url, + 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) + } + ], + 'action_id': 'expense_accessory' + } }, { 'type': 'section', 'fields': [ { 'type': 'mrkdwn', - 'text': '*Amount* \n {} {}'.format(expense['currency'], expense['amount']) + 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) }, { 'type': 'mrkdwn', - 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) + 'text': '*Receipt* \n {}'.format(receipt_message) } ] }, + { + 'type': 'divider' + } + ] + + return minimal_expense_details + + +def get_report_details_section(report: Dict) -> List[Dict]: + report_details_section = [ + { + 'type': 'divider' + }, { 'type': 'section', 'fields': [ { 'type': 'mrkdwn', - 'text': '*Report* \n {}'.format(report_message) + 'text': '*Report Name* \n {}'.format(report['purpose']) }, { 'type': 'mrkdwn', - 'text': '*Receipt* \n {}'.format(receipt_message) + 'text': '*Amount* \n {} {}'.format(report['currency'], str(report['amount'])) } ] }, @@ -987,23 +1087,26 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - 'fields': [ { 'type': 'mrkdwn', - 'text': '*Category* \n {}'.format(expense['category']['name']) + 'text': '*Expenses* \n {}'.format(str(report['num_expenses'])) }, { 'type': 'mrkdwn', - 'text': '*Project* \n {}'.format(expense['project']['name']) + 'text': '*Created On* \n {}'.format(utils.get_formatted_datetime(report['created_at'], '%B %d, %Y')) } ] }, + { + 'type': 'section', + 'text': { + 'type': 'mrkdwn', + 'text': ':page_facing_up: *Expenses*' + } + }, { 'type': 'divider' } ] - - add_to_report_dialog['blocks'].extend(expense_details_block) - - return add_to_report_dialog - + return report_details_section def get_view_report_details_dialog(user: User, report: Dict, expenses: List[Dict]) -> Dict: @@ -1025,50 +1128,12 @@ def get_view_report_details_dialog(user: User, report: Dict, expenses: List[Dict 'type': 'plain_text', 'text': 'Cancel', 'emoji': True - }, - 'blocks': [ - { - 'type': 'divider' - }, - { - 'type': 'section', - 'fields': [ - { - 'type': 'mrkdwn', - 'text': '*Report Name* \n {}'.format(report['purpose']) - }, - { - 'type': 'mrkdwn', - 'text': '*Amount* \n {} {}'.format(report['currency'], str(report['amount'])) - } - ] - }, - { - 'type': 'section', - 'fields': [ - { - 'type': 'mrkdwn', - 'text': '*Expenses* \n {}'.format(str(report['num_expenses'])) - }, - { - 'type': 'mrkdwn', - 'text': '*Created On* \n {}'.format(utils.get_formatted_datetime(report['created_at'], '%B %d, %Y')) - } - ] - }, - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': ':page_facing_up: *Expenses*' - } - }, - { - 'type': 'divider' - }, - ] + } } + view_report_dialog['blocks'] = [] + view_report_dialog['blocks'] = get_report_details_section(report) + expenses_list = [] for expense in expenses: @@ -1078,46 +1143,7 @@ def get_view_report_details_dialog(user: User, report: Dict, expenses: List[Dict if len(expense['file_ids']) > 0: receipt_message = ':white_check_mark: Attached' - minimal_expense_detail = [ - { - 'type': 'section', - 'text': { - 'type': 'mrkdwn', - 'text': '*{} ({})* expense of :money_with_wings: *{} {}*'.format(expense['purpose'], expense['merchant'], expense['currency'], str(expense['amount'])) - }, - 'accessory': { - 'type': 'overflow', - 'options': [ - { - 'text': { - 'type': 'plain_text', - 'text': ':arrow_upper_right: Open in Fyle', - 'emoji': True - }, - 'url': expense_url, - 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) - } - ], - 'action_id': 'expense_accessory' - } - }, - { - 'type': 'section', - 'fields': [ - { - 'type': 'mrkdwn', - 'text': '*Date of Spend* \n {}'.format(utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y')) - }, - { - 'type': 'mrkdwn', - 'text': '*Receipt* \n {}'.format(receipt_message) - } - ] - }, - { - 'type': 'divider' - } - ] + minimal_expense_detail = get_minimal_expense_details(expense, expense_url, receipt_message) expenses_list.extend(minimal_expense_detail) view_report_dialog['blocks'].extend(expenses_list) From 4054ac561b675fb65048e23f22fc90a7526ab8cd Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 26 Oct 2021 13:31:42 +0530 Subject: [PATCH 51/85] Removing custom fields when project is selected --- fyle_slack_app/slack/interactives/tasks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 4f4c296d..16ffcc6a 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -55,6 +55,9 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) + # Removing custom fields when project is selected + current_expense_form_details['custom_fields'] = None + private_metadata = current_expense_form_details['private_metadata'] current_expense_form_details['selected_project'] = project From 9c5a61920cb9559af176a66dc47ef9bba8a1a79c Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 26 Oct 2021 14:36:53 +0530 Subject: [PATCH 52/85] added separate func for project and cost center availability --- fyle_slack_app/fyle/expenses/views.py | 78 +++++++++++++++------------ 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 223998a0..48fba876 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -67,14 +67,44 @@ def get_reports(self, query_params: Dict) -> Dict: return self.connection.v1.fyler.reports.list(query_params=query_params) + def check_project_availability(self) -> bool: + projects_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + projects = self.get_projects(projects_query_params) + + is_project_available = True if projects['count'] > 0 else False + + return is_project_available + + + def check_cost_center_availability(self) -> bool: + cost_centers_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True) + } + + cost_centers = self.get_cost_centers(cost_centers_query_params) + + is_cost_center_available = True if cost_centers['count'] > 0 else False + + return is_cost_center_available + + @staticmethod def get_currencies(): return ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] @staticmethod - def get_expense_fields_type_mandatory_mapping(expense_fields: List[Dict]) -> Dict: - mandatory_mapping = { + def get_expense_fields_mandatory_mapping(expense_fields: List[Dict]) -> Dict: + mandatory_fields_mapping = { 'purpose': False, 'txn_dt': False, 'vendor_id': False, @@ -83,10 +113,10 @@ def get_expense_fields_type_mandatory_mapping(expense_fields: List[Dict]) -> Dic } for field in expense_fields['data']: - if field['column_name'] in mandatory_mapping: - mandatory_mapping[field['column_name']] = field['is_mandatory'] + if field['column_name'] in mandatory_fields_mapping: + mandatory_fields_mapping[field['column_name']] = field['is_mandatory'] - return mandatory_mapping + return mandatory_fields_mapping @staticmethod @@ -100,46 +130,24 @@ def get_expense_form_details(user: User) -> Dict: default_expense_fields = fyle_expense.get_default_expense_fields() - field_type_mandatory_mapping = fyle_expense.get_expense_fields_type_mandatory_mapping(default_expense_fields) - - is_project_available = False - is_cost_center_available = False - - projects_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } - - projects = fyle_expense.get_projects(projects_query_params) + mandatory_fields_mapping = fyle_expense.get_expense_fields_mandatory_mapping(default_expense_fields) - is_project_available = True if projects['count'] > 0 else False - - cost_centers_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'is_enabled': 'eq.{}'.format(True) - } - - cost_centers = fyle_expense.get_cost_centers(cost_centers_query_params) - - is_cost_center_available = True if cost_centers['count'] > 0 else False + is_project_available = fyle_expense.check_project_availability() + is_cost_center_available = fyle_expense.check_cost_center_availability() # Create a expense fields render property and set them optional in the form fields_render_property = { 'project': { 'is_project_available': is_project_available, - 'is_mandatory': field_type_mandatory_mapping['project_id'] + 'is_mandatory': mandatory_fields_mapping['project_id'] }, 'cost_center': { 'is_cost_center_available': is_cost_center_available, - 'is_mandatory': field_type_mandatory_mapping['cost_center_id'] + 'is_mandatory': mandatory_fields_mapping['cost_center_id'] }, - 'purpose': field_type_mandatory_mapping['purpose'], - 'transaction_date': field_type_mandatory_mapping['txn_dt'], - 'vendor': field_type_mandatory_mapping['vendor_id'] + 'purpose': mandatory_fields_mapping['purpose'], + 'transaction_date': mandatory_fields_mapping['txn_dt'], + 'vendor': mandatory_fields_mapping['vendor_id'] } additional_currency_details = { From a7f9994facf9bd21eb102a47b22e4c0ce3ebcb65 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 3 Nov 2021 16:49:04 +0530 Subject: [PATCH 53/85] resolved PR comments --- fyle_slack_app/slack/ui/expenses/messages.py | 116 +++---------------- 1 file changed, 15 insertions(+), 101 deletions(-) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 3167fd50..63f9a622 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -80,7 +80,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'label': { 'type': 'plain_text', 'text': '{}'.format(field_details['field_name']), - 'emoji': True, }, 'block_id': block_id, 'optional': not field_details['is_mandatory'], @@ -89,7 +88,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'placeholder': { 'type': 'plain_text', 'text': '{}'.format(field_details['placeholder']), - 'emoji': True, }, 'action_id': action_id, } @@ -103,7 +101,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'text': { 'type': 'plain_text', 'text': option, - 'emoji': True, }, 'value': option, } @@ -115,7 +112,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'text': { 'type': 'plain_text', 'text': custom_field_value, - 'emoji': True, }, 'value': custom_field_value, } @@ -127,7 +123,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'text': { 'type': 'plain_text', 'text': value, - 'emoji': True, }, 'value': value, } @@ -140,7 +135,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'text': { 'type': 'plain_text', 'text': '{}'.format(field_details['field_name']), - 'emoji': True, } } custom_field = { @@ -155,7 +149,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'label': { 'type': 'plain_text', 'text': '{}'.format(field_details['field_name']), - 'emoji': True, } } @@ -172,14 +165,12 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'placeholder': { 'type': 'plain_text', 'text': '{}'.format(field_details['placeholder']), - 'emoji': True, }, 'action_id': 'spent_at', }, 'label': { 'type': 'plain_text', 'text': '{}'.format(field_details['field_name']), - 'emoji': True } } @@ -202,20 +193,18 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None, expe 'placeholder': { 'type': 'plain_text', 'text': 'Select Currency', - 'emoji': True, }, 'min_query_length': 1, 'initial_option': { 'text': { 'type': 'plain_text', 'text': additional_currency_details['home_currency'], - 'emoji': True, }, 'value': additional_currency_details['home_currency'], }, 'action_id': 'currency', }, - 'label': {'type': 'plain_text', 'text': 'Currency', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Currency'}, } if expense is not None: @@ -234,11 +223,10 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None, expe 'placeholder': { 'type': 'plain_text', 'text': 'Enter Amount', - 'emoji': True, }, 'action_id': 'amount', }, - 'label': {'type': 'plain_text', 'text': 'Amount', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Amount'}, } if expense is not None: @@ -284,11 +272,10 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None, expe 'placeholder': { 'type': 'plain_text', 'text': 'Enter Total Amount {}'.format(additional_currency_details['home_currency']), - 'emoji': True, }, 'action_id': 'foreign_amount', }, - 'label': {'type': 'plain_text', 'text': 'Total Amount', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Total Amount'}, } if int(additional_currency_details['total_amount']) != 0: @@ -312,11 +299,10 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: 'placeholder': { 'type': 'plain_text', 'text': 'Select a date', - 'emoji': True, }, 'action_id': 'spent_at', }, - 'label': {'type': 'plain_text', 'text': 'Date of Spend', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Date of Spend'}, } if expense is not None: @@ -332,11 +318,10 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: 'placeholder': { 'type': 'plain_text', 'text': 'Eg. Client Meeting', - 'emoji': True, }, 'action_id': 'purpose', }, - 'label': {'type': 'plain_text', 'text': 'Purpose', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Purpose'}, } if expense is not None: @@ -344,47 +329,6 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: default_fields_blocks.append(purpose_block) - paid_by_me_option = { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by me', - 'emoji': True, - }, - 'value': 'true', - } - - paid_by_company_option = { - 'text': { - 'type': 'plain_text', - 'text': 'Paid by company', - 'emoji': True, - }, - 'value': 'false', - } - - payment_mode_block = { - 'type': 'input', - 'block_id': 'SELECT_default_field_payment_mode_block', - 'element': { - 'type': 'static_select', - 'placeholder': { - 'type': 'plain_text', - 'text': 'Select Payment Mode', - 'emoji': True, - }, - 'initial_option': paid_by_me_option, - 'options': [paid_by_me_option, paid_by_company_option], - 'action_id': 'is_reimbursable', - }, - 'label': {'type': 'plain_text', 'text': 'Payment Mode', 'emoji': True}, - } - - if expense is not None: - if expense['is_reimbursable'] is False: - payment_mode_block['element']['initial_option'] = paid_by_company_option - - default_fields_blocks.append(payment_mode_block) - merchant_block = { 'type': 'input', 'block_id': 'TEXT_default_field_merchant_block', @@ -393,11 +337,10 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: 'placeholder': { 'type': 'plain_text', 'text': 'Eg. Uber', - 'emoji': True, }, 'action_id': 'merchant', }, - 'label': {'type': 'plain_text', 'text': 'Merchant', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Merchant'}, } if expense is not None: @@ -421,12 +364,11 @@ def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict 'placeholder': { 'type': 'plain_text', 'text': 'Eg. Travel', - 'emoji': True, }, 'action_id': 'project_id', }, - 'label': {'type': 'plain_text', 'text': 'Project', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Project'}, } if expense is not None: @@ -434,7 +376,6 @@ def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict 'text': { 'type': 'plain_text', 'text': expense['project']['name'], - 'emoji': True, }, 'value': str(expense['project']['id']), } @@ -447,7 +388,6 @@ def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict 'text': { 'type': 'plain_text', 'text': project_display_name, - 'emoji': True, }, 'value': str(selected_project['id']), } @@ -464,13 +404,12 @@ def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict 'text': { 'type': 'plain_text', 'text': 'Billable', - 'emoji': True } } ], 'action_id': 'is_billable' }, - 'label': {'type': 'plain_text', 'text': 'Billable', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Billable'}, } return project_block, billable_block @@ -487,11 +426,10 @@ def get_categories_block(expense: Dict = None) -> Dict: 'placeholder': { 'type': 'plain_text', 'text': 'Eg. Food', - 'emoji': True, }, 'action_id': 'category_id', }, - 'label': {'type': 'plain_text', 'text': 'Category', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Category'}, } if expense is not None: @@ -499,7 +437,6 @@ def get_categories_block(expense: Dict = None) -> Dict: 'text': { 'type': 'plain_text', 'text': expense['category']['name'], - 'emoji': True, }, 'value': str(expense['category']['id']), } @@ -517,11 +454,10 @@ def get_cost_centers_block(expense: Dict = None) -> Dict: 'placeholder': { 'type': 'plain_text', 'text': 'Eg. Accounting', - 'emoji': True, }, 'action_id': 'cost_center_id', }, - 'label': {'type': 'plain_text', 'text': 'Cost Center', 'emoji': True}, + 'label': {'type': 'plain_text', 'text': 'Cost Center'}, } if expense is not None: @@ -529,7 +465,6 @@ def get_cost_centers_block(expense: Dict = None) -> Dict: 'text': { 'type': 'plain_text', 'text': expense['cost_center']['name'], - 'emoji': True, }, 'value': str(expense['cost_center']['id']), } @@ -540,8 +475,8 @@ def expense_form_loading_modal(title: str, loading_message: str) -> Dict: loading_modal = { 'type': 'modal', 'callback_id': 'upsert_expense', - 'title': {'type': 'plain_text', 'text': '{}'.format(title), 'emoji': True}, - 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True}, + 'title': {'type': 'plain_text', 'text': '{}'.format(title)}, + 'close': {'type': 'plain_text', 'text': 'Cancel'}, 'blocks': [ { 'type': 'section', @@ -567,7 +502,6 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'text': { 'type': 'plain_text', 'text': 'Add to Existing Report', - 'emoji': True }, 'value': 'existing_report' } @@ -576,7 +510,6 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'text': { 'type': 'plain_text', 'text': 'Add to New Report', - 'emoji': True }, 'value': 'new_report' } @@ -593,7 +526,6 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'label': { 'type': 'plain_text', 'text': 'Add to Report', - 'emoji': True } } blocks.append(add_to_report_block) @@ -610,14 +542,12 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'placeholder': { 'type': 'plain_text', 'text': 'Enter Report Name', - 'emoji': True }, 'action_id': 'report_name' }, 'label': { 'type': 'plain_text', 'text': 'Report Name', - 'emoji': True } }, 'option': add_to_new_report_option @@ -633,14 +563,12 @@ def get_add_to_report_blocks(add_to_report: str, action_id: str) -> Dict: 'placeholder': { 'type': 'plain_text', 'text': 'Select a Report', - 'emoji': True }, 'action_id': 'existing_report' }, 'label': { 'type': 'plain_text', 'text': 'Select Report', - 'emoji': True } }, 'option': add_to_existing_report_option @@ -671,9 +599,9 @@ def expense_dialog_form( 'type': 'modal', 'callback_id': 'upsert_expense', 'private_metadata': private_metadata, - 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, - 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, - 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True} + 'title': {'type': 'plain_text', 'text': 'Create Expense'}, + 'submit': {'type': 'plain_text', 'text': 'Add Expense'}, + 'close': {'type': 'plain_text', 'text': 'Cancel'} } view['blocks'] = [] @@ -760,7 +688,6 @@ def get_expense_message_details_section(expense: Dict, expense_url: str, actions 'text': { 'type': 'plain_text', 'text': ':pencil: Edit', - 'emoji': True }, 'value': 'edit_expense_accessory.{}'.format(expense['id']) }, @@ -768,7 +695,6 @@ def get_expense_message_details_section(expense: Dict, expense_url: str, actions 'text': { 'type': 'plain_text', 'text': ':arrow_upper_right: Open in Fyle', - 'emoji': True }, 'url': expense_url, 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) @@ -827,7 +753,6 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'text': { 'type': 'plain_text', 'text': 'Attach Receipt', - 'emoji': True, }, 'value': expense['id'], 'action_id': 'attach_receipt' @@ -847,7 +772,6 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'text': { 'type': 'plain_text', 'text': 'Submit Report', - 'emoji': True, }, 'value': expense['report_id'], 'action_id': 'open_submit_report_dialog' @@ -863,7 +787,6 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'text': { 'type': 'plain_text', 'text': 'Add to Report', - 'emoji': True, }, 'value': expense['id'], 'action_id': 'add_expense_to_report' @@ -876,7 +799,6 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'text': { 'type': 'plain_text', 'text': 'Complete Expense', - 'emoji': True, }, 'value': expense['id'], 'action_id': 'edit_expense', @@ -890,7 +812,6 @@ def view_expense_message(expense: Dict, user: User) -> Dict: 'text': { 'type': 'plain_text', 'text': 'View in Fyle', - 'emoji': True, }, 'url': fyle_utils.get_fyle_resource_url(user.fyle_refresh_token, expense, 'EXPENSE'), 'value': expense['id'], @@ -990,12 +911,10 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - 'title': { 'type': 'plain_text', 'text': ':mailbox: Add to Report', - 'emoji': True }, 'submit': { 'type': 'plain_text', 'text': 'Add', - 'emoji': True }, 'type': 'modal', 'callback_id': 'add_expense_to_report', @@ -1003,7 +922,6 @@ def get_add_expense_to_report_dialog(expense: Dict, add_to_report: str = None) - 'close': { 'type': 'plain_text', 'text': 'Cancel', - 'emoji': True }, } @@ -1034,7 +952,6 @@ def get_minimal_expense_details(expense: Dict, expense_url: str, receipt_message 'text': { 'type': 'plain_text', 'text': ':arrow_upper_right: Open in Fyle', - 'emoji': True }, 'url': expense_url, 'value': 'open_in_fyle_accessory.{}'.format(expense['id']) @@ -1117,17 +1034,14 @@ def get_view_report_details_dialog(user: User, report: Dict, expenses: List[Dict 'title': { 'type': 'plain_text', 'text': 'Report Details', - 'emoji': True }, 'submit': { 'type': 'plain_text', 'text': 'Submit Report', - 'emoji': True }, 'close': { 'type': 'plain_text', 'text': 'Cancel', - 'emoji': True } } From 7864c49cdf1ab623d3fb0fb7425f8759ffceb4bf Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 3 Nov 2021 16:59:30 +0530 Subject: [PATCH 54/85] resolved PR comments --- fyle_slack_app/fyle/expenses/views.py | 7 ++-- .../interactives/block_action_handlers.py | 39 ++++++++++--------- .../interactives/block_suggestion_handlers.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 2 +- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 48fba876..6d21be0e 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -23,12 +23,11 @@ def get_expense_fields(self, query_params: Dict) -> Dict: def get_default_expense_fields(self) -> Dict: default_expense_fields_query_params = { 'offset': 0, - 'limit': '20', + 'limit': '50', 'order': 'created_at.desc', 'column_name': 'in.(purpose, txn_dt, vendor_id, cost_center_id, project_id)', 'is_enabled': 'eq.{}'.format(True), - 'is_custom': 'eq.{}'.format(False), - 'is_mandatory': 'eq.{}'.format(True) + 'is_custom': 'eq.{}'.format(False) } return self.get_expense_fields(default_expense_fields_query_params) @@ -37,7 +36,7 @@ def get_default_expense_fields(self) -> Dict: def get_custom_fields_by_category_id(self, category_id: str) -> Dict: custom_fields_query_params = { 'offset': 0, - 'limit': '20', + 'limit': '50', 'order': 'created_at.desc', 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', 'is_enabled': 'eq.{}'.format(True), diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index e59307cb..faa0a942 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -186,15 +186,15 @@ def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) project_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'project_block'), None) project_loading_block = { - 'type': 'context', + 'type': 'context', 'block_id': 'project_loading_block', - 'elements': [ - { - 'type': 'mrkdwn', - 'text': 'Loading categories for this project' - } - ] - } + 'elements': [ + { + 'type': 'mrkdwn', + 'text': 'Loading categories for this project' + } + ] + } blocks.insert(project_block_index + 1, project_loading_block) @@ -233,15 +233,15 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str category_block_index = next((index for (index, d) in enumerate(blocks) if d['block_id'] == 'category_block'), None) category_loading_block = { - 'type': 'context', + 'type': 'context', 'block_id': 'category_loading_block', - 'elements': [ - { - 'type': 'mrkdwn', - 'text': 'Loading additional fields for this category if any' - } - ] - } + 'elements': [ + { + 'type': 'mrkdwn', + 'text': 'Loading additional fields for this category if any' + } + ] + } blocks.insert(category_block_index + 1, category_loading_block) @@ -263,7 +263,7 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str return JsonResponse({}, status=200) - def handle_currency_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + def handle_currency_selection(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: user = utils.get_or_none(User, slack_user_id=user_id) @@ -272,7 +272,7 @@ def handle_currency_select(self, slack_payload: Dict, user_id: str, team_id: str view_id = slack_payload['container']['view_id'] async_task( - 'fyle_slack_app.slack.interactives.tasks.handle_currency_select', + 'fyle_slack_app.slack.interactives.tasks.handle_currency_selection', selected_currency, view_id, team_id, @@ -346,6 +346,7 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s fyle_expense = FyleExpense(user) + # TODO: Clean this up expense_id = 'txCCVGvNpDMM' expense_query_params = { @@ -374,6 +375,7 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i slack_client = get_slack_client(team_id) + # TODO: Clean this up expense_id = 'txCCVGvNpDMM' fyle_expense = FyleExpense(user) @@ -430,6 +432,7 @@ def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id report_id = slack_payload['actions'][0]['value'] + # TODO: Clean this up report_id = 'rpKJGi7nRzMF' response = slack_client.views_open(view=loading_modal, trigger_id=slack_payload['trigger_id']) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index c80dc3e9..f1873551 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -67,7 +67,7 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: category_query_params = { 'offset': 0, - 'limit': '30', + 'limit': '10', 'order': 'display_name.asc', 'display_name': 'ilike.%{}%'.format(category_value_entered), 'system_category': 'not_in.(Unspecified, Per Diem, Mileage, Activity)', diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 16ffcc6a..324b8058 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -108,7 +108,7 @@ def handle_category_select(user: User, team_id: str, category_id: str, view_id: slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) -def handle_currency_select(selected_currency: str, view_id: str, team_id: str, slack_payload: str) -> None: +def handle_currency_selection(selected_currency: str, view_id: str, team_id: str, slack_payload: str) -> None: slack_client = get_slack_client(team_id) From 1adf87ff1569437761cf2b00edb5b180de08f514 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Mon, 8 Nov 2021 13:33:07 +0530 Subject: [PATCH 55/85] Resolved PR comments --- .../interactives/block_action_handlers.py | 14 +++++----- .../interactives/block_suggestion_handlers.py | 28 +++++++------------ fyle_slack_app/slack/interactives/tasks.py | 4 +-- fyle_slack_app/slack/ui/expenses/messages.py | 10 ++++--- fyle_slack_service/sentry.py | 7 +++++ 5 files changed, 32 insertions(+), 31 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index faa0a942..9d1aae05 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -38,9 +38,9 @@ def _initialize_block_action_handlers(self): 'expense_commented_notification_preference': self.handle_notification_preference_selection, 'edit_expense': self.handle_edit_expense, 'attach_receipt': self.handle_attach_receipt, - 'category_id': self.handle_category_select, - 'project_id': self.handle_project_select, - 'currency': self.handle_currency_select, + 'category_id': self.handle_category_selection, + 'project_id': self.handle_project_selection, + 'currency': self.handle_currency_selection, 'amount': self.handle_amount_entered, 'add_to_report': self.handle_add_to_report, 'add_expense_to_report': self.handle_add_expense_to_report, @@ -169,7 +169,7 @@ def handle_expense_accessory(self, slack_payload: Dict, user_id: str, team_id: s return JsonResponse({}) - def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + def handle_project_selection(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: project_id = slack_payload['actions'][0]['selected_option']['value'] @@ -205,7 +205,7 @@ def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) slack_client.views_update(view_id=view_id, view=current_view) async_task( - 'fyle_slack_app.slack.interactives.tasks.handle_project_select', + 'fyle_slack_app.slack.interactives.tasks.handle_project_selection', user, team_id, project_id, @@ -216,7 +216,7 @@ def handle_project_select(self, slack_payload: Dict, user_id: str, team_id: str) return JsonResponse({}) - def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: + def handle_category_selection(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: category_id = slack_payload['actions'][0]['selected_option']['value'] @@ -252,7 +252,7 @@ def handle_category_select(self, slack_payload: Dict, user_id: str, team_id: str slack_client.views_update(view_id=view_id, view=current_view) async_task( - 'fyle_slack_app.slack.interactives.tasks.handle_category_select', + 'fyle_slack_app.slack.interactives.tasks.handle_category_selection', user, team_id, category_id, diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index f1873551..2c79bf73 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -2,6 +2,8 @@ from django.http import JsonResponse +from fyle_slack_service.sentry import Sentry + from fyle_slack_app.models.users import User from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.libs import logger, utils @@ -35,6 +37,9 @@ def _handle_invalid_block_suggestions(self, slack_payload: Dict, user_id: str, t channel=user_dm_channel_id, text='Looks like something went wrong :zipper_mouth_face: \n Please try again' ) + + Sentry.capture_exception('Invalid block suggestion -> {}'.format(slack_payload['action_id'])) + return JsonResponse({}, status=200) @@ -89,15 +94,10 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: if suggested_categories['count'] > 0: for category in suggested_categories['data']: - category_display_name = category['display_name'] - if category['name'] == category['sub_category']: - category_display_name = category['name'] - option = { 'text': { 'type': 'plain_text', - 'text': category_display_name, - 'emoji': True, + 'text': category['display_name'] }, 'value': str(category['id']), } @@ -118,8 +118,7 @@ def handle_currency_suggestion(self, slack_payload: Dict, user_id: str, team_id: option = { 'text': { 'type': 'plain_text', - 'text': currency, - 'emoji': True, + 'text': currency }, 'value': currency, } @@ -147,15 +146,10 @@ def handle_project_suggestion(self, slack_payload: Dict, user_id: str, team_id: if suggested_projects['count'] > 0: for project in suggested_projects['data']: - project_display_name = project['display_name'] - if project['name'] == project['sub_project']: - project_display_name = project['name'] - option = { 'text': { 'type': 'plain_text', - 'text': project_display_name, - 'emoji': True, + 'text': project['display_name'] }, 'value': str(project['id']), } @@ -185,8 +179,7 @@ def handle_cost_center_suggestion(self, slack_payload: Dict, user_id: str, team_ option = { 'text': { 'type': 'plain_text', - 'text': cost_center['name'], - 'emoji': True, + 'text': cost_center['name'] }, 'value': str(cost_center['id']), } @@ -224,8 +217,7 @@ def handle_existing_report_suggestion(self, slack_payload: Dict, user_id: str, t option = { 'text': { 'type': 'plain_text', - 'text': report_display_text, - 'emoji': True, + 'text': report_display_text }, 'value': str(report['id']), } diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 324b8058..ea320e2e 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -27,7 +27,7 @@ def get_additional_currency_details(amount: int, home_currency: str, selected_cu return additional_currency_details -def handle_project_select(user: User, team_id: str, project_id: str, view_id: str, slack_payload: Dict) -> None: +def handle_project_selection(user: User, team_id: str, project_id: str, view_id: str, slack_payload: Dict) -> None: slack_client = get_slack_client(team_id) @@ -83,7 +83,7 @@ def handle_project_select(user: User, team_id: str, project_id: str, view_id: st slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) -def handle_category_select(user: User, team_id: str, category_id: str, view_id: str, slack_payload: str) -> None: +def handle_category_selection(user: User, team_id: str, category_id: str, view_id: str, slack_payload: str) -> None: slack_client = get_slack_client(team_id) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 63f9a622..60f4fead 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -880,10 +880,6 @@ def get_expense_details_block(expense: Dict, receipt_message: str, report_messag 'type': 'mrkdwn', 'text': '*Category* \n {}'.format(expense['category']['name']) }, - { - 'type': 'mrkdwn', - 'text': '*Project* \n {}'.format(expense['project']['name']) - } ] }, { @@ -891,6 +887,12 @@ def get_expense_details_block(expense: Dict, receipt_message: str, report_messag } ] + if expense['project'] is not None: + expense_details[5]['fields'].append({ + 'type': 'mrkdwn', + 'text': '*Project* \n {}'.format(expense['project']['name']) + }) + return expense_details diff --git a/fyle_slack_service/sentry.py b/fyle_slack_service/sentry.py index 2f73bd6f..d8e7f58c 100644 --- a/fyle_slack_service/sentry.py +++ b/fyle_slack_service/sentry.py @@ -1,6 +1,7 @@ import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration +from sentry_sdk import capture_exception, capture_message from django.conf import settings @@ -26,3 +27,9 @@ def traces_sampler(sampling_context): return 0 return 0.2 + + + @staticmethod + def capture_exception(message=None): + error = Exception(message) + capture_exception(error) From e706fb54ef9e0c795f0eca907047263c99da13ef Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Tue, 7 Dec 2021 13:06:59 +0530 Subject: [PATCH 56/85] Using cache for storing form metadata --- fyle_slack_app/fyle/expenses/views.py | 30 +++++------ fyle_slack_app/slack/commands/tasks.py | 2 +- .../interactives/block_action_handlers.py | 12 ++--- .../interactives/block_suggestion_handlers.py | 11 ++-- fyle_slack_app/slack/interactives/tasks.py | 51 ++++++++----------- fyle_slack_app/slack/ui/expenses/messages.py | 8 ++- 6 files changed, 50 insertions(+), 64 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index ff18b691..44754a49 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -1,9 +1,10 @@ from typing import Dict, List +from django.core.cache import cache + from fyle.platform.platform import Platform from fyle_slack_app.fyle.utils import get_fyle_sdk_connection -from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.models.users import User from fyle_slack_app.fyle import utils as fyle_utils @@ -119,7 +120,7 @@ def get_expense_fields_mandatory_mapping(expense_fields: List[Dict]) -> Dict: @staticmethod - def get_expense_form_details(user: User) -> Dict: + def get_expense_form_details(user: User, view_id: str) -> Dict: fyle_expense = FyleExpense(user) @@ -155,39 +156,33 @@ def get_expense_form_details(user: User) -> Dict: add_to_report = 'existing_report' - # Caching details in slack private metadata so that they can be reused again in the form without computing them again - private_metadata = { - 'fields_render_property': fields_render_property, - 'additional_currency_details': additional_currency_details, - 'add_to_report': add_to_report - } - expense_form_details = { 'fields_render_property': fields_render_property, - 'private_metadata': encode_state(private_metadata), 'additional_currency_details': additional_currency_details, 'add_to_report': add_to_report } + cache_key = '{}.form_metadata'.format(view_id) + cache.set(cache_key, expense_form_details, 3600) + return expense_form_details @staticmethod def get_current_expense_form_details(slack_payload: Dict) -> Dict: - private_metadata = slack_payload['view']['private_metadata'] - - decoded_private_metadata = decode_state(private_metadata) + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) - fields_render_property = decoded_private_metadata['fields_render_property'] + fields_render_property = form_metadata['fields_render_property'] - additional_currency_details = decoded_private_metadata.get('additional_currency_details') + additional_currency_details = form_metadata.get('additional_currency_details') - add_to_report = decoded_private_metadata.get('add_to_report') + add_to_report = form_metadata.get('add_to_report') current_ui_blocks = slack_payload['view']['blocks'] - project = decoded_private_metadata.get('project') + project = form_metadata.get('project') custom_field_blocks = [] @@ -203,7 +198,6 @@ def get_current_expense_form_details(slack_payload: Dict) -> Dict: 'selected_project': project, 'additional_currency_details': additional_currency_details, 'add_to_report': add_to_report, - 'private_metadata': private_metadata, 'custom_fields': custom_field_blocks } return current_form_details diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index 2596f20f..659a1429 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -97,7 +97,7 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: slack_client = slack_utils.get_slack_client(team_id) - expense_form_details = FyleExpense.get_expense_form_details(user) + expense_form_details = FyleExpense.get_expense_form_details(user, view_id) expense_form = expense_messages.expense_dialog_form( **expense_form_details diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 9d1aae05..f8364765 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -1,5 +1,6 @@ from typing import Callable, Dict +from django.core.cache import cache from django.http import JsonResponse from django_q.tasks import async_task @@ -311,17 +312,14 @@ def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) - private_metadata = current_expense_form_details['private_metadata'] - - decoded_private_metadata = utils.decode_state(private_metadata) + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) current_expense_form_details['add_to_report'] = add_to_report - decoded_private_metadata['add_to_report'] = add_to_report - - encoded_private_metadata = utils.encode_state(decoded_private_metadata) + form_metadata['add_to_report'] = add_to_report - current_expense_form_details['private_metadata'] = encoded_private_metadata + cache.set(cache_key, form_metadata) expense_form = expense_messages.expense_dialog_form( **current_expense_form_details diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 2c79bf73..e6952ae3 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -1,5 +1,6 @@ from typing import Dict, List +from django.core.cache import cache from django.http import JsonResponse from fyle_slack_service.sentry import Sentry @@ -79,11 +80,15 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: 'is_enabled': 'eq.{}'.format(True) } - private_metadata = slack_payload['view']['private_metadata'] + # expense_processing_details = ExpenseProcessingDetails.objects.get( + # slack_view_id=slack_payload['view']['id'] + # ) - decoded_private_metadata = utils.decode_state(private_metadata) + # form_metadata = expense_processing_details.form_metadata + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) - project = decoded_private_metadata.get('project') + project = form_metadata.get('project') if project is not None: category_query_params['id'] = 'in.{}'.format(tuple(project['category_ids'])) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index ea320e2e..a8f9c322 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,9 +1,10 @@ from typing import Dict, List +from django.core.cache import cache + from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_client -from fyle_slack_app.libs.utils import decode_state, encode_state from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form from fyle_slack_app.slack.ui.expenses import messages as expense_messages @@ -55,11 +56,12 @@ def handle_project_selection(user: User, team_id: str, project_id: str, view_id: current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) + # Removing custom fields when project is selected current_expense_form_details['custom_fields'] = None - private_metadata = current_expense_form_details['private_metadata'] - current_expense_form_details['selected_project'] = project current_ui_blocks = slack_payload['view']['blocks'] @@ -68,13 +70,9 @@ def handle_project_selection(user: User, team_id: str, project_id: str, view_id: project_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'project_loading_block'), None) current_ui_blocks.pop(project_loading_block_index) - decoded_private_metadata = decode_state(private_metadata) - - decoded_private_metadata['project'] = project - - encoded_private_metadata = encode_state(decoded_private_metadata) + form_metadata['project'] = project - current_expense_form_details['private_metadata'] = encoded_private_metadata + cache.set(cache_key, form_metadata) new_expense_dialog_form = expense_dialog_form( **current_expense_form_details @@ -114,9 +112,8 @@ def handle_currency_selection(selected_currency: str, view_id: str, team_id: str current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) - private_metadata = current_expense_form_details['private_metadata'] - - decoded_private_metadata = decode_state(private_metadata) + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) additional_currency_details = current_expense_form_details['additional_currency_details'] @@ -134,11 +131,9 @@ def handle_currency_selection(selected_currency: str, view_id: str, team_id: str current_expense_form_details['additional_currency_details'] = additional_currency_details - decoded_private_metadata['additional_currency_details'] = additional_currency_details - - encoded_private_metadata = encode_state(decoded_private_metadata) + form_metadata['additional_currency_details'] = additional_currency_details - current_expense_form_details['private_metadata'] = encoded_private_metadata + cache.set(cache_key, form_metadata) expense_form = expense_dialog_form( **current_expense_form_details @@ -157,9 +152,8 @@ def handle_amount_entered(amount_entered: float, view_id: str, team_id: str, sla current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) - private_metadata = current_expense_form_details['private_metadata'] - - decoded_private_metadata = decode_state(private_metadata) + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) exchange_rate = 70.12 @@ -169,11 +163,9 @@ def handle_amount_entered(amount_entered: float, view_id: str, team_id: str, sla current_expense_form_details['additional_currency_details'] = additional_currency_details - decoded_private_metadata['additional_currency_details'] = additional_currency_details + form_metadata['additional_currency_details'] = additional_currency_details - encoded_private_metadata = encode_state(decoded_private_metadata) - - current_expense_form_details['private_metadata'] = encoded_private_metadata + cache.set(cache_key, form_metadata) expense_form = expense_dialog_form( **current_expense_form_details @@ -203,18 +195,17 @@ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, custom_fields = fyle_expense.get_custom_fields_by_category_id(expense['category_id']) - expense_form_details = FyleExpense.get_expense_form_details(user) + expense_form_details = FyleExpense.get_expense_form_details(user, view_id) - private_metadata = decode_state(expense_form_details['private_metadata']) + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) # Add additional metadata to differentiate create and edit expense # message_ts to update message in edit case - private_metadata['expense_id'] = expense_id - private_metadata['message_ts'] = slack_payload['container']['message_ts'] - - encoded_metadata = encode_state(private_metadata) + form_metadata['expense_id'] = expense_id + form_metadata['message_ts'] = slack_payload['container']['message_ts'] - expense_form_details['private_metadata'] = encoded_metadata + cache.set(cache_key, form_metadata) expense_form = expense_dialog_form( expense=expense, diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 60f4fead..847c62a3 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -591,17 +591,15 @@ def expense_dialog_form( custom_fields: Dict = None, additional_currency_details: Dict = None, add_to_report: str = None, - private_metadata: str = None, expense : Dict = None ) -> Dict: view = { 'type': 'modal', 'callback_id': 'upsert_expense', - 'private_metadata': private_metadata, - 'title': {'type': 'plain_text', 'text': 'Create Expense'}, - 'submit': {'type': 'plain_text', 'text': 'Add Expense'}, - 'close': {'type': 'plain_text', 'text': 'Cancel'} + 'title': {'type': 'plain_text', 'text': 'Create Expense', 'emoji': True}, + 'submit': {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True}, + 'close': {'type': 'plain_text', 'text': 'Cancel', 'emoji': True} } view['blocks'] = [] From c74ee0df1fd9c2bcae9fb0500d755a2a502c58e4 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 8 Dec 2021 12:44:45 +0530 Subject: [PATCH 57/85] Merged exchange rate api --- fyle_slack_app/fyle/expenses/views.py | 10 ++++++++++ .../interactives/block_action_handlers.py | 2 ++ fyle_slack_app/slack/interactives/tasks.py | 18 +++++++++++------- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 44754a49..3d6d64f3 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -1,5 +1,7 @@ from typing import Dict, List +import datetime + from django.core.cache import cache from fyle.platform.platform import Platform @@ -67,6 +69,14 @@ def get_reports(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.reports.list(query_params=query_params) + def get_exchange_rate(self, from_currency: str, to_currency: str) -> Dict: + current_date = datetime.datetime.today().strftime('%Y-%m-%d') + exchange_rate = self.connection.v1beta.common.currencies_exchange_rate.get( + from_currency, to_currency, current_date + ) + return exchange_rate['data']['exchange_rate'] + + def check_project_availability(self) -> bool: projects_query_params = { 'offset': 0, diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index f8364765..375ea7bd 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -274,6 +274,7 @@ def handle_currency_selection(self, slack_payload: Dict, user_id: str, team_id: async_task( 'fyle_slack_app.slack.interactives.tasks.handle_currency_selection', + user, selected_currency, view_id, team_id, @@ -293,6 +294,7 @@ def handle_amount_entered(self, slack_payload: Dict, user_id: str, team_id: str) async_task( 'fyle_slack_app.slack.interactives.tasks.handle_amount_entered', + user, amount_entered, view_id, team_id, diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index a8f9c322..b23e6ffb 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -106,11 +106,13 @@ def handle_category_selection(user: User, team_id: str, category_id: str, view_i slack_client.views_update(view_id=view_id, view=new_expense_dialog_form) -def handle_currency_selection(selected_currency: str, view_id: str, team_id: str, slack_payload: str) -> None: +def handle_currency_selection(user: User, selected_currency: str, view_id: str, team_id: str, slack_payload: str) -> None: slack_client = get_slack_client(team_id) - current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) + fyle_expense = FyleExpense(user) + + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) @@ -125,7 +127,7 @@ def handle_currency_selection(selected_currency: str, view_id: str, team_id: str if home_currency != selected_currency: form_current_state = slack_payload['view']['state']['values'] - exchange_rate = 70.12 + exchange_rate = fyle_expense.get_exchange_rate(selected_currency, home_currency) amount = form_current_state['NUMBER_default_field_amount_block']['amount']['value'] additional_currency_details = get_additional_currency_details(amount, home_currency, selected_currency, exchange_rate) @@ -142,23 +144,25 @@ def handle_currency_selection(selected_currency: str, view_id: str, team_id: str slack_client.views_update(view_id=view_id, view=expense_form) -def handle_amount_entered(amount_entered: float, view_id: str, team_id: str, slack_payload: str) -> None: +def handle_amount_entered(user: User, amount_entered: float, view_id: str, team_id: str, slack_payload: str) -> None: slack_client = get_slack_client(team_id) + fyle_expense = FyleExpense(user) + form_current_state = slack_payload['view']['state']['values'] selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] - current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) - exchange_rate = 70.12 - home_currency = current_expense_form_details['additional_currency_details']['home_currency'] + exchange_rate = fyle_expense.get_exchange_rate(selected_currency, home_currency) + additional_currency_details = get_additional_currency_details(amount_entered, home_currency, selected_currency, exchange_rate) current_expense_form_details['additional_currency_details'] = additional_currency_details From 185bb8b9f38288743a9cc37e855f043b36532a7a Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Wed, 15 Dec 2021 11:49:48 +0530 Subject: [PATCH 58/85] Support for location --- fyle_slack_app/fyle/expenses/views.py | 12 ++++ .../interactives/block_suggestion_handlers.py | 60 ++++++++++++++++++- .../interactives/view_submission_handlers.py | 14 ++++- fyle_slack_app/slack/ui/expenses/messages.py | 45 ++++++++++++++ 4 files changed, 127 insertions(+), 4 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 3d6d64f3..6e1820ce 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -69,6 +69,18 @@ def get_reports(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.reports.list(query_params=query_params) + def get_employees(self, query_params: Dict) -> Dict: + return self.connection.v1beta.spender.employees.list(query_params=query_params) + + + def get_places_autocomplete(self, query: str) -> Dict: + return self.connection.v1beta.common.places_autocomplete.list(q=query) + + + def get_place_by_place_id(self, place_id: str) -> Dict: + return self.connection.v1beta.common.places.get_by_id(place_id) + + def get_exchange_rate(self, from_currency: str, to_currency: str) -> Dict: current_date = datetime.datetime.today().strftime('%Y-%m-%d') exchange_rate = self.connection.v1beta.common.currencies_exchange_rate.get( diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index e6952ae3..40da6c5d 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -25,7 +25,9 @@ def _initialize_block_suggestion_handlers(self): 'project_id': self.handle_project_suggestion, 'cost_center_id': self.handle_cost_center_suggestion, 'currency': self.handle_currency_suggestion, - 'existing_report': self.handle_existing_report_suggestion + 'existing_report': self.handle_existing_report_suggestion, + 'user_list': self.handle_user_list_suggestion, + 'places_autocomplete': self.handle_places_autocomplete_suggestion } @@ -229,3 +231,59 @@ def handle_existing_report_suggestion(self, slack_payload: Dict, user_id: str, t report_options.append(option) return report_options + + + def handle_user_list_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + + user = utils.get_or_none(User, slack_user_id=user_id) + user_value_entered = slack_payload['value'] + + fyle_expense = FyleExpense(user) + + query_params = { + 'offset': 0, + 'limit': '10', + 'order': 'email.asc', + 'email': 'ilike.{}%'.format(user_value_entered), + } + + suggested_users = fyle_expense.get_employees(query_params) + + user_options = [] + if suggested_users['count'] > 0: + for user in suggested_users['data']: + option = { + 'text': { + 'type': 'plain_text', + 'text': '{} ({})'.format(user['full_name'], user['email']) + }, + 'value': user['email'], + } + user_options.append(option) + + return user_options + + + def handle_places_autocomplete_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + + user = utils.get_or_none(User, slack_user_id=user_id) + place_value_entered = slack_payload['value'] + + fyle_expense = FyleExpense(user) + + suggested_places = fyle_expense.get_places_autocomplete(query=place_value_entered) + + place_options = [] + + if suggested_places['count'] > 0: + for place in suggested_places['data']: + option = { + 'text': { + 'type': 'plain_text', + 'text': '{}'.format(place['formatted_address']) + }, + 'value': place['id'], + } + place_options.append(option) + + return place_options diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 818999b8..3a5c8d57 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -63,7 +63,7 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) private_metadata = utils.decode_state(slack_payload['view']['private_metadata']) - expense_details, validation_errors = self.extract_form_values_and_validate(form_values) + expense_details, validation_errors = self.extract_form_values_and_validate(user, form_values) expense_id = private_metadata.get('expense_id') @@ -151,11 +151,13 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i return JsonResponse({}) - def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dict]: + def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dict, Dict]: expense_details = {} validation_errors = {} custom_fields = [] + fyle_expense = FyleExpense(user) + for key, value in form_values.items(): custom_field_mappings = {} if 'custom_field' in key: @@ -165,7 +167,13 @@ def extract_form_values_and_validate(self, form_values: Dict) -> Union[Dict, Dic if inner_value['selected_option'] is not None: value = inner_value['selected_option']['value'] - if inner_value['type'] == 'multi_static_select': + if 'LOCATION' in key: + value = fyle_expense.get_place_by_place_id(value) + + if inner_value['type'] in ['multi_static_select', 'multi_external_select']: + + if 'USER_LIST' in key: + _ , inner_key = key.split('__') values_list = [] for val in inner_value['selected_options']: diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 847c62a3..bde06c30 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -177,6 +177,51 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F if custom_field_value is not None: custom_field['element']['initial_date'] = utils.get_formatted_datetime(custom_field_value, '%B %d, %Y') + + elif field_details['type'] == 'USER_LIST': + block_id = '{}__{}'.format(block_id, field_details['field_name']) + custom_field = { + 'type': 'input', + 'label': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + }, + 'block_id': block_id, + 'optional': not field_details['is_mandatory'], + 'element': { + 'min_query_length': 1, + 'type': 'multi_external_select', + 'placeholder': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['placeholder']), + }, + 'action_id': 'user_list', + } + } + + + elif field_details['type'] == 'LOCATION': + block_id = '{}__{}'.format(block_id, field_details['field_name']) + custom_field = { + 'type': 'input', + 'label': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['field_name']), + }, + 'block_id': block_id, + 'optional': not field_details['is_mandatory'], + 'element': { + 'min_query_length': 1, + 'type': 'external_select', + 'placeholder': { + 'type': 'plain_text', + 'text': '{}'.format(field_details['placeholder']), + }, + 'action_id': 'places_autocomplete', + } + } + + return custom_field From fd9c504078fb624b19575c51c78cb3a33894a050 Mon Sep 17 00:00:00 2001 From: Shreyansh Date: Fri, 18 Feb 2022 09:58:54 +0530 Subject: [PATCH 59/85] Added merchant as select field --- fyle_slack_app/fyle/expenses/views.py | 2 + .../interactives/block_suggestion_handlers.py | 42 ++++++++++++++++--- .../interactives/view_submission_handlers.py | 4 +- fyle_slack_app/slack/ui/expenses/messages.py | 5 ++- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 6e1820ce..366b5e11 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -56,6 +56,8 @@ def get_categories(self, query_params: Dict) -> Dict: def get_projects(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.projects.list(query_params=query_params) + def get_merchants(self, query_params: Dict) -> Dict: + return self.connection.v1beta.spender.merchants.list(query_params=query_params) def get_cost_centers(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.cost_centers.list(query_params=query_params) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 40da6c5d..5f47b2b3 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -27,7 +27,8 @@ def _initialize_block_suggestion_handlers(self): 'currency': self.handle_currency_suggestion, 'existing_report': self.handle_existing_report_suggestion, 'user_list': self.handle_user_list_suggestion, - 'places_autocomplete': self.handle_places_autocomplete_suggestion + 'places_autocomplete': self.handle_places_autocomplete_suggestion, + 'merchant': self.handle_merchant_suggestion } @@ -82,11 +83,6 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: 'is_enabled': 'eq.{}'.format(True) } - # expense_processing_details = ExpenseProcessingDetails.objects.get( - # slack_view_id=slack_payload['view']['id'] - # ) - - # form_metadata = expense_processing_details.form_metadata cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) @@ -287,3 +283,37 @@ def handle_places_autocomplete_suggestion(self, slack_payload: Dict, user_id: st place_options.append(option) return place_options + + + def handle_merchant_suggestion(self, slack_payload: Dict, user_id: str, team_id: str) -> List: + + user = utils.get_or_none(User, slack_user_id=user_id) + merchant_value_entered = slack_payload['value'] + + fyle_expense = FyleExpense(user) + + query_params = { + 'offset': 0, + 'limit': '10', + 'order': 'display_name.asc', + 'q': merchant_value_entered + } + + suggested_merchants = fyle_expense.get_merchants(query_params) + + print('suggested_merchants: {}'.format(suggested_merchants)) + + merchant_options = [] + + if suggested_merchants['count'] > 0: + for merchant in suggested_merchants['data']: + option = { + 'text': { + 'type': 'plain_text', + 'text': '{}'.format(merchant['display_name']) + }, + 'value': merchant['display_name'], + } + merchant_options.append(option) + + return merchant_options diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 3a5c8d57..41b1c8ef 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -65,6 +65,8 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) expense_details, validation_errors = self.extract_form_values_and_validate(user, form_values) + print('EXPENSE -> ', json.dumps(expense_details, indent=2)) + expense_id = private_metadata.get('expense_id') message_ts = private_metadata.get('message_ts') @@ -81,8 +83,6 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) 'errors': validation_errors }) - print('EXPENSE -> ', json.dumps(expense_details, indent=2)) - slack_client = slack_utils.get_slack_client(team_id) # expense_id = 'txCCVGvNpDMM' diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index bde06c30..47b760e4 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -376,9 +376,10 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: merchant_block = { 'type': 'input', - 'block_id': 'TEXT_default_field_merchant_block', + 'block_id': 'SELECT_default_field_merchant_block', 'element': { - 'type': 'plain_text_input', + 'type': 'external_select', + 'min_query_length': 1, 'placeholder': { 'type': 'plain_text', 'text': 'Eg. Uber', From 366db3ad14d67ca3c83928f7290670c581c33a29 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Mon, 28 Mar 2022 13:41:16 +0530 Subject: [PATCH 60/85] Changes projects and categories behaviour acc to API changes --- .../slack/interactives/block_suggestion_handlers.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 3 +-- fyle_slack_app/slack/interactives/view_submission_handlers.py | 4 ++-- fyle_slack_service/settings.py | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 5f47b2b3..4064bf8d 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -89,7 +89,7 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: project = form_metadata.get('project') if project is not None: - category_query_params['id'] = 'in.{}'.format(tuple(project['category_ids'])) + category_query_params['restricted_project_ids'] = 'csn.[{}]'.format(project['id']) suggested_categories = fyle_expense.get_categories(category_query_params) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index b23e6ffb..f7fb7165 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -50,8 +50,7 @@ def handle_project_selection(user: User, team_id: str, project_id: str, view_id: 'id': project['id'], 'name': project['name'], 'display_name': project['display_name'], - 'sub_project': project['sub_project'], - 'category_ids': project['category_ids'] + 'sub_project': project['sub_project'] } current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 41b1c8ef..8a630c3b 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -61,11 +61,11 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) form_values = slack_payload['view']['state']['values'] print('REACHED CREATE EXPENSE -> ', form_values) - private_metadata = utils.decode_state(slack_payload['view']['private_metadata']) - expense_details, validation_errors = self.extract_form_values_and_validate(user, form_values) print('EXPENSE -> ', json.dumps(expense_details, indent=2)) + print('PV -> ', slack_payload['view']['private_metadata']) + private_metadata = utils.decode_state(slack_payload['view'].get('private_metadata', '')) expense_id = private_metadata.get('expense_id') diff --git a/fyle_slack_service/settings.py b/fyle_slack_service/settings.py index a9336e5a..f6099ca6 100644 --- a/fyle_slack_service/settings.py +++ b/fyle_slack_service/settings.py @@ -152,7 +152,7 @@ 'name': 'fyle_slack_service', 'compress': True, 'save_limit': 0, - 'workers': 4, + 'workers': 1, 'queue_limit': 50, 'orm': 'default', 'ack_failures': True, From de89246d98345bce6ce2bb08ef9bd18ef25497b6 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Fri, 6 May 2022 15:01:40 +0530 Subject: [PATCH 61/85] Minor --- fyle_slack_app/slack/ui/expenses/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 47b760e4..b49b619e 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -323,7 +323,7 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None, expe 'label': {'type': 'plain_text', 'text': 'Total Amount'}, } - if int(additional_currency_details['total_amount']) != 0: + if float(additional_currency_details['total_amount']) != 0: total_amount_block['element']['initial_value'] = str(additional_currency_details['total_amount']) blocks.insert(3, total_amount_block) From dd770305eeeebcefd555e06d44562418e6cbac65 Mon Sep 17 00:00:00 2001 From: shreyanshss7 <72918700+shreyanshss7@users.noreply.github.com> Date: Tue, 10 May 2022 19:49:59 +0530 Subject: [PATCH 62/85] Upsert Expense Changes via Platform (#80) * Initial expense api fixes * Using foreign currency and amount from form metadata * Initial working expense via slack * Changed amount -> claim_amount * Added locations support * Moved upsert expense to background task * Fixed edit expense * Fixed update expense issue * fixed project change issue and project clear selection * Caching project before bg task * Caching expense id and msg ts for edit expense * minor * Refactored expense payload creation function * Merged receipt attach flow Co-authored-by: shreyanshs7 --- fyle_slack_app/fyle/expenses/views.py | 34 ++- fyle_slack_app/slack/events/handlers.py | 18 ++ fyle_slack_app/slack/events/tasks.py | 8 +- .../interactives/block_action_handlers.py | 61 ++++- fyle_slack_app/slack/interactives/tasks.py | 59 ++-- .../interactives/view_submission_handlers.py | 251 ++++++++---------- fyle_slack_app/slack/ui/expenses/messages.py | 105 ++++---- fyle_slack_app/slack/utils.py | 7 + 8 files changed, 319 insertions(+), 224 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 923f7dbc..5562da8b 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -9,6 +9,7 @@ from fyle_slack_app.fyle.utils import get_fyle_sdk_connection from fyle_slack_app.models.users import User from fyle_slack_app.fyle import utils as fyle_utils +from fyle_slack_app.libs import assertions, http class FyleExpense: @@ -41,7 +42,7 @@ def get_custom_fields_by_category_id(self, category_id: str) -> Dict: 'offset': 0, 'limit': '50', 'order': 'created_at.desc', - 'column_name': 'not_in.(purpose, txn_dt, vendor_id, cost_center_id)', + 'column_name': 'not_in.(purpose, txn_dt, spent_at, merchant, vendor_id, cost_center_id)', 'is_enabled': 'eq.{}'.format(True), 'category_ids': 'cs.[{}]'.format(int(category_id)) } @@ -67,6 +68,18 @@ def get_expenses(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.expenses.list(query_params=query_params) + def get_expense_by_id(self, expense_id: str) -> Dict: + query_params = { + 'limit': 1, + 'offset': 0, + 'id': 'eq.{}'.format(expense_id), + 'order': 'created_at.asc' + } + response = self.connection.v1beta.spender.expenses.list(query_params=query_params) + expense = response['data'] if response['count'] == 1 else None + return expense + + def get_reports(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.reports.list(query_params=query_params) @@ -121,6 +134,25 @@ def check_cost_center_availability(self) -> bool: return is_cost_center_available + def upsert_expense(self, expense_payload: Dict, refresh_token: str) -> Dict: + access_token = fyle_utils.get_fyle_access_token(refresh_token) + cluster_domain = fyle_utils.get_cluster_domain(refresh_token) + + url = '{}/platform/v1/spender/expenses'.format(cluster_domain) + headers = { + 'content-type': 'application/json', + 'Authorization': 'Bearer {}'.format(access_token) + } + + expense_payload = { + 'data': expense_payload + } + + response = http.post(url, json=expense_payload, headers=headers) + assertions.assert_valid(response.status_code == 200, 'Error creating expense') + return response.json()['data'] + + @staticmethod def get_currencies(): return ['ADP','AED','AFA','ALL','AMD','ANG','AOA','ARS','ATS','AUD','AWG','AZM','BAM','BBD','BDT','BEF','BGL','BGN','BHD','BIF','BMD','BND','BOB','BOV','BRL','BSD','BTN','BWP','BYB','BZD','CAD','CDF','CHF','CLF','CLP','CNY','COP','CRC','CUP','CVE','CYP','CZK','DEM','DJF','DKK','DOP','DZD','ECS','ECV','EEK','EGP','ERN','ESP','ETB','EUR','FIM','FJD','FKP','FRF','GBP','GEL','GHC','GIP','GMD','GNF','GRD','GTQ','GWP','GYD','HKD','HNL','HRK','HTG','HUF','IDE','IDR','IEP','ILS','INR','IQD','IRR','ISK','ITL','JMD','JOD','JPY','KES','KGS','KHR','KMF','KPW','KRW','KWD','KYD','KZT','LAK','LBP','LKR','LRD','LSL','LTL','LUF','LVL','LYD','MAD','MDL','MGF','MKD','MMK','MNT','MOP','MRO','MTL','MUR','MVR','MWK','MXN','MXV','MYR','MZM','NAD','NGN','NIO','NLG','NOK','NPR','NZD','OMR','PAB','PEN','PGK','PHP','PKR','PLN','PTE','PYG','QAR','ROL','RUB','RUR','RWF','RYR','SAR','SBD','SCR','SDP','SEK','SGD','SHP','SIT','SKK','SLL','SOS','SRG','STD','SVC','SYP','SZL','THB','TJR','TMM','TND','TOP','TPE','TRL','TTD','TWD','TZS','UAH','UGX','USD','USN','USS','UYU','UZS','VEB','VND','VUV','WST','XAF','XCD','XDR','XEU','XOF','XPF','YER','YUN','ZAR','ZMK','ZRN','ZWD'] diff --git a/fyle_slack_app/slack/events/handlers.py b/fyle_slack_app/slack/events/handlers.py index 81f520b7..f122edfd 100644 --- a/fyle_slack_app/slack/events/handlers.py +++ b/fyle_slack_app/slack/events/handlers.py @@ -114,6 +114,24 @@ def handle_app_home_opened(self, slack_payload: Dict, team_id: str) -> JsonRespo return JsonResponse({}, status=200) + def handle_file_shared(self, slack_payload: Dict, team_id: str) -> JsonResponse: + file_id = slack_payload['event']['file_id'] + user_id = slack_payload['event']['user_id'] + + async_task( + 'fyle_slack_app.slack.events.tasks.handle_file_shared', + file_id, + user_id, + team_id + ) + + response = JsonResponse({}, status=200) + + # Passing this for slack not to retry `file_shared` event again + response['X-Slack-No-Retry'] = 1 + + return response + def handle_file_shared(self, slack_payload: Dict, team_id: str) -> JsonResponse: file_id = slack_payload['event']['file_id'] diff --git a/fyle_slack_app/slack/events/tasks.py b/fyle_slack_app/slack/events/tasks.py index 6123005a..8d3fae84 100644 --- a/fyle_slack_app/slack/events/tasks.py +++ b/fyle_slack_app/slack/events/tasks.py @@ -1,16 +1,20 @@ +import base64 + from typing import Dict, Union from slack_sdk import WebClient from django.conf import settings +from django.db import transaction +from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.fyle.utils import get_fyle_oauth_url -from fyle_slack_app.libs import utils, assertions, logger +from fyle_slack_app.libs import utils, assertions, logger, http from fyle_slack_app.models import Team, User, UserSubscriptionDetail from fyle_slack_app.models.user_subscription_details import SubscriptionType from fyle_slack_app.fyle import utils as fyle_utils - +from fyle_slack_app.slack.utils import get_file_content_from_slack, get_slack_client, get_slack_user_dm_channel_id from fyle_slack_app.slack.interactives.block_action_handlers import BlockActionHandler from fyle_slack_app.slack import utils as slack_utils from fyle_slack_app.slack.ui.authorization import messages diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 6d657417..a8e1bf7b 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -52,7 +52,7 @@ def _initialize_block_action_handlers(self): 'category_id': self.handle_category_selection, 'project_id': self.handle_project_selection, 'currency': self.handle_currency_selection, - 'amount': self.handle_amount_entered, + 'claim_amount': self.handle_amount_entered, 'add_to_report': self.handle_add_to_report, 'add_expense_to_report': self.handle_add_expense_to_report, 'add_expense_to_report_selection': self.handle_add_expense_to_report_selection, @@ -204,13 +204,46 @@ def handle_expense_accessory(self, slack_payload: Dict, user_id: str, team_id: s def handle_project_selection(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - - project_id = slack_payload['actions'][0]['selected_option']['value'] + if slack_payload['actions'][0]['selected_option'] is None: + project_id = None + else: + project_id = slack_payload['actions'][0]['selected_option']['value'] view_id = slack_payload['container']['view_id'] - user = utils.get_or_none(User, slack_user_id=user_id) + project = None + fyle_expense = FyleExpense(user) + + if project_id is not None: + project_query_params = { + 'offset': 0, + 'limit': '1', + 'order': 'created_at.desc', + 'id': 'eq.{}'.format(int(project_id)), + 'is_enabled': 'eq.{}'.format(True) + } + project = fyle_expense.get_projects(project_query_params) + project = project['data'][0] + project = { + 'id': project['id'], + 'name': project['name'], + 'display_name': project['display_name'], + 'sub_project': project['sub_project'] + } + + expense_form_details = { + 'project': project + } + + cache_key = '{}.form_metadata'.format(view_id) + form_metadata = cache.get(cache_key) + if form_metadata is None: + cache.set(cache_key, expense_form_details) + else: + form_metadata['project'] = project + cache.set(cache_key, form_metadata) + current_view = expense_messages.expense_form_loading_modal(title='Create Expense', loading_message='Loading the best expense form :zap:') current_view['submit'] = {'type': 'plain_text', 'text': 'Add Expense', 'emoji': True} @@ -242,7 +275,7 @@ def handle_project_selection(self, slack_payload: Dict, user_id: str, team_id: s 'fyle_slack_app.slack.interactives.tasks.handle_project_selection', user, team_id, - project_id, + project, view_id, slack_payload ) @@ -479,13 +512,27 @@ def handle_edit_expense(self, slack_payload: Dict, user_id: str, team_id: str) - expense_id = slack_payload['actions'][0]['value'] response = slack_client.views_open(view=loading_modal, trigger_id=slack_payload['trigger_id']) + view_id = response['view']['id'] + cache_key = '{}.form_metadata'.format(view_id) + form_metadata = cache.get(cache_key) + # Add additional metadata to differentiate create and edit expense + # message_ts to update message in edit case + if form_metadata is None: + form_metadata = { + 'expense_id': expense_id, + 'message_ts': slack_payload['container']['message_ts'] + } + else: + form_metadata['expense_id'] = expense_id + form_metadata['message_ts'] = slack_payload['container']['message_ts'] + cache.set(cache_key, form_metadata) async_task( 'fyle_slack_app.slack.interactives.tasks.handle_edit_expense', user, expense_id, team_id, - response['view']['id'], + view_id, slack_payload ) @@ -533,6 +580,8 @@ def handle_attach_receipt(self, slack_payload: Dict, user_id: str, team_id: str) channel=user.slack_dm_channel_id ) + return JsonResponse({}) + def handle_tasks_viewed_in_fyle(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: user = utils.get_or_none(User, slack_user_id=user_id) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index f71f9fd3..3075fc28 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -33,37 +33,17 @@ def get_additional_currency_details(amount: int, home_currency: str, selected_cu additional_currency_details = { 'foreign_currency': selected_currency, 'home_currency': home_currency, + 'claim_amount': amount, 'total_amount': round(exchange_rate * amount, 2) } return additional_currency_details -def handle_project_selection(user: User, team_id: str, project_id: str, view_id: str, slack_payload: Dict) -> None: - +def handle_project_selection(user: User, team_id: str, project: Dict, view_id: str, slack_payload: Dict) -> None: slack_client = get_slack_client(team_id) - - project_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(int(project_id)), - 'is_enabled': 'eq.{}'.format(True) - } - fyle_expense = FyleExpense(user) - project = fyle_expense.get_projects(project_query_params) - - project = project['data'][0] - - project = { - 'id': project['id'], - 'name': project['name'], - 'display_name': project['display_name'], - 'sub_project': project['sub_project'] - } - current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) @@ -138,7 +118,7 @@ def handle_currency_selection(user: User, selected_currency: str, view_id: str, if home_currency != selected_currency: form_current_state = slack_payload['view']['state']['values'] exchange_rate = fyle_expense.get_exchange_rate(selected_currency, home_currency) - amount = form_current_state['NUMBER_default_field_amount_block']['amount']['value'] + amount = form_current_state['NUMBER_default_field_amount_block']['claim_amount']['value'] additional_currency_details = get_additional_currency_details(amount, home_currency, selected_currency, exchange_rate) current_expense_form_details['additional_currency_details'] = additional_currency_details @@ -189,11 +169,7 @@ def handle_amount_entered(user: User, amount_entered: float, view_id: str, team_ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, slack_payload: List[Dict]) -> None: - slack_client = get_slack_client(team_id) - - expense_id = 'txCCVGvNpDMM' - fyle_expense = FyleExpense(user) expense_query_params = { @@ -211,7 +187,7 @@ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, expense_form_details = FyleExpense.get_expense_form_details(user, view_id) - cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + cache_key = '{}.form_metadata'.format(view_id) form_metadata = cache.get(cache_key) # Add additional metadata to differentiate create and edit expense @@ -261,6 +237,33 @@ def handle_submit_report_dialog(user: User, team_id: str, report_id: str, view_i slack_client.views_update(view_id=view_id, view=add_expense_to_report_dialog) +def handle_upsert_expense(user: User, view_id: str, team_id: str, expense_payload: Dict, expense_id: str, message_ts: str): + slack_client = get_slack_client(team_id) + fyle_expense = FyleExpense(user) + + cache_key = '{}.form_metadata'.format(view_id) + form_metadata = cache.get(cache_key) + + if 'foreign_currency' in form_metadata['additional_currency_details']: + expense_payload['foreign_currency'] = form_metadata['additional_currency_details']['foreign_currency'] + expense_payload['foreign_amount'] = form_metadata['additional_currency_details']['total_amount'] + expense_payload['claim_amount'] = form_metadata['additional_currency_details']['total_amount'] + + if 'project' in form_metadata and form_metadata['project'] is not None: + expense_payload['project_id'] = form_metadata['project']['id'] + + if expense_id is not None: + expense_payload['id'] = expense_id + + expense = fyle_expense.upsert_expense(expense_payload, user.fyle_refresh_token) + view_expense_message = expense_messages.view_expense_message(expense, user) + + if expense_id is None or message_ts is None: + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=view_expense_message) + else: + slack_client.chat_update(channel=user.slack_dm_channel_id, blocks=view_expense_message, ts=message_ts) + + def handle_feedback_submission(user: User, team_id: str, form_values: Dict, private_metadata: Dict): user_feedback_id = private_metadata['user_feedback_id'] feedback_message_ts = private_metadata['feedback_message_ts'] diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 8ba0138c..a8cc057e 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,8 +1,13 @@ import datetime import json + from typing import Callable, Dict, Union +from dateutil.parser import parse from django.http.response import JsonResponse +from django.core.cache import cache + +from django_q.tasks import async_task from django_q.tasks import async_task @@ -65,22 +70,18 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) user = utils.get_or_none(User, slack_user_id=user_id) form_values = slack_payload['view']['state']['values'] - print('REACHED CREATE EXPENSE -> ', form_values) - - expense_details, validation_errors = self.extract_form_values_and_validate(user, form_values) - print('EXPENSE -> ', json.dumps(expense_details, indent=2)) - print('PV -> ', slack_payload['view']['private_metadata']) - private_metadata = utils.decode_state(slack_payload['view'].get('private_metadata', '')) + expense_payload, validation_errors = self.extract_form_values_and_validate(user, form_values) + cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) + form_metadata = cache.get(cache_key) - expense_id = private_metadata.get('expense_id') + expense_payload['source'] = 'SLACK' + expense_payload['spent_at'] = parse(expense_payload['spent_at']).strftime("%Y-%m-%dT%H:%M:%S.%fZ") - message_ts = private_metadata.get('message_ts') + print('EXPENSE -> ', json.dumps(expense_payload, indent=2)) - if expense_id is not None: - expense_details['id'] = expense_id - - print('VALIDATION ERRORS -> ', validation_errors) + expense_id = form_metadata.get('expense_id') + message_ts = form_metadata.get('message_ts') # If valdiation errors are present then return errors if bool(validation_errors) is True: @@ -89,29 +90,15 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) 'errors': validation_errors }) - slack_client = slack_utils.get_slack_client(team_id) - - # expense_id = 'txCCVGvNpDMM' - expense_id = 'tx0mjvrfuizk' - # expense_id = 'txjNT3H5dTw1' - - fyle_expense = FyleExpense(user) - - expense_query_params = { - 'offset': 0, - 'limit': '1', - 'order': 'created_at.desc', - 'id': 'eq.{}'.format(expense_id) - } - - expense = fyle_expense.get_expenses(query_params=expense_query_params) - - view_expense_message = expense_messages.view_expense_message(expense['data'][0], user) - - if expense_id is None or message_ts is None: - slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=view_expense_message) - else: - slack_client.chat_update(channel=user.slack_dm_channel_id, blocks=view_expense_message, ts=message_ts) + async_task( + 'fyle_slack_app.slack.interactives.tasks.handle_upsert_expense', + user, + slack_payload['view']['id'], + team_id, + expense_payload, + expense_id, + message_ts + ) return JsonResponse({}) @@ -174,118 +161,100 @@ def handle_feedback_submission(self, slack_payload: Dict, user_id: str, team_id: def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dict, Dict]: - expense_details = {} + expense_payload = {} validation_errors = {} custom_fields = [] fyle_expense = FyleExpense(user) - for key, value in form_values.items(): + for block_id, value in form_values.items(): custom_field_mappings = {} - if 'custom_field' in key: - for inner_key, inner_value in value.items(): - - if inner_value['type'] in ['static_select', 'external_select']: - if inner_value['selected_option'] is not None: - value = inner_value['selected_option']['value'] - - if 'LOCATION' in key: - value = fyle_expense.get_place_by_place_id(value) - - if inner_value['type'] in ['multi_static_select', 'multi_external_select']: - - if 'USER_LIST' in key: - _ , inner_key = key.split('__') - - values_list = [] - for val in inner_value['selected_options']: - values_list.append(val['value']) - value = values_list - - elif inner_value['type'] == 'datepicker': - - if datetime.datetime.strptime(inner_value['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): - validation_errors[key] = 'Date selected cannot be in future' - - value = inner_value['selected_date'] - - elif inner_value['type'] == 'plain_text_input': - - if 'TEXT' in key: - value = inner_value['value'].strip() - - elif 'NUMBER' in key: - value = inner_value['value'] - try: - value = float(inner_value['value']) - - if value < 0: - validation_errors[key] = 'Negative numbers are not allowed' - - value = round(value, 2) - - except ValueError: - validation_errors[key] = 'Only numbers are allowed in this fields' - - elif inner_value['type'] == 'checkboxes': - - value = False - if len(inner_value['selected_options']) > 0: - value = True - - custom_field_mappings['name'] = inner_key - custom_field_mappings['value'] = value - - custom_fields.append(custom_field_mappings) - else: - for inner_key, inner_value in value.items(): - - if inner_value['type'] in ['static_select', 'external_select']: - if inner_value['selected_option'] is not None: - expense_details[inner_key] = inner_value['selected_option']['value'] - - if inner_value['type'] == 'multi_static_select': - - values_list = [] - for val in inner_value['selected_options']: - values_list.append(val['value']) - expense_details[inner_key] = values_list - - - elif inner_value['type'] == 'datepicker': - - if datetime.datetime.strptime(inner_value['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): - validation_errors[key] = 'Date selected cannot be for future' - - expense_details[inner_key] = inner_value['selected_date'] - - elif inner_value['type'] == 'plain_text_input': - if 'TEXT' in key: - value = inner_value['value'].strip() - - elif 'NUMBER' in key: - value = inner_value['value'] - try: - value = float(inner_value['value']) - - if value < 0: - validation_errors[key] = 'Negative numbers are not allowed' - - value = round(value, 2) - - except ValueError: - validation_errors[key] = 'Only numbers are allowed in this fields' - expense_details[inner_key] = value - - elif inner_value['type'] == 'checkboxes': - - expense_details[inner_key] = False - if len(inner_value['selected_options']) > 0: - expense_details[inner_key] = True - - expense_details['custom_fields'] = custom_fields - - return expense_details, validation_errors + for expense_field_key, form_detail in value.items(): + form_value = None + if form_detail['type'] in ['static_select', 'external_select']: + expense_field_key, form_value, expense_payload = self.extract_select_field_detail( + expense_field_key, + form_detail, + block_id, + fyle_expense, + expense_payload + ) + + if form_detail['type'] in ['multi_static_select', 'multi_external_select']: + expense_field_key, form_value = self.extract_multi_select_field(expense_field_key, form_detail, block_id) + + elif form_detail['type'] == 'datepicker': + form_value, validation_errors = self.extract_and_validate_date_field(form_detail, block_id, validation_errors) + + elif form_detail['type'] == 'plain_text_input': + form_value, validation_errors = self.extract_and_validate_text_field(form_detail, block_id, validation_errors) + + elif form_detail['type'] == 'checkboxes': + form_value = self.extract_checkbox_field(form_detail) + + if form_value is not None: + if 'custom_field' in block_id: + custom_field_mappings['name'] = expense_field_key + custom_field_mappings['value'] = form_value + custom_fields.append(custom_field_mappings) + else: + expense_payload[expense_field_key] = form_value + expense_payload['custom_fields'] = custom_fields + + return expense_payload, validation_errors + + def extract_select_field_detail(self, expense_field_key: str, form_detail: Dict, block_id: str, fyle_expense: FyleExpense, expense_payload: Dict): + form_value = None + if form_detail['selected_option'] is not None: + form_value = form_detail['selected_option']['value'] + + if 'LOCATION' in block_id: + _ , expense_field_key = block_id.split('__') + place_id = form_detail['selected_option']['value'] if form_detail['selected_option'] is not None else None + if place_id is not None: + location = fyle_expense.get_place_by_place_id(place_id) + if 'locations' in expense_payload: + expense_payload['locations'].append(location) + else: + expense_payload['locations'] = [location] + + return expense_field_key, form_value, expense_payload + + def extract_multi_select_field(self, expense_field_key: str, form_detail: Dict, block_id: str): + if 'USER_LIST' in block_id: + _ , expense_field_key = block_id.split('__') + values_list = [] + for val in form_detail['selected_options']: + values_list.append(val['value']) + form_value = values_list + return expense_field_key, form_value + + def extract_and_validate_date_field(self, form_detail: Dict, block_id: str, validation_errors: Dict): + if form_detail['selected_date'] is not None and datetime.datetime.strptime(form_detail['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): + validation_errors[block_id] = 'Date selected cannot be in future' + form_value = form_detail['selected_date'] + return form_value, validation_errors + + def extract_and_validate_text_field(self, form_detail: Dict, block_id: str, validation_errors: Dict): + if 'TEXT' in block_id: + form_value = form_detail['value'].strip() if form_detail['value'] is not None else None + + elif 'NUMBER' in block_id: + form_value = form_detail['value'] + try: + form_value = float(form_detail['value']) if form_detail['value'] is not None else None + if form_value is not None and form_value < 0: + validation_errors[block_id] = 'Negative numbers are not allowed' + form_value = round(form_value, 2) if form_value is not None else None + except ValueError: + validation_errors[block_id] = 'Only numbers are allowed in this fields' + return form_value, validation_errors + + def extract_checkbox_field(self, form_detail: Dict): + form_value = False + if len(form_detail['selected_options']) > 0: + form_value = True + return form_value def handle_report_approval_from_modal(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: encoded_private_metadata = slack_payload['view']['private_metadata'] diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index b49b619e..677f9868 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -166,7 +166,7 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F 'type': 'plain_text', 'text': '{}'.format(field_details['placeholder']), }, - 'action_id': 'spent_at', + 'action_id': action_id, }, 'label': { 'type': 'plain_text', @@ -252,7 +252,7 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None, expe 'label': {'type': 'plain_text', 'text': 'Currency'}, } - if expense is not None: + if expense is not None and expense['currency'] is not None: currency_block['element']['initial_option']['text']['text'] = expense['currency'] currency_block['element']['initial_option']['value'] = expense['currency'] @@ -269,13 +269,13 @@ def get_amount_and_currency_block(additional_currency_details: Dict = None, expe 'type': 'plain_text', 'text': 'Enter Amount', }, - 'action_id': 'amount', + 'action_id': 'claim_amount', }, 'label': {'type': 'plain_text', 'text': 'Amount'}, } - if expense is not None: - amount_block['element']['initial_value'] = str(expense['amount']) + if expense is not None and expense['claim_amount'] is not None: + amount_block['element']['initial_value'] = str(expense['claim_amount']) blocks.append(amount_block) @@ -350,7 +350,7 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: 'label': {'type': 'plain_text', 'text': 'Date of Spend'}, } - if expense is not None: + if expense is not None and expense['spent_at'] is not None: date_of_spend_block['element']['initial_date'] = utils.get_formatted_datetime(expense['spent_at'], '%Y-%m-%d') default_fields_blocks.append(date_of_spend_block) @@ -369,7 +369,7 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: 'label': {'type': 'plain_text', 'text': 'Purpose'}, } - if expense is not None: + if expense is not None and expense['purpose'] is not None: purpose_block['element']['initial_value'] = expense['purpose'] default_fields_blocks.append(purpose_block) @@ -389,8 +389,15 @@ def get_default_fields_blocks(additional_currency_details: Dict = None, expense: 'label': {'type': 'plain_text', 'text': 'Merchant'}, } - if expense is not None: - merchant_block['element']['initial_value'] = expense['merchant'] + if expense is not None and expense['merchant'] is not None: + initial_option = { + 'text': { + 'type': 'plain_text', + 'text': expense['merchant'], + }, + 'value': expense['merchant'], + } + merchant_block['element']['initial_option'] = initial_option default_fields_blocks.append(merchant_block) @@ -417,7 +424,7 @@ def get_projects_and_billable_block(selected_project: Dict = None, expense: Dict 'label': {'type': 'plain_text', 'text': 'Project'}, } - if expense is not None: + if expense is not None and expense['project'] is not None: project_block['element']['initial_option'] = { 'text': { 'type': 'plain_text', @@ -478,7 +485,7 @@ def get_categories_block(expense: Dict = None) -> Dict: 'label': {'type': 'plain_text', 'text': 'Category'}, } - if expense is not None: + if expense is not None and expense['category'] is not None: category_block['element']['initial_option'] = { 'text': { 'type': 'plain_text', @@ -506,7 +513,7 @@ def get_cost_centers_block(expense: Dict = None) -> Dict: 'label': {'type': 'plain_text', 'text': 'Cost Center'}, } - if expense is not None: + if expense is not None and expense['cost_center'] is not None: cost_centers_block['element']['initial_option'] = { 'text': { 'type': 'plain_text', @@ -705,10 +712,11 @@ def expense_dialog_form( }) # Add to report section - if add_to_report is not None: - add_to_report_blocks = get_add_to_report_blocks(add_to_report, action_id='add_to_report') + # TODO: Uncomment this after report APIs become available + # if add_to_report is not None: + # add_to_report_blocks = get_add_to_report_blocks(add_to_report, action_id='add_to_report') - view['blocks'].extend(add_to_report_blocks) + # view['blocks'].extend(add_to_report_blocks) return view @@ -716,11 +724,15 @@ def expense_dialog_form( def get_expense_message_details_section(expense: Dict, expense_url: str, actions: List[Dict], receipt_message: str, report_message: str) -> List[Dict]: spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') + + expense_details = expense['purpose'] + if expense['category']['name'] is not None: + expense_details = '{} ({})'.format(expense_details, expense['category']['name']) expense_message_details_section = [ { 'type': 'section', - 'block_id': expense['id'], + 'block_id': 'expense_id.{}'.format(expense['id']), 'text': { 'type': 'mrkdwn', 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) @@ -769,7 +781,7 @@ def get_expense_message_details_section(expense: Dict, expense_url: str, actions }, { 'type': 'mrkdwn', - 'text': '*Expense Details: * \n Travel to Office (Uber)' + 'text': '*Expense Details: * \n {}'.format(expense_details) } ] }, @@ -804,39 +816,40 @@ def view_expense_message(expense: Dict, user: User) -> Dict: actions.append(attach_receipt_cta) + # TODO: Uncomment after report APIs become available report_message = ':x: Not Added' if expense['report_id'] is not None: report_message = ':white_check_mark: Added' - if expense['report']['state'] in ['DRAFT', 'APPROVER_INQUIRY']: - - submit_report_cta = { - 'type': 'button', - 'style': 'primary', - 'text': { - 'type': 'plain_text', - 'text': 'Submit Report', - }, - 'value': expense['report_id'], - 'action_id': 'open_submit_report_dialog' - } - - actions.append(submit_report_cta) - - else: - - add_to_report_cta = { - 'type': 'button', - 'style': 'primary', - 'text': { - 'type': 'plain_text', - 'text': 'Add to Report', - }, - 'value': expense['id'], - 'action_id': 'add_expense_to_report' - } - - actions.append(add_to_report_cta) + # if expense['report']['state'] in ['DRAFT', 'APPROVER_INQUIRY']: + + # submit_report_cta = { + # 'type': 'button', + # 'style': 'primary', + # 'text': { + # 'type': 'plain_text', + # 'text': 'Submit Report', + # }, + # 'value': expense['report_id'], + # 'action_id': 'open_submit_report_dialog' + # } + + # actions.append(submit_report_cta) + + # else: + + # add_to_report_cta = { + # 'type': 'button', + # 'style': 'primary', + # 'text': { + # 'type': 'plain_text', + # 'text': 'Add to Report', + # }, + # 'value': expense['id'], + # 'action_id': 'add_expense_to_report' + # } + + # actions.append(add_to_report_cta) complete_expense_cta = { 'type': 'button', diff --git a/fyle_slack_app/slack/utils.py b/fyle_slack_app/slack/utils.py index d2ce884e..e4d63ff7 100644 --- a/fyle_slack_app/slack/utils.py +++ b/fyle_slack_app/slack/utils.py @@ -38,6 +38,13 @@ def get_user_display_name(slack_client: WebClient, user_details: Dict) -> str: return user_display_name +def get_file_content_from_slack(url: str, bot_access_token: str) -> str: + headers = { + 'Authorization': 'Bearer {}'.format(bot_access_token) + } + file = http.get(url, headers=headers) + return file.content + def get_currency_symbol(currency: str) -> str: c = CurrencyCodes() From e765ab508403fb66f26f5c0e8cccc7de8fe05399 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Tue, 10 May 2022 20:50:26 +0530 Subject: [PATCH 63/85] Resolved pylint issues --- .pylintrc | 1 + fyle_slack_app/fyle/expenses/views.py | 14 +------- fyle_slack_app/slack/events/handlers.py | 18 ---------- fyle_slack_app/slack/events/tasks.py | 8 +---- .../interactives/block_action_handlers.py | 33 ++----------------- fyle_slack_app/slack/interactives/tasks.py | 3 +- .../interactives/view_submission_handlers.py | 6 ++-- fyle_slack_app/slack/ui/expenses/messages.py | 4 +-- fyle_slack_app/slack/utils.py | 9 ----- 9 files changed, 11 insertions(+), 85 deletions(-) diff --git a/.pylintrc b/.pylintrc index d3ad7c5d..30dc42aa 100644 --- a/.pylintrc +++ b/.pylintrc @@ -61,6 +61,7 @@ confidence= # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable=print-statement, + consider-using-f-string, parameter-unpacking, unpacking-in-except, old-raise-syntax, diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 5562da8b..d31ee83e 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -11,7 +11,7 @@ from fyle_slack_app.fyle import utils as fyle_utils from fyle_slack_app.libs import assertions, http - +# pylint: disable=too-many-public-methods class FyleExpense: connection: Platform = None @@ -68,18 +68,6 @@ def get_expenses(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.expenses.list(query_params=query_params) - def get_expense_by_id(self, expense_id: str) -> Dict: - query_params = { - 'limit': 1, - 'offset': 0, - 'id': 'eq.{}'.format(expense_id), - 'order': 'created_at.asc' - } - response = self.connection.v1beta.spender.expenses.list(query_params=query_params) - expense = response['data'] if response['count'] == 1 else None - return expense - - def get_reports(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.reports.list(query_params=query_params) diff --git a/fyle_slack_app/slack/events/handlers.py b/fyle_slack_app/slack/events/handlers.py index f122edfd..81f520b7 100644 --- a/fyle_slack_app/slack/events/handlers.py +++ b/fyle_slack_app/slack/events/handlers.py @@ -114,24 +114,6 @@ def handle_app_home_opened(self, slack_payload: Dict, team_id: str) -> JsonRespo return JsonResponse({}, status=200) - def handle_file_shared(self, slack_payload: Dict, team_id: str) -> JsonResponse: - file_id = slack_payload['event']['file_id'] - user_id = slack_payload['event']['user_id'] - - async_task( - 'fyle_slack_app.slack.events.tasks.handle_file_shared', - file_id, - user_id, - team_id - ) - - response = JsonResponse({}, status=200) - - # Passing this for slack not to retry `file_shared` event again - response['X-Slack-No-Retry'] = 1 - - return response - def handle_file_shared(self, slack_payload: Dict, team_id: str) -> JsonResponse: file_id = slack_payload['event']['file_id'] diff --git a/fyle_slack_app/slack/events/tasks.py b/fyle_slack_app/slack/events/tasks.py index 8d3fae84..defabec1 100644 --- a/fyle_slack_app/slack/events/tasks.py +++ b/fyle_slack_app/slack/events/tasks.py @@ -1,20 +1,14 @@ -import base64 - from typing import Dict, Union from slack_sdk import WebClient from django.conf import settings -from django.db import transaction -from fyle_slack_app.fyle.expenses.views import FyleExpense - from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.fyle.utils import get_fyle_oauth_url -from fyle_slack_app.libs import utils, assertions, logger, http +from fyle_slack_app.libs import utils, assertions, logger from fyle_slack_app.models import Team, User, UserSubscriptionDetail from fyle_slack_app.models.user_subscription_details import SubscriptionType from fyle_slack_app.fyle import utils as fyle_utils -from fyle_slack_app.slack.utils import get_file_content_from_slack, get_slack_client, get_slack_user_dm_channel_id from fyle_slack_app.slack.interactives.block_action_handlers import BlockActionHandler from fyle_slack_app.slack import utils as slack_utils from fyle_slack_app.slack.ui.authorization import messages diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index a8e1bf7b..7bd9f71e 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -5,17 +5,11 @@ from django_q.tasks import async_task from fyle_slack_app.fyle.expenses.views import FyleExpense -from fyle_slack_app.models import User, NotificationPreference from fyle_slack_app.models.notification_preferences import NotificationType from fyle_slack_app.libs import assertions, utils, logger -from fyle_slack_app.slack.utils import get_slack_user_dm_channel_id, get_slack_client +from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.slack.ui.expenses import messages as expense_messages from fyle_slack_app.models import User, NotificationPreference, UserFeedback -from fyle_slack_app.models.notification_preferences import NotificationType -from fyle_slack_app.libs import assertions, utils, logger - -from fyle_slack_app.fyle.expenses.views import FyleExpense - from fyle_slack_app.slack.ui.feedbacks import messages as feedback_messages from fyle_slack_app.slack.ui.modals import messages as modal_messages from fyle_slack_app.slack.ui import common_messages @@ -25,7 +19,7 @@ logger = logger.get_logger(__name__) - +# pylint: disable=too-many-public-methods class BlockActionHandler: _block_action_handlers: Dict = {} @@ -48,7 +42,6 @@ def _initialize_block_action_handlers(self): 'report_commented_notification_preference': self.handle_notification_preference_selection, 'expense_commented_notification_preference': self.handle_notification_preference_selection, 'edit_expense': self.handle_edit_expense, - 'attach_receipt': self.handle_attach_receipt, 'category_id': self.handle_category_selection, 'project_id': self.handle_project_selection, 'currency': self.handle_currency_selection, @@ -449,7 +442,6 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s fyle_expense = FyleExpense(user) - # TODO: Clean this up expense_id = 'txCCVGvNpDMM' expense_query_params = { @@ -478,7 +470,6 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i slack_client = get_slack_client(team_id) - # TODO: Clean this up expense_id = 'txCCVGvNpDMM' fyle_expense = FyleExpense(user) @@ -549,7 +540,6 @@ def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id report_id = slack_payload['actions'][0]['value'] - # TODO: Clean this up report_id = 'rpKJGi7nRzMF' response = slack_client.views_open(view=loading_modal, trigger_id=slack_payload['trigger_id']) @@ -564,25 +554,6 @@ def handle_submit_report_dialog(self, slack_payload: Dict, user_id: str, team_id return JsonResponse({}) - - def handle_attach_receipt(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: - message_ts = slack_payload['container']['message_ts'] - - user = utils.get_or_none(User, slack_user_id=user_id) - - attach_receipt_message = '*Drag* or *attach* a receipt (to the message box) for this expense!' - - slack_client = get_slack_client(team_id) - - slack_client.chat_postMessage( - text=attach_receipt_message, - thread_ts=message_ts, - channel=user.slack_dm_channel_id - ) - - return JsonResponse({}) - - def handle_tasks_viewed_in_fyle(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: user = utils.get_or_none(User, slack_user_id=user_id) task_name = slack_payload['actions'][0]['value'] diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 3075fc28..1fc7413c 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,8 +1,8 @@ from typing import Dict, List from django.core.cache import cache +from fyle.platform import exceptions -from fyle_slack_app.models import User from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form @@ -15,7 +15,6 @@ from fyle_slack_app.slack.ui.modals import messages as modal_messages from fyle_slack_app.slack.ui import common_messages from fyle_slack_app import tracking -from fyle.platform import exceptions logger = logger.get_logger(__name__) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index a8cc057e..1a07ea21 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -9,8 +9,6 @@ from django_q.tasks import async_task -from django_q.tasks import async_task - from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.models import User from fyle_slack_app.slack import utils as slack_utils @@ -132,15 +130,19 @@ def handle_submit_report(self, slack_payload: Dict, user_id: str, team_id: str) def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: add_expense_to_report_form_values = slack_payload['view']['state']['values'] + # pylint: disable=unused-variable expense_id = slack_payload['view']['private_metadata'] if 'TEXT_add_to_new_report_block' in add_expense_to_report_form_values: + # pylint: disable=unused-variable report_name = add_expense_to_report_form_values['TEXT_add_to_new_report_block']['report_name']['value'] elif 'SELECT_add_to_existing_report_block' in add_expense_to_report_form_values: + # pylint: disable=unused-variable existing_report_id = add_expense_to_report_form_values['SELECT_add_to_existing_report_block']['existing_report']['selected_option']['value'] encoded_private_metadata = slack_payload['view']['private_metadata'] + # pylint: disable=unused-variable private_metadata = utils.decode_state(encoded_private_metadata) def handle_feedback_submission(self, slack_payload: Dict, user_id: str, team_id: str) -> JsonResponse: diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 677f9868..6e654efc 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -712,7 +712,6 @@ def expense_dialog_form( }) # Add to report section - # TODO: Uncomment this after report APIs become available # if add_to_report is not None: # add_to_report_blocks = get_add_to_report_blocks(add_to_report, action_id='add_to_report') @@ -724,7 +723,7 @@ def expense_dialog_form( def get_expense_message_details_section(expense: Dict, expense_url: str, actions: List[Dict], receipt_message: str, report_message: str) -> List[Dict]: spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') - + expense_details = expense['purpose'] if expense['category']['name'] is not None: expense_details = '{} ({})'.format(expense_details, expense['category']['name']) @@ -816,7 +815,6 @@ def view_expense_message(expense: Dict, user: User) -> Dict: actions.append(attach_receipt_cta) - # TODO: Uncomment after report APIs become available report_message = ':x: Not Added' if expense['report_id'] is not None: report_message = ':white_check_mark: Added' diff --git a/fyle_slack_app/slack/utils.py b/fyle_slack_app/slack/utils.py index e4d63ff7..eea40eb8 100644 --- a/fyle_slack_app/slack/utils.py +++ b/fyle_slack_app/slack/utils.py @@ -59,15 +59,6 @@ def get_currency_symbol(currency: str) -> str: return symbol - -def get_file_content_from_slack(url: str, bot_access_token: str) -> str: - headers = { - 'Authorization': 'Bearer {}'.format(bot_access_token) - } - file = http.get(url, headers=headers) - return file.content - - def get_slack_latest_parent_message(user: User, slack_client: WebClient, thread_ts: str) -> Dict: message_history = slack_client.conversations_history(channel=user.slack_dm_channel_id, latest=thread_ts, inclusive=True, limit=1) parent_message = message_history['messages'][0] if message_history['messages'] and message_history['messages'][0] else {} From 707821bb6462c82a300c2afd69f75fbc92802148 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Wed, 11 May 2022 12:51:11 +0530 Subject: [PATCH 64/85] Reverted qcluster workers to 4 --- fyle_slack_service/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fyle_slack_service/settings.py b/fyle_slack_service/settings.py index 09aa2ed6..993abc95 100644 --- a/fyle_slack_service/settings.py +++ b/fyle_slack_service/settings.py @@ -152,7 +152,7 @@ 'name': 'fyle_slack_service', 'compress': True, 'save_limit': 0, - 'workers': 1, + 'workers': 4, 'queue_limit': 50, 'orm': 'default', 'ack_failures': True, From 9aa3847a3aa33dfd01c7fad97a447fb3f4248cfb Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Wed, 11 May 2022 16:31:38 +0530 Subject: [PATCH 65/85] Added fix for locations and travel classes --- .../interactives/view_submission_handlers.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 1a07ea21..ea09691c 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -74,7 +74,7 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) form_metadata = cache.get(cache_key) expense_payload['source'] = 'SLACK' - expense_payload['spent_at'] = parse(expense_payload['spent_at']).strftime("%Y-%m-%dT%H:%M:%S.%fZ") + # expense_payload['spent_at'] = parse(expense_payload['spent_at']).strftime("%Y-%m-%dT%H:%M:%S.%fZ") print('EXPENSE -> ', json.dumps(expense_payload, indent=2)) @@ -168,7 +168,6 @@ def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dic custom_fields = [] fyle_expense = FyleExpense(user) - for block_id, value in form_values.items(): custom_field_mappings = {} for expense_field_key, form_detail in value.items(): @@ -195,12 +194,13 @@ def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dic form_value = self.extract_checkbox_field(form_detail) if form_value is not None: - if 'custom_field' in block_id: + if 'custom_field' in block_id and 'LOCATION' not in block_id and 'travel_classes' not in block_id: custom_field_mappings['name'] = expense_field_key custom_field_mappings['value'] = form_value custom_fields.append(custom_field_mappings) else: - expense_payload[expense_field_key] = form_value + if 'LOCATION' not in block_id and 'travel_classes' not in block_id: + expense_payload[expense_field_key] = form_value expense_payload['custom_fields'] = custom_fields return expense_payload, validation_errors @@ -219,6 +219,13 @@ def extract_select_field_detail(self, expense_field_key: str, form_detail: Dict, expense_payload['locations'].append(location) else: expense_payload['locations'] = [location] + if 'travel_classes' in block_id: + travel_class = form_detail['selected_option']['value'] if form_detail['selected_option'] is not None else None + if travel_class is not None: + if 'travel_classes' in expense_payload: + expense_payload['travel_classes'].append(travel_class) + else: + expense_payload['travel_classes'] = [travel_class] return expense_field_key, form_value, expense_payload @@ -235,6 +242,7 @@ def extract_and_validate_date_field(self, form_detail: Dict, block_id: str, vali if form_detail['selected_date'] is not None and datetime.datetime.strptime(form_detail['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): validation_errors[block_id] = 'Date selected cannot be in future' form_value = form_detail['selected_date'] + form_value = parse(form_value).strftime("%Y-%m-%dT%H:%M:%S.%fZ") return form_value, validation_errors def extract_and_validate_text_field(self, form_detail: Dict, block_id: str, validation_errors: Dict): From acf597df617d32f9cf8ed0e1e56c0613015be125 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Wed, 11 May 2022 16:32:34 +0530 Subject: [PATCH 66/85] removed hard coded expense id --- fyle_slack_app/slack/interactives/block_action_handlers.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 7bd9f71e..593b1d23 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -442,8 +442,6 @@ def handle_add_expense_to_report_selection(self, slack_payload: Dict, user_id: s fyle_expense = FyleExpense(user) - expense_id = 'txCCVGvNpDMM' - expense_query_params = { 'offset': 0, 'limit': '1', @@ -470,8 +468,6 @@ def handle_add_expense_to_report(self, slack_payload: Dict, user_id: str, team_i slack_client = get_slack_client(team_id) - expense_id = 'txCCVGvNpDMM' - fyle_expense = FyleExpense(user) expense_query_params = { From b597f817a17ffcddbdcf33535eebfc5477a0a686 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Thu, 12 May 2022 15:17:20 +0530 Subject: [PATCH 67/85] Location field changes --- .../slack/interactives/view_submission_handlers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index ea09691c..91cbeea1 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -194,7 +194,7 @@ def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dic form_value = self.extract_checkbox_field(form_detail) if form_value is not None: - if 'custom_field' in block_id and 'LOCATION' not in block_id and 'travel_classes' not in block_id: + if 'custom_field' in block_id and 'travel_classes' not in block_id: custom_field_mappings['name'] = expense_field_key custom_field_mappings['value'] = form_value custom_fields.append(custom_field_mappings) @@ -215,7 +215,9 @@ def extract_select_field_detail(self, expense_field_key: str, form_detail: Dict, place_id = form_detail['selected_option']['value'] if form_detail['selected_option'] is not None else None if place_id is not None: location = fyle_expense.get_place_by_place_id(place_id) - if 'locations' in expense_payload: + if 'custom_field' in block_id: + form_value = location + elif 'locations' in expense_payload: expense_payload['locations'].append(location) else: expense_payload['locations'] = [location] From 1c7728e6ecae1dba47138eeb813a2c56150664a3 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Thu, 12 May 2022 15:21:23 +0530 Subject: [PATCH 68/85] Minor date field none check --- fyle_slack_app/slack/interactives/view_submission_handlers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 91cbeea1..f33a2ef3 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -244,7 +244,8 @@ def extract_and_validate_date_field(self, form_detail: Dict, block_id: str, vali if form_detail['selected_date'] is not None and datetime.datetime.strptime(form_detail['selected_date'], '%Y-%m-%d') > datetime.datetime.now(): validation_errors[block_id] = 'Date selected cannot be in future' form_value = form_detail['selected_date'] - form_value = parse(form_value).strftime("%Y-%m-%dT%H:%M:%S.%fZ") + if form_value is not None: + form_value = parse(form_value).strftime("%Y-%m-%dT%H:%M:%S.%fZ") return form_value, validation_errors def extract_and_validate_text_field(self, form_detail: Dict, block_id: str, validation_errors: Dict): From 4720f5740b6cf53bd540694128dbca9d7c681260 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Thu, 12 May 2022 15:31:10 +0530 Subject: [PATCH 69/85] Minor unlink account fix --- fyle_slack_app/slack/commands/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index 033451bf..6f413335 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -51,7 +51,7 @@ def handle_slack_command(self, command: str, user_id: str, team_id: str, user_dm return handler(user_id, team_id, user_dm_channel_id, trigger_id) - def handle_fyle_unlink_account(self, user_id: str, team_id: str, user_dm_channel_id: str) -> JsonResponse: + def handle_fyle_unlink_account(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str) -> JsonResponse: message_block = [IN_PROGRESS_MESSAGE[slack_utils.AsyncOperation.UNLINKING_ACCOUNT.value]] slack_client = slack_utils.get_slack_client(team_id) From fdc67a44bf631098da1480da5db39189a8bc761c Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Fri, 13 May 2022 12:48:18 +0530 Subject: [PATCH 70/85] removed print statements --- .../slack/interactives/block_suggestion_handlers.py | 2 -- fyle_slack_app/slack/interactives/view_submission_handlers.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 4064bf8d..97b8299e 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -301,8 +301,6 @@ def handle_merchant_suggestion(self, slack_payload: Dict, user_id: str, team_id: suggested_merchants = fyle_expense.get_merchants(query_params) - print('suggested_merchants: {}'.format(suggested_merchants)) - merchant_options = [] if suggested_merchants['count'] > 0: diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index f33a2ef3..f4b8810a 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -74,10 +74,6 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) form_metadata = cache.get(cache_key) expense_payload['source'] = 'SLACK' - # expense_payload['spent_at'] = parse(expense_payload['spent_at']).strftime("%Y-%m-%dT%H:%M:%S.%fZ") - - print('EXPENSE -> ', json.dumps(expense_payload, indent=2)) - expense_id = form_metadata.get('expense_id') message_ts = form_metadata.get('message_ts') From f4e0ce4336e9d5c78fba141e8bf8c90b41681e0e Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Fri, 13 May 2022 13:01:46 +0530 Subject: [PATCH 71/85] removed unsued import - pylint issue --- fyle_slack_app/slack/interactives/view_submission_handlers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index f4b8810a..856911cf 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,5 +1,4 @@ import datetime -import json from typing import Callable, Dict, Union from dateutil.parser import parse From 7781535193e22fc9e6484b8199af35b7245e0523 Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Fri, 13 May 2022 16:33:38 +0530 Subject: [PATCH 72/85] Catching invalid usage while creating expense --- fyle_slack_app/slack/interactives/tasks.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 1fc7413c..39f066b9 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,3 +1,4 @@ +from cgitb import text from typing import Dict, List from django.core.cache import cache @@ -7,7 +8,7 @@ from fyle_slack_app.slack.utils import get_slack_client from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form from fyle_slack_app.slack.ui.expenses import messages as expense_messages -from fyle_slack_app.libs import utils, logger +from fyle_slack_app.libs import utils, logger, assertions from fyle_slack_app.fyle.report_approvals.views import FyleReportApproval from fyle_slack_app.models import User, UserFeedbackResponse from fyle_slack_app.slack import utils as slack_utils @@ -254,13 +255,18 @@ def handle_upsert_expense(user: User, view_id: str, team_id: str, expense_payloa if expense_id is not None: expense_payload['id'] = expense_id - expense = fyle_expense.upsert_expense(expense_payload, user.fyle_refresh_token) - view_expense_message = expense_messages.view_expense_message(expense, user) + try: + expense = fyle_expense.upsert_expense(expense_payload, user.fyle_refresh_token) + view_expense_message = expense_messages.view_expense_message(expense, user) + + if expense_id is None or message_ts is None: + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=view_expense_message) + else: + slack_client.chat_update(channel=user.slack_dm_channel_id, blocks=view_expense_message, ts=message_ts) + except assertions.InvalidUsage: + error_message = 'Seems like something went wrong while creating an expense, please try again or contact support@fylehq.com' + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, text=error_message) - if expense_id is None or message_ts is None: - slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=view_expense_message) - else: - slack_client.chat_update(channel=user.slack_dm_channel_id, blocks=view_expense_message, ts=message_ts) def handle_feedback_submission(user: User, team_id: str, form_values: Dict, private_metadata: Dict): From 0718df77d161be9212837120c2762ff598fbb7eb Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Fri, 13 May 2022 17:02:15 +0530 Subject: [PATCH 73/85] removed unused import --- fyle_slack_app/slack/interactives/tasks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 39f066b9..3ef23e3b 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -1,4 +1,3 @@ -from cgitb import text from typing import Dict, List from django.core.cache import cache From 3807abf895c8f5dbe9f9302ba03f130af50356d4 Mon Sep 17 00:00:00 2001 From: shreyanshss7 <72918700+shreyanshss7@users.noreply.github.com> Date: Tue, 17 May 2022 12:01:21 +0530 Subject: [PATCH 74/85] Expense Creation: DE Flow (#84) * added expense de flow * Added try except block * Minor * Minor * Minor Co-authored-by: shreyanshs7 --- fyle_slack_app/fyle/utils.py | 15 ++++++++++++++ fyle_slack_app/slack/events/tasks.py | 21 ++++++++++++++++++++ fyle_slack_app/slack/ui/expenses/messages.py | 8 ++++---- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/fyle_slack_app/fyle/utils.py b/fyle_slack_app/fyle/utils.py index d624a6e8..159995b9 100644 --- a/fyle_slack_app/fyle/utils.py +++ b/fyle_slack_app/fyle/utils.py @@ -238,3 +238,18 @@ def is_receipt_file_supported(file_info: Dict) -> Union[bool, str]: is_receipt_supported = False return is_receipt_supported, response_message + + +def extract_expense_from_receipt(receipt_payload: Dict, refresh_token: str) -> Dict: + access_token = get_fyle_access_token(refresh_token) + cluster_domain = get_cluster_domain(refresh_token) + + url = '{}/platform/v1/spender/expenses/create_from_receipt'.format(cluster_domain) + headers = { + 'content-type': 'application/json', + 'Authorization': 'Bearer {}'.format(access_token) + } + + response = http.post(url, json=receipt_payload, headers=headers) + assertions.assert_valid(response.status_code == 200, 'Error while creating an expense from receipt') + return response.json()['data'] diff --git a/fyle_slack_app/slack/events/tasks.py b/fyle_slack_app/slack/events/tasks.py index defabec1..f87b5dd9 100644 --- a/fyle_slack_app/slack/events/tasks.py +++ b/fyle_slack_app/slack/events/tasks.py @@ -1,3 +1,4 @@ +import base64 from typing import Dict, Union from slack_sdk import WebClient @@ -12,6 +13,7 @@ from fyle_slack_app.slack.interactives.block_action_handlers import BlockActionHandler from fyle_slack_app.slack import utils as slack_utils from fyle_slack_app.slack.ui.authorization import messages +from fyle_slack_app.slack.ui.expenses import messages as expense_messages from fyle_slack_app.slack.ui import common_messages @@ -107,6 +109,7 @@ def handle_file_shared(file_id: str, user_id: str, team_id: str): slack_client = slack_utils.get_slack_client(team_id) user = utils.get_or_none(User, slack_user_id=user_id) file_info, file_content, file_message_details = gather_shared_file_data(user, slack_client, file_id) + encoded_file = base64.b64encode(file_content).decode('utf-8') # If thread_ts is present in message, this means file has been shared in a thread if 'thread_ts' in file_message_details: @@ -141,6 +144,24 @@ def handle_file_shared(file_id: str, user_id: str, team_id: str): # This else block means file has been shared as a new message and an expense will be created with the file as receipt # i.e. data extraction flow else: + expense_creation_message = ':hourglass_flowing_sand: Creating an expense and uploading receipt :zap:' + expense_creation_message_block = common_messages.get_custom_text_section_block(expense_creation_message) + message_ts = file_message_details['ts'] + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=expense_creation_message_block, thread_ts=message_ts, reply_broadcast=True) + receipt_payload = { + "data": { + "file_name": file_info['file']['name'], + "file_content": encoded_file, + "source": "SLACK" + } + } + try: + expense = fyle_utils.extract_expense_from_receipt(receipt_payload, user.fyle_refresh_token) + view_expense_message = expense_messages.view_expense_message(expense, user) + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, blocks=view_expense_message, thread_ts=message_ts, reply_broadcast=True) + except assertions.InvalidUsage: + error_message = 'Seems like something went wrong while creating an expense, please try again or contact support@fylehq.com' + slack_client.chat_postMessage(channel=user.slack_dm_channel_id, text=error_message, thread_ts=message_ts, reply_broadcast=True) return None diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 6e654efc..7044c371 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -722,9 +722,9 @@ def expense_dialog_form( def get_expense_message_details_section(expense: Dict, expense_url: str, actions: List[Dict], receipt_message: str, report_message: str) -> List[Dict]: - spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') - - expense_details = expense['purpose'] + spent_at = utils.get_formatted_datetime(expense['spent_at'], '%B %d, %Y') if expense['spent_at'] is not None else 'Not Specified' + amount = expense['amount'] if expense['amount'] is not None else 0.00 + expense_details = expense['purpose'] if expense['purpose'] is not None else 'Not Specified' if expense['category']['name'] is not None: expense_details = '{} ({})'.format(expense_details, expense['category']['name']) @@ -734,7 +734,7 @@ def get_expense_message_details_section(expense: Dict, expense_url: str, actions 'block_id': 'expense_id.{}'.format(expense['id']), 'text': { 'type': 'mrkdwn', - 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], expense['amount']) + 'text': ':money_with_wings: An expense of *{} {}* has been created!'.format(expense['currency'], amount) }, 'accessory': { 'type': 'overflow', From 8bc1065aa540a4ab739eedd7598e13a6d97103ff Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Thu, 19 May 2022 17:56:16 +0530 Subject: [PATCH 75/85] Minor none check --- .../slack/interactives/block_suggestion_handlers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 97b8299e..2e8b0e85 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -86,10 +86,10 @@ def handle_category_suggestion(self, slack_payload: Dict, user_id: str, team_id: cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) - project = form_metadata.get('project') - - if project is not None: - category_query_params['restricted_project_ids'] = 'csn.[{}]'.format(project['id']) + if form_metadata is not None: + project = form_metadata.get('project') + if project is not None: + category_query_params['restricted_project_ids'] = 'csn.[{}]'.format(project['id']) suggested_categories = fyle_expense.get_categories(category_query_params) From e1709f46453aedb702be8c4df5417fa58165ddae Mon Sep 17 00:00:00 2001 From: shreyanshs7 Date: Thu, 19 May 2022 18:32:35 +0530 Subject: [PATCH 76/85] Minor none check --- .../slack/interactives/view_submission_handlers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 856911cf..1eb8508f 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -73,8 +73,11 @@ def handle_upsert_expense(self, slack_payload: Dict, user_id: str, team_id: str) form_metadata = cache.get(cache_key) expense_payload['source'] = 'SLACK' - expense_id = form_metadata.get('expense_id') - message_ts = form_metadata.get('message_ts') + expense_id = None + message_ts = None + if form_metadata is not None: + expense_id = form_metadata.get('expense_id') + message_ts = form_metadata.get('message_ts') # If valdiation errors are present then return errors if bool(validation_errors) is True: From b844c48d9918509eddef35622bda76cb34a2c3ec Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Thu, 2 Jun 2022 17:05:28 +0530 Subject: [PATCH 77/85] Minor changes --- fyle_slack_app/slack/interactives/tasks.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 3ef23e3b..3b02f669 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -5,7 +5,6 @@ from fyle_slack_app.fyle.expenses.views import FyleExpense from fyle_slack_app.slack.utils import get_slack_client -from fyle_slack_app.slack.ui.expenses.messages import expense_dialog_form from fyle_slack_app.slack.ui.expenses import messages as expense_messages from fyle_slack_app.libs import utils, logger, assertions from fyle_slack_app.fyle.report_approvals.views import FyleReportApproval @@ -63,7 +62,7 @@ def handle_project_selection(user: User, team_id: str, project: Dict, view_id: s cache.set(cache_key, form_metadata) - new_expense_dialog_form = expense_dialog_form( + new_expense_dialog_form = expense_messages.expense_dialog_form( **current_expense_form_details ) @@ -88,7 +87,7 @@ def handle_category_selection(user: User, team_id: str, category_id: str, view_i category_loading_block_index = next((index for (index, d) in enumerate(current_ui_blocks) if d['block_id'] == 'category_loading_block'), None) current_ui_blocks.pop(category_loading_block_index) - new_expense_dialog_form = expense_dialog_form( + new_expense_dialog_form = expense_messages.expense_dialog_form( **current_expense_form_details ) @@ -126,7 +125,7 @@ def handle_currency_selection(user: User, selected_currency: str, view_id: str, cache.set(cache_key, form_metadata) - expense_form = expense_dialog_form( + expense_form = expense_messages.expense_dialog_form( **current_expense_form_details ) @@ -160,7 +159,7 @@ def handle_amount_entered(user: User, amount_entered: float, view_id: str, team_ cache.set(cache_key, form_metadata) - expense_form = expense_dialog_form( + expense_form = expense_messages.expense_dialog_form( **current_expense_form_details ) @@ -196,7 +195,7 @@ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, cache.set(cache_key, form_metadata) - expense_form = expense_dialog_form( + expense_form = expense_messages.expense_dialog_form( expense=expense, custom_fields=custom_fields, **expense_form_details From de03b294f7bafee94657ea3ceffe79473bdc914b Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Tue, 7 Jun 2022 16:23:33 +0530 Subject: [PATCH 78/85] Modify expense-form slash command for Slack --- fyle_slack_app/slack/commands/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index 6f413335..0aa77dda 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -28,7 +28,7 @@ def _initialize_command_handlers(self): self._command_handlers = { 'fyle_unlink_account': self.handle_fyle_unlink_account, 'fyle_notification_preferences': self.handle_fyle_notification_preferences, - 'expense_form': self.handle_expense_form + 'fyle_expense_form': self.handle_fyle_expense_form } def handle_invalid_command(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str) -> JsonResponse: @@ -112,7 +112,7 @@ def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_ return JsonResponse({}, status=200) - def handle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str): + def handle_fyle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str): user = utils.get_or_none(User, slack_user_id=user_id) slack_client = slack_utils.get_slack_client(team_id) From da7325e96fca49afa48c04b60d0e97c145b6ebc3 Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Thu, 9 Jun 2022 09:59:11 +0530 Subject: [PATCH 79/85] Undo previous commit --- fyle_slack_app/slack/commands/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fyle_slack_app/slack/commands/handlers.py b/fyle_slack_app/slack/commands/handlers.py index 0aa77dda..6f413335 100644 --- a/fyle_slack_app/slack/commands/handlers.py +++ b/fyle_slack_app/slack/commands/handlers.py @@ -28,7 +28,7 @@ def _initialize_command_handlers(self): self._command_handlers = { 'fyle_unlink_account': self.handle_fyle_unlink_account, 'fyle_notification_preferences': self.handle_fyle_notification_preferences, - 'fyle_expense_form': self.handle_fyle_expense_form + 'expense_form': self.handle_expense_form } def handle_invalid_command(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str) -> JsonResponse: @@ -112,7 +112,7 @@ def handle_fyle_notification_preferences(self, user_id: str, team_id: str, user_ return JsonResponse({}, status=200) - def handle_fyle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str): + def handle_expense_form(self, user_id: str, team_id: str, user_dm_channel_id: str, trigger_id: str): user = utils.get_or_none(User, slack_user_id=user_id) slack_client = slack_utils.get_slack_client(team_id) From 84c1390b263dc95b37244e05c22bdddbba16bcc1 Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Tue, 14 Jun 2022 20:33:45 +0530 Subject: [PATCH 80/85] Fix edit expense form modal bug (#85) * Fix edit expense form modal bug * Minor change * More minor changes * Fix all the bugs --- fyle_slack_app/fyle/expenses/views.py | 44 ++++++-- fyle_slack_app/libs/utils.py | 3 + .../interactives/block_action_handlers.py | 3 +- .../interactives/block_suggestion_handlers.py | 50 +++++---- fyle_slack_app/slack/interactives/tasks.py | 21 ++-- .../interactives/view_submission_handlers.py | 101 +++++++++++++----- fyle_slack_app/slack/ui/expenses/messages.py | 89 +++++++++++++-- 7 files changed, 232 insertions(+), 79 deletions(-) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index d31ee83e..2dd44c74 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -11,6 +11,8 @@ from fyle_slack_app.fyle import utils as fyle_utils from fyle_slack_app.libs import assertions, http + + # pylint: disable=too-many-public-methods class FyleExpense: @@ -50,6 +52,19 @@ def get_custom_fields_by_category_id(self, category_id: str) -> Dict: return self.get_expense_fields(custom_fields_query_params) + def get_merchants_expense_field(self) -> Dict: + query_params = { + 'column_name': 'eq.merchant', + 'offset': 0, + 'limit': '50', + 'order': 'created_at.desc', + 'is_enabled': 'eq.{}'.format(True), + 'is_custom': 'eq.{}'.format(False) + } + + return self.get_expense_fields(query_params) + + def get_categories(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.categories.list(query_params=query_params) @@ -57,7 +72,13 @@ def get_categories(self, query_params: Dict) -> Dict: def get_projects(self, query_params: Dict) -> Dict: return self.connection.v1beta.spender.projects.list(query_params=query_params) - def get_merchants(self, query_params: Dict) -> Dict: + def get_merchants(self, query_text: str) -> Dict: + query_params = { + 'offset': 0, + 'limit': '10', + 'order': 'display_name.asc', + 'q': query_text + } return self.connection.v1beta.spender.merchants.list(query_params=query_params) def get_cost_centers(self, query_params: Dict) -> Dict: @@ -213,21 +234,26 @@ def get_expense_form_details(user: User, view_id: str) -> Dict: @staticmethod - def get_current_expense_form_details(slack_payload: Dict) -> Dict: + def get_current_expense_form_details(slack_payload: Dict, user: User) -> Dict: + fyle_expense = FyleExpense(user) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) - fields_render_property = form_metadata['fields_render_property'] - - additional_currency_details = form_metadata.get('additional_currency_details') - - add_to_report = form_metadata.get('add_to_report') + if form_metadata is not None: + fields_render_property = form_metadata['fields_render_property'] + additional_currency_details = form_metadata.get('additional_currency_details') + add_to_report = form_metadata.get('add_to_report') + project = form_metadata.get('project') + else: + expense_form_details = fyle_expense.get_expense_form_details(user, slack_payload['container']['view_id']) + fields_render_property = expense_form_details['fields_render_property'] + additional_currency_details = expense_form_details['additional_currency_details'] + add_to_report = expense_form_details['add_to_report'] + project = expense_form_details['project'] current_ui_blocks = slack_payload['view']['blocks'] - project = form_metadata.get('project') - custom_field_blocks = [] for block in current_ui_blocks: diff --git a/fyle_slack_app/libs/utils.py b/fyle_slack_app/libs/utils.py index 356ef013..ba41c228 100644 --- a/fyle_slack_app/libs/utils.py +++ b/fyle_slack_app/libs/utils.py @@ -27,6 +27,9 @@ def get_or_none(model: Model, **kwargs: Any) -> Union[None, Model]: def get_formatted_datetime(datetime_value: datetime, required_format: str) -> str: + # Enable support for parsing arbitrary ISO 8601 strings ('Z' strings specifically) + datetime_value = datetime_value.replace('Z', '') + datetime_value = datetime.datetime.fromisoformat(datetime_value) formatted_datetime = datetime_value.strftime(required_format) return formatted_datetime diff --git a/fyle_slack_app/slack/interactives/block_action_handlers.py b/fyle_slack_app/slack/interactives/block_action_handlers.py index 190f395c..ac116cb2 100644 --- a/fyle_slack_app/slack/interactives/block_action_handlers.py +++ b/fyle_slack_app/slack/interactives/block_action_handlers.py @@ -371,7 +371,8 @@ def handle_add_to_report(self, slack_payload: Dict, user_id: str, team_id: str) slack_client = get_slack_client(team_id) - current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload) + user = utils.get_or_none(User, slack_user_id=user_id) + current_expense_form_details = FyleExpense.get_current_expense_form_details(slack_payload, user) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) diff --git a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py index 2e8b0e85..b141da21 100644 --- a/fyle_slack_app/slack/interactives/block_suggestion_handlers.py +++ b/fyle_slack_app/slack/interactives/block_suggestion_handlers.py @@ -289,29 +289,35 @@ def handle_merchant_suggestion(self, slack_payload: Dict, user_id: str, team_id: user = utils.get_or_none(User, slack_user_id=user_id) merchant_value_entered = slack_payload['value'] - - fyle_expense = FyleExpense(user) - - query_params = { - 'offset': 0, - 'limit': '10', - 'order': 'display_name.asc', - 'q': merchant_value_entered - } - - suggested_merchants = fyle_expense.get_merchants(query_params) - merchant_options = [] + fyle_expense = FyleExpense(user) - if suggested_merchants['count'] > 0: - for merchant in suggested_merchants['data']: - option = { - 'text': { - 'type': 'plain_text', - 'text': '{}'.format(merchant['display_name']) - }, - 'value': merchant['display_name'], - } - merchant_options.append(option) + # Fetch all the options (choices) from Merchant expense field + merchants_expense_field = fyle_expense.get_merchants_expense_field() + + if len(merchants_expense_field['data'][0]['options']) > 0: + suggested_merchants = merchants_expense_field['data'][0]['options'] + + else: + # Fetch the merchant list from merchants table in DB + suggested_merchants = fyle_expense.get_merchants(merchant_value_entered) + + if suggested_merchants['count'] > 0: + # Show merchants suggestions from merchants list + suggested_merchants = [merchant['display_name'] for merchant in suggested_merchants['data']] + else: + # Else, show the suggestion as it is, what the user has entered + # In this case, this user entered text will get stored as a new merchant in merchants table + suggested_merchants = [merchant_value_entered] + + for merchant in suggested_merchants: + option = { + 'text': { + 'type': 'plain_text', + 'text': '{}'.format(merchant) + }, + 'value': merchant + } + merchant_options.append(option) return merchant_options diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 3b02f669..b7a08f59 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -42,7 +42,7 @@ def handle_project_selection(user: User, team_id: str, project: Dict, view_id: s slack_client = get_slack_client(team_id) fyle_expense = FyleExpense(user) - current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload, user) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) @@ -77,7 +77,7 @@ def handle_category_selection(user: User, team_id: str, category_id: str, view_i custom_fields = fyle_expense.get_custom_fields_by_category_id(category_id) - current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload, user) current_expense_form_details['custom_fields'] = custom_fields @@ -100,7 +100,7 @@ def handle_currency_selection(user: User, selected_currency: str, view_id: str, fyle_expense = FyleExpense(user) - current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload, user) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) @@ -142,7 +142,7 @@ def handle_amount_entered(user: User, amount_entered: float, view_id: str, team_ selected_currency = form_current_state['SELECT_default_field_currency_block']['currency']['selected_option']['value'] - current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload) + current_expense_form_details = fyle_expense.get_current_expense_form_details(slack_payload, user) cache_key = '{}.form_metadata'.format(slack_payload['view']['id']) form_metadata = cache.get(cache_key) @@ -190,9 +190,10 @@ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, # Add additional metadata to differentiate create and edit expense # message_ts to update message in edit case - form_metadata['expense_id'] = expense_id - form_metadata['message_ts'] = slack_payload['container']['message_ts'] - + if form_metadata is not None: + form_metadata['expense_id'] = expense_id + form_metadata['message_ts'] = slack_payload['container']['message_ts'] + cache.set(cache_key, form_metadata) expense_form = expense_messages.expense_dialog_form( @@ -242,12 +243,12 @@ def handle_upsert_expense(user: User, view_id: str, team_id: str, expense_payloa cache_key = '{}.form_metadata'.format(view_id) form_metadata = cache.get(cache_key) - if 'foreign_currency' in form_metadata['additional_currency_details']: + if form_metadata and 'additional_currency_details' in form_metadata and form_metadata['additional_currency_details'] and 'foreign_currency' in form_metadata['additional_currency_details']: expense_payload['foreign_currency'] = form_metadata['additional_currency_details']['foreign_currency'] - expense_payload['foreign_amount'] = form_metadata['additional_currency_details']['total_amount'] + expense_payload['foreign_amount'] = expense_payload['claim_amount'] expense_payload['claim_amount'] = form_metadata['additional_currency_details']['total_amount'] - if 'project' in form_metadata and form_metadata['project'] is not None: + if form_metadata and 'project' in form_metadata and form_metadata['project']: expense_payload['project_id'] = form_metadata['project']['id'] if expense_id is not None: diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 1eb8508f..869087d5 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -1,6 +1,6 @@ import datetime -from typing import Callable, Dict, Union +from typing import Callable, List, Dict, Union from dateutil.parser import parse from django.http.response import JsonResponse @@ -160,6 +160,61 @@ def handle_feedback_submission(self, slack_payload: Dict, user_id: str, team_id: return JsonResponse({}) + def get_travel_class_list(self, expense_payload: Dict, block_id: str, form_value: str) -> List[Dict]: + # This method will give you the travel class object - travel_classes field + travel_classes = [None, None] + + if 'travel_classes' in expense_payload: + travel_classes = expense_payload['travel_classes'] + + if '_journey_travel_class' in block_id: + travel_classes[0] = form_value + elif '_return_travel_class' in block_id: + if len(travel_classes) == 1: + travel_classes.append(form_value) + else: + travel_classes[1] = form_value + + return travel_classes + + + def append_into_expense_payload_for_upsert_expense(self, expense_payload: Dict, expense_field_key: str, form_value: any, block_id: str) -> Dict: + # expense_payload is used as the payload which is sent to POST request of /spender/expenses API + # Only single expense field will be appended to the expense payload at a time + # Can refer the expense post payload structure from here: https://docs.fylehq.com/docs/fyle-platform-docs/ -> Spender APIs -> Expenses -> Crreate or upadte expense (POST) + + if 'from_dt' in block_id: + expense_payload['started_at'] = form_value + + elif 'to_dt' in block_id: + expense_payload['ended_at'] = form_value + + elif 'custom_field' in block_id: + custom_field = { + 'name': expense_field_key, + 'value': form_value + } + if 'custom_fields' in expense_payload: + expense_payload['custom_fields'].append(custom_field) + else: + expense_payload['custom_fields'] = [custom_field] + + elif 'LOCATION' in block_id: + if 'locations' in expense_payload: + expense_payload['locations'].append(form_value) + else: + expense_payload['locations'] = [form_value] + + elif 'travel_class' in block_id: + travel_classes = self.get_travel_class_list(expense_payload, block_id, form_value) + expense_payload['travel_classes'] = travel_classes + + else: + expense_payload[expense_field_key] = form_value + + return expense_payload + + def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dict, Dict]: expense_payload = {} validation_errors = {} @@ -171,12 +226,11 @@ def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dic for expense_field_key, form_detail in value.items(): form_value = None if form_detail['type'] in ['static_select', 'external_select']: - expense_field_key, form_value, expense_payload = self.extract_select_field_detail( + expense_field_key, form_value = self.extract_select_field_detail( expense_field_key, form_detail, block_id, - fyle_expense, - expense_payload + fyle_expense ) if form_detail['type'] in ['multi_static_select', 'multi_external_select']: @@ -190,20 +244,18 @@ def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dic elif form_detail['type'] == 'checkboxes': form_value = self.extract_checkbox_field(form_detail) - - if form_value is not None: - if 'custom_field' in block_id and 'travel_classes' not in block_id: - custom_field_mappings['name'] = expense_field_key - custom_field_mappings['value'] = form_value - custom_fields.append(custom_field_mappings) - else: - if 'LOCATION' not in block_id and 'travel_classes' not in block_id: - expense_payload[expense_field_key] = form_value - expense_payload['custom_fields'] = custom_fields + + if form_value is not None: + expense_payload = self.append_into_expense_payload_for_upsert_expense( + expense_payload, + expense_field_key, + form_value, + block_id + ) return expense_payload, validation_errors - def extract_select_field_detail(self, expense_field_key: str, form_detail: Dict, block_id: str, fyle_expense: FyleExpense, expense_payload: Dict): + def extract_select_field_detail(self, expense_field_key: str, form_detail: Dict, block_id: str, fyle_expense: FyleExpense): form_value = None if form_detail['selected_option'] is not None: form_value = form_detail['selected_option']['value'] @@ -213,21 +265,14 @@ def extract_select_field_detail(self, expense_field_key: str, form_detail: Dict, place_id = form_detail['selected_option']['value'] if form_detail['selected_option'] is not None else None if place_id is not None: location = fyle_expense.get_place_by_place_id(place_id) - if 'custom_field' in block_id: - form_value = location - elif 'locations' in expense_payload: - expense_payload['locations'].append(location) - else: - expense_payload['locations'] = [location] - if 'travel_classes' in block_id: + form_value = location + form_value['display'] = location['formatted_address'] + + if 'travel_class' in block_id: travel_class = form_detail['selected_option']['value'] if form_detail['selected_option'] is not None else None - if travel_class is not None: - if 'travel_classes' in expense_payload: - expense_payload['travel_classes'].append(travel_class) - else: - expense_payload['travel_classes'] = [travel_class] + form_value = travel_class - return expense_field_key, form_value, expense_payload + return expense_field_key, form_value def extract_multi_select_field(self, expense_field_key: str, form_detail: Dict, block_id: str): if 'USER_LIST' in block_id: diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 7044c371..dce28cde 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -8,17 +8,55 @@ from fyle_slack_app.fyle import utils as fyle_utils -def get_custom_field_value(custom_fields: List, action_id: str) -> Any: + +def get_custom_field_value(custom_fields: List, action_id: str, field_type: str) -> Any: value = None for custom_field in custom_fields: if custom_field['name'] == action_id: + # if field_type == 'LOCATION': + # value = {} + # value['location_text'] = custom_field['value']['formatted_address'] + # value['location_id'] = custom_field['value']['id'] + + # else: value = custom_field['value'] break return value +def get_additional_field_value(expense: Dict, action_id: str) -> Any: + value = None + if 'flight_journey_travel_class' in action_id or 'train_travel_class' in action_id or 'bus_travel_class' in action_id: + value = expense['travel_classes'][0] if 'travel_classes' in expense and expense['travel_classes'] else None + elif 'flight_return_travel_class' in action_id: + value = expense['travel_classes'][1] if 'travel_classes' in expense and expense['travel_classes'] else None + elif 'from_dt' in action_id: + value = expense['started_at'] if 'started_at' in expense and expense['started_at'] else None + elif 'to_dt' in action_id: + value = expense['ended_at'] if 'ended_at' in expense and expense['ended_at'] else None + elif 'location1' in action_id and len(expense['locations']) > 0: + value = {} + if 'locations' in expense and expense['locations'][0] and expense['locations'][0]['formatted_address']: + # value['location_text'] = expense['locations'][0]['formatted_address'] + # value['location_id'] = expense['locations'][0]['id'] + value = expense['locations'][0] + else: + value = None + elif 'location2' in action_id and len(expense['locations']) > 0: + value = {} + if 'locations' in expense and expense['locations'][1] and expense['locations'][1]['formatted_address']: + # value['location_text'] = expense['locations'][1]['formatted_address'] + # value['location_id'] = expense['locations'][1]['id'] + value = expense['locations'][1] + else: + value = None + else: + value = str(expense[action_id]) if action_id in expense else None + return value + + # pylint: disable=too-many-branches -# is_additional_field is for fields which are not custom fields but are part of a specific categories +# is_additional_field is for fields which are not custom fields but are part of specific categories def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = False, expense: Dict = None) -> Dict: block_id = '{}_block'.format(field_details['column_name']) @@ -41,10 +79,10 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F # If already exisiting expense is passed then get the custom field value for that expense and add it to input fields if expense is not None: if is_additional_field is True: - custom_field_value = expense[action_id] + custom_field_value = get_additional_field_value(expense, action_id) elif field_details['is_custom'] is True and len(expense['custom_fields']) > 0: - custom_field_value = get_custom_field_value(expense['custom_fields'], field_details['field_name']) + custom_field_value = get_custom_field_value(expense['custom_fields'], field_details['field_name'], field_details['type']) if field_details['type'] in ['NUMBER', 'TEXT']: custom_field = { @@ -128,7 +166,8 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F } ) - custom_field['element']['initial_options'] = initial_options + if len(custom_field_value) > 0: + custom_field['element']['initial_options'] = initial_options elif field_details['type'] == 'BOOLEAN': checkbox_option = { @@ -153,7 +192,8 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F } if custom_field_value is not None: - custom_field['element']['initial_option'] = checkbox_option + checkbox_option['value'] = field_details['field_name'] + custom_field['element']['initial_options'] = [checkbox_option] elif field_details['type'] == 'DATE': custom_field = { @@ -175,8 +215,7 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F } if custom_field_value is not None: - custom_field['element']['initial_date'] = utils.get_formatted_datetime(custom_field_value, '%B %d, %Y') - + custom_field['element']['initial_date'] = utils.get_formatted_datetime(custom_field_value, '%Y-%m-%d') elif field_details['type'] == 'USER_LIST': block_id = '{}__{}'.format(block_id, field_details['field_name']) @@ -199,6 +238,29 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F } } + if custom_field_value is not None: + # custom_field['element']['initial_options'] = { + # 'text': { + # 'type': 'plain_text', + # 'text': field_details['field_name'], + # }, + # 'value': custom_field_value, + # } + + initial_options = [] + for value in custom_field_value: + initial_options.append( + { + 'text': { + 'type': 'plain_text', + 'text': value, + }, + 'value': value, + } + ) + + if len(custom_field_value) > 0: + custom_field['element']['initial_options'] = initial_options elif field_details['type'] == 'LOCATION': block_id = '{}__{}'.format(block_id, field_details['field_name']) @@ -221,6 +283,15 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F } } + if custom_field_value is not None: + location_id = custom_field_value['id'] if 'id' in custom_field_value else 'None' + custom_field['element']['initial_option'] = { + 'text': { + 'type': 'plain_text', + 'text': custom_field_value['formatted_address'], + }, + 'value': location_id, + } return custom_field @@ -678,7 +749,7 @@ def expense_dialog_form( # If custom fields are present, render them in the form if custom_fields is not None: - # If cached custom fields are pass, render/ add them to UI directly + # If cached custom fields are present, render/ add them to UI directly # Cached custom fields come from slack request payload which sends UI blocks on interaction if isinstance(custom_fields, list): view['blocks'].extend(custom_fields) From c51bf7654b60312ddbb0db76b366a8beb07435f2 Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Tue, 14 Jun 2022 23:08:32 +0530 Subject: [PATCH 81/85] Remove unused commented code --- fyle_slack_app/slack/ui/expenses/messages.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index dce28cde..ff9ee3da 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -13,12 +13,6 @@ def get_custom_field_value(custom_fields: List, action_id: str, field_type: str) value = None for custom_field in custom_fields: if custom_field['name'] == action_id: - # if field_type == 'LOCATION': - # value = {} - # value['location_text'] = custom_field['value']['formatted_address'] - # value['location_id'] = custom_field['value']['id'] - - # else: value = custom_field['value'] break return value @@ -37,16 +31,12 @@ def get_additional_field_value(expense: Dict, action_id: str) -> Any: elif 'location1' in action_id and len(expense['locations']) > 0: value = {} if 'locations' in expense and expense['locations'][0] and expense['locations'][0]['formatted_address']: - # value['location_text'] = expense['locations'][0]['formatted_address'] - # value['location_id'] = expense['locations'][0]['id'] value = expense['locations'][0] else: value = None elif 'location2' in action_id and len(expense['locations']) > 0: value = {} if 'locations' in expense and expense['locations'][1] and expense['locations'][1]['formatted_address']: - # value['location_text'] = expense['locations'][1]['formatted_address'] - # value['location_id'] = expense['locations'][1]['id'] value = expense['locations'][1] else: value = None @@ -239,14 +229,6 @@ def generate_custom_fields_ui(field_details: Dict, is_additional_field: bool = F } if custom_field_value is not None: - # custom_field['element']['initial_options'] = { - # 'text': { - # 'type': 'plain_text', - # 'text': field_details['field_name'], - # }, - # 'value': custom_field_value, - # } - initial_options = [] for value in custom_field_value: initial_options.append( From 28a8fbc5fd3449a413d95eef1854f578476d78c4 Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Tue, 14 Jun 2022 23:26:11 +0530 Subject: [PATCH 82/85] Fix pylint errors --- fyle_slack_app/libs/utils.py | 2 +- fyle_slack_app/slack/interactives/tasks.py | 2 +- .../slack/interactives/view_submission_handlers.py | 12 +++++------- fyle_slack_app/slack/ui/expenses/messages.py | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/fyle_slack_app/libs/utils.py b/fyle_slack_app/libs/utils.py index ba41c228..0cf4d74a 100644 --- a/fyle_slack_app/libs/utils.py +++ b/fyle_slack_app/libs/utils.py @@ -29,7 +29,7 @@ def get_or_none(model: Model, **kwargs: Any) -> Union[None, Model]: def get_formatted_datetime(datetime_value: datetime, required_format: str) -> str: # Enable support for parsing arbitrary ISO 8601 strings ('Z' strings specifically) datetime_value = datetime_value.replace('Z', '') - + datetime_value = datetime.datetime.fromisoformat(datetime_value) formatted_datetime = datetime_value.strftime(required_format) return formatted_datetime diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index b7a08f59..1c327809 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -193,7 +193,7 @@ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, if form_metadata is not None: form_metadata['expense_id'] = expense_id form_metadata['message_ts'] = slack_payload['container']['message_ts'] - + cache.set(cache_key, form_metadata) expense_form = expense_messages.expense_dialog_form( diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 869087d5..585625e7 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -166,11 +166,11 @@ def get_travel_class_list(self, expense_payload: Dict, block_id: str, form_value if 'travel_classes' in expense_payload: travel_classes = expense_payload['travel_classes'] - + if '_journey_travel_class' in block_id: travel_classes[0] = form_value elif '_return_travel_class' in block_id: - if len(travel_classes) == 1: + if len(travel_classes) == 1: travel_classes.append(form_value) else: travel_classes[1] = form_value @@ -181,8 +181,8 @@ def get_travel_class_list(self, expense_payload: Dict, block_id: str, form_value def append_into_expense_payload_for_upsert_expense(self, expense_payload: Dict, expense_field_key: str, form_value: any, block_id: str) -> Dict: # expense_payload is used as the payload which is sent to POST request of /spender/expenses API # Only single expense field will be appended to the expense payload at a time - # Can refer the expense post payload structure from here: https://docs.fylehq.com/docs/fyle-platform-docs/ -> Spender APIs -> Expenses -> Crreate or upadte expense (POST) - + # Can refer the expense post payload structure from here: https://docs.fylehq.com/docs/fyle-platform-docs/ -> Spender APIs -> Expenses -> Crreate or upadte expense (POST) + if 'from_dt' in block_id: expense_payload['started_at'] = form_value @@ -218,11 +218,9 @@ def append_into_expense_payload_for_upsert_expense(self, expense_payload: Dict, def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dict, Dict]: expense_payload = {} validation_errors = {} - custom_fields = [] fyle_expense = FyleExpense(user) for block_id, value in form_values.items(): - custom_field_mappings = {} for expense_field_key, form_detail in value.items(): form_value = None if form_detail['type'] in ['static_select', 'external_select']: @@ -247,7 +245,7 @@ def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dic if form_value is not None: expense_payload = self.append_into_expense_payload_for_upsert_expense( - expense_payload, + expense_payload, expense_field_key, form_value, block_id diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index ff9ee3da..4676050d 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines +# pylint: disable=too-many-statements from typing import Any, Dict, List import datetime From c01dc3de4e7c4c9e41912b69dc51b8ed43da4e60 Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Tue, 14 Jun 2022 23:29:05 +0530 Subject: [PATCH 83/85] Fix pylint errors --- fyle_slack_app/slack/interactives/view_submission_handlers.py | 4 ++-- fyle_slack_app/slack/ui/expenses/messages.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fyle_slack_app/slack/interactives/view_submission_handlers.py b/fyle_slack_app/slack/interactives/view_submission_handlers.py index 585625e7..02d3f3c4 100644 --- a/fyle_slack_app/slack/interactives/view_submission_handlers.py +++ b/fyle_slack_app/slack/interactives/view_submission_handlers.py @@ -242,8 +242,8 @@ def extract_form_values_and_validate(self, user, form_values: Dict) -> Union[Dic elif form_detail['type'] == 'checkboxes': form_value = self.extract_checkbox_field(form_detail) - - if form_value is not None: + + if form_value is not None: expense_payload = self.append_into_expense_payload_for_upsert_expense( expense_payload, expense_field_key, diff --git a/fyle_slack_app/slack/ui/expenses/messages.py b/fyle_slack_app/slack/ui/expenses/messages.py index 4676050d..3dbf433b 100644 --- a/fyle_slack_app/slack/ui/expenses/messages.py +++ b/fyle_slack_app/slack/ui/expenses/messages.py @@ -1,3 +1,4 @@ +# pylint: disable=too-many-lines # pylint: disable=too-many-statements from typing import Any, Dict, List From b3ea4cd87d2034a7ae986072dc6a138c6d3c1bff Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Wed, 15 Jun 2022 17:18:09 +0530 Subject: [PATCH 84/85] Add trackers for expense creation from slack (#87) * Add trackers for expense creation from slack * Minor changes --- fyle_slack_app/fyle/expenses/views.py | 22 ++++++++++++++++++++++ fyle_slack_app/slack/commands/tasks.py | 2 ++ fyle_slack_app/slack/events/tasks.py | 3 +++ fyle_slack_app/slack/interactives/tasks.py | 3 +++ 4 files changed, 30 insertions(+) diff --git a/fyle_slack_app/fyle/expenses/views.py b/fyle_slack_app/fyle/expenses/views.py index 2dd44c74..32df7235 100644 --- a/fyle_slack_app/fyle/expenses/views.py +++ b/fyle_slack_app/fyle/expenses/views.py @@ -9,7 +9,9 @@ from fyle_slack_app.fyle.utils import get_fyle_sdk_connection from fyle_slack_app.models.users import User from fyle_slack_app.fyle import utils as fyle_utils +from fyle_slack_app.fyle.notifications.views import FyleNotificationView from fyle_slack_app.libs import assertions, http +from fyle_slack_app import tracking @@ -283,3 +285,23 @@ def get_expense_by_id(self, expense_id: str) -> Dict: response = self.connection.v1beta.spender.expenses.list(query_params=query_params) expense = response['data'] if response['count'] == 1 else None return expense + + + @staticmethod + def get_expense_creation_tracking_data(user: User, expense_id: str = None) -> Dict: + event_data = FyleNotificationView.get_event_data(user) + event_data['org_id'] = user.fyle_org_id + if expense_id is not None: + event_data['expense_id'] = expense_id + + return event_data + + + @staticmethod + def track_expense_creation(user: User, event_name: str, expense_id: str=None) -> Dict: + event_data = FyleExpense.get_expense_creation_tracking_data(user, expense_id) + + tracking.identify_user(user.email) + tracking.track_event(user.email, event_name, event_data) + + return event_data diff --git a/fyle_slack_app/slack/commands/tasks.py b/fyle_slack_app/slack/commands/tasks.py index fb07ea38..10c4dde9 100644 --- a/fyle_slack_app/slack/commands/tasks.py +++ b/fyle_slack_app/slack/commands/tasks.py @@ -112,4 +112,6 @@ def open_expense_form(user: User, team_id: str, view_id: str) -> None: **expense_form_details ) + FyleExpense(user).track_expense_creation(user, 'User opened Expense Form modal using Slack slash command') + slack_client.views_update(view=expense_form, view_id=view_id) diff --git a/fyle_slack_app/slack/events/tasks.py b/fyle_slack_app/slack/events/tasks.py index f87b5dd9..642f6b07 100644 --- a/fyle_slack_app/slack/events/tasks.py +++ b/fyle_slack_app/slack/events/tasks.py @@ -162,6 +162,9 @@ def handle_file_shared(file_id: str, user_id: str, team_id: str): except assertions.InvalidUsage: error_message = 'Seems like something went wrong while creating an expense, please try again or contact support@fylehq.com' slack_client.chat_postMessage(channel=user.slack_dm_channel_id, text=error_message, thread_ts=message_ts, reply_broadcast=True) + + FyleExpense(user).track_expense_creation(user, 'Expense created from uploading Receipt in Slack', expense['id']) + return None diff --git a/fyle_slack_app/slack/interactives/tasks.py b/fyle_slack_app/slack/interactives/tasks.py index 1c327809..aab045db 100644 --- a/fyle_slack_app/slack/interactives/tasks.py +++ b/fyle_slack_app/slack/interactives/tasks.py @@ -204,6 +204,8 @@ def handle_edit_expense(user: User, expense_id: str, team_id: str, view_id: str, slack_client.views_update(view=expense_form, view_id=view_id) + fyle_expense.track_expense_creation(user, 'User clicked on Complete Expense button', expense['id']) + def handle_submit_report_dialog(user: User, team_id: str, report_id: str, view_id: str): @@ -266,6 +268,7 @@ def handle_upsert_expense(user: User, view_id: str, team_id: str, expense_payloa error_message = 'Seems like something went wrong while creating an expense, please try again or contact support@fylehq.com' slack_client.chat_postMessage(channel=user.slack_dm_channel_id, text=error_message) + fyle_expense.track_expense_creation(user, 'Expense created from Expense Form modal', expense_id) def handle_feedback_submission(user: User, team_id: str, form_values: Dict, private_metadata: Dict): From 184c3d8558005025be6f12f6bd8d8eb2e6e1451f Mon Sep 17 00:00:00 2001 From: Jatin Sharma Date: Wed, 15 Jun 2022 17:44:39 +0530 Subject: [PATCH 85/85] Fix pylint error --- fyle_slack_app/slack/events/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fyle_slack_app/slack/events/tasks.py b/fyle_slack_app/slack/events/tasks.py index 642f6b07..5d586744 100644 --- a/fyle_slack_app/slack/events/tasks.py +++ b/fyle_slack_app/slack/events/tasks.py @@ -164,7 +164,7 @@ def handle_file_shared(file_id: str, user_id: str, team_id: str): slack_client.chat_postMessage(channel=user.slack_dm_channel_id, text=error_message, thread_ts=message_ts, reply_broadcast=True) FyleExpense(user).track_expense_creation(user, 'Expense created from uploading Receipt in Slack', expense['id']) - + return None