aa-ledger 0.9.9__py3-none-any.whl → 0.9.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {aa_ledger-0.9.9.dist-info → aa_ledger-0.9.9.1.dist-info}/METADATA +1 -1
  2. {aa_ledger-0.9.9.dist-info → aa_ledger-0.9.9.1.dist-info}/RECORD +77 -77
  3. ledger/__init__.py +9 -9
  4. ledger/api/api_helper/billboard_helper.py +277 -277
  5. ledger/api/ledger/admin.py +289 -289
  6. ledger/app_settings.py +50 -50
  7. ledger/constants.py +5 -0
  8. ledger/decorators.py +92 -11
  9. ledger/helpers/alliance.py +353 -334
  10. ledger/helpers/character.py +260 -260
  11. ledger/helpers/core.py +565 -565
  12. ledger/helpers/corporation.py +455 -421
  13. ledger/helpers/etag.py +237 -237
  14. ledger/helpers/ref_type.py +475 -475
  15. ledger/locale/cs_CZ/LC_MESSAGES/django.po +942 -942
  16. ledger/locale/de/LC_MESSAGES/django.po +961 -961
  17. ledger/locale/django.pot +942 -942
  18. ledger/locale/es/LC_MESSAGES/django.po +943 -943
  19. ledger/locale/fr_FR/LC_MESSAGES/django.po +942 -942
  20. ledger/locale/it_IT/LC_MESSAGES/django.po +942 -942
  21. ledger/locale/ja/LC_MESSAGES/django.po +943 -943
  22. ledger/locale/ko_KR/LC_MESSAGES/django.po +942 -942
  23. ledger/locale/nl_NL/LC_MESSAGES/django.po +942 -942
  24. ledger/locale/pl_PL/LC_MESSAGES/django.po +942 -942
  25. ledger/locale/ru/LC_MESSAGES/django.po +945 -945
  26. ledger/locale/sk/LC_MESSAGES/django.po +944 -944
  27. ledger/locale/uk/LC_MESSAGES/django.po +946 -946
  28. ledger/locale/zh_Hans/LC_MESSAGES/django.po +943 -943
  29. ledger/managers/character_mining_manager.py +239 -239
  30. ledger/managers/character_planetary_manager.py +1 -1
  31. ledger/migrations/0016_characterminingledger_price_per_unit.py +21 -21
  32. ledger/models/characteraudit.py +496 -496
  33. ledger/static/ledger/css/cards.css +1 -1
  34. ledger/static/ledger/css/table.css +1 -1
  35. ledger/static/ledger/js/charts.js +221 -221
  36. ledger/static/ledger/js/planetary.js +143 -143
  37. ledger/tasks.py +442 -449
  38. ledger/templates/ledger/allyledger/admin/alliance_administration.html +46 -46
  39. ledger/templates/ledger/allyledger/admin/alliance_overview.html +108 -108
  40. ledger/templates/ledger/allyledger/alliance_ledger.html +86 -86
  41. ledger/templates/ledger/bundles/ally-administration-bundles.html +59 -59
  42. ledger/templates/ledger/bundles/char-administration-bundles.html +66 -66
  43. ledger/templates/ledger/bundles/character-ledger-bundles.html +66 -66
  44. ledger/templates/ledger/bundles/corp-administration-bundles.html +68 -68
  45. ledger/templates/ledger/bundles/corporation-ledger-bundles.html +75 -75
  46. ledger/templates/ledger/charledger/admin/character_administration.html +39 -39
  47. ledger/templates/ledger/charledger/admin/character_overview.html +106 -106
  48. ledger/templates/ledger/charledger/character_ledger.html +94 -94
  49. ledger/templates/ledger/charledger/planetary/planetary_ledger.html +54 -54
  50. ledger/templates/ledger/corpledger/admin/corporation_administration.html +39 -39
  51. ledger/templates/ledger/corpledger/admin/corporation_overview.html +108 -108
  52. ledger/templates/ledger/corpledger/corporation_ledger.html +129 -86
  53. ledger/templates/ledger/partials/administration/alliance.html +37 -37
  54. ledger/templates/ledger/partials/administration/alliance_corporations.html +58 -58
  55. ledger/templates/ledger/partials/administration/corporation_characters.html +34 -34
  56. ledger/templates/ledger/partials/information/daily.html +56 -56
  57. ledger/templates/ledger/partials/information/day.html +48 -48
  58. ledger/templates/ledger/partials/information/hourly.html +53 -53
  59. ledger/templates/ledger/partials/information/summary.html +88 -88
  60. ledger/templates/ledger/partials/information/view_character_content.html +35 -35
  61. ledger/templates/ledger/partials/table/char-ledger.html +85 -85
  62. ledger/templates/ledger/partials/table/corp-ledger.html +66 -66
  63. ledger/templates/ledger/partials/view/card.html +160 -160
  64. ledger/tests/test_decarators.py +102 -17
  65. ledger/tests/test_helpers/test_etag.py +149 -149
  66. ledger/tests/test_managers/test_character_mining_manager.py +54 -54
  67. ledger/tests/test_models/test_characterminingledger.py +107 -106
  68. ledger/tests/test_tasks.py +282 -282
  69. ledger/tests/test_templatetags.py +5 -2
  70. ledger/tests/test_views/test_access.py +852 -852
  71. ledger/tests/testdata/esi.json +1 -2
  72. ledger/tests/testdata/eveuniverse.json +391 -391
  73. ledger/urls.py +66 -21
  74. ledger/views/alliance/alliance_ledger.py +203 -203
  75. ledger/views/corporation/corporation_ledger.py +25 -9
  76. {aa_ledger-0.9.9.dist-info → aa_ledger-0.9.9.1.dist-info}/WHEEL +0 -0
  77. {aa_ledger-0.9.9.dist-info → aa_ledger-0.9.9.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,334 +1,353 @@
1
- """PvE Views"""
2
-
3
- # Standard Library
4
- import json
5
- from decimal import Decimal
6
-
7
- # Django
8
- from django.core.cache import cache
9
- from django.core.handlers.wsgi import WSGIRequest
10
- from django.db.models import DecimalField, F, Q, QuerySet, Sum
11
- from django.utils.translation import gettext as _
12
-
13
- # Alliance Auth
14
- from allianceauth.eveonline.models import EveAllianceInfo
15
- from allianceauth.services.hooks import get_extension_logger
16
-
17
- # Alliance Auth (External Libs)
18
- from app_utils.logging import LoggerAddTag
19
-
20
- # AA Ledger
21
- from ledger import __title__
22
- from ledger.app_settings import LEDGER_CACHE_ENABLED, LEDGER_CACHE_STALE
23
- from ledger.helpers.core import LedgerCore, LedgerEntity
24
- from ledger.helpers.ref_type import RefTypeManager
25
- from ledger.models.corporationaudit import (
26
- CorporationAudit,
27
- CorporationWalletJournalEntry,
28
- )
29
-
30
- logger = LoggerAddTag(get_extension_logger(__name__), __title__)
31
-
32
-
33
- class AllianceData(LedgerCore):
34
- """Class to hold alliance data for the ledger."""
35
-
36
- # pylint: disable=too-many-positional-arguments
37
- def __init__(
38
- self,
39
- request: WSGIRequest,
40
- alliance: EveAllianceInfo,
41
- year=None,
42
- month=None,
43
- day=None,
44
- ):
45
- LedgerCore.__init__(self, year, month, day)
46
- self.request = request
47
- self.alliance = alliance
48
- self.corporations = CorporationAudit.objects.filter(
49
- corporation__alliance__alliance_id=self.alliance.alliance_id
50
- ).values_list("corporation__corporation_id", flat=True)
51
- # Evaluate the existing years for the view
52
- self.existing_years = (
53
- CorporationWalletJournalEntry.objects.filter(
54
- division__corporation__corporation__alliance__alliance_id=self.alliance.alliance_id
55
- )
56
- .exclude(date__year__isnull=True)
57
- .values_list("date__year", flat=True)
58
- .order_by("-date__year")
59
- .distinct()
60
- )
61
- self.auth_char_ids = self.auth_character_ids
62
-
63
- def setup_ledger(self, entity: LedgerEntity = None):
64
- """Setup the Ledger Data for the Corporation."""
65
- if entity is not None:
66
- if (
67
- self.request.GET.get("all", False)
68
- and entity.entity_id == self.alliance.alliance_id
69
- ):
70
- self.journal = CorporationWalletJournalEntry.objects.filter(
71
- self.filter_date,
72
- division__corporation__corporation__alliance__alliance_id=self.alliance.alliance_id,
73
- )
74
- else:
75
- self.journal = CorporationWalletJournalEntry.objects.filter(
76
- self.filter_date,
77
- division__corporation__corporation__corporation_id=entity.entity_id,
78
- )
79
-
80
- self.entities = self.get_all_entities(self.journal)
81
-
82
- self.journal = self.journal.filter(
83
- Q(first_party_id__in=self.entities)
84
- | Q(second_party_id__in=self.entities)
85
- ).exclude(
86
- Q(ref_type="corporation_account_withdrawal")
87
- & Q(first_party_id=F("second_party_id"))
88
- )
89
- else:
90
- self.journal = CorporationWalletJournalEntry.objects.filter(
91
- self.filter_date,
92
- division__corporation__corporation__alliance__alliance_id=self.alliance.alliance_id,
93
- ).exclude(
94
- Q(ref_type="corporation_account_withdrawal")
95
- & Q(first_party_id=F("second_party_id"))
96
- )
97
-
98
- self.entities = self.get_all_entities(self.journal)
99
-
100
- def get_all_entities(
101
- self, journal: QuerySet[CorporationWalletJournalEntry]
102
- ) -> list[int]:
103
- """Get all entities in the alliance."""
104
- entities_ids = set(journal.values_list("second_party_id", flat=True)) | set(
105
- journal.values_list("first_party_id", flat=True)
106
- )
107
- return list(entities_ids)
108
-
109
- # pylint: disable=duplicate-code
110
- def create_entity_data(
111
- self,
112
- entity: LedgerEntity,
113
- ) -> dict:
114
- """Create the URL for entity details based on the view type."""
115
- used_pks = set()
116
- bounty = Decimal(0)
117
- ess = Decimal(0)
118
- miscellaneous = Decimal(0)
119
- costs = Decimal(0)
120
-
121
- for pk, rows in list(self.entries.items()):
122
- for row in rows:
123
- if (
124
- row["division__corporation__corporation__corporation_id"]
125
- == entity.entity_id
126
- ):
127
- if RefTypeManager.special_cases(
128
- row, ids=[], account_char_ids=self.auth_char_ids
129
- ):
130
- continue
131
- bounty += row.get("bounty") or Decimal(0)
132
- ess += row.get("ess") or Decimal(0)
133
- miscellaneous += row.get("miscellaneous") or Decimal(0)
134
- costs += row.get("costs") or Decimal(0)
135
- used_pks.add(pk)
136
-
137
- # Remove Used Pks from Entries
138
- # This is to prevent the entries from being used in the future
139
- for pk in used_pks:
140
- self.entries.pop(pk, None)
141
-
142
- misc = miscellaneous
143
- total = sum([bounty, ess, miscellaneous, costs])
144
-
145
- if total == 0:
146
- return None
147
-
148
- entity_ledger_info = {
149
- "entity": entity,
150
- "ledger": {
151
- "bounty": bounty,
152
- "ess": ess,
153
- "miscellaneous": misc,
154
- "costs": costs,
155
- "total": total,
156
- },
157
- "type": entity.type,
158
- }
159
-
160
- return entity_ledger_info
161
-
162
- # pylint: disable=duplicate-code
163
- def generate_ledger_data(self) -> dict:
164
- """Generate the ledger data for the alliance."""
165
- ledger = False
166
- finished_entities = False
167
-
168
- self.setup_ledger()
169
-
170
- journal = self.journal.values(
171
- "first_party_id",
172
- "second_party_id",
173
- "pk",
174
- "ref_type",
175
- "division__corporation__corporation__corporation_id",
176
- ).annotate(
177
- bounty=Sum(
178
- "amount",
179
- filter=Q(ref_type__in=RefTypeManager.BOUNTY_PRIZES),
180
- output_field=DecimalField(),
181
- ),
182
- ess=Sum(
183
- "amount",
184
- filter=Q(ref_type__in=RefTypeManager.ESS_TRANSFER),
185
- output_field=DecimalField(),
186
- ),
187
- costs=Sum(
188
- "amount",
189
- filter=Q(ref_type__in=RefTypeManager.all_ref_types(), amount__lt=0),
190
- output_field=DecimalField(),
191
- ),
192
- miscellaneous=Sum(
193
- "amount",
194
- filter=Q(ref_type__in=RefTypeManager.all_ref_types(), amount__gt=0),
195
- output_field=DecimalField(),
196
- ),
197
- )
198
-
199
- # Build up the journal Cache
200
- ledger_hash = self._get_ledger_journal_hash(self.journal.values_list("pk"))
201
- ledger_header = self._get_ledger_header(
202
- self.alliance.alliance_id, self.year, self.month, self.day
203
- )
204
- cache_header = cache.get(
205
- ledger_header,
206
- )
207
- logger.debug(
208
- f"Ledger Header: {ledger_header}, Cache Header: {cache_header}, Journal Hash: {ledger_hash}"
209
- )
210
-
211
- # Check if the journal is up to date
212
- journal_up_to_date = cache_header == ledger_hash
213
- ledger_key = self._build_ledger_cache_key(ledger_header)
214
-
215
- # Check if we have newest cached version of the ledger
216
- if journal_up_to_date and LEDGER_CACHE_ENABLED:
217
- ledger = cache.get(f"{ledger_key}-data", False)
218
- finished_entities = cache.get(f"{ledger_key}-finished_entities", False)
219
-
220
- if finished_entities is False or ledger is False:
221
- ledger = []
222
- finished_entities = set()
223
-
224
- # Build the entries from the journal
225
- self.entries = {}
226
- for row in journal:
227
- self.entries.setdefault(row["pk"], []).append(row)
228
-
229
- # Build Data for each corporation
230
- for corporation_id in self.corporations:
231
- # Create Details URL for the entity
232
- details_url = self.create_url(
233
- viewname="alliance_details",
234
- alliance_id=self.alliance.alliance_id,
235
- entity_id=corporation_id,
236
- )
237
-
238
- # Create the LedgerEntity object for the entity
239
- entity_obj = LedgerEntity(
240
- entity_id=corporation_id,
241
- details_url=details_url,
242
- )
243
-
244
- corp_data = self.create_entity_data(
245
- entity=entity_obj,
246
- )
247
-
248
- if corp_data is None:
249
- continue
250
-
251
- ledger.append(corp_data)
252
- finished_entities.add(corporation_id)
253
-
254
- # Create the billboard data
255
- self.create_rattingbar(list(finished_entities))
256
- self.create_chord(ledger)
257
-
258
- # Build the context for the ledger view
259
- context = self._build_context(ledger=ledger)
260
-
261
- cache.set(key=f"{ledger_key}-data", value=ledger, timeout=LEDGER_CACHE_STALE)
262
- cache.set(
263
- key=f"{ledger_key}-finished_entities",
264
- value=finished_entities,
265
- timeout=LEDGER_CACHE_STALE,
266
- )
267
- cache.set(
268
- key=self._get_ledger_header(
269
- self.alliance.alliance_id, self.year, self.month, self.day
270
- ),
271
- value=ledger_hash,
272
- timeout=None, # Cache forever until the journal changes
273
- )
274
- return context
275
-
276
- def _build_context(self, ledger):
277
- """Build the context for the ledger view."""
278
- return {
279
- "title": f"Alliance Ledger - {self.alliance.alliance_name}",
280
- "alliance_id": self.alliance.alliance_id,
281
- "billboard": json.dumps(self.billboard.dict.asdict()),
282
- "ledger": ledger,
283
- "years": list(self.existing_years),
284
- "totals": self._calculate_totals(ledger),
285
- "view": self.create_view_data(
286
- viewname="alliance_details",
287
- alliance_id=self.alliance.alliance_id,
288
- entity_id=self.alliance.alliance_id,
289
- ),
290
- }
291
-
292
- def create_rattingbar(self, entities_ids: list = None):
293
- """Create the ratting bar for the view."""
294
- if not entities_ids:
295
- return
296
-
297
- rattingbar_timeline = self.billboard.create_timeline(self.journal)
298
- rattingbar = (
299
- rattingbar_timeline.annotate_bounty_income()
300
- .annotate_ess_income()
301
- .annotate_miscellaneous()
302
- )
303
- self.billboard.create_or_update_results(rattingbar)
304
- self._build_xy_chart(title=_("Ratting Bar"))
305
-
306
- def create_chord(self, ledger_data: list[dict]):
307
- """Create the chord chart for the view."""
308
- if not ledger_data:
309
- return
310
-
311
- for entry in ledger_data:
312
- entity_name = entry["entity"].entity_name
313
- ledger = entry["ledger"]
314
- self.billboard.chord_add_data(
315
- chord_from=entity_name,
316
- chord_to=_("Bounty (Wallet)"),
317
- value=ledger.get("bounty", 0),
318
- )
319
- self.billboard.chord_add_data(
320
- chord_from=entity_name,
321
- chord_to=_("ESS (Wallet)"),
322
- value=ledger.get("ess", 0),
323
- )
324
- self.billboard.chord_add_data(
325
- chord_from=entity_name,
326
- chord_to=_("Costs (Wallet)"),
327
- value=abs(ledger.get("costs", 0)),
328
- )
329
- self.billboard.chord_add_data(
330
- chord_from=entity_name,
331
- chord_to=_("Miscellaneous (Wallet)"),
332
- value=abs(ledger.get("miscellaneous", 0)),
333
- )
334
- self.billboard.chord_handle_overflow()
1
+ """PvE Views"""
2
+
3
+ # Standard Library
4
+ import json
5
+ from decimal import Decimal
6
+
7
+ # Django
8
+ from django.core.cache import cache
9
+ from django.core.handlers.wsgi import WSGIRequest
10
+ from django.db.models import DecimalField, F, Q, QuerySet, Sum
11
+ from django.utils.translation import gettext as _
12
+
13
+ # Alliance Auth
14
+ from allianceauth.eveonline.models import EveAllianceInfo
15
+ from allianceauth.services.hooks import get_extension_logger
16
+
17
+ # Alliance Auth (External Libs)
18
+ from app_utils.logging import LoggerAddTag
19
+
20
+ # AA Ledger
21
+ from ledger import __title__
22
+ from ledger.app_settings import LEDGER_CACHE_ENABLED, LEDGER_CACHE_STALE
23
+ from ledger.helpers.core import LedgerCore, LedgerEntity
24
+ from ledger.helpers.ref_type import RefTypeManager
25
+ from ledger.models.corporationaudit import (
26
+ CorporationAudit,
27
+ CorporationWalletJournalEntry,
28
+ )
29
+
30
+ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
31
+
32
+
33
+ class AllianceData(LedgerCore):
34
+ """Class to hold alliance data for the ledger."""
35
+
36
+ # pylint: disable=too-many-positional-arguments
37
+ def __init__(
38
+ self,
39
+ request: WSGIRequest,
40
+ alliance: EveAllianceInfo,
41
+ year=None,
42
+ month=None,
43
+ day=None,
44
+ ):
45
+ LedgerCore.__init__(self, year, month, day)
46
+ self.request = request
47
+ self.alliance = alliance
48
+ self.corporations = CorporationAudit.objects.filter(
49
+ corporation__alliance__alliance_id=self.alliance.alliance_id
50
+ ).values_list("corporation__corporation_id", flat=True)
51
+ # Evaluate the existing years for the view
52
+ self.existing_years = (
53
+ CorporationWalletJournalEntry.objects.filter(
54
+ division__corporation__corporation__alliance__alliance_id=self.alliance.alliance_id
55
+ )
56
+ .exclude(date__year__isnull=True)
57
+ .values_list("date__year", flat=True)
58
+ .order_by("-date__year")
59
+ .distinct()
60
+ )
61
+ self.auth_char_ids = self.auth_character_ids
62
+
63
+ def setup_ledger(self, entity: LedgerEntity = None):
64
+ """Setup the Ledger Data for the Corporation."""
65
+ alliance_id = self.alliance.alliance_id
66
+
67
+ # Base queryset filtered by date and alliance
68
+ base_qs = CorporationWalletJournalEntry.objects.filter(
69
+ self.filter_date,
70
+ division__corporation__corporation__alliance__alliance_id=alliance_id,
71
+ )
72
+
73
+ # No entity specified: show all entries for the alliance (preserve existing exclusion)
74
+ if entity is None:
75
+ self.journal = base_qs.exclude(
76
+ Q(ref_type="corporation_account_withdrawal")
77
+ & Q(first_party_id=F("second_party_id"))
78
+ )
79
+ # Prepare auxiliary data used by the view
80
+ self.entities = self.get_all_entities(self.journal)
81
+ return
82
+
83
+ # If the entity is the alliance itself and "all" is set, show all alliance entries
84
+ if self.request.GET.get("all", False) and entity.entity_id == alliance_id:
85
+ self.journal = base_qs.exclude(
86
+ Q(ref_type="corporation_account_withdrawal")
87
+ & Q(first_party_id=F("second_party_id"))
88
+ )
89
+ self.entities = self.get_all_entities(self.journal)
90
+ return
91
+
92
+ # Regular entity filtering: include any rows where the entity is a first or second party
93
+ entity_ids = entity.get_alts_ids_or_self()
94
+ qs = base_qs.filter(
95
+ Q(first_party_id__in=entity_ids) | Q(second_party_id__in=entity_ids)
96
+ )
97
+ qs = qs.exclude(
98
+ Q(ref_type="corporation_account_withdrawal")
99
+ & Q(first_party_id=F("second_party_id"))
100
+ )
101
+
102
+ # If entity represents a corporation or alliance, exclude auth account character IDs
103
+ # that are not part of the current entity to avoid double counting
104
+ if entity.type in ["alliance", "corporation"]:
105
+ exclude_ids = self.auth_char_ids - set(entity_ids)
106
+ qs = qs.exclude(
107
+ Q(first_party_id__in=exclude_ids) | Q(second_party_id__in=exclude_ids)
108
+ )
109
+
110
+ self.journal = qs
111
+ self.entities = self.get_all_entities(self.journal)
112
+
113
+ def get_all_entities(
114
+ self, journal: QuerySet[CorporationWalletJournalEntry]
115
+ ) -> list[int]:
116
+ """Get all entities in the alliance."""
117
+ entities_ids = set(journal.values_list("second_party_id", flat=True)) | set(
118
+ journal.values_list("first_party_id", flat=True)
119
+ )
120
+ return list(entities_ids)
121
+
122
+ # pylint: disable=duplicate-code
123
+ def create_entity_data(
124
+ self,
125
+ entity: LedgerEntity,
126
+ ) -> dict:
127
+ """Create the URL for entity details based on the view type."""
128
+ used_pks = set()
129
+ bounty = Decimal(0)
130
+ ess = Decimal(0)
131
+ miscellaneous = Decimal(0)
132
+ costs = Decimal(0)
133
+
134
+ for pk, rows in list(self.entries.items()):
135
+ for row in rows:
136
+ if (
137
+ row["division__corporation__corporation__corporation_id"]
138
+ == entity.entity_id
139
+ ):
140
+ if RefTypeManager.special_cases(
141
+ row, ids=[], account_char_ids=self.auth_char_ids
142
+ ):
143
+ continue
144
+ bounty += row.get("bounty") or Decimal(0)
145
+ ess += row.get("ess") or Decimal(0)
146
+ miscellaneous += row.get("miscellaneous") or Decimal(0)
147
+ costs += row.get("costs") or Decimal(0)
148
+ used_pks.add(pk)
149
+
150
+ # Remove Used Pks from Entries
151
+ # This is to prevent the entries from being used in the future
152
+ for pk in used_pks:
153
+ self.entries.pop(pk, None)
154
+
155
+ misc = miscellaneous
156
+ total = sum([bounty, ess, miscellaneous, costs])
157
+
158
+ if total == 0:
159
+ return None
160
+
161
+ entity_ledger_info = {
162
+ "entity": entity,
163
+ "ledger": {
164
+ "bounty": bounty,
165
+ "ess": ess,
166
+ "miscellaneous": misc,
167
+ "costs": costs,
168
+ "total": total,
169
+ },
170
+ "type": entity.type,
171
+ }
172
+
173
+ return entity_ledger_info
174
+
175
+ # pylint: disable=duplicate-code
176
+ def generate_ledger_data(self) -> dict:
177
+ """Generate the ledger data for the alliance."""
178
+ ledger = False
179
+ finished_entities = False
180
+
181
+ self.setup_ledger()
182
+
183
+ journal = self.journal.values(
184
+ "first_party_id",
185
+ "second_party_id",
186
+ "pk",
187
+ "ref_type",
188
+ "division__corporation__corporation__corporation_id",
189
+ ).annotate(
190
+ bounty=Sum(
191
+ "amount",
192
+ filter=Q(ref_type__in=RefTypeManager.BOUNTY_PRIZES),
193
+ output_field=DecimalField(),
194
+ ),
195
+ ess=Sum(
196
+ "amount",
197
+ filter=Q(ref_type__in=RefTypeManager.ESS_TRANSFER),
198
+ output_field=DecimalField(),
199
+ ),
200
+ costs=Sum(
201
+ "amount",
202
+ filter=Q(ref_type__in=RefTypeManager.all_ref_types(), amount__lt=0),
203
+ output_field=DecimalField(),
204
+ ),
205
+ miscellaneous=Sum(
206
+ "amount",
207
+ filter=Q(ref_type__in=RefTypeManager.all_ref_types(), amount__gt=0),
208
+ output_field=DecimalField(),
209
+ ),
210
+ )
211
+
212
+ # Build up the journal Cache
213
+ ledger_hash = self._get_ledger_journal_hash(self.journal.values_list("pk"))
214
+ ledger_header = self._get_ledger_header(
215
+ ledger_args=f"{self.alliance.alliance_id}",
216
+ year=self.year,
217
+ month=self.month,
218
+ day=self.day,
219
+ )
220
+ cache_header = cache.get(
221
+ ledger_header,
222
+ )
223
+ logger.debug(
224
+ f"Ledger Header: {ledger_header}, Cache Header: {cache_header}, Journal Hash: {ledger_hash}"
225
+ )
226
+
227
+ # Check if the journal is up to date
228
+ journal_up_to_date = cache_header == ledger_hash
229
+ ledger_key = self._build_ledger_cache_key(ledger_header)
230
+
231
+ # Check if we have newest cached version of the ledger
232
+ if journal_up_to_date and LEDGER_CACHE_ENABLED:
233
+ ledger = cache.get(f"{ledger_key}-data", False)
234
+ finished_entities = cache.get(f"{ledger_key}-finished_entities", False)
235
+
236
+ if finished_entities is False or ledger is False:
237
+ ledger = []
238
+ finished_entities = set()
239
+
240
+ # Build the entries from the journal
241
+ self.entries = {}
242
+ for row in journal:
243
+ self.entries.setdefault(row["pk"], []).append(row)
244
+
245
+ # Build Data for each corporation
246
+ for corporation_id in self.corporations:
247
+ # Create Details URL for the entity
248
+ details_url = self.create_url(
249
+ viewname="alliance_details",
250
+ alliance_id=self.alliance.alliance_id,
251
+ entity_id=corporation_id,
252
+ )
253
+
254
+ # Create the LedgerEntity object for the entity
255
+ entity_obj = LedgerEntity(
256
+ entity_id=corporation_id,
257
+ details_url=details_url,
258
+ )
259
+
260
+ corp_data = self.create_entity_data(
261
+ entity=entity_obj,
262
+ )
263
+
264
+ if corp_data is None:
265
+ continue
266
+
267
+ ledger.append(corp_data)
268
+ finished_entities.add(corporation_id)
269
+
270
+ # Create the billboard data
271
+ self.create_rattingbar(list(finished_entities))
272
+ self.create_chord(ledger)
273
+
274
+ # Build the context for the ledger view
275
+ context = self._build_context(ledger=ledger)
276
+
277
+ cache.set(key=f"{ledger_key}-data", value=ledger, timeout=LEDGER_CACHE_STALE)
278
+ cache.set(
279
+ key=f"{ledger_key}-finished_entities",
280
+ value=finished_entities,
281
+ timeout=LEDGER_CACHE_STALE,
282
+ )
283
+ cache.set(
284
+ key=self._get_ledger_header(
285
+ ledger_args=f"{self.alliance.alliance_id}",
286
+ year=self.year,
287
+ month=self.month,
288
+ day=self.day,
289
+ ),
290
+ value=ledger_hash,
291
+ timeout=None, # Cache forever until the journal changes
292
+ )
293
+ return context
294
+
295
+ def _build_context(self, ledger):
296
+ """Build the context for the ledger view."""
297
+ return {
298
+ "title": f"Alliance Ledger - {self.alliance.alliance_name}",
299
+ "alliance_id": self.alliance.alliance_id,
300
+ "billboard": json.dumps(self.billboard.dict.asdict()),
301
+ "ledger": ledger,
302
+ "years": list(self.existing_years),
303
+ "totals": self._calculate_totals(ledger),
304
+ "view": self.create_view_data(
305
+ viewname="alliance_details",
306
+ alliance_id=self.alliance.alliance_id,
307
+ entity_id=self.alliance.alliance_id,
308
+ ),
309
+ }
310
+
311
+ def create_rattingbar(self, entities_ids: list = None):
312
+ """Create the ratting bar for the view."""
313
+ if not entities_ids:
314
+ return
315
+
316
+ rattingbar_timeline = self.billboard.create_timeline(self.journal)
317
+ rattingbar = (
318
+ rattingbar_timeline.annotate_bounty_income()
319
+ .annotate_ess_income()
320
+ .annotate_miscellaneous()
321
+ )
322
+ self.billboard.create_or_update_results(rattingbar)
323
+ self._build_xy_chart(title=_("Ratting Bar"))
324
+
325
+ def create_chord(self, ledger_data: list[dict]):
326
+ """Create the chord chart for the view."""
327
+ if not ledger_data:
328
+ return
329
+
330
+ for entry in ledger_data:
331
+ entity_name = entry["entity"].entity_name
332
+ ledger = entry["ledger"]
333
+ self.billboard.chord_add_data(
334
+ chord_from=entity_name,
335
+ chord_to=_("Bounty (Wallet)"),
336
+ value=ledger.get("bounty", 0),
337
+ )
338
+ self.billboard.chord_add_data(
339
+ chord_from=entity_name,
340
+ chord_to=_("ESS (Wallet)"),
341
+ value=ledger.get("ess", 0),
342
+ )
343
+ self.billboard.chord_add_data(
344
+ chord_from=entity_name,
345
+ chord_to=_("Costs (Wallet)"),
346
+ value=abs(ledger.get("costs", 0)),
347
+ )
348
+ self.billboard.chord_add_data(
349
+ chord_from=entity_name,
350
+ chord_to=_("Miscellaneous (Wallet)"),
351
+ value=abs(ledger.get("miscellaneous", 0)),
352
+ )
353
+ self.billboard.chord_handle_overflow()