django-ledger 0.5.5.5__py3-none-any.whl → 0.5.6.0__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/entity.py +16 -2
- django_ledger/admin/ledger.py +2 -1
- django_ledger/forms/entity.py +4 -12
- django_ledger/forms/ledger.py +19 -0
- django_ledger/forms/transactions.py +1 -1
- django_ledger/io/__init__.py +4 -1
- django_ledger/io/{io_mixin.py → io_core.py} +49 -28
- django_ledger/io/io_digest.py +7 -0
- django_ledger/io/{data_generator.py → io_generator.py} +51 -8
- django_ledger/io/io_library.py +317 -0
- django_ledger/io/{io_context.py → io_middleware.py} +16 -9
- django_ledger/migrations/0001_initial.py +413 -132
- django_ledger/migrations/0014_ledgermodel_ledger_xid_and_more.py +22 -0
- django_ledger/models/accounts.py +8 -7
- django_ledger/models/bank_account.py +12 -11
- django_ledger/models/bill.py +5 -9
- django_ledger/models/closing_entry.py +14 -14
- django_ledger/models/coa.py +1 -1
- django_ledger/models/customer.py +5 -11
- django_ledger/models/data_import.py +12 -6
- django_ledger/models/entity.py +88 -10
- django_ledger/models/estimate.py +12 -9
- django_ledger/models/invoice.py +10 -4
- django_ledger/models/items.py +11 -6
- django_ledger/models/journal_entry.py +6 -13
- django_ledger/models/ledger.py +65 -15
- django_ledger/models/mixins.py +2 -3
- django_ledger/models/purchase_order.py +11 -7
- django_ledger/models/transactions.py +3 -1
- django_ledger/models/unit.py +13 -14
- django_ledger/models/vendor.py +12 -11
- django_ledger/templates/django_ledger/journal_entry/je_list.html +3 -0
- django_ledger/templatetags/django_ledger.py +1 -1
- django_ledger/tests/base.py +1 -1
- django_ledger/urls/ledger.py +3 -0
- django_ledger/views/entity.py +9 -3
- django_ledger/views/ledger.py +14 -7
- django_ledger/views/mixins.py +9 -1
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/METADATA +8 -8
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/RECORD +45 -43
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/AUTHORS.md +0 -0
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/LICENSE +0 -0
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/WHEEL +0 -0
- {django_ledger-0.5.5.5.dist-info → django_ledger-0.5.6.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
|
+
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
|
+
|
|
5
|
+
Contributions to this module:
|
|
6
|
+
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
+
"""
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from decimal import Decimal
|
|
11
|
+
from itertools import chain
|
|
12
|
+
from typing import Union, Dict, Callable, Optional
|
|
13
|
+
from uuid import UUID
|
|
14
|
+
from datetime import date, datetime
|
|
15
|
+
|
|
16
|
+
from django.core.exceptions import ValidationError
|
|
17
|
+
from django.db.models import Q
|
|
18
|
+
from django.utils.timezone import localtime
|
|
19
|
+
from django.utils.translation import gettext_lazy as _
|
|
20
|
+
|
|
21
|
+
from django_ledger.models.accounts import AccountModel, AccountModelQuerySet, CREDIT, DEBIT
|
|
22
|
+
from django_ledger.models.coa import ChartOfAccountModel
|
|
23
|
+
from django_ledger.models.entity import EntityModel
|
|
24
|
+
from django_ledger.models.ledger import LedgerModel, LedgerModelQuerySet
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class TransactionInstructionItem:
|
|
29
|
+
"""
|
|
30
|
+
A class to represent a transaction instruction used during the development of transaction blueprints.
|
|
31
|
+
|
|
32
|
+
Attributes
|
|
33
|
+
----------
|
|
34
|
+
account_code: str
|
|
35
|
+
The account code of the AccountModel as a String.
|
|
36
|
+
amount: Decimal
|
|
37
|
+
The transaction amount as a Decimal value. Will be rounded to the nearest decimal place.
|
|
38
|
+
tx_type: str
|
|
39
|
+
A choice of 'debit' or 'credit' transaction.
|
|
40
|
+
description: str
|
|
41
|
+
Description of the transaction.
|
|
42
|
+
account_model: AccountModel
|
|
43
|
+
The resolved account model for the transaction. Not to be modified. Defaults to None.
|
|
44
|
+
"""
|
|
45
|
+
account_code: str
|
|
46
|
+
amount: Union[Decimal, float]
|
|
47
|
+
tx_type: str
|
|
48
|
+
description: Optional[str]
|
|
49
|
+
account_model: Optional[AccountModel] = None
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> Dict:
|
|
52
|
+
return {
|
|
53
|
+
'account': self.account_model,
|
|
54
|
+
'amount': self.amount,
|
|
55
|
+
'tx_type': self.tx_type,
|
|
56
|
+
'description': self.description
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IOCursorValidationError(ValidationError):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class IOCursor:
|
|
65
|
+
|
|
66
|
+
def __init__(self,
|
|
67
|
+
io_library,
|
|
68
|
+
entity_model: EntityModel,
|
|
69
|
+
user_model,
|
|
70
|
+
coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None):
|
|
71
|
+
self.IO_LIBRARY = io_library
|
|
72
|
+
self.ENTITY_MODEL = entity_model
|
|
73
|
+
self.USER_MODEL = user_model
|
|
74
|
+
self.COA_MODEL = coa_model
|
|
75
|
+
self.__COMMITTED: bool = False
|
|
76
|
+
self.blueprints = defaultdict(list)
|
|
77
|
+
self.ledger_model_qs: Optional[LedgerModelQuerySet] = None
|
|
78
|
+
self.account_model_qs: Optional[AccountModelQuerySet] = None
|
|
79
|
+
self.ledger_map = dict()
|
|
80
|
+
self.commit_plan = dict()
|
|
81
|
+
self.instructions = None
|
|
82
|
+
|
|
83
|
+
def get_ledger_model_qs(self) -> LedgerModelQuerySet:
|
|
84
|
+
return LedgerModel.objects.for_entity(
|
|
85
|
+
self.ENTITY_MODEL,
|
|
86
|
+
self.USER_MODEL
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def get_account_model_qs(self) -> AccountModelQuerySet:
|
|
90
|
+
return self.ENTITY_MODEL.get_coa_accounts(
|
|
91
|
+
coa_model=self.COA_MODEL
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def resolve_account_model_qs(self, codes):
|
|
95
|
+
if self.account_model_qs is None:
|
|
96
|
+
# codes = self.get_account_codes()
|
|
97
|
+
qs = self.get_account_model_qs()
|
|
98
|
+
qs = qs.filter(code__in=codes)
|
|
99
|
+
self.account_model_qs = qs
|
|
100
|
+
return self.account_model_qs
|
|
101
|
+
|
|
102
|
+
def resolve_ledger_model_qs(self):
|
|
103
|
+
if self.ledger_model_qs is None:
|
|
104
|
+
qs = self.get_ledger_model_qs()
|
|
105
|
+
by_uuid = [k for k in self.blueprints.keys() if isinstance(k, UUID)]
|
|
106
|
+
by_xid = [k for k in self.blueprints.keys() if isinstance(k, str)]
|
|
107
|
+
self.ledger_model_qs = qs.filter(
|
|
108
|
+
Q(uuid__in=by_uuid) | Q(ledger_xid__in=by_xid)
|
|
109
|
+
)
|
|
110
|
+
return self.ledger_model_qs
|
|
111
|
+
|
|
112
|
+
def dispatch(self,
|
|
113
|
+
name,
|
|
114
|
+
ledger_model: Optional[Union[str, LedgerModel, UUID]] = None,
|
|
115
|
+
**kwargs):
|
|
116
|
+
|
|
117
|
+
if not isinstance(ledger_model, (str, UUID, LedgerModel)):
|
|
118
|
+
raise IOCursorValidationError(
|
|
119
|
+
message=_('Ledger Model must be a string or UUID or LedgerModel')
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if isinstance(ledger_model, LedgerModel):
|
|
123
|
+
self.ENTITY_MODEL.validate_ledger_model_for_entity(ledger_model)
|
|
124
|
+
|
|
125
|
+
blueprint_gen = self.IO_LIBRARY.get_blueprint(name)
|
|
126
|
+
blueprint = blueprint_gen(**kwargs)
|
|
127
|
+
self.blueprints[ledger_model].append(blueprint)
|
|
128
|
+
|
|
129
|
+
def compile_instructions(self):
|
|
130
|
+
|
|
131
|
+
if self.instructions is None:
|
|
132
|
+
instructions = {
|
|
133
|
+
ledger_model: list(chain.from_iterable(
|
|
134
|
+
io_blueprint.registry for io_blueprint in instructions
|
|
135
|
+
)) for ledger_model, instructions in self.commit_plan.items()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for ledger_model, txs in instructions.items():
|
|
139
|
+
total_credits = sum(t.amount for t in txs if t.tx_type == CREDIT)
|
|
140
|
+
total_debits = sum(t.amount for t in txs if t.tx_type == DEBIT)
|
|
141
|
+
|
|
142
|
+
# print("{} credits, {} debits".format(total_credits, total_debits))
|
|
143
|
+
|
|
144
|
+
if total_credits != total_debits:
|
|
145
|
+
raise IOCursorValidationError(
|
|
146
|
+
message=_('Total transactions Credits and Debits must equal: ')
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
self.instructions = instructions
|
|
150
|
+
return self.instructions
|
|
151
|
+
|
|
152
|
+
def is_committed(self) -> bool:
|
|
153
|
+
return self.__COMMITTED
|
|
154
|
+
|
|
155
|
+
def commit(self,
|
|
156
|
+
je_timestamp: Optional[Union[datetime, date, str]] = None,
|
|
157
|
+
post_new_ledgers: bool = False,
|
|
158
|
+
post_journal_entries: bool = False):
|
|
159
|
+
if self.is_committed():
|
|
160
|
+
raise IOCursorValidationError(
|
|
161
|
+
message=_('Transactions already committed')
|
|
162
|
+
)
|
|
163
|
+
qs = self.resolve_ledger_model_qs()
|
|
164
|
+
self.ledger_map = {l.ledger_xid: l for l in qs if l.ledger_xid} | {l.uuid: l for l in qs}
|
|
165
|
+
|
|
166
|
+
# checks for any locked ledgers...
|
|
167
|
+
for k, ledger_model in self.ledger_map.items():
|
|
168
|
+
if ledger_model.is_locked():
|
|
169
|
+
raise IOCursorValidationError(
|
|
170
|
+
message=_(f'Cannot transact on a locked ledger: {ledger_model}')
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
for k, txs in self.blueprints.items():
|
|
174
|
+
if k is None:
|
|
175
|
+
|
|
176
|
+
# no specified xid, ledger or UUID... create one...
|
|
177
|
+
self.commit_plan[
|
|
178
|
+
self.ENTITY_MODEL.create_ledger(
|
|
179
|
+
name='Blueprint Commitment',
|
|
180
|
+
commit=False,
|
|
181
|
+
posted=post_new_ledgers
|
|
182
|
+
)
|
|
183
|
+
] = txs
|
|
184
|
+
|
|
185
|
+
elif isinstance(k, str):
|
|
186
|
+
try:
|
|
187
|
+
# ledger with xid already exists...
|
|
188
|
+
self.commit_plan[self.ledger_map[k]] = txs
|
|
189
|
+
except KeyError:
|
|
190
|
+
# create ledger with xid provided...
|
|
191
|
+
self.commit_plan[
|
|
192
|
+
self.ENTITY_MODEL.create_ledger(
|
|
193
|
+
name=f'Blueprint Commitment {k}',
|
|
194
|
+
ledger_xid=k,
|
|
195
|
+
commit=False,
|
|
196
|
+
posted=post_new_ledgers
|
|
197
|
+
)
|
|
198
|
+
] = txs
|
|
199
|
+
elif isinstance(k, LedgerModel):
|
|
200
|
+
self.commit_plan[k] = txs
|
|
201
|
+
|
|
202
|
+
else:
|
|
203
|
+
raise IOLibraryError('Unsupported ledger of type {x}'.format(x=type(k)))
|
|
204
|
+
|
|
205
|
+
instructions = self.compile_instructions()
|
|
206
|
+
account_codes = set(tx.account_code for tx in chain.from_iterable(tr for _, tr in instructions.items()))
|
|
207
|
+
account_models = {acc.code: acc for acc in self.resolve_account_model_qs(codes=account_codes)}
|
|
208
|
+
|
|
209
|
+
for tx in chain.from_iterable(tr for _, tr in instructions.items()):
|
|
210
|
+
tx.account_model = account_models[tx.account_code]
|
|
211
|
+
|
|
212
|
+
results = dict()
|
|
213
|
+
for ledger_model, tr_items in instructions.items():
|
|
214
|
+
if ledger_model._state.adding:
|
|
215
|
+
ledger_model.save()
|
|
216
|
+
je_txs = [t.to_dict() for t in tr_items]
|
|
217
|
+
|
|
218
|
+
# where the magic happens...
|
|
219
|
+
je, txs_models = ledger_model.commit_txs(
|
|
220
|
+
je_timestamp=je_timestamp if je_timestamp else localtime(),
|
|
221
|
+
je_txs=je_txs,
|
|
222
|
+
je_posted=post_journal_entries
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
results[ledger_model] = {
|
|
226
|
+
'journal_entry': je,
|
|
227
|
+
'txs_models': txs_models,
|
|
228
|
+
'instructions': tr_items
|
|
229
|
+
}
|
|
230
|
+
results['account_model_qs'] = self.account_model_qs
|
|
231
|
+
self.__COMMITTED = True
|
|
232
|
+
return results
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class IOLibraryError(ValidationError):
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class IOBluePrintValidationError(ValidationError):
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class IOBluePrint:
|
|
244
|
+
|
|
245
|
+
def __init__(self, precision_decimals: int = 2):
|
|
246
|
+
self.precision_decimals = precision_decimals
|
|
247
|
+
self.registry = list()
|
|
248
|
+
|
|
249
|
+
def _round_amount(self, amount: Decimal) -> Decimal:
|
|
250
|
+
return round(amount, self.precision_decimals)
|
|
251
|
+
|
|
252
|
+
def _amount(self, amount: Union[float, Decimal]) -> Decimal:
|
|
253
|
+
if amount <= 0:
|
|
254
|
+
raise IOBluePrintValidationError(
|
|
255
|
+
message='Amounts must be greater than 0'
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if isinstance(amount, float):
|
|
259
|
+
return self._round_amount(Decimal.from_float(amount))
|
|
260
|
+
|
|
261
|
+
elif isinstance(amount, Decimal):
|
|
262
|
+
return self._round_amount(amount)
|
|
263
|
+
|
|
264
|
+
raise IOBluePrintValidationError(
|
|
265
|
+
message='Amounts must be float or Decimal'
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def credit(self, account_code: str, amount: Union[float, Decimal], description: str = None):
|
|
269
|
+
|
|
270
|
+
self.registry.append(
|
|
271
|
+
TransactionInstructionItem(
|
|
272
|
+
account_code=account_code,
|
|
273
|
+
amount=self._amount(amount),
|
|
274
|
+
tx_type=CREDIT,
|
|
275
|
+
description=description
|
|
276
|
+
))
|
|
277
|
+
|
|
278
|
+
def debit(self, account_code: str, amount: Union[float, Decimal], description: str = None):
|
|
279
|
+
|
|
280
|
+
self.registry.append(
|
|
281
|
+
TransactionInstructionItem(
|
|
282
|
+
account_code=account_code,
|
|
283
|
+
amount=self._amount(amount),
|
|
284
|
+
tx_type=DEBIT,
|
|
285
|
+
description=description
|
|
286
|
+
))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class IOLibrary:
|
|
290
|
+
|
|
291
|
+
def __init__(self, name: str):
|
|
292
|
+
self.name = name
|
|
293
|
+
self.registry: Dict[str, Callable] = {}
|
|
294
|
+
|
|
295
|
+
def _check_func_name(self, name) -> bool:
|
|
296
|
+
return name in self.registry
|
|
297
|
+
|
|
298
|
+
def register(self, func: Callable):
|
|
299
|
+
self.registry[func.__name__] = func
|
|
300
|
+
|
|
301
|
+
def get_blueprint(self, name: str) -> Callable:
|
|
302
|
+
if not self._check_func_name(name):
|
|
303
|
+
raise IOLibraryError(message=f'Function "{name}" is not registered in IO library {self.name}')
|
|
304
|
+
return self.registry[name]
|
|
305
|
+
|
|
306
|
+
def get_cursor(
|
|
307
|
+
self,
|
|
308
|
+
entity_model: EntityModel,
|
|
309
|
+
user_model,
|
|
310
|
+
coa_model: Optional[Union[ChartOfAccountModel, UUID, str]] = None
|
|
311
|
+
) -> IOCursor:
|
|
312
|
+
return IOCursor(
|
|
313
|
+
io_library=self,
|
|
314
|
+
entity_model=entity_model,
|
|
315
|
+
user_model=user_model,
|
|
316
|
+
coa_model=coa_model,
|
|
317
|
+
)
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Django Ledger created by Miguel Sanda <msanda@arrobalytics.com>.
|
|
3
|
+
Copyright© EDMA Group Inc licensed under the GPLv3 Agreement.
|
|
4
|
+
|
|
5
|
+
Contributions to this module:
|
|
6
|
+
* Miguel Sanda <msanda@arrobalytics.com>
|
|
7
|
+
"""
|
|
1
8
|
from collections import defaultdict
|
|
2
9
|
from itertools import groupby, chain
|
|
3
10
|
|
|
@@ -9,7 +16,7 @@ from django_ledger.models.utils import LazyLoader, lazy_loader
|
|
|
9
16
|
lazy_importer = LazyLoader()
|
|
10
17
|
|
|
11
18
|
|
|
12
|
-
class
|
|
19
|
+
class AccountRoleIOMiddleware:
|
|
13
20
|
|
|
14
21
|
def __init__(self,
|
|
15
22
|
io_data: dict,
|
|
@@ -75,7 +82,7 @@ class RoleContextManager:
|
|
|
75
82
|
acc['balance'] for acc in acc_list if acc['unit_uuid'] == key[0])
|
|
76
83
|
|
|
77
84
|
|
|
78
|
-
class
|
|
85
|
+
class AccountGroupIOMiddleware:
|
|
79
86
|
GROUP_ACCOUNTS_KEY = 'group_account'
|
|
80
87
|
GROUP_BALANCE_KEY = 'group_balance'
|
|
81
88
|
GROUP_BALANCE_BY_UNIT_KEY = 'group_balance_by_unit'
|
|
@@ -148,7 +155,7 @@ class GroupContextManager:
|
|
|
148
155
|
)
|
|
149
156
|
|
|
150
157
|
|
|
151
|
-
class
|
|
158
|
+
class JEActivityIOMiddleware:
|
|
152
159
|
|
|
153
160
|
def __init__(self,
|
|
154
161
|
io_data: dict,
|
|
@@ -210,7 +217,7 @@ class ActivityContextManager:
|
|
|
210
217
|
acc['balance'] for acc in acc_list if acc['unit_uuid'] == key[0])
|
|
211
218
|
|
|
212
219
|
|
|
213
|
-
class
|
|
220
|
+
class BalanceSheetIOMiddleware:
|
|
214
221
|
BS_DIGEST_KEY = 'balance_sheet'
|
|
215
222
|
|
|
216
223
|
def __init__(self, io_data: dict):
|
|
@@ -256,7 +263,7 @@ class BalanceSheetStatementContextManager:
|
|
|
256
263
|
return self.IO_DATA
|
|
257
264
|
|
|
258
265
|
|
|
259
|
-
class
|
|
266
|
+
class IncomeStatementIOMiddleware:
|
|
260
267
|
IC_DIGEST_KEY = 'income_statement'
|
|
261
268
|
|
|
262
269
|
def __init__(self, io_data: dict):
|
|
@@ -341,7 +348,7 @@ class IncomeStatementContextManager:
|
|
|
341
348
|
return self.IO_DATA
|
|
342
349
|
|
|
343
350
|
|
|
344
|
-
class
|
|
351
|
+
class CashFlowStatementIOMiddleware:
|
|
345
352
|
CFS_DIGEST_KEY = 'cash_flow_statement'
|
|
346
353
|
|
|
347
354
|
def __init__(self, io_data: dict):
|
|
@@ -350,13 +357,13 @@ class CashFlowStatementContextManager:
|
|
|
350
357
|
self.JE_MODEL = lazy_loader.get_journal_entry_model()
|
|
351
358
|
|
|
352
359
|
def check_io_digest(self):
|
|
353
|
-
if
|
|
360
|
+
if AccountGroupIOMiddleware.GROUP_BALANCE_KEY not in self.IO_DATA:
|
|
354
361
|
raise ValidationError(
|
|
355
362
|
'IO Digest must have groups for Cash Flow Statement'
|
|
356
363
|
)
|
|
357
364
|
|
|
358
365
|
def operating(self):
|
|
359
|
-
group_balances = self.IO_DATA[
|
|
366
|
+
group_balances = self.IO_DATA[AccountGroupIOMiddleware.GROUP_BALANCE_KEY]
|
|
360
367
|
operating_activities = dict()
|
|
361
368
|
operating_activities['GROUP_CFS_NET_INCOME'] = {
|
|
362
369
|
'description': 'Net Income',
|
|
@@ -424,7 +431,7 @@ class CashFlowStatementContextManager:
|
|
|
424
431
|
self.IO_DATA[self.CFS_DIGEST_KEY]['net_cash_by_activity']['FINANCING'] = net_cash
|
|
425
432
|
|
|
426
433
|
def investing(self):
|
|
427
|
-
group_balances = self.IO_DATA[
|
|
434
|
+
group_balances = self.IO_DATA[AccountGroupIOMiddleware.GROUP_BALANCE_KEY]
|
|
428
435
|
investing_activities = dict()
|
|
429
436
|
investing_activities['GROUP_CFS_INVESTING_SECURITIES'] = {
|
|
430
437
|
'description': 'Purchase, Maturity and Sales of Investments & Securities',
|