Skip to content
This repository has been archived by the owner on Dec 30, 2024. It is now read-only.

v0.13 — Scheduled Transactions & Templates + UnformattedAmount #37

Draft
wants to merge 21 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
314d65d
Implemented concrete models & methods
jasonlessenich May 25, 2024
1fd5cea
Added "special" `RecurringRule`s
jasonlessenich May 25, 2024
f090432
Added `UnformattedAmount`
jasonlessenich May 25, 2024
dfa03ee
Implemented `UnformattedAmount#format` & added tests
jasonlessenich May 25, 2024
a15d202
Added `UnformattedAmount#fromString`
jasonlessenich May 26, 2024
af91ae2
Added `UnformattedAmount#format` isNegative
jasonlessenich May 26, 2024
702049b
Added operator overloads for `UnformattedAmount`
jasonlessenich May 26, 2024
876aba4
Added more missing operator overloads
jasonlessenich May 26, 2024
d6e0e74
Added `UnformattedAmount#zero`
jasonlessenich May 26, 2024
323f71f
Removed assertion
jasonlessenich May 26, 2024
234fbab
Check if amount if negative and remove sign if so
jasonlessenich May 26, 2024
4c230ef
Implemented Transaction#getDisplayAmount
jasonlessenich May 27, 2024
5d6465b
fixed some errors
jasonlessenich May 27, 2024
5525d96
Added TransactionTemplate#getType
jasonlessenich May 28, 2024
a0794ad
Added cache-getter for templates and recurring transactions
jasonlessenich May 28, 2024
615f425
fixed some smaller bugs
jasonlessenich Jun 2, 2024
d9e5c1c
Made lastExecutedAt nullable
jasonlessenich Jun 2, 2024
3592414
Implemented backend fixes
jasonlessenich Jun 3, 2024
7003558
Renamed RecurringTransaction & all related terms to ScheduledTransact…
jasonlessenich Jun 5, 2024
9d2191d
Implemented TransactionTemplate#createTransaction
jasonlessenich Jun 5, 2024
cfe6aa3
Replaced TransactionTemplateId with just Id
jasonlessenich Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 27 additions & 22 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.13
- Added `UnformattedAmount`, which represents the backend's, still-to-be-formatted, amount and
balance representations

# 0.12
- Added `SessionPlatformType`
- Added `SessionInfo`, which is required for any form of session creation (login, register)
Expand All @@ -12,7 +16,7 @@
- Added Restrr#createUser

## 0.9.1
- Fixed use of `EntityId<E>`
- Fixed use of `EntityId<E>`

## 0.9
- Added `EntityCacheView`
Expand All @@ -24,36 +28,37 @@

## 0.8
- Cache accounts & currencies on startup
- Added `Restrr#getAccounts`
- Added `Restrr#getCurrencies`
- Added `Restrr#getAccounts`
- Added `Restrr#getCurrencies`
- Added entity-specific Ids:
- Added `EntityId<E>` methods: `get`, `retrieve`
- Added `AccountId`
- Added `TransactionId`
- Added `CurrencyId`
- Added `UserId`
- Added `PartialSessionId`
- Entities now feature the corresponding `EntityId<E>` instead of a `Id (int)`
- Added `EntityId<E>` methods: `get`, `retrieve`
- Added `AccountId`
- Added `TransactionId`
- Added `CurrencyId`
- Added `UserId`
- Added `PartialSessionId`
- Entities now feature the corresponding `EntityId<E>` instead of a `Id (int)`
- Added `Account`
- Added `AccountRoutes`
- Added `Account` methods: `delete`, `update`, `retrieveAllTransactions`
- Added `Restrr` methods: `createAccount`, `retrieveAccountById`, `retrieveAllAccounts`
- Added `AccountRoutes`
- Added `Account` methods: `delete`, `update`, `retrieveAllTransactions`
- Added `Restrr` methods: `createAccount`, `retrieveAccountById`, `retrieveAllAccounts`
- Added `Transaction`
- Added `TransactionRoutes`
- Added `Transaction` methods: `delete`, `update`
- Added `Restrr` methods: `createTransaction`, `retrieveTransactionById`, `retrieveAllTransactions`
- Added `TransactionRoutes`
- Added `Transaction` methods: `delete`, `update`
- Added `Restrr`
methods: `createTransaction`, `retrieveTransactionById`, `retrieveAllTransactions`
- Fixed `Session#delete` using a wrong route
- Unified entity deletion
- Implemented actual `RestrrError` error codes
- Added `ErrorResponse#apiCode`
- Made `ErrorResponse#reference` `dynamic`
- Added `ErrorResponse#apiCode`
- Made `ErrorResponse#reference` `dynamic`

## 0.7
- Restructured package (many breaking changes!)
- Split package into `api` (abstraction) and `internal` (implementation)
- Split package into `api` (abstraction) and `internal` (implementation)
- Added `Session`-based authentication
- Reworked `RestrrBuilder` to support new `Session`s
- Added `RestrrBuilder#refresh`
- Added `RestrrBuilder#refresh`
- Implemented Pagination (see `Paginated<T>`)

## 0.6.2
Expand Down Expand Up @@ -97,15 +102,15 @@
- Fixed `RestrrOptions` and added `isWeb` attribute
- Removed network check when no CookieJar is set (web)

## 0.3.1
## 0.3.1
- Added ability to customize & disable Cookie Jar

## 0.3
- Added `User#displayName`
- Added `Restrr#logout`
- Added `Restrr#register`
- Further refactored error handling
- Added `errorMap` to `ApiService#request` (and similar methods)
- Added `errorMap` to `ApiService#request` (and similar methods)
- Added more tests

## 0.2.1
Expand Down
5 changes: 5 additions & 0 deletions lib/restrr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@ export 'src/api/cache/entity_cache_view.dart';
/* [ /src/api/entities ] */
export 'src/api/entities/currency/currency.dart';
export 'src/api/entities/currency/custom_currency.dart';
export 'src/api/entities/currency/unformatted_amount.dart';
export 'src/api/entities/session/partial_session.dart';
export 'src/api/entities/session/session.dart';
export 'src/api/entities/session/session_platform.dart';
export 'src/api/entities/account.dart';
export 'src/api/entities/restrr_entity.dart';
export 'src/api/entities/transaction/scheduled/cron_pattern.dart';
export 'src/api/entities/transaction/scheduled/schedule_rule.dart';
export 'src/api/entities/transaction/scheduled/scheduled_transaction_template.dart';
export 'src/api/entities/transaction/transaction.dart';
export 'src/api/entities/transaction/transaction_template.dart';
export 'src/api/entities/transaction/transaction_type.dart';
export 'src/api/entities/user.dart';

Expand Down
6 changes: 3 additions & 3 deletions lib/src/api/entities/account.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ abstract class Account extends RestrrEntity<Account, AccountId> {
String get name;
String? get description;
String? get iban;
int get balance;
int get originalBalance;
UnformattedAmount get balance;
UnformattedAmount get originalBalance;
CurrencyId get currencyId;
DateTime get createdAt;

Future<bool> delete();

Future<Account> update({String? name, String? description, String? iban, int? originalBalance, Id? currencyId});
Future<Account> update({String? name, String? description, String? iban, UnformattedAmount? originalBalance, Id? currencyId});

Future<Paginated<Transaction>> retrieveAllTransactions({int page = 1, int limit = 25, bool forceRetrieve = false});
}
72 changes: 72 additions & 0 deletions lib/src/api/entities/currency/unformatted_amount.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import 'package:restrr/restrr.dart';

class UnformattedAmount {
static const UnformattedAmount zero = UnformattedAmount(0);

final int rawAmount;

const UnformattedAmount(this.rawAmount);

/// Removes all non-digit characters from the string and parses the result as an integer.
/// (e.g. '1,234.56€' -> 123456)
static UnformattedAmount fromString(String str) {
return UnformattedAmount(int.parse(str.replaceAll(RegExp(r'[^\d]'), '')));
}

static UnformattedAmount fromJson(dynamic json) {
if (json == null || json is! int) {
throw ArgumentError.value(json, 'json', 'Invalid JSON value for UnformattedCurrencyAmount. Expected an integer.');
}
return UnformattedAmount(json);
}

String format(int decimalPlaces, String decimalSeparator, {String? currencySymbol, String? thousandsSeparator}) {
bool isNegative = rawAmount < 0;
String amount = rawAmount.toString();
// remove sign
if (isNegative) {
amount = amount.substring(1);
}
if (amount.length <= decimalPlaces) {
amount = '0'.padLeft(decimalPlaces - amount.length + 1, '0') + amount;
}
amount =
amount.substring(0, amount.length - decimalPlaces) + decimalSeparator + amount.substring(amount.length - decimalPlaces);
if (thousandsSeparator != null) {
for (int i = amount.length - decimalPlaces - 4; i > 0; i -= 3) {
amount = amount.substring(0, i) + thousandsSeparator + amount.substring(i);
}
}
return '${isNegative ? '-' : ''}$amount${currencySymbol ?? ''}';
}

String formatWithCurrency(Currency currency, String decimalSeparator, {String? thousandsSeparator}) {
return format(currency.decimalPlaces, decimalSeparator,
currencySymbol: currency.symbol, thousandsSeparator: thousandsSeparator);
}

UnformattedAmount operator +(UnformattedAmount other) => UnformattedAmount(rawAmount + other.rawAmount);

UnformattedAmount operator -(UnformattedAmount other) => UnformattedAmount(rawAmount - other.rawAmount);

UnformattedAmount operator *(int other) => UnformattedAmount(rawAmount * other);

UnformattedAmount operator /(int other) => UnformattedAmount(rawAmount ~/ other);

bool operator <(UnformattedAmount other) => rawAmount < other.rawAmount;

bool operator <=(UnformattedAmount other) => rawAmount <= other.rawAmount;

bool operator >(UnformattedAmount other) => rawAmount > other.rawAmount;

bool operator >=(UnformattedAmount other) => rawAmount >= other.rawAmount;

@override
bool operator ==(Object other) => other is UnformattedAmount && rawAmount == other.rawAmount;

@override
int get hashCode => rawAmount.hashCode;

@override
String toString() => rawAmount.toString();
}
89 changes: 89 additions & 0 deletions lib/src/api/entities/transaction/scheduled/cron_pattern.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
class CronPattern {
final String? second;
final String? minute;
final String? hour;
final String? dayOfMonth;
final String? month;
final String? dayOfWeek;

const CronPattern({
required this.second,
required this.minute,
required this.hour,
required this.dayOfMonth,
required this.month,
required this.dayOfWeek,
});

static CronPattern? fromJson(Map<String, dynamic>? json) {
if (json == null) return null;
return CronPattern(
second: json['second'],
minute: json['minute'],
hour: json['hour'],
dayOfMonth: json['day_of_month'],
month: json['month'],
dayOfWeek: json['day_of_week'],
);
}

Map<String, dynamic> toJson() {
return {
if (second != null) 'second': second,
if (minute != null) 'minute': minute,
if (hour != null) 'hour': hour,
if (dayOfMonth != null) 'day_of_month': dayOfMonth,
if (month != null) 'month': month,
if (dayOfWeek != null) 'day_of_week': dayOfWeek,
};
}

CronPattern copyWith({
String? second,
String? minute,
String? hour,
String? dayOfMonth,
String? month,
String? dayOfWeek,
}) {
return CronPattern(
second: second ?? this.second,
minute: minute ?? this.minute,
hour: hour ?? this.hour,
dayOfMonth: dayOfMonth ?? this.dayOfMonth,
month: month ?? this.month,
dayOfWeek: dayOfWeek ?? this.dayOfWeek,
);
}
}

class CronPatternBuilder {
String? second;
String? minute;
String? hour;
String? dayOfMonth;
String? month;
String? dayOfWeek;

CronPatternBuilder();

// TODO: further implement builder

CronPatternBuilder at({String? second, String? minute, String? hour}) {
this.second = second;
this.minute = minute;
this.hour = hour;
return this;
}

CronPattern build() {
return CronPattern(
second: second,
minute: minute,
hour: hour,
dayOfMonth: dayOfMonth,
month: month,
dayOfWeek: dayOfWeek,
);
}
}
39 changes: 39 additions & 0 deletions lib/src/api/entities/transaction/scheduled/schedule_rule.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import '../../../../../restrr.dart';

class ScheduleRule {
static const ScheduleRule yearly = ScheduleRule(special: '@yearly');
static const ScheduleRule annually = ScheduleRule(special: '@annually');
static const ScheduleRule monthly = ScheduleRule(special: '@monthly');
static const ScheduleRule weekly = ScheduleRule(special: '@weekly');
static const ScheduleRule daily = ScheduleRule(special: '@daily');

final CronPattern? cronPattern;
final String? special;

const ScheduleRule({this.cronPattern, this.special});

static ScheduleRule fromJson(Map<String, dynamic>? json) {
if (json == null) throw ArgumentError.notNull('json');
return ScheduleRule(
cronPattern: CronPattern.fromJson(json['cron_pattern']),
special: json['special'],
);
}

Map<String, dynamic> toJson() {
return {
if (cronPattern != null) 'cron_pattern': cronPattern!.toJson(),
if (special != null) 'special': special,
};
}

ScheduleRule copyWith({
CronPattern? cronPattern,
String? special,
}) {
return ScheduleRule(
cronPattern: cronPattern ?? this.cronPattern,
special: special ?? this.special,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import '../../../../../restrr.dart';

abstract class ScheduledTransactionTemplateId extends EntityId<ScheduledTransactionTemplate> {}

abstract class ScheduledTransactionTemplate extends RestrrEntity<ScheduledTransactionTemplate, ScheduledTransactionTemplateId> {
@override
ScheduledTransactionTemplateId get id;

TransactionTemplateId get templateId;
DateTime? get lastExecutedAt;
DateTime? get nextExecutedAt;
ScheduleRule get scheduleRule;
DateTime get createdAt;

Future<bool> delete();

Future<ScheduledTransactionTemplate> update({
Id? templateId,
DateTime? lastExecutedAt,
ScheduleRule? scheduleRule,
});
}
7 changes: 5 additions & 2 deletions lib/src/api/entities/transaction/transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ abstract class Transaction extends RestrrEntity<Transaction, TransactionId> {

AccountId? get sourceId;
AccountId? get destinationId;
int get amount;
UnformattedAmount get amount;
CurrencyId get currencyId;
String get name;
String? get description;
Expand All @@ -17,17 +17,20 @@ abstract class Transaction extends RestrrEntity<Transaction, TransactionId> {
DateTime get executedAt;

TransactionType getType(Account current);
UnformattedAmount getDisplayAmount(Account current);

Future<bool> delete();

Future<Transaction> update({
Id? sourceId,
Id? destinationId,
int? amount,
UnformattedAmount? amount,
Id? currencyId,
String? name,
String? description,
Id? budgetId,
DateTime? executedAt,
});

Future<TransactionTemplate> createTemplate();
}
Loading
Loading