style(app): остальные экраны под новый визуальный язык и форматирование
Доходы, ребалансировка, налоги, импорт, цели, здоровье данных, правила, транзакции, категории, cashflow, pending: новые виджеты и токены темы, dart format. Тесты подстроены под новые модели.
This commit is contained in:
@@ -34,31 +34,39 @@ class TargetWeight {
|
||||
final String? band;
|
||||
final String? note;
|
||||
|
||||
TargetWeight copyWith({String? bucket, String? targetWeight, String? band, String? note}) =>
|
||||
TargetWeight(
|
||||
bucket: bucket ?? this.bucket,
|
||||
targetWeight: targetWeight ?? this.targetWeight,
|
||||
band: band ?? this.band,
|
||||
note: note ?? this.note,
|
||||
);
|
||||
TargetWeight copyWith({
|
||||
String? bucket,
|
||||
String? targetWeight,
|
||||
String? band,
|
||||
String? note,
|
||||
}) => TargetWeight(
|
||||
bucket: bucket ?? this.bucket,
|
||||
targetWeight: targetWeight ?? this.targetWeight,
|
||||
band: band ?? this.band,
|
||||
note: note ?? this.note,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'bucket': bucket,
|
||||
'target_weight': targetWeight,
|
||||
'band': ?band,
|
||||
'note': ?note,
|
||||
};
|
||||
'bucket': bucket,
|
||||
'target_weight': targetWeight,
|
||||
'band': ?band,
|
||||
'note': ?note,
|
||||
};
|
||||
|
||||
static TargetWeight fromJson(Map<String, dynamic> json) => TargetWeight(
|
||||
bucket: asString(json['bucket']) ?? '',
|
||||
targetWeight: asString(json['target_weight']) ?? '0',
|
||||
band: asString(json['band']),
|
||||
note: asString(json['note']),
|
||||
);
|
||||
bucket: asString(json['bucket']) ?? '',
|
||||
targetWeight: asString(json['target_weight']) ?? '0',
|
||||
band: asString(json['band']),
|
||||
note: asString(json['note']),
|
||||
);
|
||||
}
|
||||
|
||||
class TargetSet {
|
||||
const TargetSet({required this.dimension, this.targets = const [], this.weightsSum});
|
||||
const TargetSet({
|
||||
required this.dimension,
|
||||
this.targets = const [],
|
||||
this.weightsSum,
|
||||
});
|
||||
|
||||
final String dimension;
|
||||
final List<TargetWeight> targets;
|
||||
@@ -72,18 +80,19 @@ class TargetSet {
|
||||
Decimal get localSum => sumDecimals(targets.map((t) => t.targetWeight));
|
||||
|
||||
/// The contract's tolerance: the sum must be 1 within 0.0001.
|
||||
bool get sumIsValid => (localSum - Decimal.one).abs() <= Decimal.parse('0.0001');
|
||||
bool get sumIsValid =>
|
||||
(localSum - Decimal.one).abs() <= Decimal.parse('0.0001');
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'dimension': dimension,
|
||||
'targets': [for (final t in targets) t.toJson()],
|
||||
};
|
||||
'dimension': dimension,
|
||||
'targets': [for (final t in targets) t.toJson()],
|
||||
};
|
||||
|
||||
static TargetSet fromJson(Map<String, dynamic> json) => TargetSet(
|
||||
dimension: asString(json['dimension']) ?? 'asset_class',
|
||||
targets: asObjects(json['targets']).map(TargetWeight.fromJson).toList(),
|
||||
weightsSum: asString(json['weights_sum']),
|
||||
);
|
||||
dimension: asString(json['dimension']) ?? 'asset_class',
|
||||
targets: asObjects(json['targets']).map(TargetWeight.fromJson).toList(),
|
||||
weightsSum: asString(json['weights_sum']),
|
||||
);
|
||||
}
|
||||
|
||||
class RebalanceTrade {
|
||||
@@ -119,20 +128,21 @@ class RebalanceTrade {
|
||||
/// tell an underweight recommendation from a wrong one.
|
||||
final bool blockedByCash;
|
||||
|
||||
String get title => ticker ?? name ?? (instrumentId == null ? '—' : '#$instrumentId');
|
||||
String get title =>
|
||||
ticker ?? name ?? (instrumentId == null ? '—' : '#$instrumentId');
|
||||
|
||||
static RebalanceTrade fromJson(Map<String, dynamic> json) => RebalanceTrade(
|
||||
instrumentId: asInt(json['instrument_id']),
|
||||
ticker: asString(json['ticker']),
|
||||
name: asString(json['name']),
|
||||
action: asString(json['action']) ?? 'buy',
|
||||
suggestedQty: asString(json['suggested_qty']),
|
||||
lot: asInt(json['lot']),
|
||||
price: asString(json['price']),
|
||||
priceCurrency: asString(json['price_currency']),
|
||||
amountRub: asString(json['amount_rub']),
|
||||
blockedByCash: asBool(json['blocked_by_cash']),
|
||||
);
|
||||
instrumentId: asInt(json['instrument_id']),
|
||||
ticker: asString(json['ticker']),
|
||||
name: asString(json['name']),
|
||||
action: asString(json['action']) ?? 'buy',
|
||||
suggestedQty: asString(json['suggested_qty']),
|
||||
lot: asInt(json['lot']),
|
||||
price: asString(json['price']),
|
||||
priceCurrency: asString(json['price_currency']),
|
||||
amountRub: asString(json['amount_rub']),
|
||||
blockedByCash: asBool(json['blocked_by_cash']),
|
||||
);
|
||||
}
|
||||
|
||||
class RebalanceBucket {
|
||||
@@ -161,15 +171,15 @@ class RebalanceBucket {
|
||||
final List<RebalanceTrade> trades;
|
||||
|
||||
static RebalanceBucket fromJson(Map<String, dynamic> json) => RebalanceBucket(
|
||||
bucket: asString(json['bucket']) ?? '',
|
||||
currentValueRub: asString(json['current_value_rub']),
|
||||
currentWeight: asString(json['current_weight']),
|
||||
targetWeight: asString(json['target_weight']),
|
||||
drift: asString(json['drift']),
|
||||
withinBand: asBool(json['within_band']),
|
||||
deltaValueRub: asString(json['delta_value_rub']),
|
||||
trades: asObjects(json['trades']).map(RebalanceTrade.fromJson).toList(),
|
||||
);
|
||||
bucket: asString(json['bucket']) ?? '',
|
||||
currentValueRub: asString(json['current_value_rub']),
|
||||
currentWeight: asString(json['current_weight']),
|
||||
targetWeight: asString(json['target_weight']),
|
||||
drift: asString(json['drift']),
|
||||
withinBand: asBool(json['within_band']),
|
||||
deltaValueRub: asString(json['delta_value_rub']),
|
||||
trades: asObjects(json['trades']).map(RebalanceTrade.fromJson).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
class RebalancePlan {
|
||||
@@ -191,17 +201,18 @@ class RebalancePlan {
|
||||
final List<RebalanceBucket> buckets;
|
||||
final List<String> warnings;
|
||||
|
||||
bool get everythingWithinBand => buckets.isNotEmpty && buckets.every((b) => b.withinBand);
|
||||
bool get everythingWithinBand =>
|
||||
buckets.isNotEmpty && buckets.every((b) => b.withinBand);
|
||||
|
||||
static RebalancePlan fromJson(Map<String, dynamic> json) => RebalancePlan(
|
||||
portfolioId: asInt(json['portfolio_id']) ?? 0,
|
||||
dimension: asString(json['dimension']) ?? 'asset_class',
|
||||
asOf: asDate(json['as_of']),
|
||||
totalValueRub: asString(json['total_value_rub']),
|
||||
cashAvailableRub: asString(json['cash_available_rub']),
|
||||
buckets: asObjects(json['buckets']).map(RebalanceBucket.fromJson).toList(),
|
||||
warnings: asStrings(json['warnings']),
|
||||
);
|
||||
portfolioId: asInt(json['portfolio_id']) ?? 0,
|
||||
dimension: asString(json['dimension']) ?? 'asset_class',
|
||||
asOf: asDate(json['as_of']),
|
||||
totalValueRub: asString(json['total_value_rub']),
|
||||
cashAvailableRub: asString(json['cash_available_rub']),
|
||||
buckets: asObjects(json['buckets']).map(RebalanceBucket.fromJson).toList(),
|
||||
warnings: asStrings(json['warnings']),
|
||||
);
|
||||
}
|
||||
|
||||
class RebalanceApi {
|
||||
@@ -217,12 +228,18 @@ class RebalanceApi {
|
||||
/// not — revisit once the route is in the spec.
|
||||
/// See `docs/ai/offline-cache.md`: this hand-written client returns `Cached<T>` directly
|
||||
/// (there is no generated `Response<T>` for the screen to unwrap `r.cached` from).
|
||||
Future<Cached<TargetSet>> getTargets(int portfolioId, {String dimension = 'asset_class'}) async {
|
||||
Future<Cached<TargetSet>> getTargets(
|
||||
int portfolioId, {
|
||||
String dimension = 'asset_class',
|
||||
}) async {
|
||||
final r = await _dio.get<Map<String, dynamic>>(
|
||||
'$_base/$portfolioId/targets',
|
||||
queryParameters: {'dimension': dimension},
|
||||
);
|
||||
return Cached(TargetSet.fromJson(r.data ?? const {}), fetchedAt: r.extra['fetchedAt'] as DateTime?);
|
||||
return Cached(
|
||||
TargetSet.fromJson(r.data ?? const {}),
|
||||
fetchedAt: r.extra['fetchedAt'] as DateTime?,
|
||||
);
|
||||
}
|
||||
|
||||
/// Full replacement of one dimension — partial updates are not supported by the contract.
|
||||
@@ -241,7 +258,10 @@ class RebalanceApi {
|
||||
}) async {
|
||||
final r = await _dio.get<Map<String, dynamic>>(
|
||||
'$_base/$portfolioId/rebalance',
|
||||
queryParameters: {'dimension': dimension, 'cash_available': ?cashAvailable},
|
||||
queryParameters: {
|
||||
'dimension': dimension,
|
||||
'cash_available': ?cashAvailable,
|
||||
},
|
||||
);
|
||||
return Cached(
|
||||
RebalancePlan.fromJson(r.data ?? const {}),
|
||||
|
||||
Reference in New Issue
Block a user