wbintegrator_office365 2.2.1__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.
- wbintegrator_office365/__init__.py +1 -0
- wbintegrator_office365/admin.py +209 -0
- wbintegrator_office365/apps.py +5 -0
- wbintegrator_office365/configurations/__init__.py +0 -0
- wbintegrator_office365/configurations/configurations/__init__.py +23 -0
- wbintegrator_office365/dynamic_preferences_registry.py +15 -0
- wbintegrator_office365/factories.py +102 -0
- wbintegrator_office365/filters.py +237 -0
- wbintegrator_office365/importer/__init__.py +3 -0
- wbintegrator_office365/importer/api.py +403 -0
- wbintegrator_office365/importer/disable_signals.py +43 -0
- wbintegrator_office365/importer/parser.py +135 -0
- wbintegrator_office365/kpi_handlers/__init__.py +1 -0
- wbintegrator_office365/kpi_handlers/calls.py +114 -0
- wbintegrator_office365/migrations/0001_initial_squashed_squashed_0003_alter_calendar_owner_alter_calendarevent_organizer_and_more.py +677 -0
- wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendarevent_activity_and_more.py +85 -0
- wbintegrator_office365/migrations/0003_alter_event_options.py +20 -0
- wbintegrator_office365/migrations/__init__.py +0 -0
- wbintegrator_office365/models/__init__.py +3 -0
- wbintegrator_office365/models/event.py +623 -0
- wbintegrator_office365/models/subscription.py +144 -0
- wbintegrator_office365/models/tenant.py +62 -0
- wbintegrator_office365/serializers.py +266 -0
- wbintegrator_office365/tasks.py +108 -0
- wbintegrator_office365/tests/__init__.py +0 -0
- wbintegrator_office365/tests/conftest.py +28 -0
- wbintegrator_office365/tests/test_admin.py +86 -0
- wbintegrator_office365/tests/test_models.py +65 -0
- wbintegrator_office365/tests/test_tasks.py +318 -0
- wbintegrator_office365/tests/test_views.py +128 -0
- wbintegrator_office365/tests/tests.py +12 -0
- wbintegrator_office365/urls.py +46 -0
- wbintegrator_office365/viewsets/__init__.py +31 -0
- wbintegrator_office365/viewsets/display.py +306 -0
- wbintegrator_office365/viewsets/endpoints.py +52 -0
- wbintegrator_office365/viewsets/menu.py +65 -0
- wbintegrator_office365/viewsets/titles.py +49 -0
- wbintegrator_office365/viewsets/viewsets.py +745 -0
- wbintegrator_office365-2.2.1.dist-info/METADATA +10 -0
- wbintegrator_office365-2.2.1.dist-info/RECORD +41 -0
- wbintegrator_office365-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,745 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
from datetime import date, datetime, timedelta
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
import pandas as pd
|
7
|
+
import plotly.graph_objects as go
|
8
|
+
from django.db import transaction
|
9
|
+
from django.db.models import Case, CharField, F, When
|
10
|
+
from django.http import HttpResponse
|
11
|
+
from django.views.decorators.csrf import csrf_exempt
|
12
|
+
from plotly.subplots import make_subplots
|
13
|
+
from rest_framework import filters
|
14
|
+
from wbcore import viewsets
|
15
|
+
from wbcore.filters import DjangoFilterBackend
|
16
|
+
from wbhuman_resources.models import EmployeeHumanResource
|
17
|
+
from wbintegrator_office365.filters import (
|
18
|
+
CallEventFilter,
|
19
|
+
CallEventSummaryGraphFilter,
|
20
|
+
CallUserFilter,
|
21
|
+
EventFilter,
|
22
|
+
EventLogFilter,
|
23
|
+
SubscriptionFilter,
|
24
|
+
TenantUserFilter,
|
25
|
+
)
|
26
|
+
from wbintegrator_office365.importer import parse
|
27
|
+
from wbintegrator_office365.models import (
|
28
|
+
CallEvent,
|
29
|
+
CallUser,
|
30
|
+
Event,
|
31
|
+
EventLog,
|
32
|
+
Subscription,
|
33
|
+
TenantUser,
|
34
|
+
)
|
35
|
+
from wbintegrator_office365.models.event import handle_event_from_webhook
|
36
|
+
from wbintegrator_office365.serializers import (
|
37
|
+
CallEventModelSerializer,
|
38
|
+
CallEventRepresentationSerializer,
|
39
|
+
CallUserModelSerializer,
|
40
|
+
CallUserRepresentationSerializer,
|
41
|
+
EventLogModelSerializer,
|
42
|
+
EventLogRepresentationSerializer,
|
43
|
+
EventModelSerializer,
|
44
|
+
EventRepresentationSerializer,
|
45
|
+
SubscriptionListModelSerializer,
|
46
|
+
SubscriptionModelSerializer,
|
47
|
+
SubscriptionRepresentationSerializer,
|
48
|
+
TenantUserModelSerializer,
|
49
|
+
TenantUserRepresentationSerializer,
|
50
|
+
)
|
51
|
+
|
52
|
+
from .display import (
|
53
|
+
CallEventDisplayConfig,
|
54
|
+
CallUserDisplayConfig,
|
55
|
+
EventDisplayConfig,
|
56
|
+
EventLogDisplayConfig,
|
57
|
+
EventLogEventDisplayConfig,
|
58
|
+
SubscriptionDisplayConfig,
|
59
|
+
TenantUserDisplayConfig,
|
60
|
+
)
|
61
|
+
from .endpoints import (
|
62
|
+
CallEventEndpointConfig,
|
63
|
+
CallUserEndpointConfig,
|
64
|
+
EventEndpointConfig,
|
65
|
+
EventLogEndpointConfig,
|
66
|
+
EventLogEventEndpointConfig,
|
67
|
+
SubscriptionEndpointConfig,
|
68
|
+
TenantUserEndpointConfig,
|
69
|
+
)
|
70
|
+
from .titles import (
|
71
|
+
CallEventReceptionTimeTitleConfig,
|
72
|
+
CallEventSummaryGraphTitleConfig,
|
73
|
+
CallEventTitleConfig,
|
74
|
+
CallUserTitleConfig,
|
75
|
+
SubscriptionTitleConfig,
|
76
|
+
TenantUserTitleConfig,
|
77
|
+
)
|
78
|
+
|
79
|
+
logger = logging.getLogger("wbintegrator_office365")
|
80
|
+
|
81
|
+
|
82
|
+
class TenantUserRepresentationViewSet(viewsets.RepresentationViewSet):
|
83
|
+
IDENTIFIER = "wbintegrator_office365:tenantuserrepresentation"
|
84
|
+
filter_backends = (
|
85
|
+
DjangoFilterBackend,
|
86
|
+
filters.SearchFilter,
|
87
|
+
filters.OrderingFilter,
|
88
|
+
)
|
89
|
+
search_fields = [
|
90
|
+
"profile__computed_str",
|
91
|
+
"display_name",
|
92
|
+
"mail",
|
93
|
+
"phone",
|
94
|
+
]
|
95
|
+
serializer_class = TenantUserRepresentationSerializer
|
96
|
+
queryset = TenantUser.objects.all()
|
97
|
+
|
98
|
+
|
99
|
+
class SubscriptionRepresentationViewSet(viewsets.RepresentationViewSet):
|
100
|
+
IDENTIFIER = "wbintegrator_office365:subscriptionrepresentation"
|
101
|
+
filter_backends = (
|
102
|
+
DjangoFilterBackend,
|
103
|
+
filters.SearchFilter,
|
104
|
+
filters.OrderingFilter,
|
105
|
+
)
|
106
|
+
ordering = ["-created"]
|
107
|
+
search_fields = ["subscription_id"]
|
108
|
+
serializer_class = SubscriptionRepresentationSerializer
|
109
|
+
queryset = Subscription.objects.all()
|
110
|
+
|
111
|
+
|
112
|
+
class EventRepresentationViewSet(viewsets.RepresentationViewSet):
|
113
|
+
IDENTIFIER = "wbintegrator_office365:eventrepresentation"
|
114
|
+
filter_backends = (
|
115
|
+
DjangoFilterBackend,
|
116
|
+
filters.SearchFilter,
|
117
|
+
filters.OrderingFilter,
|
118
|
+
)
|
119
|
+
ordering = ["-auto_inc_id"]
|
120
|
+
search_fields = ["id"]
|
121
|
+
serializer_class = EventRepresentationSerializer
|
122
|
+
queryset = Event.objects.all()
|
123
|
+
|
124
|
+
|
125
|
+
class CallUserRepresentationViewSet(viewsets.RepresentationViewSet):
|
126
|
+
IDENTIFIER = "wbintegrator_office365:calleventrepresentation"
|
127
|
+
|
128
|
+
filter_backends = (
|
129
|
+
DjangoFilterBackend,
|
130
|
+
filters.SearchFilter,
|
131
|
+
filters.OrderingFilter,
|
132
|
+
)
|
133
|
+
search_fields = [
|
134
|
+
"tenant_user__tenant_id",
|
135
|
+
"tenant_user__profile__computed_str",
|
136
|
+
"tenant_user__display_name",
|
137
|
+
"tenant_user__mail",
|
138
|
+
"tenant_user__phone",
|
139
|
+
]
|
140
|
+
|
141
|
+
serializer_class = CallUserRepresentationSerializer
|
142
|
+
queryset = CallUser.objects.all()
|
143
|
+
|
144
|
+
|
145
|
+
class CallEventRepresentationViewSet(viewsets.RepresentationViewSet):
|
146
|
+
IDENTIFIER = "wbintegrator_office365:calleventrepresentation"
|
147
|
+
serializer_class = CallEventRepresentationSerializer
|
148
|
+
queryset = CallEvent.objects.all()
|
149
|
+
|
150
|
+
|
151
|
+
class TenantUserViewSet(viewsets.ModelViewSet):
|
152
|
+
IDENTIFIER = "wbintegrator_office365:tenantuser"
|
153
|
+
filter_backends = (
|
154
|
+
DjangoFilterBackend,
|
155
|
+
filters.SearchFilter,
|
156
|
+
filters.OrderingFilter,
|
157
|
+
)
|
158
|
+
ordering_fields = [
|
159
|
+
"id",
|
160
|
+
"tenant_id",
|
161
|
+
"profile",
|
162
|
+
"display_name",
|
163
|
+
"mail",
|
164
|
+
"phone",
|
165
|
+
"tenant_organization_id",
|
166
|
+
"is_internal_organization",
|
167
|
+
]
|
168
|
+
ordering = ["id"]
|
169
|
+
search_fields = [
|
170
|
+
"profile__computed_str",
|
171
|
+
"display_name",
|
172
|
+
"mail",
|
173
|
+
"phone",
|
174
|
+
]
|
175
|
+
|
176
|
+
filterset_class = TenantUserFilter
|
177
|
+
|
178
|
+
display_config_class = TenantUserDisplayConfig
|
179
|
+
title_config_class = TenantUserTitleConfig
|
180
|
+
endpoint_config_class = TenantUserEndpointConfig
|
181
|
+
|
182
|
+
serializer_class = TenantUserModelSerializer
|
183
|
+
queryset = TenantUser.objects.all()
|
184
|
+
|
185
|
+
|
186
|
+
class SubscriptionViewSet(viewsets.ModelViewSet):
|
187
|
+
IDENTIFIER = "wbintegrator_office365:subscription"
|
188
|
+
filter_backends = (
|
189
|
+
DjangoFilterBackend,
|
190
|
+
filters.SearchFilter,
|
191
|
+
filters.OrderingFilter,
|
192
|
+
)
|
193
|
+
ordering_fields = [
|
194
|
+
"subscription_id",
|
195
|
+
"change_type",
|
196
|
+
"expiration_date",
|
197
|
+
"type_resource",
|
198
|
+
"is_enable",
|
199
|
+
"tenant_user",
|
200
|
+
"created",
|
201
|
+
]
|
202
|
+
ordering = ["-created"]
|
203
|
+
search_fields = [
|
204
|
+
"subscription_id",
|
205
|
+
"tenant_user__profile__computed_str",
|
206
|
+
"tenant_user__display_name",
|
207
|
+
"tenant_user__mail",
|
208
|
+
"tenant_user__phone",
|
209
|
+
]
|
210
|
+
title_config_class = SubscriptionTitleConfig
|
211
|
+
display_config_class = SubscriptionDisplayConfig
|
212
|
+
endpoint_config_class = SubscriptionEndpointConfig
|
213
|
+
|
214
|
+
filterset_class = SubscriptionFilter
|
215
|
+
|
216
|
+
serializer_class = SubscriptionModelSerializer
|
217
|
+
queryset = Subscription.objects.all()
|
218
|
+
|
219
|
+
def get_serializer_class(self):
|
220
|
+
if self.get_action() in ["list", "list-metadata"]:
|
221
|
+
return SubscriptionListModelSerializer
|
222
|
+
return super().get_serializer_class()
|
223
|
+
|
224
|
+
|
225
|
+
class CallUserViewSet(viewsets.ModelViewSet):
|
226
|
+
IDENTIFIER = "wbintegrator_office365:calluser"
|
227
|
+
filter_backends = (
|
228
|
+
DjangoFilterBackend,
|
229
|
+
filters.SearchFilter,
|
230
|
+
filters.OrderingFilter,
|
231
|
+
)
|
232
|
+
ordering_fields = ["is_guest", "is_phone", "tenant_user"]
|
233
|
+
ordering = ["id"]
|
234
|
+
search_fields = [
|
235
|
+
"tenant_user__profile__computed_str",
|
236
|
+
"tenant_user__display_name",
|
237
|
+
"tenant_user__mail",
|
238
|
+
"tenant_user__phone",
|
239
|
+
]
|
240
|
+
|
241
|
+
display_config_class = CallUserDisplayConfig
|
242
|
+
title_config_class = CallUserTitleConfig
|
243
|
+
endpoint_config_class = CallUserEndpointConfig
|
244
|
+
|
245
|
+
filterset_class = CallUserFilter
|
246
|
+
|
247
|
+
serializer_class = CallUserModelSerializer
|
248
|
+
queryset = CallUser.objects.all()
|
249
|
+
|
250
|
+
def get_queryset(self):
|
251
|
+
qs = (
|
252
|
+
super()
|
253
|
+
.get_queryset()
|
254
|
+
.annotate(
|
255
|
+
name_user=Case(
|
256
|
+
When(tenant_user__profile__isnull=True, then=F("tenant_user__display_name")),
|
257
|
+
default=F("tenant_user__profile__computed_str"),
|
258
|
+
output_field=CharField(),
|
259
|
+
),
|
260
|
+
phone=F("tenant_user__phone"),
|
261
|
+
mail=F("tenant_user__mail"),
|
262
|
+
)
|
263
|
+
)
|
264
|
+
return qs
|
265
|
+
|
266
|
+
|
267
|
+
class EventViewSet(viewsets.ModelViewSet):
|
268
|
+
IDENTIFIER = "wbintegrator_office365:event"
|
269
|
+
filter_backends = (
|
270
|
+
DjangoFilterBackend,
|
271
|
+
filters.OrderingFilter,
|
272
|
+
)
|
273
|
+
ordering_fields = [
|
274
|
+
"auto_inc_id",
|
275
|
+
"nb_received",
|
276
|
+
"id",
|
277
|
+
"type",
|
278
|
+
"change_type",
|
279
|
+
"tenant_user",
|
280
|
+
"created",
|
281
|
+
"changed",
|
282
|
+
"subscription_id",
|
283
|
+
"id_event",
|
284
|
+
"is_handled",
|
285
|
+
]
|
286
|
+
|
287
|
+
ordering = ["-changed", "-created"]
|
288
|
+
|
289
|
+
filterset_class = EventFilter
|
290
|
+
|
291
|
+
display_config_class = EventDisplayConfig
|
292
|
+
endpoint_config_class = EventEndpointConfig
|
293
|
+
|
294
|
+
serializer_class = EventModelSerializer
|
295
|
+
|
296
|
+
queryset = Event.objects.all()
|
297
|
+
|
298
|
+
|
299
|
+
class EventLogRepresentationViewSet(viewsets.RepresentationViewSet):
|
300
|
+
serializer_class = EventLogRepresentationSerializer
|
301
|
+
queryset = EventLog.objects.all()
|
302
|
+
|
303
|
+
|
304
|
+
class EventLogViewSet(viewsets.ModelViewSet):
|
305
|
+
filter_backends = (
|
306
|
+
DjangoFilterBackend,
|
307
|
+
filters.OrderingFilter,
|
308
|
+
)
|
309
|
+
endpoint_config_class = EventLogEndpointConfig
|
310
|
+
display_config_class = EventLogDisplayConfig
|
311
|
+
serializer_class = EventLogModelSerializer
|
312
|
+
filterset_class = EventLogFilter
|
313
|
+
ordering_fields = ["id", "order_received", "last_event", "change_type", "created", "changed", "id_event"]
|
314
|
+
ordering = ["-changed", "-created"]
|
315
|
+
|
316
|
+
queryset = EventLog.objects.all()
|
317
|
+
|
318
|
+
|
319
|
+
class EventLogEventViewSet(EventLogViewSet):
|
320
|
+
endpoint_config_class = EventLogEventEndpointConfig
|
321
|
+
display_config_class = EventLogEventDisplayConfig
|
322
|
+
|
323
|
+
def get_queryset(self):
|
324
|
+
return super().get_queryset().filter(last_event=self.kwargs["last_event_id"])
|
325
|
+
|
326
|
+
|
327
|
+
class CallEventViewSet(viewsets.ModelViewSet):
|
328
|
+
IDENTIFIER = "wbintegrator_office365:callevent"
|
329
|
+
filter_backends = (
|
330
|
+
DjangoFilterBackend,
|
331
|
+
filters.OrderingFilter,
|
332
|
+
)
|
333
|
+
ordering_fields = ["event", "organizer", "created", "start", "end", "change_type", "is_internal_call"]
|
334
|
+
ordering = ["-created"]
|
335
|
+
|
336
|
+
filterset_class = CallEventFilter
|
337
|
+
|
338
|
+
display_config_class = CallEventDisplayConfig
|
339
|
+
title_config_class = CallEventTitleConfig
|
340
|
+
endpoint_config_class = CallEventEndpointConfig
|
341
|
+
|
342
|
+
serializer_class = CallEventModelSerializer
|
343
|
+
queryset = CallEvent.objects.all()
|
344
|
+
|
345
|
+
def get_queryset(self):
|
346
|
+
qs = super().get_queryset()
|
347
|
+
return qs.annotate(change_type=F("event__change_type"))
|
348
|
+
|
349
|
+
|
350
|
+
class CallEventReceptionTime(viewsets.ChartViewSet):
|
351
|
+
IDENTIFIER = "wbintegrator_office365:calleventchart"
|
352
|
+
queryset = CallEvent.objects.all()
|
353
|
+
title_config_class = CallEventReceptionTimeTitleConfig
|
354
|
+
|
355
|
+
def get_plotly(self, queryset):
|
356
|
+
fig = go.Figure()
|
357
|
+
if queryset.exists():
|
358
|
+
df = pd.DataFrame(queryset.values("start", "end", "created"))
|
359
|
+
df["delay"] = np.where(df["created"] > df["end"], df["created"] - df["end"], df["created"] - df["start"])
|
360
|
+
df["minutes"] = df.delay.dt.seconds / 60
|
361
|
+
df = df.loc[df["minutes"] < 1000]
|
362
|
+
# print(df.loc[100, ["delay", "minutes"]])
|
363
|
+
df["mean_minutes"] = df.minutes.mean()
|
364
|
+
df["median_minutes"] = df.minutes.median()
|
365
|
+
fig.add_trace(
|
366
|
+
go.Scatter(
|
367
|
+
x=df.index,
|
368
|
+
y=df.minutes,
|
369
|
+
mode="lines+markers",
|
370
|
+
name="Delay",
|
371
|
+
)
|
372
|
+
)
|
373
|
+
|
374
|
+
fig.add_trace(
|
375
|
+
go.Scatter(
|
376
|
+
x=df.index,
|
377
|
+
y=df.mean_minutes,
|
378
|
+
mode="lines",
|
379
|
+
name="Average",
|
380
|
+
)
|
381
|
+
)
|
382
|
+
|
383
|
+
fig.add_trace(
|
384
|
+
go.Scatter(
|
385
|
+
x=df.index,
|
386
|
+
y=df.median_minutes,
|
387
|
+
mode="lines",
|
388
|
+
name="Median",
|
389
|
+
)
|
390
|
+
)
|
391
|
+
|
392
|
+
fig.update_layout(
|
393
|
+
title="<b>Call reception delay</b>",
|
394
|
+
xaxis=dict(
|
395
|
+
title="Calls",
|
396
|
+
),
|
397
|
+
yaxis=dict(
|
398
|
+
title="Delay in minutes",
|
399
|
+
# type= 'date'
|
400
|
+
),
|
401
|
+
autosize=True,
|
402
|
+
xaxis_rangeslider_visible=True,
|
403
|
+
height=850,
|
404
|
+
)
|
405
|
+
|
406
|
+
return fig
|
407
|
+
|
408
|
+
|
409
|
+
class CallEventSummaryGraph(viewsets.ChartViewSet):
|
410
|
+
filter_backends = (DjangoFilterBackend,)
|
411
|
+
IDENTIFIER = "wbintegrator_office365:calleventsummarygraph"
|
412
|
+
queryset = CallEvent.objects.all()
|
413
|
+
title_config_class = CallEventSummaryGraphTitleConfig
|
414
|
+
filterset_class = CallEventSummaryGraphFilter
|
415
|
+
|
416
|
+
def get_queryset(self):
|
417
|
+
return super().get_queryset().annotate(duration_seconds=F("end") - F("start"))
|
418
|
+
|
419
|
+
def get_dataframe_business_days(self, start_date, end_date):
|
420
|
+
temp_date = start_date
|
421
|
+
list_date = []
|
422
|
+
while temp_date <= end_date:
|
423
|
+
if temp_date.weekday() <= 4:
|
424
|
+
list_date.append(temp_date)
|
425
|
+
temp_date += timedelta(days=1)
|
426
|
+
return pd.DataFrame(list_date, columns=["day"])
|
427
|
+
|
428
|
+
def merge_df_with_business_days(self, df):
|
429
|
+
start_date = self.request.GET.get("start", None)
|
430
|
+
end_date = self.request.GET.get("end", None)
|
431
|
+
start_date = datetime.strptime(start_date, "%Y-%m-%d").date() if start_date and start_date != "null" else None
|
432
|
+
end_date = datetime.strptime(end_date, "%Y-%m-%d").date() if end_date and end_date != "null" else date.today()
|
433
|
+
end_date = end_date if end_date <= date.today() else date.today()
|
434
|
+
|
435
|
+
if start_date and end_date:
|
436
|
+
df_day = self.get_dataframe_business_days(start_date, end_date)
|
437
|
+
df_day.columns = pd.MultiIndex.from_product([df_day.columns, [""]])
|
438
|
+
df = df.merge(df_day, on=["day"], how="outer")
|
439
|
+
df[["count", "sum", "mean", "median"]] = df[["count", "sum", "mean", "median"]].fillna(0)
|
440
|
+
df["average_count_period"] = df["count"]["minutes"].mean()
|
441
|
+
df["average_sum_period"] = df["sum"]["minutes"].mean()
|
442
|
+
df["average_mean_period"] = df["mean"]["minutes"].mean()
|
443
|
+
df["average_median_period"] = df["median"]["minutes"].mean()
|
444
|
+
df = df.sort_values(by=["day"]).reset_index()
|
445
|
+
return df
|
446
|
+
|
447
|
+
def get_plotly(self, queryset):
|
448
|
+
fig = go.Figure()
|
449
|
+
if queryset.exists():
|
450
|
+
df = pd.DataFrame(queryset.annotate(day=F("created__date")).values("start", "end", "day"))
|
451
|
+
dict_df = {"employee": df}
|
452
|
+
df_compare_employee = pd.DataFrame()
|
453
|
+
if compare_employee_id := self.request.GET.get("compare_employee", None):
|
454
|
+
compare_employee = EmployeeHumanResource.objects.get(id=compare_employee_id)
|
455
|
+
|
456
|
+
qs2 = CallEvent.objects.filter(
|
457
|
+
participants__tenant_user__profile__computed_str__icontains=compare_employee.profile.computed_str
|
458
|
+
)
|
459
|
+
if start_compare := self.request.GET.get("start", None):
|
460
|
+
qs2 = qs2.filter(start__gte=start_compare)
|
461
|
+
|
462
|
+
if end_compare := self.request.GET.get("end", None):
|
463
|
+
qs2 = qs2.filter(end__lte=end_compare)
|
464
|
+
if call_duration := self.request.GET.get("call_duration", None):
|
465
|
+
qs2 = qs2.annotate(duration_seconds=F("end") - F("start")).filter(
|
466
|
+
duration_seconds__gte=timedelta(seconds=float(call_duration))
|
467
|
+
)
|
468
|
+
if call_type := self.request.GET.get("call_type", None):
|
469
|
+
qs2 = qs2.filter(type=call_type)
|
470
|
+
if call_area := self.request.GET.get("call_area", None):
|
471
|
+
qs2 = qs2.filter(is_internal_call=call_area)
|
472
|
+
df_compare_employee = pd.DataFrame(qs2.annotate(day=F("created__date")).values("start", "end", "day"))
|
473
|
+
if not df_compare_employee.empty:
|
474
|
+
dict_df["compare_employee"] = df_compare_employee
|
475
|
+
|
476
|
+
for key, df in dict_df.items():
|
477
|
+
df["duration"] = df["end"] - df["start"]
|
478
|
+
df["minutes"] = df.duration.dt.seconds / 60
|
479
|
+
df = df.pivot_table(index=["day"], values="minutes", aggfunc=["count", "sum", "mean", "median"])
|
480
|
+
df["average_count_period"] = df["count"]["minutes"].mean()
|
481
|
+
df["average_sum_period"] = df["sum"]["minutes"].mean()
|
482
|
+
df["average_mean_period"] = df["mean"]["minutes"].mean()
|
483
|
+
df["average_median_period"] = df["median"]["minutes"].mean()
|
484
|
+
|
485
|
+
df[
|
486
|
+
[
|
487
|
+
"sum",
|
488
|
+
"mean",
|
489
|
+
"median",
|
490
|
+
"average_count_period",
|
491
|
+
"average_sum_period",
|
492
|
+
"average_mean_period",
|
493
|
+
"average_median_period",
|
494
|
+
]
|
495
|
+
] = df[
|
496
|
+
[
|
497
|
+
"sum",
|
498
|
+
"mean",
|
499
|
+
"median",
|
500
|
+
"average_count_period",
|
501
|
+
"average_sum_period",
|
502
|
+
"average_mean_period",
|
503
|
+
"average_median_period",
|
504
|
+
]
|
505
|
+
].round(
|
506
|
+
2
|
507
|
+
)
|
508
|
+
df = df.reset_index()
|
509
|
+
|
510
|
+
dict_df[key] = df
|
511
|
+
|
512
|
+
df = dict_df["employee"]
|
513
|
+
if compare_employee_id and not df_compare_employee.empty:
|
514
|
+
df2 = dict_df["compare_employee"]
|
515
|
+
|
516
|
+
if (business_day_without_call := self.request.GET.get("business_day_without_call", None)) and (
|
517
|
+
business_day_without_call == "true"
|
518
|
+
):
|
519
|
+
df = self.merge_df_with_business_days(df)
|
520
|
+
if compare_employee_id and not df_compare_employee.empty:
|
521
|
+
df2 = self.merge_df_with_business_days(df2)
|
522
|
+
|
523
|
+
# df = df.merge(df2[['day']], on=['day'], how = 'outer', indicator=True)
|
524
|
+
# df[['count', 'sum', 'mean', 'median']] = df[['count', 'sum', 'mean', 'median']].fillna(0)
|
525
|
+
# df[["average_count_period", "average_sum_period", "average_mean_period", "average_median_period"]] = df[["average_count_period", "average_sum_period", "average_mean_period", "average_median_period"]].fillna(0)
|
526
|
+
# df = df.sort_values(by=['day']).reset_index()
|
527
|
+
|
528
|
+
# df2 = df2.merge(df[['day']], on=['day'], how = 'outer', indicator=True)
|
529
|
+
# df2[['count', 'sum', 'mean', 'median']] = df2[['count', 'sum', 'mean', 'median']].fillna(0)
|
530
|
+
# df2[["average_count_period", "average_sum_period", "average_mean_period", "average_median_period"]] = df2[["average_count_period", "average_sum_period", "average_mean_period", "average_median_period"]].fillna(0)
|
531
|
+
# df2 = df2.sort_values(by=['day']).reset_index()
|
532
|
+
|
533
|
+
fig = make_subplots(
|
534
|
+
rows=4, cols=1, shared_xaxes=True, vertical_spacing=0.01, row_heights=[0.2, 0.3, 0.25, 0.25]
|
535
|
+
)
|
536
|
+
|
537
|
+
fig.add_trace(
|
538
|
+
go.Bar(
|
539
|
+
x=df["day"],
|
540
|
+
y=df["count"]["minutes"],
|
541
|
+
name="Number of calls",
|
542
|
+
text=df["count"]["minutes"],
|
543
|
+
textposition="auto",
|
544
|
+
),
|
545
|
+
row=1,
|
546
|
+
col=1,
|
547
|
+
)
|
548
|
+
if compare_employee_id and not df_compare_employee.empty:
|
549
|
+
# df2 = dict_df["compare_employee"]
|
550
|
+
fig.add_trace(
|
551
|
+
go.Bar(
|
552
|
+
x=df["day"],
|
553
|
+
y=df2["count"]["minutes"],
|
554
|
+
name="Number of calls " + compare_employee.computed_str,
|
555
|
+
text=df2["count"]["minutes"],
|
556
|
+
textposition="auto",
|
557
|
+
marker=dict(color="#A9A9A9"),
|
558
|
+
),
|
559
|
+
row=1,
|
560
|
+
col=1,
|
561
|
+
)
|
562
|
+
|
563
|
+
fig.add_trace(
|
564
|
+
go.Scatter(
|
565
|
+
x=df["day"],
|
566
|
+
y=df["average_count_period"],
|
567
|
+
mode="lines",
|
568
|
+
line=dict(dash="dash", width=2),
|
569
|
+
name="Average number of calls for the period",
|
570
|
+
opacity=0.5,
|
571
|
+
marker=dict(color="red"),
|
572
|
+
),
|
573
|
+
row=1,
|
574
|
+
col=1,
|
575
|
+
)
|
576
|
+
|
577
|
+
fig.add_trace(
|
578
|
+
go.Bar(
|
579
|
+
x=df["day"],
|
580
|
+
y=df["sum"]["minutes"],
|
581
|
+
name="Total minutes",
|
582
|
+
text=df["sum"]["minutes"],
|
583
|
+
textposition="auto",
|
584
|
+
marker=dict(color="rgb(55, 83, 109)"),
|
585
|
+
),
|
586
|
+
row=2,
|
587
|
+
col=1,
|
588
|
+
)
|
589
|
+
if compare_employee_id and not df_compare_employee.empty:
|
590
|
+
fig.add_trace(
|
591
|
+
go.Bar(
|
592
|
+
x=df2["day"],
|
593
|
+
y=df2["sum"]["minutes"],
|
594
|
+
name="Total minutes " + compare_employee.computed_str,
|
595
|
+
text=df2["sum"]["minutes"],
|
596
|
+
textposition="auto",
|
597
|
+
marker=dict(color="#696969"),
|
598
|
+
),
|
599
|
+
row=2,
|
600
|
+
col=1,
|
601
|
+
)
|
602
|
+
|
603
|
+
fig.add_trace(
|
604
|
+
go.Scatter(
|
605
|
+
x=df["day"],
|
606
|
+
y=df["average_sum_period"],
|
607
|
+
mode="lines",
|
608
|
+
line=dict(dash="dashdot", width=2),
|
609
|
+
name="Average Total minutes for the period",
|
610
|
+
opacity=0.5,
|
611
|
+
marker=dict(color="#FFD700"),
|
612
|
+
),
|
613
|
+
row=2,
|
614
|
+
col=1,
|
615
|
+
)
|
616
|
+
|
617
|
+
fig.add_trace(
|
618
|
+
go.Bar(
|
619
|
+
x=df["day"],
|
620
|
+
y=df["median"]["minutes"],
|
621
|
+
name="Median minutes",
|
622
|
+
text=df["median"]["minutes"],
|
623
|
+
textposition="auto",
|
624
|
+
marker=dict(color="skyblue"),
|
625
|
+
),
|
626
|
+
row=3,
|
627
|
+
col=1,
|
628
|
+
)
|
629
|
+
if compare_employee_id and not df_compare_employee.empty:
|
630
|
+
fig.add_trace(
|
631
|
+
go.Bar(
|
632
|
+
x=df2["day"],
|
633
|
+
y=df2["median"]["minutes"],
|
634
|
+
name="Median minutes " + compare_employee.computed_str,
|
635
|
+
text=df2["median"]["minutes"],
|
636
|
+
textposition="auto",
|
637
|
+
marker=dict(color="#D3D3D3"),
|
638
|
+
),
|
639
|
+
row=3,
|
640
|
+
col=1,
|
641
|
+
)
|
642
|
+
|
643
|
+
fig.add_trace(
|
644
|
+
go.Scatter(
|
645
|
+
x=df["day"],
|
646
|
+
y=df["average_median_period"],
|
647
|
+
mode="lines",
|
648
|
+
line=dict(dash="dash", width=2),
|
649
|
+
name="Median minutes for the period",
|
650
|
+
opacity=0.5,
|
651
|
+
marker=dict(color="orange"),
|
652
|
+
),
|
653
|
+
row=3,
|
654
|
+
col=1,
|
655
|
+
)
|
656
|
+
|
657
|
+
fig.add_trace(
|
658
|
+
go.Bar(
|
659
|
+
x=df["day"],
|
660
|
+
y=df["mean"]["minutes"],
|
661
|
+
name="Average minutes",
|
662
|
+
text=df["mean"]["minutes"],
|
663
|
+
textposition="auto",
|
664
|
+
marker=dict(color="blue"),
|
665
|
+
),
|
666
|
+
row=4,
|
667
|
+
col=1,
|
668
|
+
)
|
669
|
+
if compare_employee_id and not df_compare_employee.empty:
|
670
|
+
fig.add_trace(
|
671
|
+
go.Bar(
|
672
|
+
x=df2["day"],
|
673
|
+
y=df2["mean"]["minutes"],
|
674
|
+
name="Average minutes " + compare_employee.computed_str,
|
675
|
+
text=df2["mean"]["minutes"],
|
676
|
+
textposition="auto",
|
677
|
+
marker=dict(color="#808080"),
|
678
|
+
),
|
679
|
+
row=4,
|
680
|
+
col=1,
|
681
|
+
)
|
682
|
+
|
683
|
+
fig.add_trace(
|
684
|
+
go.Scatter(
|
685
|
+
x=df["day"],
|
686
|
+
y=df["average_mean_period"],
|
687
|
+
mode="lines",
|
688
|
+
line=dict(dash="dash", width=2),
|
689
|
+
name="Average minutes for the period",
|
690
|
+
opacity=0.5,
|
691
|
+
marker=dict(color="#BDB76B"),
|
692
|
+
),
|
693
|
+
row=4,
|
694
|
+
col=1,
|
695
|
+
)
|
696
|
+
|
697
|
+
# Update xaxis properties
|
698
|
+
fig.update_xaxes(type="category", row=1, col=1, gridcolor="white")
|
699
|
+
fig.update_xaxes(type="category", row=2, col=1, gridcolor="white")
|
700
|
+
fig.update_xaxes(type="category", row=3, col=1, gridcolor="white")
|
701
|
+
fig.update_xaxes(
|
702
|
+
title_text="days", type="category", row=4, col=1, rangeslider_visible=True, gridcolor="white"
|
703
|
+
)
|
704
|
+
# Update yaxis properties
|
705
|
+
fig.update_yaxes(title_text="Number of calls", title_font_size=12, row=1, col=1)
|
706
|
+
fig.update_yaxes(title_text="Total minutes", title_font_size=12, row=2, col=1)
|
707
|
+
fig.update_yaxes(title_text="Median Minutes", row=3, title_font_size=12, col=1)
|
708
|
+
fig.update_yaxes(title_text="Average Minutes", row=4, title_font_size=12, col=1)
|
709
|
+
|
710
|
+
fig.update_layout(
|
711
|
+
title="<b>Call Summary Graph</b>",
|
712
|
+
autosize=True,
|
713
|
+
height=800,
|
714
|
+
hovermode="x",
|
715
|
+
)
|
716
|
+
|
717
|
+
return fig
|
718
|
+
|
719
|
+
|
720
|
+
def callback_consent_permission(request):
|
721
|
+
result = request.GET.get("admin_consent")
|
722
|
+
return HttpResponse(result, content_type="text/plain")
|
723
|
+
|
724
|
+
|
725
|
+
# @require_POST
|
726
|
+
@csrf_exempt
|
727
|
+
def listen(request):
|
728
|
+
# handle validation
|
729
|
+
if request.GET.get("validationToken"):
|
730
|
+
token = request.GET.get("validationToken")
|
731
|
+
return HttpResponse(token, content_type="text/plain")
|
732
|
+
|
733
|
+
# handle notifications
|
734
|
+
# Latency for delivery of the change notification callRecord Less than 15 minutes Maximum latency 60 minutes
|
735
|
+
notifications = None
|
736
|
+
if request.body:
|
737
|
+
# logger.warning(f"{timezone.now():%Y-%m-%d %H:%M:%S.%f} {request.body}")
|
738
|
+
json_body = json.loads(request.body)
|
739
|
+
if json_body.get("value"):
|
740
|
+
notifications = parse(json_body.get("value"))
|
741
|
+
for notification in notifications:
|
742
|
+
# print(colored(f"{timezone.now():%Y-%m-%d %H:%M:%S.%f} {notification}", 'blue'))
|
743
|
+
if (resource_data := notification.get("resource_data")) and (id_event := resource_data.get("id")):
|
744
|
+
transaction.on_commit(lambda: handle_event_from_webhook.delay(id_event, notification))
|
745
|
+
return HttpResponse(notifications, content_type="text/plain")
|