@marcos_feitoza/personal-finance-frontend-feature-investments 1.0.0 → 1.1.0
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.
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
# [1.1.0](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/compare/v1.0.1...v1.1.0) (2025-11-28)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* add confirmation dialog for deletion operations ([2972ee1](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/2972ee1e7abf6bb1c183554867006ccd1a9a0909))
|
|
7
|
+
* update readme.me ([56c5303](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/56c5303681a6110d25d600b11047be38b52f0cad))
|
|
8
|
+
|
|
9
|
+
## [1.0.1](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/compare/v1.0.0...v1.0.1) (2025-11-27)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* add crypto table update ([e2e0da7](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/e2e0da77e334dd4416d04770db50be9b91bb0c06))
|
|
15
|
+
* fix and update the tables of investments ([ff5a7b5](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/ff5a7b56d627ebf0ef128e03ff6ad6a57efda042))
|
|
16
|
+
* update rrsp sl table ([107b6dc](https://github.com/MarcosOps/personal-finance-frontend-feature-investments/commit/107b6dce59621e3563e589e00b21f2fbbb529921))
|
|
17
|
+
|
|
1
18
|
# 1.0.0 (2025-11-22)
|
|
2
19
|
|
|
3
20
|
|
package/README.md
CHANGED
|
@@ -1,39 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
This README describes the package. If you publish this package to pub.dev,
|
|
3
|
-
this README's contents appear on the landing page for your package.
|
|
1
|
+
# Feature: Investimentos (Frontend Flutter)
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
|
|
3
|
+
Este pacote contém a implementação das telas para visualização e gerenciamento de todas as contas de investimento na aplicação Personal Finance Frontend.
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
[creating packages](https://dart.dev/guides/libraries/create-packages)
|
|
10
|
-
and the Flutter guide for
|
|
11
|
-
[developing packages and plugins](https://flutter.dev/to/develop-packages).
|
|
12
|
-
-->
|
|
5
|
+
## Propósito
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
know whether this package might be useful for them.
|
|
7
|
+
O objetivo deste pacote é fornecer ao usuário uma interface completa para acompanhar a performance de seus investimentos, registrar novas transações (compra/venda, dividendos) e gerenciar suas contribuições RRSP.
|
|
16
8
|
|
|
17
|
-
##
|
|
9
|
+
## Conteúdo Principal
|
|
18
10
|
|
|
19
|
-
|
|
11
|
+
- **`lib/screens/investment_account_screen.dart`**: Tela genérica para contas de investimento (ex: TFSA, Non-Registered, RRSP Wealthsimple), mostrando resumo e histórico de trades.
|
|
12
|
+
- **`lib/screens/crypto_account_screen.dart`**: Tela específica para contas de criptomoedas, mostrando resumo e histórico de trades de cripto.
|
|
13
|
+
- **`lib/screens/rrsp_sun_life_screen.dart`**: Tela específica para a conta RRSP Sun Life, focando em histórico de contribuições.
|
|
20
14
|
|
|
21
|
-
|
|
15
|
+
---
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
start using the package.
|
|
17
|
+
## Como Usar (Instalação como Dependência)
|
|
25
18
|
|
|
26
|
-
|
|
19
|
+
Este pacote é uma dependência local para a aplicação principal (`personal-finance-frontend`).
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
to `/example` folder.
|
|
21
|
+
No `pubspec.yaml` da aplicação principal, adicione a seguinte linha em `dependencies`:
|
|
30
22
|
|
|
31
|
-
```
|
|
32
|
-
|
|
23
|
+
```yaml
|
|
24
|
+
personal_finance_frontend_feature_investments:
|
|
25
|
+
path: ../personal-finance-frontend-feature-investments
|
|
33
26
|
```
|
|
34
27
|
|
|
35
|
-
##
|
|
28
|
+
## Features
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
30
|
+
- **Resumo do Portfólio (Sintético)**: Visão geral da performance dos ativos em cada conta de investimento, com métricas como "Book Cost", "Market Value", "Unrealized P/L", "Total Return" e percentuais de alocação.
|
|
31
|
+
- **Histórico de Trades (Analítico)**: Listagem detalhada de todas as operações de compra e venda por conta.
|
|
32
|
+
- **Registro de Dividendos**: Formulário para registrar dividendos recebidos.
|
|
33
|
+
- **Acompanhamento de Contribuições RRSP**: Histórico e resumo das contribuições para contas RRSP.
|
|
34
|
+
- **Exclusão Suave (Soft Delete)**: Funcionalidade para deletar trades e contribuições de forma segura, marcando-os como excluídos em vez de remover permanentemente.
|
|
@@ -173,6 +173,7 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
|
|
|
173
173
|
return {'summary': result, 'total_value': accountPortfolioBookCost};
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
|
|
176
177
|
@override
|
|
177
178
|
Widget build(BuildContext context) {
|
|
178
179
|
return Scaffold(
|
|
@@ -349,7 +350,7 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
|
|
|
349
350
|
),
|
|
350
351
|
],
|
|
351
352
|
);
|
|
352
|
-
}
|
|
353
|
+
}
|
|
353
354
|
);
|
|
354
355
|
|
|
355
356
|
if (confirm == true) {
|
|
@@ -378,4 +379,4 @@ class _CryptoAccountScreenState extends State<CryptoAccountScreen> {
|
|
|
378
379
|
}
|
|
379
380
|
}
|
|
380
381
|
}
|
|
381
|
-
}
|
|
382
|
+
}
|
|
@@ -7,6 +7,7 @@ import 'package:personal_finance_frontend_core_ui/widgets/dividend_log_form.dart
|
|
|
7
7
|
import 'package:personal_finance_frontend_core_ui/widgets/app_widgets.dart';
|
|
8
8
|
import 'package:provider/provider.dart';
|
|
9
9
|
import 'package:personal_finance_frontend_core_services/providers/auth_provider.dart';
|
|
10
|
+
import 'package:personal_finance_frontend_core_ui/utils/app_dialogs.dart';
|
|
10
11
|
|
|
11
12
|
class InvestmentAccountScreen extends StatefulWidget {
|
|
12
13
|
final String accountName;
|
|
@@ -141,7 +142,8 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
141
142
|
if (livePrice != null) {
|
|
142
143
|
newTotalValue += shares * livePrice;
|
|
143
144
|
} else {
|
|
144
|
-
|
|
145
|
+
// If live price is not available, use book cost as a fallback for market value
|
|
146
|
+
newTotalValue += (position['book_cost'] as num?)?.toDouble() ?? 0.0;
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
149
|
|
|
@@ -155,7 +157,7 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
155
157
|
Map<String, dynamic> _calculatePortfolioSummary(
|
|
156
158
|
List<Map<String, dynamic>> trades, List<Map<String, dynamic>> dividends) {
|
|
157
159
|
Map<String, dynamic> summary = {};
|
|
158
|
-
double
|
|
160
|
+
double accountTotalBookCost = 0;
|
|
159
161
|
|
|
160
162
|
Map<String, double> dividendSummary = {};
|
|
161
163
|
if (widget.showDividends) {
|
|
@@ -186,7 +188,7 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
186
188
|
'name': asset['name'] ?? symbol,
|
|
187
189
|
'industry': asset['industry'] ?? 'N/A',
|
|
188
190
|
'shares': 0.0,
|
|
189
|
-
'
|
|
191
|
+
'book_cost': 0.0, // Renamed from total_cost for clarity
|
|
190
192
|
'total_dividends': dividendSummary[symbol] ?? 0.0,
|
|
191
193
|
'first_trade_date': tradeDate,
|
|
192
194
|
'last_activity_date': tradeDate,
|
|
@@ -202,12 +204,12 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
202
204
|
|
|
203
205
|
if (tradeType == 'buy') {
|
|
204
206
|
summary[symbol]['shares'] += shares;
|
|
205
|
-
summary[symbol]['
|
|
207
|
+
summary[symbol]['book_cost'] += shares * price;
|
|
206
208
|
} else if (tradeType == 'sell') {
|
|
207
209
|
double originalShares = summary[symbol]['shares'];
|
|
208
210
|
if (originalShares > 0) {
|
|
209
|
-
double avgPrice = summary[symbol]['
|
|
210
|
-
summary[symbol]['
|
|
211
|
+
double avgPrice = summary[symbol]['book_cost'] / originalShares;
|
|
212
|
+
summary[symbol]['book_cost'] -= shares * avgPrice;
|
|
211
213
|
}
|
|
212
214
|
summary[symbol]['shares'] -= shares;
|
|
213
215
|
}
|
|
@@ -215,21 +217,40 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
215
217
|
|
|
216
218
|
summary.removeWhere((key, value) => value['shares'] < 0.01);
|
|
217
219
|
|
|
218
|
-
|
|
219
|
-
summary.values.fold(0.0, (sum, item) => sum + item['
|
|
220
|
+
accountTotalBookCost =
|
|
221
|
+
summary.values.fold(0.0, (sum, item) => sum + item['book_cost']);
|
|
220
222
|
|
|
221
223
|
List<Map<String, dynamic>> result = [];
|
|
222
224
|
summary.forEach((symbol, data) {
|
|
223
225
|
double shares = data['shares'];
|
|
224
|
-
double
|
|
225
|
-
data['
|
|
226
|
-
|
|
227
|
-
|
|
226
|
+
double bookCost = data['book_cost'];
|
|
227
|
+
double totalDividends = data['total_dividends'];
|
|
228
|
+
|
|
229
|
+
data['avg_price'] = (shares > 0) ? bookCost / shares : 0.0;
|
|
230
|
+
data['account_allocation_percent'] = (accountTotalBookCost > 0)
|
|
231
|
+
? (bookCost / accountTotalBookCost) * 100
|
|
232
|
+
: 0.0;
|
|
233
|
+
data['portfolio_allocation_percent'] = (_totalPortfolioBookCost > 0)
|
|
234
|
+
? (bookCost / _totalPortfolioBookCost) * 100
|
|
228
235
|
: 0.0;
|
|
236
|
+
|
|
237
|
+
// Calculate Market Value, Unrealized P/L, Total Return
|
|
238
|
+
final livePrice = _livePrices[symbol];
|
|
239
|
+
double marketValue = livePrice != null ? shares * livePrice : bookCost; // Fallback to bookCost if no live price
|
|
240
|
+
double unrealizedPL = marketValue - bookCost;
|
|
241
|
+
double totalReturn = unrealizedPL + totalDividends;
|
|
242
|
+
|
|
243
|
+
data['market_value'] = marketValue;
|
|
244
|
+
data['unrealized_pl'] = unrealizedPL;
|
|
245
|
+
data['total_return'] = totalReturn;
|
|
246
|
+
|
|
247
|
+
data['percent_unrealized_pl'] = (bookCost > 0) ? (unrealizedPL / bookCost) * 100 : 0.0;
|
|
248
|
+
data['percent_total_return'] = (bookCost > 0) ? (totalReturn / bookCost) * 100 : 0.0;
|
|
249
|
+
|
|
229
250
|
result.add(data);
|
|
230
251
|
});
|
|
231
252
|
|
|
232
|
-
return {'summary': result, 'total_value':
|
|
253
|
+
return {'summary': result, 'total_value': accountTotalBookCost};
|
|
233
254
|
}
|
|
234
255
|
|
|
235
256
|
@override
|
|
@@ -357,111 +378,60 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
357
378
|
columnSpacing: 24.0,
|
|
358
379
|
columns: [
|
|
359
380
|
const DataColumn(label: Text('Symbol')),
|
|
360
|
-
const DataColumn(label: Text('Age')),
|
|
361
|
-
const DataColumn(label: Text('Purchased')),
|
|
362
381
|
const DataColumn(label: Text('Shares')),
|
|
363
382
|
const DataColumn(label: Text('Avg Price')),
|
|
364
|
-
const DataColumn(label: Text('Live')),
|
|
365
|
-
const DataColumn(label: Text('Purchased')),
|
|
366
|
-
const DataColumn(label: Text('Allocation')),
|
|
367
|
-
const DataColumn(label: Text('Stocks Allocation')),
|
|
368
|
-
const DataColumn(label: Text('Return')),
|
|
369
|
-
const DataColumn(label: Text('% Return')),
|
|
370
383
|
const DataColumn(label: Text('Book Cost')),
|
|
384
|
+
const DataColumn(label: Text('Live Price')),
|
|
385
|
+
const DataColumn(label: Text('Market Value')),
|
|
386
|
+
const DataColumn(label: Text('Unrealized P/L')),
|
|
387
|
+
const DataColumn(label: Text('% P/L')),
|
|
371
388
|
if (widget.showDividends) ...[
|
|
372
389
|
const DataColumn(label: Text('Dividends')),
|
|
373
|
-
const DataColumn(label: Text('Return
|
|
374
|
-
const DataColumn(label: Text('
|
|
375
|
-
const DataColumn(label: Text('% Return Div')),
|
|
390
|
+
const DataColumn(label: Text('Total Return')),
|
|
391
|
+
const DataColumn(label: Text('% Total Return')),
|
|
376
392
|
],
|
|
393
|
+
const DataColumn(label: Text('Account %')),
|
|
394
|
+
const DataColumn(label: Text('Portfolio %')),
|
|
377
395
|
],
|
|
378
396
|
rows: _portfolioSummary.map((position) {
|
|
379
397
|
final symbol = position['symbol'] as String;
|
|
398
|
+
final shares = position['shares'] as double;
|
|
399
|
+
final avgPrice = position['avg_price'] as double;
|
|
400
|
+
final bookCost = position['book_cost'] as double;
|
|
380
401
|
final livePrice = _livePrices[symbol];
|
|
381
|
-
final
|
|
382
|
-
final
|
|
383
|
-
final
|
|
384
|
-
|
|
385
|
-
final
|
|
386
|
-
|
|
387
|
-
final
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
final
|
|
394
|
-
?
|
|
395
|
-
: null;
|
|
396
|
-
|
|
397
|
-
double? totalReturnValue,
|
|
398
|
-
percentageReturn,
|
|
399
|
-
bookCost,
|
|
400
|
-
returnPlusDividend,
|
|
401
|
-
bookCostPlusDividend,
|
|
402
|
-
percentageReturnDividend;
|
|
403
|
-
|
|
404
|
-
if (livePrice != null && shares > 0) {
|
|
405
|
-
final currentMarketValue = livePrice * shares;
|
|
406
|
-
totalReturnValue = currentMarketValue - totalPurchased;
|
|
407
|
-
bookCost = currentMarketValue;
|
|
408
|
-
if (totalPurchased > 0)
|
|
409
|
-
percentageReturn = (totalReturnValue / totalPurchased) * 100;
|
|
410
|
-
if (widget.showDividends) {
|
|
411
|
-
returnPlusDividend = totalReturnValue + totalDividends;
|
|
412
|
-
bookCostPlusDividend = bookCost + totalDividends;
|
|
413
|
-
if (totalPurchased > 0)
|
|
414
|
-
percentageReturnDividend =
|
|
415
|
-
(returnPlusDividend / totalPurchased) * 100;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
final returnColor =
|
|
420
|
-
(totalReturnValue ?? 0) >= 0 ? Colors.green : Colors.red;
|
|
421
|
-
final returnWithDivColor =
|
|
422
|
-
(returnPlusDividend ?? 0) >= 0 ? Colors.green : Colors.red;
|
|
402
|
+
final marketValue = position['market_value'] as double;
|
|
403
|
+
final unrealizedPL = position['unrealized_pl'] as double;
|
|
404
|
+
final percentUnrealizedPL = position['percent_unrealized_pl'] as double;
|
|
405
|
+
final totalDividends = position['total_dividends'] as double;
|
|
406
|
+
final totalReturn = position['total_return'] as double;
|
|
407
|
+
final percentTotalReturn = position['percent_total_return'] as double;
|
|
408
|
+
final accountAllocationPercent = position['account_allocation_percent'] as double;
|
|
409
|
+
final portfolioAllocationPercent = position['portfolio_allocation_percent'] as double;
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
final plColor =
|
|
413
|
+
(unrealizedPL ?? 0) >= 0 ? Colors.green : Colors.red;
|
|
414
|
+
final totalReturnColor =
|
|
415
|
+
(totalReturn ?? 0) >= 0 ? Colors.green : Colors.red;
|
|
423
416
|
|
|
424
417
|
return DataRow(cells: [
|
|
425
418
|
DataCell(Text(symbol)),
|
|
426
|
-
DataCell(Text(
|
|
427
|
-
positionAge != null ? _formatDuration(positionAge) : 'N/A')),
|
|
428
|
-
DataCell(Text(position['last_activity_date'] != null
|
|
429
|
-
? DateFormat('yyyy-MM-dd')
|
|
430
|
-
.format(position['last_activity_date'])
|
|
431
|
-
: 'N/A')),
|
|
432
419
|
DataCell(Text(shares.toStringAsFixed(4))),
|
|
433
420
|
DataCell(Text(_formatCurrency(avgPrice))),
|
|
421
|
+
DataCell(Text(_formatCurrency(bookCost))),
|
|
434
422
|
DataCell(livePrice != null
|
|
435
423
|
? Text(_formatCurrency(livePrice))
|
|
436
424
|
: const Text('N/A')),
|
|
437
|
-
DataCell(Text(_formatCurrency(
|
|
438
|
-
DataCell(Text(
|
|
439
|
-
DataCell(Text('${
|
|
440
|
-
DataCell(totalReturnValue != null
|
|
441
|
-
? Text(_formatCurrency(totalReturnValue),
|
|
442
|
-
style: TextStyle(color: returnColor))
|
|
443
|
-
: const Text('N/A')),
|
|
444
|
-
DataCell(percentageReturn != null
|
|
445
|
-
? Text('${percentageReturn.toStringAsFixed(2)}%',
|
|
446
|
-
style: TextStyle(color: returnColor))
|
|
447
|
-
: const Text('N/A')),
|
|
448
|
-
DataCell(bookCost != null
|
|
449
|
-
? Text(_formatCurrency(bookCost))
|
|
450
|
-
: const Text('N/A')),
|
|
425
|
+
DataCell(Text(_formatCurrency(marketValue))),
|
|
426
|
+
DataCell(Text(_formatCurrency(unrealizedPL), style: TextStyle(color: plColor))),
|
|
427
|
+
DataCell(Text('${percentUnrealizedPL.toStringAsFixed(2)}%', style: TextStyle(color: plColor))),
|
|
451
428
|
if (widget.showDividends) ...[
|
|
452
429
|
DataCell(Text(_formatCurrency(totalDividends))),
|
|
453
|
-
DataCell(
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
? Text(_formatCurrency(bookCostPlusDividend))
|
|
459
|
-
: const Text('N/A')),
|
|
460
|
-
DataCell(percentageReturnDividend != null
|
|
461
|
-
? Text('${percentageReturnDividend.toStringAsFixed(2)}%',
|
|
462
|
-
style: TextStyle(color: returnWithDivColor))
|
|
463
|
-
: const Text('N/A')),
|
|
464
|
-
]
|
|
430
|
+
DataCell(Text(_formatCurrency(totalReturn), style: TextStyle(color: totalReturnColor))),
|
|
431
|
+
DataCell(Text('${percentTotalReturn.toStringAsFixed(2)}%', style: TextStyle(color: totalReturnColor))),
|
|
432
|
+
],
|
|
433
|
+
DataCell(Text('${accountAllocationPercent.toStringAsFixed(2)}%')),
|
|
434
|
+
DataCell(Text('${portfolioAllocationPercent.toStringAsFixed(2)}%')),
|
|
465
435
|
]);
|
|
466
436
|
}).toList(),
|
|
467
437
|
),
|
|
@@ -575,25 +545,7 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
575
545
|
}
|
|
576
546
|
|
|
577
547
|
Future<void> _confirmAndDeleteTrade(int tradeId) async {
|
|
578
|
-
final bool? confirm = await
|
|
579
|
-
context: context,
|
|
580
|
-
builder: (BuildContext context) {
|
|
581
|
-
return AlertDialog(
|
|
582
|
-
title: const Text('Confirm Deletion'),
|
|
583
|
-
content: const Text('Are you sure you want to delete this trade? This action cannot be undone.'),
|
|
584
|
-
actions: <Widget>[
|
|
585
|
-
TextButton(
|
|
586
|
-
onPressed: () => Navigator.of(context).pop(false),
|
|
587
|
-
child: const Text('Cancel'),
|
|
588
|
-
),
|
|
589
|
-
TextButton(
|
|
590
|
-
onPressed: () => Navigator.of(context).pop(true),
|
|
591
|
-
child: const Text('Delete'),
|
|
592
|
-
),
|
|
593
|
-
],
|
|
594
|
-
);
|
|
595
|
-
},
|
|
596
|
-
);
|
|
548
|
+
final bool? confirm = await AppDialogs.showDeleteConfirmationDialog(context, 'this trade');
|
|
597
549
|
|
|
598
550
|
if (confirm == true) {
|
|
599
551
|
if (_token == null) {
|
|
@@ -621,4 +573,4 @@ class _InvestmentAccountScreenState extends State<InvestmentAccountScreen> {
|
|
|
621
573
|
}
|
|
622
574
|
}
|
|
623
575
|
}
|
|
624
|
-
}
|
|
576
|
+
}
|
|
@@ -59,8 +59,6 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
|
|
|
59
59
|
final sinteticoSummaryData =
|
|
60
60
|
_calculateSinteticoSummary(contributions, totalPortfolioBookCost);
|
|
61
61
|
|
|
62
|
-
print('[DEBUG] Sintetico Summary Data: $sinteticoSummaryData');
|
|
63
|
-
|
|
64
62
|
if (!mounted) return;
|
|
65
63
|
setState(() {
|
|
66
64
|
_contributions = contributions;
|
|
@@ -85,40 +83,35 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
|
|
|
85
83
|
|
|
86
84
|
Map<String, dynamic> _calculateSinteticoSummary(
|
|
87
85
|
List<Map<String, dynamic>> contributions, double totalPortfolioBookCost) {
|
|
88
|
-
double
|
|
89
|
-
|
|
90
|
-
double
|
|
91
|
-
|
|
92
|
-
double totalReturn = 0;
|
|
86
|
+
double totalUserContribution = 0;
|
|
87
|
+
double totalCompanyContribution = 0;
|
|
88
|
+
double totalUnrealizedPL = 0;
|
|
93
89
|
|
|
94
90
|
for (var c in contributions) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
totalReturn += double.parse(c['return_amount']?.toString() ?? '0.0');
|
|
91
|
+
totalUserContribution += double.parse(c['rrsp_amount'].toString());
|
|
92
|
+
totalCompanyContribution += double.parse(c['dpsp_amount'].toString());
|
|
93
|
+
totalUnrealizedPL += double.parse(c['return_amount']?.toString() ?? '0.0');
|
|
100
94
|
}
|
|
101
95
|
|
|
102
|
-
final
|
|
103
|
-
|
|
104
|
-
final bookCost = totalPurchased + totalReturn;
|
|
96
|
+
final totalContributed = totalUserContribution + totalCompanyContribution;
|
|
97
|
+
final marketValue = totalContributed + totalUnrealizedPL;
|
|
105
98
|
|
|
106
99
|
final summaryData = {
|
|
107
|
-
'
|
|
108
|
-
'
|
|
109
|
-
'
|
|
110
|
-
'
|
|
100
|
+
'user_contribution': totalUserContribution,
|
|
101
|
+
'company_contribution': totalCompanyContribution,
|
|
102
|
+
'total_contributed': totalContributed,
|
|
103
|
+
'unrealized_pl': totalUnrealizedPL,
|
|
111
104
|
'percent_return':
|
|
112
|
-
(
|
|
113
|
-
'
|
|
114
|
-
'
|
|
115
|
-
? (
|
|
105
|
+
(totalContributed > 0) ? (totalUnrealizedPL / totalContributed) * 100 : 0.0,
|
|
106
|
+
'market_value': marketValue,
|
|
107
|
+
'portfolio_allocation_percent': (totalPortfolioBookCost > 0)
|
|
108
|
+
? (marketValue / totalPortfolioBookCost) * 100
|
|
116
109
|
: 0.0,
|
|
117
110
|
};
|
|
118
111
|
|
|
119
112
|
return {
|
|
120
113
|
'summary': [summaryData],
|
|
121
|
-
'total_value':
|
|
114
|
+
'total_value': marketValue
|
|
122
115
|
};
|
|
123
116
|
}
|
|
124
117
|
|
|
@@ -191,27 +184,33 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
|
|
|
191
184
|
}
|
|
192
185
|
|
|
193
186
|
final summary = _sinteticoSummary.first;
|
|
187
|
+
final double unrealizedPL = summary['unrealized_pl'];
|
|
188
|
+
final plColor = (unrealizedPL >= 0) ? Colors.green : Colors.red;
|
|
189
|
+
|
|
194
190
|
|
|
195
191
|
return DataTable(
|
|
196
192
|
columns: const [
|
|
197
|
-
DataColumn(label: Text('
|
|
198
|
-
DataColumn(label: Text('
|
|
199
|
-
DataColumn(label: Text('Total
|
|
200
|
-
DataColumn(label: Text('
|
|
193
|
+
DataColumn(label: Text('User Contr.')),
|
|
194
|
+
DataColumn(label: Text('Company Contr.')),
|
|
195
|
+
DataColumn(label: Text('Total Contributed')),
|
|
196
|
+
DataColumn(label: Text('Unrealized P/L')),
|
|
201
197
|
DataColumn(label: Text('% Return')),
|
|
202
|
-
DataColumn(label: Text('
|
|
198
|
+
DataColumn(label: Text('Market Value')),
|
|
199
|
+
DataColumn(label: Text('Portfolio %')),
|
|
203
200
|
],
|
|
204
201
|
rows: [
|
|
205
202
|
DataRow(
|
|
206
203
|
cells: [
|
|
207
|
-
DataCell(Text(_formatCurrency(summary['
|
|
208
|
-
DataCell(Text(_formatCurrency(summary['
|
|
204
|
+
DataCell(Text(_formatCurrency(summary['user_contribution'] as double))),
|
|
205
|
+
DataCell(Text(_formatCurrency(summary['company_contribution'] as double))),
|
|
209
206
|
DataCell(
|
|
210
|
-
Text(_formatCurrency(summary['
|
|
211
|
-
DataCell(Text(_formatCurrency(
|
|
207
|
+
Text(_formatCurrency(summary['total_contributed'] as double))),
|
|
208
|
+
DataCell(Text(_formatCurrency(unrealizedPL), style: TextStyle(color: plColor))),
|
|
209
|
+
DataCell(Text(
|
|
210
|
+
'${(summary['percent_return'] as double).toStringAsFixed(2)}%', style: TextStyle(color: plColor))),
|
|
211
|
+
DataCell(Text(_formatCurrency(summary['market_value'] as double))),
|
|
212
212
|
DataCell(Text(
|
|
213
|
-
'${(summary['
|
|
214
|
-
DataCell(Text(_formatCurrency(summary['book_cost'] as double))),
|
|
213
|
+
'${(summary['portfolio_allocation_percent'] as double).toStringAsFixed(2)}%')),
|
|
215
214
|
],
|
|
216
215
|
),
|
|
217
216
|
],
|
|
@@ -231,30 +230,30 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
|
|
|
231
230
|
return DataTable(
|
|
232
231
|
columns: const [
|
|
233
232
|
DataColumn(label: Text('Date')),
|
|
234
|
-
DataColumn(label: Text('
|
|
235
|
-
DataColumn(label: Text('
|
|
236
|
-
DataColumn(label: Text('Total
|
|
233
|
+
DataColumn(label: Text('User Contr.')),
|
|
234
|
+
DataColumn(label: Text('Company Contr.')),
|
|
235
|
+
DataColumn(label: Text('Total Contributed')),
|
|
237
236
|
DataColumn(label: Text('Return')),
|
|
238
237
|
DataColumn(label: Text('% Return')),
|
|
239
|
-
DataColumn(label: Text('
|
|
240
|
-
DataColumn(label: Text('
|
|
241
|
-
DataColumn(label: Text('Actions')),
|
|
238
|
+
DataColumn(label: Text('Cumulative Value')),
|
|
239
|
+
DataColumn(label: Text('Portfolio %')),
|
|
240
|
+
DataColumn(label: Text('Actions')),
|
|
242
241
|
],
|
|
243
242
|
rows: _contributions.map((c) {
|
|
244
243
|
final contributionId = c['id'] as int;
|
|
245
244
|
final rrspAmount = double.parse(c['rrsp_amount'].toString());
|
|
246
245
|
final dpspAmount = double.parse(c['dpsp_amount'].toString());
|
|
247
|
-
final
|
|
246
|
+
final totalContributed = rrspAmount + dpspAmount;
|
|
248
247
|
|
|
249
248
|
final returnAmount =
|
|
250
249
|
double.parse(c['return_amount']?.toString() ?? '0.0');
|
|
251
250
|
final percentReturn =
|
|
252
|
-
|
|
251
|
+
totalContributed > 0 ? (returnAmount / totalContributed) * 100 : 0.0;
|
|
253
252
|
|
|
254
|
-
final
|
|
253
|
+
final cumulativeValue = totalContributed + returnAmount;
|
|
255
254
|
|
|
256
|
-
final
|
|
257
|
-
? (
|
|
255
|
+
final portfolioAllocation = _totalPortfolioBookCost > 0
|
|
256
|
+
? (cumulativeValue / _totalPortfolioBookCost) * 100
|
|
258
257
|
: 0.0;
|
|
259
258
|
|
|
260
259
|
String dateStr;
|
|
@@ -272,12 +271,12 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
|
|
|
272
271
|
DataCell(Text(dateStr)),
|
|
273
272
|
DataCell(Text(_formatCurrency(rrspAmount))),
|
|
274
273
|
DataCell(Text(_formatCurrency(dpspAmount))),
|
|
275
|
-
DataCell(Text(_formatCurrency(
|
|
274
|
+
DataCell(Text(_formatCurrency(totalContributed))),
|
|
276
275
|
DataCell(Text(_formatCurrency(returnAmount))),
|
|
277
276
|
DataCell(Text('${percentReturn.toStringAsFixed(2)}%')),
|
|
278
|
-
DataCell(Text(_formatCurrency(
|
|
279
|
-
DataCell(Text('${
|
|
280
|
-
DataCell(
|
|
277
|
+
DataCell(Text(_formatCurrency(cumulativeValue))),
|
|
278
|
+
DataCell(Text('${portfolioAllocation.toStringAsFixed(2)}%')),
|
|
279
|
+
DataCell(
|
|
281
280
|
IconButton(
|
|
282
281
|
icon: const Icon(Icons.delete, color: Colors.red),
|
|
283
282
|
onPressed: () => _confirmAndDeleteContribution(contributionId),
|
|
@@ -337,4 +336,4 @@ class _RrspSunLifeScreenState extends State<RrspSunLifeScreen> {
|
|
|
337
336
|
}
|
|
338
337
|
}
|
|
339
338
|
}
|
|
340
|
-
}
|
|
339
|
+
}
|