django-ledger 0.5.5.4__py3-none-any.whl → 0.5.6.0__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.

Potentially problematic release.


This version of django-ledger might be problematic. Click here for more details.

Files changed (52) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/admin/__init__.py +10 -0
  3. django_ledger/admin/coa.py +135 -0
  4. django_ledger/admin/entity.py +199 -0
  5. django_ledger/admin/ledger.py +283 -0
  6. django_ledger/forms/entity.py +4 -12
  7. django_ledger/forms/ledger.py +19 -0
  8. django_ledger/forms/transactions.py +1 -1
  9. django_ledger/io/__init__.py +4 -1
  10. django_ledger/io/{io_mixin.py → io_core.py} +95 -35
  11. django_ledger/io/io_digest.py +15 -0
  12. django_ledger/io/{data_generator.py → io_generator.py} +51 -8
  13. django_ledger/io/io_library.py +317 -0
  14. django_ledger/io/{io_context.py → io_middleware.py} +16 -9
  15. django_ledger/migrations/0001_initial.py +413 -132
  16. django_ledger/migrations/0014_ledgermodel_ledger_xid_and_more.py +22 -0
  17. django_ledger/models/accounts.py +68 -7
  18. django_ledger/models/bank_account.py +12 -11
  19. django_ledger/models/bill.py +5 -9
  20. django_ledger/models/closing_entry.py +14 -14
  21. django_ledger/models/coa.py +60 -18
  22. django_ledger/models/customer.py +5 -11
  23. django_ledger/models/data_import.py +12 -6
  24. django_ledger/models/entity.py +90 -12
  25. django_ledger/models/estimate.py +12 -9
  26. django_ledger/models/invoice.py +10 -4
  27. django_ledger/models/items.py +11 -6
  28. django_ledger/models/journal_entry.py +61 -18
  29. django_ledger/models/ledger.py +90 -24
  30. django_ledger/models/mixins.py +2 -3
  31. django_ledger/models/purchase_order.py +11 -7
  32. django_ledger/models/transactions.py +3 -1
  33. django_ledger/models/unit.py +22 -13
  34. django_ledger/models/vendor.py +12 -11
  35. django_ledger/report/cash_flow_statement.py +1 -1
  36. django_ledger/report/core.py +3 -2
  37. django_ledger/templates/django_ledger/journal_entry/includes/card_journal_entry.html +1 -1
  38. django_ledger/templates/django_ledger/journal_entry/je_list.html +3 -0
  39. django_ledger/templatetags/django_ledger.py +1 -1
  40. django_ledger/tests/base.py +1 -1
  41. django_ledger/tests/test_entity.py +1 -1
  42. django_ledger/urls/ledger.py +3 -0
  43. django_ledger/views/entity.py +9 -3
  44. django_ledger/views/ledger.py +14 -7
  45. django_ledger/views/mixins.py +9 -1
  46. {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/METADATA +9 -9
  47. {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/RECORD +51 -46
  48. {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/top_level.txt +0 -1
  49. django_ledger/admin.py +0 -160
  50. {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/AUTHORS.md +0 -0
  51. {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/LICENSE +0 -0
  52. {django_ledger-0.5.5.4.dist-info → django_ledger-0.5.6.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,22 @@
1
+ # Generated by Django 4.2.6 on 2024-01-15 13:11
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('django_ledger', '0013_stagedtransactionmodel_bundle_split'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name='ledgermodel',
15
+ name='ledger_xid',
16
+ field=models.SlugField(allow_unicode=True, blank=True, help_text='User Defined Ledger ID', max_length=150, null=True, verbose_name='Ledger Slug'),
17
+ ),
18
+ migrations.AlterUniqueTogether(
19
+ name='ledgermodel',
20
+ unique_together={('entity', 'ledger_xid')},
21
+ ),
22
+ ]
@@ -34,6 +34,8 @@ from django.core.exceptions import ValidationError
34
34
  from django.db import models
35
35
  from django.db.models import Q
36
36
  from django.db.models.signals import pre_save
37
+ from django.urls import reverse
38
+ from django.utils.timezone import localdate
37
39
  from django.utils.translation import gettext_lazy as _
38
40
  from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet
39
41
 
@@ -42,8 +44,8 @@ from django_ledger.io.roles import (ACCOUNT_ROLE_CHOICES, BS_ROLES, GROUP_INVOIC
42
44
  GROUP_LIABILITIES, GROUP_CAPITAL, GROUP_INCOME, GROUP_EXPENSES, GROUP_COGS,
43
45
  ROOT_GROUP, BS_BUCKETS, ROOT_ASSETS, ROOT_LIABILITIES,
44
46
  ROOT_CAPITAL, ROOT_INCOME, ROOT_EXPENSES, ROOT_COA)
45
- from django_ledger.models import lazy_loader
46
47
  from django_ledger.models.mixins import CreateUpdateMixIn
48
+ from django_ledger.models.utils import lazy_loader
47
49
  from django_ledger.settings import DJANGO_LEDGER_ACCOUNT_CODE_GENERATE, DJANGO_LEDGER_ACCOUNT_CODE_USE_PREFIX
48
50
 
49
51
  DEBIT = 'debit'
@@ -158,6 +160,11 @@ class AccountModelManager(MP_NodeManager):
158
160
  """
159
161
  return AccountModelQuerySet(self.model).order_by('path')
160
162
 
163
+ def for_user(self, user_model):
164
+ qs = self.get_queryset()
165
+ if user_model.is_superuser:
166
+ return qs
167
+
161
168
  # todo: search for uses and pass EntityModel whenever possible.
162
169
  def for_entity(self,
163
170
  user_model,
@@ -186,7 +193,7 @@ class AccountModelManager(MP_NodeManager):
186
193
  AccountModelQuerySet
187
194
  A QuerySet of all requested EntityModel Chart of Accounts.
188
195
  """
189
- qs = self.get_queryset()
196
+ qs = self.for_user(user_model)
190
197
  if select_coa_model:
191
198
  qs = qs.select_related('coa_model')
192
199
 
@@ -201,11 +208,7 @@ class AccountModelManager(MP_NodeManager):
201
208
 
202
209
  if coa_slug:
203
210
  qs = qs.filter(coa_model__slug__exact=coa_slug)
204
-
205
- return qs.filter(
206
- Q(coa_model__entity__admin=user_model) |
207
- Q(coa_model__entity__managers__in=[user_model])
208
- ).order_by('coa_model')
211
+ return qs.order_by('coa_model')
209
212
 
210
213
  def for_entity_available(self, user_model, entity_slug, coa_slug: Optional[str] = None) -> AccountModelQuerySet:
211
214
  """
@@ -486,6 +489,54 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
486
489
  x5=self.code
487
490
  )
488
491
 
492
+ @classmethod
493
+ def create_account(cls,
494
+ name: str,
495
+ role: bool,
496
+ balance_type: str,
497
+ is_role_default: bool = False,
498
+ locked: bool = False,
499
+ active: bool = False,
500
+ **kwargs):
501
+ """
502
+ Convenience Method to Create a new Account Model. This is the preferred method to create new Accounts in order
503
+ to properly handle parent/child relationships between models.
504
+
505
+ Parameters
506
+ ----------
507
+ name: str
508
+ The name of the new Entity.
509
+ role: str
510
+ Account role.
511
+ balance_type: str
512
+ Account Balance Type. Must be 'debit' or 'credit'.
513
+ is_role_default: bool
514
+ If True, assigns account as default for role. Only once default account per role is permitted.
515
+ locked: bool
516
+ Marks account as Locked. Defaults to False.
517
+ active: bool
518
+ Marks account as Active. Defaults to True.
519
+
520
+
521
+ Returns
522
+ -------
523
+ AccountModel
524
+ The newly created AccountModel instance.
525
+
526
+ """
527
+ account_model = cls(
528
+ name=name,
529
+ role=role,
530
+ balance_type=balance_type,
531
+ role_default=is_role_default,
532
+ locked=locked,
533
+ active=active,
534
+ **kwargs
535
+ )
536
+ account_model.clean()
537
+ account_model = cls.add_root(instance=account_model)
538
+ return account_model
539
+
489
540
  @property
490
541
  def role_bs(self) -> str:
491
542
  """
@@ -649,6 +700,16 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
649
700
  ri = randint(10000, 99999)
650
701
  return f'{prefix}{ri}'
651
702
 
703
+ def get_absolute_url(self):
704
+ return reverse(
705
+ viewname='django_ledger:account-detail-year',
706
+ kwargs={
707
+ 'account_pk': self.uuid,
708
+ 'entity_slug': self.coa_model.entity.slug,
709
+ 'year': localdate().year
710
+ }
711
+ )
712
+
652
713
  def clean(self):
653
714
 
654
715
  if not self.code and DJANGO_LEDGER_ACCOUNT_CODE_GENERATE:
@@ -64,6 +64,15 @@ class BankAccountModelManager(models.Manager):
64
64
  Custom defined Model Manager for the BankAccountModel.
65
65
  """
66
66
 
67
+ def for_user(self, user_model):
68
+ qs = self.get_queryset()
69
+ if user_model.is_superuser:
70
+ return qs
71
+ return qs.filter(
72
+ Q(entity_model__admin=user_model) |
73
+ Q(entity_model__managers__in=[user_model])
74
+ )
75
+
67
76
  def for_entity(self, entity_slug, user_model) -> BankAccountModelQuerySet:
68
77
  """
69
78
  Allows only the authorized user to query the BankAccountModel for a given EntityModel.
@@ -76,21 +85,13 @@ class BankAccountModelManager(models.Manager):
76
85
  user_model
77
86
  Logged in and authenticated django UserModel instance.
78
87
  """
79
- qs = self.get_queryset()
88
+ qs = self.for_user(user_model)
80
89
  if isinstance(entity_slug, lazy_loader.get_entity_model()):
81
90
  return qs.filter(
82
- Q(entity_model=entity_slug) &
83
- (
84
- Q(entity_model__admin=user_model) |
85
- Q(entity_model__managers__in=[user_model])
86
- )
91
+ Q(entity_model=entity_slug)
87
92
  )
88
93
  return qs.filter(
89
- Q(entity_model__slug__exact=entity_slug) &
90
- (
91
- Q(entity_model__admin=user_model) |
92
- Q(entity_model__managers__in=[user_model])
93
- )
94
+ Q(entity_model__slug__exact=entity_slug)
94
95
  )
95
96
 
96
97
 
@@ -205,6 +205,8 @@ class BillModelManager(models.Manager):
205
205
  Returns a BillModelQuerySet with applied filters.
206
206
  """
207
207
  qs = self.get_queryset()
208
+ if user_model.is_superuser:
209
+ return qs
208
210
  return qs.filter(
209
211
  Q(ledger__entity__admin=user_model) |
210
212
  Q(ledger__entity__managers__in=[user_model])
@@ -233,20 +235,14 @@ class BillModelManager(models.Manager):
233
235
  BillModelQuerySet
234
236
  Returns a BillModelQuerySet with applied filters.
235
237
  """
236
- qs = self.get_queryset()
238
+ qs = self.for_user(user_model)
237
239
  if isinstance(entity_slug, EntityModel):
238
240
  return qs.filter(
239
- Q(ledger__entity=entity_slug) & (
240
- Q(ledger__entity__admin=user_model) |
241
- Q(ledger__entity__managers__in=[user_model])
242
- )
241
+ Q(ledger__entity=entity_slug)
243
242
  )
244
243
  elif isinstance(entity_slug, str):
245
244
  return qs.filter(
246
- Q(ledger__entity__slug__exact=entity_slug) & (
247
- Q(ledger__entity__admin=user_model) |
248
- Q(ledger__entity__managers__in=[user_model])
249
- )
245
+ Q(ledger__entity__slug__exact=entity_slug)
250
246
  )
251
247
 
252
248
 
@@ -35,23 +35,23 @@ class ClosingEntryModelQuerySet(models.QuerySet):
35
35
 
36
36
  class ClosingEntryModelManager(models.Manager):
37
37
 
38
+ def for_user(self, user_model):
39
+ qs = self.get_queryset()
40
+ if user_model.is_superuser:
41
+ return qs
42
+ return qs.filter(
43
+ Q(entity_model__admin=user_model) |
44
+ Q(entity_model__managers__in=[user_model])
45
+ )
46
+
38
47
  def for_entity(self, entity_slug, user_model):
48
+ qs = self.for_user(user_model)
39
49
  if isinstance(entity_slug, lazy_loader.get_entity_model()):
40
- return self.get_queryset().filter(
41
- Q(entity_model=entity_slug) &
42
- (
43
- Q(entity_model__admin=user_model) |
44
- Q(entity_model__managers__in=[user_model])
45
- )
46
-
47
- )
48
- return self.get_queryset().filter(
49
- Q(entity_model__slug__exact=entity_slug) &
50
- (
51
- Q(entity_model__admin=user_model) |
52
- Q(entity_model__managers__in=[user_model])
50
+ return qs.filter(
51
+ Q(entity_model=entity_slug)
53
52
  )
54
-
53
+ return qs.filter(
54
+ Q(entity_model__slug__exact=entity_slug)
55
55
  )
56
56
 
57
57
 
@@ -22,6 +22,8 @@ when no explicit CoA is specified, the default behavior is to use the EntityMode
22
22
  Accounts can be used when creating Journal Entries**. No commingling between CoAs is allowed in order to preserve the
23
23
  integrity of the Journal Entry.
24
24
  """
25
+ from random import choices
26
+ from string import ascii_lowercase, digits
25
27
  from typing import Optional, Union
26
28
  from uuid import uuid4
27
29
 
@@ -29,6 +31,7 @@ from django.contrib.auth import get_user_model
29
31
  from django.core.exceptions import ValidationError
30
32
  from django.db import models
31
33
  from django.db.models import Q
34
+ from django.db.models.signals import pre_save, post_save
32
35
  from django.utils.translation import gettext_lazy as _
33
36
 
34
37
  from django_ledger.io import (ROOT_COA, ROOT_GROUP_LEVEL_2, ROOT_GROUP_META, ROOT_ASSETS,
@@ -41,6 +44,8 @@ from django_ledger.settings import logger
41
44
 
42
45
  UserModel = get_user_model()
43
46
 
47
+ SLUG_SUFFIX = ascii_lowercase + digits
48
+
44
49
 
45
50
  class ChartOfAccountsModelValidationError(ValidationError):
46
51
  pass
@@ -84,7 +89,7 @@ class ChartOfAccountModelManager(models.Manager):
84
89
  Q(entity__admin=user_model) |
85
90
  Q(entity__managers__in=[user_model])
86
91
  )
87
- )
92
+ ).select_related('entity')
88
93
 
89
94
  def for_entity(self, entity_slug, user_model) -> ChartOfAccountModelQuerySet:
90
95
  """
@@ -127,7 +132,7 @@ class ChartOfAccountModelManager(models.Manager):
127
132
  Q(entity__admin=user_model) |
128
133
  Q(entity__managers__in=[user_model])
129
134
  )
130
- )
135
+ ).select_related('entity')
131
136
 
132
137
 
133
138
  class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
@@ -173,10 +178,9 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
173
178
  ]
174
179
 
175
180
  def __str__(self):
176
- return f'{self.slug}: {self.name}'
177
-
178
- # def is_configured(self, account_model_qs: Optional[AccountModelQuerySet]):
179
- # pass
181
+ if self.name is not None:
182
+ return f'{self.name} ({self.slug})'
183
+ return self.slug
180
184
 
181
185
  def get_coa_root_accounts_qs(self) -> AccountModelQuerySet:
182
186
  return self.accountmodel_set.all().is_coa_root()
@@ -222,28 +226,43 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
222
226
  root_account = self.get_coa_root_account()
223
227
  return AccountModel.dump_bulk(parent=root_account)
224
228
 
229
+ def generate_slug(self, raise_exception: bool = False) -> str:
230
+ if self.slug:
231
+ if raise_exception:
232
+ raise ChartOfAccountsModelValidationError(
233
+ message=_(f'CoA {self.uuid} already has a slug')
234
+ )
235
+ return
236
+ self.slug = f'coa-{self.entity.slug[-5:]}-' + ''.join(choices(SLUG_SUFFIX, k=15))
237
+
225
238
  def configure(self, raise_exception: bool = True):
239
+
240
+ self.generate_slug()
241
+
226
242
  root_accounts_qs = self.get_coa_root_accounts_qs()
227
243
  existing_root_roles = list(set(acc.role for acc in root_accounts_qs))
228
244
 
229
245
  if len(existing_root_roles) > 0:
230
- raise ChartOfAccountsModelValidationError(message=f'Root Nodes already Exist in CoA {self.uuid}...')
246
+ if raise_exception:
247
+ raise ChartOfAccountsModelValidationError(message=f'Root Nodes already Exist in CoA {self.uuid}...')
248
+ return
231
249
 
232
250
  if ROOT_COA not in existing_root_roles:
233
251
  # add coa root...
234
252
  role_meta = ROOT_GROUP_META[ROOT_COA]
235
253
  account_pk = uuid4()
236
- coa_root_account_model = AccountModel.add_root(
237
- instance=AccountModel(
238
- uuid=account_pk,
239
- code=role_meta['code'],
240
- name=role_meta['title'],
241
- coa_model=self,
242
- role=ROOT_COA,
243
- active=False,
244
- locked=True,
245
- balance_type=role_meta['balance_type']
246
- ))
254
+ root_account = AccountModel(
255
+ uuid=account_pk,
256
+ code=role_meta['code'],
257
+ name=role_meta['title'],
258
+ coa_model=self,
259
+ role=ROOT_COA,
260
+ role_default=True,
261
+ active=False,
262
+ locked=True,
263
+ balance_type=role_meta['balance_type']
264
+ )
265
+ coa_root_account_model = AccountModel.add_root(instance=root_account)
247
266
 
248
267
  coa_root_account_model = AccountModel.objects.get(uuid__exact=account_pk)
249
268
 
@@ -258,11 +277,17 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
258
277
  name=role_meta['title'],
259
278
  coa_model=self,
260
279
  role=root_role,
280
+ role_default=True,
261
281
  active=False,
262
282
  locked=True,
263
283
  balance_type=role_meta['balance_type']
264
284
  ))
265
285
 
286
+ def is_default(self) -> bool:
287
+ if self.entity_id is None:
288
+ return False
289
+ return self.entity.default_coa_id == self.uuid
290
+
266
291
  def validate_account_model_qs(self, account_model_qs: AccountModelQuerySet):
267
292
  if not isinstance(account_model_qs, AccountModelQuerySet):
268
293
  raise ChartOfAccountsModelValidationError(
@@ -301,8 +326,25 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
301
326
  non_root_accounts_qs.update(locked=False)
302
327
  return non_root_accounts_qs
303
328
 
329
+ def clean(self):
330
+ self.generate_slug()
331
+
304
332
 
305
333
  class ChartOfAccountModel(ChartOfAccountModelAbstract):
306
334
  """
307
335
  Base ChartOfAccounts Model
308
336
  """
337
+
338
+
339
+ # def chartofaccountmodel_presave(instance: ChartOfAccountModel, **kwargs):
340
+ # if instance._state.adding:
341
+ # instance.configure(raise_exception=True)
342
+
343
+
344
+ def chartofaccountmodel_postsave(instance: ChartOfAccountModel, **kwargs):
345
+ if instance._state.adding:
346
+ instance.configure(raise_exception=True)
347
+
348
+
349
+ # pre_save.connect(receiver=chartofaccountmodel_presave, sender=ChartOfAccountModel)
350
+ post_save.connect(receiver=chartofaccountmodel_postsave, sender=ChartOfAccountModel)
@@ -107,6 +107,8 @@ class CustomerModelManager(models.Manager):
107
107
  >>> customer_model_qs = CustomerModel.objects.for_user(user_model=request_user)
108
108
  """
109
109
  qs = self.get_queryset()
110
+ if user_model.is_superuser:
111
+ return qs
110
112
  return qs.filter(
111
113
  Q(entity__admin=user_model) |
112
114
  Q(entity__managers__in=[user_model])
@@ -135,22 +137,14 @@ class CustomerModelManager(models.Manager):
135
137
  CustomerModelQueryset
136
138
  A filtered CustomerModel QuerySet.
137
139
  """
138
- qs = self.get_queryset()
140
+ qs = self.for_user(user_model)
139
141
 
140
142
  if isinstance(entity_slug, lazy_loader.get_entity_model()):
141
143
  return qs.filter(
142
- Q(entity_model=entity_slug) &
143
- (
144
- Q(entity_model__admin=user_model) |
145
- Q(entity_model__managers__in=[user_model])
146
- )
144
+ Q(entity_model=entity_slug)
147
145
  )
148
146
  return qs.filter(
149
- Q(entity_model__slug__exact=entity_slug) &
150
- (
151
- Q(entity_model__admin=user_model) |
152
- Q(entity_model__managers__in=[user_model])
153
- )
147
+ Q(entity_model__slug__exact=entity_slug)
154
148
  )
155
149
 
156
150
 
@@ -42,14 +42,20 @@ class ImportJobModelManager(models.Manager):
42
42
  'ledger_model'
43
43
  )
44
44
 
45
- def for_entity(self, entity_slug: str, user_model):
45
+ def for_user(self, user_model):
46
46
  qs = self.get_queryset()
47
+ if user_model.is_superuser:
48
+ return qs
47
49
  return qs.filter(
48
- Q(bank_account_model__entity_model__slug__exact=entity_slug) &
49
- (
50
- Q(bank_account_model__entity_model__admin=user_model) |
51
- Q(bank_account_model__entity_model__managers__in=[user_model])
52
- )
50
+ Q(bank_account_model__entity_model__admin=user_model) |
51
+ Q(bank_account_model__entity_model__managers__in=[user_model])
52
+
53
+ )
54
+
55
+ def for_entity(self, entity_slug: str, user_model):
56
+ qs = self.for_user(user_model)
57
+ return qs.filter(
58
+ Q(bank_account_model__entity_model__slug__exact=entity_slug)
53
59
  )
54
60
 
55
61
 
@@ -27,7 +27,7 @@ from decimal import Decimal
27
27
  from itertools import zip_longest
28
28
  from random import choices
29
29
  from string import ascii_lowercase, digits
30
- from typing import Tuple, Union, Optional, List, Dict
30
+ from typing import Tuple, Union, Optional, List, Dict, Set
31
31
  from uuid import uuid4, UUID
32
32
 
33
33
  from django.contrib.auth import get_user_model
@@ -36,7 +36,7 @@ from django.core.cache import caches
36
36
  from django.core.exceptions import ValidationError, ObjectDoesNotExist
37
37
  from django.core.validators import MinValueValidator
38
38
  from django.db import models
39
- from django.db.models import Q, QuerySet
39
+ from django.db.models import Q
40
40
  from django.db.models.signals import pre_save
41
41
  from django.urls import reverse
42
42
  from django.utils.text import slugify
@@ -45,7 +45,7 @@ from django.utils.translation import gettext_lazy as _
45
45
  from treebeard.mp_tree import MP_Node, MP_NodeManager, MP_NodeQuerySet
46
46
 
47
47
  from django_ledger.io import roles as roles_module, validate_roles, IODigestContextManager
48
- from django_ledger.io.io_mixin import IOMixIn
48
+ from django_ledger.io.io_core import IOMixIn
49
49
  from django_ledger.models.accounts import AccountModel, AccountModelQuerySet, DEBIT, CREDIT
50
50
  from django_ledger.models.bank_account import BankAccountModelQuerySet, BankAccountModel
51
51
  from django_ledger.models.coa import ChartOfAccountModel, ChartOfAccountModelQuerySet
@@ -114,7 +114,7 @@ class EntityModelManager(MP_NodeManager):
114
114
 
115
115
  def get_queryset(self):
116
116
  """Sets the custom queryset as the default."""
117
- qs = super().get_queryset()
117
+ qs = EntityModelQuerySet(self.model).order_by('path')
118
118
  return qs.order_by('path').select_related('admin', 'default_coa')
119
119
 
120
120
  def for_user(self, user_model):
@@ -135,6 +135,8 @@ class EntityModelManager(MP_NodeManager):
135
135
  2. Is a manager.
136
136
  """
137
137
  qs = self.get_queryset()
138
+ if user_model.is_superuser:
139
+ return qs
138
140
  return qs.filter(
139
141
  Q(admin=user_model) |
140
142
  Q(managers__in=[user_model])
@@ -890,8 +892,15 @@ class EntityModelAbstract(MP_Node,
890
892
  return user_model.id == self.admin_id
891
893
 
892
894
  # #### LEDGER MANAGEMENT....
893
- def create_ledger(self, name):
894
- return self.ledgermodel_set.create(name=name)
895
+ def create_ledger(self, name: str, ledger_xid: Optional[str] = None, posted: bool = False, commit: bool = True):
896
+ if commit:
897
+ return self.ledgermodel_set.create(name=name, ledger_xid=ledger_xid, posted=posted)
898
+ return LedgerModel(
899
+ entity=self,
900
+ posted=posted,
901
+ name=name,
902
+ ledger_xid=ledger_xid
903
+ )
895
904
 
896
905
  # #### SLUG GENERATION ###
897
906
  @staticmethod
@@ -1007,10 +1016,10 @@ class EntityModelAbstract(MP_Node,
1007
1016
  coa_name = 'Default CoA'
1008
1017
 
1009
1018
  chart_of_accounts = ChartOfAccountModel(
1010
- slug=self.slug + ''.join(choices(ENTITY_RANDOM_SLUG_SUFFIX, k=6)) + '-coa',
1011
1019
  name=coa_name,
1012
1020
  entity=self
1013
1021
  )
1022
+
1014
1023
  chart_of_accounts.clean()
1015
1024
  chart_of_accounts.save()
1016
1025
  chart_of_accounts.configure()
@@ -1293,7 +1302,7 @@ class EntityModelAbstract(MP_Node,
1293
1302
  return self.get_coa_accounts(active=active, order_by=order_by)
1294
1303
 
1295
1304
  def get_accounts_with_codes(self,
1296
- code_list: Union[str, List[str]],
1305
+ code_list: Union[str, List[str], Set[str]],
1297
1306
  coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None
1298
1307
  ) -> AccountModelQuerySet:
1299
1308
  """
@@ -1351,12 +1360,72 @@ class EntityModelAbstract(MP_Node,
1351
1360
  return account_model_qs.get(role__exact=role)
1352
1361
 
1353
1362
  def create_account(self,
1354
- account_model_kwargs: Dict,
1363
+ code: str,
1364
+ role: str,
1365
+ name: str,
1366
+ balance_type: str,
1367
+ active: bool,
1355
1368
  coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1356
- raise_exception: bool = True) -> Tuple[ChartOfAccountModel, AccountModel]:
1369
+ raise_exception: bool = True) -> AccountModel:
1357
1370
  """
1358
1371
  Creates a new AccountModel for the EntityModel.
1359
1372
 
1373
+ Parameters
1374
+ ----------
1375
+ code: str
1376
+ The AccountModel code of the new Account.
1377
+ role: str
1378
+ The choice of role that the new Account belongs to.
1379
+ name: str
1380
+ The name of the new Account.
1381
+ balance_type: str
1382
+ The balance type of the new account. Possible values are 'debit' or 'credit'.
1383
+ active: bool
1384
+ Active status of the new account.
1385
+ coa_model: ChartOfAccountModel, UUID, str
1386
+ The ChartOfAccountsModel UUID, model instance or slug to pull accounts from. Uses default Coa if not
1387
+ provided.
1388
+ raise_exception: bool
1389
+ Raises EntityModelValidationError if ChartOfAccountsModel is not valid for the EntityModel instance.
1390
+
1391
+ Returns
1392
+ -------
1393
+ A tuple of ChartOfAccountModel, AccountModel
1394
+ The ChartOfAccountModel and AccountModel instance just created.
1395
+ """
1396
+ if coa_model:
1397
+ if isinstance(coa_model, UUID):
1398
+ coa_model = self.chartofaccountsmodel_set.get(uuid__exact=coa_model)
1399
+ elif isinstance(coa_model, str):
1400
+ coa_model = self.chartofaccountsmodel_set.get(slug__exact=coa_model)
1401
+ elif isinstance(coa_model, ChartOfAccountModel):
1402
+ self.validate_chart_of_accounts_for_entity(
1403
+ coa_model=coa_model,
1404
+ raise_exception=raise_exception
1405
+ )
1406
+ else:
1407
+ coa_model = self.default_coa
1408
+
1409
+ account_model = AccountModel(
1410
+ code=code,
1411
+ name=name,
1412
+ role=role,
1413
+ active=active,
1414
+ balance_type=balance_type
1415
+ )
1416
+
1417
+ account_model.clean()
1418
+ return coa_model.create_account(account_model=account_model)
1419
+
1420
+ def create_account_by_kwargs(self,
1421
+ account_model_kwargs: Dict,
1422
+ coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None,
1423
+ raise_exception: bool = True) -> Tuple[ChartOfAccountModel, AccountModel]:
1424
+ """
1425
+ Creates a new AccountModel for the EntityModel by passing AccountModel KWARGS.
1426
+ This is a legacy method for creating a new AccountModel for the EntityModel.
1427
+ Will be deprecated in favor of create_account().
1428
+
1360
1429
  Parameters
1361
1430
  ----------
1362
1431
  coa_model: ChartOfAccountModel, UUID, str
@@ -1378,7 +1447,10 @@ class EntityModelAbstract(MP_Node,
1378
1447
  elif isinstance(coa_model, str):
1379
1448
  coa_model = self.chartofaccountsmodel_set.get(slug__exact=coa_model)
1380
1449
  elif isinstance(coa_model, ChartOfAccountModel):
1381
- self.validate_chart_of_accounts_for_entity(coa_model=coa_model, raise_exception=raise_exception)
1450
+ self.validate_chart_of_accounts_for_entity(
1451
+ coa_model=coa_model,
1452
+ raise_exception=raise_exception
1453
+ )
1382
1454
  else:
1383
1455
  coa_model = self.default_coa
1384
1456
 
@@ -2692,6 +2764,12 @@ class EntityModelAbstract(MP_Node,
2692
2764
  data_generator.populate_entity()
2693
2765
 
2694
2766
  # URLS ----
2767
+ def get_absolute_url(self):
2768
+ return reverse(viewname='django_ledger:entity-dashboard',
2769
+ kwargs={
2770
+ 'entity_slug': self.slug
2771
+ })
2772
+
2695
2773
  def get_dashboard_url(self) -> str:
2696
2774
  """
2697
2775
  The EntityModel Dashboard URL.
@@ -2954,7 +3032,7 @@ class EntityStateModelAbstract(models.Model):
2954
3032
  ]
2955
3033
 
2956
3034
  def __str__(self):
2957
- return f'{self.__class__.__name__} {self.entity_id}: FY: {self.fiscal_year}, KEY: {self.get_key_display()}'
3035
+ return f'{self.__class__.__name__} {self.entity_model_id}: FY: {self.fiscal_year}, KEY: {self.get_key_display()}'
2958
3036
 
2959
3037
 
2960
3038
  class EntityStateModel(EntityStateModelAbstract):