style(app): остальные экраны под новый визуальный язык и форматирование
Доходы, ребалансировка, налоги, импорт, цели, здоровье данных, правила, транзакции, категории, cashflow, pending: новые виджеты и токены темы, dart format. Тесты подстроены под новые модели.
This commit is contained in:
@@ -17,14 +17,20 @@ import '../../pending/data/pending_api.dart';
|
||||
|
||||
/// A candidate account for an import whose `account_id` the server could not resolve.
|
||||
class AccountSuggestion {
|
||||
const AccountSuggestion({required this.id, required this.name, this.broker, this.sourceId});
|
||||
const AccountSuggestion({
|
||||
required this.id,
|
||||
required this.name,
|
||||
this.broker,
|
||||
this.sourceId,
|
||||
});
|
||||
|
||||
final int id;
|
||||
final String name;
|
||||
final String? broker;
|
||||
final String? sourceId;
|
||||
|
||||
static AccountSuggestion fromJson(Map<String, dynamic> json) => AccountSuggestion(
|
||||
static AccountSuggestion fromJson(Map<String, dynamic> json) =>
|
||||
AccountSuggestion(
|
||||
id: asInt(json['id'])!,
|
||||
name: asString(json['name']) ?? '#${json['id']}',
|
||||
broker: asString(json['broker']),
|
||||
@@ -97,15 +103,15 @@ class ReconPosition {
|
||||
String get title => instrumentName ?? ticker ?? isin ?? '—';
|
||||
|
||||
static ReconPosition fromJson(Map<String, dynamic> json) => ReconPosition(
|
||||
matches: json['matches'] == true,
|
||||
instrumentId: asInt(json['instrument_id']),
|
||||
instrumentName: asString(json['instrument_name']),
|
||||
ticker: asString(json['ticker']),
|
||||
isin: asString(json['isin']),
|
||||
qtyReport: asString(json['qty_report']),
|
||||
qtyDerived: asString(json['qty_derived']),
|
||||
qtyDelta: asString(json['qty_delta']),
|
||||
);
|
||||
matches: json['matches'] == true,
|
||||
instrumentId: asInt(json['instrument_id']),
|
||||
instrumentName: asString(json['instrument_name']),
|
||||
ticker: asString(json['ticker']),
|
||||
isin: asString(json['isin']),
|
||||
qtyReport: asString(json['qty_report']),
|
||||
qtyDerived: asString(json['qty_derived']),
|
||||
qtyDelta: asString(json['qty_delta']),
|
||||
);
|
||||
}
|
||||
|
||||
class ReconCash {
|
||||
@@ -124,12 +130,12 @@ class ReconCash {
|
||||
final String? delta;
|
||||
|
||||
static ReconCash fromJson(Map<String, dynamic> json) => ReconCash(
|
||||
currency: asString(json['currency']) ?? 'RUB',
|
||||
matches: json['matches'] == true,
|
||||
balanceReport: asString(json['balance_report']),
|
||||
balanceDerived: asString(json['balance_derived']),
|
||||
delta: asString(json['delta']),
|
||||
);
|
||||
currency: asString(json['currency']) ?? 'RUB',
|
||||
matches: json['matches'] == true,
|
||||
balanceReport: asString(json['balance_report']),
|
||||
balanceDerived: asString(json['balance_derived']),
|
||||
delta: asString(json['delta']),
|
||||
);
|
||||
}
|
||||
|
||||
class Reconciliation {
|
||||
@@ -197,23 +203,23 @@ class SampleEvent {
|
||||
final String? description;
|
||||
|
||||
static SampleEvent fromJson(Map<String, dynamic> json) => SampleEvent(
|
||||
lineNo: asInt(json['line_no']) ?? 0,
|
||||
kind: asString(json['kind']) ?? 'other',
|
||||
isDuplicate: json['is_duplicate'] == true,
|
||||
tradeDate: asDate(json['trade_date']),
|
||||
settleDate: asDate(json['settle_date']),
|
||||
instrumentKey: asString(json['instrument_key']),
|
||||
instrumentName: asString(json['instrument_name']),
|
||||
instrumentId: asInt(json['instrument_id']),
|
||||
quantity: asString(json['quantity']),
|
||||
price: asString(json['price']),
|
||||
amount: asString(json['amount']),
|
||||
currency: asString(json['currency']),
|
||||
fee: asString(json['fee']),
|
||||
tradeNo: asString(json['trade_no']),
|
||||
dedupeKey: asString(json['dedupe_key']),
|
||||
description: asString(json['description']),
|
||||
);
|
||||
lineNo: asInt(json['line_no']) ?? 0,
|
||||
kind: asString(json['kind']) ?? 'other',
|
||||
isDuplicate: json['is_duplicate'] == true,
|
||||
tradeDate: asDate(json['trade_date']),
|
||||
settleDate: asDate(json['settle_date']),
|
||||
instrumentKey: asString(json['instrument_key']),
|
||||
instrumentName: asString(json['instrument_name']),
|
||||
instrumentId: asInt(json['instrument_id']),
|
||||
quantity: asString(json['quantity']),
|
||||
price: asString(json['price']),
|
||||
amount: asString(json['amount']),
|
||||
currency: asString(json['currency']),
|
||||
fee: asString(json['fee']),
|
||||
tradeNo: asString(json['trade_no']),
|
||||
dedupeKey: asString(json['dedupe_key']),
|
||||
description: asString(json['description']),
|
||||
);
|
||||
}
|
||||
|
||||
/// `ImportPreview` and `ImportSummary` in one class: the summary is the same object without
|
||||
@@ -281,34 +287,38 @@ class ImportPreview {
|
||||
bool get canDelete => !isCommitted;
|
||||
|
||||
static ImportPreview fromJson(Map<String, dynamic> json) => ImportPreview(
|
||||
id: asInt(json['id'])!,
|
||||
filename: asString(json['filename']) ?? 'без имени',
|
||||
parseStatus: asString(json['parse_status']) ?? 'uploaded',
|
||||
broker: asString(json['broker']),
|
||||
sha256: asString(json['sha256']),
|
||||
sizeBytes: asInt(json['size_bytes']),
|
||||
parserName: asString(json['parser_name']),
|
||||
parserVersion: asString(json['parser_version']),
|
||||
error: asString(json['error']),
|
||||
duplicateOfId: asInt(json['duplicate_of_id']),
|
||||
accountExternalId: asString(json['account_external_id']),
|
||||
accountId: asInt(json['account_id']),
|
||||
accountName: asString(json['account_name']),
|
||||
accountSuggestions:
|
||||
asList(json['account_suggestions']).map(AccountSuggestion.fromJson).toList(),
|
||||
periodFrom: asDate(json['period_from']),
|
||||
periodTo: asDate(json['period_to']),
|
||||
uploadedAt: asDate(json['uploaded_at']),
|
||||
committedAt: asDate(json['committed_at']),
|
||||
counts: ImportCounts.fromJson(asMap(json['counts'])),
|
||||
pendingInstruments:
|
||||
asList(json['pending_instruments']).map(PendingInstrument.fromJson).toList(),
|
||||
reconciliation: Reconciliation.fromJson(asMap(json['reconciliation'])),
|
||||
warnings: json['warnings'] is List
|
||||
? (json['warnings'] as List).map((e) => e.toString()).toList()
|
||||
: const [],
|
||||
sampleEvents: asList(json['sample_events']).map(SampleEvent.fromJson).toList(),
|
||||
);
|
||||
id: asInt(json['id'])!,
|
||||
filename: asString(json['filename']) ?? 'без имени',
|
||||
parseStatus: asString(json['parse_status']) ?? 'uploaded',
|
||||
broker: asString(json['broker']),
|
||||
sha256: asString(json['sha256']),
|
||||
sizeBytes: asInt(json['size_bytes']),
|
||||
parserName: asString(json['parser_name']),
|
||||
parserVersion: asString(json['parser_version']),
|
||||
error: asString(json['error']),
|
||||
duplicateOfId: asInt(json['duplicate_of_id']),
|
||||
accountExternalId: asString(json['account_external_id']),
|
||||
accountId: asInt(json['account_id']),
|
||||
accountName: asString(json['account_name']),
|
||||
accountSuggestions: asList(json['account_suggestions'])
|
||||
.map(AccountSuggestion.fromJson)
|
||||
.toList(),
|
||||
periodFrom: asDate(json['period_from']),
|
||||
periodTo: asDate(json['period_to']),
|
||||
uploadedAt: asDate(json['uploaded_at']),
|
||||
committedAt: asDate(json['committed_at']),
|
||||
counts: ImportCounts.fromJson(asMap(json['counts'])),
|
||||
pendingInstruments: asList(json['pending_instruments'])
|
||||
.map(PendingInstrument.fromJson)
|
||||
.toList(),
|
||||
reconciliation: Reconciliation.fromJson(asMap(json['reconciliation'])),
|
||||
warnings: json['warnings'] is List
|
||||
? (json['warnings'] as List).map((e) => e.toString()).toList()
|
||||
: const [],
|
||||
sampleEvents: asList(json['sample_events'])
|
||||
.map(SampleEvent.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// What `POST /imports/{id}/commit` reports back.
|
||||
@@ -336,16 +346,16 @@ class ImportResult {
|
||||
final bool metricsRefreshed;
|
||||
|
||||
static ImportResult fromJson(Map<String, dynamic> json) => ImportResult(
|
||||
importId: asInt(json['import_id']) ?? 0,
|
||||
committed: json['committed'] == true,
|
||||
eventsCreated: asInt(json['events_created']) ?? 0,
|
||||
eventsUpdated: asInt(json['events_updated']) ?? 0,
|
||||
eventsSkipped: asInt(json['events_skipped']) ?? 0,
|
||||
eventsShadow: asInt(json['events_shadow']) ?? 0,
|
||||
pendingInstruments: asInt(json['pending_instruments']) ?? 0,
|
||||
reconciliation: Reconciliation.fromJson(asMap(json['reconciliation'])),
|
||||
metricsRefreshed: json['metrics_refreshed'] == true,
|
||||
);
|
||||
importId: asInt(json['import_id']) ?? 0,
|
||||
committed: json['committed'] == true,
|
||||
eventsCreated: asInt(json['events_created']) ?? 0,
|
||||
eventsUpdated: asInt(json['events_updated']) ?? 0,
|
||||
eventsSkipped: asInt(json['events_skipped']) ?? 0,
|
||||
eventsShadow: asInt(json['events_shadow']) ?? 0,
|
||||
pendingInstruments: asInt(json['pending_instruments']) ?? 0,
|
||||
reconciliation: Reconciliation.fromJson(asMap(json['reconciliation'])),
|
||||
metricsRefreshed: json['metrics_refreshed'] == true,
|
||||
);
|
||||
}
|
||||
|
||||
/// A report chosen by the user. On web `file_picker` can only hand over [bytes]; on desktop
|
||||
@@ -366,14 +376,14 @@ class ImportsApi {
|
||||
|
||||
static const _base = '/api/v1/imports';
|
||||
|
||||
Future<List<ImportPreview>> list({int limit = 50, int offset = 0, String? status}) async {
|
||||
Future<List<ImportPreview>> list({
|
||||
int limit = 50,
|
||||
int offset = 0,
|
||||
String? status,
|
||||
}) async {
|
||||
final r = await _dio.get<List<dynamic>>(
|
||||
_base,
|
||||
queryParameters: {
|
||||
'limit': limit,
|
||||
'offset': offset,
|
||||
'status': ?status,
|
||||
},
|
||||
queryParameters: {'limit': limit, 'offset': offset, 'status': ?status},
|
||||
);
|
||||
return (r.data ?? const [])
|
||||
.map((e) => ImportPreview.fromJson(Map<String, dynamic>.from(e as Map)))
|
||||
@@ -385,7 +395,11 @@ class ImportsApi {
|
||||
return ImportPreview.fromJson(r.data!);
|
||||
}
|
||||
|
||||
Future<ImportPreview> upload(PickedReport report, {int? accountId, String? parser}) async {
|
||||
Future<ImportPreview> upload(
|
||||
PickedReport report, {
|
||||
int? accountId,
|
||||
String? parser,
|
||||
}) async {
|
||||
final bytes = report.bytes;
|
||||
final form = FormData.fromMap({
|
||||
'file': bytes != null
|
||||
@@ -404,11 +418,14 @@ class ImportsApi {
|
||||
bool confirmDuplicates = false,
|
||||
bool dryRun = false,
|
||||
}) async {
|
||||
final r = await _dio.post<Map<String, dynamic>>('$_base/$id/commit', data: {
|
||||
'account_id': ?accountId,
|
||||
'confirm_duplicates': confirmDuplicates,
|
||||
'dry_run': dryRun,
|
||||
});
|
||||
final r = await _dio.post<Map<String, dynamic>>(
|
||||
'$_base/$id/commit',
|
||||
data: {
|
||||
'account_id': ?accountId,
|
||||
'confirm_duplicates': confirmDuplicates,
|
||||
'dry_run': dryRun,
|
||||
},
|
||||
);
|
||||
return ImportResult.fromJson(r.data ?? const {});
|
||||
}
|
||||
|
||||
@@ -438,4 +455,5 @@ List<Map<String, dynamic>> asList(Object? v) => v is List
|
||||
? v.whereType<Map>().map((e) => Map<String, dynamic>.from(e)).toList()
|
||||
: const [];
|
||||
|
||||
Map<String, dynamic>? asMap(Object? v) => v is Map ? Map<String, dynamic>.from(v) : null;
|
||||
Map<String, dynamic>? asMap(Object? v) =>
|
||||
v is Map ? Map<String, dynamic>.from(v) : null;
|
||||
|
||||
Reference in New Issue
Block a user