constec 0.7.1__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 (40) hide show
  1. constec/db/__init__.py +11 -0
  2. constec/db/apps.py +8 -0
  3. constec/db/migrations/0001_initial.py +551 -0
  4. constec/db/migrations/0002_module_level.py +38 -0
  5. constec/db/migrations/0003_remove_module_level.py +15 -0
  6. constec/db/migrations/0004_rename_entities_company_cuit_idx_entities_company_e2c50f_idx_and_more.py +345 -0
  7. constec/db/migrations/0005_event.py +46 -0
  8. constec/db/migrations/0006_automation_trigger_action_executionlog_notificationtemplate.py +275 -0
  9. constec/db/migrations/0007_add_organization_to_automations.py +91 -0
  10. constec/db/migrations/0008_refactor_creator_fields.py +173 -0
  11. constec/db/migrations/0009_rename_user_to_companyuser.py +40 -0
  12. constec/db/migrations/__init__.py +0 -0
  13. constec/db/models/__init__.py +110 -0
  14. constec/db/models/automation.py +488 -0
  15. constec/db/models/base.py +23 -0
  16. constec/db/models/company.py +36 -0
  17. constec/db/models/contact.py +71 -0
  18. constec/db/models/erp.py +101 -0
  19. constec/db/models/erp_entity.py +122 -0
  20. constec/db/models/flow.py +138 -0
  21. constec/db/models/group.py +36 -0
  22. constec/db/models/module.py +67 -0
  23. constec/db/models/organization.py +62 -0
  24. constec/db/models/person.py +28 -0
  25. constec/db/models/session.py +89 -0
  26. constec/db/models/tag.py +70 -0
  27. constec/db/models/user.py +74 -0
  28. constec/py.typed +0 -0
  29. constec/services/__init__.py +14 -0
  30. constec/services/encryption.py +92 -0
  31. constec/shared/__init__.py +20 -0
  32. constec/shared/exceptions.py +48 -0
  33. constec/utils/__init__.py +20 -0
  34. constec/utils/cuit.py +107 -0
  35. constec/utils/password.py +62 -0
  36. constec-0.7.1.dist-info/METADATA +94 -0
  37. constec-0.7.1.dist-info/RECORD +40 -0
  38. constec-0.7.1.dist-info/WHEEL +5 -0
  39. constec-0.7.1.dist-info/licenses/LICENSE +21 -0
  40. constec-0.7.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,70 @@
1
+ from django.db import models
2
+ from .base import UUIDModel
3
+ from .person import Person
4
+ from .company import Company
5
+
6
+
7
+ class TagCategory(UUIDModel):
8
+ """Tag categories per company."""
9
+ company = models.ForeignKey(
10
+ Company,
11
+ on_delete=models.CASCADE,
12
+ related_name="tag_categories",
13
+ )
14
+ name = models.CharField(max_length=50)
15
+ description = models.TextField(blank=True, null=True)
16
+
17
+ class Meta:
18
+ app_label = 'constec_db'
19
+ db_table = 'core"."tag_categories'
20
+ verbose_name = 'Tag category'
21
+ verbose_name_plural = 'Tag categories'
22
+
23
+ def __str__(self):
24
+ return f"{self.name}"
25
+
26
+
27
+ class PersonTag(UUIDModel):
28
+ """Tags for persons."""
29
+ company = models.ForeignKey(
30
+ Company,
31
+ on_delete=models.CASCADE,
32
+ related_name="person_tags",
33
+ )
34
+ category = models.ForeignKey(
35
+ TagCategory,
36
+ on_delete=models.CASCADE,
37
+ related_name="person_tags",
38
+ )
39
+ name = models.CharField(max_length=50)
40
+ color = models.CharField(max_length=7, blank=True, null=True)
41
+
42
+ class Meta:
43
+ app_label = 'constec_db'
44
+ db_table = 'core"."person_tags'
45
+ unique_together = [['company', 'category', 'name']]
46
+
47
+ def __str__(self):
48
+ return f"{self.name}"
49
+
50
+
51
+ class PersonTagged(UUIDModel):
52
+ """Person-tag relationship."""
53
+ person = models.ForeignKey(
54
+ Person,
55
+ on_delete=models.CASCADE,
56
+ related_name="tagged_items",
57
+ )
58
+ tag = models.ForeignKey(
59
+ PersonTag,
60
+ on_delete=models.CASCADE,
61
+ related_name="tagged_persons",
62
+ )
63
+
64
+ class Meta:
65
+ app_label = 'constec_db'
66
+ db_table = 'core"."person_tagged'
67
+ unique_together = [['person', 'tag']]
68
+
69
+ def __str__(self):
70
+ return f"{self.person.full_name} - {self.tag.name}"
@@ -0,0 +1,74 @@
1
+ from django.db import models
2
+ from .base import UUIDModel
3
+ from .company import Company
4
+
5
+
6
+ class CompanyUser(UUIDModel):
7
+ """User belonging to a company."""
8
+ company = models.ForeignKey(
9
+ Company,
10
+ on_delete=models.CASCADE,
11
+ related_name="users"
12
+ )
13
+ name = models.CharField(max_length=255)
14
+ email = models.EmailField(unique=True)
15
+ password_hash = models.CharField(max_length=255)
16
+
17
+ class Meta:
18
+ app_label = 'constec_db'
19
+ db_table = 'core"."company_users'
20
+
21
+ def __str__(self):
22
+ return f"{self.name} ({self.email})"
23
+
24
+
25
+ class CompanyUserRole(UUIDModel):
26
+ """Role within a company."""
27
+ company = models.ForeignKey(
28
+ Company,
29
+ on_delete=models.CASCADE,
30
+ related_name="user_roles",
31
+ null=True,
32
+ blank=True
33
+ )
34
+ name = models.CharField(max_length=100)
35
+ description = models.TextField(blank=True, null=True)
36
+ permissions = models.JSONField(default=dict)
37
+ is_system_role = models.BooleanField(default=False)
38
+
39
+ class Meta:
40
+ app_label = 'constec_db'
41
+ db_table = 'core"."company_user_roles'
42
+ unique_together = [['company', 'name']]
43
+
44
+ def __str__(self):
45
+ return f"{self.name}"
46
+
47
+
48
+ class CompanyUserAccess(UUIDModel):
49
+ """Cross-company access: one CompanyUser can access multiple Companies."""
50
+ user = models.ForeignKey(
51
+ CompanyUser,
52
+ on_delete=models.CASCADE,
53
+ related_name="company_accesses",
54
+ )
55
+ company = models.ForeignKey(
56
+ Company,
57
+ on_delete=models.CASCADE,
58
+ related_name="user_accesses",
59
+ )
60
+ role = models.ForeignKey(
61
+ CompanyUserRole,
62
+ on_delete=models.PROTECT,
63
+ related_name="user_accesses",
64
+ )
65
+
66
+ class Meta:
67
+ app_label = 'constec_db'
68
+ db_table = 'core"."company_user_access'
69
+ verbose_name = 'Company user access'
70
+ verbose_name_plural = 'Company user access'
71
+ unique_together = [['user', 'company']]
72
+
73
+ def __str__(self):
74
+ return f"{self.user.email} @ {self.company.name} ({self.role.name})"
constec/py.typed ADDED
File without changes
@@ -0,0 +1,14 @@
1
+ """
2
+ constec.services - Shared services for the Constec platform.
3
+
4
+ Usage:
5
+ from constec.services import encrypt_password, decrypt_password
6
+ """
7
+
8
+ from .encryption import encrypt_password, decrypt_password, EncryptionService
9
+
10
+ __all__ = [
11
+ 'encrypt_password',
12
+ 'decrypt_password',
13
+ 'EncryptionService',
14
+ ]
@@ -0,0 +1,92 @@
1
+ """
2
+ Fernet encryption for sensitive data like database passwords.
3
+
4
+ Usage:
5
+ from constec.services import encrypt_password, decrypt_password
6
+
7
+ # Encrypt
8
+ encrypted = encrypt_password("my_password", fernet_key)
9
+
10
+ # Decrypt
11
+ password = decrypt_password(encrypted, fernet_key)
12
+
13
+ The FERNET_KEY should be stored in environment variables and never committed.
14
+ Generate a new key with:
15
+ from cryptography.fernet import Fernet
16
+ print(Fernet.generate_key().decode())
17
+ """
18
+
19
+ from cryptography.fernet import Fernet, InvalidToken
20
+
21
+
22
+ class EncryptionService:
23
+ """Service for encrypting and decrypting sensitive data."""
24
+
25
+ def __init__(self, key: str):
26
+ """
27
+ Initialize with Fernet key.
28
+
29
+ Args:
30
+ key: Fernet key as string (will be encoded to bytes)
31
+ """
32
+ self._fernet = Fernet(key.encode() if isinstance(key, str) else key)
33
+
34
+ def encrypt(self, plaintext: str) -> str:
35
+ """
36
+ Encrypt a string.
37
+
38
+ Args:
39
+ plaintext: String to encrypt
40
+
41
+ Returns:
42
+ Encrypted string (base64 encoded)
43
+ """
44
+ return self._fernet.encrypt(plaintext.encode()).decode()
45
+
46
+ def decrypt(self, ciphertext: str) -> str:
47
+ """
48
+ Decrypt a string.
49
+
50
+ Args:
51
+ ciphertext: Encrypted string (base64 encoded)
52
+
53
+ Returns:
54
+ Decrypted string
55
+
56
+ Raises:
57
+ InvalidToken: If decryption fails (wrong key or corrupted data)
58
+ """
59
+ return self._fernet.decrypt(ciphertext.encode()).decode()
60
+
61
+
62
+ def encrypt_password(password: str, fernet_key: str) -> str:
63
+ """
64
+ Encrypt a password using Fernet symmetric encryption.
65
+
66
+ Args:
67
+ password: Plain text password
68
+ fernet_key: Fernet encryption key
69
+
70
+ Returns:
71
+ Encrypted password (base64 encoded string)
72
+ """
73
+ service = EncryptionService(fernet_key)
74
+ return service.encrypt(password)
75
+
76
+
77
+ def decrypt_password(encrypted_password: str, fernet_key: str) -> str:
78
+ """
79
+ Decrypt a password using Fernet symmetric encryption.
80
+
81
+ Args:
82
+ encrypted_password: Encrypted password (base64 encoded string)
83
+ fernet_key: Fernet encryption key
84
+
85
+ Returns:
86
+ Decrypted password
87
+
88
+ Raises:
89
+ InvalidToken: If decryption fails
90
+ """
91
+ service = EncryptionService(fernet_key)
92
+ return service.decrypt(encrypted_password)
@@ -0,0 +1,20 @@
1
+ """
2
+ Shared utilities for the Constec library ecosystem.
3
+ """
4
+ from constec.shared.exceptions import (
5
+ ConstecError,
6
+ ConstecAPIError,
7
+ ConstecConnectionError,
8
+ ConstecValidationError,
9
+ ConstecAuthenticationError,
10
+ ConstecNotFoundError,
11
+ )
12
+
13
+ __all__ = [
14
+ "ConstecError",
15
+ "ConstecAPIError",
16
+ "ConstecConnectionError",
17
+ "ConstecValidationError",
18
+ "ConstecAuthenticationError",
19
+ "ConstecNotFoundError",
20
+ ]
@@ -0,0 +1,48 @@
1
+ """
2
+ Shared exception classes for the Constec library ecosystem.
3
+ """
4
+ from typing import Optional
5
+
6
+
7
+ class ConstecError(Exception):
8
+ """Base exception for all Constec-related errors."""
9
+
10
+ def __init__(self, message: str, details: Optional[dict] = None):
11
+ self.message = message
12
+ self.details = details or {}
13
+ super().__init__(message)
14
+
15
+ def __str__(self):
16
+ if self.details:
17
+ return f"{self.message} (Details: {self.details})"
18
+ return self.message
19
+
20
+
21
+ class ConstecAPIError(ConstecError):
22
+ """Raised when API requests fail."""
23
+
24
+ def __init__(self, message: str, status_code: Optional[int] = None, response_data: Optional[dict] = None):
25
+ self.status_code = status_code
26
+ self.response_data = response_data or {}
27
+ details = {"status_code": status_code, "response": response_data}
28
+ super().__init__(message, details)
29
+
30
+
31
+ class ConstecConnectionError(ConstecError):
32
+ """Raised when connection to a service fails."""
33
+ pass
34
+
35
+
36
+ class ConstecValidationError(ConstecError):
37
+ """Raised when data validation fails."""
38
+ pass
39
+
40
+
41
+ class ConstecAuthenticationError(ConstecAPIError):
42
+ """Raised when authentication fails."""
43
+ pass
44
+
45
+
46
+ class ConstecNotFoundError(ConstecAPIError):
47
+ """Raised when a requested resource is not found."""
48
+ pass
@@ -0,0 +1,20 @@
1
+ """
2
+ constec.utils - Shared utilities for the Constec platform.
3
+
4
+ Usage:
5
+ from constec.utils import normalize_cuit, validate_cuit
6
+ from constec.utils import hash_password, verify_password
7
+ """
8
+
9
+ from .cuit import normalize_cuit, validate_cuit, format_cuit
10
+ from .password import hash_password, verify_password
11
+
12
+ __all__ = [
13
+ # CUIT
14
+ 'normalize_cuit',
15
+ 'validate_cuit',
16
+ 'format_cuit',
17
+ # Password
18
+ 'hash_password',
19
+ 'verify_password',
20
+ ]
constec/utils/cuit.py ADDED
@@ -0,0 +1,107 @@
1
+ """
2
+ CUIT (Clave Única de Identificación Tributaria) utilities.
3
+
4
+ Argentine tax identification number format: XX-XXXXXXXX-X
5
+ - First 2 digits: Type (20=person, 23/24=company, 27=foreign, 30/33/34=company)
6
+ - Middle 8 digits: ID number
7
+ - Last digit: Verification digit
8
+
9
+ Usage:
10
+ from constec.utils import normalize_cuit, validate_cuit, format_cuit
11
+
12
+ # Normalize (remove dashes)
13
+ normalized = normalize_cuit("20-12345678-9") # "20123456789"
14
+
15
+ # Validate
16
+ is_valid = validate_cuit("20-12345678-9") # True/False
17
+
18
+ # Format (add dashes)
19
+ formatted = format_cuit("20123456789") # "20-12345678-9"
20
+ """
21
+
22
+ import re
23
+
24
+
25
+ def normalize_cuit(cuit: str) -> str:
26
+ """
27
+ Normalize CUIT by removing dashes and spaces.
28
+
29
+ Args:
30
+ cuit: CUIT string (with or without dashes)
31
+
32
+ Returns:
33
+ CUIT without dashes (11 digits)
34
+
35
+ Examples:
36
+ >>> normalize_cuit("20-12345678-9")
37
+ '20123456789'
38
+ >>> normalize_cuit("20 12345678 9")
39
+ '20123456789'
40
+ """
41
+ return re.sub(r'[\s\-]', '', cuit)
42
+
43
+
44
+ def format_cuit(cuit: str) -> str:
45
+ """
46
+ Format CUIT with dashes (XX-XXXXXXXX-X).
47
+
48
+ Args:
49
+ cuit: CUIT string (11 digits, with or without dashes)
50
+
51
+ Returns:
52
+ Formatted CUIT with dashes
53
+
54
+ Examples:
55
+ >>> format_cuit("20123456789")
56
+ '20-12345678-9'
57
+ """
58
+ normalized = normalize_cuit(cuit)
59
+ if len(normalized) != 11:
60
+ return cuit # Return as-is if invalid length
61
+ return f"{normalized[:2]}-{normalized[2:10]}-{normalized[10]}"
62
+
63
+
64
+ def _calculate_verification_digit(cuit_base: str) -> int:
65
+ """Calculate the CUIT verification digit using mod 11 algorithm."""
66
+ weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
67
+ total = sum(int(d) * w for d, w in zip(cuit_base, weights))
68
+ remainder = total % 11
69
+
70
+ if remainder == 0:
71
+ return 0
72
+ elif remainder == 1:
73
+ return 9 # Special case for type 23 (women)
74
+ else:
75
+ return 11 - remainder
76
+
77
+
78
+ def validate_cuit(cuit: str) -> bool:
79
+ """
80
+ Validate a CUIT using the verification digit algorithm.
81
+
82
+ Args:
83
+ cuit: CUIT string (with or without dashes)
84
+
85
+ Returns:
86
+ True if valid, False otherwise
87
+
88
+ Examples:
89
+ >>> validate_cuit("20-12345678-9")
90
+ True # (if verification digit is correct)
91
+ """
92
+ normalized = normalize_cuit(cuit)
93
+
94
+ # Must be exactly 11 digits
95
+ if not re.match(r'^\d{11}$', normalized):
96
+ return False
97
+
98
+ # Valid type prefixes
99
+ valid_types = ['20', '23', '24', '27', '30', '33', '34']
100
+ if normalized[:2] not in valid_types:
101
+ return False
102
+
103
+ # Verify check digit
104
+ expected_digit = _calculate_verification_digit(normalized[:10])
105
+ actual_digit = int(normalized[10])
106
+
107
+ return expected_digit == actual_digit
@@ -0,0 +1,62 @@
1
+ """
2
+ Password hashing utilities using bcrypt.
3
+
4
+ Usage:
5
+ from constec.utils import hash_password, verify_password
6
+
7
+ # Hash a password
8
+ hashed = hash_password("my_password")
9
+
10
+ # Verify a password
11
+ is_valid = verify_password("my_password", hashed) # True
12
+ """
13
+
14
+ import bcrypt
15
+
16
+
17
+ def hash_password(password: str, rounds: int = 12) -> str:
18
+ """
19
+ Hash a password using bcrypt.
20
+
21
+ Args:
22
+ password: Plain text password
23
+ rounds: Cost factor (default 12, higher = slower but more secure)
24
+
25
+ Returns:
26
+ Bcrypt hash as string
27
+
28
+ Example:
29
+ >>> hashed = hash_password("secret123")
30
+ >>> hashed.startswith("$2b$")
31
+ True
32
+ """
33
+ salt = bcrypt.gensalt(rounds=rounds)
34
+ hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
35
+ return hashed.decode('utf-8')
36
+
37
+
38
+ def verify_password(password: str, hashed: str) -> bool:
39
+ """
40
+ Verify a password against a bcrypt hash.
41
+
42
+ Args:
43
+ password: Plain text password to verify
44
+ hashed: Bcrypt hash to verify against
45
+
46
+ Returns:
47
+ True if password matches, False otherwise
48
+
49
+ Example:
50
+ >>> hashed = hash_password("secret123")
51
+ >>> verify_password("secret123", hashed)
52
+ True
53
+ >>> verify_password("wrong", hashed)
54
+ False
55
+ """
56
+ try:
57
+ return bcrypt.checkpw(
58
+ password.encode('utf-8'),
59
+ hashed.encode('utf-8')
60
+ )
61
+ except (ValueError, TypeError):
62
+ return False
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: constec
3
+ Version: 0.7.1
4
+ Summary: Base library for the Constec ecosystem - shared utilities, models, and namespace foundation
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/TpmyCT/constec-python
7
+ Project-URL: Repository, https://github.com/TpmyCT/constec-python
8
+ Project-URL: Issues, https://github.com/TpmyCT/constec-python/issues
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Typing :: Typed
18
+ Classifier: Framework :: Django :: 4.2
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: db
23
+ Requires-Dist: Django>=4.2; extra == "db"
24
+ Provides-Extra: services
25
+ Requires-Dist: cryptography>=41.0; extra == "services"
26
+ Requires-Dist: bcrypt>=4.0; extra == "services"
27
+ Provides-Extra: utils
28
+ Requires-Dist: bcrypt>=4.0; extra == "utils"
29
+ Provides-Extra: all
30
+ Requires-Dist: constec[db,services,utils]; extra == "all"
31
+ Dynamic: license-file
32
+
33
+ # Constec
34
+
35
+ Base library for the Constec ecosystem - provides shared utilities for working with Constec services.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install constec
41
+ ```
42
+
43
+ ## What is this?
44
+
45
+ This is a foundational library that provides common utilities used across Constec client libraries. Most users will install this automatically as a dependency when installing specific Constec client packages like:
46
+
47
+ - **constec-erp** - Client for the Constec ERP API
48
+
49
+ If you're looking to interact with Constec services, install the specific client library you need instead of this base package.
50
+
51
+ ## Usage
52
+
53
+ This library provides exception classes for error handling when working with Constec APIs:
54
+
55
+ ```python
56
+ from constec.shared import (
57
+ ConstecError,
58
+ ConstecAPIError,
59
+ ConstecConnectionError,
60
+ ConstecValidationError,
61
+ ConstecAuthenticationError,
62
+ ConstecNotFoundError,
63
+ )
64
+
65
+ # Handle errors from Constec services
66
+ try:
67
+ # Your Constec API calls here
68
+ pass
69
+ except ConstecAuthenticationError:
70
+ print("Authentication failed - check your credentials")
71
+ except ConstecNotFoundError:
72
+ print("Resource not found")
73
+ except ConstecAPIError as e:
74
+ print(f"API error: {e.message}")
75
+ if e.status_code:
76
+ print(f"Status code: {e.status_code}")
77
+ ```
78
+
79
+ ## Available Exceptions
80
+
81
+ - `ConstecError` - Base exception for all Constec errors
82
+ - `ConstecAPIError` - API request failures (includes status code and response data)
83
+ - `ConstecAuthenticationError` - Authentication failures
84
+ - `ConstecNotFoundError` - Resource not found (404)
85
+ - `ConstecConnectionError` - Connection failures
86
+ - `ConstecValidationError` - Data validation errors
87
+
88
+ ## Requirements
89
+
90
+ - Python 3.9 or higher
91
+
92
+ ## License
93
+
94
+ MIT
@@ -0,0 +1,40 @@
1
+ constec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ constec/db/__init__.py,sha256=wou_y3aqCg1xP8Gr13FKRy0kEI2avV-19G3P2g3bzjs,287
3
+ constec/db/apps.py,sha256=zc-9lGNa049q9bvxV7957EwnZjuBozPLZq594cKAU24,221
4
+ constec/db/migrations/0001_initial.py,sha256=ru0auCkdz83Qu3e7hGX24WjZXAlM6fE7RZtgAnzANzQ,28979
5
+ constec/db/migrations/0002_module_level.py,sha256=Es27KUOuI_qrlyL8xBTjwsJJHVduAErNxn4Sdi0a-0w,1526
6
+ constec/db/migrations/0003_remove_module_level.py,sha256=3GX-VDLSZqqxaZf6KFtuc7U9nCkQ9EF8pSDxOnCz63g,275
7
+ constec/db/migrations/0004_rename_entities_company_cuit_idx_entities_company_e2c50f_idx_and_more.py,sha256=TCkutatVjJhVKxiQwp-RLhBlbOo5rtwSV1qQqao1qCI,11874
8
+ constec/db/migrations/0005_event.py,sha256=kyvEFOAbOJj3daDzOWcIhfXD8gl5EpIHYvRy3880cZc,2892
9
+ constec/db/migrations/0006_automation_trigger_action_executionlog_notificationtemplate.py,sha256=sfh1YTRPqUiBb5FIxRNJm_BnlYkDonwm7FEEihgtSe0,13628
10
+ constec/db/migrations/0007_add_organization_to_automations.py,sha256=08yDuetrA4WWwqNriyewr0qCpesLo_9x5YD36CDScj4,3467
11
+ constec/db/migrations/0008_refactor_creator_fields.py,sha256=O_bcChxFR_IPw1BV_1NDbsIdrzhlj4MfpdhPXnhdau4,6381
12
+ constec/db/migrations/0009_rename_user_to_companyuser.py,sha256=2UvFpelG88s1NRKQ-Q3-Uz9Cmp5D_HdLHrLGElwq5_c,1052
13
+ constec/db/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ constec/db/models/__init__.py,sha256=zEUX56xTEGi09ZVcRwDzSnPeKPv1Dl7pIZdAzlLhD-U,2686
15
+ constec/db/models/automation.py,sha256=abTxEg2Ko5H3rybFvGUWeoy0hcEsLm5t8khpUFqLMQM,15027
16
+ constec/db/models/base.py,sha256=Urbz9iyOtSxdTfgtEYbtBLmTiea2QAcLq0L40tAzEq8,736
17
+ constec/db/models/company.py,sha256=APYsbiNoqQ-73wxFP0jq3AByHNHn3Jjbe_J_iwpD9gM,1047
18
+ constec/db/models/contact.py,sha256=VJ66YgzCqp09dzYM5IK9rZZV1jqBepJQO8YK28gA98k,2106
19
+ constec/db/models/erp.py,sha256=kTh_MYVwuxS32li2YTCTOgygEVc0UE9CTqnKbfkSkvQ,3060
20
+ constec/db/models/erp_entity.py,sha256=1bV5vhPz89aThKf_YP3YW-baQhSsER04byXnZkwMJd0,3715
21
+ constec/db/models/flow.py,sha256=AdWRCvDo2ZRK_uRbSf-VLZQZ1aVJ-u8X1Ohe9cg3S2M,4093
22
+ constec/db/models/group.py,sha256=RllL0pV5jHreeZF17ycFUJdXBxpQG_n_VFCBsQglcz8,911
23
+ constec/db/models/module.py,sha256=z3TYqk0-9VhCYB9eYz-AH1FRSyKnv6aUbnCM6pjGrdA,1919
24
+ constec/db/models/organization.py,sha256=9dbsh5UBShtDRAeyMqqQEPEaflhzS3aiZpRWUiEluBU,1731
25
+ constec/db/models/person.py,sha256=B4BNdy2AgqmxrZrkWWvqnFxFHs4uO1n61Y_5sP6MsUo,848
26
+ constec/db/models/session.py,sha256=CSWiC2Jo2SZnXocy4fQci5qcfLTyaLM2H381iOr6yzg,2477
27
+ constec/db/models/tag.py,sha256=VlddQW8MLDtYmtt6wp7kC6MsT6SQ6odiJX8mUK8FP3o,1815
28
+ constec/db/models/user.py,sha256=UvQ6iLCp2ZBuYddV9YFuJ-v8GTmMh84wO7zN3SSadSA,2069
29
+ constec/services/__init__.py,sha256=LXGKIzaRf1gVG2y7oUc18EulUsJXHVs8-oz0nY2h7WA,312
30
+ constec/services/encryption.py,sha256=rwfAGUC7kDy1bhezHPoZG1hTjajpEB6l3AgcfGtmdvg,2368
31
+ constec/shared/__init__.py,sha256=Qe99OxBfwg3_4i4zyCQR20vCcyyaIULpLzUa0XfwBPg,435
32
+ constec/shared/exceptions.py,sha256=8Bih40RWoH0gVhto09mH2ppSQV_drHPnGWITcoD-0J0,1335
33
+ constec/utils/__init__.py,sha256=brf-G4UvU-3CK_rKNPTaHwsVsxnoJSbml_QTZJSM7d0,458
34
+ constec/utils/cuit.py,sha256=dQKGlA4pRQ5DyR-N4BiV8ZsvAle2Vgjif7PU72zHx_A,2753
35
+ constec/utils/password.py,sha256=XNpTJ9xZQSoZNjXEAnexAEZuYkwW1dFgX4AY-B5Q0gA,1462
36
+ constec-0.7.1.dist-info/licenses/LICENSE,sha256=a1R51ONDGq0UQfV-n3ybsNL7EGhcC2sQ1sXvRANaFVI,1064
37
+ constec-0.7.1.dist-info/METADATA,sha256=hrtu4CNyCT4V4ETsnv7_y19vX1G6UmSMFvDfMZMX35E,2954
38
+ constec-0.7.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
39
+ constec-0.7.1.dist-info/top_level.txt,sha256=bQ9AydOLlthShsr7tA7t7ivbLvlLPdhHOo0BdWgnh_Y,8
40
+ constec-0.7.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Constec
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ constec