wbintegrator_office365 1.43.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 (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,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()