django-ledger 0.7.4__py3-none-any.whl → 0.7.5__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 (31) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/contrib/django_ledger_graphene/bank_account/schema.py +1 -1
  3. django_ledger/forms/bank_account.py +16 -12
  4. django_ledger/forms/data_import.py +70 -33
  5. django_ledger/io/io_core.py +945 -127
  6. django_ledger/io/io_generator.py +7 -3
  7. django_ledger/io/ofx.py +37 -16
  8. django_ledger/migrations/0020_remove_bankaccountmodel_django_ledg_cash_ac_59a8af_idx_and_more.py +44 -0
  9. django_ledger/migrations/0021_alter_bankaccountmodel_account_model_and_more.py +33 -0
  10. django_ledger/models/bank_account.py +14 -11
  11. django_ledger/models/customer.py +3 -13
  12. django_ledger/models/data_import.py +690 -35
  13. django_ledger/models/entity.py +39 -24
  14. django_ledger/models/journal_entry.py +18 -8
  15. django_ledger/models/mixins.py +17 -3
  16. django_ledger/models/vendor.py +2 -2
  17. django_ledger/settings.py +18 -22
  18. django_ledger/templates/django_ledger/bank_account/tags/bank_accounts_table.html +2 -2
  19. django_ledger/templates/django_ledger/data_import/data_import_job_txs.html +1 -1
  20. django_ledger/templates/django_ledger/data_import/import_job_create.html +11 -2
  21. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +1 -1
  22. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +5 -2
  23. django_ledger/templatetags/django_ledger.py +12 -12
  24. django_ledger/views/bank_account.py +1 -1
  25. django_ledger/views/data_import.py +60 -134
  26. {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.dist-info}/METADATA +17 -17
  27. {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.dist-info}/RECORD +31 -29
  28. {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.dist-info}/WHEEL +1 -1
  29. {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.dist-info}/top_level.txt +1 -0
  30. {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.dist-info}/AUTHORS.md +0 -0
  31. {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.dist-info}/LICENSE +0 -0
@@ -282,11 +282,13 @@ class EntityDataGenerator(LoggingMixIn):
282
282
  def create_bank_accounts(self):
283
283
  self.logger.info(f'Creating entity accounts...')
284
284
  bank_account_models = [
285
+
286
+ # creates a bank cash checking account...
285
287
  self.entity_model.create_bank_account(
286
288
  name=f'{self.entity_model.name} Checking Account',
287
289
  account_type=BankAccountModel.ACCOUNT_CHECKING,
288
290
  active=True,
289
- cash_account=choice(self.accounts_by_role[ASSET_CA_CASH]),
291
+ account_model=choice(self.accounts_by_role[ASSET_CA_CASH]),
290
292
  bank_account_model_kwargs={
291
293
  'aba_number': self.fk.swift(),
292
294
  'routing_number': str(randint(0, 9999999)).zfill(9),
@@ -294,11 +296,13 @@ class EntityDataGenerator(LoggingMixIn):
294
296
  },
295
297
  commit=False
296
298
  ),
299
+
300
+ # creates a bank cash savings account...
297
301
  self.entity_model.create_bank_account(
298
302
  name=f'{self.entity_model.name} Savings Account',
299
303
  account_type=BankAccountModel.ACCOUNT_SAVINGS,
300
304
  active=True,
301
- cash_account=choice(self.accounts_by_role[ASSET_CA_CASH]),
305
+ account_model=choice(self.accounts_by_role[ASSET_CA_CASH]),
302
306
  bank_account_model_kwargs={
303
307
  'aba_number': self.fk.swift(),
304
308
  'routing_number': str(randint(0, 9999999)).zfill(9),
@@ -777,7 +781,7 @@ class EntityDataGenerator(LoggingMixIn):
777
781
 
778
782
  self.logger.info(f'Funding entity...')
779
783
  capital_acc = choice(self.accounts_by_role[EQUITY_CAPITAL])
780
- cash_acc = choice(self.bank_account_models).cash_account
784
+ cash_acc = choice(self.bank_account_models).account_model
781
785
 
782
786
  self.entity_model.deposit_capital(
783
787
  cash_account=cash_acc,
django_ledger/io/ofx.py CHANGED
@@ -6,13 +6,18 @@ Contributions to this module:
6
6
  Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
8
 
9
- from typing import List
9
+ from typing import List, Optional, Dict
10
10
 
11
+ from django.core.exceptions import ValidationError
11
12
  from ofxtools import OFXTree
12
13
  from ofxtools.models.bank import STMTRS
13
14
  from ofxtools.models.ofx import OFX
14
15
 
15
16
 
17
+ class OFXImportValidationError(ValidationError):
18
+ pass
19
+
20
+
16
21
  class OFXFileManager:
17
22
 
18
23
  def __init__(self, ofx_file_or_path, parse_on_load: bool = True):
@@ -20,31 +25,47 @@ class OFXFileManager:
20
25
  self.ofx_tree: OFXTree = OFXTree()
21
26
  self.ofx_data: OFX or None = None
22
27
  self.statements: List[STMTRS] or None = None
23
- self.NUMBER_OF_STATEMENTS: int or None = None
28
+ self.NUMBER_OF_STATEMENTS: int = 0
24
29
 
25
30
  if parse_on_load:
26
31
  self.parse_ofx()
27
32
 
33
+ if self.NUMBER_OF_STATEMENTS != 1:
34
+ raise OFXImportValidationError('Only one account per OFX file is supported.')
35
+
36
+ self.BANK_NAME = self.ofx_data.fi.org if hasattr(self.ofx_data.fi, 'org') else None
37
+ self.FID = self.ofx_data.fi.fid if hasattr(self.ofx_data.fi, 'fid') else None
38
+ self.ACCOUNT_DATA: Optional[Dict] = None
39
+
40
+ self.get_account_data()
41
+ # self.ACCOUNT_TXS = self.get_account_txs(account=self.ACCOUNTS[0]['account'].acctid)
42
+
28
43
  def parse_ofx(self):
29
44
  self.ofx_tree.parse(self.FILE)
30
45
  self.ofx_data = self.ofx_tree.convert()
31
46
  self.statements = self.ofx_data.statements
32
47
  self.NUMBER_OF_STATEMENTS = len(self.statements)
33
48
 
34
- def get_accounts(self):
35
- return [
36
- {
37
- # conditionally return the bank and fid if they are provided by the vendor
38
- 'bank': self.ofx_data.fi.org if hasattr(self.ofx_data.fi, 'org') else None,
39
- 'fid': self.ofx_data.fi.fid if hasattr(self.ofx_data.fi, 'fid') else None,
40
- 'account_type': acc.accttype,
41
- 'account_number': acc.acctid,
42
- 'routing_number': acc.bankid,
43
- } for acc in self.statements
44
- ]
45
-
46
- def get_account_txs(self, account: str):
49
+ def statement_attrs(self):
50
+ return [a for a in dir(self.statements[0]) if a[0] != '_']
51
+
52
+ def get_account_data(self):
53
+ if self.ACCOUNT_DATA is None:
54
+ self.ACCOUNT_DATA = [
55
+ dict(
56
+ (attr, getattr(account, attr)) for attr in self.statement_attrs()
57
+ ) | {
58
+ 'bank': self.BANK_NAME,
59
+ 'fid': self.FID
60
+ } for account in self.statements
61
+ ][0]
62
+ return self.ACCOUNT_DATA
63
+
64
+ def get_account_number(self):
65
+ return self.get_account_data()['account'].acctid
66
+
67
+ def get_account_txs(self):
47
68
  acc_statement = next(iter(
48
- st for st in self.ofx_data.statements if st.account.acctid == account
69
+ st for st in self.ofx_data.statements if st.account.acctid == self.get_account_number()
49
70
  ))
50
71
  return acc_statement.banktranlist
@@ -0,0 +1,44 @@
1
+ # Generated by Django 5.1.6 on 2025-02-13 01:40
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ('django_ledger', '0019_alter_transactionmodel_amount_and_more'),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.RemoveIndex(
13
+ model_name='bankaccountmodel',
14
+ name='django_ledg_cash_ac_59a8af_idx',
15
+ ),
16
+ migrations.RenameField(
17
+ model_name='bankaccountmodel',
18
+ old_name='cash_account',
19
+ new_name='account_model',
20
+ ),
21
+ migrations.AlterUniqueTogether(
22
+ name='bankaccountmodel',
23
+ unique_together={('entity_model', 'account_model', 'account_number', 'routing_number'),
24
+ ('entity_model', 'account_number')},
25
+ ),
26
+ migrations.AlterField(
27
+ model_name='bankaccountmodel',
28
+ name='account_type',
29
+ field=models.CharField(
30
+ choices=[('checking', 'Checking'), ('savings', 'Savings'), ('credit_card', 'Credit Card'),
31
+ ('mortgage', 'Mortgage')], default='checking', max_length=20, verbose_name='Account Type'),
32
+ ),
33
+ migrations.AlterField(
34
+ model_name='vendormodel',
35
+ name='account_type',
36
+ field=models.CharField(
37
+ choices=[('checking', 'Checking'), ('savings', 'Savings'), ('credit_card', 'Credit Card'),
38
+ ('mortgage', 'Mortgage')], default='checking', max_length=20, verbose_name='Account Type'),
39
+ ),
40
+ migrations.AddIndex(
41
+ model_name='bankaccountmodel',
42
+ index=models.Index(fields=['account_model'], name='django_ledg_account_ed900d_idx'),
43
+ ),
44
+ ]
@@ -0,0 +1,33 @@
1
+ # Generated by Django 5.1.6 on 2025-02-18 22:47
2
+
3
+ import django.db.models.deletion
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ dependencies = [
10
+ ('django_ledger', '0020_remove_bankaccountmodel_django_ledg_cash_ac_59a8af_idx_and_more'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AlterField(
15
+ model_name='bankaccountmodel',
16
+ name='account_model',
17
+ field=models.ForeignKey(
18
+ help_text='Account model be used to map transactions from financial institution',
19
+ on_delete=django.db.models.deletion.RESTRICT,
20
+ to=settings.DJANGO_LEDGER_ACCOUNT_MODEL,
21
+ verbose_name='Associated Account Model'
22
+ ),
23
+ ),
24
+ migrations.AlterField(
25
+ model_name='importjobmodel',
26
+ name='bank_account_model',
27
+ field=models.ForeignKey(
28
+ on_delete=django.db.models.deletion.CASCADE,
29
+ to=settings.DJANGO_LEDGER_BANK_ACCOUNT_MODEL,
30
+ verbose_name='Associated Bank Account Model'
31
+ ),
32
+ ),
33
+ ]
@@ -16,7 +16,7 @@ from django.db.models import Q, QuerySet
16
16
  from django.shortcuts import get_object_or_404
17
17
  from django.utils.translation import gettext_lazy as _
18
18
 
19
- from django_ledger.models import CreateUpdateMixIn, BankAccountInfoMixIn
19
+ from django_ledger.models import CreateUpdateMixIn, FinancialAccountInfoMixin
20
20
  from django_ledger.models.utils import lazy_loader
21
21
 
22
22
  UserModel = get_user_model()
@@ -60,6 +60,9 @@ class BankAccountModelManager(models.Manager):
60
60
  Custom defined Model Manager for the BankAccountModel.
61
61
  """
62
62
 
63
+ def get_queryset(self) -> BankAccountModelQuerySet:
64
+ return BankAccountModelQuerySet(self.model, using=self._db)
65
+
63
66
  def for_user(self, user_model):
64
67
  qs = self.get_queryset()
65
68
  if user_model.is_superuser:
@@ -91,7 +94,7 @@ class BankAccountModelManager(models.Manager):
91
94
  )
92
95
 
93
96
 
94
- class BankAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
97
+ class BankAccountModelAbstract(FinancialAccountInfoMixin, CreateUpdateMixIn):
95
98
  """
96
99
  This is the main abstract class which the BankAccountModel database will inherit from.
97
100
  The BankAccountModel inherits functionality from the following MixIns:
@@ -108,14 +111,13 @@ class BankAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
108
111
  A user defined name for the bank account as a String.
109
112
  entity_model: EntityModel
110
113
  The EntityModel associated with the BankAccountModel instance.
111
- cash_account: AccountModel
114
+ account_model: AccountModel
112
115
  The AccountModel associated with the BankAccountModel instance. Must be an account with role ASSET_CA_CASH.
113
116
  active: bool
114
117
  Determines whether the BackAccountModel instance bank account is active. Defaults to True.
115
118
  hidden: bool
116
119
  Determines whether the BackAccountModel instance bank account is hidden. Defaults to False.
117
120
  """
118
- REL_NAME_PREFIX = 'bank'
119
121
 
120
122
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
121
123
 
@@ -124,13 +126,14 @@ class BankAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
124
126
  entity_model = models.ForeignKey('django_ledger.EntityModel',
125
127
  on_delete=models.CASCADE,
126
128
  verbose_name=_('Entity Model'))
127
- cash_account = models.ForeignKey('django_ledger.AccountModel',
128
- on_delete=models.RESTRICT,
129
- verbose_name=_('Cash Account'),
130
- related_name=f'{REL_NAME_PREFIX}_cash_account')
129
+ account_model = models.ForeignKey('django_ledger.AccountModel',
130
+ on_delete=models.RESTRICT,
131
+ help_text=_(
132
+ 'Account model be used to map transactions from financial institution'),
133
+ verbose_name=_('Associated Account Model'))
131
134
  active = models.BooleanField(default=False)
132
135
  hidden = models.BooleanField(default=False)
133
- objects = BankAccountModelManager.from_queryset(queryset_class=BankAccountModelQuerySet)()
136
+ objects = BankAccountModelManager()
134
137
 
135
138
  def configure(self,
136
139
  entity_slug,
@@ -165,11 +168,11 @@ class BankAccountModelAbstract(BankAccountInfoMixIn, CreateUpdateMixIn):
165
168
  verbose_name = _('Bank Account')
166
169
  indexes = [
167
170
  models.Index(fields=['account_type']),
168
- models.Index(fields=['cash_account'])
171
+ models.Index(fields=['account_model'])
169
172
  ]
170
173
  unique_together = [
171
174
  ('entity_model', 'account_number'),
172
- ('entity_model', 'cash_account', 'account_number', 'routing_number')
175
+ ('entity_model', 'account_model', 'account_number', 'routing_number')
173
176
  ]
174
177
 
175
178
  def __str__(self):
@@ -96,18 +96,13 @@ class CustomerModelManager(Manager):
96
96
  __________
97
97
  user_model
98
98
  Logged in and authenticated django UserModel instance.
99
-
100
- Examples
101
- ________
102
- >>> request_user = request.user
103
- >>> customer_model_qs = CustomerModel.objects.for_user(user_model=request_user)
104
99
  """
105
100
  qs = self.get_queryset()
106
101
  if user_model.is_superuser:
107
102
  return qs
108
103
  return qs.filter(
109
- Q(entity__admin=user_model) |
110
- Q(entity__managers__in=[user_model])
104
+ Q(entity_model__admin=user_model) |
105
+ Q(entity_model__managers__in=[user_model])
111
106
  )
112
107
 
113
108
  def for_entity(self, entity_slug, user_model) -> CustomerModelQueryset:
@@ -122,11 +117,6 @@ class CustomerModelManager(Manager):
122
117
  user_model
123
118
  Logged in and authenticated django UserModel instance.
124
119
 
125
- Examples
126
- ________
127
- >>> request_user = request.user
128
- >>> slug = kwargs['entity_slug'] # may come from request kwargs
129
- >>> customer_model_qs = CustomerModel.objects.for_entity(user_model=request_user, entity_slug=slug)
130
120
 
131
121
  Returns
132
122
  -------
@@ -157,7 +147,7 @@ class CustomerModelAbstract(ContactInfoMixIn, TaxCollectionMixIn, CreateUpdateMi
157
147
  uuid : UUID
158
148
  This is a unique primary key generated for the table. The default value of this field is uuid4().
159
149
 
160
- entity: EntityModel
150
+ entity_model: EntityModel
161
151
  The EntityModel associated with this Customer.
162
152
 
163
153
  customer_name: str