wbintegrator_office365 1.56.1__tar.gz → 1.56.3__tar.gz
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.
Potentially problematic release.
This version of wbintegrator_office365 might be problematic. Click here for more details.
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/PKG-INFO +1 -1
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/models/__init__.py +1 -0
- wbintegrator_office365-1.56.3/wbintegrator_office365/models/reports.py +122 -0
- wbintegrator_office365-1.56.3/wbintegrator_office365/tasks.py +44 -0
- wbintegrator_office365-1.56.1/wbintegrator_office365/tasks.py +0 -234
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/.gitignore +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/pyproject.toml +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/admin.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/apps.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/configurations/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/configurations/configurations/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/dynamic_preferences_registry.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/factories.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/filters.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/importer/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/importer/api.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/importer/disable_signals.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/importer/parser.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/kpi_handlers/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/kpi_handlers/calls.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/migrations/0001_initial_squashed_squashed_0003_alter_calendar_owner_alter_calendarevent_organizer_and_more.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendarevent_activity_and_more.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/migrations/0003_alter_event_options.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/migrations/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/models/event.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/models/subscription.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/models/tenant.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/serializers.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/templates/admin/tenant_change_list.html +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/tests/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/tests/conftest.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/tests/test_admin.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/tests/test_models.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/tests/test_tasks.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/tests/test_views.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/tests/tests.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/urls.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/viewsets/__init__.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/viewsets/display.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/viewsets/endpoints.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/viewsets/menu.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/viewsets/titles.py +0 -0
- {wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/viewsets/viewsets.py +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import humanize
|
|
4
|
+
from django.contrib.auth.models import Group
|
|
5
|
+
from django.db.models import DurationField, ExpressionWrapper, F, Q, QuerySet
|
|
6
|
+
from django.dispatch import receiver
|
|
7
|
+
from wbcore.contrib.directory.models import Person
|
|
8
|
+
from wbcore.permissions.shortcuts import get_internal_users
|
|
9
|
+
from wbhuman_resources.signals import add_employee_activity_to_daily_brief
|
|
10
|
+
|
|
11
|
+
from wbintegrator_office365.models import CallEvent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def format_td(td: timedelta) -> str:
|
|
15
|
+
total_seconds = td.total_seconds()
|
|
16
|
+
if total_seconds == 0:
|
|
17
|
+
return "Missed"
|
|
18
|
+
elif total_seconds < 60:
|
|
19
|
+
return "< 1min"
|
|
20
|
+
return humanize.precisedelta(td, suppress=["hours"], minimum_unit="seconds", format="%0.0f")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def generate_call_summary(
|
|
24
|
+
profiles: QuerySet[Person],
|
|
25
|
+
start_date: date,
|
|
26
|
+
end_date: date,
|
|
27
|
+
include_detail: bool = True,
|
|
28
|
+
) -> str:
|
|
29
|
+
calls = CallEvent.objects.filter(
|
|
30
|
+
start__date__gte=start_date,
|
|
31
|
+
end__date__lte=end_date,
|
|
32
|
+
).annotate(duration=ExpressionWrapper(F("end") - F("start"), output_field=DurationField()))
|
|
33
|
+
message = """
|
|
34
|
+
<div style="background-color: white; width: 720px; margin-bottom: 50px">
|
|
35
|
+
"""
|
|
36
|
+
for profile in profiles:
|
|
37
|
+
call_events = calls.filter(
|
|
38
|
+
participants__tenant_user__profile=profile,
|
|
39
|
+
).order_by("start")
|
|
40
|
+
|
|
41
|
+
message += f"""
|
|
42
|
+
<div style="text-align: left;">
|
|
43
|
+
<p><b>{profile.computed_str}</b></p>
|
|
44
|
+
<table width="100%; table-layout: fixed; border-collapse: collapse;">
|
|
45
|
+
<tr>
|
|
46
|
+
<td style="width: 33.33%; text-align: center;">Total Calls: <b>{call_events.count()}</b></td>
|
|
47
|
+
<td style="width: 33.33%; text-align: center;">under 1 minute: <b>{call_events.filter(duration__lte=timedelta(seconds=60)).count()}</b></td>
|
|
48
|
+
<td style="width: 33.33%; text-align: center;">above 1 minute: <b>{call_events.filter(duration__gt=timedelta(seconds=60)).count()}</b></td>
|
|
49
|
+
</tr>
|
|
50
|
+
</table>
|
|
51
|
+
</div>
|
|
52
|
+
"""
|
|
53
|
+
if include_detail:
|
|
54
|
+
for call_date in call_events.dates("start", "day", order="DESC"):
|
|
55
|
+
call_day_events = call_events.filter(start__date=call_date)
|
|
56
|
+
if call_day_events.exists():
|
|
57
|
+
message += f"<p><b>{call_date:%Y-%m-%d}:</b></p>"
|
|
58
|
+
message += "<table style='border-collapse: collapse; width: 720px; table-layout: fixed;'> \
|
|
59
|
+
<tr style='color: white; background-color: #1868ae;'> \
|
|
60
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >Start</th> \
|
|
61
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >End</th> \
|
|
62
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 60px;' >Duration</th> \
|
|
63
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 80px;' >Organized by</th> \
|
|
64
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 150px;' >Participants</th> \
|
|
65
|
+
</tr>"
|
|
66
|
+
for call in call_day_events:
|
|
67
|
+
participants = ",".join(
|
|
68
|
+
filter(
|
|
69
|
+
None,
|
|
70
|
+
[
|
|
71
|
+
p.get_humanized_repr()
|
|
72
|
+
for p in call.participants.exclude(tenant_user__profile=profile)
|
|
73
|
+
],
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
message += f"<tr> \
|
|
77
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.start.astimezone():%H:%M}</td> \
|
|
78
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.end.astimezone():%H:%M}</td> \
|
|
79
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 60px;' text-align:center;><b>{format_td(call.end - call.start)}</b></td> \
|
|
80
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 80px;' ><b>{call.organizer.get_humanized_repr()}</b></td> \
|
|
81
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 150px;' >{participants}</td> \
|
|
82
|
+
</tr>"
|
|
83
|
+
message += "</table><br/>"
|
|
84
|
+
message += "</div>"
|
|
85
|
+
return message
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@receiver(add_employee_activity_to_daily_brief, sender="directory.Person")
|
|
89
|
+
def send_call_daily_summary(
|
|
90
|
+
sender,
|
|
91
|
+
instance: Person,
|
|
92
|
+
val_date: date,
|
|
93
|
+
daily_call_summary_receiver_group_ids: list[int] | None = None,
|
|
94
|
+
daily_call_summary_profile_ids: list[int] | None = None,
|
|
95
|
+
daily_call_summary_profile_group_ids: list[int] | None = None,
|
|
96
|
+
**kwargs,
|
|
97
|
+
) -> tuple[str, str] | None:
|
|
98
|
+
# if either sales or management
|
|
99
|
+
if daily_call_summary_receiver_group_ids:
|
|
100
|
+
groups = Group.objects.filter(id__in=daily_call_summary_receiver_group_ids)
|
|
101
|
+
else:
|
|
102
|
+
groups = Group.objects.filter(Q(name__iexact="sales") | Q(name__iexact="management"))
|
|
103
|
+
if instance.user_account.groups.filter(id__in=groups.values("id")).exists():
|
|
104
|
+
internal_users = get_internal_users().filter(is_active=True)
|
|
105
|
+
profiles = Person.objects.filter(user_account__in=internal_users)
|
|
106
|
+
if daily_call_summary_profile_ids:
|
|
107
|
+
profiles = profiles.filter(id__in=daily_call_summary_profile_ids)
|
|
108
|
+
elif daily_call_summary_profile_group_ids:
|
|
109
|
+
profiles = profiles.filter(
|
|
110
|
+
user_account__groups__in=Group.objects.filter(id__in=daily_call_summary_profile_group_ids)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
end_date = val_date
|
|
114
|
+
if val_date.weekday() == 0:
|
|
115
|
+
start_date = end_date - timedelta(days=8)
|
|
116
|
+
title = f"Call summary - From {start_date:%Y-%m-%d} To {end_date:%Y-%m-%d} (Weekly)"
|
|
117
|
+
message = generate_call_summary(profiles, start_date=start_date, end_date=end_date, include_detail=False)
|
|
118
|
+
else:
|
|
119
|
+
start_date = end_date - timedelta(days=1)
|
|
120
|
+
title = "Detailed Daily Call summary"
|
|
121
|
+
message = generate_call_summary(profiles, start_date=start_date, end_date=end_date, include_detail=True)
|
|
122
|
+
return title, message
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from datetime import date
|
|
2
|
+
|
|
3
|
+
from celery import shared_task
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from django.db.models import Q
|
|
6
|
+
from wbcore.contrib.notifications.dispatch import send_notification
|
|
7
|
+
|
|
8
|
+
from wbintegrator_office365.importer import MicrosoftGraphAPI
|
|
9
|
+
from wbintegrator_office365.models.subscription import Subscription
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@shared_task
|
|
13
|
+
def notify_no_active_call_record_subscription(to_email):
|
|
14
|
+
recipient = get_user_model().objects.filter(email=to_email)
|
|
15
|
+
ms_subscriptions = [elt.get("id") for elt in MicrosoftGraphAPI().subscriptions()]
|
|
16
|
+
qs_subscriptions = Subscription.objects.filter(
|
|
17
|
+
Q(is_enable=True) & Q(subscription_id__isnull=False) & Q(type_resource=Subscription.TypeResource.CALLRECORD)
|
|
18
|
+
)
|
|
19
|
+
enable_subcriptions = qs_subscriptions.filter(subscription_id__in=ms_subscriptions)
|
|
20
|
+
if recipient.exists() and (
|
|
21
|
+
len(ms_subscriptions) == 0 or (qs_subscriptions.count() > 0 and enable_subcriptions.count() == 0)
|
|
22
|
+
):
|
|
23
|
+
_day = date.today()
|
|
24
|
+
send_notification(
|
|
25
|
+
code="wbintegrator_office365.callevent.notify",
|
|
26
|
+
title=f"No active Call Record subscriptions in Microsoft - {_day}",
|
|
27
|
+
body=f"""<p>There are currently no active Call record subscriptions in Microsoft, so we are no longer receiving calls, Please check</p>
|
|
28
|
+
<ul>
|
|
29
|
+
<li>Number of subscriptions on Microsoft: <b>{len(ms_subscriptions)}</b></li>
|
|
30
|
+
<li>Number of Call subscriptions: <b>{qs_subscriptions.count()}</b></li>
|
|
31
|
+
<li>Number of enabled calling subscriptions: <b>{enable_subcriptions.count()}</b></li>
|
|
32
|
+
|
|
33
|
+
</ul>
|
|
34
|
+
""",
|
|
35
|
+
user=recipient.first(),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@shared_task
|
|
40
|
+
def periodic_resubscribe_task():
|
|
41
|
+
for subscription in Subscription.objects.filter(
|
|
42
|
+
is_enable=True, type_resource=Subscription.TypeResource.CALLRECORD, subscription_id__isnull=False
|
|
43
|
+
):
|
|
44
|
+
subscription.resubscribe()
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from collections import defaultdict
|
|
3
|
-
from datetime import date, timedelta
|
|
4
|
-
|
|
5
|
-
import boto3
|
|
6
|
-
import humanize
|
|
7
|
-
from celery import shared_task
|
|
8
|
-
from django.contrib.auth import get_user_model
|
|
9
|
-
from django.contrib.auth.models import Group
|
|
10
|
-
from django.db.models import DurationField, ExpressionWrapper, F, Q
|
|
11
|
-
from wbcore.contrib.directory.models import Person
|
|
12
|
-
from wbcore.contrib.notifications.dispatch import send_notification
|
|
13
|
-
from wbcore.permissions.shortcuts import get_internal_users
|
|
14
|
-
|
|
15
|
-
from wbintegrator_office365.importer import MicrosoftGraphAPI
|
|
16
|
-
from wbintegrator_office365.models.event import CallEvent
|
|
17
|
-
from wbintegrator_office365.models.subscription import Subscription
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def format_td(td: timedelta) -> str:
|
|
21
|
-
total_seconds = td.total_seconds()
|
|
22
|
-
if total_seconds == 0:
|
|
23
|
-
return "Missed"
|
|
24
|
-
elif total_seconds < 60:
|
|
25
|
-
return "< 1min"
|
|
26
|
-
return humanize.precisedelta(td, suppress=["hours"], minimum_unit="seconds", format="%0.0f")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
#################################################
|
|
30
|
-
################# TEMPORARY #####################
|
|
31
|
-
#################################################
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def convert_user(user):
|
|
35
|
-
email = user["Attributes"][0]["Value"]
|
|
36
|
-
first = user["Attributes"][3]["Value"]
|
|
37
|
-
last = user["Attributes"][2]["Value"]
|
|
38
|
-
|
|
39
|
-
return f"{email} ({first} {last})"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def get_fundy_users():
|
|
43
|
-
access_key = os.environ["FUNDY_BOTO_ACCESS_KEY"]
|
|
44
|
-
secret_access_key = os.environ["FUNDY_BOTO_SECRET_ACCESS_KEY"]
|
|
45
|
-
|
|
46
|
-
client = boto3.client(
|
|
47
|
-
"cognito-idp", region_name="eu-north-1", aws_access_key_id=access_key, aws_secret_access_key=secret_access_key
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
pagination_token = None
|
|
51
|
-
users = []
|
|
52
|
-
while True:
|
|
53
|
-
if not pagination_token:
|
|
54
|
-
response = client.list_users(
|
|
55
|
-
UserPoolId="eu-north-1_IFRQriJAf",
|
|
56
|
-
)
|
|
57
|
-
else:
|
|
58
|
-
response = client.list_users(
|
|
59
|
-
UserPoolId="eu-north-1_IFRQriJAf",
|
|
60
|
-
PaginationToken=pagination_token,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
users.extend(response["Users"])
|
|
64
|
-
if response.get("PaginationToken", None) is None:
|
|
65
|
-
break
|
|
66
|
-
else:
|
|
67
|
-
pagination_token = response["PaginationToken"]
|
|
68
|
-
|
|
69
|
-
users_date = defaultdict(list)
|
|
70
|
-
for user in sorted(users, key=lambda x: x["UserCreateDate"]):
|
|
71
|
-
users_date[user["UserCreateDate"].date()].append(convert_user(user))
|
|
72
|
-
return users_date, len(users)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def get_fundy_user_statistics():
|
|
76
|
-
today = date.today()
|
|
77
|
-
users, total_users = get_fundy_users()
|
|
78
|
-
|
|
79
|
-
yesterday_users = users.get(today - timedelta(days=1), [])
|
|
80
|
-
return yesterday_users, len(yesterday_users), total_users
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
#################################################
|
|
84
|
-
################ TEMPORARY END ##################
|
|
85
|
-
#################################################
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@shared_task
|
|
89
|
-
def send_call_summary(
|
|
90
|
-
to_emails: list,
|
|
91
|
-
profile_ids: list[int] | None = None,
|
|
92
|
-
group_id: int | None = None,
|
|
93
|
-
offset: int = 0,
|
|
94
|
-
include_detail: bool = True,
|
|
95
|
-
):
|
|
96
|
-
internal_users = get_internal_users().filter(is_active=True)
|
|
97
|
-
profiles = Person.objects.filter(user_account__in=internal_users)
|
|
98
|
-
if profile_ids:
|
|
99
|
-
profiles = profiles.filter(id__in=profile_ids)
|
|
100
|
-
elif group_id:
|
|
101
|
-
profiles = profiles.filter(user_account__in=Group.objects.get(id=group_id).user_set.all())
|
|
102
|
-
|
|
103
|
-
end_date = date.today()
|
|
104
|
-
start_date = end_date - timedelta(days=offset + 1)
|
|
105
|
-
if offset == 0:
|
|
106
|
-
frequency_repr = "Daily"
|
|
107
|
-
date_repr = start_date.strftime("%Y-%m-%d")
|
|
108
|
-
elif offset == 7:
|
|
109
|
-
frequency_repr = "Weekly"
|
|
110
|
-
date_repr = f"From {start_date:%Y-%m-%d} To {end_date:%Y-%m-%d}"
|
|
111
|
-
else:
|
|
112
|
-
frequency_repr = f"{offset} days"
|
|
113
|
-
date_repr = f"From {start_date:%Y-%m-%d} To {end_date:%Y-%m-%d}"
|
|
114
|
-
|
|
115
|
-
date_repr = f"{date_repr} ({frequency_repr})"
|
|
116
|
-
calls = CallEvent.objects.filter(
|
|
117
|
-
start__date__gte=start_date,
|
|
118
|
-
end__date__lte=end_date,
|
|
119
|
-
).annotate(duration=ExpressionWrapper(F("end") - F("start"), output_field=DurationField()))
|
|
120
|
-
if profiles.exists():
|
|
121
|
-
message = """
|
|
122
|
-
<div style="background-color: white; width: 720px; margin-bottom: 50px">
|
|
123
|
-
"""
|
|
124
|
-
for profile in profiles:
|
|
125
|
-
call_events = calls.filter(
|
|
126
|
-
participants__tenant_user__profile=profile,
|
|
127
|
-
).order_by("start")
|
|
128
|
-
|
|
129
|
-
message += f"""
|
|
130
|
-
<div style="text-align: left;">
|
|
131
|
-
<p><b>{profile.computed_str}</b></p>
|
|
132
|
-
<table width="100%; table-layout: fixed; border-collapse: collapse;">
|
|
133
|
-
<tr>
|
|
134
|
-
<td style="width: 33.33%; text-align: center;">Total Calls: <b>{call_events.count()}</b></td>
|
|
135
|
-
<td style="width: 33.33%; text-align: center;">under 1 minute: <b>{call_events.filter(duration__lte=timedelta(seconds=60)).count()}</b></td>
|
|
136
|
-
<td style="width: 33.33%; text-align: center;">above 1 minute: <b>{call_events.filter(duration__gt=timedelta(seconds=60)).count()}</b></td>
|
|
137
|
-
</tr>
|
|
138
|
-
</table>
|
|
139
|
-
</div>
|
|
140
|
-
"""
|
|
141
|
-
if include_detail:
|
|
142
|
-
for call_date in call_events.dates("start", "day", order="DESC"):
|
|
143
|
-
call_day_events = call_events.filter(start__date=call_date)
|
|
144
|
-
if call_day_events.exists():
|
|
145
|
-
message += f"<p><b>{call_date:%Y-%m-%d}:</b></p>"
|
|
146
|
-
message += "<table style='border-collapse: collapse; width: 720px; table-layout: fixed;'> \
|
|
147
|
-
<tr style='color: white; background-color: #1868ae;'> \
|
|
148
|
-
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >Start</th> \
|
|
149
|
-
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >End</th> \
|
|
150
|
-
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 60px;' >Duration</th> \
|
|
151
|
-
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 80px;' >Organized by</th> \
|
|
152
|
-
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 150px;' >Participants</th> \
|
|
153
|
-
</tr>"
|
|
154
|
-
for call in call_day_events:
|
|
155
|
-
participants = ",".join(
|
|
156
|
-
filter(
|
|
157
|
-
None,
|
|
158
|
-
[
|
|
159
|
-
p.get_humanized_repr()
|
|
160
|
-
for p in call.participants.exclude(tenant_user__profile=profile)
|
|
161
|
-
],
|
|
162
|
-
)
|
|
163
|
-
)
|
|
164
|
-
message += f"<tr> \
|
|
165
|
-
<td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.start.astimezone():%H:%M}</td> \
|
|
166
|
-
<td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.end.astimezone():%H:%M}</td> \
|
|
167
|
-
<td style='border: 1px solid #ddd;padding: 2px; width: 60px;' text-align:center;><b>{format_td(call.end - call.start)}</b></td> \
|
|
168
|
-
<td style='border: 1px solid #ddd;padding: 2px; width: 80px;' ><b>{call.organizer.get_humanized_repr()}</b></td> \
|
|
169
|
-
<td style='border: 1px solid #ddd;padding: 2px; width: 150px;' >{participants}</td> \
|
|
170
|
-
</tr>"
|
|
171
|
-
message += "</table><br/>"
|
|
172
|
-
|
|
173
|
-
message += "</div>"
|
|
174
|
-
|
|
175
|
-
######## TEMPORARY START ########
|
|
176
|
-
yesterday_users, yesterday_users_count, total_users_count = get_fundy_user_statistics()
|
|
177
|
-
message += f"""
|
|
178
|
-
<div>
|
|
179
|
-
<h3>FUNDY USER STATISTICS</h3>
|
|
180
|
-
<strong>Yesterday Users:</strong> {yesterday_users_count}
|
|
181
|
-
<strong>Total Users:</strong> {total_users_count}
|
|
182
|
-
<ul>
|
|
183
|
-
"""
|
|
184
|
-
for user in yesterday_users:
|
|
185
|
-
message += f"<li>{user}</li>"
|
|
186
|
-
message += "</ul></div>"
|
|
187
|
-
######## TEMPORARY END ########
|
|
188
|
-
|
|
189
|
-
title = f"Call summary - {date_repr}"
|
|
190
|
-
if include_detail:
|
|
191
|
-
title = "Detailed " + title
|
|
192
|
-
for to_email in to_emails:
|
|
193
|
-
recipient = get_user_model().objects.get(email=to_email)
|
|
194
|
-
send_notification(
|
|
195
|
-
code="wbintegrator_office365.callevent.call_summary",
|
|
196
|
-
title=title,
|
|
197
|
-
body=message,
|
|
198
|
-
user=recipient,
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
@shared_task
|
|
203
|
-
def notify_no_active_call_record_subscription(to_email):
|
|
204
|
-
recipient = get_user_model().objects.filter(email=to_email)
|
|
205
|
-
ms_subscriptions = [elt.get("id") for elt in MicrosoftGraphAPI().subscriptions()]
|
|
206
|
-
qs_subscriptions = Subscription.objects.filter(
|
|
207
|
-
Q(is_enable=True) & Q(subscription_id__isnull=False) & Q(type_resource=Subscription.TypeResource.CALLRECORD)
|
|
208
|
-
)
|
|
209
|
-
enable_subcriptions = qs_subscriptions.filter(subscription_id__in=ms_subscriptions)
|
|
210
|
-
if recipient.exists() and (
|
|
211
|
-
len(ms_subscriptions) == 0 or (qs_subscriptions.count() > 0 and enable_subcriptions.count() == 0)
|
|
212
|
-
):
|
|
213
|
-
_day = date.today()
|
|
214
|
-
send_notification(
|
|
215
|
-
code="wbintegrator_office365.callevent.notify",
|
|
216
|
-
title=f"No active Call Record subscriptions in Microsoft - {_day}",
|
|
217
|
-
body=f"""<p>There are currently no active Call record subscriptions in Microsoft, so we are no longer receiving calls, Please check</p>
|
|
218
|
-
<ul>
|
|
219
|
-
<li>Number of subscriptions on Microsoft: <b>{len(ms_subscriptions)}</b></li>
|
|
220
|
-
<li>Number of Call subscriptions: <b>{qs_subscriptions.count()}</b></li>
|
|
221
|
-
<li>Number of enabled calling subscriptions: <b>{enable_subcriptions.count()}</b></li>
|
|
222
|
-
|
|
223
|
-
</ul>
|
|
224
|
-
""",
|
|
225
|
-
user=recipient.first(),
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
@shared_task
|
|
230
|
-
def periodic_resubscribe_task():
|
|
231
|
-
for subscription in Subscription.objects.filter(
|
|
232
|
-
is_enable=True, type_resource=Subscription.TypeResource.CALLRECORD, subscription_id__isnull=False
|
|
233
|
-
):
|
|
234
|
-
subscription.resubscribe()
|
|
File without changes
|
|
File without changes
|
{wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/__init__.py
RENAMED
|
File without changes
|
{wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/admin.py
RENAMED
|
File without changes
|
{wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/apps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/factories.py
RENAMED
|
File without changes
|
{wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/filters.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wbintegrator_office365-1.56.1 → wbintegrator_office365-1.56.3}/wbintegrator_office365/urls.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|