django-ledger 0.7.2__py3-none-any.whl → 0.7.4__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 (106) hide show
  1. django_ledger/__init__.py +1 -1
  2. django_ledger/contrib/django_ledger_graphene/journal_entry/schema.py +2 -3
  3. django_ledger/contrib/django_ledger_graphene/transaction/schema.py +9 -7
  4. django_ledger/forms/account.py +4 -1
  5. django_ledger/forms/journal_entry.py +19 -12
  6. django_ledger/forms/transactions.py +8 -12
  7. django_ledger/io/io_core.py +17 -12
  8. django_ledger/io/io_library.py +3 -3
  9. django_ledger/migrations/0001_initial.py +1 -1
  10. django_ledger/migrations/0019_alter_transactionmodel_amount_and_more.py +33 -0
  11. django_ledger/models/bill.py +17 -2
  12. django_ledger/models/chart_of_accounts.py +4 -0
  13. django_ledger/models/closing_entry.py +8 -6
  14. django_ledger/models/data_import.py +1 -0
  15. django_ledger/models/invoice.py +12 -4
  16. django_ledger/models/journal_entry.py +843 -481
  17. django_ledger/models/ledger.py +45 -4
  18. django_ledger/models/mixins.py +5 -5
  19. django_ledger/models/transactions.py +303 -305
  20. django_ledger/models/unit.py +42 -22
  21. django_ledger/static/django_ledger/bundle/djetler.bundle.js +1 -1
  22. django_ledger/static/django_ledger/bundle/styles.bundle.js +1 -1
  23. django_ledger/templates/django_ledger/account/tags/account_txs_table.html +1 -1
  24. django_ledger/templates/django_ledger/account/tags/accounts_table.html +1 -1
  25. django_ledger/templates/django_ledger/bills/bill_detail.html +1 -1
  26. django_ledger/templates/django_ledger/bills/bill_update.html +1 -1
  27. django_ledger/templates/django_ledger/components/icon.html +1 -1
  28. django_ledger/templates/django_ledger/data_import/tags/data_import_job_txs_imported.html +8 -1
  29. django_ledger/templates/django_ledger/financial_statements/balance_sheet.html +1 -0
  30. django_ledger/templates/django_ledger/financial_statements/tags/balance_sheet_statement.html +4 -4
  31. django_ledger/templates/django_ledger/financial_statements/tags/income_statement.html +118 -0
  32. django_ledger/templates/django_ledger/includes/nav.html +9 -5
  33. django_ledger/templates/django_ledger/invoice/includes/card_invoice.html +69 -69
  34. django_ledger/templates/django_ledger/invoice/invoice_detail.html +1 -1
  35. django_ledger/templates/django_ledger/journal_entry/je_create.html +2 -3
  36. django_ledger/templates/django_ledger/journal_entry/je_delete.html +2 -3
  37. django_ledger/templates/django_ledger/journal_entry/je_detail.html +1 -1
  38. django_ledger/templates/django_ledger/journal_entry/je_detail_txs.html +8 -8
  39. django_ledger/templates/django_ledger/journal_entry/je_list.html +16 -13
  40. django_ledger/templates/django_ledger/journal_entry/je_update.html +2 -3
  41. django_ledger/templates/django_ledger/journal_entry/tags/je_table.html +24 -24
  42. django_ledger/templates/django_ledger/journal_entry/tags/je_txs_table.html +17 -14
  43. django_ledger/templates/django_ledger/ledger/tags/ledgers_table.html +38 -37
  44. django_ledger/templates/django_ledger/transactions/tags/txs_table.html +69 -0
  45. django_ledger/templatetags/django_ledger.py +35 -48
  46. django_ledger/urls/account.py +4 -4
  47. django_ledger/views/account.py +8 -8
  48. django_ledger/views/journal_entry.py +84 -101
  49. django_ledger/views/ledger.py +16 -21
  50. django_ledger/views/mixins.py +17 -28
  51. {django_ledger-0.7.2.dist-info → django_ledger-0.7.4.dist-info}/METADATA +8 -3
  52. {django_ledger-0.7.2.dist-info → django_ledger-0.7.4.dist-info}/RECORD +56 -104
  53. assets/node_modules/node-gyp/gyp/gyp_main.py +0 -45
  54. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSNew.py +0 -367
  55. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSProject.py +0 -206
  56. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings.py +0 -1270
  57. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSSettings_test.py +0 -1547
  58. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSToolFile.py +0 -59
  59. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSUserFile.py +0 -153
  60. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSUtil.py +0 -271
  61. assets/node_modules/node-gyp/gyp/pylib/gyp/MSVSVersion.py +0 -574
  62. assets/node_modules/node-gyp/gyp/pylib/gyp/__init__.py +0 -666
  63. assets/node_modules/node-gyp/gyp/pylib/gyp/common.py +0 -654
  64. assets/node_modules/node-gyp/gyp/pylib/gyp/common_test.py +0 -78
  65. assets/node_modules/node-gyp/gyp/pylib/gyp/easy_xml.py +0 -165
  66. assets/node_modules/node-gyp/gyp/pylib/gyp/easy_xml_test.py +0 -109
  67. assets/node_modules/node-gyp/gyp/pylib/gyp/flock_tool.py +0 -55
  68. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/__init__.py +0 -0
  69. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/analyzer.py +0 -808
  70. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/android.py +0 -1173
  71. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/cmake.py +0 -1321
  72. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/compile_commands_json.py +0 -120
  73. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/dump_dependency_json.py +0 -103
  74. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/eclipse.py +0 -464
  75. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/gypd.py +0 -89
  76. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/gypsh.py +0 -58
  77. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/make.py +0 -2518
  78. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs.py +0 -3978
  79. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/msvs_test.py +0 -44
  80. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja.py +0 -2936
  81. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/ninja_test.py +0 -55
  82. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode.py +0 -1394
  83. assets/node_modules/node-gyp/gyp/pylib/gyp/generator/xcode_test.py +0 -25
  84. assets/node_modules/node-gyp/gyp/pylib/gyp/input.py +0 -3137
  85. assets/node_modules/node-gyp/gyp/pylib/gyp/input_test.py +0 -98
  86. assets/node_modules/node-gyp/gyp/pylib/gyp/mac_tool.py +0 -771
  87. assets/node_modules/node-gyp/gyp/pylib/gyp/msvs_emulation.py +0 -1271
  88. assets/node_modules/node-gyp/gyp/pylib/gyp/ninja_syntax.py +0 -174
  89. assets/node_modules/node-gyp/gyp/pylib/gyp/simple_copy.py +0 -61
  90. assets/node_modules/node-gyp/gyp/pylib/gyp/win_tool.py +0 -374
  91. assets/node_modules/node-gyp/gyp/pylib/gyp/xcode_emulation.py +0 -1939
  92. assets/node_modules/node-gyp/gyp/pylib/gyp/xcode_ninja.py +0 -302
  93. assets/node_modules/node-gyp/gyp/pylib/gyp/xcodeproj_file.py +0 -3197
  94. assets/node_modules/node-gyp/gyp/pylib/gyp/xml_fix.py +0 -65
  95. assets/node_modules/node-gyp/gyp/setup.py +0 -42
  96. assets/node_modules/node-gyp/gyp/test_gyp.py +0 -260
  97. assets/node_modules/node-gyp/gyp/tools/graphviz.py +0 -102
  98. assets/node_modules/node-gyp/gyp/tools/pretty_gyp.py +0 -156
  99. assets/node_modules/node-gyp/gyp/tools/pretty_sln.py +0 -181
  100. assets/node_modules/node-gyp/gyp/tools/pretty_vcproj.py +0 -339
  101. assets/node_modules/node-gyp/test/fixtures/test-charmap.py +0 -31
  102. assets/node_modules/node-gyp/update-gyp.py +0 -46
  103. {django_ledger-0.7.2.dist-info → django_ledger-0.7.4.dist-info}/AUTHORS.md +0 -0
  104. {django_ledger-0.7.2.dist-info → django_ledger-0.7.4.dist-info}/LICENSE +0 -0
  105. {django_ledger-0.7.2.dist-info → django_ledger-0.7.4.dist-info}/WHEEL +0 -0
  106. {django_ledger-0.7.2.dist-info → django_ledger-0.7.4.dist-info}/top_level.txt +0 -0
@@ -8,18 +8,20 @@ Miguel Sanda <msanda@arrobalytics.com>
8
8
 
9
9
  from calendar import month_abbr
10
10
  from random import randint
11
+ from typing import Union
11
12
 
12
13
  from django import template
13
- from django.db.models import Sum
14
+ from django.db.models import Sum, F
14
15
  from django.urls import reverse
15
16
  from django.utils.formats import number_format
17
+ from rfc3986.exceptions import ValidationError
16
18
 
17
19
  from django_ledger import __version__
18
20
  from django_ledger.forms.app_filters import EntityFilterForm, ActivityFilterForm
19
21
  from django_ledger.forms.feedback import BugReportForm, RequestNewFeatureForm
20
22
  from django_ledger.io import CREDIT, DEBIT, ROLES_ORDER_ALL
21
23
  from django_ledger.io.io_core import validate_activity, get_localdate
22
- from django_ledger.models import TransactionModel, BillModel, InvoiceModel, EntityUnitModel
24
+ from django_ledger.models import TransactionModel, BillModel, InvoiceModel, EntityUnitModel, JournalEntryModel
23
25
  from django_ledger.settings import (
24
26
  DJANGO_LEDGER_FINANCIAL_ANALYSIS, DJANGO_LEDGER_CURRENCY_SYMBOL,
25
27
  DJANGO_LEDGER_SPACED_CURRENCY_SYMBOL)
@@ -106,7 +108,9 @@ def balance_sheet_statement(context, io_model, to_date=None):
106
108
  balance_sheet_statement=True)
107
109
 
108
110
  return {
109
- 'tx_digest': io_digest.get_io_data()
111
+ 'entity_slug': entity_slug,
112
+ 'user_model': user_model,
113
+ 'tx_digest': io_digest.get_io_data(),
110
114
  }
111
115
 
112
116
 
@@ -131,6 +135,8 @@ def cash_flow_statement(context, io_model):
131
135
  process_groups=True)
132
136
 
133
137
  return {
138
+ 'entity_slug': entity_slug,
139
+ 'user_model': user_model,
134
140
  'tx_digest': io_digest.get_io_data()
135
141
  }
136
142
 
@@ -162,6 +168,8 @@ def income_statement_table(context, io_model, from_date=None, to_date=None):
162
168
  )
163
169
 
164
170
  return {
171
+ 'entity_slug': entity_slug,
172
+ 'user_model': user_model,
165
173
  'tx_digest': io_digest.get_io_data()
166
174
  }
167
175
 
@@ -210,65 +218,42 @@ def jes_table(context, journal_entry_qs, next_url=None):
210
218
  'ledger_pk': ledger_pk
211
219
  })
212
220
  return {
213
- 'jes': journal_entry_qs,
221
+ 'journal_entry_qs': journal_entry_qs,
214
222
  'entity_slug': entity_slug,
215
223
  'ledger_pk': ledger_pk,
216
224
  'next_url': next_url
217
225
  }
218
226
 
219
227
 
220
- @register.inclusion_tag('django_ledger/journal_entry/tags/je_txs_table.html')
221
- def journal_entry_txs_table(journal_entry_model, style='detail'):
222
- txs_queryset = journal_entry_model.transactionmodel_set.all().select_related('account').order_by('account__code')
223
- total_credits = sum(tx.amount for tx in txs_queryset if tx.tx_type == 'credit')
224
- total_debits = sum(tx.amount for tx in txs_queryset if tx.tx_type == 'debit')
225
- return {
226
- 'txs': txs_queryset,
227
- 'total_debits': total_debits,
228
- 'total_credits': total_credits,
229
- 'style': style
230
- }
231
-
232
-
233
- @register.inclusion_tag('django_ledger/journal_entry/tags/je_txs_table.html', takes_context=True)
234
- def bill_txs_table(context, bill_model: BillModel):
235
- # todo: move this to bill model...
236
- txs_queryset = TransactionModel.objects.for_bill(
237
- bill_model=bill_model.uuid,
238
- user_model=context['request'].user,
239
- entity_slug=context['view'].kwargs['entity_slug']
240
- ).select_related('journal_entry', 'journal_entry__entity_unit', 'account').order_by('-journal_entry__timestamp')
241
- total_credits = sum(tx.amount for tx in txs_queryset if tx.tx_type == CREDIT)
242
- total_debits = sum(tx.amount for tx in txs_queryset if tx.tx_type == DEBIT)
243
- return {
244
- 'style': 'detail',
245
- 'txs': txs_queryset,
246
- 'total_debits': total_debits,
247
- 'total_credits': total_credits
248
- }
228
+ @register.inclusion_tag('django_ledger/transactions/tags/txs_table.html')
229
+ def transactions_table(object_type: Union[JournalEntryModel, BillModel, InvoiceModel], style='detail'):
230
+ if isinstance(object_type, JournalEntryModel):
231
+ transaction_model_qs = object_type.transactionmodel_set.all().with_annotated_details().order_by(
232
+ '-timestamp')
233
+ elif isinstance(object_type, BillModel):
234
+ transaction_model_qs = object_type.get_transaction_queryset(annotated=True).order_by('-timestamp')
235
+ elif isinstance(object_type, InvoiceModel):
236
+ transaction_model_qs = object_type.get_transaction_queryset(annotated=True).order_by('-timestamp')
237
+ else:
238
+ raise ValidationError(
239
+ 'Cannot handle object of type {} to get transaction model queryset'.format(type(object_type)))
249
240
 
241
+ total_credits = sum(tx.amount for tx in transaction_model_qs if tx.is_credit())
242
+ total_debits = sum(tx.amount for tx in transaction_model_qs if tx.is_debit())
250
243
 
251
- @register.inclusion_tag('django_ledger/journal_entry/tags/je_txs_table.html', takes_context=True)
252
- def invoice_txs_table(context, invoice_model: InvoiceModel):
253
- txs_queryset = TransactionModel.objects.for_invoice(
254
- invoice_model=invoice_model,
255
- user_model=context['request'].user,
256
- entity_slug=context['view'].kwargs['entity_slug']
257
- ).select_related('journal_entry', 'journal_entry__entity_unit', 'account').order_by('-journal_entry__timestamp')
258
- total_credits = sum(tx.amount for tx in txs_queryset if tx.tx_type == CREDIT)
259
- total_debits = sum(tx.amount for tx in txs_queryset if tx.tx_type == DEBIT)
260
244
  return {
261
- 'style': 'detail',
262
- 'txs': txs_queryset,
245
+ 'style': style,
246
+ 'transaction_model_qs': transaction_model_qs,
263
247
  'total_debits': total_debits,
264
- 'total_credits': total_credits
248
+ 'total_credits': total_credits,
249
+ 'object': object_type
265
250
  }
266
251
 
267
252
 
268
253
  @register.inclusion_tag('django_ledger/ledger/tags/ledgers_table.html', takes_context=True)
269
254
  def ledgers_table(context, ledger_model_qs):
270
255
  return {
271
- 'ledgers': ledger_model_qs,
256
+ 'ledger_model_qs': ledger_model_qs,
272
257
  'entity_slug': context['view'].kwargs['entity_slug'],
273
258
  }
274
259
 
@@ -389,10 +374,12 @@ def default_entity(context):
389
374
  def session_entity_name(context, request=None):
390
375
  session_key = get_default_entity_session_key()
391
376
  if not request:
392
- request = context['request']
393
- session = request.session
377
+ request = context.get('request')
394
378
  try:
379
+ session = request.session
395
380
  entity_name = session.get(session_key)['entity_name']
381
+ except AttributeError:
382
+ entity_name = 'Django Ledger'
396
383
  except KeyError:
397
384
  entity_name = 'Django Ledger'
398
385
  except TypeError:
@@ -36,15 +36,15 @@ urlpatterns = [
36
36
 
37
37
  # Account Actions...
38
38
  path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/activate/',
39
- views.AccountModelModelActionView.as_view(action_name='activate'),
39
+ views.BaseAccountModelActionView.as_view(action_name='activate'),
40
40
  name='account-action-activate'),
41
41
  path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/deactivate/',
42
- views.AccountModelModelActionView.as_view(action_name='deactivate'),
42
+ views.BaseAccountModelActionView.as_view(action_name='deactivate'),
43
43
  name='account-action-deactivate'),
44
44
  path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/lock/',
45
- views.AccountModelModelActionView.as_view(action_name='lock'),
45
+ views.BaseAccountModelActionView.as_view(action_name='lock'),
46
46
  name='account-action-lock'),
47
47
  path('<slug:entity_slug>/<slug:coa_slug>/action/<uuid:account_pk>/unlock/',
48
- views.AccountModelModelActionView.as_view(action_name='unlock'),
48
+ views.BaseAccountModelActionView.as_view(action_name='unlock'),
49
49
  name='account-action-unlock')
50
50
  ]
@@ -186,7 +186,7 @@ class AccountModelYearDetailView(BaseAccountModelBaseView,
186
186
  account_model: AccountModel = context['object']
187
187
  context['header_title'] = f'Account {account_model.code} - {account_model.name}'
188
188
  context['page_title'] = f'Account {account_model.code} - {account_model.name}'
189
- txs_qs = account_model.transactionmodel_set.all().posted().order_by(
189
+ txs_qs = account_model.transactionmodel_set.all().not_closing_entry().posted().order_by(
190
190
  'journal_entry__timestamp'
191
191
  ).select_related(
192
192
  'journal_entry',
@@ -200,28 +200,28 @@ class AccountModelYearDetailView(BaseAccountModelBaseView,
200
200
  return context
201
201
 
202
202
 
203
- class AccountModelQuarterDetailView(QuarterlyReportMixIn, AccountModelYearDetailView):
203
+ class AccountModelQuarterDetailView(AccountModelYearDetailView, QuarterlyReportMixIn):
204
204
  """
205
205
  Account Model Quarter Detail View
206
206
  """
207
207
 
208
208
 
209
- class AccountModelMonthDetailView(MonthlyReportMixIn, AccountModelYearDetailView):
209
+ class AccountModelMonthDetailView(AccountModelYearDetailView, MonthlyReportMixIn):
210
210
  """
211
211
  Account Model Month Detail View
212
212
  """
213
213
 
214
214
 
215
- class AccountModelDateDetailView(DateReportMixIn, AccountModelYearDetailView):
215
+ class AccountModelDateDetailView(AccountModelYearDetailView, DateReportMixIn):
216
216
  """
217
217
  Account Model Date Detail View
218
218
  """
219
219
 
220
220
 
221
221
  # ACTIONS...
222
- class AccountModelModelActionView(BaseAccountModelBaseView,
223
- RedirectView,
224
- SingleObjectMixin):
222
+ class BaseAccountModelActionView(BaseAccountModelBaseView,
223
+ RedirectView,
224
+ SingleObjectMixin):
225
225
  http_method_names = ['get']
226
226
  pk_url_kwarg = 'account_pk'
227
227
  action_name = None
@@ -235,7 +235,7 @@ class AccountModelModelActionView(BaseAccountModelBaseView,
235
235
  kwargs['user_model'] = self.request.user
236
236
  if not self.action_name:
237
237
  raise ImproperlyConfigured('View attribute action_name is required.')
238
- response = super(AccountModelModelActionView, self).get(request, *args, **kwargs)
238
+ response = super(BaseAccountModelActionView, self).get(request, *args, **kwargs)
239
239
  account_model: AccountModel = self.get_object()
240
240
 
241
241
  try:
@@ -5,75 +5,72 @@ Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
5
5
  Contributions to this module:
6
6
  * Miguel Sanda <msanda@arrobalytics.com>
7
7
  """
8
+ from typing import Optional
9
+
8
10
  from django.contrib import messages
9
11
  from django.core.exceptions import ImproperlyConfigured, ValidationError
10
- from django.db.models import Count
11
12
  from django.http import HttpResponseForbidden
12
13
  from django.urls import reverse
13
14
  from django.utils.translation import gettext_lazy as _
14
- from django.views.generic import (YearArchiveView, MonthArchiveView, DetailView, UpdateView, CreateView, RedirectView,
15
- ArchiveIndexView, DeleteView)
15
+ from django.views.generic import (
16
+ YearArchiveView, MonthArchiveView, DetailView, UpdateView, CreateView, RedirectView,
17
+ ArchiveIndexView, DeleteView
18
+ )
16
19
  from django.views.generic.detail import SingleObjectMixin
17
20
 
18
- from django_ledger.forms.journal_entry import (JournalEntryModelUpdateForm,
19
- JournalEntryModelCannotEditForm,
20
- JournalEntryModelCreateForm)
21
+ from django_ledger.forms.journal_entry import (
22
+ JournalEntryModelUpdateForm,
23
+ JournalEntryModelCannotEditForm,
24
+ JournalEntryModelCreateForm
25
+ )
21
26
  from django_ledger.forms.transactions import get_transactionmodel_formset_class
22
27
  from django_ledger.io.io_core import get_localtime
28
+ from django_ledger.models import EntityModel, LedgerModel
23
29
  from django_ledger.models.journal_entry import JournalEntryModel
24
- from django_ledger.models.ledger import LedgerModel
25
30
  from django_ledger.views.mixins import DjangoLedgerSecurityMixIn
26
31
 
27
32
 
28
- class JournalEntryModelModelViewQuerySetMixIn:
33
+ class JournalEntryModelModelBaseView(DjangoLedgerSecurityMixIn):
29
34
  queryset = None
35
+ ledger_model: Optional[LedgerModel] = None
30
36
 
31
37
  def get_queryset(self):
32
38
  if self.queryset is None:
33
- self.queryset = JournalEntryModel.objects.for_ledger(
34
- ledger_pk=self.kwargs['ledger_pk'],
35
- entity_slug=self.kwargs['entity_slug'],
36
- user_model=self.request.user
37
- ).select_related('entity_unit', 'ledger', 'ledger__entity')
38
- return super().get_queryset()
39
+ ledger_model: LedgerModel = self.get_ledger_model()
40
+ journal_entry_queryset = ledger_model.journal_entries.select_related('entity_unit', 'ledger', 'ledger__entity').order_by('-timestamp')
41
+ self.queryset = journal_entry_queryset
42
+ return self.queryset
43
+
44
+ def get_ledger_model(self) -> LedgerModel:
45
+ if self.ledger_model is None:
46
+ entity_model: EntityModel = self.get_authorized_entity_instance()
47
+ self.ledger_model = entity_model.get_ledgers().get(uuid__exact=self.kwargs['ledger_pk'])
48
+ return self.ledger_model
39
49
 
40
50
 
41
51
  # JE Views ---
42
- class JournalEntryCreateView(DjangoLedgerSecurityMixIn, CreateView, SingleObjectMixin):
52
+ class JournalEntryCreateView(JournalEntryModelModelBaseView, CreateView):
43
53
  template_name = 'django_ledger/journal_entry/je_create.html'
44
54
  PAGE_TITLE = _('Create Journal Entry')
45
55
  extra_context = {
46
56
  'page_title': PAGE_TITLE,
47
57
  'header_title': PAGE_TITLE
48
58
  }
49
- context_object_name = 'ledger_model'
50
- pk_url_kwarg = 'ledger_pk'
51
- ledger_model = None
59
+ ledger_model: Optional[LedgerModel] = None
52
60
 
53
61
  def get_context_data(self, **kwargs):
54
- if self.ledger_model is None:
55
- ledger_model = self.get_object()
56
- self.ledger_model = ledger_model
57
- ctx = super().get_context_data(**kwargs)
58
- ctx['page_title'] = self.PAGE_TITLE
59
- ctx['header_title'] = self.PAGE_TITLE
60
- ctx['header_subtitle'] = self.ledger_model
61
- return ctx
62
-
63
- def get_queryset(self):
64
- return LedgerModel.objects.for_entity(
65
- user_model=self.request.user,
66
- entity_slug=self.kwargs['entity_slug'],
67
- )
62
+ context = super().get_context_data(**kwargs)
63
+ ledger_model: LedgerModel = self.get_ledger_model()
64
+ context['page_title'] = self.PAGE_TITLE
65
+ context['header_title'] = self.PAGE_TITLE
66
+ context['header_subtitle'] = ledger_model.name
67
+ context['ledger_model'] = ledger_model
68
+ return context
68
69
 
69
70
  def get_form(self, form_class=None):
70
- if self.ledger_model is None:
71
- ledger_model = self.get_object()
72
- self.ledger_model = ledger_model
73
71
  return JournalEntryModelCreateForm(
74
- entity_slug=self.kwargs['entity_slug'],
75
- ledger_model=self.ledger_model,
76
- user_model=self.request.user,
72
+ entity_model=self.get_authorized_entity_instance(),
73
+ ledger_model=self.get_ledger_model(),
77
74
  **self.get_form_kwargs()
78
75
  )
79
76
 
@@ -83,42 +80,51 @@ class JournalEntryCreateView(DjangoLedgerSecurityMixIn, CreateView, SingleObject
83
80
  }
84
81
 
85
82
  def get_success_url(self):
86
- return reverse('django_ledger:je-list',
87
- kwargs={
88
- 'entity_slug': self.kwargs.get('entity_slug'),
89
- 'ledger_pk': self.kwargs.get('ledger_pk')
90
- })
83
+ ledger_model = self.get_ledger_model()
84
+ return ledger_model.get_journal_entry_list_url()
91
85
 
92
86
 
93
- class JournalEntryListView(DjangoLedgerSecurityMixIn, JournalEntryModelModelViewQuerySetMixIn, ArchiveIndexView):
94
- context_object_name = 'journal_entry_list'
87
+ # ARCHIVE VIEWS START....
88
+ class JournalEntryListView(JournalEntryModelModelBaseView, ArchiveIndexView):
89
+ context_object_name = 'journal_entry_qs'
95
90
  template_name = 'django_ledger/journal_entry/je_list.html'
96
91
  PAGE_TITLE = _('Journal Entries')
97
- extra_context = {
98
- 'page_title': PAGE_TITLE,
99
- 'header_title': PAGE_TITLE
100
- }
101
92
  http_method_names = ['get']
102
93
  date_field = 'timestamp'
103
94
  paginate_by = 20
104
95
  allow_empty = True
105
96
 
106
- def get_queryset(self):
107
- qs = super().get_queryset()
108
- qs = qs.annotate(txs_count=Count('transactionmodel'))
109
- return qs
97
+ def get_context_data(self, **kwargs):
98
+ context = super().get_context_data(**kwargs)
99
+ entity_model: EntityModel = self.get_authorized_entity_instance()
110
100
 
101
+ ledger_model = self.get_ledger_model()
102
+ context['ledger_model'] = ledger_model
103
+ context['page_title'] = self.PAGE_TITLE
104
+ context['header_title'] = self.PAGE_TITLE
105
+ context['header_subtitle'] = f'{entity_model.name} | Ledger: {ledger_model.name}'
106
+ context['header_subtitle_icon'] = 'bi:journal-check'
111
107
 
112
- class JournalEntryYearListView(YearArchiveView, JournalEntryListView):
108
+ if ledger_model.is_locked():
109
+ messages.add_message(self.request,
110
+ message=_('Locked Journal Entry. Must unlock ledger to add new Journal Entries.'),
111
+ level=messages.WARNING,
112
+ extra_tags='is-warning')
113
+ return context
114
+
115
+
116
+ class JournalEntryYearListView(JournalEntryListView, YearArchiveView):
113
117
  make_object_list = True
114
118
 
115
119
 
116
- class JournalEntryMonthListView(MonthArchiveView, JournalEntryListView):
120
+ class JournalEntryMonthListView(JournalEntryListView, MonthArchiveView):
117
121
  make_object_list = True
118
122
  month_format = '%m'
119
123
 
120
124
 
121
- class JournalEntryUpdateView(DjangoLedgerSecurityMixIn, JournalEntryModelModelViewQuerySetMixIn, UpdateView):
125
+ # ARCHIVE VIEWS END....
126
+
127
+ class JournalEntryUpdateView(JournalEntryModelModelBaseView, UpdateView):
122
128
  context_object_name = 'journal_entry'
123
129
  template_name = 'django_ledger/journal_entry/je_update.html'
124
130
  pk_url_kwarg = 'je_pk'
@@ -128,34 +134,18 @@ class JournalEntryUpdateView(DjangoLedgerSecurityMixIn, JournalEntryModelModelVi
128
134
  'header_title': PAGE_TITLE
129
135
  }
130
136
 
131
- def get_form(self, form_class=None):
137
+ def get_form_class(self, form_class=None):
132
138
  je_model: JournalEntryModel = self.object
133
139
  if not je_model.can_edit():
134
- return JournalEntryModelCannotEditForm(
135
- entity_slug=self.kwargs['entity_slug'],
136
- ledger_model=je_model.ledger,
137
- user_model=self.request.user,
138
- **self.get_form_kwargs()
139
- )
140
- return JournalEntryModelUpdateForm(
141
- entity_slug=self.kwargs['entity_slug'],
142
- ledger_model=je_model.ledger,
143
- user_model=self.request.user,
144
- **self.get_form_kwargs()
145
- )
140
+ return JournalEntryModelCannotEditForm
141
+ return JournalEntryModelUpdateForm
146
142
 
147
143
  def get_success_url(self):
148
- return reverse('django_ledger:je-list', kwargs={
149
- 'entity_slug': self.kwargs['entity_slug'],
150
- 'ledger_pk': self.kwargs['ledger_pk']
151
- })
152
-
153
- def get_queryset(self):
154
- qs = super().get_queryset()
155
- return qs.prefetch_related('transactionmodel_set', 'transactionmodel_set__account')
144
+ je_model: JournalEntryModel = self.object
145
+ return je_model.get_journal_entry_list_url()
156
146
 
157
147
 
158
- class JournalEntryDetailView(DjangoLedgerSecurityMixIn, JournalEntryModelModelViewQuerySetMixIn, DetailView):
148
+ class JournalEntryDetailView(JournalEntryModelModelBaseView, DetailView):
159
149
  context_object_name = 'journal_entry'
160
150
  template_name = 'django_ledger/journal_entry/je_detail.html'
161
151
  slug_url_kwarg = 'je_pk'
@@ -168,28 +158,19 @@ class JournalEntryDetailView(DjangoLedgerSecurityMixIn, JournalEntryModelModelVi
168
158
  }
169
159
  http_method_names = ['get']
170
160
 
171
- def get_queryset(self):
172
- qs = super().get_queryset()
173
- return qs.prefetch_related('transactionmodel_set', 'transactionmodel_set__account')
174
-
175
161
 
176
- class JournalEntryDeleteView(DjangoLedgerSecurityMixIn, JournalEntryModelModelViewQuerySetMixIn, DeleteView):
162
+ class JournalEntryDeleteView(JournalEntryModelModelBaseView, DeleteView):
177
163
  template_name = 'django_ledger/journal_entry/je_delete.html'
178
164
  context_object_name = 'je_model'
179
165
  pk_url_kwarg = 'je_pk'
180
166
 
181
167
  def get_success_url(self) -> str:
182
168
  je_model: JournalEntryModel = self.object
183
- return reverse(
184
- viewname='django_ledger:je-list',
185
- kwargs={
186
- 'entity_slug': self.AUTHORIZED_ENTITY_MODEL.slug,
187
- 'ledger_pk': je_model.ledger_id
188
- }
189
- )
169
+ return je_model.get_journal_entry_list_url()
190
170
 
191
171
 
192
- class JournalEntryModelTXSDetailView(DjangoLedgerSecurityMixIn, JournalEntryModelModelViewQuerySetMixIn, DetailView):
172
+ # todo:.... move this to transaction list view?.....
173
+ class JournalEntryModelTXSDetailView(JournalEntryModelModelBaseView, DetailView):
193
174
  template_name = 'django_ledger/journal_entry/je_detail_txs.html'
194
175
  PAGE_TITLE = _('Edit Transactions')
195
176
  pk_url_kwarg = 'je_pk'
@@ -214,11 +195,8 @@ class JournalEntryModelTXSDetailView(DjangoLedgerSecurityMixIn, JournalEntryMode
214
195
  if not txs_formset:
215
196
  TransactionModelFormSet = get_transactionmodel_formset_class(journal_entry_model=je_model)
216
197
  context['txs_formset'] = TransactionModelFormSet(
217
- user_model=self.request.user,
218
198
  je_model=je_model,
219
- ledger_pk=self.kwargs['ledger_pk'],
220
- entity_slug=self.kwargs['entity_slug'],
221
- queryset=je_model.transactionmodel_set.all().order_by('account__code')
199
+ entity_model=self.get_authorized_entity_instance(),
222
200
  )
223
201
  else:
224
202
  context['txs_formset'] = txs_formset
@@ -234,9 +212,7 @@ class JournalEntryModelTXSDetailView(DjangoLedgerSecurityMixIn, JournalEntryMode
234
212
 
235
213
  TransactionModelFormSet = get_transactionmodel_formset_class(journal_entry_model=je_model)
236
214
  txs_formset = TransactionModelFormSet(request.POST,
237
- user_model=self.request.user,
238
- ledger_pk=kwargs['ledger_pk'],
239
- entity_slug=kwargs['entity_slug'],
215
+ entity_model=self.get_authorized_entity_instance(),
240
216
  je_model=je_model)
241
217
 
242
218
  if je_model.locked:
@@ -271,7 +247,11 @@ class JournalEntryModelTXSDetailView(DjangoLedgerSecurityMixIn, JournalEntryMode
271
247
 
272
248
 
273
249
  # ACTION VIEWS...
274
- class BaseJournalEntryActionView(DjangoLedgerSecurityMixIn, RedirectView, SingleObjectMixin):
250
+ class BaseJournalEntryActionView(
251
+ JournalEntryModelModelBaseView,
252
+ RedirectView,
253
+ SingleObjectMixin
254
+ ):
275
255
  http_method_names = ['get']
276
256
  pk_url_kwarg = 'je_pk'
277
257
  action_name = None
@@ -279,11 +259,14 @@ class BaseJournalEntryActionView(DjangoLedgerSecurityMixIn, RedirectView, Single
279
259
 
280
260
  def get_queryset(self):
281
261
  return JournalEntryModel.objects.for_entity(
282
- entity_slug=self.kwargs['entity_slug'],
262
+ entity_slug=self.get_authorized_entity_instance(),
283
263
  user_model=self.request.user
284
- )
264
+ ).for_ledger(ledger_pk=self.kwargs['ledger_pk'])
285
265
 
286
266
  def get_redirect_url(self, *args, **kwargs):
267
+ next_url = self.request.GET.get('next')
268
+ if next_url:
269
+ return next_url
287
270
  return reverse('django_ledger:je-list',
288
271
  kwargs={
289
272
  'entity_slug': kwargs['entity_slug'],
@@ -17,6 +17,7 @@ from django.views.generic.detail import SingleObjectMixin
17
17
 
18
18
  from django_ledger.forms.ledger import LedgerModelCreateForm, LedgerModelUpdateForm
19
19
  from django_ledger.io.io_core import get_localdate
20
+ from django_ledger.models import EntityModel
20
21
  from django_ledger.models.ledger import LedgerModel
21
22
  from django_ledger.views.mixins import (
22
23
  YearlyReportMixIn, QuarterlyReportMixIn,
@@ -25,19 +26,17 @@ from django_ledger.views.mixins import (
25
26
  )
26
27
 
27
28
 
28
- class LedgerModelModelViewQuerySetMixIn:
29
+ class LedgerModelModelBaseView(DjangoLedgerSecurityMixIn):
29
30
  queryset = None
30
31
 
31
32
  def get_queryset(self):
32
33
  if self.queryset is None:
33
- self.queryset = LedgerModel.objects.for_entity(
34
- entity_slug=self.kwargs['entity_slug'],
35
- user_model=self.request.user
36
- ).select_related('entity')
34
+ entity_model: EntityModel = self.get_authorized_entity_instance()
35
+ self.queryset = entity_model.get_ledgers()
37
36
  return self.queryset
38
37
 
39
38
 
40
- class LedgerModelListView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuerySetMixIn, ArchiveIndexView):
39
+ class LedgerModelListView(LedgerModelModelBaseView, ArchiveIndexView):
41
40
  allow_empty = True
42
41
  context_object_name = 'ledger_list'
43
42
  template_name = 'django_ledger/ledger/ledger_list.html'
@@ -91,7 +90,7 @@ class LedgerModelMonthListView(MonthArchiveView, LedgerModelListView):
91
90
  make_object_list = True
92
91
 
93
92
 
94
- class LedgerModelCreateView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuerySetMixIn, CreateView):
93
+ class LedgerModelCreateView(LedgerModelModelBaseView, CreateView):
95
94
  template_name = 'django_ledger/ledger/ledger_create.html'
96
95
  PAGE_TITLE = _('Create Ledger')
97
96
  extra_context = {
@@ -118,7 +117,7 @@ class LedgerModelCreateView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuery
118
117
  })
119
118
 
120
119
 
121
- class LedgerModelDetailView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuerySetMixIn, RedirectView):
120
+ class LedgerModelDetailView(LedgerModelModelBaseView, RedirectView):
122
121
 
123
122
  def get_redirect_url(self, *args, **kwargs):
124
123
  return reverse('django_ledger:je-list',
@@ -128,7 +127,7 @@ class LedgerModelDetailView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuery
128
127
  })
129
128
 
130
129
 
131
- class LedgerModelUpdateView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuerySetMixIn, UpdateView):
130
+ class LedgerModelUpdateView(LedgerModelModelBaseView, UpdateView):
132
131
  context_object_name = 'ledger'
133
132
  pk_url_kwarg = 'ledger_pk'
134
133
  template_name = 'django_ledger/ledger/ledger_update.html'
@@ -153,7 +152,7 @@ class LedgerModelUpdateView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuery
153
152
  })
154
153
 
155
154
 
156
- class LedgerModelDeleteView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuerySetMixIn, DeleteView):
155
+ class LedgerModelDeleteView(LedgerModelModelBaseView, DeleteView):
157
156
  template_name = 'django_ledger/ledger/ledger_delete.html'
158
157
  pk_url_kwarg = 'ledger_pk'
159
158
  context_object_name = 'ledger_model'
@@ -167,9 +166,8 @@ class LedgerModelDeleteView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuery
167
166
 
168
167
  # ACTIONS....
169
168
 
170
- class LedgerModelModelActionView(DjangoLedgerSecurityMixIn,
169
+ class LedgerModelModelActionView(LedgerModelModelBaseView,
171
170
  RedirectView,
172
- LedgerModelModelViewQuerySetMixIn,
173
171
  SingleObjectMixin):
174
172
  http_method_names = ['get']
175
173
  pk_url_kwarg = 'ledger_pk'
@@ -200,7 +198,7 @@ class LedgerModelModelActionView(DjangoLedgerSecurityMixIn,
200
198
 
201
199
 
202
200
  # Ledger Balance Sheet Views...
203
- class BaseLedgerModelBalanceSheetView(DjangoLedgerSecurityMixIn, RedirectView):
201
+ class BaseLedgerModelBalanceSheetView(LedgerModelModelBaseView, RedirectView):
204
202
 
205
203
  def get_redirect_url(self, *args, **kwargs):
206
204
  year = get_localdate().year
@@ -211,8 +209,7 @@ class BaseLedgerModelBalanceSheetView(DjangoLedgerSecurityMixIn, RedirectView):
211
209
  })
212
210
 
213
211
 
214
- class FiscalYearLedgerModelBalanceSheetView(DjangoLedgerSecurityMixIn,
215
- LedgerModelModelViewQuerySetMixIn,
212
+ class FiscalYearLedgerModelBalanceSheetView(LedgerModelModelBaseView,
216
213
  BaseDateNavigationUrlMixIn,
217
214
  EntityUnitMixIn,
218
215
  YearlyReportMixIn,
@@ -249,7 +246,7 @@ class DateLedgerModelBalanceSheetView(FiscalYearLedgerModelBalanceSheetView, Dat
249
246
 
250
247
 
251
248
  # Ledger Income Statement Views...
252
- class BaseLedgerIncomeStatementView(DjangoLedgerSecurityMixIn, RedirectView):
249
+ class BaseLedgerIncomeStatementView(LedgerModelModelBaseView, RedirectView):
253
250
 
254
251
  def get_redirect_url(self, *args, **kwargs):
255
252
  year = get_localdate().year
@@ -261,8 +258,7 @@ class BaseLedgerIncomeStatementView(DjangoLedgerSecurityMixIn, RedirectView):
261
258
  })
262
259
 
263
260
 
264
- class FiscalYearLedgerIncomeStatementView(DjangoLedgerSecurityMixIn,
265
- LedgerModelModelViewQuerySetMixIn,
261
+ class FiscalYearLedgerIncomeStatementView(LedgerModelModelBaseView,
266
262
  BaseDateNavigationUrlMixIn,
267
263
  EntityUnitMixIn,
268
264
  YearlyReportMixIn,
@@ -298,7 +294,7 @@ class DateLedgerIncomeStatementView(FiscalYearLedgerIncomeStatementView, DateRep
298
294
 
299
295
 
300
296
  # CASH FLOW STATEMENT ----
301
- class BaseLedgerModelCashFlowStatementRedirectView(DjangoLedgerSecurityMixIn, RedirectView):
297
+ class BaseLedgerModelCashFlowStatementRedirectView(LedgerModelModelBaseView, RedirectView):
302
298
 
303
299
  def get_redirect_url(self, *args, **kwargs):
304
300
  year = get_localdate().year
@@ -310,8 +306,7 @@ class BaseLedgerModelCashFlowStatementRedirectView(DjangoLedgerSecurityMixIn, Re
310
306
  })
311
307
 
312
308
 
313
- class FiscalYearLedgerModelCashFlowStatementView(DjangoLedgerSecurityMixIn,
314
- LedgerModelModelViewQuerySetMixIn,
309
+ class FiscalYearLedgerModelCashFlowStatementView(LedgerModelModelBaseView,
315
310
  BaseDateNavigationUrlMixIn,
316
311
  EntityUnitMixIn,
317
312
  YearlyReportMixIn,