maquinaweb-shared-auth 0.2.60__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.
@@ -0,0 +1,897 @@
1
+ """
2
+ Models abstratos para customização
3
+ Estes models podem ser herdados nos apps clientes para adicionar campos e métodos customizados
4
+ """
5
+
6
+ from shared_auth.conf import GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE
7
+ from shared_auth.conf import GROUP_PERMISSIONS_PERMISSIONS_TABLE
8
+ from shared_auth.conf import PLAN_GROUP_PERMISSIONS_TABLE
9
+ from shared_auth.conf import GROUP_ORG_PERMISSIONS_TABLE
10
+ from shared_auth.conf import SUBSCRIPTION_TABLE
11
+ from shared_auth.conf import PLAN_TABLE
12
+ from shared_auth.conf import GROUP_PERMISSIONS_TABLE
13
+ from shared_auth.conf import PERMISSION_TABLE
14
+ from shared_auth.conf import SYSTEM_TABLE
15
+ import os
16
+ from django.utils import timezone
17
+ from django.contrib.auth.models import AbstractUser
18
+ from django.db import models
19
+
20
+ from .conf import MEMBER_TABLE, ORGANIZATION_TABLE, TOKEN_TABLE, USER_TABLE
21
+ from .exceptions import OrganizationNotFoundError
22
+ from .managers import SharedMemberManager, SharedOrganizationManager, UserManager
23
+ from .storage_backend import Storage
24
+
25
+
26
+ def organization_image_path(instance, filename):
27
+ return os.path.join(
28
+ "organization",
29
+ str(instance.pk),
30
+ "images",
31
+ filename,
32
+ )
33
+
34
+
35
+ class AbstractSharedToken(models.Model):
36
+ """
37
+ Model abstrato READ-ONLY da tabela authtoken_token
38
+ Usado para validar tokens em outros sistemas
39
+
40
+ Para customizar, crie um model no seu app:
41
+
42
+ from shared_auth.abstract_models import AbstractSharedToken
43
+
44
+ class CustomToken(AbstractSharedToken):
45
+ # Adicione campos customizados
46
+ custom_field = models.CharField(max_length=100)
47
+
48
+ class Meta(AbstractSharedToken.Meta):
49
+ pass
50
+
51
+ E configure no settings.py:
52
+ SHARED_AUTH_TOKEN_MODEL = 'seu_app.CustomToken'
53
+ """
54
+
55
+ key = models.CharField(max_length=40, primary_key=True)
56
+ user_id = models.IntegerField()
57
+ created = models.DateTimeField()
58
+
59
+ objects = models.Manager()
60
+
61
+ class Meta:
62
+ abstract = True
63
+ managed = False
64
+ db_table = TOKEN_TABLE
65
+
66
+ def __str__(self):
67
+ return self.key
68
+
69
+ @property
70
+ def user(self):
71
+ """Acessa usuário do token"""
72
+ from .utils import get_user_model
73
+
74
+ if not hasattr(self, "_cached_user"):
75
+ User = get_user_model()
76
+ self._cached_user = User.objects.get_or_fail(self.user_id)
77
+ return self._cached_user
78
+
79
+ def is_valid(self):
80
+ """Verifica se token ainda é válido"""
81
+ # Implementar lógica de expiração se necessário
82
+ return True
83
+
84
+
85
+ class AbstractSharedOrganization(models.Model):
86
+ """
87
+ Model abstrato READ-ONLY da tabela organization
88
+ Usado para acessar dados de organizações em outros sistemas
89
+
90
+ Para customizar, crie um model no seu app:
91
+
92
+ from shared_auth.abstract_models import AbstractSharedOrganization
93
+
94
+ class CustomOrganization(AbstractSharedOrganization):
95
+ # Adicione campos customizados
96
+ custom_field = models.CharField(max_length=100)
97
+
98
+ class Meta(AbstractSharedOrganization.Meta):
99
+ pass
100
+
101
+ E configure no settings.py:
102
+ SHARED_AUTH_ORGANIZATION_MODEL = 'seu_app.CustomOrganization'
103
+ """
104
+
105
+ # Campos principais
106
+ name = models.CharField(max_length=255)
107
+ fantasy_name = models.CharField(max_length=255, blank=True, null=True)
108
+ cnpj = models.CharField(max_length=255, blank=True, null=True)
109
+ telephone = models.CharField(max_length=50, blank=True, null=True)
110
+ cellphone = models.CharField(max_length=50, blank=True, null=True)
111
+ email = models.EmailField(blank=True, null=True)
112
+ image_organization = models.ImageField(
113
+ storage=Storage, upload_to=organization_image_path, null=True
114
+ )
115
+ logo = models.ImageField(
116
+ storage=Storage, upload_to=organization_image_path, null=True
117
+ )
118
+
119
+ # Relacionamentos
120
+ organization_group_id = models.IntegerField(null=True, blank=True)
121
+ main_organization_id = models.IntegerField(null=True, blank=True)
122
+ is_branch = models.BooleanField(default=False)
123
+ metadata = models.JSONField(default=dict)
124
+
125
+ # Metadados
126
+ created_at = models.DateTimeField()
127
+ updated_at = models.DateTimeField()
128
+ deleted_at = models.DateTimeField(null=True, blank=True)
129
+
130
+ objects = SharedOrganizationManager()
131
+
132
+ class Meta:
133
+ abstract = True
134
+ managed = False
135
+ db_table = ORGANIZATION_TABLE
136
+
137
+ def __str__(self):
138
+ return self.fantasy_name or self.name or f"Org #{self.pk}"
139
+
140
+ @property
141
+ def organization_group(self):
142
+ """
143
+ Acessa grupo de organização (lazy loading)
144
+
145
+ Usage:
146
+ if org.organization_group:
147
+ print(org.organization_group.name)
148
+ """
149
+ from .utils import get_organization_group_model
150
+
151
+ if self.organization_group_id:
152
+ OrganizationGroup = get_organization_group_model()
153
+ return OrganizationGroup.objects.get_or_fail(self.organization_group_id)
154
+ return None
155
+
156
+ @property
157
+ def main_organization(self):
158
+ """
159
+ Acessa organização principal (lazy loading)
160
+
161
+ Usage:
162
+ if org.is_branch:
163
+ main = org.main_organization
164
+ """
165
+ from .utils import get_organization_model
166
+
167
+ if self.main_organization_id:
168
+ Organization = get_organization_model()
169
+ return Organization.objects.get_or_fail(self.main_organization_id)
170
+ return None
171
+
172
+ @property
173
+ def branches(self):
174
+ """
175
+ Retorna filiais desta organização
176
+
177
+ Usage:
178
+ branches = org.branches
179
+ """
180
+ from .utils import get_organization_model
181
+
182
+ Organization = get_organization_model()
183
+ return Organization.objects.filter(main_organization_id=self.pk)
184
+
185
+ @property
186
+ def members(self):
187
+ """
188
+ Retorna membros desta organização
189
+
190
+ Usage:
191
+ members = org.members
192
+ for member in members:
193
+ print(member.user.email)
194
+ """
195
+ from .utils import get_member_model
196
+
197
+ Member = get_member_model()
198
+ return Member.objects.for_organization(self.pk)
199
+
200
+ @property
201
+ def users(self):
202
+ """
203
+ Retorna usuários desta organização
204
+
205
+ Usage:
206
+ users = org.users
207
+ """
208
+ from .utils import get_user_model
209
+
210
+ User = get_user_model()
211
+ return User.objects.filter(
212
+ id__in=self.members.values_list("user_id", flat=True)
213
+ )
214
+
215
+ def is_active(self):
216
+ """Verifica se organização está ativa"""
217
+ return self.deleted_at is None
218
+
219
+ def get_permissions_for_system(self, system):
220
+ """
221
+ Retorna permissões do sistema baseado na assinatura do grupo de organizações.
222
+ Se a organização não pertence a um grupo, retorna vazio.
223
+
224
+ Args:
225
+ system: Instance do System model ou system_id (int)
226
+
227
+ Returns:
228
+ QuerySet de Permission objects
229
+
230
+ Usage:
231
+ from shared_auth.utils import get_system_model
232
+
233
+ System = get_system_model()
234
+ system = System.objects.get(name='MeuSistema')
235
+ permissions = organization.get_permissions_for_system(system)
236
+ """
237
+ from .utils import get_subscription_model, get_permission_model
238
+
239
+ # Se não pertence a um grupo, sem permissões
240
+ if not self.organization_group_id:
241
+ Permission = get_permission_model()
242
+ return Permission.objects.none()
243
+
244
+ # Extrai system_id se foi passado um objeto
245
+ system_id = system.id if hasattr(system, 'id') else system
246
+
247
+ Subscription = get_subscription_model()
248
+ Permission = get_permission_model()
249
+
250
+ # Busca assinatura ativa do grupo
251
+ subscription = (
252
+ Subscription.objects
253
+ .filter(
254
+ organization_group_id=self.organization_group_id,
255
+ plan__system_id=system_id,
256
+ active=True,
257
+ paid=True
258
+ )
259
+ .select_related('plan')
260
+ .order_by('-started_at')
261
+ .first()
262
+ )
263
+
264
+ if not subscription:
265
+ return Permission.objects.none()
266
+
267
+ # Coleta todas as permissões dos grupos de permissões do plano
268
+ permission_ids = set()
269
+ for group in subscription.plan.group_permissions.all():
270
+ permission_ids.update(group.permissions.values_list('id', flat=True))
271
+
272
+ return Permission.objects.filter(id__in=permission_ids, system_id=system_id)
273
+
274
+
275
+ class AbstractUser(AbstractUser):
276
+ """
277
+ Model abstrato READ-ONLY da tabela auth_user
278
+
279
+ Para customizar, crie um model no seu app:
280
+
281
+ from shared_auth.abstract_models import AbstractUser
282
+
283
+ class CustomUser(AbstractUser):
284
+ # Adicione campos customizados
285
+ custom_field = models.CharField(max_length=100)
286
+
287
+ class Meta(AbstractUser.Meta):
288
+ pass
289
+
290
+ E configure no settings.py:
291
+ SHARED_AUTH_USER_MODEL = 'seu_app.CustomUser'
292
+ """
293
+
294
+ date_joined = models.DateTimeField()
295
+ last_login = models.DateTimeField(null=True, blank=True)
296
+ avatar = models.ImageField(storage=Storage, blank=True, null=True)
297
+
298
+ # Campos customizados
299
+ createdat = models.DateTimeField()
300
+ updatedat = models.DateTimeField()
301
+ deleted_at = models.DateTimeField(null=True, blank=True)
302
+
303
+ objects = UserManager()
304
+
305
+ class Meta:
306
+ abstract = True
307
+ managed = False
308
+ db_table = USER_TABLE
309
+
310
+ @property
311
+ def organizations(self):
312
+ """
313
+ Retorna todas as organizações associadas ao usuário.
314
+ """
315
+ from .utils import get_member_model, get_organization_model
316
+
317
+ Organization = get_organization_model()
318
+ Member = get_member_model()
319
+
320
+ return Organization.objects.filter(
321
+ id__in=Member.objects.filter(user_id=self.id).values_list(
322
+ "organization_id", flat=True
323
+ )
324
+ )
325
+
326
+ def get_org(self, organization_id):
327
+ """
328
+ Retorna a organização especificada pelo ID, se o usuário for membro.
329
+ """
330
+ from .utils import get_member_model, get_organization_model
331
+
332
+ Organization = get_organization_model()
333
+ Member = get_member_model()
334
+
335
+ try:
336
+ organization = Organization.objects.get(id=organization_id)
337
+ except Organization.DoesNotExist:
338
+ raise OrganizationNotFoundError(
339
+ f"Organização com ID {organization_id} não encontrada."
340
+ )
341
+
342
+ if not Member.objects.filter(
343
+ user_id=self.id, organization_id=organization.id
344
+ ).exists():
345
+ raise OrganizationNotFoundError("Usuário não é membro desta organização.")
346
+
347
+ return organization
348
+
349
+
350
+ class AbstractSharedMember(models.Model):
351
+ """
352
+ Model abstrato READ-ONLY da tabela organization_member
353
+ Relacionamento entre User e Organization
354
+
355
+ Para customizar, crie um model no seu app:
356
+
357
+ from shared_auth.abstract_models import AbstractSharedMember
358
+
359
+ class CustomMember(AbstractSharedMember):
360
+ # Adicione campos customizados
361
+ custom_field = models.CharField(max_length=100)
362
+
363
+ class Meta(AbstractSharedMember.Meta):
364
+ pass
365
+
366
+ E configure no settings.py:
367
+ SHARED_AUTH_MEMBER_MODEL = 'seu_app.CustomMember'
368
+ """
369
+
370
+ user_id = models.IntegerField()
371
+ organization_id = models.IntegerField()
372
+ metadata = models.JSONField(default=dict)
373
+
374
+ objects = SharedMemberManager()
375
+
376
+ class Meta:
377
+ abstract = True
378
+ managed = False
379
+ db_table = MEMBER_TABLE
380
+
381
+ def __str__(self):
382
+ return f"Member: User {self.user_id} - Org {self.organization_id}"
383
+
384
+ @property
385
+ def user(self):
386
+ """
387
+ Acessa usuário (lazy loading)
388
+
389
+ Usage:
390
+ member = SharedMember.objects.first()
391
+ user = member.user
392
+ print(user.email)
393
+ """
394
+ from .utils import get_user_model
395
+
396
+ User = get_user_model()
397
+ return User.objects.get_or_fail(self.user_id)
398
+
399
+ @property
400
+ def organization(self):
401
+ """
402
+ Acessa organização (lazy loading)
403
+
404
+ Usage:
405
+ member = SharedMember.objects.first()
406
+ org = member.organization
407
+ print(org.name)
408
+ """
409
+ from .utils import get_organization_model
410
+
411
+ Organization = get_organization_model()
412
+ return Organization.objects.get_or_fail(self.organization_id)
413
+
414
+ class GroupPermissionsPermission(models.Model):
415
+ grouppermissions_id = models.BigIntegerField()
416
+ permissions_id = models.BigIntegerField()
417
+
418
+ class Meta:
419
+ db_table = GROUP_PERMISSIONS_PERMISSIONS_TABLE
420
+ managed = False
421
+ unique_together = ('grouppermissions_id', 'permissions_id')
422
+ app_label = 'shared_auth'
423
+
424
+ def __str__(self):
425
+ return f"GroupPerm {self.grouppermissions_id} → Perm {self.permissions_id}"
426
+
427
+
428
+ class PlanGroupPermission(models.Model):
429
+ plan_id = models.BigIntegerField()
430
+ grouppermissions_id = models.BigIntegerField()
431
+
432
+ class Meta:
433
+ db_table = PLAN_GROUP_PERMISSIONS_TABLE
434
+ managed = False
435
+ unique_together = ('plan_id', 'grouppermissions_id')
436
+ app_label = 'shared_auth'
437
+
438
+ def __str__(self):
439
+ return f"Plan {self.plan_id} → GroupPerm {self.grouppermissions_id}"
440
+
441
+ class GroupOrgPermissionsPermission(models.Model):
442
+ grouporganizationpermissions_id = models.BigIntegerField()
443
+ permissions_id = models.BigIntegerField()
444
+
445
+ class Meta:
446
+ db_table = GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE
447
+ managed = False
448
+ unique_together = ('grouporganizationpermissions_id', 'permissions_id')
449
+ app_label = 'shared_auth'
450
+
451
+ def __str__(self):
452
+ return f"OrgGroup {self.grouporganizationpermissions_id} → Perm {self.permissions_id}"
453
+
454
+
455
+ class AbstractSystem(models.Model):
456
+ """
457
+ Model abstrato READ-ONLY da tabela plans_system
458
+ Representa um sistema externo que usa este serviço de autenticação
459
+
460
+ Para customizar, crie um model no seu app:
461
+
462
+ from shared_auth.abstract_models import AbstractSystem
463
+
464
+ class CustomSystem(AbstractSystem):
465
+ custom_field = models.CharField(max_length=100)
466
+
467
+ class Meta(AbstractSystem.Meta):
468
+ pass
469
+
470
+ E configure no settings.py:
471
+ SHARED_AUTH_SYSTEM_MODEL = 'seu_app.CustomSystem'
472
+ """
473
+
474
+ name = models.CharField(max_length=100)
475
+ description = models.TextField(blank=True)
476
+ active = models.BooleanField(default=True)
477
+ created_at = models.DateTimeField()
478
+ updated_at = models.DateTimeField()
479
+
480
+ objects = models.Manager() # Will be replaced by SystemManager in concrete model
481
+
482
+ class Meta:
483
+ abstract = True
484
+ managed = False
485
+ db_table = SYSTEM_TABLE
486
+
487
+ def __str__(self):
488
+ return self.name
489
+
490
+
491
+ class AbstractPermission(models.Model):
492
+ """
493
+ Model abstrato READ-ONLY da tabela organization_permissions
494
+ Define permissões específicas de cada sistema
495
+
496
+ Para customizar, crie um model no seu app e configure:
497
+ SHARED_AUTH_PERMISSION_MODEL = 'seu_app.CustomPermission'
498
+ """
499
+
500
+ codename = models.CharField(max_length=100)
501
+ name = models.CharField(max_length=100)
502
+ description = models.TextField()
503
+ scope = models.CharField(
504
+ max_length=100,
505
+ help_text="Agrupamento da permissão (ex: 'Recebimentos', 'Pagamentos', 'Relatórios')",
506
+ blank=True,
507
+ default=""
508
+ )
509
+ system_id = models.IntegerField()
510
+
511
+ objects = models.Manager()
512
+
513
+ class Meta:
514
+ abstract = True
515
+ managed = False
516
+ db_table = PERMISSION_TABLE
517
+
518
+ def __str__(self):
519
+ return f"{self.codename} ({self.name})"
520
+
521
+ @property
522
+ def system(self):
523
+ """Acessa sistema (lazy loading)"""
524
+ from .utils import get_system_model
525
+
526
+ if not hasattr(self, "_cached_system"):
527
+ System = get_system_model()
528
+ self._cached_system = System.objects.get_or_fail(self.system_id)
529
+ return self._cached_system
530
+
531
+
532
+ class AbstractGroupPermissions(models.Model):
533
+ """
534
+ Model abstrato READ-ONLY da tabela organization_grouppermissions
535
+ Grupos base de permissões (usados nos planos)
536
+
537
+ Para customizar, configure:
538
+ SHARED_AUTH_GROUP_PERMISSIONS_MODEL = 'seu_app.CustomGroupPermissions'
539
+ """
540
+
541
+ name = models.CharField(max_length=100)
542
+ description = models.TextField(blank=True)
543
+ system_id = models.IntegerField()
544
+
545
+ objects = models.Manager()
546
+
547
+ class Meta:
548
+ abstract = True
549
+ managed = False
550
+ db_table = GROUP_PERMISSIONS_TABLE
551
+
552
+ def __str__(self):
553
+ return self.name
554
+
555
+ @property
556
+ def system(self):
557
+ """Acessa sistema (lazy loading)"""
558
+ from .utils import get_system_model
559
+
560
+ if not hasattr(self, "_cached_system"):
561
+ System = get_system_model()
562
+ self._cached_system = System.objects.get_or_fail(self.system_id)
563
+ return self._cached_system
564
+
565
+ @property
566
+ def permissions(self):
567
+ from .utils import get_permission_model
568
+
569
+ Permission = get_permission_model()
570
+
571
+ perm_ids = GroupPermissionsPermission.objects.using('auth_db') \
572
+ .filter(grouppermissions_id=self.pk) \
573
+ .values_list('permissions_id', flat=True)
574
+
575
+ return Permission.objects.using('auth_db').filter(id__in=perm_ids)
576
+
577
+
578
+ class AbstractPlan(models.Model):
579
+ """
580
+ Model abstrato READ-ONLY da tabela plans_plan
581
+ Planos oferecidos por cada sistema, com conjunto de permissões
582
+
583
+ Para customizar, configure:
584
+ SHARED_AUTH_PLAN_MODEL = 'seu_app.CustomPlan'
585
+ """
586
+
587
+ name = models.CharField(max_length=100)
588
+ slug = models.SlugField()
589
+ system_id = models.IntegerField()
590
+ description = models.TextField(blank=True)
591
+ price = models.DecimalField(max_digits=10, decimal_places=2)
592
+ active = models.BooleanField(default=True)
593
+ recurrence = models.CharField(max_length=10)
594
+ created_at = models.DateTimeField()
595
+ updated_at = models.DateTimeField()
596
+
597
+ objects = models.Manager()
598
+
599
+ class Meta:
600
+ abstract = True
601
+ managed = False
602
+ db_table = PLAN_TABLE
603
+
604
+ def __str__(self):
605
+ return f"{self.name} - {self.system.name if hasattr(self, 'system') else self.system_id}"
606
+
607
+ @property
608
+ def system(self):
609
+ """Acessa sistema (lazy loading)"""
610
+ from .utils import get_system_model
611
+
612
+ if not hasattr(self, "_cached_system"):
613
+ System = get_system_model()
614
+ self._cached_system = System.objects.get_or_fail(self.system_id)
615
+ return self._cached_system
616
+
617
+ @property
618
+ def group_permissions(self):
619
+ from .utils import get_group_permissions_model
620
+
621
+ GroupPermissions = get_group_permissions_model()
622
+
623
+ group_ids = PlanGroupPermission.objects.using('auth_db') \
624
+ .filter(plan_id=self.pk) \
625
+ .values_list('grouppermissions_id', flat=True)
626
+
627
+ return GroupPermissions.objects.using('auth_db').filter(id__in=group_ids)
628
+
629
+
630
+ class AbstractOrganizationGroup(models.Model):
631
+ """
632
+ Model abstrato READ-ONLY da tabela organization_organizationgroup
633
+ Representa um grupo de organizações com assinatura compartilhada
634
+
635
+ Para customizar, crie um model no seu app:
636
+
637
+ from shared_auth.abstract_models import AbstractOrganizationGroup
638
+
639
+ class CustomOrganizationGroup(AbstractOrganizationGroup):
640
+ custom_field = models.CharField(max_length=100)
641
+
642
+ class Meta(AbstractOrganizationGroup.Meta):
643
+ pass
644
+
645
+ E configure no settings.py:
646
+ SHARED_AUTH_ORGANIZATION_GROUP_MODEL = 'seu_app.CustomOrganizationGroup'
647
+ """
648
+ owner_id = models.IntegerField()
649
+ name = models.CharField(
650
+ max_length=255,
651
+ help_text="Nome do grupo (ex: 'Empresas do João', 'Grupo Acme')"
652
+ )
653
+ created_at = models.DateTimeField()
654
+ updated_at = models.DateTimeField()
655
+
656
+ objects = models.Manager()
657
+
658
+ class Meta:
659
+ abstract = True
660
+ managed = False
661
+ db_table = "organization_organizationgroup" # Will be imported from conf in concrete model
662
+
663
+ def __str__(self):
664
+ return f"{self.name} (Owner: {self.owner_id})"
665
+
666
+ @property
667
+ def owner(self):
668
+ """Acessa usuário (lazy loading)"""
669
+ from .utils import get_user_model
670
+
671
+ if not hasattr(self, "_cached_owner"):
672
+ User = get_user_model()
673
+ self._cached_owner = User.objects.get_or_fail(self.owner_id)
674
+ return self._cached_owner
675
+
676
+ @property
677
+ def organizations(self):
678
+ """Retorna organizações pertencentes a este grupo"""
679
+ from .utils import get_organization_model
680
+
681
+ Organization = get_organization_model()
682
+ return Organization.objects.filter(organization_group_id=self.pk)
683
+
684
+ @property
685
+ def subscriptions(self):
686
+ """Retorna assinaturas ativas deste grupo"""
687
+ from .utils import get_subscription_model
688
+
689
+ Subscription = get_subscription_model()
690
+ return Subscription.objects.filter(organization_group_id=self.pk)
691
+
692
+
693
+ class AbstractSubscription(models.Model):
694
+ """
695
+ Model abstrato READ-ONLY da tabela plans_subscription
696
+ Assinatura de plano por grupo de organizações.
697
+ Uma assinatura cobre TODAS as organizações do grupo.
698
+
699
+ Para customizar, configure:
700
+ SHARED_AUTH_SUBSCRIPTION_MODEL = 'seu_app.CustomSubscription'
701
+ """
702
+
703
+ organization_group_id = models.IntegerField(
704
+ help_text="Grupo de organizações cobertas por esta assinatura"
705
+ )
706
+ plan_id = models.IntegerField()
707
+ period = models.CharField(max_length=20, help_text="Período da assinatura (ex: '2025-01', 'Q1-2025')")
708
+ payment_date = models.DateTimeField(null=True, blank=True)
709
+ paid = models.BooleanField(default=False)
710
+ active = models.BooleanField(default=True)
711
+ started_at = models.DateTimeField()
712
+ expires_at = models.DateTimeField(null=True, blank=True)
713
+ created_at = models.DateTimeField()
714
+ updated_at = models.DateTimeField()
715
+
716
+ objects = models.Manager() # Will be replaced by SubscriptionManager
717
+
718
+ class Meta:
719
+ abstract = True
720
+ managed = False
721
+ db_table = SUBSCRIPTION_TABLE
722
+
723
+ def __str__(self):
724
+ return f"Subscription {self.pk} - Group {self.organization_group_id}"
725
+
726
+ @property
727
+ def organization_group(self):
728
+ """Acessa grupo de organizações (lazy loading)"""
729
+ from .utils import get_organization_group_model
730
+
731
+ if not hasattr(self, "_cached_organization_group"):
732
+ OrganizationGroup = get_organization_group_model()
733
+ try:
734
+ self._cached_organization_group = OrganizationGroup.objects.get(pk=self.organization_group_id)
735
+ except OrganizationGroup.DoesNotExist:
736
+ self._cached_organization_group = None
737
+ return self._cached_organization_group
738
+
739
+ @property
740
+ def plan(self):
741
+ """Acessa plano (lazy loading)"""
742
+ from .utils import get_plan_model
743
+
744
+ if not hasattr(self, "_cached_plan"):
745
+ Plan = get_plan_model()
746
+ try:
747
+ self._cached_plan = Plan.objects.get(pk=self.plan_id)
748
+ except Plan.DoesNotExist:
749
+ self._cached_plan = None
750
+ return self._cached_plan
751
+
752
+ def is_valid(self):
753
+ """Verifica se assinatura está ativa, paga e não expirada"""
754
+
755
+ if not self.active or not self.paid:
756
+ return False
757
+
758
+ if self.expires_at and self.expires_at < timezone.now():
759
+ return False
760
+
761
+ return True
762
+
763
+ def is_expired(self):
764
+ """Verifica se a assinatura está expirada"""
765
+
766
+ if not self.expires_at:
767
+ return False
768
+ return timezone.now() > self.expires_at
769
+
770
+ def set_paid(self):
771
+ """Marca assinatura como paga (apenas para referência, não salva)"""
772
+ self.paid = True
773
+ self.payment_date = timezone.now()
774
+
775
+ def cancel(self):
776
+ """Cancela assinatura (apenas para referência, não salva)"""
777
+ self.active = False
778
+
779
+
780
+
781
+ class AbstractGroupOrganizationPermissions(models.Model):
782
+ """
783
+ Model abstrato READ-ONLY da tabela organization_grouporganizationpermissions
784
+ Grupos de permissões criados pela organização para distribuir aos usuários
785
+
786
+ Para customizar, configure:
787
+ SHARED_AUTH_GROUP_ORG_PERMISSIONS_MODEL = 'seu_app.CustomGroupOrgPermissions'
788
+ """
789
+
790
+ organization_id = models.IntegerField()
791
+ system_id = models.IntegerField()
792
+ name = models.CharField(max_length=100)
793
+ description = models.TextField(blank=True)
794
+
795
+ objects = models.Manager() # Will be replaced by GroupOrganizationPermissionsManager
796
+
797
+ class Meta:
798
+ abstract = True
799
+ managed = False
800
+ db_table = GROUP_ORG_PERMISSIONS_TABLE
801
+
802
+ def __str__(self):
803
+ return f"{self.name} (Org {self.organization_id})"
804
+
805
+ @property
806
+ def organization(self):
807
+ """Acessa organização (lazy loading)"""
808
+ from .utils import get_organization_model
809
+
810
+ if not hasattr(self, "_cached_organization"):
811
+ Organization = get_organization_model()
812
+ self._cached_organization = Organization.objects.get_or_fail(self.organization_id)
813
+ return self._cached_organization
814
+
815
+ @property
816
+ def system(self):
817
+ """Acessa sistema (lazy loading)"""
818
+ from .utils import get_system_model
819
+
820
+ if not hasattr(self, "_cached_system"):
821
+ System = get_system_model()
822
+ self._cached_system = System.objects.get_or_fail(self.system_id)
823
+ return self._cached_system
824
+
825
+ @property
826
+ def permissions(self):
827
+ from .utils import get_permission_model
828
+
829
+ Permission = get_permission_model()
830
+
831
+ perm_ids = GroupOrgPermissionsPermission.objects.using('auth_db') \
832
+ .filter(grouporganizationpermissions_id=self.pk) \
833
+ .values_list('permissions_id', flat=True)
834
+
835
+ return Permission.objects.using('auth_db').filter(id__in=perm_ids)
836
+
837
+
838
+ class AbstractMemberSystemGroup(models.Model):
839
+ """
840
+ Model abstrato READ-ONLY da tabela organization_membersystemgroup
841
+ Relaciona um membro a um grupo de permissões em um sistema específico
842
+
843
+ Para customizar, configure:
844
+ SHARED_AUTH_MEMBER_SYSTEM_GROUP_MODEL = 'seu_app.CustomMemberSystemGroup'
845
+ """
846
+
847
+ member_id = models.IntegerField()
848
+ group_id = models.IntegerField()
849
+ system_id = models.IntegerField()
850
+ created_at = models.DateTimeField()
851
+
852
+ objects = models.Manager() # Will be replaced by MemberSystemGroupManager
853
+
854
+ class Meta:
855
+ abstract = True
856
+ managed = False
857
+ db_table = GROUP_ORG_PERMISSIONS_TABLE
858
+
859
+ def __str__(self):
860
+ return f"Member {self.member_id} - Group {self.group_id} - System {self.system_id}"
861
+
862
+ @property
863
+ def member(self):
864
+ """Acessa membro (lazy loading)"""
865
+ from .utils import get_member_model
866
+
867
+ if not hasattr(self, "_cached_member"):
868
+ Member = get_member_model()
869
+ try:
870
+ self._cached_member = Member.objects.get(pk=self.member_id)
871
+ except Member.DoesNotExist:
872
+ self._cached_member = None
873
+ return self._cached_member
874
+
875
+ @property
876
+ def group(self):
877
+ """Acessa grupo (lazy loading)"""
878
+ from .utils import get_group_organization_permissions_model
879
+
880
+ if not hasattr(self, "_cached_group"):
881
+ GroupOrgPermissions = get_group_organization_permissions_model()
882
+ try:
883
+ self._cached_group = GroupOrgPermissions.objects.get(pk=self.group_id)
884
+ except GroupOrgPermissions.DoesNotExist:
885
+ self._cached_group = None
886
+ return self._cached_group
887
+
888
+ @property
889
+ def system(self):
890
+ """Acessa sistema (lazy loading)"""
891
+ from .utils import get_system_model
892
+
893
+ if not hasattr(self, "_cached_system"):
894
+ System = get_system_model()
895
+ self._cached_system = System.objects.get_or_fail(self.system_id)
896
+ return self._cached_system
897
+