leadguru-jobs 0.647.0__py3-none-any.whl → 0.648.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: leadguru_jobs
3
- Version: 0.647.0
3
+ Version: 0.648.0
4
4
  Summary: LGT jobs builds
5
5
  Author-email: developer@leadguru.co
6
6
  Classifier: Development Status :: 5 - Production/Stable
@@ -21,7 +21,7 @@ lgt_jobs/jobs/mass_message.py,sha256=1mFcBlL2MhzLbj5yrd_NyJc7TXDWCcROAzGDnr0miMU
21
21
  lgt_jobs/jobs/send_code.py,sha256=dIlLPkG3GgGKIEqGiElyzrtdrnJNTL1Ak2V0xnE-WIQ,824
22
22
  lgt_jobs/jobs/send_slack_message.py,sha256=yJVzGQijM5KL-QDA0EQy-8-kvR7TSZmRw-4tX1ge5Hk,2343
23
23
  lgt_jobs/jobs/update_slack_profile.py,sha256=GU28azaNz1zYsCAAF_BShJxLUecQb7AbO5XPgiv7yHg,3654
24
- lgt_jobs/jobs/workspace_connect.py,sha256=iapbDVdFL6ZkZJlIZywASATAmK25ZikSd1IiB1LLrRo,7203
24
+ lgt_jobs/jobs/workspace_connect.py,sha256=E0vVVkgepZbj0d6HGHXLBU-8xEsGm2zfptUH5KmFXVM,7354
25
25
  lgt_jobs/lgt_common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  lgt_jobs/lgt_common/helpers.py,sha256=3T47_Abw7datSIxVqIHTCB-sh-rnahWrmrFG8sSY9ZE,178
27
27
  lgt_jobs/lgt_common/discord_client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -39,10 +39,10 @@ lgt_jobs/lgt_common/slack_client/web_client.py,sha256=WCu8mqYhauuxp9iDAMsjocchKD
39
39
  lgt_jobs/lgt_data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
40
  lgt_jobs/lgt_data/analytics.py,sha256=yfrFPXrBofzZqUNn479JkrbZXDa2ljgk-Le0YrQ9rew,21690
41
41
  lgt_jobs/lgt_data/engine.py,sha256=GY8FOl3dl7euKjQRML_H3ophaSTgfQ8ICMRv1NpE2oE,7745
42
- lgt_jobs/lgt_data/enums.py,sha256=27Gd_o5QVCrSUMIfL0DJ72xV77T9fK0kYrOaC78O0SU,2292
42
+ lgt_jobs/lgt_data/enums.py,sha256=si1DwMf4pOJYRYjz_c_SxmWxU5fBr8nN0kH4zFzNQ5Y,2338
43
43
  lgt_jobs/lgt_data/helpers.py,sha256=S_1P7pnx14dllmen-TzjA9pkKWQr39x8-v6iv7FUYnQ,423
44
44
  lgt_jobs/lgt_data/model.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- lgt_jobs/lgt_data/mongo_repository.py,sha256=0MnMiEANtXQ9n7GCf9rhS25mmIy_LbDV5QRTt4jXRuw,54367
45
+ lgt_jobs/lgt_data/mongo_repository.py,sha256=TxJ5jDVfZODHPX--a4Nc8DQv572nrJ0VL494imZpxrI,55528
46
46
  lgt_jobs/lgt_data/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
47
  lgt_jobs/lgt_data/models/base.py,sha256=YaiceCaaF45snOLx-bQoYJ1aTWrfgMQeHWjAaRnzGKE,592
48
48
  lgt_jobs/lgt_data/models/boards/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -75,7 +75,7 @@ lgt_jobs/lgt_data/models/leads/extended_lead.py,sha256=ezFrKv-f5GBXr3QAM-V_qJQ-i
75
75
  lgt_jobs/lgt_data/models/leads/lead.py,sha256=mTZh8YV06nX9OWYvv_X7rDdIperB-RPChrEuheeYMU0,1300
76
76
  lgt_jobs/lgt_data/models/leads/message.py,sha256=7mXwYfSpmhY_zC8TfUdAZzxsPP-02bOqVjdkN4Vid5s,1533
77
77
  lgt_jobs/lgt_data/models/notifications/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
- lgt_jobs/lgt_data/models/notifications/notification.py,sha256=bUtpKs1_--A3p8N2cQC-cQIa0ja9tiUDofSG38-j13I,10456
78
+ lgt_jobs/lgt_data/models/notifications/notification.py,sha256=WMdtEz2Krr8efsgsDIitklDG6agDu00z6J6UfW6FCTg,12803
79
79
  lgt_jobs/lgt_data/models/notifications/notification_settings.py,sha256=VFddLvk2ROz4n50wYuZmqBDn7MssFwj-e9DbWweIVA4,2780
80
80
  lgt_jobs/lgt_data/models/people/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
81
  lgt_jobs/lgt_data/models/people/people.py,sha256=pm60uMVmNIxOGCBAtZWhmXSJOe0SJ4C1y_HurUq00oY,3420
@@ -103,7 +103,7 @@ lgt_jobs/lgt_data/repositories/post/posts.py,sha256=ZA19L-CYdxLhlapyMQtSJzP3QMUl
103
103
  lgt_jobs/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
104
104
  lgt_jobs/services/web_client.py,sha256=oMyWJxwGeIe3f40fPT7xcisjDg3BhA3Ipf8dr1jVT-Y,1549
105
105
  lgt_jobs/templates/new_message.html,sha256=dZl8UmdAOOMq4nidvAgMFroSrTV7Pw0RWt2yLp_2idg,6989
106
- leadguru_jobs-0.647.0.dist-info/METADATA,sha256=yownuhghwkwO20wEC1wW7paRk9ys3Yfo0_z_oBxogE0,1319
107
- leadguru_jobs-0.647.0.dist-info/WHEEL,sha256=nCVcAvsfA9TDtwGwhYaRrlPhTLV9m-Ga6mdyDtuwK18,91
108
- leadguru_jobs-0.647.0.dist-info/top_level.txt,sha256=rIuw1DqwbnZyeoarBSC-bYeGOhv9mZBs7_afl9q4_JI,9
109
- leadguru_jobs-0.647.0.dist-info/RECORD,,
106
+ leadguru_jobs-0.648.0.dist-info/METADATA,sha256=82-dhLiD4Bi-gtndSQ8Ml3ML8ITpSu6gE2gtHHRGeLE,1319
107
+ leadguru_jobs-0.648.0.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
108
+ leadguru_jobs-0.648.0.dist-info/top_level.txt,sha256=rIuw1DqwbnZyeoarBSC-bYeGOhv9mZBs7_afl9q4_JI,9
109
+ leadguru_jobs-0.648.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (73.0.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,5 @@
1
+ import random
2
+ import time
1
3
  from abc import ABC
2
4
  from typing import Dict
3
5
  import requests
@@ -45,35 +47,41 @@ class ConnectSlackAccountJob(BaseBackgroundJob, ABC):
45
47
  if code_confirmed_response.status_code != 200:
46
48
  log.warning(f'Unable to confirm code due to error: {code_confirmed_response.content}')
47
49
  slack_user.status = StatusConnection.FAILED
48
- UserMongoRepository().set(current_user.id,
49
- slack_users=[user.to_dic() for user in current_user.slack_users])
50
+ UserMongoRepository().set(current_user.id, slack_users=[user.to_dic() for user in current_user.slack_users])
50
51
  return
51
52
 
52
53
  code_confirmed = code_confirmed_response.json().get('ok', False)
53
54
  if not code_confirmed:
54
55
  slack_user.status = StatusConnection.FAILED
55
- UserMongoRepository().set(current_user.id,
56
- slack_users=[user.to_dic() for user in current_user.slack_users])
56
+ UserMongoRepository().set(current_user.id, slack_users=[user.to_dic() for user in current_user.slack_users])
57
57
  log.warning(f'Invalid code')
58
58
  return
59
59
 
60
60
  slack_user.cookies = client.client.cookies = code_confirmed_response.cookies.get_dict()
61
61
 
62
- workspaces_response = client.find_workspaces(data.user_agent)
63
- if workspaces_response.status_code != 200:
64
- slack_user.status = StatusConnection.FAILED
65
- UserMongoRepository().set(current_user.id,
66
- slack_users=[user.to_dic() for user in current_user.slack_users])
67
- log.warning(f'Unable to get workspaces due to error: {workspaces_response.content}')
68
- return
69
- if not workspaces_response.json().get('ok', False):
70
- slack_user.status = StatusConnection.FAILED
71
- log.warning(f'Unable to get workspaces due to error: {workspaces_response.content}')
62
+ workspaces_response = None
63
+ attempt = 1
64
+ while attempt <= 5:
65
+ workspaces_response = client.find_workspaces(data.user_agent)
66
+ if workspaces_response.status_code != 200:
67
+ slack_user.status = StatusConnection.FAILED
68
+ log.warning(f'Attempt: {attempt}. Unable to get workspaces due to error: {workspaces_response.content}')
69
+ attempt += 1
70
+ time.sleep(60)
71
+ if not workspaces_response.json().get('ok', False):
72
+ slack_user.status = StatusConnection.FAILED
73
+ log.warning(f'Attempt: {attempt}. Unable to get workspaces due to error: {workspaces_response.json()}')
74
+ attempt += 1
75
+ time.sleep(60)
76
+ else:
77
+ slack_user.status = StatusConnection.IN_PROGRESS
78
+ break
79
+
80
+ UserMongoRepository().set(current_user.id, slack_users=[user.to_dic() for user in current_user.slack_users])
81
+
82
+ if attempt > 5:
72
83
  return
73
84
 
74
- UserMongoRepository().set(current_user.id,
75
- slack_users=[user.to_dic() for user in current_user.slack_users])
76
-
77
85
  log.info(f'{slack_user.email}: got workspaces data {workspaces_response.json()}')
78
86
  user_workspaces = next((user for user in workspaces_response.json()['current_teams']
79
87
  if user['email'] == data.slack_email), {}).get('teams', [])
@@ -97,14 +105,14 @@ class ConnectSlackAccountJob(BaseBackgroundJob, ABC):
97
105
  workspace.magic_login_url = login_url
98
106
  workspace.token = token
99
107
  workspace.domain = workspace.domain
108
+ time.sleep(random.uniform(0.5, 2))
100
109
 
101
110
  slack_user.cookies = session.cookies.get_dict()
102
111
  slack_user.workspaces = user_workspaces
103
112
  slack_user.status = StatusConnection.COMPLETE
104
113
 
105
114
  slack_users_dict = [user.to_dic() for user in current_user.slack_users]
106
- UserMongoRepository().set(current_user.id,
107
- slack_users=slack_users_dict)
115
+ UserMongoRepository().set(current_user.id, slack_users=slack_users_dict)
108
116
 
109
117
  dedicated_bots_repository = DedicatedBotRepository()
110
118
  dedicated_bots = dedicated_bots_repository.get_all(user_id=current_user.id, user_name=data.slack_email,
@@ -97,3 +97,4 @@ class NotificationType(str, Enum):
97
97
  ONCE_A_WEEK = 'once_a_week'
98
98
  WEEK_BEFORE = 'week_before'
99
99
  UNREAD_FOR_FEW_MINUTES = 'unread_for_few_minutes'
100
+ FEW_MINUTES_BEFORE = 'few_minutes_before'
@@ -198,6 +198,35 @@ class BulkReactionsNotification(Notification):
198
198
 
199
199
  class FollowUpNotification(Notification):
200
200
 
201
+ @property
202
+ def need_to_notify_now(self) -> bool:
203
+ allowed_types = [NotificationType.ONCE_A_DAY, NotificationType.ONCE_A_DAY, NotificationType.FEW_MINUTES_BEFORE]
204
+ if not self.enabled or self.type not in allowed_types:
205
+ return False
206
+
207
+ now = datetime.now(UTC)
208
+ if self.last_notification:
209
+ self.last_notification = self.last_notification.replace(tzinfo=UTC)
210
+
211
+ if (self.type == NotificationType.ONCE_A_DAY and self.last_notification
212
+ and (self.last_notification.day == now.day or self.hour != now.hour)):
213
+ return False
214
+
215
+ if (self.type == NotificationType.ONCE_A_WEEK and self.last_notification
216
+ and (self.last_notification.day == now.day or (now.hour != self.hour or now.day != self.day))):
217
+ return False
218
+
219
+ return True
220
+
221
+ def has_not_notified_leads(self, actual: list[UserLeadModel]) -> bool:
222
+ if self.type == NotificationType.FEW_MINUTES_BEFORE and self.last_notification:
223
+ self.last_notification = self.last_notification.replace(tzinfo=UTC)
224
+ not_notified_leads = [lead for lead in actual if lead.followup_date.replace(tzinfo=UTC) >
225
+ (self.last_notification + timedelta(minutes=self.minute))]
226
+ return bool(not_notified_leads)
227
+
228
+ return True
229
+
201
230
  @staticmethod
202
231
  def get_button_name(actual: list[UserLeadModel]) -> str:
203
232
  if len(actual) > 1:
@@ -206,7 +235,7 @@ class FollowUpNotification(Notification):
206
235
 
207
236
  def get_button_url(self, actual: list[UserLeadModel]) -> str:
208
237
  if len(actual) > 1:
209
- if self.type == NotificationType.ONCE_A_DAY:
238
+ if self.type == NotificationType.ONCE_A_DAY or self.type == NotificationType.FEW_MINUTES_BEFORE:
210
239
  return f'{portal_url}/dashboard/calendar?view=day'
211
240
  return f'{portal_url}/dashboard/calendar?view=week'
212
241
  return f'{portal_url}/feed?senderId={actual[0].message.sender_id}&sourceId={actual[0].message.source.source_id}'
@@ -218,6 +247,10 @@ class FollowUpNotification(Notification):
218
247
  return f'{subject_text} for today'
219
248
  elif self.type == NotificationType.ONCE_A_WEEK:
220
249
  return f'{subject_text} for this week'
250
+ elif self.type == NotificationType.FEW_MINUTES_BEFORE:
251
+ return f'{subject_text} for today in {self.minute} minutes'
252
+
253
+ return subject_text
221
254
 
222
255
  def get_notification_text(self, actual: list[ExtendedUserLeadModel], overdue: list[ExtendedUserLeadModel]) -> str:
223
256
  notification_text = ''
@@ -240,6 +273,18 @@ class FollowUpNotification(Notification):
240
273
  case _:
241
274
  notification_text = (f'You have planned to send follow-up to {", ".join(names[:3])} '
242
275
  f'and {len(names) - 3} other leads this week.')
276
+ elif self.type == NotificationType.FEW_MINUTES_BEFORE:
277
+ match len(actual):
278
+ case 1:
279
+ notification_text = (f'You have planned to send follow-up today in '
280
+ f'{self.minute} minutes to {names[0]}.')
281
+ case 2 | 3:
282
+ notification_text = (f'You have planned to send follow-up today in '
283
+ f'{self.minute} minutes to {", ".join(names)}.')
284
+ case _:
285
+ notification_text = (f'You have planned to send follow-up today in '
286
+ f'{self.minute} minutes to {", ".join(names[:3])} '
287
+ f'and {len(names) - 3} other leads.')
243
288
 
244
289
  return f'{notification_text} Plus you have {len(overdue)} overdue follow-ups.' if overdue else notification_text
245
290
 
@@ -242,10 +242,29 @@ class UserLeadMongoRepository(BaseMongoRepository):
242
242
  'notification_settings': '$user.notification_settings',
243
243
  'subscription_id': "$user.subscription_id",
244
244
  'followup_to': {
245
- '$cond': {
246
- 'if': {'$eq': ['$user.notification_settings.follow_ups.type', 'once_a_day']},
247
- 'then': this_day,
248
- 'else': after_week
245
+ '$switch': {
246
+ 'branches': [
247
+ {
248
+ 'case': {'$eq': ['$user.notification_settings.follow_ups.type', 'once_a_day']},
249
+ 'then': this_day
250
+ },
251
+ {
252
+ 'case': {'$eq': ['$user.notification_settings.follow_ups.type', 'once_a_week']},
253
+ 'then': after_week
254
+ },
255
+ {
256
+ 'case': {'$eq': ['$user.notification_settings.follow_ups.type',
257
+ 'few_minutes_before']},
258
+ 'then': {
259
+ '$dateSubtract': {
260
+ 'startDate': now,
261
+ 'unit': 'minute',
262
+ 'amount': '$user.notification_settings.follow_ups.minute'
263
+ }
264
+ }
265
+ }
266
+ ],
267
+ 'default': this_day
249
268
  }
250
269
  },
251
270
  'followup_date': 1,
@@ -811,12 +830,12 @@ class BoardsMongoRepository(BaseMongoRepository):
811
830
  if not board:
812
831
  return None
813
832
 
814
- board.statuses.append(BoardedStatus().from_dic({'name': 'Lead', 'order': 0}))
815
- board.statuses.append(BoardedStatus().from_dic({'name': 'Prospect', 'order': 1}))
816
- board.statuses.append(BoardedStatus().from_dic({'name': 'Opportunity', 'order': 2}))
817
- board.statuses.append(BoardedStatus().from_dic({'name': 'Call', 'order': 3}))
818
- board.statuses.append(BoardedStatus().from_dic({'name': 'Contract', 'order': 4}))
819
- board.statuses.append(BoardedStatus().from_dic({'name': 'Refused', 'order': 5}))
833
+ board.statuses.append(BoardedStatus().from_dic({'id': 'Lead', 'name': 'Lead', 'order': 0}))
834
+ board.statuses.append(BoardedStatus().from_dic({'id': 'Prospect', 'name': 'Prospect', 'order': 1}))
835
+ board.statuses.append(BoardedStatus().from_dic({'id': 'Opportunity', 'name': 'Opportunity', 'order': 2}))
836
+ board.statuses.append(BoardedStatus().from_dic({'id': 'Call', 'name': 'Call', 'order': 3}))
837
+ board.statuses.append(BoardedStatus().from_dic({'id': 'Contract', 'name': 'Contract', 'order': 4}))
838
+ board.statuses.append(BoardedStatus().from_dic({'id': 'Refused', 'name': 'Refused', 'order': 5}))
820
839
 
821
840
  return self.update_board(user_id, board_id, statuses=board.statuses)
822
841