django-ledger 0.6.0__py3-none-any.whl → 0.6.0.2__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/admin/ledger.py +3 -1
- django_ledger/io/io_core.py +18 -5
- django_ledger/io/io_library.py +79 -30
- django_ledger/io/roles.py +80 -86
- django_ledger/migrations/0016_remove_accountmodel_django_ledg_coa_mod_e19964_idx_and_more.py +44 -0
- django_ledger/models/accounts.py +73 -43
- django_ledger/models/bill.py +2 -0
- django_ledger/models/coa.py +2 -0
- django_ledger/models/entity.py +1 -1
- django_ledger/models/invoice.py +2 -1
- django_ledger/models/items.py +4 -0
- django_ledger/models/ledger.py +11 -4
- django_ledger/models/transactions.py +3 -3
- django_ledger/settings.py +1 -0
- django_ledger/urls/entity.py +1 -1
- django_ledger/urls/unit.py +1 -1
- django_ledger/views/entity.py +18 -12
- django_ledger/views/ledger.py +0 -1
- django_ledger/views/mixins.py +23 -8
- {django_ledger-0.6.0.dist-info → django_ledger-0.6.0.2.dist-info}/METADATA +20 -18
- {django_ledger-0.6.0.dist-info → django_ledger-0.6.0.2.dist-info}/RECORD +26 -25
- {django_ledger-0.6.0.dist-info → django_ledger-0.6.0.2.dist-info}/top_level.txt +1 -0
- {django_ledger-0.6.0.dist-info → django_ledger-0.6.0.2.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.6.0.dist-info → django_ledger-0.6.0.2.dist-info}/LICENSE +0 -0
- {django_ledger-0.6.0.dist-info → django_ledger-0.6.0.2.dist-info}/WHEEL +0 -0
django_ledger/__init__.py
CHANGED
django_ledger/admin/ledger.py
CHANGED
django_ledger/io/io_core.py
CHANGED
|
@@ -259,6 +259,9 @@ class IODatabaseMixIn:
|
|
|
259
259
|
helps minimize the number of transactions to aggregate for a given request.
|
|
260
260
|
"""
|
|
261
261
|
|
|
262
|
+
TRANSACTION_MODEL_CLASS = None
|
|
263
|
+
JOURNAL_ENTRY_MODEL_CLASS = None
|
|
264
|
+
|
|
262
265
|
def is_entity_model(self):
|
|
263
266
|
return isinstance(self, lazy_loader.get_entity_model())
|
|
264
267
|
|
|
@@ -276,6 +279,16 @@ class IODatabaseMixIn:
|
|
|
276
279
|
elif self.is_entity_unit_model():
|
|
277
280
|
return self.entity
|
|
278
281
|
|
|
282
|
+
def get_transaction_model(self):
|
|
283
|
+
if self.TRANSACTION_MODEL_CLASS is not None:
|
|
284
|
+
return self.TRANSACTION_MODEL_CLASS
|
|
285
|
+
return lazy_loader.get_txs_model()
|
|
286
|
+
|
|
287
|
+
def get_journal_entry_model(self):
|
|
288
|
+
if self.JOURNAL_ENTRY_MODEL_CLASS is not None:
|
|
289
|
+
return self.JOURNAL_ENTRY_MODEL_CLASS
|
|
290
|
+
return lazy_loader.get_journal_entry_model()
|
|
291
|
+
|
|
279
292
|
def database_digest(self,
|
|
280
293
|
entity_slug: Optional[str] = None,
|
|
281
294
|
unit_slug: Optional[str] = None,
|
|
@@ -337,7 +350,7 @@ class IODatabaseMixIn:
|
|
|
337
350
|
IOResult
|
|
338
351
|
"""
|
|
339
352
|
|
|
340
|
-
TransactionModel =
|
|
353
|
+
TransactionModel = self.get_transaction_model()
|
|
341
354
|
|
|
342
355
|
# get_initial txs_queryset... where the IO model is operating from??...
|
|
343
356
|
if self.is_entity_model():
|
|
@@ -604,7 +617,7 @@ class IODatabaseMixIn:
|
|
|
604
617
|
use_closing_entries=use_closing_entries,
|
|
605
618
|
**kwargs)
|
|
606
619
|
|
|
607
|
-
TransactionModel =
|
|
620
|
+
TransactionModel = self.get_transaction_model()
|
|
608
621
|
|
|
609
622
|
for tx_model in io_result.txs_queryset:
|
|
610
623
|
if tx_model['account__balance_type'] != tx_model['tx_type']:
|
|
@@ -801,8 +814,8 @@ class IODatabaseMixIn:
|
|
|
801
814
|
force_je_retrieval: bool = False,
|
|
802
815
|
**kwargs):
|
|
803
816
|
|
|
804
|
-
|
|
805
|
-
|
|
817
|
+
TransactionModel = self.get_transaction_model()
|
|
818
|
+
JournalEntryModel = self.get_journal_entry_model()
|
|
806
819
|
|
|
807
820
|
# Validates that credits/debits balance.
|
|
808
821
|
check_tx_balance(je_txs, perform_correction=False)
|
|
@@ -897,7 +910,7 @@ class IODatabaseMixIn:
|
|
|
897
910
|
if staged_tx_model:
|
|
898
911
|
staged_tx_model.transaction_model = tx
|
|
899
912
|
|
|
900
|
-
txs_models =
|
|
913
|
+
txs_models = TransactionModel.objects.bulk_create(i[0] for i in txs_models)
|
|
901
914
|
je_model.save(verify=True, post_on_verify=je_posted)
|
|
902
915
|
return je_model, txs_models
|
|
903
916
|
|
django_ledger/io/io_library.py
CHANGED
|
@@ -7,12 +7,13 @@ Contributions to this module:
|
|
|
7
7
|
|
|
8
8
|
This module contains classes and functions used to document, dispatch and commit new transaction into the database.
|
|
9
9
|
"""
|
|
10
|
+
import enum
|
|
10
11
|
from collections import defaultdict
|
|
11
12
|
from dataclasses import dataclass
|
|
12
13
|
from datetime import date, datetime
|
|
13
14
|
from decimal import Decimal
|
|
14
15
|
from itertools import chain
|
|
15
|
-
from typing import Union, Dict, Callable, Optional, List
|
|
16
|
+
from typing import Union, Dict, Callable, Optional, List, Set
|
|
16
17
|
from uuid import UUID
|
|
17
18
|
|
|
18
19
|
from django.core.exceptions import ValidationError
|
|
@@ -63,6 +64,11 @@ class IOCursorValidationError(ValidationError):
|
|
|
63
64
|
pass
|
|
64
65
|
|
|
65
66
|
|
|
67
|
+
class IOCursorMode(enum.Enum):
|
|
68
|
+
STRICT = 'strict'
|
|
69
|
+
PERMISSIVE = 'permissive'
|
|
70
|
+
|
|
71
|
+
|
|
66
72
|
class IOCursor:
|
|
67
73
|
"""
|
|
68
74
|
Represents a Django Ledger cursor capable of dispatching transactions to the database.
|
|
@@ -86,18 +92,20 @@ class IOCursor:
|
|
|
86
92
|
io_library,
|
|
87
93
|
entity_model: EntityModel,
|
|
88
94
|
user_model,
|
|
95
|
+
mode: IOCursorMode = IOCursorMode.PERMISSIVE,
|
|
89
96
|
coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None):
|
|
90
97
|
self.IO_LIBRARY = io_library
|
|
98
|
+
self.MODE = mode
|
|
91
99
|
self.ENTITY_MODEL = entity_model
|
|
92
100
|
self.USER_MODEL = user_model
|
|
93
101
|
self.COA_MODEL = coa_model
|
|
94
|
-
self.__COMMITTED: bool = False
|
|
95
102
|
self.blueprints = defaultdict(list)
|
|
96
103
|
self.ledger_model_qs: Optional[LedgerModelQuerySet] = None
|
|
97
104
|
self.account_model_qs: Optional[AccountModelQuerySet] = None
|
|
98
105
|
self.ledger_map = dict()
|
|
99
106
|
self.commit_plan = dict()
|
|
100
107
|
self.instructions = None
|
|
108
|
+
self.__COMMITTED: bool = False
|
|
101
109
|
|
|
102
110
|
def get_ledger_model_qs(self) -> LedgerModelQuerySet:
|
|
103
111
|
"""
|
|
@@ -122,9 +130,9 @@ class IOCursor:
|
|
|
122
130
|
"""
|
|
123
131
|
return self.ENTITY_MODEL.get_coa_accounts(
|
|
124
132
|
coa_model=self.COA_MODEL
|
|
125
|
-
)
|
|
133
|
+
).can_transact()
|
|
126
134
|
|
|
127
|
-
def resolve_account_model_qs(self, codes:
|
|
135
|
+
def resolve_account_model_qs(self, codes: Set[str]) -> AccountModelQuerySet:
|
|
128
136
|
"""
|
|
129
137
|
Resolves the final AccountModelQuerySet associated with the given account codes used by the blueprint.
|
|
130
138
|
|
|
@@ -164,6 +172,12 @@ class IOCursor:
|
|
|
164
172
|
)
|
|
165
173
|
return self.ledger_model_qs
|
|
166
174
|
|
|
175
|
+
def is_permissive(self) -> bool:
|
|
176
|
+
return self.MODE == IOCursorMode.PERMISSIVE
|
|
177
|
+
|
|
178
|
+
def is_strict(self) -> bool:
|
|
179
|
+
return self.MODE == IOCursorMode.STRICT
|
|
180
|
+
|
|
167
181
|
def dispatch(self,
|
|
168
182
|
name,
|
|
169
183
|
ledger_model: Optional[Union[str, LedgerModel, UUID]] = None,
|
|
@@ -183,13 +197,14 @@ class IOCursor:
|
|
|
183
197
|
The keyword arguments to be passed to the blueprint function.
|
|
184
198
|
"""
|
|
185
199
|
|
|
186
|
-
if
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
200
|
+
if ledger_model is not None:
|
|
201
|
+
if not isinstance(ledger_model, (str, UUID, LedgerModel)):
|
|
202
|
+
raise IOCursorValidationError(
|
|
203
|
+
message=_('Ledger Model must be a string or UUID or LedgerModel')
|
|
204
|
+
)
|
|
190
205
|
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
if isinstance(ledger_model, LedgerModel):
|
|
207
|
+
self.ENTITY_MODEL.validate_ledger_model_for_entity(ledger_model)
|
|
193
208
|
|
|
194
209
|
blueprint_func = self.IO_LIBRARY.get_blueprint(name)
|
|
195
210
|
blueprint_txs = blueprint_func(**kwargs)
|
|
@@ -238,6 +253,7 @@ class IOCursor:
|
|
|
238
253
|
|
|
239
254
|
def commit(self,
|
|
240
255
|
je_timestamp: Optional[Union[datetime, date, str]] = None,
|
|
256
|
+
je_description: Optional[str] = None,
|
|
241
257
|
post_new_ledgers: bool = False,
|
|
242
258
|
post_journal_entries: bool = False,
|
|
243
259
|
**kwargs):
|
|
@@ -251,6 +267,8 @@ class IOCursor:
|
|
|
251
267
|
----------
|
|
252
268
|
je_timestamp: Optional[Union[datetime, date, str]]
|
|
253
269
|
The date or timestamp used for the committed journal entries. If none, localtime will be used.
|
|
270
|
+
je_description: Optional[str]
|
|
271
|
+
The description of the journal entries. If none, no description will be used.
|
|
254
272
|
post_new_ledgers: bool
|
|
255
273
|
If a new ledger is created, the ledger model will be posted to the database.
|
|
256
274
|
post_journal_entries: bool
|
|
@@ -275,29 +293,39 @@ class IOCursor:
|
|
|
275
293
|
for k, txs in self.blueprints.items():
|
|
276
294
|
if k is None:
|
|
277
295
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
self.
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
296
|
+
if self.is_permissive():
|
|
297
|
+
# no specified xid, ledger or UUID... create one...
|
|
298
|
+
self.commit_plan[
|
|
299
|
+
self.ENTITY_MODEL.create_ledger(
|
|
300
|
+
name='Blueprint Commitment',
|
|
301
|
+
commit=False,
|
|
302
|
+
posted=post_new_ledgers
|
|
303
|
+
)
|
|
304
|
+
] = txs
|
|
305
|
+
else:
|
|
306
|
+
raise IOCursorValidationError(
|
|
307
|
+
message=_('Cannot commit transactions to a non-existing ledger')
|
|
284
308
|
)
|
|
285
|
-
] = txs
|
|
286
309
|
|
|
287
310
|
elif isinstance(k, str):
|
|
288
311
|
try:
|
|
289
312
|
# ledger with xid already exists...
|
|
290
313
|
self.commit_plan[self.ledger_map[k]] = txs
|
|
291
314
|
except KeyError:
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
self.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
315
|
+
if self.is_permissive():
|
|
316
|
+
# create ledger with xid provided...
|
|
317
|
+
self.commit_plan[
|
|
318
|
+
self.ENTITY_MODEL.create_ledger(
|
|
319
|
+
name=f'Blueprint Commitment {k}',
|
|
320
|
+
ledger_xid=k,
|
|
321
|
+
commit=False,
|
|
322
|
+
posted=post_new_ledgers
|
|
323
|
+
)
|
|
324
|
+
] = txs
|
|
325
|
+
else:
|
|
326
|
+
raise IOCursorValidationError(
|
|
327
|
+
message=_(f'Cannot commit transactions to a non-existing ledger_xid {k}')
|
|
299
328
|
)
|
|
300
|
-
] = txs
|
|
301
329
|
|
|
302
330
|
elif isinstance(k, UUID):
|
|
303
331
|
try:
|
|
@@ -315,12 +343,18 @@ class IOCursor:
|
|
|
315
343
|
|
|
316
344
|
instructions = self.compile_instructions()
|
|
317
345
|
account_codes = set(tx.account_code for tx in chain.from_iterable(tr for _, tr in instructions.items()))
|
|
346
|
+
account_model_qs = self.resolve_account_model_qs(codes=account_codes)
|
|
318
347
|
account_models = {
|
|
319
|
-
acc.code: acc for acc in
|
|
348
|
+
acc.code: acc for acc in account_model_qs
|
|
320
349
|
}
|
|
321
350
|
|
|
322
351
|
for tx in chain.from_iterable(tr for _, tr in instructions.items()):
|
|
323
|
-
|
|
352
|
+
try:
|
|
353
|
+
tx.account_model = account_models[tx.account_code]
|
|
354
|
+
except KeyError:
|
|
355
|
+
raise IOCursorValidationError(
|
|
356
|
+
message=_(f'Account code {tx.account_code} not found. Is account available and not locked?')
|
|
357
|
+
)
|
|
324
358
|
|
|
325
359
|
results = dict()
|
|
326
360
|
for ledger_model, tr_items in instructions.items():
|
|
@@ -333,15 +367,20 @@ class IOCursor:
|
|
|
333
367
|
je_timestamp=je_timestamp if je_timestamp else get_localtime(),
|
|
334
368
|
je_txs=je_txs,
|
|
335
369
|
je_posted=post_journal_entries,
|
|
370
|
+
je_desc=je_description,
|
|
336
371
|
**kwargs
|
|
337
372
|
)
|
|
338
373
|
|
|
374
|
+
je.txs_models = txs_models
|
|
375
|
+
|
|
339
376
|
results[ledger_model] = {
|
|
377
|
+
'ledger_model': ledger_model,
|
|
340
378
|
'journal_entry': je,
|
|
341
379
|
'txs_models': txs_models,
|
|
342
|
-
'instructions': tr_items
|
|
380
|
+
'instructions': tr_items,
|
|
381
|
+
'account_model_qs': self.account_model_qs
|
|
343
382
|
}
|
|
344
|
-
|
|
383
|
+
|
|
345
384
|
self.__COMMITTED = True
|
|
346
385
|
return results
|
|
347
386
|
|
|
@@ -518,6 +557,8 @@ class IOLibrary:
|
|
|
518
557
|
The human-readable name of the library (i.e. PayRoll, Expenses, Rentals, etc...)
|
|
519
558
|
"""
|
|
520
559
|
|
|
560
|
+
IO_CURSOR_CLASS = IOCursor
|
|
561
|
+
|
|
521
562
|
def __init__(self, name: str):
|
|
522
563
|
self.name = name
|
|
523
564
|
self.registry: Dict[str, Callable] = {}
|
|
@@ -545,10 +586,14 @@ class IOLibrary:
|
|
|
545
586
|
raise IOLibraryError(message=f'Function "{name}" is not registered in IO library {self.name}')
|
|
546
587
|
return self.registry[name]
|
|
547
588
|
|
|
589
|
+
def get_io_cursor_class(self):
|
|
590
|
+
return self.IO_CURSOR_CLASS
|
|
591
|
+
|
|
548
592
|
def get_cursor(
|
|
549
593
|
self,
|
|
550
594
|
entity_model: EntityModel,
|
|
551
595
|
user_model,
|
|
596
|
+
mode: IOCursorMode = IOCursorMode.PERMISSIVE,
|
|
552
597
|
coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None
|
|
553
598
|
) -> IOCursor:
|
|
554
599
|
"""
|
|
@@ -562,14 +607,18 @@ class IOLibrary:
|
|
|
562
607
|
The user model instance executing the transactions.
|
|
563
608
|
coa_model: ChartOfAccountModel or UUID or str, optional
|
|
564
609
|
The ChartOfAccountsModel instance or identifier used to determine the AccountModelQuerySet used for the transactions.
|
|
610
|
+
mode: IOCursorMode
|
|
611
|
+
The Mode of the cursor instance. Defaults to IOCursorMode.PERMISSIVE.
|
|
565
612
|
|
|
566
613
|
Returns
|
|
567
614
|
-------
|
|
568
615
|
IOCursor
|
|
569
616
|
"""
|
|
570
|
-
|
|
617
|
+
io_cursor_class = self.get_io_cursor_class()
|
|
618
|
+
return io_cursor_class(
|
|
571
619
|
io_library=self,
|
|
572
620
|
entity_model=entity_model,
|
|
573
621
|
user_model=user_model,
|
|
574
622
|
coa_model=coa_model,
|
|
623
|
+
mode=mode
|
|
575
624
|
)
|
django_ledger/io/roles.py
CHANGED
|
@@ -517,92 +517,86 @@ ACCOUNT_ROLE_CHOICES = [
|
|
|
517
517
|
))
|
|
518
518
|
]
|
|
519
519
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
#
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
#
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
#
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
#
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
#
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
#
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
#
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
#
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
#
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
#
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
# (ROOT_CAPITAL, 'Capital Root Account'),
|
|
601
|
-
# (ROOT_INCOME, 'Income Root Account'),
|
|
602
|
-
# (ROOT_COGS, 'COGS Root Account'),
|
|
603
|
-
# (ROOT_EXPENSES, 'Expenses Root Account'),
|
|
604
|
-
# ))
|
|
605
|
-
# ]
|
|
520
|
+
ACCOUNT_ROLE_CHOICES_FOR_FORMS = [
|
|
521
|
+
('Asset', (
|
|
522
|
+
# CURRENT ASSETS ----
|
|
523
|
+
(ASSET_CA_CASH, _('Current Asset')),
|
|
524
|
+
(ASSET_CA_MKT_SECURITIES, _('Marketable Securities')),
|
|
525
|
+
(ASSET_CA_RECEIVABLES, _('Receivables')),
|
|
526
|
+
(ASSET_CA_INVENTORY, _('Inventory')),
|
|
527
|
+
(ASSET_CA_UNCOLLECTIBLES, _('Uncollectibles')),
|
|
528
|
+
(ASSET_CA_PREPAID, _('Prepaid')),
|
|
529
|
+
(ASSET_CA_OTHER, _('Other Liquid Assets')),
|
|
530
|
+
|
|
531
|
+
# LONG TERM INVESTMENTS ---
|
|
532
|
+
(ASSET_LTI_NOTES_RECEIVABLE, _('Notes Receivable')),
|
|
533
|
+
(ASSET_LTI_LAND, _('Land')),
|
|
534
|
+
(ASSET_LTI_SECURITIES, _('Securities')),
|
|
535
|
+
|
|
536
|
+
# PPE ...
|
|
537
|
+
(ASSET_PPE_BUILDINGS, _('Buildings')),
|
|
538
|
+
(ASSET_PPE_BUILDINGS_ACCUM_DEPRECIATION, _('Buildings - Accum. Depreciation')),
|
|
539
|
+
(ASSET_PPE_PLANT, _('Plant')),
|
|
540
|
+
(ASSET_PPE_PLANT_ACCUM_DEPRECIATION, _('Plant - Accum. Depreciation')),
|
|
541
|
+
(ASSET_PPE_EQUIPMENT, _('Equipment')),
|
|
542
|
+
(ASSET_PPE_EQUIPMENT_ACCUM_DEPRECIATION, _('Equipment - Accum. Depreciation')),
|
|
543
|
+
|
|
544
|
+
# Other Assets ...
|
|
545
|
+
(ASSET_INTANGIBLE_ASSETS, _('Intangible Assets')),
|
|
546
|
+
(ASSET_INTANGIBLE_ASSETS_ACCUM_AMORTIZATION, _('Intangible Assets - Accum. Amortization')),
|
|
547
|
+
(ASSET_ADJUSTMENTS, _('Other Assets')),
|
|
548
|
+
)),
|
|
549
|
+
('Liabilities', (
|
|
550
|
+
|
|
551
|
+
# CURRENT LIABILITIES ---
|
|
552
|
+
(LIABILITY_CL_ACC_PAYABLE, _('Accounts Payable')),
|
|
553
|
+
(LIABILITY_CL_WAGES_PAYABLE, _('Wages Payable')),
|
|
554
|
+
(LIABILITY_CL_INTEREST_PAYABLE, _('Interest Payable')),
|
|
555
|
+
(LIABILITY_CL_TAXES_PAYABLE, _('Taxes Payable')),
|
|
556
|
+
(LIABILITY_CL_ST_NOTES_PAYABLE, _('Short Term Notes Payable')),
|
|
557
|
+
(LIABILITY_CL_LTD_MATURITIES, _('Current Maturities of Long Tern Debt')),
|
|
558
|
+
(LIABILITY_CL_DEFERRED_REVENUE, _('Deferred Revenue')),
|
|
559
|
+
(LIABILITY_CL_OTHER, _('Other Liabilities')),
|
|
560
|
+
|
|
561
|
+
# LONG TERM LIABILITIES ----
|
|
562
|
+
(LIABILITY_LTL_NOTES_PAYABLE, _('Long Term Notes Payable')),
|
|
563
|
+
(LIABILITY_LTL_BONDS_PAYABLE, _('Bonds Payable')),
|
|
564
|
+
(LIABILITY_LTL_MORTGAGE_PAYABLE, _('Mortgage Payable')),
|
|
565
|
+
)),
|
|
566
|
+
('Capital', (
|
|
567
|
+
|
|
568
|
+
# EQUITY ---
|
|
569
|
+
(EQUITY_CAPITAL, _('Capital')),
|
|
570
|
+
(EQUITY_COMMON_STOCK, _('Common Stock')),
|
|
571
|
+
(EQUITY_PREFERRED_STOCK, _('Preferred Stock')),
|
|
572
|
+
(EQUITY_ADJUSTMENT, _('Other Equity Adjustments')),
|
|
573
|
+
(EQUITY_DIVIDENDS, _('Dividends & Distributions to Shareholders')),
|
|
574
|
+
)),
|
|
575
|
+
|
|
576
|
+
('Income', (
|
|
577
|
+
# INCOME ---
|
|
578
|
+
(INCOME_OPERATIONAL, _('Operational Income')),
|
|
579
|
+
(INCOME_PASSIVE, _('Investing/Passive Income')),
|
|
580
|
+
(INCOME_INTEREST, _('Interest Income')),
|
|
581
|
+
(INCOME_CAPITAL_GAIN_LOSS, _('Capital Gain/Loss Income')),
|
|
582
|
+
(INCOME_OTHER, _('Other Income')),
|
|
583
|
+
)),
|
|
584
|
+
|
|
585
|
+
('Expense', (
|
|
586
|
+
# COGS ----
|
|
587
|
+
(COGS, _('Cost of Goods Sold')),
|
|
588
|
+
|
|
589
|
+
# EXPENSES ----
|
|
590
|
+
(EXPENSE_OPERATIONAL, _('Regular Expense')),
|
|
591
|
+
(EXPENSE_INTEREST_ST, _('Interest Expense - Short Term Debt')),
|
|
592
|
+
(EXPENSE_INTEREST_LT, _('Interest Expense - Long Term Debt')),
|
|
593
|
+
(EXPENSE_TAXES, _('Tax Expense')),
|
|
594
|
+
(EXPENSE_CAPITAL, _('Capital Expense')),
|
|
595
|
+
(EXPENSE_DEPRECIATION, _('Depreciation Expense')),
|
|
596
|
+
(EXPENSE_AMORTIZATION, _('Amortization Expense')),
|
|
597
|
+
(EXPENSE_OTHER, _('Other Expense')),
|
|
598
|
+
))
|
|
599
|
+
]
|
|
606
600
|
|
|
607
601
|
ACCOUNT_CHOICES_NO_ROOT = [c for c in ACCOUNT_ROLE_CHOICES if c[0] != 'Root']
|
|
608
602
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Generated by Django 5.0.4 on 2024-04-25 13:41
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
dependencies = [
|
|
10
|
+
('django_ledger', '0015_remove_chartofaccountmodel_locked_and_more'),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.RemoveIndex(
|
|
15
|
+
model_name='accountmodel',
|
|
16
|
+
name='django_ledg_coa_mod_e19964_idx',
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name='ledgermodel',
|
|
20
|
+
name='entity',
|
|
21
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='django_ledger.entitymodel', verbose_name='Ledger Entity'),
|
|
22
|
+
),
|
|
23
|
+
migrations.AlterField(
|
|
24
|
+
model_name='ledgermodel',
|
|
25
|
+
name='ledger_xid',
|
|
26
|
+
field=models.SlugField(allow_unicode=True, blank=True, help_text='User Defined Ledger ID', max_length=150, null=True, verbose_name='Ledger External ID'),
|
|
27
|
+
),
|
|
28
|
+
migrations.AddIndex(
|
|
29
|
+
model_name='accountmodel',
|
|
30
|
+
index=models.Index(fields=['coa_model', 'code'], name='django_ledg_coa_mod_e073bc_idx'),
|
|
31
|
+
),
|
|
32
|
+
migrations.AddIndex(
|
|
33
|
+
model_name='accountmodel',
|
|
34
|
+
index=models.Index(fields=['code'], name='django_ledg_code_081adc_idx'),
|
|
35
|
+
),
|
|
36
|
+
migrations.AddIndex(
|
|
37
|
+
model_name='ledgermodel',
|
|
38
|
+
index=models.Index(fields=['entity', 'ledger_xid'], name='django_ledg_entity__7be095_idx'),
|
|
39
|
+
),
|
|
40
|
+
migrations.AddIndex(
|
|
41
|
+
model_name='ledgermodel',
|
|
42
|
+
index=models.Index(fields=['ledger_xid'], name='django_ledg_ledger__05f099_idx'),
|
|
43
|
+
),
|
|
44
|
+
]
|
django_ledger/models/accounts.py
CHANGED
|
@@ -87,6 +87,26 @@ class AccountModelQuerySet(MP_NodeQuerySet):
|
|
|
87
87
|
"""
|
|
88
88
|
return self.filter(active=False)
|
|
89
89
|
|
|
90
|
+
def locked(self):
|
|
91
|
+
"""
|
|
92
|
+
Filter locked elements.
|
|
93
|
+
|
|
94
|
+
This method filters the elements based on the `locked` attribute and returns a filtered queryset.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
A filtered queryset containing the locked elements.
|
|
98
|
+
"""
|
|
99
|
+
return self.filter(locked=True)
|
|
100
|
+
|
|
101
|
+
def unlocked(self):
|
|
102
|
+
"""
|
|
103
|
+
Returns a filtered version of an object, excluding any locked items.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
A filtered version of the object, excluding any locked items.
|
|
107
|
+
"""
|
|
108
|
+
return self.filter(locked=False)
|
|
109
|
+
|
|
90
110
|
def with_roles(self, roles: Union[List, str]):
|
|
91
111
|
"""
|
|
92
112
|
This method is used to make query of accounts with a certain role. For instance, the fixed assets like
|
|
@@ -110,12 +130,25 @@ class AccountModelQuerySet(MP_NodeQuerySet):
|
|
|
110
130
|
return self.filter(role__in=roles)
|
|
111
131
|
|
|
112
132
|
def expenses(self):
|
|
133
|
+
"""
|
|
134
|
+
Return the expenses filtered by the roles specified in GROUP_EXPENSES.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
QuerySet: A queryset containing the expenses filtered by the GROUP_EXPENSES roles..
|
|
138
|
+
"""
|
|
113
139
|
return self.filter(role__in=GROUP_EXPENSES)
|
|
114
140
|
|
|
115
141
|
def is_coa_root(self):
|
|
142
|
+
"""
|
|
143
|
+
Check if the account model instance is the Chart of Account Root.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool: True if the Account is the CoA Root, False otherwise.
|
|
147
|
+
"""
|
|
116
148
|
return self.filter(role__in=ROOT_GROUP)
|
|
117
149
|
|
|
118
150
|
def not_coa_root(self):
|
|
151
|
+
|
|
119
152
|
return self.exclude(role__in=ROOT_GROUP)
|
|
120
153
|
|
|
121
154
|
def for_entity(self, entity_slug, user_model):
|
|
@@ -146,12 +179,16 @@ class AccountModelQuerySet(MP_NodeQuerySet):
|
|
|
146
179
|
def is_role_default(self):
|
|
147
180
|
return self.not_coa_root().filter(role_default=True)
|
|
148
181
|
|
|
182
|
+
def can_transact(self):
|
|
183
|
+
return self.filter(
|
|
184
|
+
Q(locked=False) & Q(active=True)
|
|
185
|
+
)
|
|
186
|
+
|
|
149
187
|
|
|
150
188
|
class AccountModelManager(MP_NodeManager):
|
|
151
189
|
"""
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
function which return all rows of a model.
|
|
190
|
+
AccountModelManager class provides methods to manage and retrieve AccountModel objects.
|
|
191
|
+
It inherits from MP_NodeManager for tree-like model implementation.
|
|
155
192
|
"""
|
|
156
193
|
|
|
157
194
|
def get_queryset(self) -> AccountModelQuerySet:
|
|
@@ -407,49 +444,41 @@ def account_code_validator(value: str):
|
|
|
407
444
|
|
|
408
445
|
|
|
409
446
|
class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
410
|
-
"""
|
|
411
|
-
Django Ledger Base Account Model Abstract. This is the main abstract class which the Account Model database will
|
|
412
|
-
inherit, and it contains the fields/columns/attributes which the said ledger table will have. In addition to the
|
|
413
|
-
attributes mentioned below, it also has the fields/columns/attributes mentioned in the ParentChileMixin & the
|
|
414
|
-
CreateUpdateMixIn. Read about these mixin here.
|
|
447
|
+
""" AccountModelAbstract
|
|
415
448
|
|
|
416
|
-
|
|
449
|
+
Abstract class representing an Account Model.
|
|
417
450
|
|
|
418
451
|
Attributes
|
|
419
452
|
----------
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
coa_model: ChartOfAccountsModel
|
|
451
|
-
Each Accounts must be assigned a ChartOfAccountsModel. By default, one CoA will be created for each entity.
|
|
452
|
-
However, the creating of a new AccountModel must have an explicit assignment of a ChartOfAccountModel.
|
|
453
|
+
BALANCE_TYPE : list
|
|
454
|
+
List of choices for the balance type of the account.
|
|
455
|
+
|
|
456
|
+
uuid : UUIDField
|
|
457
|
+
UUID field representing the primary key of the account.
|
|
458
|
+
|
|
459
|
+
code : CharField
|
|
460
|
+
CharField representing the account code.
|
|
461
|
+
|
|
462
|
+
name : CharField
|
|
463
|
+
CharField representing the account name.
|
|
464
|
+
|
|
465
|
+
role : CharField
|
|
466
|
+
CharField representing the account role.
|
|
467
|
+
|
|
468
|
+
role_default : BooleanField
|
|
469
|
+
BooleanField representing whether the account is a default account for the role.
|
|
470
|
+
|
|
471
|
+
balance_type : CharField
|
|
472
|
+
CharField representing the balance type of the account. Must be 'debit' or 'credit'.
|
|
473
|
+
|
|
474
|
+
locked : BooleanField
|
|
475
|
+
BooleanField representing whether the account is locked.
|
|
476
|
+
|
|
477
|
+
active : BooleanField
|
|
478
|
+
BooleanField representing whether the account is active.
|
|
479
|
+
|
|
480
|
+
coa_model : ForeignKey
|
|
481
|
+
ForeignKey representing the associated ChartOfAccountModel.
|
|
453
482
|
"""
|
|
454
483
|
BALANCE_TYPE = [
|
|
455
484
|
(CREDIT, _('Credit')),
|
|
@@ -485,7 +514,8 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
|
|
|
485
514
|
models.Index(fields=['balance_type']),
|
|
486
515
|
models.Index(fields=['active']),
|
|
487
516
|
models.Index(fields=['locked']),
|
|
488
|
-
models.Index(fields=['coa_model'])
|
|
517
|
+
models.Index(fields=['coa_model', 'code']),
|
|
518
|
+
models.Index(fields=['code'])
|
|
489
519
|
]
|
|
490
520
|
|
|
491
521
|
def __str__(self):
|
django_ledger/models/bill.py
CHANGED
|
@@ -486,6 +486,8 @@ class BillModelAbstract(
|
|
|
486
486
|
|
|
487
487
|
if self.can_generate_bill_number():
|
|
488
488
|
self.generate_bill_number(commit=commit)
|
|
489
|
+
ledger_model.ledger_xid = f'bill-{self.bill_number.lower()}-{str(ledger_model.entity_id)[-5:]}'
|
|
490
|
+
ledger_model.save(update_fields=['ledger_xid'])
|
|
489
491
|
|
|
490
492
|
self.clean()
|
|
491
493
|
|
django_ledger/models/coa.py
CHANGED
|
@@ -460,6 +460,8 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
|
|
|
460
460
|
raise ChartOfAccountsModelValidationError(
|
|
461
461
|
message=f'Invalid Account Model {account_model} for CoA {self}'
|
|
462
462
|
)
|
|
463
|
+
else:
|
|
464
|
+
account_model.coa_model = self
|
|
463
465
|
|
|
464
466
|
if not root_account_qs:
|
|
465
467
|
root_account_qs = self.get_coa_root_accounts_qs()
|
django_ledger/models/entity.py
CHANGED
|
@@ -113,7 +113,7 @@ class EntityModelManager(MP_NodeManager):
|
|
|
113
113
|
|
|
114
114
|
def get_queryset(self):
|
|
115
115
|
"""Sets the custom queryset as the default."""
|
|
116
|
-
qs = EntityModelQuerySet(self.model).order_by('path')
|
|
116
|
+
qs = EntityModelQuerySet(self.model, using=self._db).order_by('path')
|
|
117
117
|
return qs.order_by('path').select_related('admin', 'default_coa')
|
|
118
118
|
|
|
119
119
|
def for_user(self, user_model):
|
django_ledger/models/invoice.py
CHANGED
|
@@ -446,10 +446,11 @@ class InvoiceModelAbstract(
|
|
|
446
446
|
|
|
447
447
|
if self.can_generate_invoice_number():
|
|
448
448
|
self.generate_invoice_number(commit=commit)
|
|
449
|
+
ledger_model.ledger_xid=f'invoice-{self.invoice_number.lower()}-{str(ledger_model.entity_id)[-5:]}'
|
|
450
|
+
ledger_model.save(update_fields=['ledger_xid'])
|
|
449
451
|
|
|
450
452
|
self.clean()
|
|
451
453
|
|
|
452
|
-
|
|
453
454
|
if commit:
|
|
454
455
|
self.save()
|
|
455
456
|
|
django_ledger/models/items.py
CHANGED
|
@@ -890,6 +890,10 @@ class ItemTransactionModelManager(models.Manager):
|
|
|
890
890
|
|
|
891
891
|
def for_entity(self, user_model, entity_slug):
|
|
892
892
|
qs = self.for_user(user_model)
|
|
893
|
+
if isinstance(entity_slug, lazy_loader.get_entity_model()):
|
|
894
|
+
qs.filter(
|
|
895
|
+
Q(item_model__entity=entity_slug)
|
|
896
|
+
)
|
|
893
897
|
return qs.filter(
|
|
894
898
|
Q(item_model__entity__slug__exact=entity_slug)
|
|
895
899
|
)
|
django_ledger/models/ledger.py
CHANGED
|
@@ -184,13 +184,11 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
184
184
|
_WRAPPED_MODEL_KEY = 'wrapped_model'
|
|
185
185
|
uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
|
|
186
186
|
ledger_xid = models.SlugField(allow_unicode=True, max_length=150, null=True, blank=True,
|
|
187
|
-
verbose_name=_('Ledger
|
|
187
|
+
verbose_name=_('Ledger External ID'),
|
|
188
188
|
help_text=_('User Defined Ledger ID'))
|
|
189
189
|
name = models.CharField(max_length=150, null=True, blank=True, verbose_name=_('Ledger Name'))
|
|
190
190
|
|
|
191
|
-
# todo: rename to entity_model...
|
|
192
191
|
entity = models.ForeignKey('django_ledger.EntityModel',
|
|
193
|
-
editable=False,
|
|
194
192
|
on_delete=models.CASCADE,
|
|
195
193
|
verbose_name=_('Ledger Entity'))
|
|
196
194
|
posted = models.BooleanField(default=False, verbose_name=_('Posted Ledger'))
|
|
@@ -212,13 +210,22 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
|
|
|
212
210
|
models.Index(fields=['entity']),
|
|
213
211
|
models.Index(fields=['entity', 'posted']),
|
|
214
212
|
models.Index(fields=['entity', 'locked']),
|
|
213
|
+
models.Index(fields=['entity', 'ledger_xid']),
|
|
214
|
+
models.Index(fields=['ledger_xid']),
|
|
215
215
|
]
|
|
216
216
|
unique_together = [
|
|
217
217
|
('entity', 'ledger_xid')
|
|
218
218
|
]
|
|
219
219
|
|
|
220
220
|
def __str__(self):
|
|
221
|
-
|
|
221
|
+
if self.name is not None:
|
|
222
|
+
ledger_str = f'LedgerModel: {self.name}'
|
|
223
|
+
elif self.ledger_xid is not None:
|
|
224
|
+
ledger_str = f'LedgerModel: {self.ledger_xid}'
|
|
225
|
+
else:
|
|
226
|
+
ledger_str = f'LedgerModel: {self.uuid}'
|
|
227
|
+
return f'{ledger_str} | Posted: {self.posted} | Locked: {self.locked}'
|
|
228
|
+
|
|
222
229
|
|
|
223
230
|
def has_wrapped_model_info(self):
|
|
224
231
|
if self.additional_info is not None:
|
|
@@ -24,7 +24,7 @@ from django.contrib.auth import get_user_model
|
|
|
24
24
|
from django.core.exceptions import ValidationError
|
|
25
25
|
from django.core.validators import MinValueValidator
|
|
26
26
|
from django.db import models
|
|
27
|
-
from django.db.models import Q, QuerySet
|
|
27
|
+
from django.db.models import Q, QuerySet, F
|
|
28
28
|
from django.db.models.signals import pre_save
|
|
29
29
|
from django.utils.translation import gettext_lazy as _
|
|
30
30
|
|
|
@@ -238,7 +238,7 @@ class TransactionModelQuerySet(QuerySet):
|
|
|
238
238
|
return self.filter(journal_entry__is_closing_entry=True)
|
|
239
239
|
|
|
240
240
|
|
|
241
|
-
class
|
|
241
|
+
class TransactionModelManager(models.Manager):
|
|
242
242
|
"""
|
|
243
243
|
A manager class for the TransactionModel.
|
|
244
244
|
"""
|
|
@@ -531,7 +531,7 @@ class TransactionModelAbstract(CreateUpdateMixIn):
|
|
|
531
531
|
verbose_name=_('Tx Description'),
|
|
532
532
|
help_text=_('A description to be included with this individual transaction'))
|
|
533
533
|
|
|
534
|
-
objects =
|
|
534
|
+
objects = TransactionModelManager()
|
|
535
535
|
|
|
536
536
|
class Meta:
|
|
537
537
|
abstract = True
|
django_ledger/settings.py
CHANGED
|
@@ -34,6 +34,7 @@ logger.info(f'Django Ledger GraphQL Enabled: {DJANGO_LEDGER_GRAPHQL_SUPPORT_ENAB
|
|
|
34
34
|
DJANGO_LEDGER_USE_CLOSING_ENTRIES = getattr(settings, 'DJANGO_LEDGER_USE_CLOSING_ENTRIES', True)
|
|
35
35
|
DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT = getattr(settings,
|
|
36
36
|
'DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT', 3600)
|
|
37
|
+
DJANGO_LEDGER_AUTHORIZED_SUPERUSER = getattr(settings, 'DJANGO_LEDGER_AUTHORIZED_SUPERUSER', False)
|
|
37
38
|
DJANGO_LEDGER_LOGIN_URL = getattr(settings, 'DJANGO_LEDGER_LOGIN_URL', settings.LOGIN_URL)
|
|
38
39
|
DJANGO_LEDGER_BILL_NUMBER_LENGTH = getattr(settings, 'DJANGO_LEDGER_BILL_NUMBER_LENGTH', 10)
|
|
39
40
|
DJANGO_LEDGER_INVOICE_NUMBER_LENGTH = getattr(settings, 'DJANGO_LEDGER_INVOICE_NUMBER_LENGTH', 10)
|
django_ledger/urls/entity.py
CHANGED
|
@@ -9,7 +9,7 @@ urlpatterns = [
|
|
|
9
9
|
|
|
10
10
|
# DASHBOARD Views...
|
|
11
11
|
path('<slug:entity_slug>/dashboard/',
|
|
12
|
-
views.
|
|
12
|
+
views.EntityModelDetailHandlerView.as_view(),
|
|
13
13
|
name='entity-dashboard'),
|
|
14
14
|
path('<slug:entity_slug>/dashboard/year/<int:year>/',
|
|
15
15
|
views.FiscalYearEntityModelDashboardView.as_view(),
|
django_ledger/urls/unit.py
CHANGED
|
@@ -19,7 +19,7 @@ urlpatterns = [
|
|
|
19
19
|
|
|
20
20
|
# DASHBOARD Views ...
|
|
21
21
|
path('<slug:entity_slug>/dashboard/<slug:unit_slug>/',
|
|
22
|
-
views.
|
|
22
|
+
views.EntityModelDetailHandlerView.as_view(),
|
|
23
23
|
name='unit-dashboard'),
|
|
24
24
|
path('<slug:entity_slug>/dashboard/<slug:unit_slug>/year/<int:year>/',
|
|
25
25
|
views.FiscalYearEntityModelDashboardView.as_view(),
|
django_ledger/views/entity.py
CHANGED
|
@@ -159,9 +159,9 @@ class EntityDeleteView(DjangoLedgerSecurityMixIn, EntityModelModelViewQuerySetMi
|
|
|
159
159
|
|
|
160
160
|
|
|
161
161
|
# DASHBOARD VIEWS START ----
|
|
162
|
-
class
|
|
163
|
-
|
|
164
|
-
|
|
162
|
+
class EntityModelDetailHandlerView(DjangoLedgerSecurityMixIn,
|
|
163
|
+
EntityUnitMixIn,
|
|
164
|
+
RedirectView):
|
|
165
165
|
|
|
166
166
|
def get_redirect_url(self, *args, **kwargs):
|
|
167
167
|
loc_date = get_localdate()
|
|
@@ -182,14 +182,14 @@ class EntityModelDetailView(DjangoLedgerSecurityMixIn,
|
|
|
182
182
|
})
|
|
183
183
|
|
|
184
184
|
|
|
185
|
-
class
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
185
|
+
class EntityModelDetailBaseView(DjangoLedgerSecurityMixIn,
|
|
186
|
+
EntityModelModelViewQuerySetMixIn,
|
|
187
|
+
BaseDateNavigationUrlMixIn,
|
|
188
|
+
UnpaidElementsMixIn,
|
|
189
|
+
EntityUnitMixIn,
|
|
190
|
+
DigestContextMixIn,
|
|
191
|
+
YearlyReportMixIn,
|
|
192
|
+
DetailView):
|
|
193
193
|
context_object_name = 'entity'
|
|
194
194
|
slug_url_kwarg = 'entity_slug'
|
|
195
195
|
template_name = 'django_ledger/entity/entity_dashboard.html'
|
|
@@ -203,7 +203,7 @@ class FiscalYearEntityModelDashboardView(DjangoLedgerSecurityMixIn,
|
|
|
203
203
|
IO_DIGEST_EQUITY = True
|
|
204
204
|
|
|
205
205
|
def get_context_data(self, **kwargs):
|
|
206
|
-
context = super(
|
|
206
|
+
context = super().get_context_data(**kwargs)
|
|
207
207
|
entity_model: EntityModel = self.object
|
|
208
208
|
context['page_title'] = entity_model.name
|
|
209
209
|
context['header_title'] = entity_model.name
|
|
@@ -228,6 +228,12 @@ class FiscalYearEntityModelDashboardView(DjangoLedgerSecurityMixIn,
|
|
|
228
228
|
return context
|
|
229
229
|
|
|
230
230
|
|
|
231
|
+
class FiscalYearEntityModelDashboardView(EntityModelDetailBaseView):
|
|
232
|
+
"""
|
|
233
|
+
Entity Fiscal Year Dashboard View.
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
|
|
231
237
|
class QuarterlyEntityDashboardView(FiscalYearEntityModelDashboardView, QuarterlyReportMixIn):
|
|
232
238
|
"""
|
|
233
239
|
Entity Quarterly Dashboard View.
|
django_ledger/views/ledger.py
CHANGED
|
@@ -107,7 +107,6 @@ class LedgerModelCreateView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuery
|
|
|
107
107
|
def form_valid(self, form):
|
|
108
108
|
instance = form.save(commit=False)
|
|
109
109
|
instance.entity = self.AUTHORIZED_ENTITY_MODEL
|
|
110
|
-
self.object = form.save()
|
|
111
110
|
return super().form_valid(form)
|
|
112
111
|
|
|
113
112
|
def get_success_url(self):
|
django_ledger/views/mixins.py
CHANGED
|
@@ -10,8 +10,8 @@ from calendar import monthrange
|
|
|
10
10
|
from datetime import timedelta, date
|
|
11
11
|
from typing import Tuple, Optional
|
|
12
12
|
|
|
13
|
-
from django.contrib.auth.mixins import PermissionRequiredMixin
|
|
14
|
-
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
13
|
+
from django.contrib.auth.mixins import PermissionRequiredMixin, LoginRequiredMixin
|
|
14
|
+
from django.core.exceptions import ValidationError, ObjectDoesNotExist, ImproperlyConfigured
|
|
15
15
|
from django.db.models import Q
|
|
16
16
|
from django.http import Http404, HttpResponse, HttpResponseNotFound
|
|
17
17
|
from django.urls import reverse
|
|
@@ -21,7 +21,7 @@ from django.views.generic.dates import YearMixin, MonthMixin, DayMixin
|
|
|
21
21
|
|
|
22
22
|
from django_ledger.models import EntityModel, InvoiceModel, BillModel
|
|
23
23
|
from django_ledger.models.entity import EntityModelFiscalPeriodMixIn
|
|
24
|
-
from django_ledger.settings import DJANGO_LEDGER_PDF_SUPPORT_ENABLED
|
|
24
|
+
from django_ledger.settings import DJANGO_LEDGER_PDF_SUPPORT_ENABLED, DJANGO_LEDGER_AUTHORIZED_SUPERUSER
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class YearlyReportMixIn(YearMixin, EntityModelFiscalPeriodMixIn):
|
|
@@ -292,19 +292,34 @@ class SuccessUrlNextMixIn:
|
|
|
292
292
|
return reverse('django_ledger:home')
|
|
293
293
|
|
|
294
294
|
|
|
295
|
-
class DjangoLedgerSecurityMixIn(PermissionRequiredMixin):
|
|
295
|
+
class DjangoLedgerSecurityMixIn(LoginRequiredMixin, PermissionRequiredMixin):
|
|
296
|
+
ENTITY_SLUG_URL_KWARG = 'entity_slug'
|
|
296
297
|
AUTHORIZED_ENTITY_MODEL: Optional[EntityModel] = None
|
|
298
|
+
AUTHORIZE_SUPERUSER: bool = DJANGO_LEDGER_AUTHORIZED_SUPERUSER
|
|
297
299
|
permission_required = []
|
|
298
300
|
|
|
299
301
|
def get_login_url(self):
|
|
300
302
|
return reverse('django_ledger:login')
|
|
301
303
|
|
|
304
|
+
def get_entity_slug(self):
|
|
305
|
+
return self.kwargs[self.ENTITY_SLUG_URL_KWARG]
|
|
306
|
+
|
|
307
|
+
def get_entity_slug_kwarg(self):
|
|
308
|
+
if self.ENTITY_SLUG_URL_KWARG is None:
|
|
309
|
+
raise ImproperlyConfigured(
|
|
310
|
+
_('ENTITY_SLUG_URL_KWARG must be provided.')
|
|
311
|
+
)
|
|
312
|
+
return self.ENTITY_SLUG_URL_KWARG
|
|
313
|
+
|
|
302
314
|
def has_permission(self):
|
|
315
|
+
entity_slug_kwarg = self.get_entity_slug_kwarg()
|
|
303
316
|
if self.request.user.is_superuser:
|
|
304
|
-
if
|
|
317
|
+
if not self.AUTHORIZE_SUPERUSER:
|
|
318
|
+
return False
|
|
319
|
+
if entity_slug_kwarg in self.kwargs:
|
|
305
320
|
try:
|
|
306
321
|
entity_model_qs = self.get_authorized_entity_queryset()
|
|
307
|
-
self.AUTHORIZED_ENTITY_MODEL = entity_model_qs.get(slug__exact=self.kwargs[
|
|
322
|
+
self.AUTHORIZED_ENTITY_MODEL = entity_model_qs.get(slug__exact=self.kwargs[entity_slug_kwarg])
|
|
308
323
|
except ObjectDoesNotExist:
|
|
309
324
|
return False
|
|
310
325
|
return True
|
|
@@ -312,10 +327,10 @@ class DjangoLedgerSecurityMixIn(PermissionRequiredMixin):
|
|
|
312
327
|
has_perm = super().has_permission()
|
|
313
328
|
if not has_perm:
|
|
314
329
|
return False
|
|
315
|
-
if
|
|
330
|
+
if entity_slug_kwarg in self.kwargs:
|
|
316
331
|
try:
|
|
317
332
|
entity_model_qs = self.get_authorized_entity_queryset()
|
|
318
|
-
self.AUTHORIZED_ENTITY_MODEL = entity_model_qs.get(slug__exact=self.kwargs[
|
|
333
|
+
self.AUTHORIZED_ENTITY_MODEL = entity_model_qs.get(slug__exact=self.kwargs[entity_slug_kwarg])
|
|
319
334
|
except ObjectDoesNotExist:
|
|
320
335
|
return False
|
|
321
336
|
return True
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-ledger
|
|
3
|
-
Version: 0.6.0
|
|
3
|
+
Version: 0.6.0.2
|
|
4
4
|
Summary: Double entry accounting system built on the Django Web Framework.
|
|
5
5
|
Author-email: Miguel Sanda <msanda@arrobalytics.com>
|
|
6
6
|
Maintainer-email: Miguel Sanda <msanda@arrobalytics.com>
|
|
@@ -21,16 +21,16 @@ Requires-Python: >=3.10
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
License-File: AUTHORS.md
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
24
|
+
Requires-Dist: django >=2.2
|
|
25
|
+
Requires-Dist: django-treebeard >=4.5.1
|
|
26
|
+
Requires-Dist: faker >=15.3.3
|
|
27
|
+
Requires-Dist: markdown >=3.4.1
|
|
28
|
+
Requires-Dist: ofxtools >=0.9.5
|
|
29
|
+
Requires-Dist: pillow >=9.3.0
|
|
30
|
+
Requires-Dist: python-dateutil >=2.8.2 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
31
|
+
Requires-Dist: six >=1.16.0 ; python_version >= "2.7" and python_version not in "3.0, 3.1, 3.2, 3.3"
|
|
32
|
+
Requires-Dist: sqlparse >=0.4.3 ; python_version >= "3.5"
|
|
33
|
+
Requires-Dist: asgiref >=3.5.2 ; python_version >= "3.7"
|
|
34
34
|
Provides-Extra: dev
|
|
35
35
|
Requires-Dist: sphinx ~=4.5.0 ; extra == 'dev'
|
|
36
36
|
Requires-Dist: behave ~=1.2.6 ; extra == 'dev'
|
|
@@ -44,9 +44,10 @@ Requires-Dist: furo ; extra == 'dev'
|
|
|
44
44
|
|
|
45
45
|
Introducing __Django Ledger__, a powerful double entry accounting system designed for financially driven applications
|
|
46
46
|
using the [Django Web Framework](https://www.djangoproject.com). Developed by lead developer Miguel Sanda, this system
|
|
47
|
-
offers a simplified, high-level API, making it easier for users to navigate the complexities of accounting.
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
offers a simplified, high-level API, making it easier for users to navigate the complexities of accounting.
|
|
48
|
+
|
|
49
|
+
If you have prior experience with Django, you'll find this software even more effective. And, for those interested
|
|
50
|
+
in contributing, consider joining our new discord channel for further collaboration and discussions.
|
|
50
51
|
|
|
51
52
|
### Questions? Join our Discord Channel [Here](https://discord.gg/c7PZcbYgrc)
|
|
52
53
|
|
|
@@ -82,13 +83,14 @@ Feel free to initiate an Issue describing your new feature request.
|
|
|
82
83
|
Finance and Accounting is a complicated subject. Django Ledger stands out from other Django projects due to its focus
|
|
83
84
|
on providing a developer-friendly accounting engine and a reliable, extensible API for financially driven applications.
|
|
84
85
|
The project requires expertise in Python, Django programming, finance, and accounting. In essence, the project is
|
|
85
|
-
seeking assistance from individuals with the specific skill set needed to contribute effectively.
|
|
86
|
-
they are in need of support from individuals with the right expertise.
|
|
86
|
+
seeking assistance from individuals with the specific skill set needed to contribute effectively.
|
|
87
87
|
|
|
88
88
|
The project is actively seeking contributors with financial and/or accounting experience. Prior accounting experience
|
|
89
89
|
is a big plus for potential contributors. If you have the relevant experience and want to contribute, feel free to
|
|
90
|
-
reach out to me
|
|
91
|
-
|
|
90
|
+
reach out to me or submit your pull request.
|
|
91
|
+
|
|
92
|
+
You can find the contribution guidelines at the specified link.
|
|
93
|
+
The project welcomes anyone interested in making a contribution.
|
|
92
94
|
|
|
93
95
|
See __[contribution guidelines](https://github.com/arrobalytics/django-ledger/blob/develop/Contribute.md)__.
|
|
94
96
|
|
|
@@ -48,15 +48,15 @@ assets/node_modules/node-gyp/gyp/tools/pretty_gyp.py,sha256=2ZCRPW-MZfK7gdnCIaqh
|
|
|
48
48
|
assets/node_modules/node-gyp/gyp/tools/pretty_sln.py,sha256=b_Fxm-SXUCPL3Tix4EyNwZNmQ-zkeRIFFmuL0R5wFhw,5482
|
|
49
49
|
assets/node_modules/node-gyp/gyp/tools/pretty_vcproj.py,sha256=AwQrxK1F-jhjsbbT35XQjrvWNbc3IBFaKXoJogqMh_o,10633
|
|
50
50
|
assets/node_modules/node-gyp/test/fixtures/test-charmap.py,sha256=5raXzaQnO2eJnrlFtlDtWftryhZX7Fj0amFW3hdSnhE,547
|
|
51
|
-
django_ledger/__init__.py,sha256=
|
|
51
|
+
django_ledger/__init__.py,sha256=c-3dvReg1GgIIrDElZzpUfB38BXg2TAKf5d9uhe5WMQ,458
|
|
52
52
|
django_ledger/apps.py,sha256=H-zEWUjKGakgSDSZmLIoXChZ2h6e0dth0ZO5SpoT-8U,163
|
|
53
53
|
django_ledger/exceptions.py,sha256=rML8sQQ0Hq-DYMLZ76dfw2RYSAsXWUoyHuyC_yP9o1o,491
|
|
54
|
-
django_ledger/settings.py,sha256=
|
|
54
|
+
django_ledger/settings.py,sha256=bZyPKgjmRcO_Rj7hDi4gGlW0VFr_LP2yKeUVIkmWgQM,6321
|
|
55
55
|
django_ledger/utils.py,sha256=l8xq-uSvUdJNpyDjC_0UrsSfjeEpwf7B-tavbnt40a8,4305
|
|
56
56
|
django_ledger/admin/__init__.py,sha256=MipzxmBhXswpx63uf3Ai2amyBMAP5fZL7mKXKxjNRIY,458
|
|
57
57
|
django_ledger/admin/coa.py,sha256=BcBsvNs4Z1hOyZy4YqCtIfk1aw8DejrI1bAEH93Tkjc,3542
|
|
58
58
|
django_ledger/admin/entity.py,sha256=DhH-6o3kjUdkhVPHzwOSF3crtvf5MCzcc1vPCk9O2Bk,6287
|
|
59
|
-
django_ledger/admin/ledger.py,sha256=
|
|
59
|
+
django_ledger/admin/ledger.py,sha256=z33FYDT50ahrK4AGs-bZhnrvdIt-imG0QJpZ_KRGUWw,7914
|
|
60
60
|
django_ledger/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
61
|
django_ledger/contrib/django_ledger_graphene/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
62
|
django_ledger/contrib/django_ledger_graphene/api.py,sha256=exrsmMcX21-Vhpe2_9X0eRLcdlnoE2ut0KUxBLu-TM8,871
|
|
@@ -115,14 +115,14 @@ django_ledger/forms/unit.py,sha256=rXUefjpuAmUU0vPOqu1ObO4k-bN-_Q6kOqHJ4kp_Vlg,1
|
|
|
115
115
|
django_ledger/forms/utils.py,sha256=sgkwBZs15_rZ5NT7h-8Z7wi3-ItM1E1sqoVDo3NQ5Jc,513
|
|
116
116
|
django_ledger/forms/vendor.py,sha256=Nuh8MmSpz4ycMZwiVe--U9Ec6ezIsfACHDkhA2SyiZ4,2215
|
|
117
117
|
django_ledger/io/__init__.py,sha256=Y9R-mY4peg8EpxmlXKaBER1IHMU-Nos8_dII41Kj0Ho,445
|
|
118
|
-
django_ledger/io/io_core.py,sha256=
|
|
118
|
+
django_ledger/io/io_core.py,sha256=Cutbj5WQQ0Mrja2_kLw02KL6_JygDdJvDp7JF5KzWJU,46923
|
|
119
119
|
django_ledger/io/io_digest.py,sha256=W_bCH6JxGw6eASDb1k43JuGAejvOVfyA7WkCS7AEqDQ,4280
|
|
120
120
|
django_ledger/io/io_generator.py,sha256=JF4plsABUkCIrtI2X-YD7o5eNghRIgLUseNcBIGOj3U,34613
|
|
121
|
-
django_ledger/io/io_library.py,sha256=
|
|
121
|
+
django_ledger/io/io_library.py,sha256=vvQm3IQRLFdH7HS_DYX46Xe-U9IvgZ6MQnHjy0-fyjk,22480
|
|
122
122
|
django_ledger/io/io_middleware.py,sha256=c-vwpcjg2HbYbb4O36fdf6011dFOnoNsDHOAQXmJgB8,20052
|
|
123
123
|
django_ledger/io/ofx.py,sha256=JnmDjhIpLySoixK1WVe6DivRuu02rYsBjqI8yi5Opzs,1488
|
|
124
124
|
django_ledger/io/ratios.py,sha256=dsuCv9-r73SMLv3OrxeoT5JebfRmrDsRKG_YzHggWFw,3542
|
|
125
|
-
django_ledger/io/roles.py,sha256=
|
|
125
|
+
django_ledger/io/roles.py,sha256=J9Z8WtunOQShKORCY97HpFtlAHG4N4hPfBkpUtRQDIY,20223
|
|
126
126
|
django_ledger/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
127
127
|
django_ledger/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
128
128
|
django_ledger/management/commands/generate_oauth2_codes.py,sha256=H92pSOMA0VFjdCLXOqdIWql-aKU12uaAPdXgz2DB9Go,1495
|
|
@@ -141,25 +141,26 @@ django_ledger/migrations/0012_stagedtransactionmodel_activity.py,sha256=Tv0rXC1H
|
|
|
141
141
|
django_ledger/migrations/0013_stagedtransactionmodel_bundle_split.py,sha256=7Uxd5JEKbP31vSnlP1Us_JA6mtJzAwFnr0XNCKYJDao,469
|
|
142
142
|
django_ledger/migrations/0014_ledgermodel_ledger_xid_and_more.py,sha256=UHuEQrnFr1dV4p2mxeUtWk39psSnqwBymDz_xt57sZc,663
|
|
143
143
|
django_ledger/migrations/0015_remove_chartofaccountmodel_locked_and_more.py,sha256=GZDKJDjpqo52pY7sXusHpyvXsUwsuvoZqTQNda9Eo1I,560
|
|
144
|
+
django_ledger/migrations/0016_remove_accountmodel_django_ledg_coa_mod_e19964_idx_and_more.py,sha256=wpapkPycqZ9drUMlPGBs1IRy7pz6HyDgNvZBaf-E86o,1655
|
|
144
145
|
django_ledger/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
145
146
|
django_ledger/models/__init__.py,sha256=8mn-OGhAVgLs8YASEBwo8dpX6tHyGtMxRHVPGDGECVU,793
|
|
146
|
-
django_ledger/models/accounts.py,sha256=
|
|
147
|
+
django_ledger/models/accounts.py,sha256=0OWMrv89fUdec7RF1EiWE6xZdJMOdEpgYPWechAJYrM,28881
|
|
147
148
|
django_ledger/models/bank_account.py,sha256=0-eTBxxRyvUKOVVNcGqWV1kiOKcXA2KPQIdiVHDUDCY,7678
|
|
148
|
-
django_ledger/models/bill.py,sha256=
|
|
149
|
+
django_ledger/models/bill.py,sha256=ZC6PmPYeSFMUBSUqTqebQOZrWEpo59PH6Y4evTO5uy8,63717
|
|
149
150
|
django_ledger/models/closing_entry.py,sha256=557vVKhrRZOdzqmmvtVlU48VbzJk8tTV018b0dTfpek,17746
|
|
150
|
-
django_ledger/models/coa.py,sha256=
|
|
151
|
+
django_ledger/models/coa.py,sha256=o-VM2XK64djM3px6pJlGrUVTXu5qNb4ENESS70I___0,27154
|
|
151
152
|
django_ledger/models/coa_default.py,sha256=4Zj8OMhgBiYuREjM82cFfyGWd8uCAeqggVkeNhg4SLU,27338
|
|
152
153
|
django_ledger/models/customer.py,sha256=JQOktcYKUlENJv4frek9rAW6sRerrQ0xXHlC5KPmhWk,11807
|
|
153
154
|
django_ledger/models/data_import.py,sha256=2H-4oTVLa7qXq03m9fd7T5zSQLkZKOAn2OAeOQBzMPA,19477
|
|
154
|
-
django_ledger/models/entity.py,sha256=
|
|
155
|
+
django_ledger/models/entity.py,sha256=YYcgiVsC4aDl-htStJRQ6_1Hdmw5oHK4mBP2L0XanwI,121708
|
|
155
156
|
django_ledger/models/estimate.py,sha256=-qB5t2cEdyYpFUq7tOUQnFqvE6EDUiVdTtzjEbESwEQ,55829
|
|
156
|
-
django_ledger/models/invoice.py,sha256=
|
|
157
|
-
django_ledger/models/items.py,sha256=
|
|
157
|
+
django_ledger/models/invoice.py,sha256=h5Jh5KOfYr31Eu9gFW1mdoGoVzx7nW8qBdx7vyiXnZU,61568
|
|
158
|
+
django_ledger/models/items.py,sha256=O9ujsut2jiISwKEmYcCTdUdsFywm0S-RKUDPCeXvPgY,55093
|
|
158
159
|
django_ledger/models/journal_entry.py,sha256=VfXXvm3tUFuy2Z6j3PLlDk9ndHqsZgn_PuhrxTNqaiY,50918
|
|
159
|
-
django_ledger/models/ledger.py,sha256=
|
|
160
|
+
django_ledger/models/ledger.py,sha256=q6yWf9jGK05kah_19Cbijc8HZErPM1koPejKO3ZbYXI,23382
|
|
160
161
|
django_ledger/models/mixins.py,sha256=SBcSMfFuFzLqFQv298aDOfAJrF5kT91oXyo384auKqc,51903
|
|
161
162
|
django_ledger/models/purchase_order.py,sha256=CDibi90e7Yhpv_UiyP32mMcsQ0EUElXJ2r8pLzuS7yE,42729
|
|
162
|
-
django_ledger/models/transactions.py,sha256=
|
|
163
|
+
django_ledger/models/transactions.py,sha256=aRZuP-zg-ZrxBi6rt3wx2ELXhsRC-BS9NtNUDCU2DV0,24236
|
|
163
164
|
django_ledger/models/unit.py,sha256=x5FFJXgOi1OdajQejIakW6wGY4DjrJhL3S0Pm5OimMk,8074
|
|
164
165
|
django_ledger/models/utils.py,sha256=3gkdCrfJp9qwN3Sf8R96AliilzwcKBm31UEao4WJO9o,8436
|
|
165
166
|
django_ledger/models/vendor.py,sha256=akJCO86GIwjlZ_jPUZCDXlMeuJe-8zKTm-52aJXGFpg,11320
|
|
@@ -376,7 +377,7 @@ django_ledger/urls/closing_entry.py,sha256=3W0fCuAWGB3h8cWg0cxOb9EClVrydeIdHEX0q
|
|
|
376
377
|
django_ledger/urls/customer.py,sha256=I3tWSb5Gmdr-boBSlCst_5cvCHz6JhpGxuwglqJeaG0,426
|
|
377
378
|
django_ledger/urls/data_import.py,sha256=bOi6U8gN2adxQUjOeNCJGuDRB--hwWeUIQOYTMbFarw,780
|
|
378
379
|
django_ledger/urls/djl_api.py,sha256=4BOkWI8MyfJlHXRn21hL08KYF39j7Njd1l7FIxTcsuc,952
|
|
379
|
-
django_ledger/urls/entity.py,sha256=
|
|
380
|
+
django_ledger/urls/entity.py,sha256=8ysVslj0KhzGeOZyfRMJW6SYROyGM_azwGxFkkG4ICQ,1286
|
|
380
381
|
django_ledger/urls/estimate.py,sha256=4dmIv-IElYgl88HsEuwIYBr6XK4Dhbhtj09TmDa5H8k,2058
|
|
381
382
|
django_ledger/urls/feedback.py,sha256=TY7UWFHHdN6teL6HiLibmjGCx4pXSijYZWaLt3L7-qs,273
|
|
382
383
|
django_ledger/urls/financial_statement.py,sha256=frEM-gPH3r9QkkyqmpQc3xf5IdqoCAdVQ5PgjvHD_PU,8565
|
|
@@ -388,7 +389,7 @@ django_ledger/urls/journal_entry.py,sha256=sKuYtKDSaAVcW--iIe8GQecuVZ7wF6W3vOtgA
|
|
|
388
389
|
django_ledger/urls/ledger.py,sha256=9OD7jvR3D3F6KY9RU-Hj6asvH4OiapQzvwaG3IS7haY,2801
|
|
389
390
|
django_ledger/urls/purchase_order.py,sha256=iUNdzy8dcxkkmDAOt2fO4Up3e0pHDqZNSf9JOKbO4Wo,2388
|
|
390
391
|
django_ledger/urls/transactions.py,sha256=e_x_z5qbkR6i7o8OWWdXshDiY_WVmu9WVhR9A96fnhI,80
|
|
391
|
-
django_ledger/urls/unit.py,sha256=
|
|
392
|
+
django_ledger/urls/unit.py,sha256=QEVKrgcw2dqMaaXsUHfqYecTa5-iaPlS9smrYJ1QsgM,1506
|
|
392
393
|
django_ledger/urls/vendor.py,sha256=ODHpAwe5lomluj8ZCqbMtugTeeRsv0Yo9SqkZEmfYaw,393
|
|
393
394
|
django_ledger/views/__init__.py,sha256=l5Pm2_oAW6Q_jJbXf-BiHA3ilCbiGb6gkXCm73K5DGY,1158
|
|
394
395
|
django_ledger/views/account.py,sha256=d2pzYXKPOF74hCD4ehsQ_WNFsgqyGXXekCh22gDawAM,10523
|
|
@@ -400,7 +401,7 @@ django_ledger/views/coa.py,sha256=WnWQVz-4Ik9v28KHzD_WiKcgix7l6bBj1A60p4k-eos,49
|
|
|
400
401
|
django_ledger/views/customer.py,sha256=RoBsXBxZC9b79DSNNHaoSZtQ2AoXf7DJAGmZEO3xdxs,3672
|
|
401
402
|
django_ledger/views/data_import.py,sha256=_H8gjGRIE2Jm97ivvEQn0uEWrM3VvKkYQeXQ1GbKn3g,11950
|
|
402
403
|
django_ledger/views/djl_api.py,sha256=6ADX9fBK8DroTeg8UIeCf2x4wt6-AF5xLlDQnqXBfsM,4411
|
|
403
|
-
django_ledger/views/entity.py,sha256=
|
|
404
|
+
django_ledger/views/entity.py,sha256=egJoB4-HAyzKd_5tZ8gOh8nxMKA09_Ds2H7elGt5_DE,9457
|
|
404
405
|
django_ledger/views/estimate.py,sha256=ZFG0k2_nAV10EjO-p8yp7EVMa4x2qOcFSHl2xFpNDaM,12811
|
|
405
406
|
django_ledger/views/feedback.py,sha256=qoIN44fJnblPx-pJFe5yYeO-dMqp-FReFZiyw0qQb_s,2460
|
|
406
407
|
django_ledger/views/financial_statement.py,sha256=B4FE9qyBYs8tJvBJ1n9-7kR-pH2EJWn6SnjBdtbRfuE,7335
|
|
@@ -409,15 +410,15 @@ django_ledger/views/inventory.py,sha256=ZyCmrkdYLu-D7Fnt0if0-wW6-wSWMgK9EQivVATA
|
|
|
409
410
|
django_ledger/views/invoice.py,sha256=iUzTG-EbdYqNX-eYwHBnQRUD_1wTOGutw0BfDMKcI6s,20304
|
|
410
411
|
django_ledger/views/item.py,sha256=FY53vk_giTRgvJ47FRqChQ8vyDYPDp4DGTvVhGAb36E,21347
|
|
411
412
|
django_ledger/views/journal_entry.py,sha256=21kuiRBlhlkgv8xZKM4mj9djv0Fu0BhB80QOEOHCa-w,12135
|
|
412
|
-
django_ledger/views/ledger.py,sha256=
|
|
413
|
-
django_ledger/views/mixins.py,sha256=
|
|
413
|
+
django_ledger/views/ledger.py,sha256=Yk6uDoYhJs5vf5JRqsy8n0nUNDEHk7NzjR1PglyqaAM,12647
|
|
414
|
+
django_ledger/views/mixins.py,sha256=pXiEEen4rKrAluaROMAZC7nLR967LUV5fOiOAfq22tY,21966
|
|
414
415
|
django_ledger/views/purchase_order.py,sha256=1J3u4QnCkM7z1Y6DePijVdM67x4CQgfmQJcs3Y4kclU,21082
|
|
415
416
|
django_ledger/views/transactions.py,sha256=5taQRGLSMkM_N8paQJ07HMspI_Nl7PawF8OohCiRmao,206
|
|
416
417
|
django_ledger/views/unit.py,sha256=_RgPJO9mR6v5ohBXlnL3T8nTWgS1lwlCvERQcHk0wHE,10232
|
|
417
418
|
django_ledger/views/vendor.py,sha256=gUdBPTFLeSwlNfdHSA1KFdE_y3QpwpkFhEB0r3-UYdI,3461
|
|
418
|
-
django_ledger-0.6.0.dist-info/AUTHORS.md,sha256=SRM2cynD89ZfEsL09zrbUVeO17r9zE2ZM7y6ReMqVRo,713
|
|
419
|
-
django_ledger-0.6.0.dist-info/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
|
420
|
-
django_ledger-0.6.0.dist-info/METADATA,sha256=
|
|
421
|
-
django_ledger-0.6.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
422
|
-
django_ledger-0.6.0.dist-info/top_level.txt,sha256=
|
|
423
|
-
django_ledger-0.6.0.dist-info/RECORD,,
|
|
419
|
+
django_ledger-0.6.0.2.dist-info/AUTHORS.md,sha256=SRM2cynD89ZfEsL09zrbUVeO17r9zE2ZM7y6ReMqVRo,713
|
|
420
|
+
django_ledger-0.6.0.2.dist-info/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
|
421
|
+
django_ledger-0.6.0.2.dist-info/METADATA,sha256=lx74R33LSCnxzSR4EcoICDKCz8vpi04rr1d31KbzoVg,9643
|
|
422
|
+
django_ledger-0.6.0.2.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
423
|
+
django_ledger-0.6.0.2.dist-info/top_level.txt,sha256=0U3SjF63ND36grQNWDONVe-T9-T07lFl5e6QkG7bR2E,44
|
|
424
|
+
django_ledger-0.6.0.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|