nanopy-bank 1.0.8__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 (47) hide show
  1. nanopy_bank/__init__.py +20 -0
  2. nanopy_bank/api/__init__.py +10 -0
  3. nanopy_bank/api/server.py +242 -0
  4. nanopy_bank/app.py +282 -0
  5. nanopy_bank/cli.py +152 -0
  6. nanopy_bank/core/__init__.py +85 -0
  7. nanopy_bank/core/audit.py +404 -0
  8. nanopy_bank/core/auth.py +306 -0
  9. nanopy_bank/core/bank.py +407 -0
  10. nanopy_bank/core/beneficiary.py +258 -0
  11. nanopy_bank/core/branch.py +319 -0
  12. nanopy_bank/core/fees.py +243 -0
  13. nanopy_bank/core/holding.py +416 -0
  14. nanopy_bank/core/models.py +308 -0
  15. nanopy_bank/core/products.py +300 -0
  16. nanopy_bank/data/__init__.py +31 -0
  17. nanopy_bank/data/demo.py +846 -0
  18. nanopy_bank/documents/__init__.py +11 -0
  19. nanopy_bank/documents/statement.py +304 -0
  20. nanopy_bank/sepa/__init__.py +10 -0
  21. nanopy_bank/sepa/sepa.py +452 -0
  22. nanopy_bank/storage/__init__.py +11 -0
  23. nanopy_bank/storage/json_storage.py +127 -0
  24. nanopy_bank/storage/sqlite_storage.py +326 -0
  25. nanopy_bank/ui/__init__.py +14 -0
  26. nanopy_bank/ui/pages/__init__.py +33 -0
  27. nanopy_bank/ui/pages/accounts.py +85 -0
  28. nanopy_bank/ui/pages/advisor.py +140 -0
  29. nanopy_bank/ui/pages/audit.py +73 -0
  30. nanopy_bank/ui/pages/beneficiaries.py +115 -0
  31. nanopy_bank/ui/pages/branches.py +64 -0
  32. nanopy_bank/ui/pages/cards.py +36 -0
  33. nanopy_bank/ui/pages/common.py +18 -0
  34. nanopy_bank/ui/pages/dashboard.py +100 -0
  35. nanopy_bank/ui/pages/fees.py +60 -0
  36. nanopy_bank/ui/pages/holding.py +943 -0
  37. nanopy_bank/ui/pages/loans.py +105 -0
  38. nanopy_bank/ui/pages/login.py +174 -0
  39. nanopy_bank/ui/pages/sepa.py +118 -0
  40. nanopy_bank/ui/pages/settings.py +48 -0
  41. nanopy_bank/ui/pages/transfers.py +94 -0
  42. nanopy_bank/ui/pages.py +16 -0
  43. nanopy_bank-1.0.8.dist-info/METADATA +72 -0
  44. nanopy_bank-1.0.8.dist-info/RECORD +47 -0
  45. nanopy_bank-1.0.8.dist-info/WHEEL +5 -0
  46. nanopy_bank-1.0.8.dist-info/entry_points.txt +2 -0
  47. nanopy_bank-1.0.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,416 @@
1
+ """
2
+ Holding and Group Management Models
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime, date
7
+ from decimal import Decimal
8
+ from enum import Enum
9
+ from typing import Optional, List, Dict
10
+ import uuid
11
+
12
+
13
+ class SubsidiaryType(Enum):
14
+ """Types of subsidiaries"""
15
+ BANK = "bank"
16
+ ASSET_MANAGEMENT = "asset_management"
17
+ INSURANCE = "insurance"
18
+ LEASING = "leasing"
19
+ FINTECH = "fintech"
20
+ SERVICES = "services"
21
+
22
+
23
+ class SubsidiaryStatus(Enum):
24
+ """Subsidiary status"""
25
+ ACTIVE = "active"
26
+ STARTUP = "startup"
27
+ RESTRUCTURING = "restructuring"
28
+ DIVESTING = "divesting"
29
+ CLOSED = "closed"
30
+
31
+
32
+ class LoanDirection(Enum):
33
+ """Direction of intra-group loan"""
34
+ DOWNSTREAM = "downstream" # Holding -> Filiale
35
+ UPSTREAM = "upstream" # Filiale -> Holding
36
+ LATERAL = "lateral" # Filiale -> Filiale
37
+
38
+
39
+ @dataclass
40
+ class Holding:
41
+ """
42
+ Holding company (maison mere)
43
+ """
44
+ holding_id: str = field(default_factory=lambda: f"HOLD{uuid.uuid4().hex[:6].upper()}")
45
+
46
+ # Identity
47
+ name: str = "Nova x Genesis"
48
+ legal_name: str = "Nova x Genesis Financial Services SASU"
49
+ registration_number: str = "" # SIREN
50
+ lei: str = "" # Legal Entity Identifier
51
+
52
+ # Address
53
+ address: str = ""
54
+ city: str = "Paris"
55
+ postal_code: str = "75001"
56
+ country: str = "FR"
57
+
58
+ # Contact
59
+ phone: str = ""
60
+ email: str = ""
61
+ website: str = ""
62
+
63
+ # Accounts
64
+ main_account_iban: str = ""
65
+ treasury_account_iban: str = ""
66
+
67
+ # Financials
68
+ share_capital: Decimal = Decimal("100000000.00")
69
+ total_assets: Decimal = Decimal("0.00")
70
+ total_equity: Decimal = Decimal("0.00")
71
+ consolidated_revenue: Decimal = Decimal("0.00")
72
+ consolidated_profit: Decimal = Decimal("0.00")
73
+
74
+ # Metadata
75
+ founded_date: date = field(default_factory=date.today)
76
+ fiscal_year_end: str = "12-31"
77
+ created_at: datetime = field(default_factory=datetime.now)
78
+
79
+ def to_dict(self) -> dict:
80
+ return {
81
+ "holding_id": self.holding_id,
82
+ "name": self.name,
83
+ "legal_name": self.legal_name,
84
+ "registration_number": self.registration_number,
85
+ "lei": self.lei,
86
+ "address": self.address,
87
+ "city": self.city,
88
+ "postal_code": self.postal_code,
89
+ "country": self.country,
90
+ "phone": self.phone,
91
+ "email": self.email,
92
+ "website": self.website,
93
+ "main_account_iban": self.main_account_iban,
94
+ "treasury_account_iban": self.treasury_account_iban,
95
+ "share_capital": str(self.share_capital),
96
+ "total_assets": str(self.total_assets),
97
+ "total_equity": str(self.total_equity),
98
+ "consolidated_revenue": str(self.consolidated_revenue),
99
+ "consolidated_profit": str(self.consolidated_profit),
100
+ "founded_date": self.founded_date.isoformat(),
101
+ "fiscal_year_end": self.fiscal_year_end,
102
+ "created_at": self.created_at.isoformat(),
103
+ }
104
+
105
+
106
+ @dataclass
107
+ class Subsidiary:
108
+ """
109
+ Subsidiary company (filiale)
110
+ """
111
+ subsidiary_id: str = field(default_factory=lambda: f"SUB{uuid.uuid4().hex[:6].upper()}")
112
+ holding_id: str = ""
113
+
114
+ # Identity
115
+ name: str = ""
116
+ legal_name: str = ""
117
+ subsidiary_type: SubsidiaryType = SubsidiaryType.BANK
118
+ registration_number: str = ""
119
+
120
+ # Ownership
121
+ ownership_percent: Decimal = Decimal("100.00")
122
+ voting_rights_percent: Decimal = Decimal("100.00")
123
+ acquisition_date: date = field(default_factory=date.today)
124
+ acquisition_price: Decimal = Decimal("0.00")
125
+
126
+ # Address
127
+ address: str = ""
128
+ city: str = ""
129
+ postal_code: str = ""
130
+ country: str = "FR"
131
+
132
+ # Financials
133
+ share_capital: Decimal = Decimal("0.00")
134
+ total_assets: Decimal = Decimal("0.00")
135
+ total_equity: Decimal = Decimal("0.00")
136
+ revenue: Decimal = Decimal("0.00")
137
+ net_income: Decimal = Decimal("0.00")
138
+ employees: int = 0
139
+
140
+ # Inter-company
141
+ account_with_holding_iban: str = "" # Compte courant d'associe
142
+ dividend_policy: str = ""
143
+ last_dividend_date: Optional[date] = None
144
+ last_dividend_amount: Decimal = Decimal("0.00")
145
+
146
+ # Status
147
+ status: SubsidiaryStatus = SubsidiaryStatus.ACTIVE
148
+ consolidation_method: str = "full" # full, proportional, equity
149
+
150
+ # Metadata
151
+ created_at: datetime = field(default_factory=datetime.now)
152
+
153
+ def to_dict(self) -> dict:
154
+ return {
155
+ "subsidiary_id": self.subsidiary_id,
156
+ "holding_id": self.holding_id,
157
+ "name": self.name,
158
+ "legal_name": self.legal_name,
159
+ "subsidiary_type": self.subsidiary_type.value,
160
+ "registration_number": self.registration_number,
161
+ "ownership_percent": str(self.ownership_percent),
162
+ "voting_rights_percent": str(self.voting_rights_percent),
163
+ "acquisition_date": self.acquisition_date.isoformat(),
164
+ "acquisition_price": str(self.acquisition_price),
165
+ "address": self.address,
166
+ "city": self.city,
167
+ "postal_code": self.postal_code,
168
+ "country": self.country,
169
+ "share_capital": str(self.share_capital),
170
+ "total_assets": str(self.total_assets),
171
+ "total_equity": str(self.total_equity),
172
+ "revenue": str(self.revenue),
173
+ "net_income": str(self.net_income),
174
+ "employees": self.employees,
175
+ "account_with_holding_iban": self.account_with_holding_iban,
176
+ "dividend_policy": self.dividend_policy,
177
+ "last_dividend_date": self.last_dividend_date.isoformat() if self.last_dividend_date else None,
178
+ "last_dividend_amount": str(self.last_dividend_amount),
179
+ "status": self.status.value,
180
+ "consolidation_method": self.consolidation_method,
181
+ "created_at": self.created_at.isoformat(),
182
+ }
183
+
184
+
185
+ @dataclass
186
+ class IntraGroupLoan:
187
+ """
188
+ Intra-group loan (pret intra-groupe)
189
+ """
190
+ loan_id: str = field(default_factory=lambda: f"IGL{uuid.uuid4().hex[:8].upper()}")
191
+
192
+ # Parties
193
+ lender_id: str = "" # holding_id or subsidiary_id
194
+ lender_name: str = ""
195
+ borrower_id: str = ""
196
+ borrower_name: str = ""
197
+ direction: LoanDirection = LoanDirection.DOWNSTREAM
198
+
199
+ # Loan details
200
+ principal: Decimal = Decimal("0.00")
201
+ interest_rate: Decimal = Decimal("0.00") # Annual rate
202
+ currency: str = "EUR"
203
+
204
+ # Schedule
205
+ start_date: date = field(default_factory=date.today)
206
+ maturity_date: Optional[date] = None
207
+ repayment_frequency: str = "quarterly" # monthly, quarterly, annual, bullet
208
+
209
+ # Current state
210
+ outstanding_principal: Decimal = Decimal("0.00")
211
+ accrued_interest: Decimal = Decimal("0.00")
212
+ total_interest_paid: Decimal = Decimal("0.00")
213
+ total_principal_paid: Decimal = Decimal("0.00")
214
+
215
+ # Status
216
+ status: str = "active" # active, repaid, defaulted, restructured
217
+
218
+ # Documentation
219
+ contract_reference: str = ""
220
+ purpose: str = ""
221
+ collateral: str = ""
222
+
223
+ # Metadata
224
+ created_at: datetime = field(default_factory=datetime.now)
225
+
226
+ def __post_init__(self):
227
+ if self.outstanding_principal == 0:
228
+ self.outstanding_principal = self.principal
229
+
230
+ def to_dict(self) -> dict:
231
+ return {
232
+ "loan_id": self.loan_id,
233
+ "lender_id": self.lender_id,
234
+ "lender_name": self.lender_name,
235
+ "borrower_id": self.borrower_id,
236
+ "borrower_name": self.borrower_name,
237
+ "direction": self.direction.value,
238
+ "principal": str(self.principal),
239
+ "interest_rate": str(self.interest_rate),
240
+ "currency": self.currency,
241
+ "start_date": self.start_date.isoformat(),
242
+ "maturity_date": self.maturity_date.isoformat() if self.maturity_date else None,
243
+ "repayment_frequency": self.repayment_frequency,
244
+ "outstanding_principal": str(self.outstanding_principal),
245
+ "accrued_interest": str(self.accrued_interest),
246
+ "total_interest_paid": str(self.total_interest_paid),
247
+ "total_principal_paid": str(self.total_principal_paid),
248
+ "status": self.status,
249
+ "contract_reference": self.contract_reference,
250
+ "purpose": self.purpose,
251
+ "collateral": self.collateral,
252
+ "created_at": self.created_at.isoformat(),
253
+ }
254
+
255
+
256
+ @dataclass
257
+ class CashPool:
258
+ """
259
+ Cash pooling arrangement (centralisation tresorerie)
260
+ """
261
+ pool_id: str = field(default_factory=lambda: f"POOL{uuid.uuid4().hex[:6].upper()}")
262
+ holding_id: str = ""
263
+
264
+ # Pool details
265
+ name: str = "Cash Pool Principal"
266
+ pool_type: str = "notional" # notional, physical, hybrid
267
+
268
+ # Master account (compte centralisateur)
269
+ master_account_iban: str = ""
270
+ master_account_balance: Decimal = Decimal("0.00")
271
+
272
+ # Participants
273
+ participant_accounts: List[str] = field(default_factory=list) # List of IBANs
274
+
275
+ # Interest rates
276
+ credit_rate: Decimal = Decimal("0.50") # Rate paid on positive balances
277
+ debit_rate: Decimal = Decimal("2.00") # Rate charged on negative balances
278
+
279
+ # Limits
280
+ max_debit_per_participant: Decimal = Decimal("10000000.00")
281
+
282
+ # Current state
283
+ total_credit_balance: Decimal = Decimal("0.00")
284
+ total_debit_balance: Decimal = Decimal("0.00")
285
+ net_position: Decimal = Decimal("0.00")
286
+
287
+ # Status
288
+ is_active: bool = True
289
+ start_date: date = field(default_factory=date.today)
290
+
291
+ # Metadata
292
+ created_at: datetime = field(default_factory=datetime.now)
293
+
294
+ def to_dict(self) -> dict:
295
+ return {
296
+ "pool_id": self.pool_id,
297
+ "holding_id": self.holding_id,
298
+ "name": self.name,
299
+ "pool_type": self.pool_type,
300
+ "master_account_iban": self.master_account_iban,
301
+ "master_account_balance": str(self.master_account_balance),
302
+ "participant_accounts": self.participant_accounts,
303
+ "credit_rate": str(self.credit_rate),
304
+ "debit_rate": str(self.debit_rate),
305
+ "max_debit_per_participant": str(self.max_debit_per_participant),
306
+ "total_credit_balance": str(self.total_credit_balance),
307
+ "total_debit_balance": str(self.total_debit_balance),
308
+ "net_position": str(self.net_position),
309
+ "is_active": self.is_active,
310
+ "start_date": self.start_date.isoformat(),
311
+ "created_at": self.created_at.isoformat(),
312
+ }
313
+
314
+
315
+ @dataclass
316
+ class Dividend:
317
+ """
318
+ Dividend payment from subsidiary to holding
319
+ """
320
+ dividend_id: str = field(default_factory=lambda: f"DIV{uuid.uuid4().hex[:8].upper()}")
321
+
322
+ # Parties
323
+ subsidiary_id: str = ""
324
+ subsidiary_name: str = ""
325
+ holding_id: str = ""
326
+
327
+ # Amount
328
+ gross_amount: Decimal = Decimal("0.00")
329
+ withholding_tax: Decimal = Decimal("0.00")
330
+ net_amount: Decimal = Decimal("0.00")
331
+ currency: str = "EUR"
332
+
333
+ # Dates
334
+ declaration_date: date = field(default_factory=date.today)
335
+ record_date: Optional[date] = None
336
+ payment_date: Optional[date] = None
337
+
338
+ # Details
339
+ fiscal_year: str = ""
340
+ dividend_type: str = "ordinary" # ordinary, exceptional, interim
341
+
342
+ # Status
343
+ status: str = "declared" # declared, approved, paid
344
+
345
+ # Metadata
346
+ created_at: datetime = field(default_factory=datetime.now)
347
+
348
+ def to_dict(self) -> dict:
349
+ return {
350
+ "dividend_id": self.dividend_id,
351
+ "subsidiary_id": self.subsidiary_id,
352
+ "subsidiary_name": self.subsidiary_name,
353
+ "holding_id": self.holding_id,
354
+ "gross_amount": str(self.gross_amount),
355
+ "withholding_tax": str(self.withholding_tax),
356
+ "net_amount": str(self.net_amount),
357
+ "currency": self.currency,
358
+ "declaration_date": self.declaration_date.isoformat(),
359
+ "record_date": self.record_date.isoformat() if self.record_date else None,
360
+ "payment_date": self.payment_date.isoformat() if self.payment_date else None,
361
+ "fiscal_year": self.fiscal_year,
362
+ "dividend_type": self.dividend_type,
363
+ "status": self.status,
364
+ "created_at": self.created_at.isoformat(),
365
+ }
366
+
367
+
368
+ @dataclass
369
+ class ManagementFee:
370
+ """
371
+ Management fee (frais de siege) from holding to subsidiary
372
+ """
373
+ fee_id: str = field(default_factory=lambda: f"MGT{uuid.uuid4().hex[:8].upper()}")
374
+
375
+ # Parties
376
+ holding_id: str = ""
377
+ subsidiary_id: str = ""
378
+ subsidiary_name: str = ""
379
+
380
+ # Amount
381
+ amount: Decimal = Decimal("0.00")
382
+ currency: str = "EUR"
383
+
384
+ # Details
385
+ service_type: str = "" # IT, HR, Legal, Finance, Strategy
386
+ period: str = "" # "2025-Q1", "2025-01", etc.
387
+ description: str = ""
388
+
389
+ # Invoice
390
+ invoice_number: str = ""
391
+ invoice_date: date = field(default_factory=date.today)
392
+ due_date: Optional[date] = None
393
+
394
+ # Status
395
+ status: str = "invoiced" # invoiced, paid, cancelled
396
+
397
+ # Metadata
398
+ created_at: datetime = field(default_factory=datetime.now)
399
+
400
+ def to_dict(self) -> dict:
401
+ return {
402
+ "fee_id": self.fee_id,
403
+ "holding_id": self.holding_id,
404
+ "subsidiary_id": self.subsidiary_id,
405
+ "subsidiary_name": self.subsidiary_name,
406
+ "amount": str(self.amount),
407
+ "currency": self.currency,
408
+ "service_type": self.service_type,
409
+ "period": self.period,
410
+ "description": self.description,
411
+ "invoice_number": self.invoice_number,
412
+ "invoice_date": self.invoice_date.isoformat(),
413
+ "due_date": self.due_date.isoformat() if self.due_date else None,
414
+ "status": self.status,
415
+ "created_at": self.created_at.isoformat(),
416
+ }