wbintegrator_office365 1.43.1__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) 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/templates/admin/tenant_change_list.html +12 -0
  26. wbintegrator_office365/tests/__init__.py +0 -0
  27. wbintegrator_office365/tests/conftest.py +28 -0
  28. wbintegrator_office365/tests/test_admin.py +86 -0
  29. wbintegrator_office365/tests/test_models.py +65 -0
  30. wbintegrator_office365/tests/test_tasks.py +318 -0
  31. wbintegrator_office365/tests/test_views.py +128 -0
  32. wbintegrator_office365/tests/tests.py +12 -0
  33. wbintegrator_office365/urls.py +46 -0
  34. wbintegrator_office365/viewsets/__init__.py +31 -0
  35. wbintegrator_office365/viewsets/display.py +306 -0
  36. wbintegrator_office365/viewsets/endpoints.py +52 -0
  37. wbintegrator_office365/viewsets/menu.py +65 -0
  38. wbintegrator_office365/viewsets/titles.py +49 -0
  39. wbintegrator_office365/viewsets/viewsets.py +745 -0
  40. wbintegrator_office365-1.43.1.dist-info/METADATA +10 -0
  41. wbintegrator_office365-1.43.1.dist-info/RECORD +42 -0
  42. wbintegrator_office365-1.43.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")