wbintegrator_office365 2.2.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 (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,623 @@
1
+ import phonenumbers
2
+ from celery import shared_task
3
+ from django.contrib.postgres.fields import ArrayField
4
+ from django.core.exceptions import MultipleObjectsReturned
5
+ from django.db import models, transaction
6
+ from django.db.models import Case, CharField, F, Q, When
7
+ from django.db.models.signals import m2m_changed
8
+ from django.dispatch import receiver
9
+ from wbcore.contrib.directory.models import EmailContact, Person, TelephoneContact
10
+ from wbcore.models import WBModel
11
+ from wbintegrator_office365.importer import MicrosoftGraphAPI
12
+
13
+ from .tenant import TenantUser
14
+
15
+
16
+ class Event(WBModel):
17
+ class Type(models.TextChoices):
18
+ CALLRECORD = "CALLRECORD", "Call Record"
19
+ CALENDAR = "CALENDAR", "Calendar"
20
+
21
+ class Meta:
22
+ verbose_name = "Event"
23
+ verbose_name_plural = "Events"
24
+ permissions = [("administrate_event", "Can Administrate call and calendar events")]
25
+
26
+ auto_inc_id = models.IntegerField(default=0)
27
+ nb_received = models.IntegerField(default=0)
28
+
29
+ uuid_event = models.CharField(
30
+ max_length=1000,
31
+ verbose_name="Event UID",
32
+ help_text="UUID obtained from Microsoft",
33
+ null=True,
34
+ blank=True,
35
+ unique=True,
36
+ )
37
+ id_event = models.CharField(
38
+ max_length=1000,
39
+ verbose_name="Event ID",
40
+ help_text="Event ID obtained from Microsoft",
41
+ null=True,
42
+ blank=True,
43
+ unique=True,
44
+ )
45
+ type = models.CharField(
46
+ max_length=32, choices=Type.choices, null=True, blank=True, verbose_name="Type", default=""
47
+ )
48
+ subscription_id = models.CharField(
49
+ max_length=255, null=True, blank=True, verbose_name="Subscription ID", default=""
50
+ )
51
+ change_type = models.CharField(max_length=255, null=True, blank=True, verbose_name="Change Type", default="")
52
+ resource = models.CharField(max_length=1000, null=True, blank=True, verbose_name="Resource", default="")
53
+ created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
54
+ changed = models.DateTimeField(auto_now=True, verbose_name="Changed")
55
+ is_handled = models.BooleanField(default=True)
56
+ data = models.JSONField(default=dict, null=True, blank=True)
57
+
58
+ tenant_user = models.ForeignKey(
59
+ "wbintegrator_office365.TenantUser",
60
+ related_name="tenant_events",
61
+ null=True,
62
+ blank=True,
63
+ on_delete=models.deletion.SET_NULL,
64
+ verbose_name="Tenant User",
65
+ )
66
+
67
+ def save(self, *args, **kwargs):
68
+ events = Event.objects.filter(id=self.id)
69
+ object_list = Event.objects.order_by("auto_inc_id")
70
+ if len(events) == 0:
71
+ self.nb_received = 1
72
+ if len(object_list) == 0: # if there are no objects
73
+ self.auto_inc_id = 1
74
+ else:
75
+ self.auto_inc_id = object_list.last().auto_inc_id + 1
76
+ else:
77
+ self.nb_received = events.first().nb_received + 1
78
+ self.auto_inc_id = events.first().auto_inc_id
79
+ super().save(*args, **kwargs)
80
+
81
+ def __str__(self):
82
+ return f"{self.id}"
83
+
84
+ @classmethod
85
+ def is_administrator(cls, user):
86
+ user_groups = user.groups
87
+ user_permission = user.user_permissions
88
+ return (
89
+ user_groups.filter(permissions__codename="administrate_event").exists()
90
+ or user_permission.filter(codename="administrate_event").exists()
91
+ or user.is_superuser
92
+ )
93
+
94
+ @classmethod
95
+ def get_endpoint_basename(self):
96
+ return "wbintegrator_office365:event"
97
+
98
+ @classmethod
99
+ def get_representation_endpoint(cls):
100
+ return "wbintegrator_office365:eventrepresentation-list"
101
+
102
+ @classmethod
103
+ def get_representation_value_key(cls):
104
+ return "id"
105
+
106
+ @classmethod
107
+ def get_representation_label_key(cls):
108
+ return "{{id_str}} - {{uuid_event}}"
109
+
110
+ def fetch_call(self):
111
+ if data := MicrosoftGraphAPI().call(self.uuid_event):
112
+ # call user organiser
113
+ is_user = False
114
+ is_guest = False
115
+ is_phone = False
116
+ organizer_id = None
117
+ organizer_display_name = None
118
+ organizer_organization_id = None
119
+ if data.get("organizer.user.id") or (
120
+ data.get("organizer.user.id") == "" and data.get("organizer.user.tenantId")
121
+ ):
122
+ is_user = True
123
+ organizer_id = data.get("organizer.user.id")
124
+ organizer_display_name = data.get("organizer.user.display_name")
125
+ organizer_organization_id = data.get("organizer.user.tenantId")
126
+ elif data.get("organizer.guest.id") or (
127
+ data.get("organizer.guest.id") == "" and data.get("organizer.guest.tenantId")
128
+ ):
129
+ is_guest = True
130
+ organizer_id = data.get("organizer.guest.id")
131
+ organizer_display_name = data.get("organizer.guest.display_name")
132
+ organizer_organization_id = data.get("organizer.guest.tenantId")
133
+ elif data.get("organizer.phone.id") or (
134
+ data.get("organizer.phone.id") == "" and data.get("organizer.phone.tenantId")
135
+ ):
136
+ is_phone = True
137
+ organizer_id = data.get("organizer.phone.id")
138
+ organizer_display_name = data.get("organizer.phone.display_name")
139
+ organizer_organization_id = data.get("organizer.phone.tenantId")
140
+
141
+ call_user_organizer = create_or_update_call_user(
142
+ tenant_id=organizer_id,
143
+ display_name=organizer_display_name,
144
+ tenant_organization_id=organizer_organization_id,
145
+ is_user=is_user,
146
+ is_guest=is_guest,
147
+ is_phone=is_phone,
148
+ acs_user=data.get("organizer.acs_user"),
149
+ splool_user=data.get("organizer.splool_user"),
150
+ encrypted=data.get("organizer.encrypted"),
151
+ on_premises=data.get("organizer.on_premises"),
152
+ acs_application_instance=data.get("organizer.acs_application_instance"),
153
+ spool_application_instance=data.get("organizer.spool_application_instance"),
154
+ application_instance=data.get("organizer.application_instance"),
155
+ application=data.get("organizer.application"),
156
+ device=data.get("organizer.device"),
157
+ )
158
+
159
+ # CALL EVENT
160
+ try:
161
+ call, created = CallEvent.objects.update_or_create(
162
+ event=self,
163
+ defaults={
164
+ "version": data.get("version"),
165
+ "type": data.get("type"),
166
+ "modalities": data.get("modalities"),
167
+ "last_modified": data.get("last_modified"),
168
+ "start": data.get("start"),
169
+ "end": data.get("end"),
170
+ "join_web_url": data.get("join_web_url"),
171
+ "organizer": call_user_organizer,
172
+ "data": data,
173
+ },
174
+ )
175
+ except MultipleObjectsReturned:
176
+ call = CallEvent.objects.filter(event=self).order_by("-pk")[0]
177
+ CallEvent.objects.filter(event=self).exclude(id=call.id).delete()
178
+ CallEvent.objects.filter(event=self).update(
179
+ version=data.get("version"),
180
+ type=data.get("type"),
181
+ modalities=data.get("modalities"),
182
+ last_modified=data.get("last_modified"),
183
+ start=data.get("start"),
184
+ end=data.get("end"),
185
+ join_web_url=data.get("join_web_url"),
186
+ organizer=call_user_organizer,
187
+ data=data,
188
+ )
189
+
190
+ # call user for all participants
191
+ for participant in data.get("participants"):
192
+ is_user = False
193
+ is_guest = False
194
+ is_phone = False
195
+ participant_id = None
196
+ participant_display_name = None
197
+ participant_organization_id = None
198
+ if participant.get("user"):
199
+ is_user = True
200
+ participant_id = participant.get("user").get("id")
201
+ participant_display_name = participant.get("user").get("displayName")
202
+ participant_organization_id = participant.get("user").get("tenantId")
203
+ elif participant.get("guest"):
204
+ is_guest = True
205
+ participant_id = participant.get("guest").get("id")
206
+ participant_display_name = participant.get("guest").get("displayName")
207
+ participant_organization_id = participant.get("guest").get("tenantId")
208
+ elif participant.get("phone"):
209
+ is_phone = True
210
+ participant_id = participant.get("phone").get("id")
211
+ participant_display_name = participant.get("phone").get("displayName")
212
+ participant_organization_id = participant.get("phone").get("tenantId")
213
+
214
+ call_user = create_or_update_call_user(
215
+ tenant_id=participant_id,
216
+ display_name=participant_display_name,
217
+ tenant_organization_id=participant_organization_id,
218
+ is_user=is_user,
219
+ is_guest=is_guest,
220
+ is_phone=is_phone,
221
+ acs_user=participant.get("acsUser"),
222
+ splool_user=participant.get("spoolUser"),
223
+ encrypted=participant.get("encrypted"),
224
+ on_premises=participant.get("on_premises"),
225
+ acs_application_instance=participant.get("acsApplicationInstance"),
226
+ spool_application_instance=participant.get("spoolApplicationInstance"),
227
+ application_instance=participant.get("applicationInstance"),
228
+ application=participant.get("application"),
229
+ device=participant.get("device"),
230
+ )
231
+ call.participants.add(call_user)
232
+
233
+
234
+ class EventLog(WBModel):
235
+ class Meta:
236
+ verbose_name = "Event Log"
237
+ verbose_name_plural = "Event Logs"
238
+
239
+ last_event = models.ForeignKey(
240
+ "Event",
241
+ related_name="event_logs",
242
+ verbose_name="Last Event",
243
+ on_delete=models.deletion.CASCADE,
244
+ )
245
+ id_event = models.CharField(max_length=1000, null=True, blank=True, verbose_name="Event ID")
246
+ order_received = models.IntegerField(default=0)
247
+ change_type = models.CharField(max_length=255, null=True, blank=True, verbose_name="Change Type", default="")
248
+ created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
249
+ changed = models.DateTimeField(auto_now=True, verbose_name="Changed")
250
+ resource = models.CharField(max_length=1000, null=True, blank=True, verbose_name="Resource", default="")
251
+ is_handled = models.BooleanField(default=True)
252
+ data = models.JSONField(default=dict, null=True, blank=True)
253
+
254
+ @classmethod
255
+ def get_endpoint_basename(self):
256
+ return "wbintegrator_office365:eventlog"
257
+
258
+ @classmethod
259
+ def get_representation_endpoint(cls):
260
+ return "wbintegrator_office365:eventlogrepresentation-list"
261
+
262
+ @classmethod
263
+ def get_representation_value_key(cls):
264
+ return "id"
265
+
266
+ @classmethod
267
+ def get_representation_label_key(cls):
268
+ return "{{last_event}} ({{order_received}})"
269
+
270
+
271
+ class CallUser(WBModel):
272
+ acs_user = models.CharField(max_length=256, default="", null=True, blank=True)
273
+ splool_user = models.CharField(max_length=256, default="", null=True, blank=True)
274
+ encrypted = models.CharField(max_length=256, default="", null=True, blank=True)
275
+ on_premises = models.CharField(max_length=256, default="", null=True, blank=True)
276
+ acs_application_instance = models.CharField(max_length=256, default="", null=True, blank=True)
277
+ spool_application_instance = models.CharField(max_length=256, default="", null=True, blank=True)
278
+ application_instance = models.CharField(max_length=256, default="", null=True, blank=True)
279
+ application = models.CharField(max_length=256, default="", null=True, blank=True)
280
+ device = models.CharField(max_length=256, default="", null=True, blank=True)
281
+ is_guest = models.BooleanField(default=False)
282
+ is_phone = models.BooleanField(default=False)
283
+ tenant_user = models.ForeignKey(
284
+ "wbintegrator_office365.TenantUser",
285
+ related_name="call_users",
286
+ null=True,
287
+ blank=True,
288
+ on_delete=models.deletion.SET_NULL,
289
+ )
290
+
291
+ def __str__(self):
292
+ if self.tenant_user:
293
+ if self.tenant_user.profile:
294
+ return f"{self.tenant_user.profile.computed_str}"
295
+ elif self.tenant_user.mail or self.tenant_user.display_name:
296
+ mail = self.tenant_user.mail if self.tenant_user.mail else self.tenant_user.id
297
+ return f"{self.tenant_user.display_name}({mail})"
298
+ else:
299
+ return f"{self.tenant_user.tenant_id}"
300
+ return f"{self.id}"
301
+
302
+ @classmethod
303
+ def annotated_queryset(cls, qs):
304
+ return qs.annotate(
305
+ tenant_user_str=Case(
306
+ When(tenant_user__isnull=True, then=F("id_str")),
307
+ default=F("tenant_user__profile_str"),
308
+ output_field=CharField(),
309
+ ),
310
+ )
311
+
312
+ @classmethod
313
+ def get_endpoint_basename(cls):
314
+ return "wbintegrator_office365:calluser"
315
+
316
+ @classmethod
317
+ def get_representation_endpoint(cls):
318
+ return "wbintegrator_office365:calluserrepresentation-list"
319
+
320
+ @classmethod
321
+ def get_representation_value_key(cls):
322
+ return "id"
323
+
324
+ @classmethod
325
+ def get_representation_label_key(cls):
326
+ return "{{tenant_user_str}}"
327
+
328
+
329
+ class CallEvent(WBModel):
330
+ class Meta:
331
+ verbose_name = "Call Event"
332
+ verbose_name_plural = "Calls Events"
333
+
334
+ notification_types = [
335
+ (
336
+ "wbintegrator_office365.callevent.notify",
337
+ "Call Event Notification",
338
+ "Sends a notification when something happens with a Call Event triggered from Office 365",
339
+ True,
340
+ True,
341
+ False,
342
+ ),
343
+ ]
344
+
345
+ event = models.OneToOneField(to="wbintegrator_office365.Event", related_name="calls", on_delete=models.CASCADE)
346
+ version = models.CharField(max_length=256, null=True, blank=True, verbose_name="Version", default="")
347
+ type = models.CharField(max_length=256, null=True, blank=True, verbose_name="Type", default="")
348
+ modalities = ArrayField(
349
+ models.CharField(max_length=256), blank=True, null=True, verbose_name="Modalities", default=list
350
+ )
351
+ last_modified = models.DateTimeField(null=True, blank=True, verbose_name="Last Modified")
352
+ start = models.DateTimeField(null=True, blank=True, verbose_name="Start")
353
+ end = models.DateTimeField(null=True, blank=True, verbose_name="End")
354
+ join_web_url = models.CharField(max_length=256, null=True, blank=True, verbose_name="Join Web Url", default="")
355
+ organizer = models.ForeignKey(
356
+ CallUser, verbose_name="Organizer", null=True, blank=True, on_delete=models.deletion.SET_NULL
357
+ )
358
+ participants = models.ManyToManyField(
359
+ CallUser, blank=True, related_name="participates", verbose_name="Call participants"
360
+ )
361
+ activity = models.OneToOneField(
362
+ "wbcrm.Activity",
363
+ related_name="call_event",
364
+ null=True,
365
+ blank=True,
366
+ on_delete=models.deletion.SET_NULL,
367
+ verbose_name="Activity",
368
+ )
369
+ created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
370
+ is_internal_call = models.BooleanField(null=True, blank=True, verbose_name="Is internal call")
371
+ data = models.JSONField(default=dict, null=True, blank=True)
372
+
373
+ def __str__(self):
374
+ return f"{self.event.id} ({self.start} - {self.end})"
375
+
376
+ def check_call_is_internal_call(self, caller_id=None):
377
+ is_internal = True
378
+ callers = self.participants.all()
379
+
380
+ if caller_id:
381
+ is_internal = self.is_internal_call if self.is_internal_call is not None else is_internal
382
+ callers = callers.filter(id=caller_id)
383
+
384
+ for caller in callers:
385
+ if (tenant_user := caller.tenant_user) and (person := tenant_user.profile) and person.is_internal:
386
+ is_internal &= True
387
+ else:
388
+ is_internal &= False
389
+
390
+ self.is_internal_call = is_internal
391
+ self.save()
392
+
393
+ @classmethod
394
+ def get_endpoint_basename(cls):
395
+ return "wbintegrator_office365:callevent"
396
+
397
+ @classmethod
398
+ def get_representation_endpoint(cls):
399
+ return "wbintegrator_office365:calleventrepresentation-list"
400
+
401
+ @classmethod
402
+ def get_representation_value_key(cls):
403
+ return "id"
404
+
405
+ @classmethod
406
+ def get_representation_label_key(cls):
407
+ return "{{event.id}} ({{start}} - {{end}})"
408
+
409
+
410
+ # search person by phone if not found
411
+ def get_person_by_phone(phone):
412
+ persons = Person.objects.none()
413
+ phone_numbers = None
414
+ try:
415
+ parser_number = phonenumbers.parse(phone, "CH")
416
+ phone_numbers = (
417
+ phonenumbers.format_number(parser_number, phonenumbers.PhoneNumberFormat.E164)
418
+ if parser_number
419
+ else "".join(phone.split())
420
+ )
421
+ if phone and phone_numbers:
422
+ phone2 = "".join(phone.replace("+41", "").split())
423
+ entries = (
424
+ TelephoneContact.objects.filter(
425
+ Q(number__contains=phone) | Q(number__contains=phone2) | Q(number__contains=phone_numbers)
426
+ )
427
+ .values("entry")
428
+ .distinct()
429
+ )
430
+ persons = Person.objects.filter(id__in=entries)
431
+ except Exception as e:
432
+ print(e, phone) # noqa: T201
433
+ return persons, phone_numbers
434
+
435
+
436
+ def fetch_tenantusers():
437
+ datum = MicrosoftGraphAPI().users()
438
+ count_added = 0
439
+ if datum:
440
+ for data in datum:
441
+ # username = user.get("email")
442
+ # if user.get("display_name") and user.get("id"):
443
+ # username = re.sub(' +', ' ', user.get("display_name").lower()).replace(",", "").replace(" ", "-").strip()
444
+ tenant_id = data.get("id")
445
+ display_name = data.get("display_name")
446
+ mail = data.get("mail") if data.get("mail") else data.get("user_principal_name")
447
+ phone = (
448
+ next(iter(data.get("business_phones") or []), None)
449
+ if data.get("business_phones")
450
+ else data.get("mobile_phone")
451
+ )
452
+ # print(display_name,"/", mail,"/", phone,"/", data.get("given_name"),"/", data.get("surname"))
453
+ # 1) search person by email
454
+ entries = EmailContact.objects.filter(address=mail).values("entry").distinct()
455
+ persons = Person.objects.filter(id__in=entries)
456
+ phone_numbers = None
457
+ # 2) search person by phone if not found
458
+ if persons.count() == 0 and phone:
459
+ persons, phone_numbers = get_person_by_phone(phone)
460
+ # 3) search person by surname and given_name if not found
461
+ if persons.count() == 0 and data.get("surname") and data.get("given_name"):
462
+ persons = Person.objects.filter(
463
+ first_name__icontains=data.get("given_name"), last_name__icontains=data.get("surname")
464
+ ).distinct()
465
+ # get the first person found otherwise it will be None
466
+ person = None
467
+ if persons.count() > 0:
468
+ person = persons.first()
469
+ else:
470
+ first_name, last_name = (
471
+ display_name.split(" ") if len(display_name.split(" ")) == 2 else ["AnonymousUser", tenant_id]
472
+ )
473
+ qs_person = Person.objects.filter(first_name=first_name, last_name=last_name)
474
+ if qs_person.count() > 0:
475
+ person = qs_person.first()
476
+ # person, created = Person.objects.get_or_create(first_name=first_name, last_name=last_name)
477
+ # if mail:
478
+ # EmailContact.objects.get_or_create(
479
+ # primary=True,
480
+ # entry=person,
481
+ # address=mail,
482
+ # )
483
+ # if phone_numbers:
484
+ # TelephoneContact.objects.get_or_create(
485
+ # entry=person,
486
+ # number=phone_numbers
487
+ # )
488
+
489
+ # update or create tenant user
490
+ tenantuser, created = TenantUser.objects.update_or_create(
491
+ tenant_id=tenant_id,
492
+ defaults={"display_name": display_name, "mail": mail, "phone": phone, "profile": person},
493
+ )
494
+ if created:
495
+ count_added += 1
496
+ return datum, count_added
497
+
498
+
499
+ def create_or_update_call_user(
500
+ tenant_id=None,
501
+ display_name=None,
502
+ tenant_organization_id=None,
503
+ is_user=False,
504
+ is_guest=False,
505
+ is_phone=False,
506
+ acs_user=None,
507
+ splool_user=None,
508
+ encrypted=None,
509
+ on_premises=None,
510
+ acs_application_instance=None,
511
+ spool_application_instance=None,
512
+ application_instance=None,
513
+ application=None,
514
+ device=None,
515
+ ):
516
+ tenant_user = None
517
+ # get or create Person
518
+ if tenant_id:
519
+ # search person by tenant id
520
+ qs_tenant_user = TenantUser.objects.filter(tenant_id=tenant_id)
521
+ if qs_tenant_user.count() == 0:
522
+ first_name, last_name = ["AnonymousUser", tenant_id]
523
+ phone_numbers = None
524
+ persons = Person.objects.none()
525
+ person = None
526
+ # search person by display name (also for phone anonymous) # the phone number obtained is "anonymous"
527
+ if is_user or is_guest or (is_phone and tenant_id == "anonymous"):
528
+ # first_name, last_name = display_name.split(" ") if len(display_name.split(" ")) == 2 and display_name != "Guest User" and display_name != "External user" else first_name, last_name
529
+ # Sometimes we obtained "Guest User" or "External user" for certains external user
530
+ if display_name:
531
+ if (
532
+ len(display_name.strip(" ").split(" ")) == 2
533
+ and display_name.lower() != "guest user"
534
+ and display_name.lower() != "external user"
535
+ ):
536
+ first_name, last_name = display_name.strip(" ").split(" ")
537
+ else:
538
+ first_name = display_name
539
+
540
+ persons = Person.objects.filter(
541
+ first_name__icontains=first_name, last_name__icontains=last_name
542
+ ).distinct()
543
+ # search person by phone
544
+ elif is_phone and tenant_id != "anonymous":
545
+ persons, phone_numbers = get_person_by_phone(tenant_id)
546
+
547
+ if persons.count():
548
+ person = persons.first()
549
+ else:
550
+ qs_person = Person.objects.filter(first_name=first_name, last_name=last_name)
551
+ if qs_person.count() > 0:
552
+ person = qs_person.first()
553
+ # person, created = Person.objects.get_or_create(first_name=first_name, last_name=last_name)
554
+ # if phone_numbers:
555
+ # TelephoneContact.objects.get_or_create(
556
+ # entry=person,
557
+ # number=phone_numbers
558
+ # )
559
+ # phone_numbers = tenant_id
560
+
561
+ tenant_user, created = TenantUser.objects.get_or_create(
562
+ tenant_id=tenant_id, defaults={"phone": phone_numbers, "display_name": display_name, "profile": person}
563
+ )
564
+ else:
565
+ tenant_user = TenantUser.objects.get(tenant_id=tenant_id)
566
+
567
+ if tenant_organization_id:
568
+ tenant_user.tenant_organization_id = tenant_organization_id
569
+ tenant_user.save()
570
+ else:
571
+ tenant_user, _ = TenantUser.objects.get_or_create(
572
+ tenant_id=tenant_id, display_name=display_name, tenant_organization_id=tenant_organization_id
573
+ )
574
+
575
+ call_user, created = CallUser.objects.update_or_create(
576
+ acs_user=acs_user,
577
+ splool_user=splool_user,
578
+ encrypted=encrypted,
579
+ on_premises=on_premises,
580
+ acs_application_instance=acs_application_instance,
581
+ spool_application_instance=spool_application_instance,
582
+ application_instance=application_instance,
583
+ application=application,
584
+ device=device,
585
+ tenant_user=tenant_user,
586
+ is_phone=is_phone,
587
+ is_guest=is_guest,
588
+ )
589
+ return call_user
590
+
591
+
592
+ @shared_task
593
+ def handle_event_from_webhook(id_event, notification):
594
+ with transaction.atomic():
595
+ from wbintegrator_office365.models.subscription import Subscription
596
+
597
+ if (
598
+ (subscription_id := notification.get("subscription_id"))
599
+ and (_subscription := Subscription.objects.filter(subscription_id=subscription_id).first())
600
+ and _subscription.type_resource == Subscription.TypeResource.CALLRECORD
601
+ ):
602
+ event, _ = Event.objects.update_or_create(
603
+ uuid_event=id_event,
604
+ defaults={
605
+ "type": _subscription.type_resource,
606
+ "subscription_id": subscription_id,
607
+ "change_type": notification.get("change_type"),
608
+ "data": notification,
609
+ "resource": notification.get("resource"),
610
+ "is_handled": True,
611
+ "tenant_user": _subscription.tenant_user,
612
+ },
613
+ )
614
+ event.fetch_call()
615
+
616
+
617
+ @receiver(m2m_changed, sender=CallEvent.participants.through)
618
+ def post_save_participant_event(sender, instance, action, reverse, pk_set, *args, **kwargs):
619
+ if action == "post_add" and not reverse:
620
+ for participant_id in pk_set:
621
+ instance.check_call_is_internal_call(participant_id)
622
+ elif action == "post_remove" and not reverse:
623
+ instance.check_call_is_internal_call()