From 55f86ce09c320e94ca5ac8b026e0cb15c131e63d Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Thu, 22 Sep 2022 13:35:49 +0300 Subject: [PATCH] Transactions --- lib/data/models/transaction_model.dart | 13 +- lib/data/models/transaction_model.g.dart | 51 +++-- lib/ui/transaction/view/transaction_view.dart | 174 +++++++++++++++++- lib/ui/vendor/vendor_list_item.dart | 13 +- lib/ui/vendor/vendor_list_vm.dart | 5 +- 5 files changed, 227 insertions(+), 29 deletions(-) diff --git a/lib/data/models/transaction_model.dart b/lib/data/models/transaction_model.dart index 405b65657..4e9cc6e61 100644 --- a/lib/data/models/transaction_model.dart +++ b/lib/data/models/transaction_model.dart @@ -85,6 +85,7 @@ abstract class TransactionEntity extends Object invoiceIds: '', statusId: '', baseType: TYPE_DEPOSIT, + transactionId: 0, ); } @@ -105,7 +106,7 @@ abstract class TransactionEntity extends Object @BuiltValueField(wireName: 'category_type') String get category; - @BuiltValueField(wireName: 'baseType') + @BuiltValueField(wireName: 'base_type') String get baseType; String get date; @@ -118,12 +119,18 @@ abstract class TransactionEntity extends Object @BuiltValueField(wireName: 'status_id') String get statusId; + //@BuiltValueField(wireName: 'ninja_category_id') + //String get categoryId; + @BuiltValueField(wireName: 'invoice_ids') String get invoiceIds; @BuiltValueField(wireName: 'expense_id') String get expenseId; + @BuiltValueField(wireName: 'transaction_id') + int get transactionId; + //@BuiltValueField(wireName: 'is_matched') //bool get isMached; @@ -226,9 +233,7 @@ abstract class TransactionEntity extends Object if (response == 0) { // STARTER: sort default - do not remove comment - return transactionA.description - .toLowerCase() - .compareTo(transactionB.description.toLowerCase()); + return transactionA.transactionId.compareTo(transactionB.transactionId); } else { return response; } diff --git a/lib/data/models/transaction_model.g.dart b/lib/data/models/transaction_model.g.dart index 473549202..3da710b46 100644 --- a/lib/data/models/transaction_model.g.dart +++ b/lib/data/models/transaction_model.g.dart @@ -131,7 +131,7 @@ class _$TransactionEntitySerializer 'category_type', serializers.serialize(object.category, specifiedType: const FullType(String)), - 'baseType', + 'base_type', serializers.serialize(object.baseType, specifiedType: const FullType(String)), 'date', @@ -151,6 +151,9 @@ class _$TransactionEntitySerializer 'expense_id', serializers.serialize(object.expenseId, specifiedType: const FullType(String)), + 'transaction_id', + serializers.serialize(object.transactionId, + specifiedType: const FullType(int)), 'created_at', serializers.serialize(object.createdAt, specifiedType: const FullType(int)), @@ -219,7 +222,7 @@ class _$TransactionEntitySerializer result.category = serializers.deserialize(value, specifiedType: const FullType(String)) as String; break; - case 'baseType': + case 'base_type': result.baseType = serializers.deserialize(value, specifiedType: const FullType(String)) as String; break; @@ -247,6 +250,10 @@ class _$TransactionEntitySerializer result.expenseId = serializers.deserialize(value, specifiedType: const FullType(String)) as String; break; + case 'transaction_id': + result.transactionId = serializers.deserialize(value, + specifiedType: const FullType(int)) as int; + break; case 'isChanged': result.isChanged = serializers.deserialize(value, specifiedType: const FullType(bool)) as bool; @@ -549,6 +556,8 @@ class _$TransactionEntity extends TransactionEntity { @override final String expenseId; @override + final int transactionId; + @override final bool isChanged; @override final int createdAt; @@ -580,6 +589,7 @@ class _$TransactionEntity extends TransactionEntity { this.statusId, this.invoiceIds, this.expenseId, + this.transactionId, this.isChanged, this.createdAt, this.updatedAt, @@ -608,6 +618,8 @@ class _$TransactionEntity extends TransactionEntity { invoiceIds, 'TransactionEntity', 'invoiceIds'); BuiltValueNullFieldError.checkNotNull( expenseId, 'TransactionEntity', 'expenseId'); + BuiltValueNullFieldError.checkNotNull( + transactionId, 'TransactionEntity', 'transactionId'); BuiltValueNullFieldError.checkNotNull( createdAt, 'TransactionEntity', 'createdAt'); BuiltValueNullFieldError.checkNotNull( @@ -639,6 +651,7 @@ class _$TransactionEntity extends TransactionEntity { statusId == other.statusId && invoiceIds == other.invoiceIds && expenseId == other.expenseId && + transactionId == other.transactionId && isChanged == other.isChanged && createdAt == other.createdAt && updatedAt == other.updatedAt && @@ -670,21 +683,24 @@ class _$TransactionEntity extends TransactionEntity { $jc( $jc( $jc( - 0, - amount + $jc( + 0, + amount + .hashCode), + currencyId .hashCode), - currencyId + category .hashCode), - category + baseType .hashCode), - baseType - .hashCode), - date.hashCode), - bankAccountId.hashCode), - description.hashCode), - statusId.hashCode), - invoiceIds.hashCode), - expenseId.hashCode), + date.hashCode), + bankAccountId + .hashCode), + description.hashCode), + statusId.hashCode), + invoiceIds.hashCode), + expenseId.hashCode), + transactionId.hashCode), isChanged.hashCode), createdAt.hashCode), updatedAt.hashCode), @@ -708,6 +724,7 @@ class _$TransactionEntity extends TransactionEntity { ..add('statusId', statusId) ..add('invoiceIds', invoiceIds) ..add('expenseId', expenseId) + ..add('transactionId', transactionId) ..add('isChanged', isChanged) ..add('createdAt', createdAt) ..add('updatedAt', updatedAt) @@ -765,6 +782,10 @@ class TransactionEntityBuilder String get expenseId => _$this._expenseId; set expenseId(String expenseId) => _$this._expenseId = expenseId; + int _transactionId; + int get transactionId => _$this._transactionId; + set transactionId(int transactionId) => _$this._transactionId = transactionId; + bool _isChanged; bool get isChanged => _$this._isChanged; set isChanged(bool isChanged) => _$this._isChanged = isChanged; @@ -816,6 +837,7 @@ class TransactionEntityBuilder _statusId = $v.statusId; _invoiceIds = $v.invoiceIds; _expenseId = $v.expenseId; + _transactionId = $v.transactionId; _isChanged = $v.isChanged; _createdAt = $v.createdAt; _updatedAt = $v.updatedAt; @@ -863,6 +885,7 @@ class TransactionEntityBuilder invoiceIds: BuiltValueNullFieldError.checkNotNull( invoiceIds, 'TransactionEntity', 'invoiceIds'), expenseId: BuiltValueNullFieldError.checkNotNull(expenseId, 'TransactionEntity', 'expenseId'), + transactionId: BuiltValueNullFieldError.checkNotNull(transactionId, 'TransactionEntity', 'transactionId'), isChanged: isChanged, createdAt: BuiltValueNullFieldError.checkNotNull(createdAt, 'TransactionEntity', 'createdAt'), updatedAt: BuiltValueNullFieldError.checkNotNull(updatedAt, 'TransactionEntity', 'updatedAt'), diff --git a/lib/ui/transaction/view/transaction_view.dart b/lib/ui/transaction/view/transaction_view.dart index 31f626bdc..2d804f017 100644 --- a/lib/ui/transaction/view/transaction_view.dart +++ b/lib/ui/transaction/view/transaction_view.dart @@ -10,6 +10,7 @@ import 'package:invoiceninja_flutter/ui/app/search_text.dart'; import 'package:invoiceninja_flutter/ui/invoice/invoice_list_item.dart'; import 'package:invoiceninja_flutter/ui/transaction/view/transaction_view_vm.dart'; import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart'; +import 'package:invoiceninja_flutter/ui/vendor/vendor_list_item.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/icons.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; @@ -162,6 +163,18 @@ class _MatchDepositsState extends State<_MatchDeposits> { }); } + bool get isFiltered { + if (_minAmount.isNotEmpty || _maxAmount.isNotEmpty) { + return true; + } + + if (_startDate.isNotEmpty || _endDate.isNotEmpty) { + return true; + } + + return false; + } + @override void dispose() { _filterController.dispose(); @@ -218,7 +231,7 @@ class _MatchDepositsState extends State<_MatchDeposits> { onPressed: () { setState(() => _showFilter = !_showFilter); }, - color: _showFilter ? state.accentColor : null, + color: _showFilter || isFiltered ? state.accentColor : null, icon: Icon(Icons.filter_alt), tooltip: state.prefState.enableTooltips ? localization.filter : '', @@ -312,6 +325,7 @@ class _MatchDepositsState extends State<_MatchDeposits> { final invoice = _invoices[index]; return InvoiceListItem( invoice: invoice, + filter: _filterController.text, showCheck: true, isChecked: _selectedInvoices.contains(invoice), onTap: () => setState(() { @@ -361,3 +375,161 @@ class _MatchDepositsState extends State<_MatchDeposits> { ); } } + +class _MatchWithdrawals extends StatefulWidget { + const _MatchWithdrawals({ + Key key, + @required this.viewModel, + }) : super(key: key); + + final TransactionViewVM viewModel; + + @override + State<_MatchWithdrawals> createState() => _MatchWithdrawalsState(); +} + +class _MatchWithdrawalsState extends State<_MatchWithdrawals> { + TextEditingController _filterController; + FocusNode _focusNode; + List _vendors; + List _categories; + VendorEntity _selectedVendor; + ExpenseCategoryEntity _selectedCategory; + + @override + void initState() { + super.initState(); + _filterController = TextEditingController(); + _focusNode = FocusNode(); + + updateVendorList(); + } + + void updateVendorList() { + final state = widget.viewModel.state; + final vendorState = state.vendorState; + + _vendors = vendorState.map.values.where((vendor) { + if (_selectedVendor != null) { + if (vendor.id != _selectedVendor.id) { + return false; + } + } + + if (vendor.isDeleted) { + return false; + } + + final filter = _filterController.text; + + if (filter.isNotEmpty) { + if (!vendor.matchesFilter(filter)) { + return false; + } + } + + return true; + }).toList(); + _vendors.sort((vendorA, vendorB) { + /* + if (_selectedInvoices.contains(invoiceA)) { + return -1; + } else if (_selectedInvoices.contains(invoiceB)) { + return 1; + } + */ + + return vendorB.name.toLowerCase().compareTo(vendorA.name.toLowerCase()); + }); + } + + @override + void dispose() { + _filterController.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + final viewModel = widget.viewModel; + final transaction = viewModel.transaction; + final state = viewModel.state; + + return Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 18, top: 12, right: 10, bottom: 12), + child: SearchText( + filterController: _filterController, + focusNode: _focusNode, + onChanged: (value) { + setState(() { + updateVendorList(); + }); + }, + onCleared: () { + setState(() { + _filterController.text = ''; + updateVendorList(); + }); + }, + placeholder: localization.search, + ), + ), + ), + ], + ), + ListDivider(), + Expanded( + child: ListView.separated( + separatorBuilder: (context, index) => ListDivider(), + itemCount: _vendors.length, + itemBuilder: (BuildContext context, int index) { + final vendor = _vendors[index]; + return VendorListItem( + vendor: vendor, + filter: _filterController.text, + showCheck: true, + isChecked: _selectedVendor.id == vendor.id, + onTap: () => setState(() { + _selectedVendor = vendor; + updateVendorList(); + }), + ); + }, + ), + ), + ListDivider(), + Padding( + padding: const EdgeInsets.only( + left: 20, + bottom: 18, + right: 20, + ), + child: AppButton( + label: localization.convertToPayment, + onPressed: _selectedVendor == null + ? null + : () { + final viewModel = widget.viewModel; + viewModel.onConvertToExpense( + context, + transaction.id, + _selectedVendor.id, + ); + }, + iconData: getEntityActionIcon(EntityAction.convertToExpense), + ), + ) + ], + ); + } +} diff --git a/lib/ui/vendor/vendor_list_item.dart b/lib/ui/vendor/vendor_list_item.dart index a62113eab..d99c91bda 100644 --- a/lib/ui/vendor/vendor_list_item.dart +++ b/lib/ui/vendor/vendor_list_item.dart @@ -16,22 +16,22 @@ import 'package:invoiceninja_flutter/utils/platforms.dart'; class VendorListItem extends StatelessWidget { const VendorListItem({ - @required this.user, @required this.vendor, @required this.filter, this.onTap, this.onLongPress, + this.showCheck = false, this.onCheckboxChanged, this.isChecked = false, }); - final UserEntity user; final GestureTapCallback onTap; final GestureTapCallback onLongPress; final VendorEntity vendor; final String filter; final Function(bool) onCheckboxChanged; final bool isChecked; + final bool showCheck; @override Widget build(BuildContext context) { @@ -43,18 +43,18 @@ class VendorListItem extends StatelessWidget { ? vendor.matchesFilterValue(filter) : null; final listUIState = vendorUIState.listUIState; - final isInMultiselect = listUIState.isInMultiselect(); - final showCheckbox = onCheckboxChanged != null || isInMultiselect; final textStyle = TextStyle(fontSize: 16); final textColor = Theme.of(context).textTheme.bodyText1.color; final documents = vendor.documents ?? []; return DismissibleEntity( isSelected: isDesktop(context) && + !showCheck && vendor.id == (uiState.isEditing ? vendorUIState.editing.id : vendorUIState.selectedId), + showCheckbox: showCheck, userCompany: store.state.userCompany, entity: vendor, child: LayoutBuilder( @@ -77,11 +77,10 @@ class VendorListItem extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(right: 16), - child: showCheckbox + child: showCheck ? Padding( padding: const EdgeInsets.only(right: 20), child: IgnorePointer( - ignoring: listUIState.isInMultiselect(), child: Checkbox( value: isChecked, materialTapTargetSize: @@ -160,7 +159,7 @@ class VendorListItem extends StatelessWidget { onLongPress: () => onLongPress != null ? onLongPress() : selectEntity(entity: vendor, longPress: true), - leading: showCheckbox + leading: showCheck ? IgnorePointer( ignoring: listUIState.isInMultiselect(), child: Checkbox( diff --git a/lib/ui/vendor/vendor_list_vm.dart b/lib/ui/vendor/vendor_list_vm.dart index 6ab77321b..492f4b6e2 100644 --- a/lib/ui/vendor/vendor_list_vm.dart +++ b/lib/ui/vendor/vendor_list_vm.dart @@ -45,13 +45,12 @@ class VendorListBuilder extends StatelessWidget { final vendor = viewModel.vendorMap[vendorId]; final state = viewModel.state; final listUIState = state.getListState(EntityType.vendor); - final isInMultiselect = listUIState.isInMultiselect(); return VendorListItem( - user: viewModel.state.user, filter: viewModel.filter, vendor: vendor, - isChecked: isInMultiselect && listUIState.isSelected(vendor.id), + isChecked: listUIState.isSelected(vendor.id), + showCheck: listUIState.isInMultiselect(), ); }, );