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.
Files changed (41) hide show
  1. wbintegrator_office365/__init__.py +1 -0
  2. wbintegrator_office365/admin.py +209 -0
  3. wbintegrator_office365/apps.py +5 -0
  4. wbintegrator_office365/configurations/__init__.py +0 -0
  5. wbintegrator_office365/configurations/configurations/__init__.py +23 -0
  6. wbintegrator_office365/dynamic_preferences_registry.py +15 -0
  7. wbintegrator_office365/factories.py +102 -0
  8. wbintegrator_office365/filters.py +237 -0
  9. wbintegrator_office365/importer/__init__.py +3 -0
  10. wbintegrator_office365/importer/api.py +403 -0
  11. wbintegrator_office365/importer/disable_signals.py +43 -0
  12. wbintegrator_office365/importer/parser.py +135 -0
  13. wbintegrator_office365/kpi_handlers/__init__.py +1 -0
  14. wbintegrator_office365/kpi_handlers/calls.py +114 -0
  15. wbintegrator_office365/migrations/0001_initial_squashed_squashed_0003_alter_calendar_owner_alter_calendarevent_organizer_and_more.py +677 -0
  16. wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendarevent_activity_and_more.py +85 -0
  17. wbintegrator_office365/migrations/0003_alter_event_options.py +20 -0
  18. wbintegrator_office365/migrations/__init__.py +0 -0
  19. wbintegrator_office365/models/__init__.py +3 -0
  20. wbintegrator_office365/models/event.py +623 -0
  21. wbintegrator_office365/models/subscription.py +144 -0
  22. wbintegrator_office365/models/tenant.py +62 -0
  23. wbintegrator_office365/serializers.py +266 -0
  24. wbintegrator_office365/tasks.py +108 -0
  25. wbintegrator_office365/tests/__init__.py +0 -0
  26. wbintegrator_office365/tests/conftest.py +28 -0
  27. wbintegrator_office365/tests/test_admin.py +86 -0
  28. wbintegrator_office365/tests/test_models.py +65 -0
  29. wbintegrator_office365/tests/test_tasks.py +318 -0
  30. wbintegrator_office365/tests/test_views.py +128 -0
  31. wbintegrator_office365/tests/tests.py +12 -0
  32. wbintegrator_office365/urls.py +46 -0
  33. wbintegrator_office365/viewsets/__init__.py +31 -0
  34. wbintegrator_office365/viewsets/display.py +306 -0
  35. wbintegrator_office365/viewsets/endpoints.py +52 -0
  36. wbintegrator_office365/viewsets/menu.py +65 -0
  37. wbintegrator_office365/viewsets/titles.py +49 -0
  38. wbintegrator_office365/viewsets/viewsets.py +745 -0
  39. wbintegrator_office365-2.2.1.dist-info/METADATA +10 -0
  40. wbintegrator_office365-2.2.1.dist-info/RECORD +41 -0
  41. 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")