wbintegrator_office365 1.55.4__py2.py3-none-any.whl → 1.55.6__py2.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.
@@ -290,15 +290,23 @@ class CallUser(WBModel):
290
290
  on_delete=models.deletion.SET_NULL,
291
291
  )
292
292
 
293
- def __str__(self):
293
+ def get_humanized_repr(self) -> str | None:
294
294
  if self.tenant_user:
295
295
  if self.tenant_user.profile:
296
- return f"{self.tenant_user.profile.computed_str}"
296
+ return self.tenant_user.profile.computed_str
297
297
  elif self.tenant_user.mail or self.tenant_user.display_name:
298
298
  mail = self.tenant_user.mail if self.tenant_user.mail else self.tenant_user.id
299
299
  return f"{self.tenant_user.display_name}({mail})"
300
300
  else:
301
- return f"{self.tenant_user.tenant_id}"
301
+ contacts = TelephoneContact.objects.filter(number=self.tenant_user.tenant_id, entry__isnull=False)
302
+ if contacts.exists():
303
+ return str(contacts.first().entry)
304
+ elif self.tenant_user.tenant_id[0] == "+":
305
+ return self.tenant_user.tenant_id
306
+
307
+ def __str__(self):
308
+ if repr := self.get_humanized_repr():
309
+ return repr
302
310
  return f"{self.id}"
303
311
 
304
312
  @classmethod
@@ -1,77 +1,125 @@
1
- import math
2
1
  from datetime import date, timedelta
3
2
 
3
+ import humanize
4
4
  from celery import shared_task
5
5
  from django.contrib.auth import get_user_model
6
- from django.db.models import Q
6
+ from django.contrib.auth.models import Group
7
+ from django.db.models import DurationField, ExpressionWrapper, F, Q
7
8
  from wbcore.contrib.directory.models import Person
8
9
  from wbcore.contrib.notifications.dispatch import send_notification
10
+ from wbcore.permissions.shortcuts import get_internal_users
9
11
 
10
12
  from wbintegrator_office365.importer import MicrosoftGraphAPI
11
13
  from wbintegrator_office365.models.event import CallEvent
12
14
  from wbintegrator_office365.models.subscription import Subscription
13
15
 
14
16
 
15
- @shared_task
16
- def send_call_summary(to_emails: list, profile_ids: list, offset: int = 1):
17
- for to_email in to_emails:
18
- if (recipient := get_user_model().objects.filter(email=to_email).first()) and (
19
- profiles := Person.objects.filter(id__in=profile_ids)
20
- ):
21
- _day = date.today() - timedelta(days=offset)
17
+ def format_td(td: timedelta) -> str:
18
+ total_seconds = td.total_seconds()
19
+ if total_seconds == 0:
20
+ return "Missed"
21
+ elif total_seconds < 60:
22
+ return "< 1min"
23
+ return humanize.precisedelta(td, suppress=["hours"], minimum_unit="seconds", format="%0.0f")
22
24
 
23
- message = "<html><head><style> #summery_table tr:nth-child(even){background-color: #f2f2f2;}</style></head><body>"
24
25
 
25
- for profile in profiles:
26
- call_events = CallEvent.objects.filter(
27
- participants__tenant_user__profile__computed_str__icontains=profile.computed_str,
28
- start__date=_day,
29
- end__date=_day,
30
- ).order_by("start")
26
+ @shared_task
27
+ def send_call_summary(
28
+ to_emails: list,
29
+ profile_ids: list[int] | None = None,
30
+ group_id: int | None = None,
31
+ offset: int = 0,
32
+ include_detail: bool = True,
33
+ ):
34
+ internal_users = get_internal_users().filter(is_active=True)
35
+ profiles = Person.objects.filter(user_account__in=internal_users)
36
+ if profile_ids:
37
+ profiles = profiles.filter(id__in=profile_ids)
38
+ elif group_id:
39
+ profiles = profiles.filter(user_account__in=Group.objects.get(id=group_id).user_set.all())
31
40
 
32
- message += f"<p> \
33
- <span><b> {profile.computed_str} </b></span><br/> \
34
- <span>Date: <b>{_day}</b> </span><br/> \
35
- <span>Total number of calls: <b>{call_events.count()}</b> </span> \
36
- </p>"
37
- if call_events.count():
38
- message += "<table id='summery_table' style='border-collapse: collapse;'> \
39
- <tr style='color: white; background-color: #1868ae;'> \
40
- <th style='border: 1px solid #ddd;padding: 10px 7px;' >Start</th> \
41
- <th style='border: 1px solid #ddd;padding: 10px 7px;' >End</th> \
42
- <th style='border: 1px solid #ddd;padding: 10px 7px;' >Duration (min)</th> \
43
- <th style='border: 1px solid #ddd;padding: 10px 7px;' >Organized by</th> \
44
- <th style='border: 1px solid #ddd;padding: 10px 7px;' >Participants</th> \
45
- </tr>"
46
- for call in call_events:
47
- participants = ""
48
- count = 0
49
- total_participants = call.participants.exclude(tenant_user__profile=profile).count()
50
- for participant in call.participants.exclude(tenant_user__profile=profile):
51
- count += 1
52
- participants += f"{participant.__str__()}"
53
- if count < total_participants:
54
- participants += ", "
41
+ end_date = date.today()
42
+ start_date = end_date - timedelta(days=offset + 1)
43
+ if offset == 0:
44
+ frequency_repr = "Daily"
45
+ date_repr = start_date.strftime("%Y-%m-%d")
46
+ elif offset == 7:
47
+ frequency_repr = "Weekly"
48
+ date_repr = f"From {start_date:%Y-%m-%d} To {end_date:%Y-%m-%d}"
49
+ else:
50
+ frequency_repr = f"{offset} days"
51
+ date_repr = f"From {start_date:%Y-%m-%d} To {end_date:%Y-%m-%d}"
55
52
 
56
- delta_in_minutes = divmod((call.end - call.start).total_seconds(), 60)
57
- _duration = f"{math.floor(delta_in_minutes[0])}:{math.floor(delta_in_minutes[1])}"
58
- message += f"<tr> \
59
- <td style='border: 1px solid #ddd;padding: 2px;' >{call.start.astimezone().strftime('%Y-%m-%d %H:%M:%S')}</td> \
60
- <td style='border: 1px solid #ddd;padding: 2px;' >{call.end.astimezone().strftime('%Y-%m-%d %H:%M:%S')}</td> \
61
- <td style='border: 1px solid #ddd;padding: 0px;' text-align:center;><b>{_duration}</b></td> \
62
- <td style='border: 1px solid #ddd;padding: 2px;' ><b>{call.organizer.__str__()}</b></td> \
63
- <td style='border: 1px solid #ddd;padding: 2px;' >{participants}</td> \
64
- </tr>"
65
- message += "</table><br/>"
53
+ date_repr = f"{date_repr} ({frequency_repr})"
54
+ calls = CallEvent.objects.filter(
55
+ start__date__gte=start_date,
56
+ end__date__lte=end_date,
57
+ ).annotate(duration=ExpressionWrapper(F("end") - F("start"), output_field=DurationField()))
58
+ if profiles.exists():
59
+ message = """
60
+ <div style="background-color: white; width: 720px; margin-bottom: 50px">
61
+ """
62
+ for profile in profiles:
63
+ call_events = calls.filter(
64
+ participants__tenant_user__profile=profile,
65
+ ).order_by("start")
66
66
 
67
- message += "</body></html>"
67
+ message += f"""
68
+ <div style="text-align: left;">
69
+ <p><b>{profile.computed_str}</b></p>
70
+ <table width="100%; table-layout: fixed; border-collapse: collapse;">
71
+ <tr>
72
+ <td style="width: 33.33%; text-align: center;">Total Calls: <b>{call_events.count()}</b></td>
73
+ <td style="width: 33.33%; text-align: center;">under 1 minute: <b>{call_events.filter(duration__lte=timedelta(seconds=60)).count()}</b></td>
74
+ <td style="width: 33.33%; text-align: center;">above 1 minute: <b>{call_events.filter(duration__gt=timedelta(seconds=60)).count()}</b></td>
75
+ </tr>
76
+ </table>
77
+ </div>
78
+ """
79
+ if include_detail:
80
+ for call_date in call_events.dates("start", "day", order="DESC"):
81
+ call_day_events = call_events.filter(start__date=call_date)
82
+ if call_day_events.exists():
83
+ message += f"<p><b>{call_date:%Y-%m-%d}:</b></p>"
84
+ message += "<table style='border-collapse: collapse; width: 720px; table-layout: fixed;'> \
85
+ <tr style='color: white; background-color: #1868ae;'> \
86
+ <th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >Start</th> \
87
+ <th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >End</th> \
88
+ <th style='border: 1px solid #ddd;padding: 2px 7px; width: 60px;' >Duration</th> \
89
+ <th style='border: 1px solid #ddd;padding: 2px 7px; width: 80px;' >Organized by</th> \
90
+ <th style='border: 1px solid #ddd;padding: 2px 7px; width: 150px;' >Participants</th> \
91
+ </tr>"
92
+ for call in call_day_events:
93
+ participants = ",".join(
94
+ filter(
95
+ None,
96
+ [
97
+ p.get_humanized_repr()
98
+ for p in call.participants.exclude(tenant_user__profile=profile)
99
+ ],
100
+ )
101
+ )
102
+ message += f"<tr> \
103
+ <td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.start.astimezone():%H:%M}</td> \
104
+ <td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.end.astimezone():%H:%M}</td> \
105
+ <td style='border: 1px solid #ddd;padding: 2px; width: 60px;' text-align:center;><b>{format_td(call.end - call.start)}</b></td> \
106
+ <td style='border: 1px solid #ddd;padding: 2px; width: 80px;' ><b>{call.organizer.get_humanized_repr()}</b></td> \
107
+ <td style='border: 1px solid #ddd;padding: 2px; width: 150px;' >{participants}</td> \
108
+ </tr>"
109
+ message += "</table><br/>"
68
110
 
69
- send_notification(
70
- code="wbintegrator_office365.callevent.call_summary",
71
- title=f"Call summary - {_day}",
72
- body=message,
73
- user=recipient,
74
- )
111
+ message += "</div>"
112
+ title = f"Call summary - {date_repr}"
113
+ if include_detail:
114
+ title = "Detailed " + title
115
+ for to_email in to_emails:
116
+ recipient = get_user_model().objects.get(email=to_email)
117
+ send_notification(
118
+ code="wbintegrator_office365.callevent.call_summary",
119
+ title=title,
120
+ body=message,
121
+ user=recipient,
122
+ )
75
123
 
76
124
 
77
125
  @shared_task
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbintegrator_office365
3
- Version: 1.55.4
3
+ Version: 1.55.6
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
5
  Requires-Dist: bs4==0.0.*
6
6
  Requires-Dist: oauthlib==3.1.*
@@ -5,7 +5,7 @@ wbintegrator_office365/dynamic_preferences_registry.py,sha256=RIptl8WGibV_XMynXl
5
5
  wbintegrator_office365/factories.py,sha256=pG8moGurbSElIugs_Gz3nd7-UJt3vC6mGy-EZQIeldI,3872
6
6
  wbintegrator_office365/filters.py,sha256=bSX1jrP1SrqiGkysoX__AqPRUPSU-uQq_-tOHinqijU,7447
7
7
  wbintegrator_office365/serializers.py,sha256=tR_HdsyxwCZo2_sOdhj2NErOEjTRyoIXA6DRR-BjVKs,8097
8
- wbintegrator_office365/tasks.py,sha256=Mo65SUtEH1sjy63x7iJc6HwVYjFQMHWIY21YGkx1cqA,5772
8
+ wbintegrator_office365/tasks.py,sha256=gley52lfXrOQUCz3L3NN3OGp5L7_sBVI2PtdxWDQBnE,7800
9
9
  wbintegrator_office365/urls.py,sha256=qfIIb3CNJw40xhGJnE7ztEMt2l-kgnRXA_P8Y3sDlPg,1922
10
10
  wbintegrator_office365/configurations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  wbintegrator_office365/configurations/configurations/__init__.py,sha256=tIBWiG0KWrDJ1xtKe5m1W9aKX5PMNLwsYuoy7xrQ4uI,1145
@@ -20,7 +20,7 @@ wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendareven
20
20
  wbintegrator_office365/migrations/0003_alter_event_options.py,sha256=rUYPQdwEuJowo10veLOZPtV3WMqxJu8_uan600EcpN4,585
21
21
  wbintegrator_office365/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
22
  wbintegrator_office365/models/__init__.py,sha256=sZKutR9oNMm2IaON35doaMVPgeMfxmWDqoAdALHRLIA,126
23
- wbintegrator_office365/models/event.py,sha256=G4lLb7JWcTSqjQpOfjeqrbi8U9B09gdxxSLjt-MYpFg,26622
23
+ wbintegrator_office365/models/event.py,sha256=5mKpTheP8UpUlLF_eFujfNDlsYjjbdwwdH5fB3Tg8Rg,27002
24
24
  wbintegrator_office365/models/subscription.py,sha256=EnesSa2OnuygsyfXXY8ABYOjgOaUv9Jj3c6OVQOPWyU,6424
25
25
  wbintegrator_office365/models/tenant.py,sha256=-FS6jg9nt8IgUv_729D92QFvrrbKleBUGnXhwaLv5Sc,2357
26
26
  wbintegrator_office365/templates/admin/tenant_change_list.html,sha256=mI4C1ZmTkl5MdN4CgoIWgCziSReqlHiK71aI7zJpVEM,358
@@ -37,6 +37,6 @@ wbintegrator_office365/viewsets/endpoints.py,sha256=NxEdH-tKk87mJdSNxpOZdN2z0AhY
37
37
  wbintegrator_office365/viewsets/menu.py,sha256=hafwoiHZmN3ltf30dTfwHreDwhyfdHlGOQk8bB7jB_o,2406
38
38
  wbintegrator_office365/viewsets/titles.py,sha256=9GZ_fqN9-sJzYHFvibOhNrRQQMNTh_m4eAv66VaGzDM,1214
39
39
  wbintegrator_office365/viewsets/viewsets.py,sha256=T9u8vViyeHrK1xzPbQqIKXgpf4qM-5KkPzbZGdLLujA,26331
40
- wbintegrator_office365-1.55.4.dist-info/METADATA,sha256=yvkfqEz97OaiGOxpjtenUOs8Ra1CZwbGM_jbnimOS9I,304
41
- wbintegrator_office365-1.55.4.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
42
- wbintegrator_office365-1.55.4.dist-info/RECORD,,
40
+ wbintegrator_office365-1.55.6.dist-info/METADATA,sha256=dXTKs3IGettBFHIupPsZ-W0VITAjGYA0__LRM3vT8hs,304
41
+ wbintegrator_office365-1.55.6.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
42
+ wbintegrator_office365-1.55.6.dist-info/RECORD,,