accounts creation
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful

This commit is contained in:
Stephan D
2026-01-23 00:13:43 +01:00
parent b677d37b99
commit 218c4e20b3
64 changed files with 1641 additions and 338 deletions

View File

@@ -0,0 +1,32 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:pshared/data/dto/describable.dart';
import 'package:pshared/data/dto/ledger/type.dart';
part 'create.g.dart';
@JsonSerializable()
class CreateLedgerAccountRequest {
final Map<String, String>? metadata;
final String currency;
final bool allowNegative;
final bool isSettlement;
final DescribableDTO describable;
final String? ownerRef;
final LedgerAccountTypeDTO accountType;
const CreateLedgerAccountRequest({
this.metadata,
required this.currency,
required this.allowNegative,
required this.isSettlement,
required this.describable,
required this.accountType,
this.ownerRef,
});
factory CreateLedgerAccountRequest.fromJson(Map<String, dynamic> json) => _$CreateLedgerAccountRequestFromJson(json);
Map<String, dynamic> toJson() => _$CreateLedgerAccountRequestToJson(this);
}

View File

@@ -1,6 +1,5 @@
import 'package:json_annotation/json_annotation.dart';
part 'base.g.dart';

View File

@@ -0,0 +1,25 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:pshared/data/dto/describable.dart';
import 'package:pshared/data/dto/wallet/chain_asset.dart';
part 'create.g.dart';
@JsonSerializable()
class CreateWalletRequest {
final Map<String, String>? metadata;
final DescribableDTO describable;
final String? ownerRef;
final ChainAssetDTO asset;
const CreateWalletRequest({
this.metadata,
required this.asset,
required this.describable,
this.ownerRef,
});
factory CreateWalletRequest.fromJson(Map<String, dynamic> json) => _$CreateWalletRequestFromJson(json);
Map<String, dynamic> toJson() => _$CreateWalletRequestToJson(this);
}

View File

@@ -60,22 +60,23 @@ class WalletsController with ChangeNotifier {
String? get selectedWalletRef => _selectedWalletRef;
void selectWallet(Wallet wallet) => selectWalletByRef(wallet.id);
void selectWallet(Wallet wallet, {bool allowHidden = false}) =>
selectWalletByRef(wallet.id, allowHidden: allowHidden);
void selectWalletByRef(String walletRef) {
void selectWalletByRef(String walletRef, {bool allowHidden = false}) {
if (_selectedWalletRef == walletRef) return;
// Prevent selecting a hidden wallet
if (!_visibleWalletRefs.contains(walletRef)) return;
if (!allowHidden && !_visibleWalletRefs.contains(walletRef)) return;
_selectedWalletRef = walletRef;
notifyListeners();
}
/// Toggle wallet visibility
void toggleVisibility(String walletId) {
final existed = _visibleWalletRefs.remove(walletId);
if (!existed) _visibleWalletRefs.add(walletId);
void toggleVisibility(String accountRef) {
final existed = _visibleWalletRefs.remove(accountRef);
if (!existed) _visibleWalletRefs.add(accountRef);
_selectedWalletRef = _resolveSelectedId(
currentRef: _selectedWalletRef,

View File

@@ -2,6 +2,8 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:pshared/data/dto/describable.dart';
import 'package:pshared/data/dto/ledger/balance.dart';
import 'package:pshared/data/dto/ledger/status.dart';
import 'package:pshared/data/dto/ledger/type.dart';
part 'account.g.dart';
@@ -12,9 +14,9 @@ class LedgerAccountDTO {
final String organizationRef;
final String? ownerRef;
final String accountCode;
final String accountType;
final LedgerAccountTypeDTO accountType;
final String currency;
final String status;
final LedgerAccountStatusDTO status;
final bool allowNegative;
final bool isSettlement;
final Map<String, String>? metadata;

View File

@@ -0,0 +1,13 @@
import 'package:json_annotation/json_annotation.dart';
enum LedgerAccountStatusDTO {
@JsonValue('unspecified')
unspecified,
@JsonValue('active')
active,
@JsonValue('frozen')
frozen,
}

View File

@@ -0,0 +1,19 @@
import 'package:json_annotation/json_annotation.dart';
enum LedgerAccountTypeDTO {
@JsonValue('unspecified')
unspecified,
@JsonValue('asset')
asset,
@JsonValue('liability')
liability,
@JsonValue('revenue')
revenue,
@JsonValue('expense')
expense,
}

View File

@@ -1,20 +1,21 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:pshared/data/dto/wallet/chain_asset.dart';
part 'asset.g.dart';
@JsonSerializable()
class WalletAssetDTO {
final String chain;
final String tokenSymbol;
class WalletAssetDTO extends ChainAssetDTO {
final String contractAddress;
const WalletAssetDTO({
required this.chain,
required this.tokenSymbol,
required super.chain,
required super.tokenSymbol,
required this.contractAddress,
});
factory WalletAssetDTO.fromJson(Map<String, dynamic> json) => _$WalletAssetDTOFromJson(json);
@override
Map<String, dynamic> toJson() => _$WalletAssetDTOToJson(this);
}

View File

@@ -0,0 +1,18 @@
import 'package:json_annotation/json_annotation.dart';
part 'chain_asset.g.dart';
@JsonSerializable()
class ChainAssetDTO {
final String chain;
final String tokenSymbol;
const ChainAssetDTO({
required this.chain,
required this.tokenSymbol,
});
factory ChainAssetDTO.fromJson(Map<String, dynamic> json) => _$ChainAssetDTOFromJson(json);
Map<String, dynamic> toJson() => _$ChainAssetDTOToJson(this);
}

View File

@@ -1,6 +1,8 @@
import 'package:pshared/data/dto/ledger/account.dart';
import 'package:pshared/data/mapper/describable.dart';
import 'package:pshared/data/mapper/ledger/balance.dart';
import 'package:pshared/data/mapper/ledger/status.dart';
import 'package:pshared/data/mapper/ledger/type.dart';
import 'package:pshared/models/ledger/account.dart';
@@ -10,9 +12,9 @@ extension LedgerAccountDTOMapper on LedgerAccountDTO {
organizationRef: organizationRef,
ownerRef: ownerRef,
accountCode: accountCode,
accountType: accountType,
accountType: accountType.toDomain(),
currency: currency,
status: status,
status: status.toDomain(),
allowNegative: allowNegative,
isSettlement: isSettlement,
metadata: metadata,
@@ -29,9 +31,9 @@ extension LedgerAccountModelMapper on LedgerAccount {
organizationRef: organizationRef,
ownerRef: ownerRef,
accountCode: accountCode,
accountType: accountType,
accountType: accountType.toDTO(),
currency: currency,
status: status,
status: status.toDTO(),
allowNegative: allowNegative,
isSettlement: isSettlement,
metadata: metadata,

View File

@@ -0,0 +1,29 @@
import 'package:pshared/data/dto/ledger/status.dart';
import 'package:pshared/models/ledger/status.dart';
extension LedgerAccountStatusDTOMapper on LedgerAccountStatusDTO {
LedgerAccountStatus toDomain() {
switch (this) {
case LedgerAccountStatusDTO.unspecified:
return LedgerAccountStatus.unspecified;
case LedgerAccountStatusDTO.active:
return LedgerAccountStatus.active;
case LedgerAccountStatusDTO.frozen:
return LedgerAccountStatus.frozen;
}
}
}
extension LedgerAccountStatusMapper on LedgerAccountStatus {
LedgerAccountStatusDTO toDTO() {
switch (this) {
case LedgerAccountStatus.unspecified:
return LedgerAccountStatusDTO.unspecified;
case LedgerAccountStatus.active:
return LedgerAccountStatusDTO.active;
case LedgerAccountStatus.frozen:
return LedgerAccountStatusDTO.frozen;
}
}
}

View File

@@ -0,0 +1,37 @@
import 'package:pshared/data/dto/ledger/type.dart';
import 'package:pshared/models/ledger/type.dart';
extension LedgerAccountTypeDTOMapper on LedgerAccountTypeDTO {
LedgerAccountType toDomain() {
switch (this) {
case LedgerAccountTypeDTO.unspecified:
return LedgerAccountType.unspecified;
case LedgerAccountTypeDTO.asset:
return LedgerAccountType.asset;
case LedgerAccountTypeDTO.liability:
return LedgerAccountType.liability;
case LedgerAccountTypeDTO.revenue:
return LedgerAccountType.revenue;
case LedgerAccountTypeDTO.expense:
return LedgerAccountType.expense;
}
}
}
extension LedgerAccountTypeModelMapper on LedgerAccountType {
LedgerAccountTypeDTO toDTO() {
switch (this) {
case LedgerAccountType.unspecified:
return LedgerAccountTypeDTO.unspecified;
case LedgerAccountType.asset:
return LedgerAccountTypeDTO.asset;
case LedgerAccountType.liability:
return LedgerAccountTypeDTO.liability;
case LedgerAccountType.revenue:
return LedgerAccountTypeDTO.revenue;
case LedgerAccountType.expense:
return LedgerAccountTypeDTO.expense;
}
}
}

View File

@@ -0,0 +1,18 @@
import 'package:pshared/data/dto/wallet/chain_asset.dart';
import 'package:pshared/data/mapper/payment/enums.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
extension ChainAssetDTOMapper on ChainAssetDTO {
ChainAsset toDomain() => ChainAsset(
chain: chainNetworkFromValue(chain),
tokenSymbol: tokenSymbol,
);
}
extension ChainAssetMapper on ChainAsset {
ChainAssetDTO toDTO() => ChainAssetDTO(
chain: chainNetworkToValue(chain),
tokenSymbol: tokenSymbol,
);
}

View File

@@ -1,5 +1,7 @@
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/ledger/balance.dart';
import 'package:pshared/models/ledger/status.dart';
import 'package:pshared/models/ledger/type.dart';
class LedgerAccount implements Describable {
@@ -7,9 +9,9 @@ class LedgerAccount implements Describable {
final String organizationRef;
final String? ownerRef;
final String accountCode;
final String accountType;
final LedgerAccountType accountType;
final String currency;
final String status;
final LedgerAccountStatus status;
final bool allowNegative;
final bool isSettlement;
final Map<String, String>? metadata;

View File

@@ -0,0 +1,5 @@
enum LedgerAccountStatus {
unspecified,
active,
frozen,
}

View File

@@ -0,0 +1,7 @@
enum LedgerAccountType {
unspecified,
asset,
liability,
revenue,
expense,
}

View File

@@ -1,14 +1,12 @@
import 'package:pshared/models/payment/chain_network.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
class WalletAsset {
final ChainNetwork chain;
final String tokenSymbol;
class WalletAsset extends ChainAsset {
final String contractAddress;
const WalletAsset({
required this.chain,
required this.tokenSymbol,
required super.chain,
required super.tokenSymbol,
required this.contractAddress,
});
}

View File

@@ -0,0 +1,12 @@
import 'package:pshared/models/payment/chain_network.dart';
class ChainAsset {
final ChainNetwork chain;
final String tokenSymbol;
const ChainAsset({
required this.chain,
required this.tokenSymbol,
});
}

View File

@@ -6,6 +6,7 @@ import 'package:pshared/models/organization/employee.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/accounts/employees.dart';
import 'package:pshared/utils/exception.dart';
class EmployeesProvider extends ChangeNotifier {
@@ -46,10 +47,7 @@ class EmployeesProvider extends ChangeNotifier {
error: null,
);
} catch (e) {
_employees = _employees.copyWith(
error: e is Exception ? e : Exception('Unknown error: ${e.toString()}'),
isLoading: false,
);
_employees = _employees.copyWith(error: toException(e), isLoading: false);
}
notifyListeners();

View File

@@ -4,8 +4,10 @@ import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/currency.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/resource.dart';
@@ -159,6 +161,30 @@ class LedgerAccountsProvider with ChangeNotifier {
}
}
Future<void> create({
required Describable describable,
required Currency currency,
String? ownerRef,
}) async {
final org = _organizations;
if (org == null || !org.isOrganizationSet) return;
_applyResource(_resource.copyWith(isLoading: true, error: null), notify: true);
try {
await _service.create(
organizationRef: org.current.id,
currency: currency,
describable: describable,
ownerRef: ownerRef,
);
await loadAccountsWithBalances();
} catch (e) {
_applyResource(_resource.copyWith(isLoading: false, error: toException(e)), notify: true);
rethrow;
}
}
// ---------- internals ----------
void _applyResource(Resource<List<LedgerAccount>> newResource, {required bool notify}) {

View File

@@ -5,7 +5,9 @@ import 'package:flutter/foundation.dart';
import 'package:collection/collection.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/provider/organizations.dart';
import 'package:pshared/provider/resource.dart';
import 'package:pshared/service/payment/wallets.dart';
@@ -159,6 +161,30 @@ class WalletsProvider with ChangeNotifier {
}
}
Future<void> create({
required Describable describable,
required ChainAsset asset,
required String? ownerRef,
}) async {
final org = _organizations;
if (org == null || !org.isOrganizationSet) return;
_applyResource(_resource.copyWith(isLoading: true, error: null), notify: true);
try {
await _service.create(
organizationRef: org.current.id,
describable: describable,
asset: asset,
ownerRef: ownerRef,
);
await loadWalletsWithBalances();
} catch (e) {
_applyResource(_resource.copyWith(isLoading: false, error: toException(e)), notify: true);
rethrow;
}
}
// ---------- internals ----------
void _applyResource(Resource<List<Wallet>> newResource, {required bool notify}) {

View File

@@ -1,11 +1,18 @@
import 'package:pshared/api/requests/ledger/create.dart';
import 'package:pshared/api/responses/ledger/accounts.dart';
import 'package:pshared/api/responses/ledger/balance.dart';
import 'package:pshared/data/mapper/describable.dart';
import 'package:pshared/data/mapper/ledger/account.dart';
import 'package:pshared/data/mapper/ledger/balance.dart';
import 'package:pshared/data/mapper/ledger/type.dart';
import 'package:pshared/models/currency.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/ledger/account.dart';
import 'package:pshared/models/ledger/balance.dart';
import 'package:pshared/models/ledger/type.dart';
import 'package:pshared/service/authorization/service.dart';
import 'package:pshared/service/services.dart';
import 'package:pshared/utils/currency.dart';
class LedgerService {
@@ -29,4 +36,22 @@ class LedgerService {
);
return LedgerBalanceResponse.fromJson(json).balance.toDomain();
}
Future<void> create({
required String organizationRef,
required Describable describable,
required String? ownerRef,
required Currency currency,
}) async => AuthorizationService.getPOSTResponse(
_objectType,
'/$organizationRef',
CreateLedgerAccountRequest(
describable: describable.toDTO(),
ownerRef: ownerRef,
allowNegative: false,
isSettlement: false,
accountType: LedgerAccountType.asset.toDTO(),
currency: currencyCodeToString(currency),
).toJson(),
);
}

View File

@@ -1,11 +1,19 @@
import 'package:pshared/data/mapper/wallet/ui.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/service/wallet.dart' as shared_wallet_service;
abstract class WalletsService {
Future<List<Wallet>> getWallets(String organizationRef);
Future<double> getBalance(String organizationRef, String walletRef);
Future<void> create({
required String organizationRef,
required Describable describable,
required ChainAsset asset,
required String? ownerRef,
});
}
class ApiWalletsService implements WalletsService {
@@ -24,4 +32,17 @@ class ApiWalletsService implements WalletsService {
final amount = balance.available?.amount;
return amount == null ? 0 : double.tryParse(amount) ?? 0;
}
@override
Future<void> create({
required String organizationRef,
required Describable describable,
required ChainAsset asset,
required String? ownerRef,
}) => shared_wallet_service.WalletService.create(
organizationRef: organizationRef,
describable: describable,
asset: asset,
ownerRef: ownerRef,
);
}

View File

@@ -1,7 +1,12 @@
import 'package:pshared/api/requests/wallet/create.dart';
import 'package:pshared/api/responses/wallet_balance.dart';
import 'package:pshared/api/responses/wallets.dart';
import 'package:pshared/data/mapper/describable.dart';
import 'package:pshared/data/mapper/wallet/chain_asset.dart';
import 'package:pshared/data/mapper/wallet/response.dart';
import 'package:pshared/models/describable.dart';
import 'package:pshared/models/wallet/balance.dart';
import 'package:pshared/models/wallet/chain_asset.dart';
import 'package:pshared/models/wallet/wallet.dart';
import 'package:pshared/service/authorization/service.dart';
import 'package:pshared/service/services.dart';
@@ -28,4 +33,19 @@ class WalletService {
);
return WalletBalanceResponse.fromJson(json).toDomain();
}
static Future<void> create({
required String organizationRef,
required Describable describable,
required ChainAsset asset,
required String? ownerRef,
}) async => AuthorizationService.getPOSTResponse(
_objectType,
'/$organizationRef',
CreateWalletRequest(
asset: asset.toDTO(),
describable: describable.toDTO(),
ownerRef: ownerRef,
).toJson(),
);
}