Recurring invoices

This commit is contained in:
Hillel Coren 2020-09-14 09:24:41 +03:00
parent 8ddc039897
commit 27f142e75d
4 changed files with 161 additions and 57 deletions

View File

@ -146,6 +146,14 @@ List<String> filteredInvoicesSelector(
!invoiceListState.custom2Filters.contains(invoice.customValue2)) { !invoiceListState.custom2Filters.contains(invoice.customValue2)) {
return false; return false;
} }
if (invoiceListState.custom3Filters.isNotEmpty &&
!invoiceListState.custom3Filters.contains(invoice.customValue3)) {
return false;
}
if (invoiceListState.custom4Filters.isNotEmpty &&
!invoiceListState.custom4Filters.contains(invoice.customValue4)) {
return false;
}
return true; return true;
}).toList(); }).toList();

View File

@ -32,31 +32,55 @@ List<String> filteredRecurringInvoicesSelector(
BuiltMap<String, InvoiceEntity> recurringInvoiceMap, BuiltMap<String, InvoiceEntity> recurringInvoiceMap,
BuiltMap<String, ClientEntity> clientMap, BuiltMap<String, ClientEntity> clientMap,
BuiltList<String> recurringInvoiceList, BuiltList<String> recurringInvoiceList,
ListUIState recurringInvoiceListState, ListUIState invoiceListState,
StaticState staticState, StaticState staticState,
BuiltMap<String, UserEntity> userMap, BuiltMap<String, UserEntity> userMap,
) { ) {
final list = recurringInvoiceList.where((recurringInvoiceId) { final list = recurringInvoiceList.where((recurringInvoiceId) {
final recurringInvoice = recurringInvoiceMap[recurringInvoiceId]; final invoice = recurringInvoiceMap[recurringInvoiceId];
if (filterEntityId != null && recurringInvoice.id != filterEntityId) { final client =
return false; clientMap[invoice.clientId] ?? ClientEntity(id: invoice.clientId);
} else {}
if (!recurringInvoice if (!client.isActive &&
.matchesStates(recurringInvoiceListState.stateFilters)) { !client.matchesEntityFilter(filterEntityType, filterEntityId)) {
return false; return false;
} }
if (recurringInvoiceListState.custom1Filters.isNotEmpty &&
!recurringInvoiceListState.custom1Filters if (filterEntityType == EntityType.client && client.id != filterEntityId) {
.contains(recurringInvoice.customValue1)) { return false;
} else if (filterEntityType == EntityType.user &&
invoice.assignedUserId != filterEntityId) {
return false; return false;
} }
if (recurringInvoiceListState.custom2Filters.isNotEmpty &&
!recurringInvoiceListState.custom2Filters if (!invoice.matchesStates(invoiceListState.stateFilters)) {
.contains(recurringInvoice.customValue2)) {
return false; return false;
} }
return recurringInvoice.matchesFilter(recurringInvoiceListState.filter); if (!invoice.matchesStatuses(invoiceListState.statusFilters)) {
return false;
}
if (!invoice.matchesFilter(invoiceListState.filter) &&
!client.matchesFilter(invoiceListState.filter)) {
return false;
}
if (invoiceListState.custom1Filters.isNotEmpty &&
!invoiceListState.custom1Filters.contains(invoice.customValue1)) {
return false;
}
if (invoiceListState.custom2Filters.isNotEmpty &&
!invoiceListState.custom2Filters.contains(invoice.customValue2)) {
return false;
}
if (invoiceListState.custom3Filters.isNotEmpty &&
!invoiceListState.custom3Filters.contains(invoice.customValue3)) {
return false;
}
if (invoiceListState.custom4Filters.isNotEmpty &&
!invoiceListState.custom4Filters.contains(invoice.customValue4)) {
return false;
}
return true;
}).toList(); }).toList();
list.sort((recurringInvoiceAId, recurringInvoiceBId) { list.sort((recurringInvoiceAId, recurringInvoiceBId) {
@ -65,8 +89,8 @@ List<String> filteredRecurringInvoicesSelector(
return recurringInvoiceA.compareTo( return recurringInvoiceA.compareTo(
invoice: recurringInvoiceB, invoice: recurringInvoiceB,
sortField: recurringInvoiceListState.sortField, sortField: invoiceListState.sortField,
sortAscending: recurringInvoiceListState.sortAscending, sortAscending: invoiceListState.sortAscending,
clientMap: clientMap, clientMap: clientMap,
staticState: staticState, staticState: staticState,
userMap: userMap, userMap: userMap,
@ -76,8 +100,52 @@ List<String> filteredRecurringInvoicesSelector(
return list; return list;
} }
var memoizedRecurringInvoiceStatsForClient = memo2(
(String clientId, BuiltMap<String, InvoiceEntity> invoiceMap) =>
recurringInvoiceStatsForClient(clientId, invoiceMap));
EntityStats recurringInvoiceStatsForClient(
String clientId, BuiltMap<String, InvoiceEntity> invoiceMap) {
int countActive = 0;
int countArchived = 0;
invoiceMap.forEach((invoiceId, invoice) {
if (invoice.clientId == clientId) {
if (invoice.isActive) {
countActive++;
} else if (invoice.isArchived) {
countArchived++;
}
}
});
return EntityStats(countActive: countActive, countArchived: countArchived);
}
var memoizedRecurringInvoiceStatsForUser = memo2(
(String userId, BuiltMap<String, InvoiceEntity> invoiceMap) =>
recurringInvoiceStatsForUser(userId, invoiceMap));
EntityStats recurringInvoiceStatsForUser(
String userId, BuiltMap<String, InvoiceEntity> invoiceMap) {
int countActive = 0;
int countArchived = 0;
invoiceMap.forEach((invoiceId, invoice) {
if (invoice.assignedUserId == userId) {
if (invoice.isActive) {
countActive++;
} else if (invoice.isDeleted) {
countArchived++;
}
}
});
return EntityStats(countActive: countActive, countArchived: countArchived);
}
bool hasRecurringInvoiceChanges(InvoiceEntity recurringInvoice, bool hasRecurringInvoiceChanges(InvoiceEntity recurringInvoice,
BuiltMap<String, InvoiceEntity> recurringInvoiceMap) => BuiltMap<String, InvoiceEntity> recurringInvoiceMap) =>
recurringInvoice.isNew recurringInvoice.isNew
? recurringInvoice.isChanged ? recurringInvoice.isChanged
: recurringInvoice != recurringInvoiceMap[recurringInvoice.id]; : recurringInvoice != recurringInvoiceMap[recurringInvoice.id];

View File

@ -10,6 +10,7 @@ import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart'; import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart';
import 'package:invoiceninja_flutter/redux/project/project_selectors.dart'; import 'package:invoiceninja_flutter/redux/project/project_selectors.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart';
import 'package:invoiceninja_flutter/redux/task/task_selectors.dart'; import 'package:invoiceninja_flutter/redux/task/task_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/FieldGrid.dart'; import 'package:invoiceninja_flutter/ui/app/FieldGrid.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_list_tile.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_list_tile.dart';
@ -176,6 +177,19 @@ class ClientOverview extends StatelessWidget {
memoizedInvoiceStatsForClient(client.id, state.invoiceState.map) memoizedInvoiceStatsForClient(client.id, state.invoiceState.map)
.present(localization.active, localization.archived), .present(localization.active, localization.archived),
), ),
if (company.isModuleEnabled(EntityType.recurringInvoice))
EntitiesListTile(
isFilter: isFilter,
entityType: EntityType.recurringInvoice,
title: localization.recurringInvoices,
onTap: () =>
viewModel.onEntityPressed(context, EntityType.recurringInvoice),
onLongPress: () => viewModel.onEntityPressed(
context, EntityType.recurringInvoice, true),
subtitle: memoizedRecurringInvoiceStatsForClient(
client.id, state.recurringInvoiceState.map)
.present(localization.active, localization.archived),
),
if (company.isModuleEnabled(EntityType.payment)) if (company.isModuleEnabled(EntityType.payment))
EntitiesListTile( EntitiesListTile(
isFilter: isFilter, isFilter: isFilter,

View File

@ -4,6 +4,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/credit/credit_selectors.dart'; import 'package:invoiceninja_flutter/redux/credit/credit_selectors.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_selectors.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart'; import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_list_tile.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_list_tile.dart';
import 'package:invoiceninja_flutter/ui/app/entity_header.dart'; import 'package:invoiceninja_flutter/ui/app/entity_header.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart'; import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
@ -44,47 +45,60 @@ class UserView extends StatelessWidget {
secondValue: user.phone, secondValue: user.phone,
), ),
ListDivider(), ListDivider(),
if (userCompany.canViewOrCreate(EntityType.invoice))
EntitiesListTile( EntitiesListTile(
isFilter: isFilter, isFilter: isFilter,
title: localization.invoices, title: localization.invoices,
entityType: EntityType.invoice, entityType: EntityType.invoice,
onTap: () => viewModel.onEntityPressed(context, EntityType.invoice), onTap: () =>
viewModel.onEntityPressed(context, EntityType.invoice),
onLongPress: () => onLongPress: () =>
viewModel.onEntityPressed(context, EntityType.invoice, true), viewModel.onEntityPressed(context, EntityType.invoice, true),
subtitle: subtitle:
memoizedInvoiceStatsForUser(user.id, state.invoiceState.map) memoizedInvoiceStatsForUser(user.id, state.invoiceState.map)
.present(localization.active, localization.archived), .present(localization.active, localization.archived),
), ),
userCompany.canViewOrCreate(EntityType.quote) if (userCompany.canViewOrCreate(EntityType.recurringInvoice))
? EntitiesListTile( EntitiesListTile(
isFilter: isFilter,
title: localization.recurringInvoices,
entityType: EntityType.recurringInvoice,
onTap: () => viewModel.onEntityPressed(
context, EntityType.recurringInvoice),
onLongPress: () => viewModel.onEntityPressed(
context, EntityType.recurringInvoice, true),
subtitle: memoizedRecurringInvoiceStatsForUser(
user.id, state.recurringInvoiceState.map)
.present(localization.active, localization.archived),
),
if (userCompany.canViewOrCreate(EntityType.quote))
EntitiesListTile(
isFilter: isFilter, isFilter: isFilter,
entityType: EntityType.quote, entityType: EntityType.quote,
title: localization.quotes, title: localization.quotes,
onTap: () => onTap: () => viewModel.onEntityPressed(context, EntityType.quote),
viewModel.onEntityPressed(context, EntityType.quote), onLongPress: () =>
onLongPress: () => viewModel.onEntityPressed( viewModel.onEntityPressed(context, EntityType.quote, true),
context, EntityType.quote, true),
subtitle: memoizedQuoteStatsForUser( subtitle: memoizedQuoteStatsForUser(
user.id, user.id,
state.quoteState.map, state.quoteState.map,
).present(localization.active, localization.archived), ).present(localization.active, localization.archived),
) ),
: Container(), if (userCompany.canViewOrCreate(EntityType.credit))
userCompany.canViewOrCreate(EntityType.credit) EntitiesListTile(
? EntitiesListTile(
isFilter: isFilter, isFilter: isFilter,
entityType: EntityType.credit, entityType: EntityType.credit,
title: localization.credits, title: localization.credits,
onTap: () => onTap: () =>
viewModel.onEntityPressed(context, EntityType.credit), viewModel.onEntityPressed(context, EntityType.credit),
onLongPress: () => viewModel.onEntityPressed( onLongPress: () =>
context, EntityType.credit, true), viewModel.onEntityPressed(context, EntityType.credit, true),
subtitle: memoizedCreditStatsForUser( subtitle: memoizedCreditStatsForUser(
user.id, user.id,
state.creditState.map, state.creditState.map,
).present(localization.active, localization.archived), ).present(localization.active, localization.archived),
) ),
: Container(),
/* /*
userCompany.canViewOrCreate(EntityType.project) userCompany.canViewOrCreate(EntityType.project)
? EntityListTile( ? EntityListTile(