Line data Source code
1 : // Copyright 2023 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'package:app_finance/_classes/herald/app_locale.dart';
5 : import 'package:app_finance/_classes/herald/app_start_of_month.dart';
6 : import 'package:app_finance/_classes/herald/app_start_of_week.dart';
7 : import 'package:app_finance/_classes/storage/app_preferences.dart';
8 : import 'package:app_finance/_classes/structure/abstract_app_data.dart';
9 : import 'package:app_finance/_classes/storage/app_data.dart';
10 : import 'package:app_finance/_classes/structure/currency/exchange.dart';
11 : import 'package:app_finance/_classes/structure/invoice_app_data.dart';
12 : import 'package:app_finance/_configs/budget_type.dart';
13 : import 'package:app_finance/_ext/date_time_ext.dart';
14 : import 'package:app_finance/_ext/int_ext.dart';
15 : import 'package:app_finance/_ext/string_ext.dart';
16 : import 'package:app_finance/_mixins/storage_mixin.dart';
17 : import 'package:material_ui/material_ui.dart';
18 : import 'package:flutter_currency_picker/flutter_currency_picker.dart';
19 :
20 : class BudgetAppData extends AbstractAppData with StorageMixin {
21 : double amount;
22 : Map<int, double> amountSet;
23 : String type;
24 : final int _month = DateTime.now().month;
25 :
26 3 : BudgetAppData({
27 : required super.title,
28 : super.uuid,
29 : super.progress = 0.0,
30 : super.color,
31 : super.icon,
32 : super.currency,
33 : super.updatedAt,
34 : super.createdAt,
35 : super.createdAtFormatted,
36 : amountLimit = 0.0,
37 : this.amountSet = const {},
38 : this.amount = 0.0,
39 : this.type = '',
40 : super.hidden,
41 : super.skip,
42 3 : }) : super(
43 : details: amountLimit,
44 : );
45 :
46 0 : @override
47 : String getClassName() => 'BudgetAppData';
48 :
49 0 : @override
50 : AppDataType getType() => AppDataType.budgets;
51 :
52 1 : @override
53 : BudgetAppData clone() {
54 1 : return BudgetAppData(
55 1 : title: super.title,
56 1 : type: type,
57 1 : uuid: super.uuid,
58 1 : progress: super.progress,
59 1 : color: super.color,
60 1 : icon: super.icon,
61 1 : currency: super.currency,
62 1 : createdAt: super.createdAt,
63 1 : amountLimit: super.details,
64 1 : amountSet: amountSet,
65 1 : amount: amount,
66 1 : hidden: super.hidden,
67 1 : skip: super.skip,
68 : );
69 : }
70 :
71 0 : factory BudgetAppData.fromJson(Map<String, dynamic> json) {
72 0 : return BudgetAppData(
73 0 : title: json['title'],
74 0 : type: json['type'] ?? AppBudgetType.month.name,
75 0 : uuid: json['uuid'],
76 0 : progress: 0.0 + json['progress'],
77 0 : color: json['color'] != null ? MaterialColor(json['color'], const <int, Color>{}) : null,
78 0 : icon: (json['icon'] as int?)?.toIcon(),
79 0 : currency: CurrencyProvider.find(json['currency']),
80 0 : updatedAt: DateTime.parse(json['updatedAt']),
81 0 : createdAt: DateTime.parse(json['createdAt']),
82 0 : amountLimit: 0.0 + json['amountLimit'],
83 0 : amountSet: json['amountSet'] != null ? json['amountSet'].toString().toMap<int, double>() : {},
84 0 : hidden: json['hidden'],
85 0 : skip: json['skip'] ?? false,
86 : );
87 : }
88 :
89 0 : @override
90 0 : Map<String, dynamic> toJson() => {
91 0 : ...super.toJson()..remove('description'),
92 0 : 'amountLimit': amountLimit,
93 0 : 'amountSet': amountSet.toString(),
94 0 : 'type': type,
95 : };
96 :
97 1 : @override
98 : double get details {
99 2 : if (super.details > 0 && super.details < 1) {
100 0 : return _relativeAmountLimit() - amount;
101 2 : } else if (amountLimit == 0) {
102 2 : return -amount;
103 0 : } else if (super.details > 0) {
104 0 : return amountLimit * (1 - progress);
105 : } else {
106 : return 0.0;
107 : }
108 : }
109 :
110 0 : String get detailsFormatted {
111 0 : if (super.details > 0 && super.details < 1) {
112 0 : final curr = (_relativeAmountLimit() - amount).toCurrency(currency: currency, withPattern: false);
113 0 : return '$curr ${AppLocale.labels.left}';
114 0 : } else if (super.details > 0) {
115 0 : return '${details.toCurrency(currency: currency, withPattern: false)} ${AppLocale.labels.left}';
116 : } else {
117 0 : return '${amount.toCurrency(currency: currency, withPattern: false)} ${AppLocale.labels.spent}';
118 : }
119 : }
120 :
121 0 : String _description() {
122 : String description = '';
123 0 : if (super.details > 0 && super.details < 1) {
124 0 : final percents = (amountLimit * 100).toStringAsFixed(2);
125 0 : description = '${(amount).toCurrency(currency: currency, withPattern: false)} /'
126 0 : ' ${_relativeAmountLimit().toCurrency(currency: currency, withPattern: false)} ($percents%)';
127 0 : } else if (super.details > 0) {
128 0 : description = '${(amountLimit * progress).toCurrency(currency: currency, withPattern: false)} /'
129 0 : ' ${(amountLimit).toCurrency(currency: currency, withPattern: false)}'
130 0 : '${multiplication > 1 ? ' (${multiplication.toStringAsFixed(2)} ${AppLocale.labels.coef})' : ''}';
131 : }
132 0 : if (type == AppBudgetType.year.name || type == AppBudgetType.week.name) {
133 0 : description += ', ${BudgetType.getLabel(type)}';
134 : }
135 : return description;
136 : }
137 :
138 0 : double get progressLeft => progress < 1 ? 1 - progress : 0.0;
139 :
140 12 : double get multiplication => amountSet[_month] ?? 1.0;
141 :
142 12 : double get amountLimit => super.details * multiplication;
143 2 : set amountLimit(double value) => super.details = value;
144 :
145 1 : DateTime getDateBoundary() {
146 1 : DateTime boundary = DateTime.now();
147 3 : if (type == AppBudgetType.year.name) {
148 2 : boundary = DateTime(boundary.year);
149 3 : } else if (type == AppBudgetType.week.name) {
150 1 : int boundaryDay = switch (AppPreferences.get(AppPreferences.prefWeekStartDay)) {
151 1 : AppStartOfWeek.MONDAY => DateTime.monday,
152 1 : AppStartOfWeek.SATURDAY => DateTime.saturday,
153 1 : AppStartOfWeek.SUNDAY => DateTime.sunday,
154 : _ => DateTime.monday,
155 : };
156 1 : boundary = boundary.getPreviousDay(boundaryDay);
157 : } else {
158 2 : boundary = boundary.getStartingDay(AppStartOfMonth.get());
159 : }
160 : return boundary;
161 : }
162 :
163 0 : double _relativeAmountLimit() {
164 0 : final ex = Exchange(store: getState());
165 0 : return amountLimit *
166 0 : getState()
167 0 : .getStream(AppDataType.invoice)
168 0 : .getTill(getDateBoundary().millisecondsSinceEpoch.toDouble())
169 0 : .cast<InvoiceAppData>()
170 0 : .where((e) => e.accountFrom == null)
171 0 : .fold(0.0, (v, e) => v + ex.reform(e.details, e.currency, currency));
172 : }
173 :
174 0 : @override
175 0 : String get description => _description();
176 :
177 0 : @override
178 0 : set description(String? value) => {};
179 : }
|