django-ledger 0.7.4__py3-none-any.whl → 0.7.5.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.
Potentially problematic release.
This version of django-ledger might be problematic. Click here for more details.
- django_ledger/__init__.py +1 -1
- django_ledger/contrib/django_ledger_graphene/bank_account/schema.py +1 -1
- django_ledger/forms/bank_account.py +16 -12
- django_ledger/forms/data_import.py +70 -33
- django_ledger/io/io_core.py +945 -127
- django_ledger/io/io_generator.py +7 -3
- django_ledger/io/ofx.py +37 -16
- django_ledger/migrations/0020_remove_bankaccountmodel_django_ledg_cash_ac_59a8af_idx_and_more.py +44 -0
- django_ledger/migrations/0021_alter_bankaccountmodel_account_model_and_more.py +33 -0
- django_ledger/models/bank_account.py +14 -11
- django_ledger/models/customer.py +3 -13
- django_ledger/models/data_import.py +690 -35
- django_ledger/models/entity.py +39 -24
- django_ledger/models/journal_entry.py +18 -8
- django_ledger/models/mixins.py +17 -3
- django_ledger/models/vendor.py +2 -2
- django_ledger/settings.py +18 -22
- django_ledger/templates/django_ledger/bank_account/tags/bank_accounts_table.html +2 -2
- django_ledger/templates/django_ledger/data_import/data_import_job_txs.html +1 -1
- django_ledger/templates/django_ledger/data_import/import_job_create.html +11 -2
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +1 -1
- django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_table.html +5 -2
- django_ledger/templatetags/django_ledger.py +12 -12
- django_ledger/views/bank_account.py +1 -1
- django_ledger/views/data_import.py +60 -134
- {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.1.dist-info}/METADATA +44 -17
- {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.1.dist-info}/RECORD +31 -29
- {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.1.dist-info}/WHEEL +1 -1
- {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.1.dist-info}/top_level.txt +1 -0
- {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.1.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.7.4.dist-info → django_ledger-0.7.5.1.dist-info}/LICENSE +0 -0
django_ledger/io/io_generator.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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).
|
|
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
|
|
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
|
|
35
|
-
return [
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 ==
|
|
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
|
django_ledger/migrations/0020_remove_bankaccountmodel_django_ledg_cash_ac_59a8af_idx_and_more.py
ADDED
|
@@ -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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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=['
|
|
171
|
+
models.Index(fields=['account_model'])
|
|
169
172
|
]
|
|
170
173
|
unique_together = [
|
|
171
174
|
('entity_model', 'account_number'),
|
|
172
|
-
('entity_model', '
|
|
175
|
+
('entity_model', 'account_model', 'account_number', 'routing_number')
|
|
173
176
|
]
|
|
174
177
|
|
|
175
178
|
def __str__(self):
|
django_ledger/models/customer.py
CHANGED
|
@@ -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(
|
|
110
|
-
Q(
|
|
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
|
-
|
|
150
|
+
entity_model: EntityModel
|
|
161
151
|
The EntityModel associated with this Customer.
|
|
162
152
|
|
|
163
153
|
customer_name: str
|